Utilisation de barrières de ressources pour synchroniser les états des ressources dans Direct3D 12

Pour réduire l’utilisation globale du processeur et activer le multithreading et le prétraitement des pilotes, Direct3D 12 déplace la responsabilité de la gestion de l’état par ressource du pilote graphique vers l’application. Un exemple d’état par ressource est de savoir si une ressource de texture est actuellement accessible par le biais d’une vue de ressource de nuanceur ou d’un affichage cible de rendu. Dans Direct3D 11, les pilotes devaient suivre cet état en arrière-plan. Cela est coûteux du point de vue du processeur et complique considérablement toute sorte de conception multithread. Dans Microsoft Direct3D 12, la plupart de l’état par ressource est géré par l’application avec une seule API, ID3D12GraphicsCommandList::ResourceBarrier.

Utilisation de l’API ResourceBarrier pour gérer l’état par ressource

ResourceBarrier avertit le pilote graphique des situations dans lesquelles le pilote peut avoir besoin de synchroniser plusieurs accès à la mémoire dans laquelle une ressource est stockée. La méthode est appelée avec une ou plusieurs structures de description de barrière de ressources indiquant le type de barrière de ressources déclaré.

Il existe trois types d’obstacles aux ressources :

  • Barrière de transition : une barrière de transition indique qu’un ensemble de sous-ressources effectue la transition entre différentes utilisations. Une structure D3D12_RESOURCE_TRANSITION_BARRIER est utilisée pour spécifier la sous-ressource en cours de transition, ainsi que les états avant et après de la sous-ressource.

    Le système vérifie que les transitions de sous-ressources dans une liste de commandes sont cohérentes avec les transitions précédentes dans la même liste de commandes. La couche de débogage suit davantage l’état de sous-ressource pour rechercher d’autres erreurs, mais cette validation est conservatrice et non exhaustive.

    Notez que vous pouvez utiliser l’indicateur D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES pour spécifier que toutes les sous-ressources d’une ressource sont en cours de transition.

  • Barrière d’aliasing : une barrière d’aliasing indique une transition entre les utilisations de deux ressources différentes qui ont des mappages qui se chevauchent dans le même tas. Cela s’applique aux ressources réservées et placées. Une structure D3D12_RESOURCE_ALIASING_BARRIER est utilisée pour spécifier à la fois la ressource avant et la ressource après .

    Notez qu’une ou les deux ressources peuvent avoir la valeur NULL, ce qui indique que n’importe quelle ressource en mosaïque peut provoquer un alias. Pour plus d’informations sur l’utilisation de ressources en mosaïques, consultez Ressources en mosaïques et Ressources en mosaïque en volume.

  • Barrière d’affichage d’accès non ordonné (UAV) : une barrière UAV indique que tous les accès par UAV, en lecture ou en écriture, à une ressource particulière doivent se terminer entre tous les futurs accès UAV, en lecture ou en écriture. Il n’est pas nécessaire qu’une application place une barrière UAV entre deux appels de tirage ou de distribution qui lisent uniquement à partir d’un UAV. En outre, il n’est pas nécessaire de placer une barrière UAV entre deux appels de tirage ou de distribution qui écrivent sur le même UAV si l’application sait qu’il est sûr d’exécuter l’accès À l’UAV dans n’importe quel ordre. Une structure D3D12_RESOURCE_UAV_BARRIER est utilisée pour spécifier la ressource UAV à laquelle la barrière s’applique. L’application peut spécifier NULL pour l’UAV de la barrière, ce qui indique que tout accès UAV peut nécessiter la barrière.

Lorsque ResourceBarrier est appelé avec un tableau de descriptions de barrière de ressources, l’API se comporte comme si elle avait été appelée une fois pour chaque élément, dans l’ordre dans lequel elles ont été fournies.

À tout moment, une sous-ressource se trouve exactement dans un état, déterminé par l’ensemble d’indicateurs D3D12_RESOURCE_STATES fournis à ResourceBarrier. L’application doit s’assurer que les états avant et après des appels consécutifs à ResourceBarrier sont d’accord.

Conseil

Les applications doivent traiter par lot plusieurs transitions en un seul appel d’API dans la mesure du possible.

 

États des ressources

Pour obtenir la liste complète des états de ressource entre lequel une ressource peut effectuer la transition, consultez la rubrique de référence pour l’énumération D3D12_RESOURCE_STATES .

Pour les barrières de ressources fractionnées, reportez-vous également à D3D12_RESOURCE_BARRIER_FLAGS.

États initiaux des ressources

Les ressources peuvent être créées avec n’importe quel état initial spécifié par l’utilisateur (valide pour la description de la ressource), avec les exceptions suivantes :

  • Les tas de chargement doivent commencer dans l’état D3D12_RESOURCE_STATE_GENERIC_READ qui est une combinaison de bits OU 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
  • Les tas de récupération doivent commencer dans l’état D3D12_RESOURCE_STATE_COPY_DEST.
  • Les mémoires tampons d’arrière de chaîne d’échange démarrent automatiquement à l’état D3D12_RESOURCE_STATE_COMMON.

Avant qu’un tas puisse être la cible d’une opération de copie GPU, le tas doit normalement être passé à l’état D3D12_RESOURCE_STATE_COPY_DEST. Toutefois, les ressources créées sur les tas DE CHARGEMENT doivent démarrer dans et ne peuvent pas changer à partir de l’état GENERIC_READ, car seul le processeur effectue l’écriture. À l’inverse, les ressources validées créées dans les tas READBACK doivent démarrer dans et ne peuvent pas changer à partir de l’état COPY_DEST.

Restrictions d’état des ressources en lecture-écriture

Les bits d’utilisation de l’état des ressources utilisés pour décrire un état de ressource sont divisés en états en lecture seule et en lecture/écriture. La rubrique de référence pour le D3D12_RESOURCE_STATES indique le niveau d’accès en lecture/écriture pour chaque bit de l’énumération.

Au maximum, un seul bit de lecture/écriture peut être défini pour n’importe quelle ressource. Si un bit d’écriture est défini, aucun bit en lecture seule ne peut être défini pour cette ressource. Si aucun bit d’écriture n’est défini, un nombre quelconque de bits de lecture peut être défini.

États des ressources pour la présentation des mémoires tampons de retour

Avant qu’une mémoire tampon arrière ne soit présentée, elle doit être à l’état D3D12_RESOURCE_STATE_COMMON. Notez que l’état de la ressource D3D12_RESOURCE_STATE_PRESENT est synonyme de D3D12_RESOURCE_STATE_COMMON, et qu’ils ont tous deux la valeur 0. Si IDXGISwapChain::P resent (ou IDXGISwapChain1::P resent1) est appelé sur une ressource qui n’est pas actuellement dans cet état, un avertissement de couche de débogage est émis.

Abandon de ressources

Toutes les sous-ressources d’une ressource doivent être à l’état RENDER_TARGET, ou à l’état DEPTH_WRITE, respectivement pour les cibles de rendu/les ressources de gabarit de profondeur, lorsque ID3D12GraphicsCommandList::D iscardResource est appelé.

Transitions d’état implicites

Les ressources ne peuvent être « promues » qu’à partir de D3D12_RESOURCE_STATE_COMMON. De même, les ressources ne se décomposeront qu’en D3D12_RESOURCE_STATE_COMMON.

Promotion d’état courante

Toutes les ressources de mémoire tampon ainsi que les textures avec le jeu d’indicateurs de D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS sont implicitement promues de D3D12_RESOURCE_STATE_COMMON à l’état approprié lors du premier accès GPU, y compris GENERIC_READ pour couvrir n’importe quel scénario de lecture. Toute ressource à l’état COMMON est accessible, car elle était dans un état unique avec

1 indicateur WRITE ou 1 ou plusieurs indicateurs READ

Les ressources peuvent être promues à partir de l’état COMMON en fonction du tableau suivant :

Indicateur d’état Mémoires tampons et textures Simultaneous-Access Textures d’accès non simultané
VERTEX_AND_CONSTANT_BUFFER Oui Non
INDEX_BUFFER Oui Non
RENDER_TARGET Oui Non
UNORDERED_ACCESS Oui Non
DEPTH_WRITE Non* Non
DEPTH_READ Non* Non
NON_PIXEL_SHADER_RESOURCE Oui Oui
PIXEL_SHADER_RESOURCE Oui Oui
STREAM_OUT Oui Non
INDIRECT_ARGUMENT Oui Non
COPY_DEST Oui Oui
COPY_SOURCE Oui Oui
RESOLVE_DEST Oui Non
RESOLVE_SOURCE Oui Non
PRÉDICATION Oui Non

 

*Les ressources de gabarit de profondeur doivent être des textures à accès non simultané et ne peuvent donc jamais être promues implicitement.

Lorsque cet accès se produit, la promotion agit comme un obstacle implicite aux ressources. Pour les accès suivants, les barrières de ressources devront modifier l’état de la ressource si nécessaire. Notez que la promotion d’un état de lecture promu vers plusieurs états de lecture est valide, mais ce n’est pas le cas pour les états d’écriture.
Par exemple, si une ressource à l’état commun est promue en PIXEL_SHADER_RESOURCE dans un appel Draw, elle peut toujours être promue en NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE dans un autre appel Draw. Toutefois, s’il est utilisé dans une opération d’écriture, telle qu’une destination de copie, une barrière de transition d’état de ressource à partir des états de lecture promus combinés, ici NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE, pour COPY_DEST est nécessaire.
De même, s’il est promu de COMMON à COPY_DEST, un obstacle est toujours nécessaire pour passer d’COPY_DEST à RENDER_TARGET.

Notez que la promotion d’état courante est « gratuite », car il n’est pas nécessaire que le GPU effectue des attentes de synchronisation. La promotion représente le fait que les ressources à l’état COMMON ne doivent pas nécessiter de travail GPU ou de suivi de pilote supplémentaire pour prendre en charge certains accès.

État de décroissance en commun

Le revers de la promotion de l’État commun est la décadence à D3D12_RESOURCE_STATE_COMMON. Les ressources qui répondent à certaines exigences sont considérées comme sans état et retournent effectivement à l’état commun lorsque le GPU termine l’exécution d’une opération ExecuteCommandLists . La décroissance ne se produit pas entre les listes de commandes exécutées ensemble dans le même appel ExecuteCommandLists .

Les ressources suivantes se décomposent lorsqu’une opération ExecuteCommandLists est terminée sur le GPU :

  • Ressources accessibles dans une file d’attente de copie, ou
  • Mettre en mémoire tampon les ressources sur n’importe quel type de file d’attente, ou
  • Ressources de texture sur n’importe quel type de file d’attente dont l’indicateur D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS est défini, ou
  • Toute ressource implicitement promue à un état en lecture seule.

Comme la promotion d’état courante, la désintégration est gratuite en ce qu’aucune synchronisation supplémentaire n’est nécessaire. La combinaison de la promotion et de la désintégration d’état courantes peut aider à éliminer de nombreuses transitions ResourceBarrier inutiles. Dans certains cas, cela peut apporter des améliorations significatives des performances.

Les mémoires tampons et les ressources Simultaneous-Access passent à l’état commun, qu’elles aient été explicitement transférées à l’aide de barrières de ressources ou promues implicitement.

Impact sur les performances

Lorsque vous enregistrez des transitions ResourceBarrier explicites sur des ressources à l’état commun, il est correct d’utiliser D3D12_RESOURCE_STATE_COMMON ou n’importe quel état pouvant être promotionnel comme valeur BeforeState dans la structure D3D12_RESOURCE_TRANSITION_BARRIER. Cela permet une gestion d’état traditionnelle qui ignore la désintégration automatique des mémoires tampons et des textures d’accès simultané. Cela peut toutefois ne pas être souhaitable, car éviter les appels ResourceBarrier de transition avec des ressources connues pour être dans l’état commun peut améliorer considérablement les performances. Les obstacles aux ressources peuvent être coûteux. Ils sont conçus pour forcer les vidages du cache, les modifications de disposition de la mémoire et d’autres synchronisations qui peuvent ne pas être nécessaires pour les ressources déjà dans l’état commun. Une liste de commandes qui utilise une barrière de ressources d’un état non commun à un autre état non commun sur une ressource actuellement dans l’état commun peut introduire beaucoup de surcharge inutile.

En outre, évitez les transitions explicites de ResourceBarrier vers D3D12_RESOURCE_STATE_COMMON, sauf si cela est absolument nécessaire (par exemple, l’accès suivant se trouve dans une file d’attente de commandes COPY qui nécessite que les ressources commencent dans l’état commun). Les transitions excessives vers l’état commun peuvent ralentir considérablement les performances du GPU.

En résumé, essayez de vous appuyer sur la promotion et la décroissance d’état courantes chaque fois que sa sémantique vous permet de vous en sortir sans émettre d’appels ResourceBarrier .

Cloisons fractionnées

Une barrière de transition des ressources avec l’indicateur D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY commence une barrière de fractionnement et la barrière de transition est dite en attente. Pendant que la barrière est en attente, la ressource (sous-)ne peut pas être lue ou écrite par le GPU. Le seul obstacle de transition juridique qui peut être appliqué à une (sous-)ressource avec un obstacle en attente est un avec les mêmes états avant et après et l’indicateur D3D12_RESOURCE_BARRIER_FLAG_END_ONLY, qui termine la transition en attente.

Les barrières fractionnées fournissent au GPU des indications indiquant qu’une ressource à l’état A sera ensuite utilisée à l’état B un peu plus tard. Cela donne au GPU la possibilité d’optimiser la charge de travail de transition, ce qui peut réduire ou éliminer les blocages d’exécution. L’émission de la barrière de fin uniquement garantit que tout le travail de transition GPU est terminé avant de passer à la commande suivante.

L’utilisation de barrières fractionnées peut aider à améliorer les performances, en particulier dans les scénarios multi-moteurs ou lorsque les ressources sont transférées de manière éparse en lecture/écriture dans une ou plusieurs listes de commandes.

Exemple de scénario de barrière de ressources

Les extraits de code suivants montrent l’utilisation de la méthode ResourceBarrier dans un exemple de multithreading.

Création de la vue de gabarit de profondeur, transition vers un état accessible en écriture.

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

Création de l’affichage de la mémoire tampon de vertex, tout d’abord en passant d’un état commun à une destination, puis d’une destination à un état générique lisible.

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

Création de l’affichage de la mémoire tampon d’index, d’abord en passant d’un état commun à une destination, puis d’une destination à un état générique lisible.

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

Création de textures et d’affichages de ressources de nuanceur. La texture passe d’un état commun à une destination, puis d’une destination à une ressource de nuanceur de pixels.

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

Début d’un cadre ; Cela utilise non seulement ResourceBarrier pour indiquer que le backbuffer doit être utilisé comme cible de rendu, mais initialise également la ressource frame (qui appelle ResourceBarrier sur la mémoire tampon de gabarit de profondeur).

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

Fin d’un frame, indiquant que la mémoire tampon d’arrière-mémoire est maintenant utilisée pour présenter.

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

L’initialisation d’une ressource frame, appelée lors du démarrage d’un frame, fait passer la mémoire tampon de gabarit de profondeur en mémoire tampon accessible en écriture.

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

Les barrières sont permutées au milieu de l’image, ce qui fait passer la carte fantôme de la carte en écriture à la lecture.

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 est appelé lorsqu’un frame est terminé, ce qui fait passer le shadow map à un état commun.

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

Exemple de promotion et de désintégration d’état courants

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

Exemple de barrières fractionnées

L’exemple suivant montre comment utiliser une barrière de fractionnement pour réduire les blocages de pipeline. Le code qui suit n’utilise pas de barrières fractionnées :

 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

Le code suivant utilise des barrières fractionnées :

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

Tutoriels vidéo sur l’apprentissage avancé DirectX : Obstacles aux ressources et suivi de l’état

Synchronisation multi-moteur

Envoi de travail dans Direct3D 12

Vue d’ensemble des barrières d’état des ressources D3D12