장치 초기화

ㅋㅋ·2022년 7월 14일
0

DirectX12강의

목록 보기
3/39

Engine

RECT rect = { 0, 0, width, height };
::AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false);
::SetWindowPos(_window.hwnd, 0, 100, 100, width, height, 0);

AdjustWindowRect => 윈도우창의 크기 조절
SetWindowPos => 윈도우창의 위치 조절 (100, 100에 위치하도록)
:: => 글로벌 스페이스 함수를 명시적으로 표현

D3D12_VIEWPORT _viewport{};
D3D12_RECT _scissorRect{};

_viewport = { 0, 0, static_cast<FLOAT>(width), static_cast<FLOAT>(height), 0.0f, 1.0f };
_scissorRect = CD3DX12_RECT(0, 0, width, height);

그려질 화면 크기를 설정


shared_ptr<class Device> _device;
shared_ptr<class CommandQueue> _commandQueue;
shared_ptr<class SwapChain> _swapChain;
shared_ptr<class DescriptorHeap> _descriptorHeap;

엔진 사총사


Device

  • 디바이스 삼총사
    debug, DXGI, device

  • COM (component obejct model)
    그래픽카드를 사용할 수 있도록 도와주는 인터페이스
    ComPtr COM 객체 포인터

ComPtr<ID3D12Debug> _debugController;

::D3D12GetDebugInterface(IID_PPV_ARGS(&_debugController));
_debugController->EnableDebugLayer();

출력창에 상세한 디버깅 메시지를 출력하도록 함

디버그 장치 생성 후 활성화

ComPtr<IDXGIFactory> _dxgi;

::CreateDXGIFactory(IID_PPV_ARGS(&_dxgi));

DXGI(DirectX Graphics Infrastructure)

Direct3D와 함께 쓰이는 API
전체 화면 모드 전환, 지원되는 디스플레이 모드 열거 등

모니터 표시와 관련된 API 사용

ComPtr<ID3D12Device> _device;

::D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device));

실제 그래픽카드를 나타내는 객체

그래픽을 출력할 어댑터(모니터)와 응용 프로그램이 요구하는 Direct 버전을 넣어 생성

어댑터가 지정되지 않고 null일 경우 기본 모니터에 출력


Command Queue

DX12부터 등장함

그래픽카드에 계산을 요청할 때 일정 버퍼를 채우고 요청을 보냄

ComPtr<ID3D12CommandQueue>			_cmdQueue;
ComPtr<ID3D12CommandAllocator>		_cmdAlloc;
ComPtr<ID3D12GraphicsCommandList>	_cmdList;

큐와 관련된 데이터들

ComPtr<ID3D12Fence>					_fence;
uint32								_fenceValue = 0;
HANDLE								_fenceEvent = INVALID_HANDLE_VALUE;

GPU와 동기화를 위하여 요청한 일의 끝남을 알기위한 데이터들

void CommandQueue::Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain, shared_ptr<DescriptorHeap> descHeap)
{
	...

	device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&_cmdQueue));

	device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&_cmdAlloc));

	device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&_cmdList));

	_cmdList->Close();

	device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));
	_fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
}

큐와 펜스 자체는 디바이스에서 만들게 되고 이를 사용함

_fenceEvent는 사용하지 않게 된다면 CloseHandle 함수를 통하여 릴리즈 해주어야 한다.

void CommandQueue::WaitSync()
{
	...

	_cmdQueue->Signal(_fence.Get(), _fenceValue);

	if (_fence->GetCompletedValue() < _fenceValue)
	{
		_fence->SetEventOnCompletion(_fenceValue, _fenceEvent);

		::WaitForSingleObject(_fenceEvent, INFINITE);
	}
}

커맨드큐에 요청 번호를 넣어 신호를 보내고 이 이벤트가 끝나기까지 기다리게 됨

(초보 단계라 비효율적임)


Swap Chain

간단히 말하여 display buffering

화면에는 하나의 캔버스를 띄워두고,

다른 캔버스 하나는 GPU가 작업하도록 함

작업이 끝나면 현재 캔버스와 작업이 끝난 캔버스를 swap

버퍼는 일반적으로 2개를 사용하나 더 많이 사용할 수 있음

void SwapChain::Init(const WindowInfo& info, ComPtr<IDXGIFactory> dxgi, ComPtr<ID3D12CommandQueue> cmdQueue)
{
	_swapChain.Reset();
    
	DXGI_SWAP_CHAIN_DESC sd;
	sd.BufferDesc.Width = static_cast<uint32>(info.width); 
	sd.BufferDesc.Height = static_cast<uint32>(info.height);
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1; // 60 / 1 => 60 fps
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	sd.SampleDesc.Count = 1; 
	sd.SampleDesc.Quality = 0; // 멀티 샘플링(안티) OFF
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 후면 버퍼를 랜더링 타겟으로 사용
	sd.BufferCount = SWAP_CHAIN_BUFFER_COUNT; // 전면+후면 버퍼
	sd.OutputWindow = info.hwnd;
	sd.Windowed = info.windowed; // 창모드 여부
	sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // 전면 후면 버퍼 교체 시 이전 프레임 정보 버림
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

	dxgi->CreateSwapChain(cmdQueue.Get(), &sd, &_swapChain);

	for (int32 i = 0; i < SWAP_CHAIN_BUFFER_COUNT; i++)
		_swapChain->GetBuffer(i, IID_PPV_ARGS(&_renderTargets[i]));
}

DXGI_SWAP_CHAIN_DESC 자료를 만들어 dxgi에게 SwapChain을 만들어 달라고 요청

void SwapChain::Present()
{
	_swapChain->Present(0, 0);
}

현재 프레임을 Present 함


Descriptor Heap

DX11까지는 view였으며 DX12부터 이름이 바뀜

11까지 다양한 view가 있었는데 이를 통합하여 하나로 만듬

GPU 요청을 할 때 해당 리소스가 어떤 용도인지 설명하는 데이터 클래스

void DescriptorHeap::Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain)
{
	_swapChain = swapChain;

	_rtvHeapSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

	D3D12_DESCRIPTOR_HEAP_DESC rtvDesc;
	rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	rtvDesc.NumDescriptors = SWAP_CHAIN_BUFFER_COUNT;
	rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	rtvDesc.NodeMask = 0;

	device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&_rtvHeap));

	D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapBegin = _rtvHeap->GetCPUDescriptorHandleForHeapStart();

	for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; i++)
	{
		_rtvHandle[i] = CD3DX12_CPU_DESCRIPTOR_HANDLE(rtvHeapBegin, i * _rtvHeapSize);
		device->CreateRenderTargetView(swapChain->GetRenderTarget(i).Get(), nullptr, _rtvHandle[i]);
	}
}

RTV => Render Target View

랜더 타겟 크기만큼 Heap 생성

swap chain으로부터 랜더 타겟 정보를 가져와 정보들로 descriptor를 만든다.


Render

void Engine::Render()
{
	RenderBegin();

	//todo something
	...
    
	RenderEnd();
}

RednerBegin: 커맨드 큐에 요청할 일들을 push
RenderEnd: 요청할 일들이 GPU로 넘어가 작업 시작

void CommandQueue::RenderBegin(const D3D12_VIEWPORT* vp, const D3D12_RECT* rect)
{
	_cmdAlloc->Reset();
	_cmdList->Reset(_cmdAlloc.Get(), nullptr);

	D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
		_swapChain->GetCurrentBackBufferResource().Get(),
		D3D12_RESOURCE_STATE_PRESENT,
		D3D12_RESOURCE_STATE_RENDER_TARGET);

	_cmdList->ResourceBarrier(1, &barrier);

	_cmdList->RSSetViewports(1, vp);
	_cmdList->RSSetScissorRects(1, rect);

	D3D12_CPU_DESCRIPTOR_HANDLE backBufferView = _descHeap->GetBackBufferView();
	_cmdList->ClearRenderTargetView(backBufferView, Colors::LightSteelBlue, 0, nullptr);
	_cmdList->OMSetRenderTargets(1, &backBufferView, FALSE, nullptr);
}

Transition 함수를 통해 현재 화면과 render target swap을 요청하기 위해 barrier 생성

OMSetRenderTargets 함수를 통해 작업할 버퍼를 알려줌

void CommandQueue::RenderEnd()
{
	D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
		_swapChain->GetCurrentBackBufferResource().Get(),
		D3D12_RESOURCE_STATE_RENDER_TARGET,
		D3D12_RESOURCE_STATE_PRESENT);

	_cmdList->ResourceBarrier(1, &barrier);
	_cmdList->Close();

	ID3D12CommandList* cmdListArr[] = { _cmdList.Get() };
	_cmdQueue->ExecuteCommandLists(_countof(cmdListArr), cmdListArr);

	_swapChain->Present();

	WaitSync();

	_swapChain->SwapIndex();
}

Transition 함수를 통해 render target과 현재 화면 swap을 요청하기 위해 barrier 생성

ExecuteCommandLists 함수를 실행하여 그래픽카드로 작업들을 요청

_swapChain->Present 함수를 통해 모니터에 실제로 그려지게 됨

한마디로 RenderEnd 함수 전까지 _cmdList에 요청할 것들을 전부 담아두고,

RednerEnd 함수가 불릴 때 비로소 그래픽카드로 전달하고 현재 화면을 표시함.

요청한 일이 끝나면 버퍼를 바꾸고 다시 반복한다.

0개의 댓글