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:

  1. 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).
  2. Dynamic Instancing / Compaction: For all visible objects, the compute shader increments an atomic counter using HLSL atomic operations (like InterlockedAdd).
  3. Parameter Writing: The compute shader writes the finalized instance count, or sets IndexCountPerInstance directly 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::DrawInstancedIndirect
  • ID3D11DeviceContext::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 ExecuteIndirect command 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.