建立和記錄命令清單和組合

本主題描述 Direct3D 12 應用程式中的錄製命令清單和套件組合。 命令清單和套件組合都允許應用程式記錄繪圖或狀態變更呼叫,以便在圖形處理單元上稍後執行, (GPU) 。

除了命令清單之外,API 會藉由新增第二層命令清單,以利用 GPU 硬體中存在的功能,這稱為 套件組合。 套件組合的目的是允許應用程式將少數 API 命令群組在一起,以供稍後執行。 在套件組合建立期間,驅動程式會盡可能執行大部分的前置處理,讓稍後執行這些成本較低。 套件組合是設計成使用及重複使用任何次數。 另一方面,命令清單通常只會執行一次。 不過,只要應用程式確保先前的執行已完成,就可以 (多次執行 命令清單, 然後再提交新的執行) 。

不過,一般而言,將 API 呼叫建置成套件組合,並將 API 呼叫和套件組合建置成命令清單,並將命令清單顯示在下圖中,並指出在命令清單 1命令清單 2中重複使用套件組合 1,而且圖表中的 API 方法名稱就像範例一樣。 您可以使用許多不同的 API 呼叫。

將命令、套件組合和命令清單建置成框架

建立和執行套件組合和直接命令清單有不同的限制,而且本主題中會說明這些差異。

建立命令清單

直接命令清單和套件組合的建立方式是呼叫 ID3D12Device::CreateCommandListID3D12Device4::CreateCommandList1

使用 ID3D12Device4::CreateCommandList1 建立已關閉的命令清單,而不是建立新的清單並立即關閉它。 這可避免使用配置器和 PSO 建立清單的效率不佳,但不會使用這些清單。

ID3D12Device::CreateCommandList 會採用下列參數作為輸入:

D3D12_COMMAND_LIST_TYPE

D3D12_COMMAND_LIST_TYPE列舉表示所建立的命令清單類型。 它可以是直接命令清單、套件組合、計算命令清單或複製命令清單。

ID3D12CommandAllocator

命令配置器可讓應用程式管理配置給命令清單的記憶體。 命令配置器是藉由呼叫 CreateCommandAllocator 來建立。 建立命令清單時, 由 D3D12_COMMAND_LIST_TYPE所指定配置器的命令清單類型必須與所建立的命令清單類型相符。 指定的配置器一次只能與 一個目前錄製 的命令清單相關聯,但一個命令配置器可用來建立任意數目的 GraphicsCommandList 物件。

若要回收命令配置器所配置的記憶體,應用程式會呼叫 ID3D12CommandAllocator::Reset。 這可讓配置器重複使用給新的命令,但不會減少其基礎大小。 但在這麼做之前,應用程式必須確定 GPU 不再執行與配置器相關聯的任何命令清單;否則,呼叫將會失敗。 此外,請注意,此 API 不是自由執行緒,因此無法在多個執行緒的相同配置器上同時呼叫。

ID3D12PipelineState

命令清單的初始管線狀態。 在 Microsoft Direct3D 12 中,大部分的圖形管線狀態都是使用 ID3D12PipelineState 物件在命令清單中設定。 應用程式會建立大量這些專案,通常是在應用程式初始化期間,然後藉由使用 ID3D12GraphicsCommandList::SetPipelineState變更目前系結的狀態物件來更新狀態。 如需管線狀態物件的詳細資訊,請參閱 在 Direct3D 12 中管理圖形管線狀態

請注意,套件組合不會在直接命令清單中繼承其父系的先前呼叫所設定的管線狀態。

如果此參數為 Null,則會使用預設狀態。

錄製命令清單

在建立之後,命令清單會處於錄製狀態。 您也可以呼叫 ID3D12GraphicsCommandList::Reset來重複使用現有的命令清單,這也會讓命令清單處於錄製狀態。 不同于 ID3D12CommandAllocator::Reset,您可以在命令清單仍在執行時呼叫 Reset 。 典型的模式是提交命令清單,然後立即重設它,以重複使用配置給另一個命令清單的記憶體。 請注意,每個命令配置器只能有一個與每個命令配置器相關聯的命令清單一次處於錄製狀態。

一旦命令清單處於錄製狀態,您只要呼叫 ID3D12GraphicsCommandList 介面的方法,即可將命令新增至清單。 其中許多方法可讓 Microsoft Direct3D 11 開發人員熟悉的常見 Direct3D 功能;其他 API 是 Direct3D 12 的新功能。

將命令新增至命令清單之後,您可以呼叫 Close,將命令清單從錄製狀態轉換出來。

命令配置器可以成長但無法縮小 - 共用和重複使用配置器應該視為能提升應用程式的效率。 您可以在重設之前,將多個清單記錄到相同的配置器,前提是一次只有一個清單錄製到指定的配置器。 您可以將每個清單視覺化為擁有配置器的一部分,以指出 ID3D12CommandQueue::ExecuteCommandLists 將會執行哪些 ID3D12CommandLists

簡單的配置器共用策略應該以大約 numCommandLists * MaxFrameLatency 配置器為目標。 例如,如果您記錄 6 個清單,並允許最多 3 個延遲畫面格,您可以合理預期 18-20 個配置器。 更進階的共用策略,可針對相同執行緒上的多個清單重複使用配置器,其目標可能是 numRecordingThreads * MaxFrameLatency 配置器。 使用先前的範例,如果線上程 A 上記錄了 2 個清單、執行緒 B 上的 2 個、執行緒 C 上的 1,而執行緒 D 上的 1 個清單,則您實際的目標是 12-14 配置器。

使用柵欄來判斷指定配置器何時能夠重複使用。

由於命令清單可以在執行後立即重設,因此可以簡單共用,在每次呼叫 ID3D12CommandQueue::ExecuteCommandLists之後,將它們新增回集區。

範例

下列程式碼片段說明如何建立和錄製命令清單。 請注意,此範例包含下列 Direct3D 12 功能:

  • 管線狀態物件 - 這些物件可用來從命令清單中設定轉譯管線的大部分狀態參數。 如需詳細資訊,請參閱 在 Direct3D 12 中管理圖形管線狀態
  • 描述元堆積 - 應用程式會使用描述元堆積來管理記憶體資源的管線系結。
  • 資源屏障 - 這可用來管理從某個狀態到另一個狀態的資源轉換,例如從轉譯目標檢視轉換為著色器資源檢視。 如需詳細資訊,請參閱 使用資源屏障同步處理資源狀態

例如,

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

建立並記錄命令清單之後,就可以使用命令佇列來執行。 如需詳細資訊,請參閱 執行和同步處理命令清單

參考計數

大部分的 D3D12 API 會繼續使用下列 COM 慣例的參考計數。 這是值得注意的例外狀況,是 D3D12 圖形命令清單 API。 ID3D12GraphicsCommandList上的所有 API 都不會保存傳遞至這些 API 之物件的參考。 這表示應用程式負責確保永遠不會提交命令清單,以執行參考損毀的資源。

命令清單錯誤

ID3D12GraphicsCommandList上的大部分 API 都不會傳回錯誤。 在命令清單建立期間發生的錯誤會延遲到 ID3D12GraphicsCommandList::Close為止。 其中一個例外狀況是DXGI_ERROR_DEVICE_REMOVED,這會更進一步延遲。 請注意,這與 D3D11 不同,其中有許多參數驗證錯誤會以無訊息方式卸載,且永遠不會傳回給呼叫端。

應用程式預期會在下列 API 呼叫中看到DXGI_DEVICE_REMOVED錯誤:

命令清單 API 限制

某些命令清單 API 只能在特定類型的命令清單上呼叫。 下表顯示哪些命令清單 API 在每種類型的命令清單上呼叫有效。 它也會顯示哪些 API 在 D3D12 轉譯階段中呼叫有效。

API 名稱 圖形 計算 複製 套件組合 在轉譯階段中
AtomicCopyBufferUINT
AtomicCopyBufferUINT64
BeginQuery
BeginRenderPass
BuildRaytracingAccelerationStructure
ClearDepthStencilView
ClearRenderTargetView
ClearState
ClearUnorderedAccessViewFloat
ClearUnorderedAccessViewUint
CopyBufferRegion
CopyRaytracingAccelerationStructure
CopyResource
CopyTextureRegion
CopyTiles
DiscardResource
分派
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

套件組合限制

限制可讓 Direct3D 12 驅動程式在記錄時間執行與套件組合相關聯的大部分工作,進而讓 ExecuteBundle API 以低負荷執行。 套件組合所參考的所有管線狀態物件都必須具有相同的轉譯目標格式、深度緩衝區格式和範例描述。

在以類型建立的命令清單上不允許下列命令清單 API 呼叫:D3D12_COMMAND_LIST_TYPE_BUNDLE:

您可以在套件組合上呼叫SetDescriptorHeaps,但套件組合描述元堆積必須符合呼叫的命令清單描述元堆積。

如果在套件組合上呼叫上述任何 API,執行時間將會卸載呼叫。 每當發生此錯誤時,偵錯層就會發出錯誤。

Direct3D 12 中的工作提交