다중 엔진 다체(n-body) 중력 시뮬레이션
D3D12nBodyGravity 샘플은 컴퓨팅 작업을 비동기적으로 수행하는 방법을 보여 줍니다. 이 샘플은 각각 컴퓨팅 명령 큐를 포함하는 많은 스레드를 실행하고 다체(n-body) 중력 시뮬레이션을 수행하는 GPU에서 컴퓨팅 작업을 예약합니다. 각 스레드는 위치 및 속도 데이터로 가득 찬 두 개의 버퍼에서 작동합니다. 반복할 때마다 컴퓨팅 셰이더는 한 버퍼에서 현재 위치 및 속도 데이터를 읽고 다음 반복을 다른 버퍼에 기록합니다. 반복이 완료되면 컴퓨팅 셰이더가 위치/속도 데이터를 읽는 데 필요한 SRV인 버퍼와 각 버퍼에서 리소스 상태를 변경하여 위치/속도 업데이트를 쓰는 데 필요한 UAV인 버퍼를 교환합니다.
루트 서명 만들기
먼저 LoadAssets 메서드에서 그래픽 및 컴퓨팅 루트 서명을 둘 다 만듭니다. 두 루트 서명에는 모두 루트 CBV(상수 버퍼 보기) 및 SRV(셰이더 리소스 뷰) 설명자 테이블이 있습니다. 컴퓨팅 루트 서명에는 UAV(순서가 지정되지 않은 액세스 뷰) 설명자 테이블이 있습니다.
// Create the root signatures.
{
CD3DX12_DESCRIPTOR_RANGE ranges[2];
ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0);
CD3DX12_ROOT_PARAMETER rootParameters[RootParametersCount];
rootParameters[RootParameterCB].InitAsConstantBufferView(0, 0, D3D12_SHADER_VISIBILITY_ALL);
rootParameters[RootParameterSRV].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_VERTEX);
rootParameters[RootParameterUAV].InitAsDescriptorTable(1, &ranges[1], D3D12_SHADER_VISIBILITY_ALL);
// The rendering pipeline does not need the UAV parameter.
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init(_countof(rootParameters) - 1, 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. Must change visibility for the SRV.
rootParameters[RootParameterSRV].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
CD3DX12_ROOT_SIGNATURE_DESC computeRootSignatureDesc(_countof(rootParameters), rootParameters, 0, nullptr);
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 및 UAV 버퍼 만들기
SRV 및 UAV 버퍼는 위치 및 속도 데이터의 배열로 구성됩니다.
// Position and velocity data for the particles in the system.
// Two buffers full of Particle data are utilized in this sample.
// The compute thread alternates writing to each of them.
// The render thread renders using the buffer that is not currently
// in use by the compute shader.
struct Particle
{
XMFLOAT4 position;
XMFLOAT4 velocity;
};
호출 흐름 | 매개 변수 |
---|---|
XMFLOAT4 |
CBV 및 꼭짓점 버퍼 만들기
그래픽 파이프라인의 경우 CBV는 기하 도형 셰이더에서 사용하는 두 개의 매트릭스를 포함하는 구조체입니다. 기하 도형 셰이더는 시스템에서 각 파티클의 위치를 사용하며 이 매트릭스를 사용하여 위치를 나타내는 4분면을 생성합니다.
struct ConstantBufferGS
{
XMMATRIX worldViewProjection;
XMMATRIX inverseView;
// Constant buffers are 256-byte aligned in GPU memory. Padding is added
// for convenience when computing the struct's size.
float padding[32];
};
호출 흐름 | 매개 변수 |
---|---|
XMMATRIX |
따라서 꼭짓점 셰이더에서 사용하는 꼭짓점 버퍼에는 실제로 위치 데이터가 포함되지 않습니다.
// "Vertex" definition for particles. Triangle vertices are generated
// by the geometry shader. Color data will be assigned to those
// vertices via this struct.
struct ParticleVertex
{
XMFLOAT4 color;
};
호출 흐름 | 매개 변수 |
---|---|
XMFLOAT4 |
컴퓨팅 파이프라인의 경우 CBV는 컴퓨팅 셰이더에서 다체(n-body) 중력 시뮬레이션에 사용되는 일부 상수를 포함하는 구조체입니다.
struct ConstantBufferCS
{
UINT param[4];
float paramf[4];
};
렌더링 및 컴퓨팅 스레드 동기화
버퍼가 모두 초기화된 후 렌더링 및 컴퓨팅 작업이 시작됩니다. 컴퓨팅 스레드는 시뮬레이션에서 반복될 때 두 위치/속도 버퍼 상태를 SRV와 UAV 간에 변경하며, 렌더링 스레드는 SRV에서 작동하는 그래픽 파이프라인에서 작업을 예약하는지 확인해야 합니다. 펜스는 두 버퍼에 대한 액세스를 동기화하는 데 사용됩니다.
렌더링 스레드에서:
// Render the scene.
void D3D12nBodyGravity::OnRender()
{
// Let the compute thread know that a new frame is being rendered.
for (int n = 0; n < ThreadCount; n++)
{
InterlockedExchange(&m_renderContextFenceValues[n], m_renderContextFenceValue);
}
// Compute work must be completed before the frame can render or else the SRV
// will be in the wrong state.
for (UINT n = 0; n < ThreadCount; n++)
{
UINT64 threadFenceValue = InterlockedGetValue(&m_threadFenceValues[n]);
if (m_threadFences[n]->GetCompletedValue() < threadFenceValue)
{
// Instruct the rendering command queue to wait for the current
// compute work to complete.
ThrowIfFailed(m_commandQueue->Wait(m_threadFences[n].Get(), threadFenceValue));
}
}
// Record all the commands we need to render the scene into the command list.
PopulateCommandList();
// Execute the command list.
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
// Present the frame.
ThrowIfFailed(m_swapChain->Present(0, 0));
MoveToNextFrame();
}
호출 흐름 | 매개 변수 |
---|---|
InterlockedExchange | |
InterlockedGetValue | |
GetCompletedValue | |
연결 시도 간격 | |
ID3D12CommandList | |
ExecuteCommandLists | |
IDXGISwapChain1::Present1 |
샘플을 약간 단순화하기 위해 컴퓨팅 스레드는 추가 컴퓨팅 작업을 예약하기 전에 GPU가 각 반복을 완료할 때까지 기다립니다. 실제로 애플리케이션은 GPU에서 최대 성능을 얻기 위해 컴퓨팅 큐를 가득 찬 상태로 유지하려고 할 수 있습니다.
컴퓨팅 스레드에서:
DWORD D3D12nBodyGravity::AsyncComputeThreadProc(int threadIndex)
{
ID3D12CommandQueue* pCommandQueue = m_computeCommandQueue[threadIndex].Get();
ID3D12CommandAllocator* pCommandAllocator = m_computeAllocator[threadIndex].Get();
ID3D12GraphicsCommandList* pCommandList = m_computeCommandList[threadIndex].Get();
ID3D12Fence* pFence = m_threadFences[threadIndex].Get();
while (0 == InterlockedGetValue(&m_terminating))
{
// Run the particle simulation.
Simulate(threadIndex);
// Close and execute the command list.
ThrowIfFailed(pCommandList->Close());
ID3D12CommandList* ppCommandLists[] = { pCommandList };
pCommandQueue->ExecuteCommandLists(1, ppCommandLists);
// Wait for the compute shader to complete the simulation.
UINT64 threadFenceValue = InterlockedIncrement(&m_threadFenceValues[threadIndex]);
ThrowIfFailed(pCommandQueue->Signal(pFence, threadFenceValue));
ThrowIfFailed(pFence->SetEventOnCompletion(threadFenceValue, m_threadFenceEvents[threadIndex]));
WaitForSingleObject(m_threadFenceEvents[threadIndex], INFINITE);
// Wait for the render thread to be done with the SRV so that
// the next frame in the simulation can run.
UINT64 renderContextFenceValue = InterlockedGetValue(&m_renderContextFenceValues[threadIndex]);
if (m_renderContextFence->GetCompletedValue() < renderContextFenceValue)
{
ThrowIfFailed(pCommandQueue->Wait(m_renderContextFence.Get(), renderContextFenceValue));
InterlockedExchange(&m_renderContextFenceValues[threadIndex], 0);
}
// Swap the indices to the SRV and UAV.
m_srvIndex[threadIndex] = 1 - m_srvIndex[threadIndex];
// Prepare for the next frame.
ThrowIfFailed(pCommandAllocator->Reset());
ThrowIfFailed(pCommandList->Reset(pCommandAllocator, m_computeState.Get()));
}
return 0;
}
샘플 실행