Partager via


Création et enregistrement de listes de commandes et de bundles

Cette rubrique décrit l’enregistrement des listes de commandes et des bundles dans les applications Direct3D 12. Les listes et les bundles de commandes permettent aux applications d’enregistrer des appels de dessin ou de changement d’état pour une exécution ultérieure sur l’unité de traitement graphique (GPU).

Au-delà des listes de commandes, l’API exploite les fonctionnalités présentes dans le matériel GPU en ajoutant un deuxième niveau de listes de commandes, appelées bundles. L’objectif des offres groupées est de permettre aux applications de regrouper un petit nombre de commandes d’API pour une exécution ultérieure. Au moment de la création de l’offre groupée, le pilote effectue autant de prétraitements que possible pour les exécuter plus tard. Les offres groupées sont conçues pour être utilisées et réutilisées à tout moment. En revanche, les listes de commandes ne sont généralement exécutées qu’une seule fois. Toutefois, une liste de commandes peut être exécutée plusieurs fois (à condition que l’application s’assure que les exécutions précédentes sont terminées avant d’envoyer de nouvelles exécutions).

En règle générale, la génération des appels d’API dans des bundles, et des appels d’API et des bundles dans des listes de commandes, et des listes de commandes dans une trame unique, est illustrée dans le diagramme suivant, notant la réutilisation de l’ensemble 1 dans la liste de commandes 1 et la liste de commandes 2, et que les noms des méthodes d’API dans le diagramme sont justes des exemples, De nombreux appels d’API différents peuvent être utilisés.

génération de commandes, d’offres groupées et de listes de commandes dans des cadres

Il existe différentes restrictions pour la création et l’exécution de bundles et de listes de commandes directes, et ces différences sont notées dans cette rubrique.

Création de listes de commandes

Les listes de commandes directes et les bundles sont créés en appelant ID3D12Device::CreateCommandList ou ID3D12Device4::CreateCommandList1.

Utilisez ID3D12Device4::CreateCommandList1 pour créer une liste de commandes fermée, au lieu de créer une liste et de la fermer immédiatement. Cela permet d’éviter l’inefficacité de la création d’une liste avec un allocateur et un psO, mais de ne pas les utiliser.

ID3D12Device::CreateCommandList prend les paramètres suivants en entrée :

D3D12_COMMAND_LIST_TYPE

L’énumération D3D12_COMMAND_LIST_TYPE indique le type de liste de commandes en cours de création. Il peut s’agir d’une liste de commandes directes, d’un bundle, d’une liste de commandes de calcul ou d’une liste de commandes de copie.

ID3D12CommandAllocator

Un répartiteur de commandes permet à l’application de gérer la mémoire allouée aux listes de commandes. L’allocateur de commande est créé en appelant CreateCommandAllocator. Lors de la création d’une liste de commandes, le type de liste de commandes de l’allocateur, spécifié par D3D12_COMMAND_LIST_TYPE, doit correspondre au type de liste de commandes en cours de création. Un allocateur donné ne peut être associé qu’à une liste de commandes en cours d’enregistrement à la fois, bien qu’un seul allocateur de commandes puisse être utilisé pour créer un nombre quelconque d’objets GraphicsCommandList .

Pour récupérer la mémoire allouée par un allocateur de commande, une application appelle ID3D12CommandAllocator::Reset. Cela permet à l’allocateur d’être réutilisé pour de nouvelles commandes, mais ne réduit pas sa taille sous-jacente. Mais avant cela, l’application doit s’assurer que le GPU n’exécute plus de listes de commandes associées à l’allocateur ; sinon, l’appel échoue. Notez également que cette API n’est pas à thread libre et ne peut donc pas être appelée sur le même allocateur en même temps à partir de plusieurs threads.

ID3D12PipelineState

État initial du pipeline pour la liste de commandes. Dans Microsoft Direct3D 12, l’état de la plupart des pipelines graphiques est défini dans une liste de commandes à l’aide de l’objet ID3D12PipelineState . Une application en crée un grand nombre, généralement pendant l’initialisation de l’application, puis l’état est mis à jour en modifiant l’objet d’état actuellement lié à l’aide de ID3D12GraphicsCommandList::SetPipelineState. Pour plus d’informations sur les objets d’état de pipeline, consultez Gestion de l’état du pipeline graphique dans Direct3D 12.

Notez que les bundles n’héritent pas de l’état du pipeline défini par les appels précédents dans les listes de commandes directes qui sont leurs parents.

Si ce paramètre a la valeur NULL, un état par défaut est utilisé.

Enregistrement des listes de commandes

Immédiatement après leur création, les listes de commandes sont à l’état d’enregistrement. Vous pouvez également réutiliser une liste de commandes existante en appelant ID3D12GraphicsCommandList::Reset, ce qui laisse également la liste de commandes dans l’état d’enregistrement. Contrairement à ID3D12CommandAllocator::Reset, vous pouvez appeler Reset pendant l’exécution de la liste de commandes. Un modèle classique consiste à envoyer une liste de commandes, puis à la réinitialiser immédiatement pour réutiliser la mémoire allouée pour une autre liste de commandes. Notez qu’une seule liste de commandes associée à chaque allocateur de commandes peut être dans un état d’enregistrement à la fois.

Une fois qu’une liste de commandes est à l’état d’enregistrement, il vous suffit d’appeler les méthodes de l’interface ID3D12GraphicsCommandList pour ajouter des commandes à la liste. Bon nombre de ces méthodes permettent d’activer des fonctionnalités Direct3D communes qui seront familières aux développeurs Microsoft Direct3D 11 ; d’autres API sont nouvelles pour Direct3D 12.

Après avoir ajouté des commandes à la liste de commandes, vous sortez la liste de commandes de l’état d’enregistrement en appelant Fermer.

Les allocateurs de commandes peuvent croître, mais ne se réduisent pas : le regroupement et la réutilisation des allocateurs doivent être pris en compte pour optimiser l’efficacité de votre application. Vous pouvez enregistrer plusieurs listes dans le même allocateur avant sa réinitialisation, à condition qu’une seule liste s’enregistre sur un allocateur donné à la fois. Vous pouvez visualiser chaque liste comme propriétaire d’une partie de l’allocateur, ce qui indique ce qu’ID3D12CommandQueue::ExecuteCommandLists exécutera.

Une stratégie de regroupement d’allocateurs simple doit viser approximativement numCommandLists * MaxFrameLatency les allocateurs. Par exemple, si vous enregistrez 6 listes et autorisez jusqu’à 3 images latentes, vous pouvez raisonnablement vous attendre à 18 à 20 allocateurs. Une stratégie de regroupement plus avancée, qui réutilise les allocateurs pour plusieurs listes sur le même thread, peut viser numRecordingThreads * MaxFrameLatency des allocateurs. En utilisant l’exemple précédent, si 2 listes ont été enregistrées sur le thread A, 2 sur le thread B, 1 sur le thread C et 1 sur le thread D, vous pouvez viser de manière réaliste 12-14 allocators.

Utilisez une clôture pour déterminer quand un allocateur donné peut être réutilisé.

Comme les listes de commandes peuvent être réinitialisées immédiatement après l’exécution, elles peuvent être regroupées de manière triviale, en les ajoutant au pool après chaque appel à ID3D12CommandQueue::ExecuteCommandLists.

Exemple

Les extraits de code suivants illustrent la création et l’enregistrement d’une liste de commandes. Notez que cet exemple inclut les fonctionnalités Direct3D 12 suivantes :

Par exemple,

void D3D12HelloTriangle::LoadAssets()
{
    // Create an empty root signature.
    {
        CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
        rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

        ComPtr<ID3DBlob> signature;
        ComPtr<ID3DBlob> error;
        ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
        ThrowIfFailed(m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&m_rootSignature)));
    }

    // Create the pipeline state, which includes compiling and loading shaders.
    {
        ComPtr<ID3DBlob> vertexShader;
        ComPtr<ID3DBlob> pixelShader;

#if defined(_DEBUG)
        // Enable better shader debugging with the graphics debugging tools.
        UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
        UINT compileFlags = 0;
#endif

        ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr));
        ThrowIfFailed(D3DCompileFromFile(GetAssetFullPath(L"shaders.hlsl").c_str(), nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr));

        // Define the vertex input layout.
        D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
            { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
        };

        // Describe and create the graphics pipeline state object (PSO).
        D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
        psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
        psoDesc.pRootSignature = m_rootSignature.Get();
        psoDesc.VS = { reinterpret_cast<UINT8*>(vertexShader->GetBufferPointer()), vertexShader->GetBufferSize() };
        psoDesc.PS = { reinterpret_cast<UINT8*>(pixelShader->GetBufferPointer()), pixelShader->GetBufferSize() };
        psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
        psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
        psoDesc.DepthStencilState.DepthEnable = FALSE;
        psoDesc.DepthStencilState.StencilEnable = FALSE;
        psoDesc.SampleMask = UINT_MAX;
        psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
        psoDesc.NumRenderTargets = 1;
        psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
        psoDesc.SampleDesc.Count = 1;
        ThrowIfFailed(m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_pipelineState)));
    }

    // Create the command list.
    ThrowIfFailed(m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator.Get(), m_pipelineState.Get(), IID_PPV_ARGS(&m_commandList)));

    // Command lists are created in the recording state, but there is nothing
    // to record yet. The main loop expects it to be closed, so close it now.
    ThrowIfFailed(m_commandList->Close());

    // Create the vertex buffer.
    {
        // Define the geometry for a triangle.
        Vertex triangleVertices[] =
        {
            { { 0.0f, 0.25f * m_aspectRatio, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
            { { 0.25f, -0.25f * m_aspectRatio, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
            { { -0.25f, -0.25f * m_aspectRatio, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
        };

        const UINT vertexBufferSize = sizeof(triangleVertices);

        // Note: using upload heaps to transfer static data like vert buffers is not 
        // recommended. Every time the GPU needs it, the upload heap will be marshalled 
        // over. Please read up on Default Heap usage. An upload heap is used here for 
        // code simplicity and because there are very few verts to actually transfer.
        ThrowIfFailed(m_device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize),
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&m_vertexBuffer)));

        // Copy the triangle data to the vertex buffer.
        UINT8* pVertexDataBegin;
        CD3DX12_RANGE readRange(0, 0);        // We do not intend to read from this resource on the CPU.
        ThrowIfFailed(m_vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin)));
        memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
        m_vertexBuffer->Unmap(0, nullptr);

        // Initialize the vertex buffer view.
        m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
        m_vertexBufferView.StrideInBytes = sizeof(Vertex);
        m_vertexBufferView.SizeInBytes = vertexBufferSize;
    }

    // Create synchronization objects and wait until assets have been uploaded to the GPU.
    {
        ThrowIfFailed(m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)));
        m_fenceValue = 1;

        // Create an event handle to use for frame synchronization.
        m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
        if (m_fenceEvent == nullptr)
        {
            ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
        }

        // Wait for the command list to execute; we are reusing the same command 
        // list in our main loop but for now, we just want to wait for setup to 
        // complete before continuing.
        WaitForPreviousFrame();
    }
}

Une fois qu’une liste de commandes a été créée et enregistrée, elle peut être exécutée à l’aide d’une file d’attente de commandes. Pour plus d’informations, consultez Exécution et synchronisation des listes de commandes.

Décompte de références

La plupart des API D3D12 continuent d’utiliser le comptage de références conformément aux conventions COM. Les API de liste de commandes graphiques D3D12 constituent une exception notable. Toutes les API sur ID3D12GraphicsCommandList ne contiennent pas de références aux objets passés dans ces API. Cela signifie que les applications sont chargées de s’assurer qu’une liste de commandes n’est jamais envoyée pour exécution qui fait référence à une ressource détruite.

Erreurs de liste de commandes

La plupart des API sur ID3D12GraphicsCommandList ne retournent pas d’erreurs. Les erreurs rencontrées lors de la création de la liste de commandes sont différées jusqu’à ID3D12GraphicsCommandList::Close. La seule exception est DXGI_ERROR_DEVICE_REMOVED, qui est encore plus différé. Notez que cela est différent de D3D11, où de nombreuses erreurs de validation de paramètre sont supprimées en mode silencieux et ne sont jamais retournées à l’appelant.

Les applications peuvent s’attendre à voir DXGI_DEVICE_REMOVED erreurs dans les appels d’API suivants :

Restrictions de l’API de liste de commandes

Certaines API de liste de commandes ne peuvent être appelées que sur certains types de listes de commandes. Le tableau ci-dessous indique les API de liste de commandes qui peuvent être appelées sur chaque type de liste de commandes. Il montre également quelles API sont valides pour appeler dans une passe de rendu D3D12.

Nom de l’API Graphiques Calcul Copier Bundle Dans la passe de rendu
AtomicCopyBufferUINT
AtomicCopyBufferUINT64
BeginQuery
BeginRenderPass
BuildRaytracingAccelerationStructure
ClearDepthStencilView
ClearRenderTargetView
ClearState
ClearUnorderedAccessViewFloat
ClearUnorderedAccessViewUint
CopyBufferRegion
CopyRaytracingAccelerationStructure
CopyResource
CopyTextureRegion
CopyTiles
DiscardResource
Dispatch
DispatchRays
DrawIndexedInstanced
DrawInstanced
EmitRaytracingAccelerationStructurePostbuildInfo
EndQuery
EndRenderPass
ExecuteBundle
ExecuteIndirect
ExecuteMetaCommand
IASetIndexBuffer
IASetPrimitiveTopology
IASetVertexBuffers
InitializeMetaCommand
OMSetBlendFactor
OMSetDepthBounds
OMSetRenderTargets
OMSetStencilRef
ResolveQueryData
ResolveSubresource
ResolveSubresourceRegion
ResourceBarrier
RSSetScissorRects
RSSetShadingRate
RSSetShadingRateImage
RSSetViewports
SetComputeRoot32BitConstant
SetComputeRoot32BitConstants
SetComputeRootConstantBufferView
SetComputeRootDescriptorTable
SetComputeRootShaderResourceView
SetComputeRootSignature
SetComputeRootUnorderedAccessView
SetDescriptorHeaps
SetGraphicsRoot32BitConstant
SetGraphicsRoot32BitConstants
SetGraphicsRootConstantBufferView
SetGraphicsRootDescriptorTable
SetGraphicsRootShaderResourceView
SetGraphicsRootSignature
SetGraphicsRootUnorderedAccessView
SetPipelineState
SetPipelineState1
SetPredication
SetProtectedResourceSession
SetSamplePositions
SetViewInstanceMask
SOSetTargets
WriteBufferImmediate

Restrictions des offres groupées

Les restrictions permettent aux pilotes Direct3D 12 d’effectuer la plupart du travail associé aux offres groupées au moment de l’enregistrement, ce qui permet à l’API ExecuteBundle d’être exécutée avec une faible surcharge. Tous les objets d’état de pipeline référencés par un bundle doivent avoir les mêmes formats cibles de rendu, le même format de mémoire tampon de profondeur et les mêmes exemples de descriptions.

Les appels d’API de liste de commandes suivants ne sont pas autorisés sur les listes de commandes créées avec le type : D3D12_COMMAND_LIST_TYPE_BUNDLE :

SetDescriptorHeaps peut être appelé sur un bundle, mais les tas de descripteurs de bundle doivent correspondre au tas de descripteur de la liste de commandes appelante.

Si l’une de ces API est appelée sur un bundle, le runtime supprime l’appel. La couche de débogage génère une erreur chaque fois que cela se produit.

Envoi de travail dans Direct3D 12