Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En el ejemplo D3D12ExecuteIndirect se muestra cómo usar comandos indirectos para dibujar contenido. También muestra cómo se pueden manipular estos comandos en la GPU en un sombreador de proceso antes de que se emitan.
- Definir los comandos indirectos
- Crear un de firma raíz de proceso y gráficos
- Crear una vista de recursos del sombreador (SRV) para el sombreador de proceso
- Crear los búferes de comandos indirectos
- Crear el de UAV de proceso
- Dibujar el marco
- Ejecutar el de ejemplo
- temas relacionados
El ejemplo crea un búfer de comandos que describe 1024 llamadas de dibujo. Cada llamada de dibujo representa un triángulo con un color aleatorio, una posición y una velocidad. Los triángulos se animan sin fin a través de la pantalla. Hay dos modos en este ejemplo. En el primer modo, un sombreador de proceso inspecciona los comandos indirectos y decide si se va a agregar ese comando a una vista de acceso sin ordenar (UAV) que describe qué comandos se deben ejecutar. En el segundo modo, todos los comandos se ejecutan simplemente. Al presionar la barra espaciadora, se alternará entre los modos.
Definición de los comandos indirectos
Para empezar, definimos el aspecto que deben tener los comandos indirectos. En este ejemplo, los comandos que queremos ejecutar son:
- 1. Actualice la vista del búfer de constantes (CBV).
2. Dibuje el triángulo.
Estos comandos de dibujo se representan mediante la siguiente estructura en la definición de clase D3D12ExecuteIndirect. Los comandos se ejecutan secuencialmente en el orden en que se definen en esta estructura.
// Data structure to match the command signature used for ExecuteIndirect.
struct IndirectCommand
{
D3D12_GPU_VIRTUAL_ADDRESS cbv;
D3D12_DRAW_ARGUMENTS drawArguments;
};
Flujo de llamadas | Parámetros |
---|---|
D3D12_GPU_VIRTUAL_ADDRESS (simplemente un UINT64) | |
D3D12_DRAW_ARGUMENTS |
Para acompañar la estructura de datos, también se crea una firma de comando que indica a la GPU cómo interpretar los datos pasados a la API de ExecuteIndirect. Esto, y la mayor parte del código siguiente, se agrega al método 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)));
}
Flujo de llamadas | Parámetros |
---|---|
D3D12_INDIRECT_ARGUMENT_DESC | D3D12_INDIRECT_ARGUMENT_TYPE |
D3D12_COMMAND_SIGNATURE_DESC | |
CreateCommandSignature |
Creación de gráficos y firma raíz de proceso
También se crean gráficos y una firma raíz de proceso. La firma raíz de gráficos simplemente define un CBV raíz. Tenga en cuenta que asignamos el índice de este parámetro raíz en el D3D12_INDIRECT_ARGUMENT_DESC (mostrado anteriormente) cuando se define la firma del comando. La firma raíz de proceso define:
- Una tabla de descriptores común con tres ranuras (dos SRV y un UAV):
- Un SRV expone los búferes de constantes al sombreador de proceso.
- Un SRV expone el búfer de comandos al sombreador de proceso.
- El UAV es donde el sombreador de proceso guarda los comandos de los triángulos visibles.
- Cuatro constantes raíz:
- Mitad del ancho de un lado del triángulo
- Posición z de los vértices del triángulo
- Desplazamiento +/- x del plano de selección en espacio homogéneo [-1,1]
- Número de comandos indirectos en el búfer de comandos
// 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)));
}
Flujo de llamadas | Parámetros |
---|---|
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 |
Creación de una vista de recursos de sombreador (SRV) para el sombreador de proceso
Después de crear los objetos de estado de canalización, los búferes de vértices, una galería de símbolos de profundidad y los búferes de constantes, el ejemplo crea una vista de recursos de sombreador (SRV) del búfer de constantes para que el sombreador de proceso pueda acceder a los datos en el búfer de constantes.
// 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);
}
Flujo de llamadas | Parámetros |
---|---|
D3D12_SHADER_RESOURCE_VIEW_DESC | |
CD3DX12_CPU_DESCRIPTOR_HANDLE | GetCPUDescriptorHandleForHeapStart |
CreateShaderResourceView |
Creación de los búferes de comandos indirectos
A continuación, creamos los búferes de comandos indirectos y definimos su contenido mediante el código siguiente. Dibujamos los mismos vértices de triángulo 1024 veces, pero apuntamos a una ubicación de búfer de constantes diferente con cada llamada de dibujo.
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);
}
}
Flujo de llamadas | Parámetros |
---|---|
D3D12_GPU_VIRTUAL_ADDRESS | GetGPUVirtualAddress |
Después de cargar los búferes de comandos en la GPU, también se crea un SRV para que el sombreador de proceso lea. Esto es muy similar al SRV creado del búfer de constantes.
// 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);
}
Flujo de llamadas | Parámetros |
---|---|
D3D12_SHADER_RESOURCE_VIEW_DESC | |
CD3DX12_CPU_DESCRIPTOR_HANDLE | GetCPUDescriptorHandleForHeapStart |
CreateShaderResourceView |
Creación de las UAV de proceso
Es necesario crear las UMV que almacenarán los resultados del trabajo de proceso. Cuando el sombreador de proceso considera que un triángulo es visible para el destino de representación, se anexará a este UAV y, a continuación, lo usará la API de ExecuteIndirect.
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);
}
Flujo de llamadas | Parámetros |
---|---|
CD3DX12_CPU_DESCRIPTOR_HANDLE | GetCPUDescriptorHandleForHeapStart |
CD3DX12_RESOURCE_DESC | D3D12_RESOURCE_FLAGS |
CreateCommittedResource | |
D3D12_UNORDERED_ACCESS_VIEW_DESC | |
CreateUnorderedAccessView |
Dibujar el marco
Cuando llegue el momento de dibujar el fotograma, si estamos en modo cuando se invoca el sombreador de proceso y los comandos indirectos se procesan mediante la GPU, primero Dispatch que funcionan para rellenar el búfer de comandos para ExecuteIndirect. Los siguientes fragmentos de código se agregan al método 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());
Flujo de llamadas | Parámetros |
---|---|
D3D12_GPU_DESCRIPTOR_HANDLE | GetGPUDescriptorHandleForHeapStart |
SetComputeRootSignature | |
ID3D12DescriptorHeap | |
SetDescriptorHeaps | |
setComputeRootDescriptorTable | CD3DX12_GPU_DESCRIPTOR_HANDLE |
SetComputeRoot32BitConstants | |
CopyBufferRegion | |
D3D12_RESOURCE_BARRIER | |
resourceBarrier | |
dispatch | |
Cerrar |
A continuación, ejecutaremos los comandos en UAV (selección de GPU habilitada) o en el búfer de comandos completo (selección de GPU deshabilitado).
// 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());
}
Flujo de llamadas | Parámetros |
---|---|
setGraphicsRootSignature | |
ID3D12DescriptorHeap | |
SetDescriptorHeaps | |
RSSetViewports | |
RSSetScisorRects | |
D3D12_RESOURCE_BARRIER | |
resourceBarrier | |
CD3DX12_CPU_DESCRIPTOR_HANDLE | GetCPUDescriptorHandleForHeapStart |
OMSetRenderTargets | |
ClearRenderTargetView | |
ClearDepthStencilView | D3D12_CLEAR_FLAGS |
iaSetPrimitiveTopology | D3D_PRIMITIVE_TOPOLOGY |
IASetVertexBuffers | |
executeIndirect | |
resourceBarrier | D3D12_RESOURCE_STATES |
Cerrar |
Si estamos en modo de selección de GPU, tendremos que la cola de comandos de gráficos espere a que se complete el trabajo de proceso antes de empezar a ejecutar los comandos indirectos. En el método OnRender se agrega el siguiente fragmento de código.
// 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);
Flujo de llamadas | Parámetros |
---|---|
ID3D12CommandList | |
ExecuteCommandLists | |
Signal | |
de espera | |
ID3D12CommandList | |
ExecuteCommandLists |
Ejecución del ejemplo
Ejemplo con selección primitiva de GPU.
de selección de GPU
Ejemplo sin selección primitiva de GPU.
Temas relacionados