間接繪圖和 GPU 清除
D3D12ExecuteIndirect 範例示範如何使用間接命令來繪製內容。 它也示範如何在發出這些命令之前,在計算著色器中的 GPU 上操作這些命令。
此範例會建立描述 1024 繪製呼叫的命令緩衝區。 每個繪製呼叫都會轉譯具有隨機色彩、位置和速度的三角形。 三角形會在畫面上以無限方式產生動畫效果。 此範例中有兩種模式。 在第一個模式中,計算著色器會檢查間接命令,並決定是否要將該命令新增至未排序的存取檢視, (UAV) 描述應該執行的命令。 在第二個模式中,只會執行所有命令。 按下空格鍵會在模式之間切換。
定義間接命令
我們一開始會定義間接命令的外觀。 在此範例中,我們想要執行的命令是:
- 1.更新 CBV) (常數緩衝區檢視。
2.繪製三角形。
這些繪圖命令是由 D3D12ExecuteIndirect 類別定義中的下列結構表示。 命令會依這個結構中定義的順序循序執行。
// Data structure to match the command signature used for ExecuteIndirect.
struct IndirectCommand
{
D3D12_GPU_VIRTUAL_ADDRESS cbv;
D3D12_DRAW_ARGUMENTS drawArguments;
};
通話流程 | 參數 |
---|---|
只D3D12_GPU_VIRTUAL_ADDRESS (UINT64) | |
D3D12_DRAW_ARGUMENTS |
為了伴隨資料結構,也會建立命令簽章,指示 GPU 如何解譯傳入 ExecuteIndirect API 的資料。 這和下列大部分的程式碼都會新增至 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)));
}
通話流程 | 參數 |
---|---|
D3D12_INDIRECT_ARGUMENT_DESC | D3D12_INDIRECT_ARGUMENT_TYPE |
D3D12_COMMAND_SIGNATURE_DESC | |
CreateCommandSignature |
建立圖形和計算根簽章
我們也建立圖形和計算根簽章。 圖形根簽章只會定義根 CBV。 請注意,我們會在定義命令簽章時,將上述 ) D3D12_INDIRECT_ARGUMENT_DESC ( 中此根參數的索引對應。 計算根簽章會定義:
- 具有三個位置的一般描述中繼資料表, (兩個 SRV 和一個 UAV) :
- 一個 SRV 會將常數緩衝區公開給計算著色器
- 一個 SRV 會將命令緩衝區公開給計算著色器
- UAV 是計算著色器儲存可見三角形命令的位置
- 四個根常數:
- 三角形一邊的一半寬度
- 三角形頂點的 z 位置
- 同質空間中擷取平面的 +/- x 位移 [-1,1]
- 命令緩衝區中的間接命令數目
// 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)));
}
為計算著色器建立著色器資源檢視 (SRV)
建立管線狀態物件、頂點緩衝區、深度樣板和常數緩衝區之後,範例接著會建立常數緩衝區 (SRV 資源檢視 (SRV) ,讓計算著色器可以存取常數緩衝區中的資料。
// 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);
}
通話流程 | 參數 |
---|---|
D3D12_SHADER_RESOURCE_VIEW_DESC | |
CD3DX12_CPU_DESCRIPTOR_HANDLE | GetCPUDescriptorHandleForHeapStart |
CreateShaderResourceView |
建立間接命令緩衝區
然後,我們會建立間接命令緩衝區,並使用下列程式碼定義其內容。 我們會繪製相同的三角形頂點 1024 次,但會指向具有每個繪製呼叫的不同常數緩衝區位置。
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);
}
}
通話流程 | 參數 |
---|---|
D3D12_GPU_VIRTUAL_ADDRESS | GetGPUVirtualAddress |
將命令緩衝區上傳至 GPU 之後,我們也會為其建立 SRV,讓計算著色器讀取來源。 這與建立常數緩衝區的 SRV 非常類似。
// 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);
}
通話流程 | 參數 |
---|---|
D3D12_SHADER_RESOURCE_VIEW_DESC | |
CD3DX12_CPU_DESCRIPTOR_HANDLE | GetCPUDescriptorHandleForHeapStart |
CreateShaderResourceView |
建立計算 UAV
我們需要建立將儲存計算工作結果的 UAV。 當計算著色器視為呈現目標可見的三角形時,它會附加至此 UAV,然後由 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);
}
通話流程 | 參數 |
---|---|
CD3DX12_CPU_DESCRIPTOR_HANDLE | GetCPUDescriptorHandleForHeapStart |
CD3DX12_RESOURCE_DESC | D3D12_RESOURCE_FLAGS |
CreateCommittedResource | |
D3D12_UNORDERED_ACCESS_VIEW_DESC | |
CreateUnorderedAccessView |
繪製框架
繪製框架時,如果我們在叫用計算著色器且 GPU 正在處理間接命令時處於模式,我們會先 分派 該工作來填入 ExecuteIndirect的命令緩衝區。 下列程式碼片段會新增至 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());
然後,我們會在已啟用 UAV (GPU 擷取) 或完整命令緩衝區中執行命令, (GPU 停用) 。
// 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());
}
如果我們處於 GPU 擷取模式,則會讓圖形命令佇列等候計算工作完成,再開始執行間接命令。 在 OnRender 方法中,會新增下列程式碼片段。
// 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);
通話流程 | 參數 |
---|---|
ID3D12CommandList | |
ExecuteCommandLists | |
訊號 | |
Wait | |
ID3D12CommandList | |
ExecuteCommandLists |
執行範例
具有 GPU 基本擷取的範例。
沒有 GPU 基本擷取的範例。
相關主題