Compartilhar via


Como usar barreiras de recursos para sincronizar estados de recursos no Direct3D 12

Para reduzir o uso geral da CPU e habilitar o multi-threading e o pré-processamento do driver, o Direct3D 12 move a responsabilidade do gerenciamento de estado por recurso do driver gráfico para o aplicativo. Um exemplo de estado por recurso é se um recurso de textura está sendo acessado no momento por meio de um Modo de Exibição de Recurso de Sombreador ou como uma Exibição de Destino de Renderização. No Direct3D 11, os drivers eram necessários para acompanhar esse estado em segundo plano. Isso é caro do ponto de vista da CPU e complica significativamente qualquer tipo de design multi-threaded. No Microsoft Direct3D 12, a maior parte do estado por recurso é gerenciada pelo aplicativo com uma única API, ID3D12GraphicsCommandList::ResourceBarrier.

Usando a API ResourceBarrier para gerenciar o estado por recurso

ResourceBarrier notifica o driver gráfico de situações em que o driver pode precisar sincronizar vários acessos à memória em que um recurso é armazenado. O método é chamado com uma ou mais estruturas de descrição de barreira de recursos indicando o tipo de barreira de recursos que está sendo declarada.

Há três tipos de barreiras de recursos:

  • Barreira de transição – uma barreira de transição indica que um conjunto de sub-recursos faz a transição entre diferentes usos. Uma estrutura D3D12_RESOURCE_TRANSITION_BARRIER é usada para especificar o sub-recurso que está fazendo a transição, bem como os estados antes e depois do sub-recurso.

    O sistema verifica se as transições de sub-recurso em uma lista de comandos são consistentes com transições anteriores na mesma lista de comandos. A camada de depuração rastreia ainda mais o estado do sub-recurso para encontrar outros erros, no entanto, essa validação é conservadora e não exaustiva.

    Observe que você pode usar o sinalizador D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES para especificar que todos os sub-recursos dentro de um recurso estão sendo transferidos.

  • Barreira de alias – uma barreira de alias indica uma transição entre os usos de dois recursos diferentes que têm mapeamentos sobrepostos no mesmo heap. Isso se aplica a recursos reservados e colocados. Uma estrutura D3D12_RESOURCE_ALIASING_BARRIER é usada para especificar o recurso before e o recurso after .

    Observe que um ou ambos os recursos podem ser NULL, o que indica que qualquer recurso lado a lado pode causar alias. Para obter mais informações sobre como usar recursos lado a lado, consulte Recursos lado a lado e Recursos lado a lado de volume.

  • Barreira de UAV ( exibição de acesso não ordenada) – uma barreira UAV indica que todos os acessos UAV, tanto de leitura quanto de gravação, a um recurso específico devem ser concluídos entre quaisquer acessos UAV futuros, tanto de leitura quanto de gravação. Não é necessário que um aplicativo coloque uma barreira UAV entre duas chamadas de desenho ou expedição que somente são lidas de um UAV. Além disso, não é necessário colocar uma barreira UAV entre duas chamadas de desenho ou expedição que gravam no mesmo UAV se o aplicativo souber que é seguro executar o acesso UAV em qualquer ordem. Uma estrutura D3D12_RESOURCE_UAV_BARRIER é usada para especificar o recurso UAV ao qual a barreira se aplica. O aplicativo pode especificar NULL para o UAV da barreira, o que indica que qualquer acesso UAV pode exigir a barreira.

Quando ResourceBarrier é chamado com uma matriz de descrições de barreira de recursos, a API se comporta como se fosse chamada uma vez para cada elemento, na ordem em que foram fornecidos.

A qualquer momento, um sub-recurso está exatamente em um estado, determinado pelo conjunto de sinalizadores de D3D12_RESOURCE_STATES fornecidos ao ResourceBarrier. O aplicativo deve garantir que os estados antes e depois de chamadas consecutivas para ResourceBarrier concordem.

Dica

Os aplicativos devem agrupar várias transições em uma chamada à API sempre que possível.

 

Estados de recurso

Para obter a lista completa de estados de recurso entre os quais um recurso pode fazer a transição, consulte o tópico de referência para a enumeração D3D12_RESOURCE_STATES .

Para dividir barreiras de recursos, consulte também D3D12_RESOURCE_BARRIER_FLAGS.

Estados iniciais para recursos

Os recursos podem ser criados com qualquer estado inicial especificado pelo usuário (válido para a descrição do recurso), com as seguintes exceções:

  • Os heaps de carregamento devem começar no estado D3D12_RESOURCE_STATE_GENERIC_READ que é uma combinação OR bit a bit de:
    • 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
  • Os heaps de readback devem começar no estado D3D12_RESOURCE_STATE_COPY_DEST.
  • Os buffers de back da cadeia de troca são iniciados automaticamente no estado D3D12_RESOURCE_STATE_COMMON.

Antes que um heap possa ser o destino de uma operação de cópia de GPU, normalmente o heap deve primeiro ser transferido para o estado D3D12_RESOURCE_STATE_COPY_DEST. No entanto, os recursos criados em heaps upload devem começar no e não podem ser alterados do estado GENERIC_READ, pois apenas a CPU fará a gravação. Por outro lado, os recursos confirmados criados em heaps READBACK devem começar em e não podem ser alterados do estado COPY_DEST.

Restrições de estado do recurso de leitura/gravação

Os bits de uso do estado do recurso usados para descrever um estado de recurso são divididos em estados somente leitura e leitura/gravação. O tópico de referência do D3D12_RESOURCE_STATES indica o nível de acesso de leitura/gravação para cada bit na enumeração.

No máximo, apenas um bit de leitura/gravação pode ser definido para qualquer recurso. Se um bit de gravação for definido, nenhum bit somente leitura poderá ser definido para esse recurso. Se nenhum bit de gravação estiver definido, qualquer número de bits de leitura poderá ser definido.

Estados de recurso para apresentar buffers de volta

Antes que um buffer de fundo seja apresentado, ele deve estar no estado D3D12_RESOURCE_STATE_COMMON. Observe que o estado do recurso D3D12_RESOURCE_STATE_PRESENT é sinônimo de D3D12_RESOURCE_STATE_COMMON e ambos têm um valor de 0. Se IDXGISwapChain::P resent (ou IDXGISwapChain1::P resent1) for chamado em um recurso que não está atualmente nesse estado, um aviso de camada de depuração será emitido.

Descartando recursos

Todos os sub-recursos em um recurso devem estar no estado RENDER_TARGET, ou DEPTH_WRITE estado, para renderizar destinos/recursos de estêncil de profundidade, respectivamente, quando ID3D12GraphicsCommandList::D iscardResource é chamado.

Transições de estado implícitas

Os recursos só podem ser "promovidos" de D3D12_RESOURCE_STATE_COMMON. Da mesma forma, os recursos só serão "decaidos" para D3D12_RESOURCE_STATE_COMMON.

Promoção de estado comum

Todos os recursos de buffer, bem como texturas com o conjunto de sinalizadores D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS são implicitamente promovidos de D3D12_RESOURCE_STATE_COMMON para o estado relevante no primeiro acesso à GPU, incluindo GENERIC_READ para cobrir qualquer cenário de leitura. Qualquer recurso no estado COMMON pode ser acessado por meio dele em um único estado com

1 Sinalizador WRITE ou 1 ou mais sinalizadores READ

Os recursos podem ser promovidos do estado COMMON com base na tabela a seguir:

Sinalizador de estado Buffers e texturas de Simultaneous-Access Texturas de acesso não simultâneo
VERTEX_AND_CONSTANT_BUFFER Sim Não
INDEX_BUFFER Sim Não
RENDER_TARGET Sim Não
UNORDERED_ACCESS Sim Não
DEPTH_WRITE Não* Não
DEPTH_READ Não* Não
NON_PIXEL_SHADER_RESOURCE Sim Sim
PIXEL_SHADER_RESOURCE Sim Sim
STREAM_OUT Sim Não
INDIRECT_ARGUMENT Sim Não
COPY_DEST Sim Sim
COPY_SOURCE Sim Sim
RESOLVE_DEST Sim Não
RESOLVE_SOURCE Sim Não
PREDICATION Sim Não

 

*Os recursos de estêncil de profundidade devem ser texturas de acesso não simultâneo e, portanto, nunca podem ser promovidos implicitamente.

Quando esse acesso ocorre, a promoção atua como uma barreira de recursos implícita. Para acessos subsequentes, as barreiras de recursos serão necessárias para alterar o estado do recurso, se necessário. Observe que a promoção de um estado de leitura promovido para vários estados de leitura é válida, mas esse não é o caso para estados de gravação.
Por exemplo, se um recurso no estado comum for promovido a PIXEL_SHADER_RESOURCE em uma chamada de Desenho, ele ainda poderá ser promovido a NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE em outra chamada de Desenho. No entanto, se ele for usado em uma operação de gravação, como um destino de cópia, uma barreira de transição de estado de recurso dos estados de leitura promovidos combinados, aqui NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE, para COPY_DEST é necessário.
Da mesma forma, se promovido de COMMON para COPY_DEST, uma barreira ainda será necessária para fazer a transição de COPY_DEST para RENDER_TARGET.

Observe que a promoção de estado comum é "gratuita", pois não há necessidade de a GPU executar quaisquer esperas de sincronização. A promoção representa o fato de que os recursos no estado COMMON não devem exigir trabalho adicional de GPU ou acompanhamento de driver para dar suporte a determinados acessos.

Decaimento de estado para comum

O outro lado da promoção de estado comum é decair de volta para D3D12_RESOURCE_STATE_COMMON. Os recursos que atendem a determinados requisitos são considerados sem estado e retornam efetivamente ao estado comum quando a GPU conclui a execução de uma operação ExecuteCommandLists . A decaimento não ocorre entre listas de comandos executadas juntas na mesma chamada ExecuteCommandLists .

Os seguintes recursos serão decaidos quando uma operação ExecuteCommandLists for concluída na GPU:

  • Recursos que estão sendo acessados em uma fila de Cópia ou
  • Recursos de buffer em qualquer tipo de fila ou
  • Recursos de textura em qualquer tipo de fila que tenha o sinalizador D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS definido ou
  • Qualquer recurso implicitamente promovido a um estado somente leitura.

Assim como a promoção de estado comum, a decaimento é gratuita, porque nenhuma sincronização adicional é necessária. A combinação de promoção e decaimento de estado comum pode ajudar a eliminar muitas transições desnecessárias do ResourceBarrier . Em alguns casos, isso pode fornecer melhorias significativas de desempenho.

Buffers e Simultaneous-Access recursos serão decaidos para o estado comum, independentemente de terem sido explicitamente transferidos usando barreiras de recursos ou promovidos implicitamente.

Implicações de desempenho

Ao registrar transições explícitas do ResourceBarrier em recursos no estado comum, é correto usar D3D12_RESOURCE_STATE_COMMON ou qualquer estado passível de promoção como o valor BeforeState na estrutura D3D12_RESOURCE_TRANSITION_BARRIER. Isso permite o gerenciamento de estado tradicional que ignora a decadência automática de buffers e texturas de acesso simultâneo. No entanto, isso pode não ser desejável, pois evitar a transição de chamadas ResourceBarrier com recursos conhecidos por estarem no estado comum pode melhorar significativamente o desempenho. As barreiras de recursos podem ser caras. Eles foram projetados para forçar liberações de cache, alterações de layout de memória e outras sincronizações que podem não ser necessárias para recursos que já estão no estado comum. Uma lista de comandos que usa uma barreira de recursos de um estado não comum para outro estado não comum em um recurso atualmente no estado comum pode introduzir muita sobrecarga desnecessária.

Além disso, evite transições explícitas do ResourceBarrier para D3D12_RESOURCE_STATE_COMMON, a menos que seja absolutamente necessário (por exemplo, o próximo acesso está em uma fila de comando COPY que exige que os recursos comecem no estado comum). Transições excessivas para o estado comum podem reduzir drasticamente o desempenho da GPU.

Em resumo, tente contar com a promoção de estado comum e decai-la sempre que sua semântica permitir que você saia sem emitir chamadas resourceBarrier .

Dividir barreiras

Uma barreira de transição de recursos com o sinalizador D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY inicia uma barreira dividida e a barreira de transição é considerada pendente. Embora a barreira esteja pendente, o recurso (sub)não pode ser lido ou gravado pela GPU. A única barreira de transição legal que pode ser aplicada a um (sub)recurso com uma barreira pendente é aquela com os mesmos estados antes e depois e o sinalizador de D3D12_RESOURCE_BARRIER_FLAG_END_ONLY, que conclui a transição pendente.

As barreiras divididas fornecem dicas para a GPU de que um recurso no estado A será usado em seguida no estado B algum tempo depois. Isso dá à GPU a opção de otimizar a carga de trabalho de transição, possivelmente reduzindo ou eliminando paralisações de execução. A emissão da barreira somente final garante que todo o trabalho de transição de GPU seja concluído antes de passar para o próximo comando.

O uso de barreiras divididas pode ajudar a melhorar o desempenho, especialmente em cenários de vários mecanismos ou em que os recursos são de leitura/gravação com transição esparsa em uma ou mais listas de comandos.

Cenário de exemplo de barreira de recursos

Os snippets a seguir mostram o uso do método ResourceBarrier em um exemplo de vários threadings.

Criando a exibição de estêncil de profundidade, fazendo a transição para um estado gravável.

// 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());
}

Criando a exibição de buffer de vértice, primeiro alterando-a de um estado comum para um destino e, em seguida, de um destino para um estado legível genérico.

// 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());
    }

Criando a exibição de buffer de índice, primeiro alterando-a de um estado comum para um destino e, em seguida, de um destino para um estado legível genérico.

// 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;
}

Criando texturas e exibições de recursos de sombreador. A textura é alterada de um estado comum para um destino e, em seguida, de um destino para um recurso de sombreador de pixel.

    // 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);
    }

Iniciando um quadro; isso não só usa ResourceBarrier para indicar que o backbuffer deve ser usado como um destino de renderização, mas também inicializa o recurso de quadro (que chama ResourceBarrier no buffer de estêncil de profundidade).

// 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());
}

Encerrando um quadro, indicando que o buffer de fundo agora é usado para apresentar.

// 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());
}

Inicializar um recurso de quadro, chamado ao iniciar um quadro, faz a transição do buffer de estêncil de profundidade para gravável.

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()));
    }
}

As barreiras são trocadas no meio do quadro, fazendo a transição do mapa de sombra de gravável para legível.

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 é chamado quando um quadro é encerrado, fazendo a transição do mapa de sombra para um estado comum.

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));
}

Exemplo comum de decadência e promoção de estado

    // 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

Exemplo de barreiras divididas

O exemplo a seguir mostra como usar uma barreira dividida para reduzir as paradas de pipeline. O código a seguir não usa barreiras divididas:

 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

O código a seguir usa barreiras divididas:

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

Tutoriais de vídeo de aprendizado avançado do DirectX: Barreiras de Recursos e Acompanhamento de Estado

Sincronização de vários mecanismos

Envio de trabalho no Direct3D 12

Uma olhada dentro das barreiras de estado do recurso D3D12