DirectX는 "전체적인 흐름을 먼저 보고 숲을 이해하는 것"이 중요합니다. 처음부터 모든 코드를 이해하려 하면 금방 포기하게 됩니다. 그렇기 때문에 처음에는 각 객체가 어떤 역할을 하고, 어떤 흐름으로 상호작용하는지만 파악해도 충분합니다.
장치 초기화는 GPU에게 외주를 맡기기 위한 준비 작업입니다. 이 과정을 통해 CPU는 반복적이고 계산량 많은 그래픽 처리를 GPU에게 위임하고, 자신은 게임 로직과 같은 고차원적인 연산에 집중할 수 있습니다.
DirectX12에서 장치 초기화는 다음 네 가지 클래스를 중심으로 구성됩니다:
DirectX는 COM 기반으로 동작합니다.
COM 객체들은 IUnknown을 상속받으며 다음과 같은 특성을 가집니다:
AddRef(): 참조 카운트 증가 Release(): 참조 카운트 감소 (0이 되면 메모리 해제)이로 인해 언어 독립성, 하위 호환성이 보장됩니다.
ComPtr<IDXGIFactory4> mdxgiFactory;
CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory));
DXGI는 Direct3D의 기반 라이브러리로 전체 화면 전환, 디스플레이 모드 열거 등 저수준 작업을 담당합니다.
ComPtr<ID3D12Device> md3dDevice;
D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&md3dDevice));
nullptr: 기본 디스플레이 어댑터 사용D3D_FEATURE_LEVEL_11_0: 최소 요구 기능 수준하드웨어가 DX12를 지원하지 않을 경우, WARP 어댑터를 사용해 소프트웨어 기반으로 생성합니다.
GPU에게 명령을 직접 전달하지 않고, Command List에 명령을 모아 Command Queue를 통해 한꺼번에 전달합니다.
ComPtr<ID3D12CommandQueue> mCommandQueue;
D3D12_COMMAND_QUEUE_DESC desc = {};
desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
md3dDevice->CreateCommandQueue(&desc, IID_PPV_ARGS(&mCommandQueue));
DXGI_SWAP_CHAIN_DESC scDesc = {};
scDesc.BufferCount = 2;
scDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
mdxgiFactory->CreateSwapChain(mCommandQueue.Get(), &scDesc, &mSwapChain);
SwapChain은 단순 리소스일 뿐이며, DescriptorHeap을 통해 뷰(View)를 생성해야 GPU가 인식합니다.
DescriptorHeap은 GPU에 리소스를 전달하기 위한 View들을 담는 테이블입니다.
각 View는 리소스를 어떻게 사용할 것인지 설명하는 메타데이터입니다.
예) RenderTargetView, DepthStencilView
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {};
rtvDesc.NumDescriptors = 2;
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
md3dDevice->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&mRtvHeap));
이후 백버퍼 각각에 대해 RenderTargetView를 생성합니다.
CPU와 GPU는 비동기적으로 작업하므로 동기화(Synchronization)가 필요합니다.
Fence는 GPU가 특정 작업까지 완료했는지 추적하는 데 사용됩니다.
md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence));
mScreenViewport = { 0, 0, (float)mClientWidth, (float)mClientHeight, 0.0f, 1.0f };
mScissorRect = { 0, 0, mClientWidth, mClientHeight };