Creazione e registrazione di elenchi di comandi e bundle

In questo argomento vengono descritti gli elenchi di comandi e i bundle nelle app Direct3D 12. Gli elenchi di comandi e i bundle consentono alle app di registrare chiamate di disegno o modifica dello stato per l'esecuzione successiva nell'unità di elaborazione grafica (GPU).

Oltre agli elenchi di comandi, le funzionalità dell'API sfruttano l'hardware della GPU aggiungendo un secondo livello di elenchi di comandi, denominati bundle. Lo scopo dei bundle è consentire alle app di raggruppare un numero ridotto di comandi API insieme per un'esecuzione successiva. Al momento della creazione del bundle, il driver eseguirà il maggior numero possibile di pre-elaborazione possibile per renderli economici per l'esecuzione in un secondo momento. I bundle sono progettati per essere usati e riutilizzati in qualsiasi numero di volte. Gli elenchi di comandi, invece, vengono in genere eseguiti solo una sola volta. Tuttavia, un elenco di comandi può essere eseguito più volte (purché l'applicazione assicuri che le esecuzioni precedenti siano state completate prima di inviare nuove esecuzioni).

In genere, la compilazione di chiamate API in bundle e chiamate e bundle API in elenchi di comandi e elenchi di comandi e elenchi di comandi in un singolo frame, è illustrata nel diagramma seguente, notando il riutilizzo di Bundle 1nell'elenco comandi 1 e nell'elenco comandi 2 e che i nomi dei metodi API nel diagramma sono esattamente come esempi, è possibile usare molte chiamate API diverse.

compilazione di comandi, bundle e elenchi di comandi in frame

Esistono diverse restrizioni per la creazione e l'esecuzione di bundle ed elenchi di comandi diretti e queste differenze vengono annotate in questo argomento.

Creazione di elenchi di comandi

Gli elenchi di comandi diretti e i bundle vengono creati chiamando ID3D12Device::CreateCommandList o ID3D12Device4::CreateCommandList1.

Usare ID3D12Device4::CreateCommandList1 per creare un elenco comandi chiuso, anziché creare un nuovo elenco e chiuderlo immediatamente. Ciò evita l'inefficacia della creazione di un elenco con un allocatore e un PSO, ma non li usa.

ID3D12Device::CreateCommandList accetta i parametri seguenti come input:

D3D12_COMMAND_LIST_TYPE

L'enumerazione D3D12_COMMAND_LIST_TYPE indica il tipo di elenco di comandi creato. Può essere un elenco di comandi diretto, un bundle, un elenco di comandi di calcolo o un elenco di comandi di copia.

ID3D12CommandAllocator

Un allocatore di comandi consente all'app di gestire la memoria allocata per gli elenchi di comandi. Il comando allocatore viene creato chiamando CreateCommandAllocator. Quando si crea un elenco di comandi, il tipo di elenco comandi dell'allocatore, specificato da D3D12_COMMAND_LIST_TYPE, deve corrispondere al tipo di elenco comandi creato. Un determinato allocatore può essere associato a non più di un elenco di comandi attualmente registrato alla volta, anche se un allocatore di comandi può essere usato per creare un numero qualsiasi di oggetti GraphicsCommandList .

Per recuperare la memoria allocata da un allocatore di comandi, un'app chiama ID3D12CommandAllocator::Reset. Ciò consente all'allocatore di essere riutilizzato per i nuovi comandi, ma non ridurrà le dimensioni sottostanti. Prima di farlo, tuttavia, l'app deve assicurarsi che la GPU non eseziona più gli elenchi di comandi associati all'allocatore; in caso contrario, la chiamata avrà esito negativo. Si noti anche che questa API non è thread gratuita e pertanto non può essere chiamata allo stesso allocatore contemporaneamente da più thread.

ID3D12PipelineState

Stato della pipeline iniziale per l'elenco dei comandi. In Microsoft Direct3D 12 la maggior parte dello stato della pipeline grafica viene impostata all'interno di un elenco di comandi usando l'oggetto ID3D12PipelineState . Un'app creerà un numero elevato di questi, in genere durante l'inizializzazione dell'app e quindi lo stato viene aggiornato modificando l'oggetto stato attualmente associato usando ID3D12GraphicsCommandList::SetPipelineState. Per altre informazioni sugli oggetti stato della pipeline, vedere Gestione dello stato della pipeline grafica in Direct3D 12.

Si noti che i bundle non ereditano lo stato della pipeline impostata dalle chiamate precedenti negli elenchi di comandi diretti che sono i loro genitori.

Se questo parametro è NULL, viene usato uno stato predefinito.

Elenchi di comandi di registrazione

Subito dopo la creazione, gli elenchi di comandi si trovano nello stato di registrazione. È anche possibile riutilizzare un elenco di comandi esistente chiamando ID3D12GraphicsCommandList::Reset, che lascia anche l'elenco dei comandi nello stato di registrazione. A differenza di ID3D12CommandAllocator::Reset, è possibile chiamare Reimposta mentre l'elenco di comandi è ancora in esecuzione. Un modello tipico consiste nell'inviare un elenco di comandi e quindi reimpostarlo immediatamente per riutilizzare la memoria allocata per un altro elenco di comandi. Si noti che un solo elenco di comandi associato a ogni allocatore di comandi può essere in uno stato di registrazione alla volta.

Una volta che un elenco di comandi si trova nello stato di registrazione, è sufficiente chiamare i metodi dell'interfaccia ID3D12GraphicsCommandList per aggiungere comandi all'elenco. Molti di questi metodi consentono funzionalità direct3D comuni che saranno familiari con gli sviluppatori di Microsoft Direct3D 11; altre API sono nuove per Direct3D 12.

Dopo l'aggiunta di comandi all'elenco di comandi, eseguire la transizione dell'elenco di comandi dallo stato di registrazione chiamando Close.

Gli allocatori dei comandi possono crescere ma non compattare: il pooling e la riutilizzo degli allocatori devono essere considerati per massimizzare l'efficienza dell'app. È possibile registrare più elenchi nello stesso allocatore prima della reimpostazione, purché un solo elenco sia registrato in un determinato allocatore alla volta. È possibile visualizzare ogni elenco come proprietario di una parte dell'allocatore che indica l'esecuzione di ID3D12CommandQueue::ExecuteCommandLists .

Una semplice strategia di pool di allocatori deve essere destinata a circa numCommandLists * MaxFrameLatency allocatori. Ad esempio, se si registrano 6 elenchi e si consentono fino a 3 fotogrammi latenti, è possibile prevedere ragionevolmente 18-20 allocatori. Una strategia di pooling più avanzata, che riutilizza gli allocatori per più elenchi nello stesso thread, potrebbe mirare agli numRecordingThreads * MaxFrameLatency allocatori. Usando l'esempio precedente, se 2 elenchi sono stati registrati nel thread A, 2 nel thread B, 1 nel thread C e 1 nel thread D, è possibile mirare realisticamente a 12-14 allocatori.

Usare una recinzione per determinare quando un determinato allocatore può essere riutilizzato.

Poiché gli elenchi di comandi possono essere immediatamente reimpostati dopo l'esecuzione, possono essere facilmente raggruppati, aggiungendoli nuovamente al pool dopo ogni chiamata a ID3D12CommandQueue::ExecuteCommandLists.

Esempio

I frammenti di codice seguenti illustrano la creazione e la registrazione di un elenco di comandi. Si noti che questo esempio include le funzionalità Direct3D 12 seguenti:

  • Oggetti di stato della pipeline: vengono usati per impostare la maggior parte dei parametri di stato della pipeline di rendering dall'interno di un elenco di comandi. Per altre informazioni, vedere Gestione dello stato della pipeline grafica in Direct3D 12.
  • Heap descrittore: le app usano gli heaps descrittori per gestire l'associazione della pipeline alle risorse di memoria.
  • Barriera delle risorse: viene usata per gestire la transizione delle risorse da uno stato a un altro, ad esempio da una visualizzazione di destinazione di rendering a una visualizzazione risorsa shader. Per altre informazioni, vedere Uso delle barriere delle risorse per sincronizzare gli stati delle risorse.

Ad esempio,

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

Dopo aver creato e registrato un elenco di comandi, può essere eseguito usando una coda di comandi. Per altre informazioni, vedere Esecuzione e sincronizzazione degli elenchi di comandi.

Conteggio riferimenti

La maggior parte delle API D3D12 continua a usare il conteggio dei riferimenti seguendo le convenzioni COM. Un'eccezione notevole a questa è l'API dell'elenco di comandi grafici D3D12. Tutte le API in ID3D12GraphicsCommandList non contengono riferimenti agli oggetti passati in tali API. Ciò significa che le applicazioni sono responsabili della garanzia che un elenco di comandi non venga mai inviato per l'esecuzione che fa riferimento a una risorsa eliminata.

Errori nell'elenco dei comandi

La maggior parte delle API in ID3D12GraphicsCommandList non restituisce errori. Gli errori rilevati durante la creazione dell'elenco di comandi vengono posticipati fino a ID3D12GraphicsCommandList::Close. L'unica eccezione è DXGI_ERROR_DEVICE_REMOVED, che viene posticipata ancora di più. Si noti che questo è diverso da D3D11, dove molti errori di convalida dei parametri vengono eliminati in modo invisibile all'utente e non vengono mai restituiti al chiamante.

Le applicazioni possono prevedere di visualizzare gli errori di DXGI_DEVICE_REMOVED nelle chiamate API seguenti:

Restrizioni api elenco comandi

Alcune API dell'elenco di comandi possono essere chiamate solo in determinati tipi di elenchi di comandi. La tabella seguente mostra quali API dell'elenco di comandi sono valide per chiamare su ogni tipo di elenco di comandi. Mostra anche quali API sono valide per chiamare in un passaggio di rendering D3D12.

Nome API Grafica Calcolo Copia Bundle In Passaggio di rendering
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

Restrizioni di bundle

Le restrizioni consentono ai driver Direct3D 12 di eseguire la maggior parte del lavoro associato ai bundle in fase di record, consentendo così l'esecuzione dell'API ExecuteBundle con un sovraccarico ridotto. Tutti gli oggetti dello stato della pipeline a cui fa riferimento un bundle devono avere gli stessi formati di destinazione di rendering, il formato del buffer di profondità e le descrizioni di esempio.

Le chiamate API dell'elenco di comandi seguenti non sono consentite negli elenchi di comandi creati con tipo: D3D12_COMMAND_LIST_TYPE_BUNDLE:

SetDescriptorHeaps può essere chiamato in un bundle, ma il descrittore di bundle deve corrispondere all'heap dell'heap dell'elenco comandi chiamante.

Se una di queste API viene chiamata in un bundle, il runtime elimina la chiamata. Il livello di debug genererà un errore ogni volta che si verifica.

Invio di lavoro in Direct3D 12