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 함수가 불릴 때 비로소 그래픽카드로 전달하고 현재 화면을 표시함.
요청한 일이 끝나면 버퍼를 바꾸고 다시 반복한다.