How Conditional Rendering Works in DirectX 12

Conditional rendering in DirectX 12 enables the GPU to discard draw or dispatch calls dynamically based on the outcome of a prior GPU query, completely eliminating CPU-GPU synchronization bottlenecks. By using hardware predication through the ID3D12GraphicsCommandList::SetPredication API, the rendering pipeline evaluates visibility or execution conditions directly on the hardware. This approach allows developers to skip rendering complex geometry—such as objects hidden behind other geometry—without needing to stall the pipeline to read query results back to the CPU.

The Problem with Traditional Query-Based Culling

In standard occlusion culling pipelines, the CPU issues a simplified bounding-box draw call to test whether an object is visible via an occlusion query. To determine whether the full-detail mesh should be drawn, the CPU traditionally needs to read the query result from GPU memory. This round-trip latency introduces pipeline stalls, forcing the engine to either wait several frames to retrieve the data (introducing latency and visibility popping) or stall the CPU thread waiting for the GPU to finish execution.

The Mechanism of Predication

DirectX 12 solves this via predication, allowing commands recorded into a command list to be conditionally executed or bypassed by the GPU's Command Processor (CP) at runtime. The process functions in four distinct steps:

  1. Recording Queries: The application issues a lightweight draw call (such as a bounding box) surrounded by query markers using ID3D12GraphicsCommandList::BeginQuery and EndQuery with a query heap configured for D3D12_QUERY_TYPE_OCCLUSION or D3D12_QUERY_TYPE_BINARY_OCCLUSION. Binary occlusion is typically preferred for conditional rendering because it only returns a 0 (not visible) or non-zero (visible) 64-bit value, optimizing performance.
  2. Resolving Query Data: The raw query results inside the query heap are converted into standard buffer data using ID3D12GraphicsCommandList::ResolveQueryData. This writes the 64-bit visibility results to a designated ID3D12Resource buffer on the GPU.
  3. Setting Predication: Before issuing the draw or dispatch calls for the actual complex object, the CPU records ID3D12GraphicsCommandList::SetPredication. This method accepts:
    • A pointer to the buffer resource containing the resolved query result.
    • An aligned byte offset to the specific 64-bit query value.
    • A predication operation flag (D3D12_PREDICATION_OP_EQUAL_ZERO or D3D12_PREDICATION_OP_NOT_EQUAL_ZERO).
  4. Hardware-Level Bypassing: All subsequent draw, dispatch, or copy commands recorded in the command list will reference this predication status until SetPredication is called again with nullptr.

GPU-Side Execution Flow

When the GPU reaches the predicated commands in the command buffer:

  • The Command Processor fetches the 64-bit integer stored at the designated buffer offset.
  • If D3D12_PREDICATION_OP_EQUAL_ZERO is specified, the GPU executes the subsequent commands only if the query result is equal to zero (meaning no pixels passed the depth test).
  • If D3D12_PREDICATION_OP_NOT_EQUAL_ZERO is specified, the GPU executes the commands only if the value is non-zero (meaning at least one pixel passed the depth test and the object is visible).
  • If the predication test fails, the Command Processor discards the draw command. No vertex shaders, pixel shaders, or rasterization states are scheduled or executed for that call, saving substantial vertex and fragment processing overhead.

Performance Considerations

Because predication operates at the Command Processor level, commands are skipped with minimal overhead. However, the command packets still travel through the GPU's front-end pipeline. While conditional rendering effectively prevents expensive geometry and pixel shading workloads on hidden objects, drawing millions of tiny, empty predicated packets can still create a minor bottleneck in front-end command processing. For massive scene hierarchies, conditional rendering is best paired with coarse CPU-side frustum culling or compute-based GPU-driven pipelines (via ExecuteIndirect) to maximize throughput.