#pragma once
// Device의 헤더
// 하드웨어와 직접 소통하기 위해 필요한 클래스
// 그래픽 카드
class Device
{
public:
void Init();
ComPtr<IDXGIFactory> GetDXGI() { return _dxgi; }
ComPtr<ID3D12Device> GetDevice() { return _device; }
private:
// Com(컴포넌트오브젝트모델)
// Com Ptr: Dx의 헬퍼클래스
// Com Ptr: 일종의 DX API를 손쉽게 쓸 수 있는 스마트 포인터로 정의한 클래스
ComPtr<ID3D12Debug> _debugController;
ComPtr<IDXGIFactory> _dxgi; // 화면 관련 기능들
ComPtr<ID3D12Device> _device; // 각종 객체 생성
};
// Device의 cpp
#include "pch.h"
#include "Device.h"
void Device::Init()
{
#ifdef _DEBUG //디버그 모드라면
::D3D12GetDebugInterface(IID_PPV_ARGS(&_debugController));
_debugController->EnableDebugLayer();
#endif
// DXGI: DirectX Graphics Infrastructure
// Drect 3D와 함께 쓰이는 API
// - 전체 화면 모드 전환
// - 지원되는 디스플레이 모드 열거 등등
::CreateDXGIFactory(IID_PPV_ARGS(&_dxgi));
// 디스플레이 어댑터(그래픽카드)르 나타내는 객체
// nullptr로 지정하면 시스템 기본 디스플레이 어댑터
// D3D_FEATURE_LEVEL_11_0: 응용프로그램이 요구하는 최소 기능 수준
// (옛날 그래픽 카드를 걸러낼 수 있음)
// riid: 디바이스의 COM ID
::D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device));
}
::CreateDXGIFactory(IID_PPV_ARGS(&_dxgi));
CreateDXGIFactory1 함수로 Factory를 생성해준다.
::D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device));
D3D12CreateDevice 함수를 통해 Device를 생성한다.
DirectX는 CPU에서 명령들을 모아 제출하면 GPU가 순차적으로 명령을 처리하는 식으로 동작하게 된다.
명령들을 담고 있는 CPU의 CommandList와 리스트를 제출 할 GPU의 CommandQueue를 생성하는 작업을 해보자.
#pragma once
// 명령어들의 집합소
// GPU에게 일을 시키기 위한 명세서를 기록해 놓은 공간
// GPU에게 외주를 요청할때 하나씩 요청하면 비효율적이니 목록에 쌓아놨다가 한번에 요청하는 역할
class SwapChain;
class DescriptorHeap;
//CommandQueue의 헤더 파일
class CommandQueue
{
public:
~CommandQueue();
void Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain, shared_ptr<DescriptorHeap> descHeap);
void WaitSync();
private:
// CommandQueue는 DX12에서 처음 등장
ComPtr<ID3D12CommandQueue> _cmdQueue; // CmdList에서 받아온 명령을 GPU에 제출
ComPtr<ID3D12CommandAllocator> _cmdAlloc; // 명령리스트에 추가된 명령들을 메모리에 저장하는 역할
ComPtr<ID3D12GraphicsCommandList> _cmdList; // Cmd Queue에 넣을 그래픽 렌더링 명령을 담는 리스트.
};
//CommandQueue의 cpp 파일
#include "pch.h"
#include "CommandQueue.h"
void CommandQueue::Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain, shared_ptr<DescriptorHeap> descHeap)
{
_swapChain = swapChain;
_descHeap = descHeap;
D3D12_COMMAND_QUEUE_DESC queueDesc = {}; // 세부 목록
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; // GPU가 직접 실행할 수 있는 명령 리스트, GPU가 하나인 시스템에서는 0으로 지정
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; // 기본 명령 큐
// CommandQueue 생성
device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&_cmdQueue));
// CommandAllocator 생성
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&_cmdAlloc));
// CommandList 생성
// nullptr = 초기상태로 지정(그리기 명령은 nullptr로 지정)
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&_cmdList));
// 커맨드리스트에서는 Close상태와 Open상태가 존재하는데
// Open상태에서는 커맨드를 넣어주고, Close한 다음에는 제출한다.
_cmdList->Close();
}
Command Queue: cmdList에서 받아온 명령을 GPU에 제출
Command Allocator: 명령리스트에 추가된 명령들을 메모리에 저장하는 역할
Command List: cmdQueue에 넣을 그래픽 렌더링 명령을 담는 리스트.
Command Queue, Command Allocator, Command List를 각각 선언하고 Init에서 할당하여 준다.
CPU와 GPU의 동기화, 속도를 맞추기 위해 필요한 Fence를 Device를 통해 만들어주자.
//CommandQueue의 헤더 파일
// Fence: 울타리
// CPU와 GPU 사이의 동기화를 위한 도구
// 렌더링 화면 문제를 해결하기 위함 (더블 버퍼링)
ComPtr<ID3D12Fence> _fence;
uint32 _fenceValue = 0;
HANDLE _fenceEvent = INVALID_HANDLE_VALUE;
//CommandQueue의 cpp 파일
CommandQueue::~CommandQueue()
{
::CloseHandle(_fenceEvent); //_fenceEvent를 닫는다
}
void CommandQueue::Init(ComPtr<ID3D12Device> device, shared_ptr<SwapChain> swapChain, shared_ptr<DescriptorHeap> descHeap)
{
....
// Fence(울타리) 생성. CPU와 GPU 사이의 동기화수단으로 쓰인다.
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));
// Event생성. 멀티스레드에서 동기화 할 때 주로 사용하는 방법
// 이벤트 핸들변수를 선언해서 신호등처럼 사용할 수 있다.
// 빨간불일땐 멈춰있다가 파란불이 켜질떄까지 기다렸다가 동기화를 시키는 용도로 사용
_fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr); //CreateEvent를 했으면 닫아줘야 함.(헤더의 소멸자)
}
void CommandQueue::WaitSync() //동기화 되는걸 기다리는 함수
{
// GPU에게 외주를 줄 때마다 인덱스를 증가
_fenceValue++;
// 명령 대기열에 새로운 펜스 포인트를 설정하는 명령을 추가
// GPU타임라인(대기명령)에 있으므로 GPU가 일을 마무리할 때까지 새로운 펜스가 설정되지 않는다.
_cmdQueue->Signal(_fence.Get(), _fenceValue); //신호를 줌
// GPU가 이 펜스 지점까지 명령을 완료할떄가지 기다린다.
if (_fence->GetCompletedValue() < _fenceValue)
{
// GPU가 현재 펜스에 도달하면 이벤트를 발생(파란불 켜짐)
_fence->SetEventOnCompletion(_fenceValue, _fenceEvent);
// GPU가 현재 펜스에 도달할때까지 기다려라. 곧 이벤트가 시작할테니까
// 즉 CPU가 기다리고 있는중
::WaitForSingleObject(_fenceEvent, INFINITE);
}
}
CreateFence함수를 통해 Fence를 생성한다.
그 후 Handle 타입인 _fenceEvent에 CreateEvent를 통해 이벤트를 할당한다.
CreateEvent를 통해 생긴 이벤트를 닫아주지 않으면 누수가 생기므로 CommandQueue의 소멸자에 CloseHandle을 추가한다.