GPGPU
일반적인 연산 용도로 GPU를 사용
이펙트나 애니메이션 등 계산 시 유용
CPU와 GPU 통신 속도에 제한이 있기 때문에
CPU 부담이 큰 일을 처리할 때만 사용
compute 쉐이더 파일
RWTexture2D<float4> g_rwtex_0 : register(u0);
[numthreads(1024, 1, 1)]
void CS_Main(int3 threadIndex : SV_DispatchThreadID)
{
if (threadIndex.y % 2 == 0)
...
else
...
}
u 레지스터는 일반적인 그래픽 연산에 사용하는 레지스터가 아님
GPU는 group들로 이루어져 있고 gropu은 group thread로 이루어져 있음
numthreads의 인자들로 gropu thread가 만들어지게 되고
Dispatch라는 GPU 함수가 불릴 때 인자들로 group의 형태가 만들어 진다.
command queue 클래스 수정
일반적인 그래픽 용도의 커맨드 큐와 연산용 커맨드 큐는 만들 때 옵션 설정이 다르다
class ComputeCommandQueue
{
public:
...
private:
ComPtr<ID3D12CommandQueue> _cmdQueue;
ComPtr<ID3D12CommandAllocator> _cmdAlloc;
ComPtr<ID3D12GraphicsCommandList> _cmdList;
ComPtr<ID3D12Fence> _fence;
uint32 _fenceValue = 0;
HANDLE _fenceEvent = INVALID_HANDLE_VALUE;
};
기존 구현했던 커맨드 큐를 그래픽스 커맨드 큐로 바꾸고
연산 시 사용할 compute command queue 클래스 구현
해당 큐는 리소스를 필요로 하지 않음
void ComputeCommandQueue::Init(ComPtr<ID3D12Device> device)
{
D3D12_COMMAND_QUEUE_DESC computeQueueDesc = {};
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));
...
}
커맨드 큐를 만들 때 타입을 D3D12_COMMAND_LIST_TYPE_COMPUTE으로 하여
큐, 리스트와 얼로케이터 생성
root signature 클래스 수정
class RootSignature
{
...
ComPtr<ID3D12RootSignature> GetComputeRootSignature() { return _computeRootSignature; }
...
void CreateComputeRootSignature();
private:
ComPtr<ID3D12RootSignature> _computeRootSignature;
...
};
연산용으로 사용될 공간을 관리할 root signature 정의
void RootSignature::CreateComputeRootSignature()
{
...
D3D12_ROOT_SIGNATURE_DESC sigDesc = CD3DX12_ROOT_SIGNATURE_DESC(_countof(param), param);
sigDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE;
...
}
root signature의 descriptor를 만들 때 플래그로 D3D12_ROOT_SIGNATURE_FLAG_NONE을 사용
class ComputeDescriptorHeap
{
public:
...
void SetUAV(D3D12_CPU_DESCRIPTOR_HANDLE srcHandle, UAV_REGISTER reg);
void CommitTable();
...
D3D12_CPU_DESCRIPTOR_HANDLE GetCPUHandle(UAV_REGISTER reg);
...
};
Unordered Access View 레지스터를 사용할 descriptor heap 클래스 정의 및 구현
shader 클래스 수정
enum class SHADER_TYPE : uint8
{
DEFERRED,
FORWARD,
LIGHTING,
COMPUTE,
};
쉐이더 타입에 compute 추가
class Shader : public Object
{
public:
...
void CreateComputeShader(const wstring& path, const string& name, const string& version);
...
private:
...
ComPtr<ID3DBlob> _csBlob;
D3D12_COMPUTE_PIPELINE_STATE_DESC _computePipelineDesc = {};
};
compute 쉐이더를 사용할 변수 및 생성 함수 정의
texture 클래스 수정
if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)
{
...
uavHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
uavHeapDesc.NumDescriptors = 1;
uavHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
uavHeapDesc.NodeMask = 0;
DEVICE->CreateDescriptorHeap(&uavHeapDesc, IID_PPV_ARGS(&_uavHeap));
...
}
텍스쳐 생성 시 UAV 사용 플래그가 있다면 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV 타입으로 생성
void Texture::Create(DXGI_FORMAT format, uint32 width, uint32 height,
const D3D12_HEAP_PROPERTIES& heapProperty, D3D12_HEAP_FLAGS heapFlags,
D3D12_RESOURCE_FLAGS resFlags, Vec4 clearColor)
{
...
D3D12_CLEAR_VALUE optimizedClearValue = {};
D3D12_CLEAR_VALUE* pOptimizedClearValue = nullptr;
D3D12_RESOURCE_STATES resourceStates = D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_COMMON;
if (resFlags & D3D12_RESOURCE_FLAGS::D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)
{
...
pOptimizedClearValue = &optimizedClearValue;
}
else if (resFlags & D3D12_RESOURCE_FLAGS::D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET)
{
...
pOptimizedClearValue = &optimizedClearValue;
}
HRESULT hr = DEVICE->CreateCommittedResource(
&heapProperty,
heapFlags,
&desc,
resourceStates,
pOptimizedClearValue,
IID_PPV_ARGS(&_tex2D));
...
}
UAV로 사용할 텍스쳐의 경우 clear value가 null이어야 함
constant buffer 클래스 수정
material 클래스 수정
class Material : public Object
{
public:
...
void PushComputeData();
void Dispatch(uint32 x, uint32 y, uint32 z);
...
};
material 데이터들을 GPU에 업로드하고 실제 GPU가 compute 쉐이더를 계산하도록하는
Dispatch 함수 정의
void Material::Dispatch(uint32 x, uint32 y, uint32 z)
{
PushComputeData();
GEngine->GetComputeDescHeap()->CommitTable();
COMPUTE_CMD_LIST->Dispatch(x, y, z);
GEngine->GetComputeCmdQueue()->FlushComputeCommandQueue();
}
compute 시 사용할 데이터를 업로드,
커맨드 큐의 dispatch를 사용하여 요청
compute shader 사용
{
shared_ptr<Shader> shader = GET_SINGLE(Resources)->Get<Shader>(L"ComputeShader");
shared_ptr<Texture> texture = GET_SINGLE(Resources)->CreateTexture(L"UAVTexture",
DXGI_FORMAT_R8G8B8A8_UNORM, 1024, 1024,
CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
...
GEngine->GetComputeDescHeap()->SetUAV(texture->GetUAVHandle(), UAV_REGISTER::u0);
material->Dispatch(1, 1024, 1);
}
텍스쳐 생성 시
플래그가 D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET가 아닌
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS 사용