How DirectX Handles Async Compute Pipelines
DirectX 12 handles asynchronous compute by exposing distinct hardware command queues that allow compute workloads to execute concurrently alongside the main graphics rendering pipeline. By decoupling compute tasks from the rasterization-heavy direct queue, the API enables the GPU to schedule and run independent tasks—such as physics, culling, and post-processing—during idle hardware phases, significantly maximizing compute unit saturation and overall frame throughput.
Multi-Queue Architecture
The foundation of asynchronous compute in DirectX 12 lies in the
separation of command queues via ID3D12CommandQueue. Rather
than funneling all commands through a single serialization pipe, DirectX
exposes three primary queue types:
- DIRECT Queues: Capable of executing all types of commands, including rasterization (draw calls), compute shaders, and memory copies. This is the primary queue for traditional rendering.
- COMPUTE Queues: Restricted to compute shaders and memory copy operations. They bypass the fixed-function graphics pipeline entirely.
- COPY Queues: Dedicated strictly to DMA transfer engines for uploading or reading back data, entirely independent of execution units.
When a developer submits an asynchronous compute task, it is recorded
into a command list targeted at a COMPUTE queue, while
standard rendering continues on the DIRECT queue.
Hardware-Level Concurrency and "Bubbles"
Graphics rendering often encounters bottlenecks caused by fixed-function stages, such as primitive assembly, rasterization, or ROP (render target output) blend operations. During these stages, the GPU’s programmable execution cores (Streaming Multiprocessors or Compute Units) can become underutilized, creating execution "bubbles."
Because the DirectX runtime and the underlying GPU hardware scheduler
manage queues independently, the hardware scheduler can interleave
compute waves (warps/wavefronts) into these idle compute units while the
DIRECT queue handles fixed-function tasks. As long as the
two workloads do not saturate the same hardware bottlenecks—such as
memory bandwidth or cache hierarchy—both tasks make progress
simultaneously without context-switching overhead.
Synchronization with Fences
Parallel execution creates potential data hazards where one pipeline
might attempt to read a resource that another is still writing. DirectX
12 handles this cross-queue synchronization via ID3D12Fence
objects.
Fences operate directly on the GPU timeline without requiring CPU intervention:
- Signal: A queue calls
CommandQueue::Signal(Fence, Value)to record that a specific workload has completed. - Wait: Another queue calls
CommandQueue::Wait(Fence, Value)to stall its execution until the specified fence value is reached.
For example, an async compute queue might compute screen-space ambient occlusion (SSAO). The compute queue signals a fence once the calculation finishes. The direct queue waits on that fence before reading the resulting SSAO texture in the final lighting pass, ensuring safe, ordered memory access without stalling the CPU.
Resource Transitions and Aliasing
Sharing resources between concurrent queues requires explicit state
management. DirectX 12 requires subresources to be placed into
appropriate D3D12_RESOURCE_STATES before use. When
transferring ownership or visibility of a resource between a direct
queue and a compute queue, applications must issue matching resource
barriers on both queues to resolve hazards and flush GPU caches,
guaranteeing cache coherency across different hardware engines.