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)[source]

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[source]
property indirect_memory: Any[source]
add_draw(index_count: int, instance_count: int = 1, first_index: int = 0, vertex_offset: int = 0, first_instance: int = 0) int[source]

Add a draw command. Returns the draw index.

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

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][source]

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[source]

Upload draw commands to GPU indirect buffer.

draw(cmd: Any) None[source]

Record draw commands for the entire batch.

draw_range(cmd: Any, offset: int, count: int) None[source]

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[source]

Clear batch for next frame and free buffers retired long enough ago.

grow(physical_device: Any, max_draws: int) None[source]

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 buffer is retired (freed after FRAMES_IN_FLIGHT resets) rather than freed immediately, and draw_count is reset (callers rebuild after an arena grow).

destroy() None[source]

Free GPU resources.