- Compute Shader(GPGPU) : 랜더링 이외의, 일반적 연산을 GPU에게 맡겨보자
- 병렬적으로 처리해야 하는 연산의 경우 Compute Shader가 유리하다(소수찾기, 각 픽셀의 연산 등)
DirectX를 이용해 Compute Shader 기능 사용하기
우선 쉐이더 먼저 구현
#ifndef _COMPUTE_FX_
#define _COMPUTE_FX_
#include "params.fx"
// u0는 Compute Shader 전용 레지스트리
RWTexture2D<float4> g_rwtex_0 : register(u0);
// 쓰레드 그룹당 쓰레드 개수
// max : 1024 (CS_5.0)
// - 하나의 쓰레드 그룹은 하나의 다중처리기에서 실행
[numthreads(1024, 1, 1)]
void CS_Main(int3 threadIndex : SV_DispatchThreadID)
{
// thread index에 따라 색을 구분
if (threadIndex.y % 2 == 0)
g_rwtex_0[threadIndex.xy] = float4(1.f, 0.f, 0.f, 1.f);
else
g_rwtex_0[threadIndex.xy] = float4(0.f, 1.f, 0.f, 1.f);
}
// Q. numtherads, threadindex는 뭐지???
#endif
아직 numtherads, threadindex의 설명이 없는데 우선 아래를 먼저 알아야한다.
void Material::Dispatch(uint32 x, uint32 y, uint32 z)
{
// CBV + SRV + SetPipelineState
PushComputeData();
// SetDescriptorHeaps + SetComputeRootDescriptorTable
GEngine->GetComputeDescHeap()->CommitTable();
// Compute Shader는 Dispatch명령으로 실행이 된다.
COMPUTE_CMD_LIST->Dispatch(x, y, z);
GEngine->GetComputeCmdQueue()->FlushComputeCommandQueue();
}
MSDN의 Dispatch함수를 보면 세 개의 ThreadGroupCount를 넘기는데
void Dispatch(
UINT ThreadGroupCountX,
UINT ThreadGroupCountY,
UINT ThreadGroupCountZ
);
여기서 말하는 ThreadGroupCount란 Thread블록내의 스레드 테이블을 의미한다.
다시 쉐이더로 돌아와서
// 쓰레드 그룹당 쓰레드 개수
// max : 1024 (CS_5.0)
// 여기서 말하는 numthreads(x, y, z)는
// 몇개의 Thread를 사용할지 여부이다
// 최대 1024개이며 개수는 x * y * z로 정해진다.
// 그럼 그냥 1024 넣으면 되지 왜 x, y, z를 뒀느냐?
// x, y, z에 따라 index가 달라지게 된다.
[numthreads(1024, 1, 1)]
예를들어 5, 3, 2로 넣었다면
Thread 테이블이 X축 5, Y축 3, Z축 2개를 만들게 되며 그 numthread는 5 * 3 * 2가 된다.
[numthreads(1024, 1, 1)] // 1차원 배열로 1024개 Thread 할당
void CS_Main(int3 threadIndex : SV_DispatchThreadID) // SV_DispatchThreadID : 유니크한 ThreadID 를 for문처럼 threadIndex에 넣어달라
{
if (threadIndex.y % 2 == 0)
g_rwtex_0[threadIndex.xy] = float4(1.f, 0.f, 0.f, 1.f);
else
g_rwtex_0[threadIndex.xy] = float4(0.f, 1.f, 0.f, 1.f);
}
이제 코드 구현
class ComputeCommandQueue
{
public:
~ComputeCommandQueue();
void Init(ComPtr<ID3D12Device> device);
void WaitSync();
void FlushComputeCommandQueue();
ComPtr<ID3D12CommandQueue> GetCmdQueue() { return _cmdQueue; }
ComPtr<ID3D12GraphicsCommandList> GetComputeCmdList() { return _cmdList; }
private:
ComPtr<ID3D12CommandQueue> _cmdQueue;
ComPtr<ID3D12CommandAllocator> _cmdAlloc;
ComPtr<ID3D12GraphicsCommandList> _cmdList;
ComPtr<ID3D12Fence> _fence;
uint32 _fenceValue = 0;
HANDLE _fenceEvent = INVALID_HANDLE_VALUE;
};
void ComputeCommandQueue::Init(ComPtr<ID3D12Device> device)
{
D3D12_COMMAND_QUEUE_DESC computeQueueDesc = {};
// D3D12_COMMAND_LIST_TYPE_COMPUTE로 만듦
computeQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
computeQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
device->CreateCommandQueue(&computeQueueDesc, IID_PPV_ARGS(&_cmdQueue));
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COMPUTE, IID_PPV_ARGS(&_cmdAlloc));
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COMPUTE, _cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&_cmdList));
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));
// CreateFence
// - CPU와 GPU의 동기화 수단으로 쓰인다
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));
_fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
}
RootSignature도 Compute용으로 새로만들어야 한다.
void RootSignature::CreateComputeRootSignature()
{
CD3DX12_DESCRIPTOR_RANGE ranges[] =
{
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, CBV_REGISTER_COUNT, 0), // b0~b4
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, SRV_REGISTER_COUNT, 0), // t0~t9
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, UAV_REGISTER_COUNT, 0), // u0~u4
};
CD3DX12_ROOT_PARAMETER param[1];
param[0].InitAsDescriptorTable(_countof(ranges), ranges);
D3D12_ROOT_SIGNATURE_DESC sigDesc = CD3DX12_ROOT_SIGNATURE_DESC(_countof(param), param);
sigDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE;
ComPtr<ID3DBlob> blobSignature;
ComPtr<ID3DBlob> blobError;
::D3D12SerializeRootSignature(&sigDesc, D3D_ROOT_SIGNATURE_VERSION_1, &blobSignature, &blobError);
DEVICE->CreateRootSignature(0, blobSignature->GetBufferPointer(), blobSignature->GetBufferSize(), IID_PPV_ARGS(&_computeRootSignature));
COMPUTE_CMD_LIST->SetComputeRootSignature(_computeRootSignature.Get());
}