Instanced indirect rendering pipeline for SceneRenderer #10

Open
opened 2026-09-22 23:27:16 -07:00 by burkkyy · 0 comments
Owner

Summary

Replace the current per-entity draw loop in SceneRenderer with a GPU-driven instanced indirect rendering pipeline: batch entities by mesh, upload per-instance data (transform, color, etc.) to a buffer, and submit draws via vkCmdDrawIndexedIndirect / vkCmdDrawIndexedIndirectCount instead of one push-constant draw call per entity.

Motivation

SceneRenderer::render (engine/src/rendering/scene_renderer.cpp) is currently a stub with the intended-but-unimplemented approach of grouping entities by mesh and issuing one draw + push constant per transform:

for batch in Work Batch:
    batch.mesh.bind()
    for transform in batch.transforms:
        commandBuffer.pushConstants(transform)
        batch.mesh.draw(commandBuffer)

This doesn't scale — every instance is a separate draw call and CPU-side pushConstants update. Moving to indirect + instanced rendering lets the GPU batch identical-mesh draws together and cuts CPU submission overhead as scene entity counts grow.

Scope (rough, needs discovery)

  • Per-instance data buffer (SSBO) — transform, color, and whatever else replaces PushConstantData — populated per-frame from the ECS (entt) view of TransformComponent/mesh-owning entities.
  • VkDrawIndexedIndirectCommand buffer, one entry per unique mesh batch, built from grouping entities by MeshHandle.
  • Vertex shader changes to pull per-instance data via gl_InstanceIndex instead of push constants.
  • Batching/grouping strategy: build the mesh -> entity-list map each frame (as sketched in the current code comments) vs. maintaining it incrementally as components are added/removed.
  • Buffer lifecycle: sizing/growth strategy for the instance and indirect-command buffers (see Buffer/Device in engine/src/vulkan/), and whether this reuses ResourceRegistry or introduces new resource types.
  • Whether to target vkCmdDrawIndexedIndirect (loop of one call per mesh batch) or vkCmdDrawIndexedIndirectCount (single call, GPU-culled count) — likely worth starting with the former.

TODOs / open questions (discovery)

  • Confirm target Vulkan feature/extension requirements (drawIndirectFirstInstance, multiDrawIndirect availability on target hardware).
  • Decide where per-instance data gets written from (staging buffer upload each frame? persistent mapped buffer?).
  • Decide how mesh batches map to indirect command buffer entries, and how Mesh (engine/src/resources/mesh.hpp) exposes what's needed (index count, first index, vertex offset) to build a VkDrawIndexedIndirectCommand.
  • Define the instance data layout/shader interface (replacing PushConstantData in scene_renderer.cpp).
  • Sort out frustum/visibility culling — in scope now, or a later follow-up once indirect draws exist?
  • Update/replace the !TEMP to be decoupled once scene batching and rendering impl note on SceneRenderer once batching lands.
  • Benchmark against current stub/naive draw loop once both exist, to confirm the win.

Notes

This is a fairly large change touching SceneRenderer, Mesh, ResourceRegistry, and likely the Vulkan buffer/device wrappers — expect this to get broken into smaller follow-up issues once the approach is nailed down.

## Summary Replace the current per-entity draw loop in `SceneRenderer` with a GPU-driven **instanced indirect rendering** pipeline: batch entities by mesh, upload per-instance data (transform, color, etc.) to a buffer, and submit draws via `vkCmdDrawIndexedIndirect` / `vkCmdDrawIndexedIndirectCount` instead of one push-constant draw call per entity. ## Motivation `SceneRenderer::render` (`engine/src/rendering/scene_renderer.cpp`) is currently a stub with the intended-but-unimplemented approach of grouping entities by mesh and issuing one draw + push constant per transform: ```cpp for batch in Work Batch: batch.mesh.bind() for transform in batch.transforms: commandBuffer.pushConstants(transform) batch.mesh.draw(commandBuffer) ``` This doesn't scale — every instance is a separate draw call and CPU-side pushConstants update. Moving to indirect + instanced rendering lets the GPU batch identical-mesh draws together and cuts CPU submission overhead as scene entity counts grow. ## Scope (rough, needs discovery) - Per-instance data buffer (SSBO) — transform, color, and whatever else replaces `PushConstantData` — populated per-frame from the ECS (`entt`) view of `TransformComponent`/mesh-owning entities. - `VkDrawIndexedIndirectCommand` buffer, one entry per unique mesh batch, built from grouping entities by `MeshHandle`. - Vertex shader changes to pull per-instance data via `gl_InstanceIndex` instead of push constants. - Batching/grouping strategy: build the mesh -> entity-list map each frame (as sketched in the current code comments) vs. maintaining it incrementally as components are added/removed. - Buffer lifecycle: sizing/growth strategy for the instance and indirect-command buffers (see `Buffer`/`Device` in `engine/src/vulkan/`), and whether this reuses `ResourceRegistry` or introduces new resource types. - Whether to target `vkCmdDrawIndexedIndirect` (loop of one call per mesh batch) or `vkCmdDrawIndexedIndirectCount` (single call, GPU-culled count) — likely worth starting with the former. ## TODOs / open questions (discovery) - [ ] Confirm target Vulkan feature/extension requirements (`drawIndirectFirstInstance`, multiDrawIndirect availability on target hardware). - [ ] Decide where per-instance data gets written from (staging buffer upload each frame? persistent mapped buffer?). - [ ] Decide how mesh batches map to indirect command buffer entries, and how `Mesh` (`engine/src/resources/mesh.hpp`) exposes what's needed (index count, first index, vertex offset) to build a `VkDrawIndexedIndirectCommand`. - [ ] Define the instance data layout/shader interface (replacing `PushConstantData` in `scene_renderer.cpp`). - [ ] Sort out frustum/visibility culling — in scope now, or a later follow-up once indirect draws exist? - [ ] Update/replace the `!TEMP to be decoupled once scene batching and rendering impl` note on `SceneRenderer` once batching lands. - [ ] Benchmark against current stub/naive draw loop once both exist, to confirm the win. ## Notes This is a fairly large change touching `SceneRenderer`, `Mesh`, `ResourceRegistry`, and likely the Vulkan buffer/device wrappers — expect this to get broken into smaller follow-up issues once the approach is nailed down.
burkkyy added the enhancementdiscovery labels 2026-09-22 23:27:16 -07:00
burkkyy added this to the sophia project 2026-09-23 00:42:04 -07:00
burkkyy moved this to Blocked / Discussion in sophia on 2026-09-25 20:43:52 -07:00
Sign in to join this conversation.