[DX12] 장치 초기화 (1) (Device 생성, CommandQueue 구현, Fence 생성)

alsry._.112·2024년 3월 20일
0

DX12

목록 보기
2/2
post-thumbnail

Device 생성

#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를 생성한다.

CommandQueue 구현

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에서 할당하여 준다.

Fence 생성

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을 추가한다.

profile
소통해요

0개의 댓글