DirectX 12 ExecuteIndirect and GPU-Driven Rendering
DirectX 12 introduces powerful execution indirect capabilities
through the ExecuteIndirect API, which shifts the
responsibility of generating and issuing rendering commands from the CPU
to the GPU. Traditionally, the CPU evaluates scene data and submits
every draw or dispatch call individually, creating an architectural
bottleneck in complex scenes. By allowing the GPU to dynamically
generate, filter, and execute its own draw commands via compute shaders
and indirect argument buffers, DirectX 12 fundamentally enables
scalable, GPU-driven rendering pipelines that drastically minimize CPU
overhead.
Understanding ExecuteIndirect in DirectX 12
In a standard rendering workflow, the CPU records individual commands
such as DrawInstanced, DrawIndexedInstanced,
or Dispatch into a command list. Each call requires
CPU-side preparation of state, resources, and parameters.
DirectX 12's ExecuteIndirect decouples command
generation from the CPU. Instead of executing hard-coded parameters,
ExecuteIndirect instructs the GPU to read its execution
arguments directly from a GPU-accessible buffer
(ID3D12Resource).
To define the layout of this buffer, DirectX 12 uses an
ID3D12CommandSignature. A command signature specifies what
data the indirect buffer contains, which can include:
- Draw or dispatch arguments (e.g., vertex count, instance count, start index).
- Vertex buffer and index buffer view bindings.
- Root constant or root descriptor table updates per draw call.
Because the buffer resides in GPU memory, the parameters defining what gets drawn—and how many instances are rendered—can be generated, modified, and consumed entirely on the GPU without round-tripping back to the CPU.
How ExecuteIndirect Enables GPU-Driven Rendering
GPU-driven rendering shifts scene traversal, visibility
determination, and command generation from the CPU onto compute shaders.
ExecuteIndirect acts as the critical bridge that transforms
raw GPU compute output into hardware draw calls.
1. GPU-Based Visibility and Culling
In a classic architecture, the CPU performs frustum and occlusion
culling to decide which meshes to submit. With
ExecuteIndirect, the entire object population resides in
structured buffers on the GPU. A compute shader runs culling passes—such
as bounding box frustum tests, depth pyramid occlusion culling, and
distance-based Level of Detail (LoD) selection—in parallel across
thousands of threads.
2. Dynamic Command Generation
Once the compute shader identifies visible instances:
- It writes the resulting draw arguments (e.g., setting the
InstanceCountto the number of visible instances) into an indirect argument buffer. - If an entire object group is culled, the compute shader simply
writes an
InstanceCountof0, effectively skipping the draw without CPU intervention. - The compute pass can also perform stream compaction to pack visible draw commands contiguously.
3. Execution via Multi-Draw Indirect
DirectX 12 supports issuing batches of commands in a single
ExecuteIndirect invocation. The CPU issues one command that
points to the indirect argument buffer, along with an optional count
buffer that tracks the total number of valid draws to execute. This
multi-draw indirect pattern allows a single API call to render thousands
of distinct meshes with varying parameters, completely eliminating CPU
draw-call bottlenecks.
Architectural Advantages
- Minimized CPU Overhead: The CPU handles high-level frame orchestration rather than traversing large scene graphs and recording thousands of individual state changes.
- Zero CPU-to-GPU Latency: Because visibility decisions occur on the GPU in the same frame, occlusion queries and culling results can be consumed instantly by rendering passes without CPU synchronization stalls.
- Massive Scene Scalability: Environments requiring millions of instances, such as dense foliage, procedural terrain, or dynamic particle systems, scale linearly with GPU compute performance rather than stalling on CPU single-thread limits.