Dela via


Indirekt ritning och GPU-utslaktning

Exemplet D3D12ExecuteIndirect visar hur du använder indirekta kommandon för att rita innehåll. Den visar också hur dessa kommandon kan manipuleras på GPU:n i en beräkningsskuggning innan de utfärdas.

Exemplet skapar en kommandobuffert som beskriver 1024-anrop. Varje anrop renderar en triangel med slumpmässig färg, position och hastighet. Trianglar animerar oändligt över skärmen. Det finns två lägen i det här exemplet. I det första läget inspekterar en beräkningsskuggare de indirekta kommandona och avgör om kommandot ska läggas till i en osorterad åtkomstvy (UAV) som beskriver vilka kommandon som ska köras. I det andra läget körs alla kommandon helt enkelt. Om du trycker på blanksteget växlas mellan lägena.

Definiera de indirekta kommandona

Vi börjar med att definiera hur de indirekta kommandona ska se ut. I det här exemplet är de kommandon som vi vill köra till:

1. Uppdatera den konstanta buffertvyn (CBV). 2. Rita triangeln.

Dessa ritkommandon representeras av följande struktur i D3D12ExecuteIndirect klassdefinition. Kommandon körs sekventiellt i den ordning de definieras i den här strukturen.

  
// Data structure to match the command signature used for ExecuteIndirect.
struct IndirectCommand
{
       D3D12_GPU_VIRTUAL_ADDRESS cbv;
       D3D12_DRAW_ARGUMENTS drawArguments;
};
Samtalsflöde Parametrar
D3D12_GPU_VIRTUAL_ADDRESS (helt enkelt en UINT64)
D3D12_DRAW_ARGUMENTS

 

För att följa med datastrukturen skapas även en kommandosignatur som instruerar GPU:n hur du tolkar data som skickas till ExecuteIndirect API. Detta och det mesta av följande kod läggs till i metoden LoadAssets.

// Create the command signature used for indirect drawing.
{
       // Each command consists of a CBV update and a DrawInstanced call.
       D3D12_INDIRECT_ARGUMENT_DESC argumentDescs[2] = {};
       argumentDescs[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT_BUFFER_VIEW;
       argumentDescs[0].ConstantBufferView.RootParameterIndex = Cbv;
       argumentDescs[1].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW;

       D3D12_COMMAND_SIGNATURE_DESC commandSignatureDesc = {};
       commandSignatureDesc.pArgumentDescs = argumentDescs;
       commandSignatureDesc.NumArgumentDescs = _countof(argumentDescs);
       commandSignatureDesc.ByteStride = sizeof(IndirectCommand);

       ThrowIfFailed(m_device->CreateCommandSignature(&commandSignatureDesc, m_rootSignature.Get(), IID_PPV_ARGS(&m_commandSignature)));
}
Samtalsflöde Parametrar
D3D12_INDIRECT_ARGUMENT_DESC D3D12_INDIRECT_ARGUMENT_TYPE
D3D12_COMMAND_SIGNATURE_DESC
CreateCommandSignature

 

Skapa en grafik- och beräkningsrotsignatur

Vi skapar även både en grafik och en beräkningsrotsignatur. Signaturen för grafikroten definierar bara en rot-CBV. Observera att vi mappar indexet för den här rotparametern i D3D12_INDIRECT_ARGUMENT_DESC (visas ovan) när kommandosignaturen har definierats. Rotsignaturen för beräkning definierar:

  • En gemensam deskriptortabell med tre fack (två SRV:er och en UAV):
    • En SRV exponerar konstantbuffertarna för beräkningsskuggningen
    • En SRV exponerar kommandobufferten för beräkningsskuggningen
    • UAV är den plats där beräkningsskuggaren sparar kommandona för de synliga trianglarna
  • Fyra rotkonstanter:
    • Halva bredden på ena sidan av triangeln
    • Z-positionen för triangelhörn
    • +/- x-förskjutningen av gallringsplanet i homogent utrymme [-1,1]
    • Antalet indirekta kommandon i kommandobufferten
// Create the root signatures.
{
       CD3DX12_ROOT_PARAMETER rootParameters[GraphicsRootParametersCount];
       rootParameters[Cbv].InitAsConstantBufferView(0, 0, D3D12_SHADER_VISIBILITY_VERTEX);

       CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
       rootSignatureDesc.Init(_countof(rootParameters), rootParameters, 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 compute signature.
       CD3DX12_DESCRIPTOR_RANGE ranges[2];
       ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 2, 0);
       ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0);

       CD3DX12_ROOT_PARAMETER computeRootParameters[ComputeRootParametersCount];
       computeRootParameters[SrvUavTable].InitAsDescriptorTable(2, ranges);
       computeRootParameters[RootConstants].InitAsConstants(4, 0);

       CD3DX12_ROOT_SIGNATURE_DESC computeRootSignatureDesc;
       computeRootSignatureDesc.Init(_countof(computeRootParameters), computeRootParameters);

       ThrowIfFailed(D3D12SerializeRootSignature(&computeRootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
       ThrowIfFailed(m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&m_computeRootSignature)));
}
Samtalsflöde Parametrar
CD3DX12_ROOT_PARAMETER D3D12_SHADER_VISIBILITY
CD3DX12_ROOT_SIGNATURE_DESC D3D12_ROOT_SIGNATURE_FLAGS
ID3DBlob
D3D12SerializeRootSignature D3D_ROOT_SIGNATURE_VERSION
CreateRootSignature
CD3DX12_DESCRIPTOR_RANGE D3D12_DESCRIPTOR_RANGE_TYPE
CD3DX12_ROOT_PARAMETER D3D12_SHADER_VISIBILITY
CD3DX12_ROOT_SIGNATURE_DESC D3D12_ROOT_SIGNATURE_FLAGS
ID3DBlob
D3D12SerializeRootSignature D3D_ROOT_SIGNATURE_VERSION
CreateRootSignature

 

Skapa en skuggningsresursvy (SRV) för beräkningsskuggningen

När du har skapat pipelinetillståndsobjekt, hörnbuffertar, en djupstencil och de konstanta buffertarna skapar exemplet sedan en skuggningsresursvy (SRV) för den konstanta bufferten så att beräkningsskuggaren kan komma åt data i den konstanta bufferten.

// Create shader resource views (SRV) of the constant buffers for the
// compute shader to read from.
       D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
       srvDesc.Format = DXGI_FORMAT_UNKNOWN;
       srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
       srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
       srvDesc.Buffer.NumElements = TriangleCount;
       srvDesc.Buffer.StructureByteStride = sizeof(ConstantBufferData);
       srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;

       CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandle(m_cbvSrvUavHeap->GetCPUDescriptorHandleForHeapStart(), CbvSrvOffset, m_cbvSrvUavDescriptorSize);
       for (UINT frame = 0; frame < FrameCount; frame++)
       {
              srvDesc.Buffer.FirstElement = frame * TriangleCount;
              m_device->CreateShaderResourceView(m_constantBuffer.Get(), &srvDesc, cbvSrvHandle);
              cbvSrvHandle.Offset(CbvSrvUavDescriptorCountPerFrame, m_cbvSrvUavDescriptorSize);
       }
Samtalsflöde Parametrar
D3D12_SHADER_RESOURCE_VIEW_DESC
DXGI_FORMAT
D3D12_SRV_DIMENSION
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
CD3DX12_CPU_DESCRIPTOR_HANDLE GetCPUDescriptorHandleForHeapStart
CreateShaderResourceView

 

Skapa de indirekta kommandobuffertarna

Sedan skapar vi de indirekta kommandobuffertarna och definierar deras innehåll med hjälp av följande kod. Vi ritar samma triangelhörn 1024 gånger, men pekar på en annan konstant buffertplats med varje anrop.

       D3D12_GPU_VIRTUAL_ADDRESS gpuAddress = m_constantBuffer->GetGPUVirtualAddress();
       UINT commandIndex = 0;

       for (UINT frame = 0; frame < FrameCount; frame++)
       {
              for (UINT n = 0; n < TriangleCount; n++)
              {
                    commands[commandIndex].cbv = gpuAddress;
                    commands[commandIndex].drawArguments.VertexCountPerInstance = 3;
                    commands[commandIndex].drawArguments.InstanceCount = 1;
                    commands[commandIndex].drawArguments.StartVertexLocation = 0;
                    commands[commandIndex].drawArguments.StartInstanceLocation = 0;

                    commandIndex++;
                    gpuAddress += sizeof(ConstantBufferData);
              }
       }
Samtalsflöde Parametrar
D3D12_GPU_VIRTUAL_ADDRESS GetGPUVirtualAddress

 

När du har laddat upp kommandobuffertarna till GPU:n skapar vi även en SRV för dem som beräkningsskuggaren ska läsa från. Detta liknar den SRV som skapats av konstantbufferten.

// Create SRVs for the command buffers.
       D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
       srvDesc.Format = DXGI_FORMAT_UNKNOWN;
       srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
       srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
       srvDesc.Buffer.NumElements = TriangleCount;
       srvDesc.Buffer.StructureByteStride = sizeof(IndirectCommand);
       srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;

       CD3DX12_CPU_DESCRIPTOR_HANDLE commandsHandle(m_cbvSrvUavHeap->GetCPUDescriptorHandleForHeapStart(), CommandsOffset, m_cbvSrvUavDescriptorSize);
       for (UINT frame = 0; frame < FrameCount; frame++)
       {
              srvDesc.Buffer.FirstElement = frame * TriangleCount;
              m_device->CreateShaderResourceView(m_commandBuffer.Get(), &srvDesc, commandsHandle);
              commandsHandle.Offset(CbvSrvUavDescriptorCountPerFrame, m_cbvSrvUavDescriptorSize);
       }
Samtalsflöde Parametrar
D3D12_SHADER_RESOURCE_VIEW_DESC
DXGI_FORMAT
D3D12_SRV_DIMENSION
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
D3D12_BUFFER_SRV_FLAG
CD3DX12_CPU_DESCRIPTOR_HANDLE GetCPUDescriptorHandleForHeapStart
CreateShaderResourceView

 

Skapa UAV:er för beräkning

Vi måste skapa UAV:er som lagrar resultatet av beräkningsarbetet. När en triangel bedöms av beräkningsskuggaren vara synlig för återgivningsmålet läggs den till i den här UAV:n och förbrukas sedan av ExecuteIndirect API.

CD3DX12_CPU_DESCRIPTOR_HANDLE processedCommandsHandle(m_cbvSrvUavHeap->GetCPUDescriptorHandleForHeapStart(), ProcessedCommandsOffset, m_cbvSrvUavDescriptorSize);
for (UINT frame = 0; frame < FrameCount; frame++)
{
       // Allocate a buffer large enough to hold all of the indirect commands
       // for a single frame as well as a UAV counter.
       commandBufferDesc = CD3DX12_RESOURCE_DESC::Buffer(CommandBufferSizePerFrame + sizeof(UINT), D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
       CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
       ThrowIfFailed(m_device->CreateCommittedResource(
             &heapProps,
             D3D12_HEAP_FLAG_NONE,
             &commandBufferDesc,
             D3D12_RESOURCE_STATE_COPY_DEST,
             nullptr,
             IID_PPV_ARGS(&m_processedCommandBuffers[frame])));

       D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
       uavDesc.Format = DXGI_FORMAT_UNKNOWN;
       uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
       uavDesc.Buffer.FirstElement = 0;
       uavDesc.Buffer.NumElements = TriangleCount;
       uavDesc.Buffer.StructureByteStride = sizeof(IndirectCommand);
       uavDesc.Buffer.CounterOffsetInBytes = CommandBufferSizePerFrame;
       uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE;

       m_device->CreateUnorderedAccessView(
             m_processedCommandBuffers[frame].Get(),
             m_processedCommandBuffers[frame].Get(),
             &uavDesc,
             processedCommandsHandle);

       processedCommandsHandle.Offset(CbvSrvUavDescriptorCountPerFrame, m_cbvSrvUavDescriptorSize);
}
Samtalsflöde Parametrar
CD3DX12_CPU_DESCRIPTOR_HANDLE GetCPUDescriptorHandleForHeapStart
CD3DX12_RESOURCE_DESC D3D12_RESOURCE_FLAGS
CreateCommittedResource
CD3DX12_HEAP_PROPERTIES
D3D12_HEAP_TYPE
D3D12_HEAP_FLAG
D3D12_RESOURCE_STATES
D3D12_UNORDERED_ACCESS_VIEW_DESC
DXGI_FORMAT
D3D12_UAV_DIMENSION
D3D12_BUFFER_UAV_FLAGS
CreateUnorderedAccessView

 

Rita ramen

När det är dags att rita ramen, om vi är i läget när beräkningsskuggningen anropas och indirekta kommandon bearbetas av GPU:n, kommer vi först Dispatch som arbetar med att fylla i vår kommandobuffert för ExecuteIndirect-. Följande kodfragment läggs till i metoden PopulateCommandLists.

// Record the compute commands that will cull triangles and prevent them from being processed by the vertex shader.
if (m_enableCulling)
{
       UINT frameDescriptorOffset = m_frameIndex * CbvSrvUavDescriptorCountPerFrame;
       D3D12_GPU_DESCRIPTOR_HANDLE cbvSrvUavHandle = m_cbvSrvUavHeap->GetGPUDescriptorHandleForHeapStart();

       m_computeCommandList->SetComputeRootSignature(m_computeRootSignature.Get());

       ID3D12DescriptorHeap* ppHeaps[] = { m_cbvSrvUavHeap.Get() };
       m_computeCommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

       m_computeCommandList->SetComputeRootDescriptorTable(
              SrvUavTable,
              CD3DX12_GPU_DESCRIPTOR_HANDLE(cbvSrvUavHandle, CbvSrvOffset + frameDescriptorOffset, m_cbvSrvUavDescriptorSize));

       m_computeCommandList->SetComputeRoot32BitConstants(RootConstants, 4, reinterpret_cast<void*>(&m_csRootConstants), 0);

       // Reset the UAV counter for this frame.
       m_computeCommandList->CopyBufferRegion(m_processedCommandBuffers[m_frameIndex].Get(), CommandBufferSizePerFrame, m_processedCommandBufferCounterReset.Get(), 0, sizeof(UINT));

       D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_processedCommandBuffers[m_frameIndex].Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
       m_computeCommandList->ResourceBarrier(1, &barrier);

       m_computeCommandList->Dispatch(static_cast<UINT>(ceil(TriangleCount / float(ComputeThreadBlockSize))), 1, 1);
}

ThrowIfFailed(m_computeCommandList->Close());
Samtalsflöde Parametrar
D3D12_GPU_DESCRIPTOR_HANDLE GetGPUDescriptorHandleForHeapStart
SetComputeRootSignature
ID3D12DescriptorHeap
SetDescriptorHeaps
SetComputeRootDescriptorTable CD3DX12_GPU_DESCRIPTOR_HANDLE
SetComputeRoot32BitConstants
CopyBufferRegion
D3D12_RESOURCE_BARRIER
CD3DX12_RESOURCE_BARRIER
D3D12_RESOURCE_STATES
ResourceBarrier
Dispatch
Stäng

 

Sedan kör vi kommandona i antingen UAV (GPU-utslaktning aktiverat) eller den fullständiga kommandobufferten (GPU-utgallring inaktiverad).

// Record the rendering commands.
{
       // Set necessary state.
       m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());

       ID3D12DescriptorHeap* ppHeaps[] = { m_cbvSrvUavHeap.Get() };
       m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

       m_commandList->RSSetViewports(1, &m_viewport);
       m_commandList->RSSetScissorRects(1, m_enableCulling ? &m_cullingScissorRect : &m_scissorRect);

       // Indicate that the command buffer will be used for indirect drawing
       // and that the back buffer will be used as a render target.
       D3D12_RESOURCE_BARRIER barriers[2] = {
              CD3DX12_RESOURCE_BARRIER::Transition(
                    m_enableCulling ? m_processedCommandBuffers[m_frameIndex].Get() : m_commandBuffer.Get(),
                    m_enableCulling ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
                    D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT),
              CD3DX12_RESOURCE_BARRIER::Transition(
                    m_renderTargets[m_frameIndex].Get(),
                    D3D12_RESOURCE_STATE_PRESENT,
                    D3D12_RESOURCE_STATE_RENDER_TARGET)
       };

       m_commandList->ResourceBarrier(_countof(barriers), barriers);

       CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
       CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
       m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);

       // Record commands.
       const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
       m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
       m_commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

       m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
       m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);

       if (m_enableCulling)
       {
              // Draw the triangles that have not been culled.
              m_commandList->ExecuteIndirect(
                    m_commandSignature.Get(),
                    TriangleCount,
                    m_processedCommandBuffers[m_frameIndex].Get(),
                    0,
                    m_processedCommandBuffers[m_frameIndex].Get(),
                    CommandBufferSizePerFrame);
       }
       else
       {
              // Draw all of the triangles.
              m_commandList->ExecuteIndirect(
                    m_commandSignature.Get(),
                    TriangleCount,
                    m_commandBuffer.Get(),
                    CommandBufferSizePerFrame * m_frameIndex,
                    nullptr,
                    0);
       }

       // Indicate that the command buffer may be used by the compute shader
       // and that the back buffer will now be used to present.
       barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
       barriers[0].Transition.StateAfter = m_enableCulling ? D3D12_RESOURCE_STATE_COPY_DEST : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
       barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
       barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;

       m_commandList->ResourceBarrier(_countof(barriers), barriers);

       ThrowIfFailed(m_commandList->Close());
}
Samtalsflöde Parametrar
SetGraphicsRootSignature
ID3D12DescriptorHeap
SetDescriptorHeaps
RSSetViewports
RSSetScissorRects
D3D12_RESOURCE_BARRIER
CD3DX12_RESOURCE_BARRIER
D3D12_RESOURCE_STATES
ResourceBarrier
CD3DX12_CPU_DESCRIPTOR_HANDLE GetCPUDescriptorHandleForHeapStart
OMSetRenderTargets
ClearRenderTargetView
ClearDepthStencilView D3D12_CLEAR_FLAGS
IASetPrimitiveTopology D3D_PRIMITIVE_TOPOLOGY
IASetVertexBuffers
ExecuteIndirect
ResourceBarrier D3D12_RESOURCE_STATES
Stäng

 

Om vi är i GPU-skyddsläge väntar grafikkommandokön på att beräkningsarbetet ska slutföras innan det börjar köra de indirekta kommandona. I metoden OnRender läggs följande kodfragment till.

// Execute the compute work.
if (m_enableCulling)
{
       ID3D12CommandList* ppCommandLists[] = { m_computeCommandList.Get() };
       m_computeCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
       m_computeCommandQueue->Signal(m_computeFence.Get(), m_fenceValues[m_frameIndex]);

       // Execute the rendering work only when the compute work is complete.
       m_commandQueue->Wait(m_computeFence.Get(), m_fenceValues[m_frameIndex]);
}

// Execute the rendering work.
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
Samtalsflöde Parametrar
ID3D12CommandList
ExecuteCommandLists
Signal
Vänta
ID3D12CommandList
ExecuteCommandLists

 

Kör exemplet

Exemplet med primitiv GPU-utslaktning.

skärmbild av det indirekta exectue-exemplet med gpu-gallring

Exemplet utan primitiv GPU-utslaktning.

skärmbild av det indirekta exectue-exemplet utan gpu-gallring

genomgång av D3D12-kod

Självstudier om avancerad inlärning i DirectX: Kör indirekt och Async GPU-utgallring

indirekt ritning