simvx.graphics.renderer.decal_pack¶
Shared decal wire packing (design D9, RM-E5).
Dependency-free (numpy only, no Vulkan) so BOTH the desktop forward pass
(forward.py -> the DecalBuffer SSBO at set0 binding 19) and, one step
behind, the browser exporter (RM-E5w, WebGPU under Pyodide where vulkan is
unavailable) pack a :class:simvx.core.decal.Decal3D into the identical std430
record. One canonical layout, mirrored by the Decal struct in
cube_textured.frag (desktop) and, later, forward3d.wgsl (web).
A decal projects its albedo (and an optional normal map) onto the geometry
inside an oriented box, along the box’s local -Y axis. world_to_local is the
inverse of the box’s world TRS (scaled by the half-extents), so a world position
inside the box maps into the cube [-1, 1]^3; the uber loop reads the thin
G-buffer world position (design A9/D3), tests the box, and composites the decal
over the material albedo (design D9). A scene with no Decal3D keeps
decal_count at 0 and the loop never runs, so the frame is byte-identical
(zero cost when unused, the epic backbone).
Module Contents¶
Functions¶
Pack one decal into a :data: |
|
Build the per-screen-tile decal bitmask for the standalone cull grid (RM-G8). |
|
Assemble the full |
Data¶
API¶
- simvx.graphics.renderer.decal_pack.__all__¶
[‘MAX_DECALS’, ‘DECAL_DTYPE’, ‘DECAL_STRIDE’, ‘DECAL_HEADER_SIZE’, ‘DECAL_BUFFER_SIZE’, ‘DECAL_TILE_…
- simvx.graphics.renderer.decal_pack.MAX_DECALS¶
64
- simvx.graphics.renderer.decal_pack.DECAL_DTYPE¶
‘dtype(…)’
- simvx.graphics.renderer.decal_pack.DECAL_STRIDE¶
None
- simvx.graphics.renderer.decal_pack.DECAL_HEADER_SIZE¶
16
- simvx.graphics.renderer.decal_pack.DECAL_BUFFER_SIZE¶
None
- simvx.graphics.renderer.decal_pack.DECAL_TILE_PX¶
16
- simvx.graphics.renderer.decal_pack.pack_decal(model: numpy.ndarray, modulate: Any, albedo_mix: float, albedo_tex: int, normal_tex: int, normal_mix: float, upper_fade: float, lower_fade: float, dist_begin: float, dist_length: float, cull_mask: int = 4294967295) numpy.ndarray[source]¶
Pack one decal into a :data:
DECAL_DTYPErecord.modelis the box world TRS (row-major, translate * rotate * scale where scale is the world-space half-extents); it is inverted and transposed to the column-majorworld_to_localthe shader multiplies the world position by.albedo_tex/normal_texare bindless texture indices (-1 = none).cull_maskis the 32-bit render-layer bitmask (design D9/RM-G8): a fragment only receives the decal when its instancerender_layerintersects this mask. It is stored bit-exact intex_params.w(read back withfloatBitsToUint); the default0xFFFFFFFFmatches every layer, so an unmasked decal is byte-identical to the pre-G8 record.
- simvx.graphics.renderer.decal_pack.compute_decal_tile_masks(models: list[numpy.ndarray], view: numpy.ndarray, proj: numpy.ndarray, width: int, height: int, tile_px: int = DECAL_TILE_PX) tuple[int, int, numpy.ndarray][source]¶
Build the per-screen-tile decal bitmask for the standalone cull grid (RM-G8).
Each of the (up to :data:
MAX_DECALS)modelsis the box world TRS whose 8 corners are projected withproj @ viewinto thewidthxheightgl_FragCoordspace; the conservative screen AABB (padded one tile on every side, and widened to the whole grid for a projector that crosses the near plane) sets the decal’s bit in every tile it can touch. The mask is a 64-bit field per tile (twouint32lanes), so the shader iterates only the decals whose bit is set for its tile. Because the coverage is always a superset of the real box footprint, the tiled loop composites the exact same decals a brute-force loop would: byte-identical output, just fewer per-fragment tests.Returns
(grid_x, grid_y, masks)wheremasksis a(grid_x*grid_y, 2)uint32array in row-major tile order (tile = ty * grid_x + tx). Pure numpy so the web twin (RM-G8w) can mirror it verbatim in JS.
- simvx.graphics.renderer.decal_pack.build_decal_buffer(records: list[numpy.ndarray], *, tile_masks: numpy.ndarray | None = None, grid_x: int = 0, grid_y: int = 0, tile_px: int = 0) numpy.ndarray[source]¶
Assemble the full
DecalBufferbytes (16-byte header + record array).The record region is always the fixed :data:
MAX_DECALSslots (the shader declaresDecal decals[MAX_DECALS]so the trailing tile-mask array has a fixed offset); only the firstdecal_countare populated. A zeroed header (no records) is byte-identical to the never-baked default buffer.When
tile_masksis supplied (desktop screen-tile cull, RM-G8) the(grid_x, grid_y, tile_px)header words are set and theuint32mask lanes are appended after the record region as the shader’stile_mask[]runtime array. Called without it (the web twin, one step behind) the header tiling words stay 0 and the buffer is exactly the pre-G8 wire, so the brute-force shader path runs and nothing changes.