다음을 통해 공유


간접 그리기 및 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 2개와 UAV 1개)이 있는 공통 설명자 테이블:
    • 하나의 SRV는 컴퓨팅 셰이더에 상수 버퍼를 노출합니다.
    • 하나의 SRV가 명령 버퍼를 컴퓨팅 셰이더에 노출합니다.
    • UAV는 컴퓨팅 셰이더가 표시되는 삼각형에 대한 명령을 저장하는 위치입니다.
  • 4개의 루트 상수:
    • 삼각형의 한쪽 너비의 절반
    • 삼각형 꼭짓점의 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)));
}
통화 흐름 매개 변수
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

 

컴퓨팅 셰이더에 대한 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
DXGI_FORMAT
D3D12_SRV_DIMENSION
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
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
DXGI_FORMAT
D3D12_SRV_DIMENSION
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
D3D12_BUFFER_SRV_FLAG
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
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

 

프레임 그리기

프레임을 그릴 때 컴퓨팅 셰이더가 호출되고 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());
통화 흐름 매개 변수
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
디스패치
닫기

 

그런 다음 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());
}
통화 흐름 매개 변수
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
닫기

 

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
신호
대기
ID3D12CommandList
ExecuteCommandLists

 

샘플 실행

GPU 기본 컬링이 있는 샘플입니다.

gpu 컬링스크린샷

GPU 기본 컬링이 없는 샘플입니다.

gpu 컬링스크린샷

D3D12 코드 연습

DirectX 고급 학습 비디오 자습서: 간접 및 비동기 GPU 컬링 실행

간접 그리기