How DirectX 12 Split Barriers Hide GPU Latency

DirectX 12 split barriers optimize GPU pipeline performance by decoupling the initiation and completion of resource state transitions. Unlike traditional instant barriers that force the GPU to stall until a transition completes, split barriers divide the operation into two distinct phases using dedicated flags. This article explains the mechanics of split barriers, how they decouple resource availability, and how developers can interleave independent workloads between barrier phases to completely absorb transition latency and prevent pipeline bubbles.

The Problem with Traditional Resource Barriers

In modern graphics APIs like DirectX 12, resources regularly change states—for example, transitioning a texture from a render target (D3D12_RESOURCE_STATE_RENDER_TARGET) to a shader resource view (D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE). When using a standard barrier, the transition is instantaneous from the command stream's perspective.

Under the hood, however, this transition requires physical hardware actions:

  • Flushing and invalidating L1 and L2 caches.
  • Decompressing compressed color, depth, or stencil data.
  • Waiting for prior pipeline stages to drain completely.

When these operations execute within a single barrier command, the GPU frequently experiences a pipeline bubble. Execution units (ALUs) sit idle while memory controllers and fixed-function hardware perform synchronization and cache flushing, resulting in measurable performance overhead.

How Split Barriers Function

Split barriers address this latency by splitting a transition into two separate calls:

  1. The Begin Phase (D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY): This signals to the driver and GPU that the application is finished using the resource in its "before" state. The GPU can immediately start asynchronous background tasks, such as draining read/write queues, resolving metadata, or invalidating relevant caches.
  2. The End Phase (D3D12_RESOURCE_BARRIER_FLAG_END_ONLY): This informs the GPU that the resource is now required in its "after" state. The pipeline will only halt at this point if the background operations initiated by the begin barrier have not yet concluded.

During the execution window between the begin and end calls, the resource is in an indeterminate transitional state. It cannot be read or written by the GPU during this interval, but the pipeline remains entirely free to execute unrelated work.

Hiding Latency with Independent Workloads

The primary advantage of split barriers is that they turn idle waiting time into productive compute cycles. To effectively hide transition latency:

  1. Issue the Begin Barrier Early: As soon as a pass finishes writing to a resource (e.g., generating a shadow map or downsampling a bloom buffer), emit the BEGIN_ONLY barrier.
  2. Schedule Independent Work: Submit draw calls, compute dispatches, or state setup commands that do not depend on the transitioning resource. For instance, while a shadow map resolves its depth metadata, the GPU can run compute dispatches for particle updates or execute unrelated post-processing passes.
  3. Issue the End Barrier Late: Immediately before the command that actually binds the resource in its new state (e.g., reading the shadow map in the main forward-lighting pass), submit the END_ONLY barrier.

If the independent work takes longer to execute than the hardware transition operations, the latency of the state change is effectively zero from the perspective of the application frame time. The GPU reaches the END_ONLY barrier, detects that all decompression, cache syncs, and memory updates are complete, and executes the dependent pass without stalling.

Implementation Considerations

  • Workload Balance: The effectiveness of split barriers depends directly on having sufficient independent work to fill the gap. If the end barrier is placed too close to the begin barrier, the GPU will still stall waiting for completion.
  • Enhanced Barriers: In newer iterations of the DirectX 12 Agility SDK, Enhanced Barriers formalize this concept even further via sync and access scopes, allowing fine-grained control over specific cache lines and pipeline stages rather than coarse legacy states. Regardless of the API variation used, the fundamental architectural principle remains the same: starting synchronization work as early as possible to completely overlap memory latency with independent ALU computation.