simvx.graphics.renderer.light_cull_pass¶
Forward+ tiled light culling via compute shader.
Dispatches a compute shader that assigns lights to 16x16 screen-space tiles using depth-aware frustum culling. The output buffers (light index list and per-tile offset/count) are bound to the fragment shader so it can loop over only the lights relevant to each pixel’s tile.
Every buffer here is per-frame by construction: the params block carries an
atomic write head that the host resets on each dispatch, and the two outputs are
written by compute and read by the next pass’s fragment stage. So all three are
ringed to :data:FRAMES_IN_FLIGHT, along with the descriptor set that points at
them, and a dispatch takes its slot from engine.current_frame. Without the
ring, frame N’s host reset of the atomic would race frame N-1’s atomicAdd and
frame N’s compute would overwrite the tile lists frame N-1 is still shading from.
This is a plain object rather than a RenderPass: the dispatch is recorded by
an explicit call, the way the ocean and particle compute already are, and the
setup signature takes an extent rather than a render context.
- class:
~simvx.graphics.renderer.forward.Rendererowns it. It builds the pass on the first frametiled_lighting_enabledis armed and the light count clears- data:
TILED_LIGHTING_MIN_LIGHTS, records the dispatch inpre_renderbefore the shadow passes, and points forward-set bindings 5 and 6 at- attr:
~LightCullPass.light_index_buffersand :attr:~LightCullPass.tile_buffers.
Module Contents¶
Classes¶
GPU tiled light culling for Forward+ rendering. |
Data¶
API¶
- simvx.graphics.renderer.light_cull_pass.__all__¶
[‘MAX_LIGHTS_PER_TILE’, ‘TILED_LIGHTING_MIN_LIGHTS’, ‘LightCullPass’]
- simvx.graphics.renderer.light_cull_pass.log¶
‘getLogger(…)’
- simvx.graphics.renderer.light_cull_pass.TILED_LIGHTING_MIN_LIGHTS¶
32
- simvx.graphics.renderer.light_cull_pass.MAX_LIGHTS_PER_TILE¶
256
- class simvx.graphics.renderer.light_cull_pass.LightCullPass(engine: Any)¶
GPU tiled light culling for Forward+ rendering.
Creates a compute pipeline that reads a depth texture and light SSBO, then outputs per-tile light index lists consumed by the fragment shader. The params block and both outputs are ringed one copy per frame in flight, with one descriptor set per copy;
dispatchselects the slot.Initialization
- setup(width: int, height: int, max_lights: int = MAX_LIGHTS_PER_TILE) None¶
Create the compute pipeline, the ringed SSBOs and their descriptor sets.
Args: width: Viewport width in pixels. height: Viewport height in pixels. max_lights: Maximum number of lights in the scene. Retained, so a later
resizerebuilds at the same capacity.
- update_descriptors(depth_view: Any, light_ssbos: collections.abc.Sequence[Any], light_ssbo_size: int) None¶
Point every ring slot’s set at the depth texture (1) and its light SSBO (2).
The depth texture is one image every slot samples. The light SSBO is NOT: it is ringed across
FRAMES_IN_FLIGHT, so slot i’s set must take slot i’s buffer, which is why this takes a sequence rather than a handle. Binding one buffer into every set would make the tile cull read a different frame’s lights than the forward pass shades with, on every frame the ring is not on that slot, and nothing downstream would say so.The arguments are retained and replayed by
setup, so aresizethat rebuilds the sets does not silently drop them.
- dispatch(cmd: Any, view_mat: numpy.ndarray, proj_mat: numpy.ndarray, light_count: int, near: float, far: float, depth_valid: bool = False) None¶
Record the light culling compute dispatch into a command buffer.
Must be called outside a render pass, after depth is available and before the main geometry pass that reads the tile data.
Args: cmd: Command buffer to record into. view_mat: Camera view matrix, row-major. proj_mat: Camera projection matrix, row-major. Inverted here and pushed inverted, which is the only form the shader uses. light_count: Number of lights in the light SSBO. near: Camera near plane distance. far: Camera far plane distance. depth_valid: True only when binding 1 carries this frame’s scene depth. While it is a placeholder view the shader must not reduce it, so the tile frusta span the full near-to-far slab and the cull is purely screen-space.
- property ready: bool¶
- property tile_buffers: list[Any]¶
Tile SSBO handles, one per frame in flight, in ring-slot order.
The forward set for slot
imust be pointed at elementi, or a frame shades from the tile lists another frame is writing.
- property tile_buffer_size: int¶
- property light_index_buffers: list[Any]¶
Light index SSBO handles, one per frame in flight, in ring-slot order.
- property light_index_buffer_size: int¶
- property max_lights: int¶
Per-tile light capacity this pass was set up with.
- property grid_dims: tuple[int, int]¶
- resize(width: int, height: int) None¶
Rebuild at a new extent, keeping the capacity
setupwas given.A no-op when the tile grid is unchanged, so a resize within one tile of the same grid costs nothing. The caller must repoint whatever holds
- Attr:
tile_buffers/ :attr:light_index_buffers: the rebuild allocates new handles.
- destroy() None¶
Destroy all GPU resources and drop every handle.
Every handle is cleared rather than left dangling, so a destroy followed by an inspection (or by a second destroy) cannot read freed memory.