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.
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:
- Qualsiasi metodo di creazione di risorse
- ID3D12Resource::Map
- IDXGISwapChain1::Present1
- GetDeviceRemovedReason
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:
- Qualsiasi metodo Clear
- Qualsiasi metodo Copy
- RimuoviResource
- ExecuteBundle
- ResourceBarrier
- ResolveSubresource
- SetPredication
- BeginQuery
- EndQuery
- SOSetTargets
- OMSetRenderTargets
- RSSetViewports
- RSSetScissorRects
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.