使用资源屏障在 Direct3D 12 中同步资源状态

为了减少总体 CPU 使用率并启用驱动程序多线程处理和预处理,Direct3D 12 将每个资源状态管理的责任从图形驱动程序转移到应用程序。 每个资源状态的示例是纹理资源当前是通过着色器资源视图还是呈现目标视图进行访问。 在 Direct3D 11 中,驱动程序需要在后台跟踪此状态。 从 CPU 的角度来看,这消耗大量 CPU 资源,显著增加了多线程设计的复杂性。 在 Microsoft Direct3D 12 中,大多数每个资源状态由应用程序使用单个 API ID3D12GraphicsCommandList::ResourceBarrier 管理。

使用 ResourceBarrier API 管理每资源状态

ResourceBarrier 通知图形驱动程序,在这种情况下,驱动程序可能需要同步对存储资源的内存的多个访问。 使用一个或多个资源屏障描述结构调用该方法,该结构指示要声明的资源屏障的类型。

有三种类型的资源屏障:

  • 转换屏障。 转换屏障指示一组子资源在不同用法之间的转换。 D3D12_RESOURCE_TRANSITION_BARRIER结构用于指定正在转换的子资源以及子资源转换前后的状态。

    系统验证命令列表中的子资源转换是否与相同命令列表中的以前的转换一致。 调试层会进一步跟踪子资源状态,以查找其他错误,但此验证是保守的,并不详尽。

    请注意,您可以使用D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES标志来指定资源中的所有子资源正在被转换。

  • 别名屏障。 别名屏障指示两个不同资源的使用情况之间的转换,这些资源具有重叠映射到同一堆。 这同时适用于保留的资源和放置的资源。 D3D12_RESOURCE_ALIASING_BARRIER结构用于同时指定资源之前的资源和之后的资源。

    请注意,一个或两个资源可能为 NULL,这意味着任何平铺资源都可能导致混叠。 有关使用平铺资源的详细信息,请参阅平铺资源卷平铺资源

  • 无序访问视图(UAV)障碍。 UAV 屏障指示,所有对特定资源的 UAV 读取或写入访问必须完成,然后才能开始对该资源的任何未来 UAV 读取或写入访问。 应用程序不必在两个仅从 UAV 读取 的绘图或调度调用之间设置 UAV 屏障。 此外,如果应用程序知道以任意顺序执行对同一 UAV 的访问是安全的,则不必在两次绘制或调度调用之间设置 UAV 屏障。 D3D12_RESOURCE_UAV_BARRIER结构用于指定屏障应用到的 UAV 资源。 应用程序可以为屏障的 UAV 指定 NULL,这表明任何 UAV 访问可能需要屏障。

使用资源屏障描述数组调用 ResourceBarrier 时,API 的行为就像为每个元素调用一次,按照它们提供的顺序。

在任何给定时间,子资源完全处于一种状态,由提供给 ResourceBarrierD3D12_RESOURCE_STATES标志集决定。 应用程序必须确保 ResourceBarrier 的连续调用前后状态达成一致。

小窍门

应尽可能将多个转换批处理到一个 API 调用中。

资源状态

有关资源状态可在多个状态之间转换的完整列表,请参阅 D3D12_RESOURCE_STATES 枚举的参考主题。

有关拆分资源屏障,另请参阅 D3D12_RESOURCE_BARRIER_FLAGS

资源的初始状态

可以使用任何用户指定的初始状态(对资源说明有效)创建资源,但有以下例外:

  • 上传堆必须以状态 D3D12_RESOURCE_STATE_GENERIC_READ 开始,这是按位 OR 运算结果的组合:
    • D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER
    • D3D12_RESOURCE_STATE_INDEX_BUFFER
    • D3D12_RESOURCE_STATE_COPY_SOURCE
    • D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
    • D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
    • D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT
  • 读回堆必须以D3D12_RESOURCE_STATE_COPY_DEST状态开始。
  • 交换链后台缓冲区自动以D3D12_RESOURCE_STATE_COMMON状态启动。

在堆可以是 GPU 复制作的目标之前,通常必须先将堆转换为D3D12_RESOURCE_STATE_COPY_DEST状态。 但是,在 UPLOAD 堆上创建的资源必须启动,并且无法从GENERIC_READ状态更改,因为只有 CPU 才会写入。 相反,在 READBACK 堆中创建的已提交资源必须从 COPY_DEST 状态启动,并且无法更改。

读取/写入资源状态限制

用于描述资源状态的资源状态使用位分为只读状态和读/写状态。 D3D12_RESOURCE_STATES的参考主题指示枚举中每个位的读/写访问级别。

最多只能为任何资源设置一个读/写位。 如果设置了写入位,则无法为该资源设置只读位。 如果未设置写入位,则可以设置任意数量的读取位。

用于呈现后台缓冲区的资源状态

在显示后台缓冲区之前,它必须处于D3D12_RESOURCE_STATE_COMMON状态。 请注意,资源状态D3D12_RESOURCE_STATE_PRESENT是D3D12_RESOURCE_STATE_COMMON的同义词,它们都具有值 0。 如果对当前未处于此状态的资源调用 IDXGISwapChain::P resent (或 IDXGISwapChain1::P resent1),则会发出调试层警告。

放弃资源

调用 ID3D12GraphicsCommandList::DiscardResource 时,资源中的所有子资源必须处于 RENDER_TARGET 状态或 DEPTH_WRITE 状态,才能对渲染目标/深度模板资源进行相应处理。

隐式状态转换

资源只能从D3D12_RESOURCE_STATE_COMMON中“提升”。 同样,资源只会“衰减”到D3D12_RESOURCE_STATE_COMMON。

常见状态进阶

所有缓冲区资源以及设置了D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS标志的纹理在首次 GPU 访问时会自动从D3D12_RESOURCE_STATE_COMMON转换到对应状态,其中包括GENERIC_READ以涵盖任何读取情况。 处于 COMMON 状态的任何资源都可以像它处于单独状态一样进行访问。

1 个 WRITE 标志,或 1 个或多个 READ 标志

可以根据下表从 COMMON 状态升级资源:

州旗 缓冲区和同步访问纹理 非同时访问纹理
VERTEX_AND_CONSTANT_BUFFER(顶点和常量缓冲区) 是的
INDEX_BUFFER 是的
RENDER_TARGET 是的
无序访问 是的
DEPTH_WRITE *
DEPTH_READ *
非像素着色器资源 是的 是的
像素着色器资源 是的 是的
STREAM_OUT 是的
INDIRECT_ARGUMENT 是的
COPY_DEST 是的 是的
COPY_SOURCE 是的 是的
RESOLVE_DEST 是的
RESOLVE_SOURCE 是的
预测 是的

 

*深度模板资源必须是非同时访问纹理,因此永远无法自动提升。

当发生此访问时,提升的行为类似于隐式资源屏障。 对于后续访问,如果有必要,需要资源屏障来更改资源状态。 请注意,从一个被提升的读取状态转变为多个读取状态是有效的,但这不适用于写入状态。
例如,如果在一次 Draw 调用中将处于公共状态的资源升级为 PIXEL_SHADER_RESOURCE,则在另一次 Draw 调用中仍可升级为 NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE。 如果在写操作中使用,如作为复制目标,则需要从组合的读状态(此处为NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE)转换为COPY_DEST的资源状态转换屏障。
同样,如果从 COMMON 升级到 COPY_DEST,则仍需要从 COPY_DEST 转换到 RENDER_TARGET。

请注意,常见的状态升级是“免费”,因此无需 GPU 执行任何同步等待。 提升表示处于 COMMON 状态的资源不应需要额外的 GPU 工作或驱动程序跟踪来支持某些访问。

状态衰减为常见

常见状态提升的另一面是退回到D3D12_RESOURCE_STATE_COMMON状态。 当 GPU 完成 ExecuteCommandLists 作的执行时,满足某些要求的资源被视为无状态,并有效地返回到通用状态。 在同一 ExecuteCommandLists 调用中一起执行的命令列表之间不会发生衰减。

在 GPU 上完成 ExecuteCommandLists 作时,以下资源将衰减:

  • 通过复制队列访问的资源,
  • 在任何队列类型上缓冲资源,
  • 设置了D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS标志的任何队列类型上的纹理资源,
  • 任何自动提升为只读状态的资源。

与常见的状态提升一样,衰减是免费的,不需要额外的同步。 结合常见的状态提升和衰减有助于消除许多不必要的 ResourceBarrier 转换。 在某些情况下,这可以提供显著的性能改进。

无论它们是使用资源屏障显式转换还是隐式提升,缓冲区和 Simultaneous-Access 资源都会衰减到通用状态。

性能影响

记录公共状态下资源的显式 ResourceBarrier 转换时,正确使用 D3D12_RESOURCE_STATE_COMMON 或任何可提升状态作为 D3D12_RESOURCE_TRANSITION_BARRIER 结构中的 BeforeState 值。 这允许传统的状态管理忽略缓冲区和同时访问纹理的自动衰减。 不过,这可能不是可取的,因为避免使用已知处于常见状态的资源转换 ResourceBarrier 调用可以显著提高性能。 资源屏障可能很昂贵。 它们旨在强制缓存刷新、内存布局更改以及其他同步操作,而这些操作对于已经处于常见状态的资源可能并非必需。 使用资源屏障将一种处于通用状态的资源从一种非常见状态转为另一种非常见状态的命令列表可能会引入许多不必要的开销。

此外,请避免显式 ResourceBarrier 转换为D3D12_RESOURCE_STATE_COMMON,除非绝对必要(例如,下一次访问位于需要资源以通用状态开始的 COPY 命令队列上)。 过度转换到通用状态可能会显著降低 GPU 性能。

总之,尝试在语义允许的情况下依赖于常见状态的提升和衰减,而不是发出 ResourceBarrier 调用。

拆分屏障

具有D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY标志的资源转换屏障开始拆分屏障,过渡屏障据说正在等待。 在屏障待决期间,GPU 无法读取或写入(子)资源。 唯一可应用于具有挂起屏障的(sub)资源的合法转换屏障,是具有相同状态的之前之后状态并带有D3D12_RESOURCE_BARRIER_FLAG_END_ONLY标志的屏障,该屏障完成挂起的转换。

拆分屏障向 GPU 提供提示,提示下一个状态 A 中的资源稍后将在状态 B 中使用。 这使得 GPU 能够优化过渡工作负荷,从而可能减少或消除执行停顿。 发出仅限于结束的屏障可确保在转到下一个命令之前完成所有 GPU 状态转换工作。

使用分隔屏障有助于提高性能,尤其是在多引擎方案中,或者在一个或多个命令列表中稀疏转换读取/写入资源时。

资源屏障示例方案

以下代码片段演示了多线程示例中 ResourceBarrier 方法的使用。

创建深度模板视图,并将其转换为可写状态。

// Create the depth stencil.
{
    CD3DX12_RESOURCE_DESC shadowTextureDesc(
        D3D12_RESOURCE_DIMENSION_TEXTURE2D,
        0,
        static_cast<UINT>(m_viewport.Width), 
        static_cast<UINT>(m_viewport.Height), 
        1,
        1,
        DXGI_FORMAT_D32_FLOAT,
        1, 
        0,
        D3D12_TEXTURE_LAYOUT_UNKNOWN,
        D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE);

    D3D12_CLEAR_VALUE clearValue;    // Performance tip: Tell the runtime at resource creation the desired clear value.
    clearValue.Format = DXGI_FORMAT_D32_FLOAT;
    clearValue.DepthStencil.Depth = 1.0f;
    clearValue.DepthStencil.Stencil = 0;

    ThrowIfFailed(m_device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &shadowTextureDesc,
        D3D12_RESOURCE_STATE_DEPTH_WRITE,
        &clearValue,
        IID_PPV_ARGS(&m_depthStencil)));

    // Create the depth stencil view.
    m_device->CreateDepthStencilView(m_depthStencil.Get(), nullptr, m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
}

创建顶点缓冲区视图,首先将其从通用状态更改为目标,然后从目标更改为通用可读状态。

// Create the vertex buffer.
{
    ThrowIfFailed(m_device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(SampleAssets::VertexDataSize),
        D3D12_RESOURCE_STATE_COPY_DEST,
        nullptr,
        IID_PPV_ARGS(&m_vertexBuffer)));

    {
        ThrowIfFailed(m_device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(SampleAssets::VertexDataSize),
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&m_vertexBufferUpload)));

        // Copy data to the upload heap and then schedule a copy 
        // from the upload heap to the vertex buffer.
        D3D12_SUBRESOURCE_DATA vertexData = {};
        vertexData.pData = pAssetData + SampleAssets::VertexDataOffset;
        vertexData.RowPitch = SampleAssets::VertexDataSize;
        vertexData.SlicePitch = vertexData.RowPitch;

        PIXBeginEvent(commandList.Get(), 0, L"Copy vertex buffer data to default resource...");

        UpdateSubresources<1>(commandList.Get(), m_vertexBuffer.Get(), m_vertexBufferUpload.Get(), 0, 0, 1, &vertexData);
        commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_vertexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER));

        PIXEndEvent(commandList.Get());
    }

创建索引缓冲区视图,首先将其从通用状态更改为目标,然后从目标更改为通用可读状态。

// Create the index buffer.
{
    ThrowIfFailed(m_device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(SampleAssets::IndexDataSize),
        D3D12_RESOURCE_STATE_COPY_DEST,
        nullptr,
        IID_PPV_ARGS(&m_indexBuffer)));

    {
        ThrowIfFailed(m_device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(SampleAssets::IndexDataSize),
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&m_indexBufferUpload)));

        // Copy data to the upload heap and then schedule a copy 
        // from the upload heap to the index buffer.
        D3D12_SUBRESOURCE_DATA indexData = {};
        indexData.pData = pAssetData + SampleAssets::IndexDataOffset;
        indexData.RowPitch = SampleAssets::IndexDataSize;
        indexData.SlicePitch = indexData.RowPitch;

        PIXBeginEvent(commandList.Get(), 0, L"Copy index buffer data to default resource...");

        UpdateSubresources<1>(commandList.Get(), m_indexBuffer.Get(), m_indexBufferUpload.Get(), 0, 0, 1, &indexData);
        commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_indexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER));

        PIXEndEvent(commandList.Get());
    }

    // Initialize the index buffer view.
    m_indexBufferView.BufferLocation = m_indexBuffer->GetGPUVirtualAddress();
    m_indexBufferView.SizeInBytes = SampleAssets::IndexDataSize;
    m_indexBufferView.Format = SampleAssets::StandardIndexFormat;
}

创建纹理和着色器资源视图。 纹理从通用状态更改为目标,然后从目标更改为像素着色器资源。

    // Create each texture and SRV descriptor.
    const UINT srvCount = _countof(SampleAssets::Textures);
    PIXBeginEvent(commandList.Get(), 0, L"Copy diffuse and normal texture data to default resources...");
    for (int i = 0; i < srvCount; i++)
    {
        // Describe and create a Texture2D.
        const SampleAssets::TextureResource &tex = SampleAssets::Textures[i];
        CD3DX12_RESOURCE_DESC texDesc(
            D3D12_RESOURCE_DIMENSION_TEXTURE2D,
            0,
            tex.Width, 
            tex.Height, 
            1,
            static_cast<UINT16>(tex.MipLevels),
            tex.Format,
            1, 
            0,
            D3D12_TEXTURE_LAYOUT_UNKNOWN,
            D3D12_RESOURCE_FLAG_NONE);

        ThrowIfFailed(m_device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
            D3D12_HEAP_FLAG_NONE,
            &texDesc,
            D3D12_RESOURCE_STATE_COPY_DEST,
            nullptr,
            IID_PPV_ARGS(&m_textures[i])));

        {
            const UINT subresourceCount = texDesc.DepthOrArraySize * texDesc.MipLevels;
            UINT64 uploadBufferSize = GetRequiredIntermediateSize(m_textures[i].Get(), 0, subresourceCount);
            ThrowIfFailed(m_device->CreateCommittedResource(
                &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
                D3D12_HEAP_FLAG_NONE,
                &CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize),
                D3D12_RESOURCE_STATE_GENERIC_READ,
                nullptr,
                IID_PPV_ARGS(&m_textureUploads[i])));

            // Copy data to the intermediate upload heap and then schedule a copy 
            // from the upload heap to the Texture2D.
            D3D12_SUBRESOURCE_DATA textureData = {};
            textureData.pData = pAssetData + tex.Data->Offset;
            textureData.RowPitch = tex.Data->Pitch;
            textureData.SlicePitch = tex.Data->Size;

            UpdateSubresources(commandList.Get(), m_textures[i].Get(), m_textureUploads[i].Get(), 0, 0, subresourceCount, &textureData);
            commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_textures[i].Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
        }

        // Describe and create an SRV.
        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.Format = tex.Format;
        srvDesc.Texture2D.MipLevels = tex.MipLevels;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        m_device->CreateShaderResourceView(m_textures[i].Get(), &srvDesc, cbvSrvHandle);

        // Move to the next descriptor slot.
        cbvSrvHandle.Offset(cbvSrvDescriptorSize);
    }

开始框架,这不仅使用 ResourceBarrier 来指示后备缓冲区将用作呈现目标,而且还初始化帧资源(该资源调用深度模板缓冲区上的 ResourceBarrier)。

// Assemble the CommandListPre command list.
void D3D12Multithreading::BeginFrame()
{
    m_pCurrentFrameResource->Init();

    // Indicate that the back buffer will be used as a render target.
    m_pCurrentFrameResource->m_commandLists[CommandListPre]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

    // Clear the render target and depth stencil.
    const float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
    m_pCurrentFrameResource->m_commandLists[CommandListPre]->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
    m_pCurrentFrameResource->m_commandLists[CommandListPre]->ClearDepthStencilView(m_dsvHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

    ThrowIfFailed(m_pCurrentFrameResource->m_commandLists[CommandListPre]->Close());
}

// Assemble the CommandListMid command list.
void D3D12Multithreading::MidFrame()
{
    // Transition our shadow map from the shadow pass to readable in the scene pass.
    m_pCurrentFrameResource->SwapBarriers();

    ThrowIfFailed(m_pCurrentFrameResource->m_commandLists[CommandListMid]->Close());
}

结束显示帧,指示后台缓冲区现在用于呈现。

// Assemble the CommandListPost command list.
void D3D12Multithreading::EndFrame()
{
    m_pCurrentFrameResource->Finish();

    // Indicate that the back buffer will now be used to present.
    m_pCurrentFrameResource->m_commandLists[CommandListPost]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

    ThrowIfFailed(m_pCurrentFrameResource->m_commandLists[CommandListPost]->Close());
}

初始化帧资源(在开始帧时调用)会将深度模板缓冲区转换为可写。

void FrameResource::Init()
{
    // Reset the command allocators and lists for the main thread.
    for (int i = 0; i < CommandListCount; i++)
    {
        ThrowIfFailed(m_commandAllocators[i]->Reset());
        ThrowIfFailed(m_commandLists[i]->Reset(m_commandAllocators[i].Get(), m_pipelineState.Get()));
    }

    // Clear the depth stencil buffer in preparation for rendering the shadow map.
    m_commandLists[CommandListPre]->ClearDepthStencilView(m_shadowDepthView, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

    // Reset the worker command allocators and lists.
    for (int i = 0; i < NumContexts; i++)
    {
        ThrowIfFailed(m_shadowCommandAllocators[i]->Reset());
        ThrowIfFailed(m_shadowCommandLists[i]->Reset(m_shadowCommandAllocators[i].Get(), m_pipelineStateShadowMap.Get()));

        ThrowIfFailed(m_sceneCommandAllocators[i]->Reset());
        ThrowIfFailed(m_sceneCommandLists[i]->Reset(m_sceneCommandAllocators[i].Get(), m_pipelineState.Get()));
    }
}

屏障在帧中交换,将阴影映射从可写状态切换为可读状态。

void FrameResource::SwapBarriers()
{
    // Transition the shadow map from writeable to readable.
    m_commandLists[CommandListMid]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_shadowTexture.Get(), D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
}

在帧结束时调用 Finish,将阴影映射转换为通用状态。

void FrameResource::Finish()
{
    m_commandLists[CommandListPost]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_shadowTexture.Get(), D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_DEPTH_WRITE));
}

常见状态提升和衰减示例

    // Create a buffer resource using D3D12_RESOURCE_STATE_COMMON as the init state
    ID3D12Resource *pResource;
    CreateCommittedVertexBufferInCommonState(1024, &pResource);

    // Copy data to the buffer without the need for a barrier.
    // Promotes pResource state to D3D12_RESOURCE_STATE_COPY_DEST.
    pCommandList->CopyBufferRegion(pResource, 0, pOtherResource, 0, 1024); 

    // To use pResource as a vertex buffer a transition barrier is needed.
    // Note the StateBefore is D3D12_RESOURCE_STATE_COPY_DEST.
    D3D12_RESOURCE_BARRIER BarrierDesc = {};
    BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    BarrierDesc.Transition.pResource = pResource;
    BarrierDesc.Transition.Subresource = 0;
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
    pCommandList->ResourceBarrier( 1, &BarrierDesc );

    // Use pResource as a vertex buffer
    D3D12_VERTEX_BUFFER_VIEW vbView;
    vbView.BufferLocation = pResource->GetGPUVirtualAddress();
    vbView.SizeInBytes = 1024;
    vbView.StrideInBytes = sizeof(MyVertex);

    pCommandList->IASetVertexBuffers(0, 1, &vbView);
    pCommandList->Draw();

    pCommandQueue->ExecuteCommandLists(1, &pCommandList); 
    pCommandList->Reset(pAllocator, pPipelineState);

    // Since the reset command list will be executed in a separate call to 
    // ExecuteCommandLists, the previous state of pResource
    // will have decayed to D3D12_RESOURCE_STATE_COMMON so, again, no barrier is needed
    pCommandList->CopyBufferRegion(pResource, 0, pDifferentResource, 0, 1024);

    FinishRecordingCommandList(pCommandList);
    pCommandQueue->ExecuteCommandLists(1, &pCommandList); 

    WaitForQueue(pCommandQueue);

    // The previous ExecuteCommandLists call has finished so 
    // pResource has decayed to D3D12_RESOURCE_STATE_COMMON

拆分屏障的示例

以下示例演示如何使用拆分屏障来减少管道停止。 下面的代码不使用拆分屏障:

 D3D12_RESOURCE_BARRIER BarrierDesc = {};
    BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TRANSITION;
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_NONE;
    BarrierDesc.Transition.pResource = pResource;
    BarrierDesc.Transition.Subresource = 0;
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;

    pCommandList->ResourceBarrier( 1, &BarrierDesc );
    
    Write(pResource); // ... render to pResource
    OtherStuff(); // .. other gpu work

    // Transition pResource to PIXEL_SHADER_RESOURCE
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
    
    pCommandList->ResourceBarrier( 1, &BarrierDesc );

    Read(pResource); // ... read from pResource

以下代码使用分隔栅栏:

D3D12_RESOURCE_BARRIER BarrierDesc = {};
    BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TRANSITION;
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_NONE;
    BarrierDesc.Transition.pResource = pResource;
    BarrierDesc.Transition.Subresource = 0;
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;

    pCommandList->ResourceBarrier( 1, &BarrierDesc );
    
    Write(pResource); // ... render to pResource

    // Done writing to pResource. Start barrier to PIXEL_SHADER_RESOURCE and
    // then do other work
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_BEGIN_ONLY;
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
    pCommandList->ResourceBarrier( 1, &BarrierDesc );

    OtherStuff(); // .. other gpu work

    // Need to read from pResource so end barrier
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_END_ONLY;

    pCommandList->ResourceBarrier( 1, &BarrierDesc );
    Read(pResource); // ... read from pResource

DirectX 高级学习视频教程:资源屏障和状态跟踪

多引擎同步

Direct3D 12 中的工作提交

深入探讨 D3D12 资源状态屏障