Erstellen und Aufzeichnen von Befehlslisten und Bundles

In diesem Thema werden Die Aufzeichnung von Befehlslisten und Bundles in Direct3D 12-Apps beschrieben. Befehlslisten und -bundles ermöglichen Apps das Aufzeichnen von Zeichnungs- oder Zustandsänderungsaufrufen für die spätere Ausführung auf der Grafikverarbeitungseinheit (GPU).

Neben Befehlslisten nutzt die API die in GPU-Hardware vorhandenen Funktionen aus, indem sie eine zweite Ebene von Befehlslisten hinzufügt, die als Bundles bezeichnet werden. Der Zweck von Bundles besteht darin, Apps das Gruppieren einer kleinen Anzahl von API-Befehlen für die spätere Ausführung zu ermöglichen. Zur Erstellungszeit des Bundles führt der Treiber so viel Vorverarbeitung wie möglich durch, damit diese später kostengünstig ausgeführt werden können. Bundles sind so konzipiert, dass sie beliebig oft verwendet und wiederverwendet werden können. Befehlslisten werden dagegen in der Regel nur ein einziges Mal ausgeführt. Eine Befehlsliste kann jedoch mehrmals ausgeführt werden (solange die Anwendung sicherstellt, dass die vorherigen Ausführungen abgeschlossen wurden, bevor neue Ausführungen übermittelt werden).

In der Regel wird jedoch der Aufbau von API-Aufrufen in Bündeln und API-Aufrufen und Bündeln in Befehlslisten und Befehlslisten in einem einzelnen Frame im folgenden Diagramm gezeigt, wobei die Wiederverwendung von Bundle 1 in Befehlsliste 1 und Befehlsliste 2 angegeben wird, und dass die API-Methodennamen im Diagramm nur beispiele sind: es können viele verschiedene API-Aufrufe verwendet werden.

Erstellen von Befehlen, Bundles und Befehlslisten in Frames

Es gibt verschiedene Einschränkungen für das Erstellen und Ausführen von Bundles und direkten Befehlslisten, und diese Unterschiede werden in diesem Thema beachtet.

Erstellen von Befehlslisten

Direkte Befehlslisten und Bundles werden erstellt, indem ID3D12Device::CreateCommandList oder ID3D12Device4::CreateCommandList1 aufgerufen wird.

Verwenden Sie ID3D12Device4::CreateCommandList1 , um eine geschlossene Befehlsliste zu erstellen, anstatt eine neue Liste zu erstellen und sie sofort zu schließen. Dadurch wird die Ineffizienz beim Erstellen einer Liste mit einem Allocator und PSO vermieden, aber nicht verwendet.

ID3D12Device::CreateCommandList verwendet die folgenden Parameter als Eingabe:

D3D12_COMMAND_LIST_TYPE

Die D3D12_COMMAND_LIST_TYPE-Enumeration gibt den Typ der Befehlsliste an, die erstellt wird. Es kann sich um eine direkte Befehlsliste, ein Bundle, eine Computebefehlsliste oder eine Kopierbefehlsliste handelt.

ID3D12CommandAllocator

Mit einer Befehlszuordnung kann die App den Arbeitsspeicher verwalten, der für Befehlslisten zugewiesen wird. Die Befehlszuordnung wird durch Aufrufen von CreateCommandAllocator erstellt. Beim Erstellen einer Befehlsliste muss der Von D3D12_COMMAND_LIST_TYPE angegebene Befehlslistentyp des Zuteilungstyps mit dem Typ der zu erstellenden Befehlsliste übereinstimmen. Ein bestimmter Zuteilungsgeber kann nicht mehr als einer gleichzeitigen Aufzeichnungsbefehlsliste zugeordnet werden, obwohl ein Befehlszuteilungsgeber verwendet werden kann, um eine beliebige Anzahl von GraphicsCommandList-Objekten zu erstellen.

Um den von einem Befehlszuteilungsgeber zugewiesenen Arbeitsspeicher wiederzugewinnen, ruft eine App ID3D12CommandAllocator::Reset auf. Dadurch kann der Zuweisungsgeber für neue Befehle wiederverwendet werden, die zugrunde liegende Größe wird jedoch nicht reduziert. Zuvor muss die App jedoch sicherstellen, dass die GPU keine Befehlslisten mehr ausführt, die dem Zuordnungsgeber zugeordnet sind. Andernfalls schlägt der Aufruf fehl. Beachten Sie außerdem, dass diese API nicht free-threaded ist und daher von mehreren Threads nicht gleichzeitig für denselben Zuteilungsgeber aufgerufen werden kann.

ID3D12PipelineState

Der anfängliche Pipelinestatus für die Befehlsliste. In Microsoft Direct3D 12 wird der meiste Grafikpipelinestatus in einer Befehlsliste mithilfe des ID3D12PipelineState-Objekts festgelegt. Eine App erstellt eine große Anzahl davon, in der Regel während der App-Initialisierung, und dann wird der Zustand aktualisiert, indem das aktuell gebundene Zustandsobjekt mithilfe von ID3D12GraphicsCommandList::SetPipelineState geändert wird. Weitere Informationen zu Pipelinestatusobjekten finden Sie unter Verwalten des Zustands der Grafikpipeline in Direct3D 12.

Beachten Sie, dass Bundles nicht den Pipelinestatus erben, der von vorherigen Aufrufen in direkten Befehlslisten festgelegt wurde, die ihre übergeordneten Elemente sind.

Wenn dieser Parameter NULL ist, wird ein Standardzustand verwendet.

Aufzeichnen von Befehlslisten

Unmittelbar nach der Erstellung befinden sich Befehlslisten im Aufzeichnungszustand. Sie können auch eine vorhandene Befehlsliste erneut verwenden, indem Sie ID3D12GraphicsCommandList::Reset aufrufen, wodurch die Befehlsliste auch im Aufzeichnungszustand verbleibt. Im Gegensatz zu ID3D12CommandAllocator::Reset können Sie Reset aufrufen, während die Befehlsliste noch ausgeführt wird. Ein typisches Muster besteht darin, eine Befehlsliste zu übermitteln und dann sofort zurückzusetzen, um den zugewiesenen Arbeitsspeicher für eine andere Befehlsliste wiederzuverwenden. Beachten Sie, dass sich nur eine Befehlsliste, die jedem Befehlszuweisungsbefehl zugeordnet ist, gleichzeitig in einem Aufzeichnungszustand befinden kann.

Sobald sich eine Befehlsliste im Aufzeichnungszustand befindet, rufen Sie einfach Methoden der ID3D12GraphicsCommandList-Schnittstelle auf, um der Liste Befehle hinzuzufügen. Viele dieser Methoden ermöglichen allgemeine Direct3D-Funktionen, die Microsoft Direct3D 11-Entwicklern vertraut sind. andere APIs sind neu für Direct3D 12.

Nachdem Sie der Befehlsliste Befehle hinzugefügt haben, wechseln Sie die Befehlsliste aus dem Aufzeichnungszustand, indem Sie Schließen aufrufen.

Befehlszuweisungen können wachsen, aber nicht verkleinern . Pooling und Wiederverwendung von Allocators sollten in Betracht gezogen werden, um die Effizienz Ihrer App zu maximieren. Sie können mehrere Listen auf denselben Zuordnungsator aufzeichnen, bevor sie zurückgesetzt wird, vorausgesetzt, nur eine Liste wird gleichzeitig an einen bestimmten Zuweisungsator aufgezeichnet. Sie können jede Liste als Besitzer eines Teils des Zuteilungselements visualisieren, der angibt, welche ID3D12CommandQueue::ExecuteCommandLists ausgeführt wird.

Eine einfache Allocatorpooling-Strategie sollte auf ungefähr numCommandLists * MaxFrameLatency Zuweisungen abzielen. Wenn Sie beispielsweise 6 Listen aufzeichnen und bis zu 3 latente Frames zulassen, können Sie vernünftigerweise mit 18 bis 20 Zuteilungen rechnen. Eine erweiterte Poolingstrategie, bei der Zuordnungen für mehrere Listen im selben Thread wiederverwendet werden, könnte auf numRecordingThreads * MaxFrameLatency Zuordnungen abzielen. Wenn im vorherigen Beispiel 2 Listen für Thread A, 2 für Thread B, 1 für Thread C und 1 für Thread D aufgezeichnet wurden, könnten Sie realistischerweise 12-14 Allokatoren anstreben.

Verwenden Sie einen Zaun, um zu bestimmen, wann ein bestimmter Zuordnungsgeber wiederverwendet werden kann.

Da Befehlslisten nach der Ausführung sofort zurückgesetzt werden können, können sie trivial gruppiert werden und nach jedem Aufruf von ID3D12CommandQueue::ExecuteCommandLists wieder dem Pool hinzugefügt werden.

Beispiel

Die folgenden Codeausschnitte veranschaulichen die Erstellung und Aufzeichnung einer Befehlsliste. Beachten Sie, dass dieses Beispiel die folgenden Direct3D 12-Features enthält:

Beispiel:

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

Nachdem eine Befehlsliste erstellt und aufgezeichnet wurde, kann sie mithilfe einer Befehlswarteschlange ausgeführt werden. Weitere Informationen finden Sie unter Ausführen und Synchronisieren von Befehlslisten.

Verweisanzahl

Die meisten D3D12-APIs verwenden weiterhin die Verweiszählung gemäß COM-Konventionen. Eine bemerkenswerte Ausnahme sind die D3D12-Grafikbefehlslisten-APIs. Alle APIs in ID3D12GraphicsCommandList enthalten keine Verweise auf die An diese APIs übergebenen Objekte. Dies bedeutet, dass Anwendungen dafür verantwortlich sind, sicherzustellen, dass niemals eine Befehlsliste zur Ausführung übermittelt wird, die auf eine zerstörte Ressource verweist.

Befehlslistenfehler

Die meisten APIs für ID3D12GraphicsCommandList geben keine Fehler zurück. Fehler, die beim Erstellen von Befehlslisten auftreten, werden bis ID3D12GraphicsCommandList::Close zurückgestellt. Die eine Ausnahme ist DXGI_ERROR_DEVICE_REMOVED, die noch weiter zurückgestellt wird. Beachten Sie, dass sich dies von D3D11 unterscheidet, bei dem viele Parameterüberprüfungsfehler automatisch gelöscht und nie an den Aufrufer zurückgegeben werden.

Anwendungen können erwarten, dass in den folgenden API-Aufrufen DXGI_DEVICE_REMOVED Fehler angezeigt werden:

Api-Einschränkungen für Befehlslisten

Einige Befehlslisten-APIs können nur für bestimmte Arten von Befehlslisten aufgerufen werden. Die folgende Tabelle zeigt, welche Befehlslisten-APIs für den Aufruf für jeden Typ von Befehlsliste gültig sind. Außerdem wird angezeigt, welche APIs für den Aufruf in einem D3D12-Renderdurchlauf gültig sind.

API-Name Grafiken Compute Kopieren Bundle Im Renderdurchlauf
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

Bundle-Einschränkungen

Einschränkungen ermöglichen Es Direct3D 12-Treibern, den Größten Teil der Arbeit im Zusammenhang mit Bundles zur Rekordzeit zu erledigen, sodass die ExecuteBundle-API mit geringem Mehraufwand ausgeführt werden kann. Alle Pipelinezustandsobjekte, auf die von einem Bundle verwiesen wird, müssen über die gleichen Renderzielformate, Tiefenpufferformate und Beispielbeschreibungen verfügen.

Die folgenden Befehlslisten-API-Aufrufe sind für Befehlslisten, die mit dem Typ erstellt wurden, nicht zulässig: D3D12_COMMAND_LIST_TYPE_BUNDLE:

SetDescriptorHeaps kann für ein Bundle aufgerufen werden, aber die Bundle-Deskriptorheaps müssen mit dem aufrufenden Befehlslistendeskriptorheap übereinstimmen.

Wenn eine dieser APIs für ein Bundle aufgerufen wird, wird der Aufruf von der Runtime gelöscht. Die Debugebene gibt immer dann einen Fehler aus.

Arbeitsübermittlung in Direct3D 12