🙇‍♀️장치 초기화


🪐장치 초기화

DirectX는 러프하게 큰 숲을 바라본다라는 느낌으로 공부!

SKD, MSDN 문서를 이용해서 함수들을 알아보자.

H가 붙어 있다는 건 정수라는 뜻, HAND는 포인터와 역할이 비슷하다.

DirectX는 GPU와 대화하기 위한 장치

그래픽스는 연산량이 많다 -> GPU에게 일감을 떠넘기기 (CPU는 고급 인력이니까)

GPU에게 일을 시킬때는 Device라는 애를 이용한다.

화면 관련 기능들 : IDGIFactory

ComPtr : 그래픽 카드와 통신하는 객체


🪐Device

void Device::Init()
{
	// D3D12 디버그층 활성화
	// - VC++ 출력창에 상세한 디버깅 메시지 출력
	// - riid : 디바이스의 COM ID
	// - ppDevice : 생성된 장치가 매개변수에 설정
#ifdef _DEBUG
	::D3D12GetDebugInterface(IID_PPV_ARGS(&_debugController));
	_debugController->EnableDebugLayer();
#endif

	// DXGI(DirectX Graphics Infrastructure)
	// Direct3D와 함께 쓰이는 API
	// - 전체 화면 모드 전환
	// - 지원되는 디스플레이 모드 열거 등
	// CreateDXGIFactory
	// - riid : 디바이스의 COM ID
	// - ppDevice : 생성된 장치가 매개변수에 설정
	::CreateDXGIFactory(IID_PPV_ARGS(&_dxgi));

	// CreateDevice
	// - 디스플레이 어댑터(그래픽 카드)를 나타내는 객체
	// - pAdapter : nullptr 지정하면 시스템 기본 디스플레이 어댑터
	// - MinimumFeatureLevel : 응용 프로그램이 요구하는 최소 기능 수준 (구닥다리 걸러낸다)
	// - riid : 디바이스의 COM ID
	// - ppDevice : 생성된 장치가 매개변수에 설정
	::D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device));
}

특히 D3D12CreateDevice가 중요함.

GPU에도 RAM처럼 기억하기 위한 장치가 있는데 VRAM이라고 하고, 이를 사용하기 위해서 Device를 이용함.


🪐SwapChain

double buffering

교환 사슬

  • [외주 과정]
    • 현재 게임 세상에 있는 상황을 묘사
    • 어떤 공식으로 어떻게 계산할지 던져줌
    • GPU가 열심히 계산 (외주)
    • 결과물 받아서 화면에 그려준다
  • [외주 결과물]을 어디에 받지?
    • 어떤 종이(Buffer)에 그려서 건내달라고 부탁해보자
    • 특수 종이를 만들어서 -> 처음에 건내주고 -> 결과물을 해당 종이에 받는다 OK
    • 우리 화면에 특수 종이(외주 결과물) 출력해준다
  • [?]
    • 그런데 화면에 현재 결과물 출력하는 와중에, 다음 화면도 외주를 맡겨야 함
    • 현재 화면 결과물은 이미 화면 출력에 사용중
    • 특수 종이를 2개 만들어서, 하나는 현재 화면을 그려주고, 하나는 외주 맡기고…
    • Double Buffering!
  • [0] [1]
    • 현재 화면 [1] <-> GPU 작업중 [1] BackBuffer

SwapChain이 만든 결과물은 그저 리소스이기에 어떤 용도로 사용하는지 모름 -> DescriptorHeap 사용

DescriptorHeap은 배열같은 느낌 크기는 할당됐지만 객체는 만들어지지 않음.

void SwapChain::Init(const WindowInfo& info, ComPtr<ID3D12Device> device, ComPtr<IDXGIFactory> dxgi, ComPtr<ID3D12CommandQueue> cmdQueue)
{
	CreateSwapChain(info, dxgi, cmdQueue);
	CreateRTV(device);
}

void SwapChain::Present()
{
	// Present the frame.
	_swapChain->Present(0, 0);
}

void SwapChain::SwapIndex()
{
	_backBufferIndex = (_backBufferIndex + 1) % SWAP_CHAIN_BUFFER_COUNT;
}

void SwapChain::CreateSwapChain(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; // 화면 갱신 비율
	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; // 멀티 샘플링 OFF
	sd.SampleDesc.Quality = 0;
	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(&_rtvBuffer[i]));
}

void SwapChain::CreateRTV(ComPtr<ID3D12Device> device)
{
	// Descriptor (DX12) = View (~DX11)
	// [서술자 힙]으로 RTV 생성
	// DX11의 RTV(RenderTargetView), DSV(DepthStencilView), 
	// CBV(ConstantBufferView), SRV(ShaderResourceView), UAV(UnorderedAccessView)를 전부!

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

	// 같은 종류의 데이터끼리 배열로 관리
	// RTV 목록 : [ ] [ ]
	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(_rtvBuffer[i].Get(), nullptr, _rtvHandle[i]);
	}
}


🪐DescriptorHeap

SwapChain에서 만든 것을 어떤 용도로 사용할 것인지 기안서를 만드는 역할.


🪐CommandQueue

GPU와 CPU가 서로 왔다갔다하면서 일을 함. 둘 다 동시다발적으로 일을 하는데 일을 시킨다고 GPU가 일을 바로 하는 것이 아니고 일감 목록을 준다.
=> CommandQueue

  • CommandQueue : DX12에 등장
    • 외주를 요청할 때, 하나씩 요청하면 비효율적
    • [외주 목록]에 일감을 차곡차곡 기록했다가 한 방에 요청하는 것


🪐정리

Device : 대장(여기서부터 시작 됨)

SwapChain : 더블 버퍼링을 이용하여 그림 그리기

DescriptorHeap : SwapChain의 결과물을 무슨 용도로 사용할지 서술

CommandQueue : 일감 목록을 만들어서 한꺼번에 GPU에게 일 넘김


🪐추가

장치 초기화
DirectX는 GPU와 통신을 하면서 작동하는 것이기 때문에 초기화부터 어려움

MSDN을 활용해서 모르는 함수들을 알아보자

H가 붙은 애들은 정수라고 생각하면 됨, 일종의 포인터 같은 개념, 창에다가 번호가 붙어있기 때문

순서는 엔진을 고치고, 게임, 클라 순으로 고침

GEngine : EnginePch.h에 extern unique_ptr GEngine;, EnginePch.cpp에 unique_ptr GEngine = make_unique();으로 전역 객체로 만든 것

Device, CommandQueue, SwapChain, DescriptorHeap

Engine의 Init에서 각각 생성 후 각 Init함수 실행,
Engine::Render()에서 그려주기,
Engine::RenderBegin(), RenderEnd()는 CommandQueue에서 관리

Device
GPU와 통신하기 위한 라이브러리 : DirectX
device : 인력사무소
cpu : 고급인력, 몇 개 없음
gpu : cpu의 노예?, cpu가 외주를 맡기면 일 함, 디따 많음
IDXGIFactory : 화면을 그리는 기능들(dxgi)
ID3D12Device : 각종 객체 생성(device)
COM(Component Object Model) : DirectX의 프로그래밍 언어 독립성과 하위 호환성을 가능하게 하는 기술
WRL(Windows Runtime Library)

D3D12GetDebugInterface(IID_PPV_ARGS(&_debugController)); _debugController->EnableDebugLayer();
=> D3D12 디버그층 활성화
VC++ 출력창에 상세한 디버깅 메시지 출력
riid : 디바이스의 COM ID
ppDevice : 생성된 장치가 매개변수에 설정

CreateDXGIFactory(IID_PPV_ARGS(&_dxgi));
=> DXGI(DirectX Graphics Infrastructure)
Direct3D 함께 쓰이는 API
전체 화면 모드 전환
지원되는 디스플레이 모드 열거 등
CreateDXGIFactory
riid : 디바이스의 COM ID
ppDevice : 생성된 장치가 매개변수에 설정

D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL__11_0, IID_PPV_ARGS(&_device));
=> CreateDevice
디스플레이 어댑터(그래픽 카드)를 나타내는 객체
pAdapter : nullptr 지정하면 시스템 기본 디스플레이 어댑터
MinimumFeatureLevel : 응용 프로그램이 요구하는 최소 기능 수준 (구닥다리 걸러냄)
riid : 디바이스의 COM ID
ppDevice : 생성된 객체가 매개변수에 설정

CPU (뇌), RAM (기억), GPU(연산/기억(VRAM))
device가 동작해서 gpu의 vram을 이용한다

SwapChain
doubleBuffering과 연관돼서 질문이 들어옴
교환 사슬
[외주 과정]
현재 게임 세상에 있는 상황을 묘사
어떤 공식으로 어떻게 계산할지 던져줌
GPU가 열심히 계산 (외주)
결과물 받아서 화면에 그린다
[외주 결과물]을 어디서 받지?
어떤 종이(Buffer)에 그려서 건내달라고 부탁해보자
특수 종이를 만들어서->처음에 건내주고->결과물을 해당 종이에 받는다
우리 화면에 특수 종이(종이 결과물) 출력해준다
[?]
그런데 화면에 현재 결과물 출려하는 와중에 다음 화면도 외주를 맡겨햐 함
현재 화면 결과물은 이미 화면 출력에 사용중
특수 종이를 2개 만들어서, 하나는 현재 화면을 그려주고, 하나는 외주 맡긴 결과물을 그리고..
Double Buffering
[0] [1]
현재 화면[0] <-> GPU 작업중 [1] BackBuffer

ComPtr : Com객체로 그래픽 카드와 통신하는 객체

DescriptorHeap : 서술자 힙
Descriptor (DX12) = View (~DX11)
[서술자 힙]으로 RTV 생성
DX11의 RTV(RenderTargetView), DSV(DepthStencilView),
CBV(ConstantView), SRV(ShaderResourceView), UAV(UnorderedAccessView) 를 전부~~~~
[기안서]
외주를 맡길 때 이런 저런 정보를 같이 넘겨줘야 하는데,
아무 형태로나 요청하면 못 알아먹는다
각종 리소스를 어떤 용도로 사용하는지 꼼꼼하게 적어서 넘겨준다.

CreateDescriptorHeap()으로 우리가 사용하는 아이를 묘사
그거를 D3D12_DESCRIPTOR_HEAP_DESC _rtvDes라고 서술자 힙의 서술에 저장
rtvDesc.~~로 설정(설명)

handle : 포인터라고 생각하자. 사물을 가르키는 번호니깐

CommandQueue
외주 일감 목록
명령을 하고 나중에 실행됨
CreateCommandQueue(), CreateCommandAllocator(), CreateCommandList()를 호출
할당자의 개념?