What Are Indirect Draw Calls in DirectX?
Indirect draw calls are an advanced rendering technique where the arguments for a draw operation are sourced directly from GPU memory rather than passed from the CPU. In modern APIs like DirectX 11 and DirectX 12, this capability decouples the CPU from the frame-by-frame rendering workload, allowing compute shaders to dynamically generate geometry, perform culling, and dispatch draw calls entirely on the GPU. This article explains how indirect drawing works, the mechanics behind GPU-driven parameter generation in DirectX, and why this technique is fundamental to modern high-performance graphics engines.
Understanding Traditional vs. Indirect Draw Calls
In a traditional rendering pipeline (direct drawing), the CPU
determines the parameters for every object that needs to be rendered.
Using functions such as DrawInstanced or
DrawIndexedInstanced, the CPU specifies:
- The number of indices or vertices to draw.
- The number of instances to render.
- Start offsets for vertices, indices, and instances.
This model creates a CPU bottleneck when rendering scenes with tens of thousands of objects. If the CPU must test bounding boxes against the view frustum (frustum culling) or check if an object is hidden behind another (occlusion culling), it must read data, process the scene hierarchy, and submit individual draw commands. If the GPU performs this work instead, copying the visibility results back to the CPU to assemble draw calls introduces severe synchronization stalls and pipeline latency.
Indirect draw calls solve this problem by pointing the graphics
command list to a GPU buffer (such as an ID3D12Resource in
DirectX 12 or an ID3D11Buffer in DirectX 11) that contains
the exact draw arguments the GPU needs to execute the command.
How the GPU Populates Draw Parameters
The core benefit of indirect drawing is the ability for the GPU to update its own drawing parameters on the fly using Compute Shaders.
1. The Argument Buffer Structure
The parameters for an indirect call mirror the standard parameters
passed to CPU draw calls. For example, in DirectX 12, an indexed
indirect draw uses the D3D12_DRAW_INDEXED_ARGUMENTS
structure:
IndexCountPerInstance(UINT)InstanceCount(UINT)StartIndexLocation(UINT)BaseVertexLocation(INT)StartInstanceLocation(UINT)
This data is stored inside a readable and writable buffer (such as an
RWStructuredBuffer or RWByteAddressBuffer in
HLSL).
2. Compute Shader Execution and Culling
Instead of the CPU determining visibility, a compute pass runs prior to drawing:
- Frustum and Occlusion Culling: The compute shader receives a list of candidate object bounds. It evaluates whether each object is within the camera's view frustum and checks its depth against a hierarchical Z-buffer (HZB).
- Dynamic Instancing / Compaction: For all visible
objects, the compute shader increments an atomic counter using HLSL
atomic operations (like
InterlockedAdd). - Parameter Writing: The compute shader writes the
finalized instance count, or sets
IndexCountPerInstancedirectly to zero for culled objects, into the GPU-resident argument buffer.
Because these operations run massively in parallel across GPU execution units, thousands of visibility tests can be resolved in a fraction of a millisecond without any CPU intervention.
Indirect Execution in DirectX
The implementation details depend on the version of DirectX being targeted:
DirectX 11
DirectX 11 provides specialized API functions:
ID3D11DeviceContext::DrawInstancedIndirectID3D11DeviceContext::DrawIndexedInstancedIndirect
The developer binds the argument buffer created with the
D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS flag. The draw call
reads the structure at a specified byte offset within the buffer.
DirectX 12 and
ExecuteIndirect
DirectX 12 expands this concept dramatically through the
ID3D12GraphicsCommandList::ExecuteIndirect API and the
ID3D12CommandSignature interface.
Instead of being limited strictly to draw arguments, a DirectX 12 command signature allows a single indirect buffer to package multiple commands together, including:
- Draw or Dispatch parameters (
DrawInstanced,DrawIndexedInstanced,Dispatch). - Root constants (
SetGraphicsRoot32BitConstants). - Vertex and index buffer views (
SetVertexBuffers,SetIndexBuffer). - Descriptor table bindings.
Using ExecuteIndirect, the GPU can evaluate a scene,
dynamically select different meshes and materials by binding new
descriptor tables and vertex buffers, set instance counts, and dispatch
drawing commands—all completely independent of the CPU.
Key Advantages
- Elimination of CPU Overhead: The CPU only needs to
issue a single
ExecuteIndirectcommand rather than thousands of individual direct draw calls. - No Pipeline Stalls: Because data stays entirely in VRAM, there is no need for costly readbacks from the GPU to the CPU.
- Dynamic Level of Detail (LOD): Compute shaders can measure an object's distance from the camera and automatically populate the draw buffer with the correct mesh level of detail and index ranges.
- Scalable Particle and Physics Systems: Simulation data produced entirely on the GPU (such as particle positions and alive-particle counts) can be rendered directly by writing the active particle count to the indirect argument buffer.