헛둘이 2023. 1. 16. 12:28

지난 번에 설명했듯 RootDescriptor는 무한정으로 늘릴 수 없다.

 

어떤 정책을 사용할 것인가에 따라 해당 DescriptorTable이 활성화된다.

1번을 켜겠다 or 2번을 켜겠다에 따라 활성화되는 테이블이 달라진다.

 

- ConstantBuffer에 값을 넣고 그 값을 참조하는 View를 만든다.

- View를 참조하는 DescriptorHeap을 만든다

- 그 Heap을 Shader Visible한 Heap에 복사한다. (TableDescriptorHeap)

- 그려질 여러 도형에 대해 값을 세팅하고 각각 적용하려면 타이밍 문제가 발생하므로

- Heap을 참조하는 View는 여러 개가 되어야 한다. (값을 써넣는 부분은 즉시 이루어지고, 상수 버퍼를 참조하는 부분은 CommandList로 작업이 이루어지기 때문에 하나의 버퍼만 사용할 경우 값을 집어넣는 부분에서 덮어써진다) 

- 먼저 SetDescriptorHeaps 함수를 통해 쉐이더 프로그램에서 사용할 힙을 설정해준다. (그리기 시작하는 시점)

- SetGraphicsRootDescriptorTable로 힙에 대한 정보를 알려준다. (그려지기 직전 시점 (메시측) )

 

1. 테이블을 넣겠다고 RootSignature에 서명한다.

void RootSignature::Init(ComPtr<ID3D12Device> device)
{
	// 테이블 내부 모양을 묘사
	CD3DX12_DESCRIPTOR_RANGE ranges[] =
	{
		CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, CBV_REGISTER_COUNT, 0)
	};

	// param에 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;
	

	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(&_signature));

	
}

- 테이블 내부 구조는 ranges를 통해 묘사되어 있다.

- param에 range를 기반으로 테이블을 생성하고 이전 예제처럼 루트 시그니쳐를 생성한다.

 

2. ConstantBuffer에 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));

	_cpuHandleBegin = _cbvHeap->GetCPUDescriptorHandleForHeapStart();
	_handleIncrementSize = DEVICE->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

	for (uint32 i = 0; i < _elementCount; ++i)
	{
		D3D12_CPU_DESCRIPTOR_HANDLE cbvHandle = GetCpuHandle(i);
		
		D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
		cbvDesc.BufferLocation = _cbvBuffer->GetGPUVirtualAddress() + static_cast<uint64>(_elementSize) * i;
		cbvDesc.SizeInBytes = _elementSize;

		DEVICE->CreateConstantBufferView(&cbvDesc, cbvHandle);
	}
}

 

 

 

3. TableDescriptorHeap 클래스를 정의한다.

- TableDescriptorHeap은 View의 2차원 배열

- 예제에서는 행을 Group이라고 부르고, 열을 index로 접근하는 방식으로 사용한다.

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;
}

- 행을 _groupCount로 초기화한다.

- DescriptorHeap을 생성할 때 D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE 플래그를 주면

- GPU는 쉐이더에서 이 DescriptorHeap을 직접 참조할 수 있게 된다 (성능 향상에 도움이 된다)

- 이 플래그를 주지 않으면 CPU에서 이 힙을 참조할 수밖에 없다.

 

4. Mesh의 Render에서 마찬가지로 ConstantBuffer에 정보를 밀어넣고, Shader Visible한 Heap에 복사한다.

void Mesh::Render()
{
	// 실제로는 그려줄 때는 이 뷰를 사용해서 그려주게 된다.
	CMD_LIST->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	CMD_LIST->IASetVertexBuffers(0, 1, &_vertexBufferView);

	// 1. Buffer에 데이터 세팅
	// - GPU 메모리에 뚜껑을 열고 데이터를 집어넣는 부분이 Mesh의 Init에 비슷하게 구현되어 있음
	// 2. Buffer의 주소를 Register에 전송

	{
		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->SetGraphicsRootConstantBufferView(0, GEngine->GetCB()->GetGpuVirtualAddress(0));

	CMD_LIST->DrawInstanced(_vertexCount, 1, 0, 0);
}

- 달라진 것은 ConstantBuffer 클래스의 PushData에서 핸들을 반환하고, 그 핸들을 통해 ConstantBufferView에 값을 세팅하는 것이다.

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;
}
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);

	// 디스크립터를 복사하는 함수
}

- ConstantBuffer에 데이터를 밀어넣고, 현재 인덱스의 디스크립터 포인터를 반환한다.

- SetCBV에서는 그 포인터를 받아서 Shader Visible한 DescriptorHeap에 값을 복사한다. (인자로 받은 위치에)

 

D3D12_CPU_DESCRIPTOR_HANDLE이란?

- GPU의 메모리를 참조하는 CPU측의 핸들이고, Descriptor를 참조한다.

 

 

5. TableDescriptorHeap을 커밋한다.

ID3D12DescriptorHeap* descHeap = GEngine->GetTableDescHeap()->GetDescriptorHeap().Get();
_cmdList->SetDescriptorHeaps(1, &descHeap);
// 무거우니 한 프레임당 한번만
// 이걸 먼저 해주고 그다음 TableDesc의 CommitTable을 해줘야 크래시가 나지 않는다

- 커밋하기 전에 쉐이더에서 어떤 힙을 사용할 것인지 전달해줘야 한다

 

void TableDescriptorHeap::CommitTable()
{
	D3D12_GPU_DESCRIPTOR_HANDLE handle = _descHeap->GetGPUDescriptorHandleForHeapStart();
	handle.ptr += _currentGroupIndex * _groupSize;

	CMD_LIST->SetGraphicsRootDescriptorTable(0, handle);

	_currentGroupIndex++;

	// SetCBV로 복사를 해준 후 CommitTable을 통해 값이 할당된다.
}

- 그러면 CommitTable에서 CMD_LIST->SetGraphicsRootDescriptorTable(0, handle)에서 어떤 정보를 사용할 건지 전달할 수 있다.