simvx.graphics.renderer.gpu_batch

GPU-driven batch rendering with multi-draw indirect (MDI) and fallback.

Module Contents

Classes

GPUBatch

Manages batched draw commands.

Data

API

simvx.graphics.renderer.gpu_batch.log

‘getLogger(…)’

simvx.graphics.renderer.gpu_batch.__all__

[‘GPUBatch’]

class simvx.graphics.renderer.gpu_batch.GPUBatch(engine: Any, device: Any, physical_device: Any, max_draws: int = 1000, *, use_mdi: bool = True, concurrent_families: list[int] | None = None)

Manages batched draw commands.

When use_mdi=True (default), uses vkCmdDrawIndexedIndirect to issue all draw commands in a single GPU call. When use_mdi=False, falls back to a loop of vkCmdDrawIndexed calls: functionally identical but slower on GPUs that support MDI.

Usage::

batch = GPUBatch(engine, device, physical_device, max_draws=100)
batch.add_draw(index_count=36, first_instance=0)
batch.upload()
batch.on_draw(cmd)

Initialization

property indirect_buffer: Any
property indirect_memory: Any
add_draw(index_count: int, instance_count: int = 1, first_index: int = 0, vertex_offset: int = 0, first_instance: int = 0) int

Add a draw command. Returns the draw index.

add_draws(index_count: int, first_instances: numpy.ndarray | list[int]) int

Bulk-add draw commands sharing the same mesh: avoids per-instance Python loop.

Args: index_count: Index count for the mesh (same for all draws). first_instances: (N,) array of SSBO instance indices.

Returns: Batch offset of the first added draw command.

add_instanced_runs(index_count: int, slots: numpy.ndarray | list[int]) tuple[int, int]

Add draws for SSBO slots, coalescing contiguous slots into instanced draws.

Each maximal run of consecutive slots becomes ONE indirect command with instance_count = run length and first_instance = run start. The vertex shader reads transforms[gl_InstanceIndex] where gl_InstanceIndex = gl_InstanceID + first_instance, so a run of contiguous slots is drawn correctly by a single instanced command. A MultiMesh (whose N instances occupy N consecutive slots) thus collapses to a single draw.

Returns (batch_offset, command_count) where command_count is the number of runs (== number of indirect commands added), for draw_range.

Must NOT be used for the occlusion-cull batch: that path needs one command per instance so the cull compute can zero instance_count per object. Use :meth:add_draws there.

upload() None

Upload draw commands to GPU indirect buffer.

draw(cmd: Any) None

Record draw commands for the entire batch.

draw_range(cmd: Any, offset: int, count: int) None

Draw a sub-range of commands.

Args: cmd: Vulkan command buffer offset: First draw command index (not byte offset) count: Number of draw commands to execute

reset() None

Clear the batch for the next frame.

grow(physical_device: Any, max_draws: int) None

Reallocate the indirect buffer to hold at least max_draws commands.

A no-op when max_draws does not exceed the current capacity. Used when the transform-SSBO arena grows so the indirect batch can keep pace. The old buffers are retired (freed once the frames that could name them have completed) rather than freed immediately, and draw_count is reset (callers rebuild after an arena grow).

destroy() None

Free GPU resources.

Buffers already handed to engine.retire are not this method’s to free: Engine._destroy_device_objects drains the queue before it reaches the pass teardown that lands here.