共用方式為


建立和錄製命令清單和套件組合

本主題描述 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

簡單的配置器共用策略應該針對大約 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 ✓ 有效 ✓ 有效 ✓ 有效
開始查詢 ✓ 有效 ✓ 有效
BeginRenderPass ✓ 有效
構建光線追蹤加速結構 ✓ 有效 ✓ 有效
ClearDepthStencilView ✓ 有效
ClearRenderTargetView ✓ 有效
ClearState ✓ 有效 ✓ 有效
ClearUnorderedAccessViewFloat ✓ 有效 ✓ 有效
清除無序存取視圖整數 (ClearUnorderedAccessViewUint) ✓ 有效 ✓ 有效
複製緩衝區區域 ✓ 有效 ✓ 有效 ✓ 有效
CopyRaytracingAccelerationStructure(複製光線追蹤加速結構) ✓ 有效 ✓ 有效
CopyResource ✓ 有效 ✓ 有效 ✓ 有效
複製紋理區域 ✓ 有效 ✓ 有效 ✓ 有效
CopyTiles ✓ 有效 ✓ 有效 ✓ 有效
DiscardResource ✓ 有效 ✓ 有效
派遣 ✓ 有效 ✓ 有效 ✓ 有效
DispatchRays ✓ 有效 ✓ 有效 ✓ 有效
DrawIndexedInstanced ✓ 有效 ✓ 有效 ✓ 有效
DrawInstanced ✓ 有效 ✓ 有效 ✓ 有效
EmitRaytracingAccelerationStructurePostbuildInfo ✓ 有效 ✓ 有效
結束查詢 ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
EndRenderPass ✓ 有效 ✓ 有效
ExecuteBundle ✓ 有效 ✓ 有效
ExecuteIndirect ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
ExecuteMetaCommand ✓ 有效 ✓ 有效
IASetIndexBuffer ✓ 有效 ✓ 有效 ✓ 有效
IASetPrimitiveTopology ✓ 有效 ✓ 有效 ✓ 有效
IASetVertexBuffers ✓ 有效 ✓ 有效 ✓ 有效
InitializeMetaCommand ✓ 有效 ✓ 有效
OMSetBlendFactor ✓ 有效 ✓ 有效 ✓ 有效
OMSetDepthBounds ✓ 有效 ✓ 有效 ✓ 有效
OMSetRenderTargets ✓ 有效
OMSetStencilRef ✓ 有效 ✓ 有效 ✓ 有效
ResolveQueryData ✓ 有效 ✓ 有效 ✓ 有效
ResolveSubresource ✓ 有效
ResolveSubresourceRegion ✓ 有效
資源障礙 ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
RSSetScissorRects ✓ 有效 ✓ 有效
RSSetShadingRate ✓ 有效 ✓ 有效 ✓ 有效
RSSetShadingRateImage ✓ 有效 ✓ 有效 ✓ 有效
RSSetViewports ✓ 有效 ✓ 有效
SetComputeRoot32BitConstant ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
SetComputeRoot32BitConstants ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
SetComputeRootConstantBufferView ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
SetComputeRootDescriptorTable ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
設定計算根著色器資源檢視 ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
設定計算根簽名 ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
SetComputeRootUnorderedAccessView ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
設置描述符堆 ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
SetGraphicsRoot32BitConstant(設定圖形根32位常數) ✓ 有效 ✓ 有效 ✓ 有效
SetGraphicsRoot32BitConstants ✓ 有效 ✓ 有效 ✓ 有效
SetGraphicsRootConstantBufferView ✓ 有效 ✓ 有效 ✓ 有效
SetGraphicsRootDescriptorTable ✓ 有效 ✓ 有效 ✓ 有效
SetGraphicsRootShaderResourceView ✓ 有效 ✓ 有效 ✓ 有效
設定圖形根簽名 ✓ 有效 ✓ 有效 ✓ 有效
SetGraphicsRootUnorderedAccessView ✓ 有效 ✓ 有效 ✓ 有效
SetPipelineState ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效
SetPipelineState1 ✓ 有效 ✓ 有效 ✓ 有效
SetPredication ✓ 有效 ✓ 有效 ✓ 有效
設定受保護資源會話 (SetProtectedResourceSession) ✓ 有效 ✓ 有效 ✓ 有效
SetSamplePositions ✓ 有效 ✓ 有效 ✓ 有效
SetViewInstanceMask ✓ 有效 ✓ 有效 ✓ 有效
SOSetTargets ✓ 有效 ✓ 有效
WriteBufferImmediate ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效 ✓ 有效

套件組合限制

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

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

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

如果在套件上呼叫任何這些 API,執行階段將會忽略呼叫。 每當發生此錯誤時,偵錯層就會發出錯誤。

Direct3D 12 中的 工作提交