기존에 작성되었던 방식은 아래와 같습니다.
void ExampleApp::Update(const GameTimer >) {
mCamera.Update();
UpdateConstantBuffer(mWorld, mCamera.GetViewMatrix(), mProj);
}
void ExampleApp::UpdateConstantBuffer(const XMMATRIX& world, const XMMATRIX& view, const XMMATRIX& proj)
{
XMMATRIX matWorldViewProj = XMMatrixTranspose(world * view * proj);
XMFLOAT4X4 WorldViewProj{};
XMStoreFloat4x4(&WorldViewProj, matWorldViewProj);
BYTE* pData;
HRESULT hr = mCB->Map(0, nullptr, reinterpret_cast<void**>(&pData));
if (FAILED(hr))
{
assert(false && "Failed to map constant buffer");
return;
}
ObjectConstants objConstants(WorldViewProj);
memcpy(pData, &objConstants, sizeof(ObjectConstants));
mCB->Unmap(0, nullptr);
}
코드를 확인해보면, CPU 내부에서 matWorldViewProj를 만들기 위해서 World, View, Projection Matrix를 연산하는 것을 볼 수 있습니다.
이것이 모든 오브젝트 마다 수행된다고 생각해보면 굉장히 많은 양의 Matrix 연산이 CPU 내부에서 일어난다는 것을 상상할 수 있습니다.
하지만 CPU 내부에서 이런 연산을 수행하는 것은 GPU에서 연산을 수행하는 것에 비해 매우 비효율적입니다.
따라서 GPU 내부에서 연산을 수행하도록 코드를 변경했습니다.
하지만 이럴 경우 문제가 생기는데, 이전에는 하나의 Matrix를 GPU로 복사하기만 하면 됐지만, 연산을 하지 않을 경우 3가지 Matrix를 복사하여야 합니다.
이때 '오브젝트'마다 공통적으로 사용하는 CB와 그렇지 않은 CB를 나눌 수 있습니다.
View MatrixProj MatrixWorld Matrix데이터의 개수가 얼마되지 않지만 선언한 structure에 값을 추가로 넣기만 하면되므로 확장성이 좋습니다. 따라서 틀을 만든다고 생각하면 좋을 것 같습니다.
분리한 이후의 코드는 아래와 같습니다.
template <typename T>
void UpdateConstantBuffer(ID3D12Resource* buffer, const ConstantBufferData& data)
{
BYTE* pData;
HRESULT hr = buffer->Map(0, nullptr, reinterpret_cast<void**>(&pData));
if (FAILED(hr))
{
assert(false && "Failed to map constant buffer");
return;
}
if constexpr (std::is_same<T, DynamicObjectConstants>::value) {
XMFLOAT4X4 wm = {};
XMStoreFloat4x4(&wm, XMMatrixTranspose(data.World));
T objConstants(wm);
memcpy(pData, &objConstants, sizeof(T));
}
else if constexpr (std::is_same<T, StaticObjectConstants>::value) {
XMFLOAT4X4 vm = {};
XMFLOAT4X4 pm = {};
XMStoreFloat4x4(&vm, XMMatrixTranspose(data.View));
XMStoreFloat4x4(&pm, XMMatrixTranspose(data.Proj));
T objConstants(vm, pm);
memcpy(pData, &objConstants, sizeof(T));
}
buffer->Unmap(0, nullptr);
}
이제 연산을 CPU에서 수행하지 않고 Shader에서 연산을 수행할 수 있도록 데이터를 복사만하는 것을 볼 수 있습니다.
위에서 한 것과 같이 데이터를 복사하는 것 만으로는 부족하기 때문에 추가로 필요한 부분을 수정해보겠습니다.
void ExampleApp::CreatePSO() {
CD3DX12_ROOT_PARAMETER slotRootParameter[2];
// cbvTable1: cbPass에 해당하는 상수 버퍼
CD3DX12_DESCRIPTOR_RANGE cbvTable1;
cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable1);
// cbvTable2: cbPerObject에 해당하는 상수 버퍼
CD3DX12_DESCRIPTOR_RANGE cbvTable2;
cbvTable2.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);
slotRootParameter[1].InitAsDescriptorTable(1, &cbvTable2);
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(2, slotRootParameter, 0, nullptr,
D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
...
}
코드와 같이 Signature를 수정하여 CB가 사용될 수 있도록 정의해줍니다.
Signature는 Shader와 GPU 사이에서 사용되는 인터페이스를 정의하는 것으로 볼 수 있습니다. 해당 코드에서는 하나의 'CB heap'을 사용하는 방식을 사용하기에 'DescriptorTable'로 인터페이스를 정의해 주었습니다.
void ExampleApp::BindConstantBuffer(ID3D12GraphicsCommandList* cmdList, UINT objectIndex) {
ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
mCommandList->SetGraphicsRootDescriptorTable(0,
CD3DX12_GPU_DESCRIPTOR_HANDLE(
mCbvHeap->GetGPUDescriptorHandleForHeapStart(),
objectIndex,
mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)));
mCommandList->SetGraphicsRootDescriptorTable(1,
CD3DX12_GPU_DESCRIPTOR_HANDLE(
mCbvHeap->GetGPUDescriptorHandleForHeapStart(),
objectIndex + NUMBER_OF_STATIC_CONSTANTBUFFER,
mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)));
}
하나의 heap을 사용하기에 index에 +1을 하여 바로 다음 CB를 Bind하는 것을 볼 수 있습니다.
cbuffer cbPass : register(b0)
{
float4x4 gView;
float4x4 gProj;
};
cbuffer cbPerObject : register(b1)
{
float4x4 gWorld;
};
struct VertexIn
{
float3 position : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// WVP = gProj * gView * gWorld
float4x4 WVP = mul(mul(gWorld, gView), gProj);
vout.PosH = mul(float4(vin.position, 1.0f), WVP);
vout.Color = vin.Color;
return vout;
}
더 이상 CPU 내부에서 연산을 수행하지 않기 때문에 Shader에서 Matrix 연산을 수행하는 것을 확인할 수 있습니다.