Skip to content

Resource Barriers

Chuck Walbourn edited this page Apr 26, 2022 · 17 revisions
DirectXTK

When you are rendering or running a compute shader, many threads of work are executed on the GPU which in most cases is massively parallel hardware. In order to ensure all pending work (like rendering to a texture) is completed before you use the results in another operation, you have two choices:

  1. You can insert a fence, submit the command-list, and wait for the GPU to stall
  2. You can insert a resource barrier into the command-list which indicates the point at which one state moves to the next.

For example, when uploading textures you first copy the data a 'upload heap' source (which requires the source texture to be in D3D12_RESOURCE_STATE_COPY_SOURCE, and the VRAM texture resource be in D3D12_RESOURCE_STATE_COPY_DEST), then you want to render that just-loaded texture (which typically requires the VRAM texture resource to be in D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE).

Transition methods

A number of DirectX Tool Kit for DX12 classes include support for these state transition resource barriers. For example, the ResourceUploadBatch lets you automatically queue a transition like the one in the example above:

ResourceUploadBatch resourceUpload(device);

resourceUpload.Begin();

resourceUpload.Upload(texture.Get(), 0, &textureData, 1);

resourceUpload.Transition(
    texture.Get(),
    D3D12_RESOURCE_STATE_COPY_DEST,
    D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);

auto uploadResourcesFinished = resourceUpload.End(m_deviceResources->GetCommandQueue());

uploadResourcesFinished.wait();

In fact, this is done automatically by the DDSTextureLoader / WICTextureLoader since this is the most common use case.

There are similar Transition methods available for GeometricPrimitive and Model when utilizing static vertex buffers & index buffers.

Creating resource barriers

The easiest way to create your own resource barriers is to utilize a D3DX12 helper. Here's a case where you want to transition from being a source of a copy to something used by a compute shader:

auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(texture,
    D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
commandList->ResourceBarrier(1, &barrier);

If to simplify code flow you decide to track state with a variable, you may find the DirectXHelpers TransitionResource method useful. The main reason to use this is it checks to see if the before & after states are the same before submitting the barrier.

TransitionResource(commandList, texture, m_txtState,
    D3D12_RESOURCE_STATE_UNORDERED_ACCESS);

...

TransitionResource(commandList, texture,
    D3D12_RESOURCE_STATE_UNORDERED_ACCESS, m_txtState);

Aliasing

Another style of barrier is when working with two resources that are placed in the same memory. For example, if you want to treat a texture as a UAV. In this case, you need to indicate when you are done with one 'view' of the data and want to use 'another':

auto aliasBarrier = CD3DX12_RESOURCE_BARRIER::Aliasing(dataAsTexture,dataAsUAV);
commandList->ResourceBarrier(1, &aliasBarrier);

UAV

If you want to ensure the work from a compute shader has completed output to a UAV resource before proceeding, this is done with:

auto uavBarrier = CD3DX12_RESOURCE_BARRIER::UAV(uavBuffer);
commandList->ResourceBarrier(1, &uavBarrier);
auto uavBarrier = CD3DX12_RESOURCE_BARRIER::UAV(nullptr);
commandList->ResourceBarrier(1, &uavBarrier);

Batching resource barriers

If you need to execute a number of resource barriers at once, it's best to insert them in a batch. This allows the driver to decide how to handle it based on the GPU architecture. For example, here we have two barriers submitting at once:

D3D12_RESOURCE_BARRIER barriers[2];
barriers[0] = CD3DX12_RESOURCE_BARRIER::Transition(uavBuffer,
    D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
barriers[1] = CD3DX12_RESOURCE_BARRIER::Transition(texture,
    D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
commandList->ResourceBarrier(static_cast<UINT>(_countof(barriers)), barriers);

If you have a set of barriers you apply and then reverse, you may find DirectXHelpers's ScopedBarrier class useful:

{
    ScopedBarrier barriers(commandList,
    {
        CD3DX12_RESOURCE_BARRIER::Transition(uavBuffer,
        D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS),

        CD3DX12_RESOURCE_BARRIER::Transition(texture,
        D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE)
    });

    ...

    // At the end of the scope, the same barrier with all the states swapped is executed
}

Swap-chain resource states

The swap-chain is the collection of 2 or more render targets associated with display presentation. In order to display a backbuffer, it must be in the D3D12_RESOURCE_STATE_PRESENT state. In typical a rendering scenario, the current backbuffer is in this state from the previous frame, must be transitioned to the D3D12_RESOURCE_STATE_RENDER_TARGET at the start of the render frame, and then transitioned back just before Present.

The DeviceResources abstraction supports these transitions via the default parameters to the Prepare and Present methods. You can provide different states to help optimize the backbuffer resource barriers for more complex scenes such as when doing post-processing.

void Prepare(
    D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_PRESENT,
    D3D12_RESOURCE_STATES afterState = D3D12_RESOURCE_STATE_RENDER_TARGET);

The Prepare method will always Reset the command-allocator and a command-list for the new frame. It then injects a resource barrier for the current back-buffer resource from beforeState to afterState.

void Present(
    D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_RENDER_TARGET);

The Present method will inject a resource barrier for the current back-buffer resource from beforeState to D3D12_RESOURCE_STATE_PRESENT. It calls Close on the current command-list and then calls ExecuteCommandLists on the command queue.

Note if you call Prepare(D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_PRESENT); or Present(D3D12_RESOURCE_STATE_PRESENT);, then DeviceResources will not inject any resource barrier into the command-list and assumes you have already put the resource into the correct resource state.

Further reading

Using Resource Barriers to Synchronize Resource States in Direct3D 12

A Look Inside D3D12 Resource State Barriers

DirectX 12: Resource Barriers and State Tracking (You-Tube)

For Use

  • Universal Windows Platform apps
  • Windows desktop apps
  • Windows 11
  • Windows 10
  • Xbox One
  • Xbox Series X|S

Architecture

  • x86
  • x64
  • ARM64

For Development

  • Visual Studio 2022
  • Visual Studio 2019 (16.11)
  • clang/LLVM v12 - v18
  • MinGW 12.2, 13.2
  • CMake 3.20

Related Projects

DirectX Tool Kit for DirectX 11

DirectXMesh

DirectXTex

DirectXMath

Tools

Test Suite

Model Viewer

Content Exporter

DxCapsViewer

See also

DirectX Landing Page

Clone this wiki locally