DirectX

6.5 상수 버퍼(Constant buffer)

Awesome Red Tomato 2021. 12. 26. 07:22

상수 버퍼는 셰이더 프로그램에서 참조하는 자료를 담은 GPU 자원(ID3D12Resource)의 예이다. Vertex Shader 코드 중 이런 구문이 있다. 

cbuffer cbPerObject : register(b0)
{
	float4x4 gWorldViewProj;
};

 

여기서 cbPerObject 라는 cbuffer 객체(상수 버퍼)를 참조한다. 여기서 상수 버퍼는 gWorldViewProj라는 4 x 4 행렬 하나만 저장한다.

 

정점, 색인 버퍼와는 달리 상수 버퍼는 프레임마다 갱신해주는 것이 일반적이다. 카메라가 매 프레임 이동할 때 마다 상수 버퍼를 새 시야 행렬로 갱신해야 할 것이다. 따라서 상수 버퍼는 디폴트 힙이 아닌 업로드 힙에 만든다.

 

또한, 상수 버퍼에는 특별한 하드웨어 조건이 따른다. 크기가 반드시 최소 하드웨어 할당 크기(256byte)의 배수이어야 한다는 것이다.

 

같은 종류의 상수 버퍼를 여러 개 사용해야 하는 경우가 많다. 예를 들어 cbPerObject는 물체마다 달라지는 상수들을 담으므로, 물체가 n개면 이 종류의 상수 버퍼는 n개가 필요하다. 다음 코드는 NumElements개의 상수 버퍼 객체를 담는 하나의 버퍼를 생성한다.

struct ObjectConstants
{
	DirectX::XMFLOAT4X4 WorldViewProj = MathHelper::Identity4x4();
};

UploadBuffer(ID3D12Device* pDevice, UINT elementCount, bool isConstantBuffer)
	:
	mIsConstantBuffer(isConstantBuffer)
{
	if (isConstantBuffer)
	{
		mElementByteSize = D3DUtil::CalcConstantBufferByteSize(sizeof(T));
	}
	mElementByteSize = sizeof(T);

	ThrowIfFailed(pDevice->CreateCommittedResource(
		&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
		D3D12_HEAP_FLAG_NONE,
		&CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize * elementCount),
		D3D12_RESOURCE_STATE_GENERIC_READ,
		nullptr,
		IID_PPV_ARGS(mUploadBuffer.GetAddressOf())));

}
// 256byte 경계에 맞게 바이트들이 암묵적으로 채워진다.
cbuffer cbPerObject : register(b0)
{
	float4x4 gWorldViewProj;
};


// 256byte 경계에 맞게 명시적으로 바이트들을 채운다.
cbuffer cbPerObject : register(b0)
{
	float4x4 gWorldViewProj;
	float4x4 Pad0;
	float4x4 Pad1;
	float4x4 Pad1;
};

 

 

상수 버퍼를 D3D12_HEAP_TYPE_UPLOAD에 생성했으므로 CPU에서 상수 버퍼 자원에 자료를 올릴 수 있다. 자원을 올리려면 자원 자료를 가리키는 포인터를 얻어야 하는데 그러려면 Map()을 호출해야 한다.

ThrowIfFailed(mUploadBuffer->Map(
	0,
	nullptr,
	reinterpret_cast<void**>(&mMappedData)));

 

시스템 메모리에 있는 자료를 상수 버퍼에 복사하려면 memcpy를 이용한다.

memcpy(mMappedData, &data, dataSizeInBytes);

 

상수버퍼에 자료를 다 복사했으면 해당 메모리를 해제하기 전에 Ummap()을 호출한다.

if(mUploadBuffer != nullptr)
{
	mUploadBuffer->Unmap(0, nullptr);
}

mMappedData = nullptr;

 

이제 이 상수 버퍼를 렌더링 파이프라인에 묶으려면 서술자 객체가 필요하다. 상수 버퍼 서술자는 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_STV_UAV 형식의 서술자 힙에 담긴다.
(상수 버퍼는 업로드 힙에, 상수 버퍼 뷰는 CBV_STV_UAV 힙에 생성)

void BoxApp::BuildConstantDescriptorHeap()
{
	D3D12_DESCRIPTOR_HEAP_DESC cbvDesc;
	cbvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	cbvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	cbvDesc.NumDescriptors = 1;
	cbvDesc.NodeMask = 0;
	ThrowIfFailed(mDevice->CreateDescriptorHeap(
		&cbvDesc, 
		IID_PPV_ARGS(mCbvHeap.GetAddressOf())));
}

그리고 이제 상수 버퍼 뷰(=서술자)를 생성한다.

void BoxApp::BuildConstantBuffer()
{
	mConstantBuffer =
		std::make_unique<UploadBuffer<ObjectConstants>>(mDevice.Get(), 1, true);

	UINT cbByteSize = D3DUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));

	D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
	cbvDesc.BufferLocation = mConstantBuffer->GetConstantBuffer()->GetGPUVirtualAddress();
	cbvDesc.SizeInBytes = cbByteSize;

	mDevice->CreateConstantBufferView(
		&cbvDesc,
		mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}

 

'DirectX' 카테고리의 다른 글

6.7 셰이더의 컴파일  (0) 2021.12.31
6.6 루트 서명과 서술자 테이블  (0) 2021.12.31
6.4 픽셀 셰이더(Pixel Shader)  (0) 2021.12.26
6.3 정점 셰이더(Vertex Shader)  (0) 2021.12.26
6.2 색인(index)  (0) 2021.12.24