공부/게임수학 및 DirectX12

Direct3D 12 그래픽 파이프라인

원클릭쓰리버그 2022. 4. 6. 21:03
728x90

Shader

 

struct VS_IN
{
    float3 pos : POSITION;
    float4 color : COLOR;
};

struct VS_OUT
{
    float4 pos : SV_Position;
    float4 color : COLOR;
};

VS_OUT VS_Main(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;

    output.pos = float4(input.pos, 1.f);
    output.color = input.color;

    return output;
}

float4 PS_Main(VS_OUT input) : SV_Target
{
    return input.color;
}

 

 

 

만약 셰이더를 꾸미고 싶다면 단순히 셰이더 스크립트에 가서 작업을 한다면 좋겠지만, 셰이더는 셰이더만의 문법이 있기때문에 불가능하다. 

 

Direct3D 12 그래픽 파이프라인

위의 흐름도에서 볼 수 있 듯, Root arguments를 이용하여 우리가 스크립팅한 Descrptor Heap을 전달하고 있다. 저기서 Root arguments 는 Redering Pipeline에 적용 할 수 있도록 해주는 허가서 역할을 해준다.

 

 

 

CPU vs GPU memory hierarchy

위의 조직도는 가장 상위의 FP, Core단( 빠른 연산이 가능하지만 적은 메모리 공간)과 하위단(하위로 내려갈수록 상대적으로 느린 연산과 상대적으로 넓은 메모리 공간)을 보여 준다. 적은 메모리 공간을 효율적으로 관리하기 위해서는 하위 단에서 상위단의 단계에 도달할때마다 공간마다 높은 기준의 허가가 필요하다. 

 

 

이러한 허가를 만드는 단계가 Root Signature 단계이다.

 

https://docs.microsoft.com/ko-kr/windows/win32/direct3d12/example-root-signatures

 

루트 서명 예제 - Win32 apps

다음 섹션에서는 비어 있음부터 완전 가득 참까지 복잡성이 다양한 루트 서명을 보여 줍니다.

docs.microsoft.com

RootSignature.cpp ()

#include "pch.h"
#include "RootSignature.h"

void RootSignature::Init(ComPtr<ID3D12Device> device)
{
	D3D12_ROOT_SIGNATURE_DESC sigDesc = CD3DX12_ROOT_SIGNATURE_DESC(D3D12_DEFAULT);
	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));
}

위의 스크립트 상태

 

  • root contant : 일반 상수가 들어 갈 수 있는 Register에 들어가겠다.
  • root CBV(Costant Buffer View) : Resources가 들어 갈 수 있는 Register에 들어가겠다.
  • desc.Table : 허가 묶음 정책을 배포하여 Register에 들어가겠다.
    • 다양한 정책들을 묶어서 값을 들어가게 한다. 만약 정책이 틀리다면 허가를 하지 않음.
    • 왜 Table가 필요한가 : 상위단의 Register으로 갈 수록 성능은 좋지만 저장 공간이 적어지므로 메모리 공간의 분배가 필요하다.

 

 

root CBV 선언

 

#include "pch.h"
#include "RootSignature.h"

void RootSignature::Init(ComPtr<ID3D12Device> device)
{
	CD3DX12_ROOT_PARAMETER param[2];
	param[0].InitAsConstantBufferView(0); // 0번 -> b0 -> CBV
	param[1].InitAsConstantBufferView(1); // 1번 -> b1 -> CBV

	D3D12_ROOT_SIGNATURE_DESC sigDesc = CD3DX12_ROOT_SIGNATURE_DESC(2,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));
}

위의 그림과 같이 0번 1번에 root CBV를 b0,b1으로 선언하겠다.

그걸 허락서에 작성한다.라는 문구로 이해하면 된다. 

 

 

 

 

CPU에서 연산처리가 되는 작업과 CommandQueue에서 작업하는 2방식에 대해 이해하기 쉽게 만든 그림이다.

 

Mesh.cpp

void* vertexDataBuffer = nullptr;
	CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
	_vertexBuffer->Map(0, &readRange, &vertexDataBuffer);
	::memcpy(vertexDataBuffer, &vec[0], bufferSize);
	_vertexBuffer->Unmap(0, nullptr);

임의의 포인터 공간을 할당하여, vertexBuffer를 넘겨주는 행위.

 

 

constantBuffer.h

위의 그림에서 GPU에 할당할 공간 b0, b1과 Buffer공간 2개를 만드는 작업.

#pragma once

class ConstantBuffer
{
public:
	ConstantBuffer();
	~ConstantBuffer();

	void Init(uint32 size, uint32 count);

	void Clear();
	void PushData(int32 rootParamIndex, void* buffer, uint32 size);

	D3D12_GPU_VIRTUAL_ADDRESS GetGpuVirtualAddress(uint32 index);

private:
	void CreateBuffer();

private:
	ComPtr<ID3D12Resource>	_cbvBuffer;
	BYTE*					_mappedBuffer = nullptr;
	uint32					_elementSize = 0;
	uint32					_elementCount = 0;

	uint32					_currentIndex = 0;
};

 

constantBuffer.cpp

 

#include "pch.h"
#include "ConstantBuffer.h"
#include "Engine.h"

ConstantBuffer::ConstantBuffer()
{
}

ConstantBuffer::~ConstantBuffer()
{
	if (_cbvBuffer)
	{
		if (_cbvBuffer != nullptr)
			_cbvBuffer->Unmap(0, nullptr);
		//소멸자가 필요없을 경우 작업을 마무리한다.
		_cbvBuffer = nullptr;
	}
}



void ConstantBuffer::Init(uint32 size, uint32 count)
{
	// 상수 버퍼는 256 바이트 배수로 만들어야 한다
	// 0 256 512 768
	_elementSize = (size + 255) & ~255;
	_elementCount = count;

	CreateBuffer();
}

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

	_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).
}

void ConstantBuffer::Clear()
{
	_currentIndex = 0;
}

void ConstantBuffer::PushData(int32 rootParamIndex, void* buffer, uint32 size)
{
	assert(_currentIndex < _elementSize);

	::memcpy(&_mappedBuffer[_currentIndex * _elementSize], buffer, size);

	D3D12_GPU_VIRTUAL_ADDRESS address = GetGpuVirtualAddress(_currentIndex);
	CMD_LIST->SetGraphicsRootConstantBufferView(rootParamIndex, address);
	_currentIndex++;
}

D3D12_GPU_VIRTUAL_ADDRESS ConstantBuffer::GetGpuVirtualAddress(uint32 index)
{
	D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = _cbvBuffer->GetGPUVirtualAddress();
	objCBAddress += index * _elementSize;
	return objCBAddress;
}

'공부 > 게임수학 및 DirectX12' 카테고리의 다른 글

기초 베이스  (0) 2022.03.29
Library  (0) 2022.03.28
Rendering Pipe Line & GPU  (0) 2022.03.28