명령 목록 및 번들 만들기 및 기록
이 항목에서는 Direct3D 12 앱에서 명령 목록과 번들을 기록하는 방법을 설명합니다. 명령 목록 및 번들을 사용하면 앱에서는 GPU(그래픽 처리 장치)에서 나중에 실행할 수 있도록 그리기 또는 상태 변경 호출을 기록할 수 있습니다.
명령 목록 외에도, API는 번들(bundle)이라는 명령 목록의 두 번째 수준을 추가하여 GPU 하드웨어에 있는 기능을 활용합니다. 번들의 목적은 앱이 소수의 API 명령을 나중에 실행할 수 있도록 그룹화하는 것입니다. 번들 생성 시 드라이버는 나중에 실행하는 비용을 저렴하게 만들기 위해 사전 처리 작업을 최대한 많이 수행합니다. 번들은 여러 번 사용하고 재사용하도록 설계되었습니다. 반면에 명령 목록은 대개 한 번만 실행됩니다. 하지만 명령 목록을 여러 번 실행할 수 있습니다(새 실행을 제출하기 전에 이전 실행이 완료되었음을 애플리케이션이 확인하기만 한다면).
하지만 일반적으로 API 호출이 번들로 구성되고 API 호출과 번들이 명령 목록으로 호출되고 명령 목록이 단일 프레임으로 표시됩니다. 다음 다이어그램에서는 명령 목록 1 및 명령 목록 2에서 번들 1의 재사용과 다이어그램의 API 메서드 이름이 예시임을 알 수 있습니다. 다양한 API 호출을 사용할 수 있습니다.
번들 및 직접 명령 목록을 만들고 실행하는 데는 여러 가지 제한 사항이 있으며, 이러한 차이는 이 문서 전체에 언급되어 있습니다.
명령 목록 만들기
직접 명령 목록 및 번들은 ID3D12Device::CreateCommandList 또는 ID3D12Device4::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의 새로운 API입니다.
명령 목록에 명령을 추가한 후 Close를 호출하여 명령 목록을 기록 상태에서 전환합니다.
명령 할당자는 증가할 수 있지만 축소되지 않습니다. 앱의 효율성을 극대화하려면 할당자를 풀링하고 다시 사용하는 것이 좋습니다. 한 번에 하나의 목록만 지정된 할당자에 기록하면 다시 설정되기 전에 동일한 할당자에 여러 목록을 기록할 수 있습니다. 각 목록은 ID3D12CommandQueue::ExecuteCommandLists가 실행할 ID를 나타내는 할당자의 일부를 소유하는 것으로 시각화할 수 있습니다.
간단한 할당자 풀링 전략은 대략 numCommandLists * MaxFrameLatency
할당자를 목표로 해야 합니다. 예를 들어 6개의 목록을 기록하고 최대 3개의 대기 시간 프레임을 허용하는 경우 18~20개의 할당자를 합리적으로 예상할 수 있습니다. 동일한 스레드의 여러 목록에 할당자를 다시 사용하는 고급 풀링 전략은 할당자를 목표로 numRecordingThreads * MaxFrameLatency
할 수 있습니다. 이전 예제를 사용하여 2개의 목록이 스레드 A에 기록된 경우, 스레드 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를 보여 줍니다. 또한 D3D12 렌더링 패스에서 호출할 수 있는 API도 보여 줍니다.
API 이름 | 그래픽 | Compute | 복사 | 번들 | 렌더링 패스에서 |
---|---|---|---|---|---|
AtomicCopyBufferUINT | ✓ | ✓ | ✓ | ||
AtomicCopyBufferUINT64 | ✓ | ✓ | ✓ | ||
BeginQuery | ✓ | ✓ | |||
BeginRenderPass | ✓ | ||||
BuildRaytracingAccelerationStructure | ✓ | ✓ | |||
ClearDepthStencilView | ✓ | ||||
ClearRenderTargetView | ✓ | ||||
ClearState | ✓ | ✓ | |||
ClearUnorderedAccessViewFloat | ✓ | ✓ | |||
ClearUnorderedAccessViewUint | ✓ | ✓ | |||
CopyBufferRegion | ✓ | ✓ | ✓ | ||
CopyRaytracingAccelerationStructure | ✓ | ✓ | |||
CopyResource | ✓ | ✓ | ✓ | ||
CopyTextureRegion | ✓ | ✓ | ✓ | ||
CopyTiles | ✓ | ✓ | ✓ | ||
DiscardResource | ✓ | ✓ | |||
Dispatch | ✓ | ✓ | ✓ | ||
DispatchRays | ✓ | ✓ | ✓ | ||
DrawIndexedInstanced | ✓ | ✓ | ✓ | ||
DrawInstanced | ✓ | ✓ | ✓ | ||
EmitRaytracingAccelerationStructurePostbuildInfo | ✓ | ✓ | |||
EndQuery | ✓ | ✓ | ✓ | ✓ | |
EndRenderPass | ✓ | ✓ | |||
ExecuteBundle | ✓ | ✓ | |||
ExecuteIndirect | ✓ | ✓ | ✓ | ✓ | |
ExecuteMetaCommand | ✓ | ✓ | |||
IASetIndexBuffer | ✓ | ✓ | ✓ | ||
IASetPrimitiveTopology | ✓ | ✓ | ✓ | ||
IASetVertexBuffers | ✓ | ✓ | ✓ | ||
InitializeMetaCommand | ✓ | ✓ | |||
OMSetBlendFactor | ✓ | ✓ | ✓ | ||
OMSetDepthBounds | ✓ | ✓ | ✓ | ||
OMSetRenderTargets | ✓ | ||||
OMSetStencilRef | ✓ | ✓ | ✓ | ||
ResolveQueryData | ✓ | ✓ | ✓ | ||
ResolveSubresource | ✓ | ||||
ResolveSubresourceRegion | ✓ | ||||
ResourceBarrier | ✓ | ✓ | ✓ | ✓ | |
RSSetScissorRects | ✓ | ✓ | |||
RSSetShadingRate | ✓ | ✓ | ✓ | ||
RSSetShadingRateImage | ✓ | ✓ | ✓ | ||
RSSetViewports | ✓ | ✓ | |||
SetComputeRoot32BitConstant | ✓ | ✓ | ✓ | ✓ | |
SetComputeRoot32BitConstants | ✓ | ✓ | ✓ | ✓ | |
SetComputeRootConstantBufferView | ✓ | ✓ | ✓ | ✓ | |
SetComputeRootDescriptorTable | ✓ | ✓ | ✓ | ✓ | |
SetComputeRootShaderResourceView | ✓ | ✓ | ✓ | ✓ | |
SetComputeRootSignature | ✓ | ✓ | ✓ | ✓ | |
SetComputeRootUnorderedAccessView | ✓ | ✓ | ✓ | ✓ | |
SetDescriptorHeaps | ✓ | ✓ | ✓ | ✓ | |
SetGraphicsRoot32BitConstant | ✓ | ✓ | ✓ | ||
SetGraphicsRoot32BitConstants | ✓ | ✓ | ✓ | ||
SetGraphicsRootConstantBufferView | ✓ | ✓ | ✓ | ||
SetGraphicsRootDescriptorTable | ✓ | ✓ | ✓ | ||
SetGraphicsRootShaderResourceView | ✓ | ✓ | ✓ | ||
SetGraphicsRootSignature | ✓ | ✓ | ✓ | ||
SetGraphicsRootUnorderedAccessView | ✓ | ✓ | ✓ | ||
SetPipelineState | ✓ | ✓ | ✓ | ✓ | |
SetPipelineState1 | ✓ | ✓ | ✓ | ||
SetPredication | ✓ | ✓ | ✓ | ||
SetProtectedResourceSession | ✓ | ✓ | ✓ | ||
SetSamplePositions | ✓ | ✓ | ✓ | ||
SetViewInstanceMask | ✓ | ✓ | ✓ | ||
SOSetTargets | ✓ | ✓ | |||
WriteBufferImmediate | ✓ | ✓ | ✓ | ✓ | ✓ |
번들 제한 사항
제한을 통해 Direct3D 12 드라이버는 기록 시 번들과 관련된 대부분의 작업을 수행할 수 있기 때문에 낮은 오버헤드로 ExecuteBundle API를 실행할 수 있습니다. 번들에서 참조하는 모든 파이프라인 상태 개체에는 동일한 렌더링 대상 형식, 깊이 버퍼 형식 및 샘플 설명이 있어야 합니다.
다음 명령 목록 API 호출은 형식으로 만든 명령 목록에서 허용되지 않습니다. D3D12_COMMAND_LIST_TYPE_BUNDLE.
- Clear 메서드
- Copy 메서드
- DiscardResource
- ExecuteBundle
- ResourceBarrier
- ResolveSubresource
- SetPredication
- BeginQuery
- EndQuery
- SOSetTargets
- OMSetRenderTargets
- RSSetViewports
- RSSetScissorRects
SetDescriptorHeaps는 번들에서 호출할 수 있지만 번들 설명자 힙이 호출 명령 목록 설명자 힙과 일치해야 합니다.
번들에서 이러한 API 중 하나가 호출되면, 런타임은 호출을 중단합니다. 디버그 레이어는 이러한 상황이 발생할 때마다 오류를 발생시킵니다.