이번에는 Constant Buffer를 배열의 형태로 사용해 보자.
// EnginePch.h
// ...
enum class CBV_REGISTER
{
// 데이터를 넣는데 사용할 레지스터
b0,
b1,
b2,
b3,
b4,
END
};
enum
{
SWAP_CHAIN_BUFFER_COUNT = 2,
CBV_REGISTER_COUNT = CBV_REGISTER::END,
REGISTER_COUNT = CBV_REGISTER::END,
};
// ...
void RootSignature::Init(ComPtr<ID3D12Device> device)
{
void RootSignature::Init(ComPtr<ID3D12Device> device)
{
CD3DX12_DESCRIPTOR_RANGE ranges[] =
{
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_CBV,
CBV_REGISTER_COUNT, // 5 (b0~b4)
0),
// GPU레지스터 공간에 b0~b4 를 쓸 예정
// CD3DX12_DESCRIPTOR_RANGE를 굳이 배열로 만들어야하나??
// 이후에 다른 레지스터사용시에 간단히 CD3DX12_DESCRIPTOR_RANGE만 추가해서 사용하기 위함.
};
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_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
// ...
}
기존 코드(Constant Buffer)와 차이점은 Constant Buffer View를 직접 param에 넣는지 혹은 DescriptorTable(View Table)형태로 넣는지의 차이다.
Constant Buffer를 배열의 형태로 쓰기위해선 Descritor Table형태로 넣어야 한다.
void ConstantBuffer::Init(uint32 size, uint32 count)
{
// 상수 버퍼는 256 바이트 배수로 만들어야 한다
// 0 256 512 768
_elementSize = (size + 255) & ~255;
_elementCount = count;
CreateBuffer();
CreateView();
}
// GPU에 메모리를 할당하는 방법은 기존과 동일하며
void ConstantBuffer::CreateBuffer()
{
uint32 bufferSize = _elementSize * _elementCount;
D3D12_HEAP_PROPERTIES heapProperty = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
// 버퍼를 할당해 달라.
DEVICE->CreateCommittedResource(
&heapProperty,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&_cbvBuffer));
// 이 버퍼를 GPU와 통신하는데 쓸 것이다 선언
_cbvBuffer->Map(0, nullptr, reinterpret_cast<void**>(&_mappedBuffer));
// We do not need to unmap until we are done with the resource. However, we must not write to
// the resource while it is in use by the GPU (so we must use synchronization techniques).
}
// 이 View는 위에서 할당한 Table을 컨트롤하기 위한 View(핸들)이다.
void ConstantBuffer::CreateView()
{
D3D12_DESCRIPTOR_HEAP_DESC cbvDesc = {};
cbvDesc.NumDescriptors = _elementCount;
cbvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
cbvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
DEVICE->CreateDescriptorHeap(&cbvDesc, IID_PPV_ARGS(&_cbvHeap));
// View의 시작주소를 갖고있고
_cpuHandleBegin = _cbvHeap->GetCPUDescriptorHandleForHeapStart();
// View의 주소 크기를 알면 모든 View에 접근이 가능(시작주소 + View주소크기 * i)
// View면 HANDLE인데 항상 주소의 크기는 같은거 아님??? -> Nope! 그래픽 카드에 따라 주소의 크기가 달라질수 있기에 구해오는게 맞음.
_handleIncrementSize = DEVICE->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
for (uint32 i = 0; i < _elementCount; ++i)
{
D3D12_CPU_DESCRIPTOR_HANDLE cbvHandle = GetCpuHandle(i); // (시작주소 + View주소크기 * i)
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = _cbvBuffer->GetGPUVirtualAddress() + static_cast<uint64>(_elementSize) * i;
cbvDesc.SizeInBytes = _elementSize; // CB size is required to be 256-byte aligned.
DEVICE->CreateConstantBufferView(&cbvDesc, cbvHandle);
}
}
D3D12_CPU_DESCRIPTOR_HANDLE ConstantBuffer::PushData(int32 rootParamIndex, void* buffer, uint32 size)
{
assert(_currentIndex < _elementSize);
::memcpy(&_mappedBuffer[_currentIndex * _elementSize], buffer, size);
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = GetCpuHandle(_currentIndex);
_currentIndex++;
return cpuHandle;
}
여기까지하면 GPU에 메모리공간을 할당한거까지는 끝난다.
이제 할당된 GPU메모리에 데이터를 넣고, 다시 레지스터로 보내는것을 확인하면 끝
TableDescriptorHeap
void TableDescriptorHeap::Init(uint32 count)
{
_groupCount = count;
D3D12_DESCRIPTOR_HEAP_DESC desc = {};
desc.NumDescriptors = count * REGISTER_COUNT;
desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
DEVICE->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_descHeap));
_handleSize = DEVICE->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
_groupSize = _handleSize * REGISTER_COUNT;
}
GPU메모리에 데이터 넣기
void TableDescriptorHeap::SetCBV(D3D12_CPU_DESCRIPTOR_HANDLE srcHandle, CBV_REGISTER reg)
{
D3D12_CPU_DESCRIPTOR_HANDLE destHandle = GetCPUHandle(reg);
uint32 destRange = 1;
uint32 srcRange = 1;
DEVICE->CopyDescriptors(1, &destHandle, &destRange, 1, &srcHandle, &srcRange, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}
레지스터로 데이터 보내기
void TableDescriptorHeap::CommitTable()
{
D3D12_GPU_DESCRIPTOR_HANDLE handle = _descHeap->GetGPUDescriptorHandleForHeapStart();
handle.ptr += _currentGroupIndex * _groupSize;
CMD_LIST->SetGraphicsRootDescriptorTable(0, handle);
_currentGroupIndex++;
}
void Mesh::Render()
{
CMD_LIST->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
CMD_LIST->IASetVertexBuffers(0, 1, &_vertexBufferView); // Slot: (0~15)
// TODO
// 1) Buffer에다가 데이터 세팅
// 2) TableDescHeap에다가 CBV 전달
// 3) 모두 세팅이 끝났으면 TableDescHeap 커밋
{
D3D12_CPU_DESCRIPTOR_HANDLE handle = GEngine->GetCB()->PushData(0, &_transform, sizeof(_transform));
GEngine->GetTableDescHeap()->SetCBV(handle, CBV_REGISTER::b0);
}
{
D3D12_CPU_DESCRIPTOR_HANDLE handle = GEngine->GetCB()->PushData(0, &_transform, sizeof(_transform));
GEngine->GetTableDescHeap()->SetCBV(handle, CBV_REGISTER::b1);
}
GEngine->GetTableDescHeap()->CommitTable();
CMD_LIST->DrawInstanced(_vertexCount, 1, 0, 0);
}
헷갈리는 부분 정리
GetCpuHandle ?? 뭘 받는건가
// ConstantBuffer.cpp
D3D12_CPU_DESCRIPTOR_HANDLE ConstantBuffer::GetCpuHandle(uint32 index)
{
return CD3DX12_CPU_DESCRIPTOR_HANDLE(_cpuHandleBegin, index * _handleIncrementSize);
}
// TableDescriptorHeap.cpp
D3D12_CPU_DESCRIPTOR_HANDLE TableDescriptorHeap::GetCPUHandle(CBV_REGISTER reg)
{
return GetCPUHandle(static_cast<uint32>(reg));
}
D3D12_CPU_DESCRIPTOR_HANDLE TableDescriptorHeap::GetCPUHandle(uint32 reg)
{
D3D12_CPU_DESCRIPTOR_HANDLE handle = _descHeap->GetCPUDescriptorHandleForHeapStart();
handle.ptr += _currentGroupIndex * _groupSize;
handle.ptr += reg * _handleSize;
return handle;
}
결국 데이터를 올릴 레지스터의 DescriptorHeap의 주소를 리턴한다.
어떻게 사용되나 보면
void Mesh::Render()
{
// ...
D3D12_CPU_DESCRIPTOR_HANDLE handle = GEngine->GetCB()->PushData(0, &_transform, sizeof(_transform));
GEngine->GetTableDescHeap()->SetCBV(handle, CBV_REGISTER::b0);
D3D12_CPU_DESCRIPTOR_HANDLE ConstantBuffer::PushData(int32 rootParamIndex, void* buffer, uint32 size)
{
assert(_currentIndex < _elementSize);
// _mapped된 ConstantBuffer에 데이터를 복사 후
::memcpy(&_mappedBuffer[_currentIndex * _elementSize], buffer, size);
// 복사된 핸들값을 리턴
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = GetCpuHandle(_currentIndex);
_currentIndex++;
return cpuHandle;
}
void TableDescriptorHeap::SetCBV(D3D12_CPU_DESCRIPTOR_HANDLE srcHandle, CBV_REGISTER reg)
{
// 현재 DesciptorHeap의 reg에 맞는 핸들을 받아서
D3D12_CPU_DESCRIPTOR_HANDLE destHandle = GetCPUHandle(reg);
uint32 destRange = 1;
uint32 srcRange = 1;
// 복사할 원본소스(src)의 데이터를 목적지(desHandle)로 복사
DEVICE->CopyDescriptors(1, &destHandle, &destRange, 1, &srcHandle, &srcRange, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}