루키스 게임수학 정주행 2회차 - 장치 초기화

2023. 5. 10. 17:11DirectX12

1. CPU vs GPU

우리가 대부분 사용하는 컴퓨터에는 CPU와 GPU라는 부품이 거의 대부분 기본적으로 장착되어 있을 것이다. 오늘 할 것은 엔진을 만드는 데 있어서 GPU에게 효율적으로 일을 시키는 일인데 그 전에 CPU와 GPU가 어떤 차이가 있는지 간략하게 알아볼 필요가 있다. 우선 차이점을 그림으로 그리면 다음과 같다.

출처 - 인프런 Rookiss (장치 초기화 강의)

위의 이미지를 보면 CPU와 GPU에 달려있는 레지스터, 캐시 등의 갯수와 구조에서 차이를 볼 수 있다. 이런 차이를 보이는 이유는 CPU가 하나의 연산을 아주 빠르게 처리하기 위한 것이라면 GPU는 여러 개의 작업을 병렬적으로 한꺼번에 빠르게 처리하기 위해 설계되었기 때문이다.

 

실제로 우리가 앞으로 공부하게 될 그래픽스의 내용은 게임 월드에 있는 객체들을 모니터 상으로 표현하는 일련의 과정을 보여주는데 이러한 단순하고 병렬적으로 한꺼번에 처리할 수 있는 연산이 GPU가 처리하기 적합해서 GPU에게 이런 일을 던져주는 것이다. 그리고 우리는 DX12를 이용해서 구체적으로 GPU에게 어떻게 일을 시켜야 하는지 소스 코드로 직접 작성하게 될 것이다.

 

참고로 이번부터 꽤나 갑작스럽게 처음보는 함수들이나 몇몇 기능들이 우후죽순으로 튀어나올텐데 거기에 겁 먹을 필요는 전혀 없다. 강의에 따르면 당장 그 함수 하나하나가 어떤 원리로 동작하는지 외울 필요는 없기 때문이다. 이전 포스팅에서 언급한 적이 있는 것으로 아는데 하나를 깊게 파는 행위가 절대 나쁜 것은 아니지만 나무만 보느라 숲을 보지 못하는 상황이 나오지 않도록 주의해야 한다. 기능 하나에만 너무 초점을 맞추지 말고 전체적인 구조를 보라는 뜻이다.

 

 

2. 더블 버퍼링

우리가 사용하는 모니터가 사용자에게 화면을 보여줄 때, 그냥 보여주는 것처럼 보이지만 사실 그렇지 않다. 그럼 어떻게 보여주고 있는 것이냐? 화면을 엄청 빠른 속도로 지웠다가 그렸다가를 반복하고 있다. 우리 눈에 도저히 인식할 수 없을 정도로 지웠다가 그렸다가를 반복해서 그냥 평범하게 모니터의 픽셀에 빛이 계속 들어오고 있는 것처럼 보일 뿐이다. 그래서 옛날 모니터는 미세하게 화면이 깜빡이는 것처럼 보이는 현상이 있었다.

 

이 문제를 해결하기 위한 방법이 바로 더블 버퍼링이다. 그래픽 카드가 우리가 사용하는 모니터 화면에 그림을 그려주는 방법이다. 아래의 그림을 보자.

2개의 버퍼에 있는 이미지를 번갈아서 보여준다.

더블 버퍼링을 직역해보자. 버퍼는 우리가 아는 그 버퍼 즉, 메모리이다. 그래픽 카드가 그려서 우리가 보게 될 이미지 화면을 저장하는 메모리인 것이다. 그리고 더블은 말 그대로 2개를 뜻하며 정리하면 우리가 보게 될 이미지 화면을 저장하는 메모리를 2개를 사용하는 것이다. 화면이 깜빡이는 것처럼 보이는 이유가 하나의 메모리에 그래픽 카드가 그렸다가 지우고 다시 그리는 그 과정이 우리 눈에 아주 조금이나마 인식이 되기 때문이다.

 

그렇기 때문에 다음에 보여줄 화면을 미리 그려서 모니터 이미지 화면을 지운 후에 빠르게 화면에 내보내서 이 현상을 해결하는 것이다. 그동안 그래픽 카드는 다른 메모리에 다른 이미지를 그리고 또 다음 프레임에 미리 그려놓은 이미지를 내보내는 방법을 사용하는 것이 더블 버퍼링이다. 지금부터 우리는 DirectX12를 이용해서 화면에 이미지를 그리고 지우는 과정을 구현하는 코드를 작성하게 될 것이다.

 

 

3. 클래스 생성

본격적으로 화면에 그림을 그려주기 위한 준비 작업을 하기 위해 5개의 클래스를 만들어야 한다. 아래는 내가 만들게 될 클래스와 각 클래스의 역할에 대해 서술한 것이다.

  • Engine: 창을 띄우는 역할을 하며 모니터 화면에 그림을 그리는데 있어서 가장 핵심적인 역할을 하게 될 클래스이다. 아래에 있는 4개의 클래스를 멤버로 1개씩 가지고 있다.
  • GraphicDevice: GPU에게 명령을 던져주는 역할을 하게 될 클래스이다. 강의에서는 이를 인력 사무소에 비유했다. 마치 GPU라는 인력에게 일을 던져주는 역할을 하는 것처럼 말이다.
  • CommandQueue: GPU가 처리할 일의 목록을 큐에 모았다가 필요할 때, 한꺼번에 내보내는 방식을 구현하는 클래스이다. 커맨드 패턴과 매우 유사하며 옛날에는 없었으나 비교적 최근 버전으로 넘어오면서 생긴 기능이라고 한다.
  • SwapChain: 더블 버퍼링을 구현하기 위한 클래스이다. 두 개의 메모리에 번갈아서 화면에 그려줄 것들을 저장하면서 어떤 메모리에 있는 것을 화면에 그릴지 Swap시키는 것이다.
  • DescriptorHeap: GPU가 사용할 각종 리소스를 관리하는 역할을 하는 클래스이다. DescriptorHeap에 Descriptor를 할당해서 GPU가 Descriptor를 사용하게 만드는 구조이다.

 

이제 아래와 같이 코드를 작성하자. 아래의 코드를 이해하는데 있어서 가장 중요한 것은 전체적인 흐름을 이해하는 것이다. 절대로 처음보는 모든 키워드가 어떻게 동작하는지를 반드시 알아야 하는 것은 아니다.

 

CoreMinimal.h

#ifndef __CORE_MINIMAL_H__
#define __CORE_MINIMAL_H__

// boost 라이브러리
#include <vector.hpp>
#include <smart_ptr.hpp>
#include <make_unique.hpp>

#include "d3dx12.h"

#include <d3d12.h>
#include <wrl.h>
#include <d3dcompiler.h>
#include <dxgi.h>
#include <d3d12sdklayers.h>
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <DirectXColors.h>

#pragma comment(lib, "d3d12")
#pragma comment(lib, "dxgi")
#pragma comment(lib, "dxguid")
#pragma comment(lib, "d3dcompiler")

using int8 = __int8;
using int16 = __int16;
using int32 = __int32;
using int64 = __int64;

using uint8 = unsigned __int8;
using uint16 = unsigned __int16;
using uint32 = unsigned __int32;
using uint64 = unsigned __int64;

using Vector2 = DirectX::XMFLOAT2;
using Vector3 = DirectX::XMFLOAT3;
using Vector4 = DirectX::XMFLOAT4;
using Matrix = DirectX::XMMATRIX;
using Viewport = D3D12_VIEWPORT;
using Rect = D3D12_RECT;

enum 
{ 
    SWAP_CHAIN_BUFFER_COUNT = 2
};

// 띄워질 창의 정보를 담는 구조체
struct WindowInfo
{
    HWND hwnd;        // 출력될 창의 핸들
    uint32 width;     // 창의 가로 길이
    uint32 height;    // 창의 세로 길이
    bool windowed;    // 전체 모드 or 창 모드
};

#endif

 

Engine.h

#ifndef __ENGINE_H__
#define __ENGINE_H__

#include "CoreMinimal.h"

class Engine
{
public:

    // 창의 정보를 받아와서 해당 정보대로 창을 띄운다.
    void Init(const WindowInfo& windowInfo);

    void Render();

    void RenderBegin();

    void RenderEnd();

    void ResizeWindow(uint32 width, uint32 height);

private:

    // 창을 띄우는데 필요한 멤버들이다.
    WindowInfo windowInfo;
    Viewport viewport = {};
    Rect scissorRect = {};

    // 띄워진 창에 그림을 그리는데 필요한 멤버들이다.
    boost::shared_ptr<class GraphicDevice> graphicDevice;
    boost::shared_ptr<class CommandQueue> commandQueue;
    boost::shared_ptr<class SwapChain> swapChain;
    boost::shared_ptr<class DescriptorHeap> descriptorHeap;

};

#endif

 

Engine.cpp

#include "CoreMinimal.h"
#include "Engine.h"
#include "GraphicDevice.h"
#include "CommandQueue.h"
#include "SwapChain.h"
#include "DescriptorHeap.h"

void Engine::Init(const WindowInfo& windowInfo)
{
    this->windowInfo = windowInfo;
    
    ResizeWindow(windowInfo.width, windowInfo.height);
    
    // 창 내부에서 그림이 그려질 뷰포트의 크기 설정
    viewport = { 0, 0, static_cast<FLOAT>(windowInfo.width), static_cast<FLOAT>(windowInfo.height), 0.0f, 1.0f };
    scissorRect = CD3DX12_RECT(0, 0, windowInfo.width, windowInfo.height);
    
    graphicDevice = boost::make_shared<GraphicDevice>();
    commandQueue = boost::make_shared<CommandQueue>();
    swapChain = boost::make_shared<SwapChain>();
    descriptorHeap = boost::make_shared<DescriptorHeap>();
    
    graphicDevice->Init();
    commandQueue->Init(graphicDevice->GetDevice(), swapChain, descriptorHeap);
    swapChain->Init(windowInfo, graphicDevice->GetDXGI(), commandQueue->GetCommandQueue());
    descriptorHeap->Init(graphicDevice->GetDevice(), swapChain);
}

void Engine::Render()
{
    RenderBegin();
    
    RenderEnd();
}

void Engine::RenderBegin()
{
    commandQueue->RenderBegin(&viewport, &scissorRect);
}

void Engine::RenderEnd()
{
    commandQueue->RenderEnd();
}

void Engine::ResizeWindow(uint32 width, uint32 height)
{
    windowInfo.width = width;
    windowInfo.height = height;
    
    Rect rect = { 0, 0, width, height };
    
    // 창의 크기를 조절하는 함수
    ::AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false);
    
    // 창의 위치를 지정하는 함수
    ::SetWindowPos(windowInfo.hwnd, 0, 100, 100, width, height, 0);
}

 

GraphicDevice.h

#ifndef __GRAPHIC_DEVICE_H__
#define __GRAPHIC_DEVICE_H__

#include "CoreMinimal.h"

class GraphicDevice
{
public:

    void Init();
    
    inline Microsoft::WRL::ComPtr<IDXGIFactory> GetDXGI() const;
    
    inline Microsoft::WRL::ComPtr<ID3D12Device> GetDevice() const;

private:
    // COM(Component Object Model)
    // - DX의 프로그래밍 언어 독립성과 하위 호환성을 가능하게 하는 기술
    // - COM 객체(COM의 인터페이스)를 사용. 세부사항은 우리한테 숨겨짐
    // - ComPtr 일종의 스마트 포인터
    Microsoft::WRL::ComPtr<ID3D12Debug> debugController;
    
    Microsoft::WRL::ComPtr<IDXGIFactory> dxgi;    // 화면 관련 기능들
    
    Microsoft::WRL::ComPtr<ID3D12Device> device;  // 각종 객체 생성

};

#endif

 

GraphicDevice.cpp

#include "GraphicDevice.h"

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

    // DXGI (DirectX Graphic Instructure)
    // Direct3D와 함께 쓰이는 API
    // - 전체 화면 모드 전환
    // - 지원되는 디스플레이 모드 열거 등
    // - riid: 디바이스의 COM ID
    // - ppDevice: 생성된 장치가 매개변수에 설정
    ::CreateDXGIFactory(IID_PPV_ARGS(&dxgi));
    
    // - 디스플레이 어댑터(그래픽 카드)를 나타내는 객체
    // - pAdapter: nullptr을 지정하면 시스템 기본 디스플레이 어댑터
    // - MinimumFeatureLevel: 응용 프로그램이 요구하는 최소 기능 수준
    // - riid: 디바이스의 COM ID
    // - ppDevice: 생성된 장치가 매개변수에 설정
    ::D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device));
}

inline Microsoft::WRL::ComPtr<IDXGIFactory> GraphicDevice::GetDXGI() const
{
    return dxgi;
}

inline Microsoft::WRL::ComPtr<ID3D12Device> GraphicDevice::GetDevice() const
{
    return device;
}

 

CommandQueue.h

#ifndef __COMMAND_QUEUE_H__
#define __COMMAND_QUEUE_H__

#include "CoreMinimal.h"

class CommandQueue
{
public:

    ~CommandQueue();

public:

    void Init(const Microsoft::WRL::ComPtr<class ID3D12Device>& device,
    	      const boost::shared_ptr<class SwapChain>& swapChain,
    	      const boost::shared_ptr<class DescriptorHeap>& descriptorHeap);
    
    void WaitSync();
    
    void RenderBegin(const D3D12_VIEWPORT* viewport, const D3D12_RECT* rect);
    
    void RenderEnd();
    
    inline Microsoft::WRL::ComPtr<ID3D12CommandQueue> GetCommandQueue() const;

private:

    // CommandQueue: DX12에 등장
    // 그래픽 카드에 작업을 요청할 때, 하나씩 요청하면 비효율적
    // 작업 목록에 해야할 일을 기록했다가 한꺼번에 요청하는 것
    Microsoft::WRL::ComPtr<ID3D12CommandQueue> commandQueue;
    Microsoft::WRL::ComPtr<ID3D12CommandAllocator> commandAllocator;
    Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> commandList;
    
    // Fence: GPU로 어떤 일을 요청할 때, 다른 작업이 먼저 
    // 끝나서 결과값이 나오기를 기다려야 하는 경우가 있다면
    // 그걸 기다리는 용도로 사용하는 것. 마치 울타리처럼
    // 작업과 작업 사이를 구분하는 역할을 한다.
    // CPU / GPU 동기화를 위한 간단한 도구
    Microsoft::WRL::ComPtr<ID3D12Fence> fence;
    uint32 fenceValue;
    HANDLE fenceEvent;
        
    boost::shared_ptr<class SwapChain> swapChain;
    boost::shared_ptr<class DescriptorHeap> descriptorHeap;

};

#endif

 

CommandQueue.cpp

#include "CommandQueue.h"
#include "SwapChain.h"
#include "DescriptorHeap.h"

CommandQueue::~CommandQueue()
{
    ::CloseHandle(fenceEvent);
}

void CommandQueue::Init(const Microsoft::WRL::ComPtr<ID3D12Device>& device, 
			const boost::shared_ptr<SwapChain>& swapChain, 
			const boost::shared_ptr<DescriptorHeap>& descriptorHeap)
{
    this->swapChain = swapChain;
    this->descriptorHeap = descriptorHeap;
    
    D3D12_COMMAND_QUEUE_DESC queueDescriptor = {};
    queueDescriptor.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    queueDescriptor.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    
    device->CreateCommandQueue(&queueDescriptor, IID_PPV_ARGS(&commandQueue));
    
    // - D3D12_COMMAND_LIST_TYPE_DIRECT: GPU가 직접 실행하는 명령 목록
    device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator));
    
    // GPU가 하나인 시스템이면 0을 입력
    // DIRECT or BUNDLE
    // Allocator
    // 초기 상태 (그리기 명령은 nullptr 지정)
    device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList));
    
    // CommandList는 Close / Open 상태가 있는데
    // Open 상태에서 Command를 넣다가 Close한 다음 제출하는 개념
    commandList->Close();
    
    // CreateFence
    // - CPU와 GPU의 동기화 수단으로 쓰인다.
    device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
    fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
}

// 주의: 이 부분은 효율적인 코드가 아니며 
// 초기 코드여서 이렇게 짰지만 추후에 수정될 예정이다. 
void CommandQueue::WaitSync()
{
    // GPU에게 명령들을 한 번 내보낼 때, 그 상태에 대해서 번호를 부여할 것이다.
    // 한꺼번에 명령을 보낸 그 상태에 번호를 부여하기 위해 fenceValue를 증가시키는 것이다.
    ++fenceValue;
    
    // 이제 증가된 fenceValue를 커맨드 큐에 전달해서 번호를 부여한다.
    commandQueue->Signal(fence.Get(), fenceValue);
    
    // 이 조건이 참이면 아직 GPU가 fenceValue만큼의 작업을 처리하지 못한 상태인 것이다.
    if (fence->GetCompletedValue() < fenceValue)
    {
        // 만약 fenceValue에 해당하는 명령이 수행되었으면
        // 지정된 Event를 수행하라는 코드이다.
        fence->SetEventOnCompletion(fenceValue, fenceEvent);
        
        // 여기까지 코드가 호출된 다음 CPU가 살짝 기다린다.
        // GPU가 fenceValue까지 작업을 마쳐서 fenceEvent가 호출되면
        // 다시 CPU가 작업을 시작할 수 있게 되는 것이다.
        ::WaitForSingleObject(fenceEvent, INFINITE);
    }
}

void CommandQueue::RenderBegin(const D3D12_VIEWPORT* viewport, const D3D12_RECT* rect)
{
    // 그리기 전 초기화 작업
    commandAllocator->Reset();
    commandList->Reset(commandAllocator.Get(), nullptr);
    
    // 기존에 백버퍼에서 화면에 출력하던 것을 
    // GPU가 작업하는 공간으로 바꾸는 것을 요청하는 부분
    D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
        swapChain->GetCurrentBackBufferResource().Get(),
        D3D12_RESOURCE_STATE_PRESENT,
        D3D12_RESOURCE_STATE_RENDER_TARGET);
    
    // 요청한 것을 작업 목록에 세팅
    commandList->ResourceBarrier(1, &barrier);
    
    // 커맨드 리스트에 viewport, scissorsRect 세팅
    commandList->RSSetViewports(1, viewport);
    commandList->RSSetScissorRects(1, rect);
    
    // 버퍼에 어떤 그림을 그려야 하는지 세팅
    D3D12_CPU_DESCRIPTOR_HANDLE backBufferView = descriptorHeap->GetBackBufferView();
    commandList->ClearRenderTargetView(backBufferView, DirectX::Colors::LightSteelBlue, 0, nullptr);
    commandList->OMSetRenderTargets(1, &backBufferView, FALSE, nullptr);
}

void CommandQueue::RenderEnd()
{
    // 75번 코드에서 했던 것을 거꾸로 바꿈
    D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
        swapChain->GetCurrentBackBufferResource().Get(),
        D3D12_RESOURCE_STATE_RENDER_TARGET,
        D3D12_RESOURCE_STATE_PRESENT);
    
    // 요청한 것을 작업 목록에 세팅하고 작업 목록을 닫음
    commandList->ResourceBarrier(1, &barrier);
    commandList->Close();
    
    // GPU의 작업 목록 실행
    ID3D12CommandList* commandListArray[] = { commandList.Get() };
    commandQueue->ExecuteCommandLists(_countof(commandListArray), commandListArray);
    
    swapChain->Present();
    
    // 앞서 GPU가 했던 작업들이 끝날 때까지 CPU가 대기함
    WaitSync();
    
    // 프론트버퍼, 백버퍼 인덱스 Swap
    swapChain->SwapIndex();
}

inline Microsoft::WRL::ComPtr<ID3D12CommandQueue> CommandQueue::GetCommandQueue() const
{
    return commandQueue;
}

 

SwapChain.h

#ifndef __SWAP_CHAIN_H__
#define __SWAP_CHAIN_H__

#include "CoreMinimal.h"

class SwapChain
{
public:

    void Init(const WindowInfo& windowInfo, 
              const Microsoft::WRL::ComPtr<IDXGIFactory>& dxgi, 
              const Microsoft::WRL::ComPtr<ID3D12CommandQueue>& commandQueue);
    
    void Present();
    
    void SwapIndex();
    
    inline Microsoft::WRL::ComPtr<IDXGISwapChain> GetSwapChain() const;
    
    inline Microsoft::WRL::ComPtr<ID3D12Resource> GetRenderTarget(uint32 index) const;
    
    inline uint32 GetCurrentBackBufferIndex() const;
    
    inline Microsoft::WRL::ComPtr<ID3D12Resource> GetCurrentBackBufferResource() const;

private:

    Microsoft::WRL::ComPtr<IDXGISwapChain> swapChain;
    
    // 렌더링할 것들을 저장할 메모리. 현재 2칸을 할당했다.
    Microsoft::WRL::ComPtr<ID3D12Resource> renderTargets[SWAP_CHAIN_BUFFER_COUNT];
    
    uint32 backBufferIndex = 0;

};

#endif

 

SwapChain.cpp

#include "SwapChain.h"

void SwapChain::Init(const WindowInfo& windowInfo, 
	             const Microsoft::WRL::ComPtr<IDXGIFactory>& dxgi, 
	             const Microsoft::WRL::ComPtr<ID3D12CommandQueue>& commandQueue)
{
    // 이전에 만든 정보를 날림
    swapChain.Reset();
    
    // 더블 버퍼링에 대한 초기화
    DXGI_SWAP_CHAIN_DESC swapChainDescriptor;
    
    // 버퍼 해상도의 넓이
    swapChainDescriptor.BufferDesc.Width = static_cast<uint32>(windowInfo.width);
    
    // 버퍼 해상도의 높이
    swapChainDescriptor.BufferDesc.Height = static_cast<uint32>(windowInfo.height);
    
    // 화면 갱신 비율. 초당 60번 갱신.
    swapChainDescriptor.BufferDesc.RefreshRate.Numerator = 60;
    swapChainDescriptor.BufferDesc.RefreshRate.Denominator = 1;
    
    // 버퍼의 디스플레이 형식. RGBA 각각을 8비트씩 사용한다는 뜻.
    swapChainDescriptor.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    
    swapChainDescriptor.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    swapChainDescriptor.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    
    swapChainDescriptor.SampleDesc.Count = 1;    // 멀티 샘플링 OFF. 나중에 사용함.
    swapChainDescriptor.SampleDesc.Quality = 0;
    
    // 후면 버퍼에 사용하겠다는 선언
    swapChainDescriptor.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    
    // 전면, 후면 버퍼를 쓰겠다는 선언. CoreMinimal.h에 2로 정의되어 있다.
    swapChainDescriptor.BufferCount = SWAP_CHAIN_BUFFER_COUNT;
    
    swapChainDescriptor.OutputWindow = windowInfo.hwnd;
    swapChainDescriptor.Windowed = windowInfo.windowed;
    
    // 전면, 후면 버퍼 교체 시 이전 프레임 정보 버림
    swapChainDescriptor.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    
    swapChainDescriptor.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
    
    dxgi->CreateSwapChain(commandQueue.Get(), &swapChainDescriptor, &swapChain);
    
    for (uint32 i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
    {
        swapChain->GetBuffer(i, IID_PPV_ARGS(&renderTargets[i]));
    }
}

void SwapChain::Present()
{
    // 그려줄 버퍼 전환
    swapChain->Present(0, 0);
}

void SwapChain::SwapIndex()
{
    // 2개의 버퍼에서 그려줄 부분과 보여줄 부분을 index를 바꿔서 Swap시킴
    backBufferIndex = (backBufferIndex + 1) % SWAP_CHAIN_BUFFER_COUNT;
}

inline Microsoft::WRL::ComPtr<IDXGISwapChain> SwapChain::GetSwapChain() const
{
    return swapChain;
}

inline Microsoft::WRL::ComPtr<ID3D12Resource> SwapChain::GetRenderTarget(uint32 index) const
{
    return renderTargets[index];
}

inline uint32 SwapChain::GetCurrentBackBufferIndex() const
{
    return backBufferIndex;
}

inline Microsoft::WRL::ComPtr<ID3D12Resource> SwapChain::GetCurrentBackBufferResource() const
{
    return renderTargets[backBufferIndex];
}

 

DescriptorHeap.h

#ifndef __DESCRIPTOR_HEAP_H__
#define __DESCRIPTOR_HEAP_H__

#include "CoreMinimal.h"

class DescriptorHeap
{
public:

    void Init(const Microsoft::WRL::ComPtr<ID3D12Device>& device, 
              const boost::shared_ptr<class SwapChain>& swapChain);
    
    inline D3D12_CPU_DESCRIPTOR_HANDLE GetRenderTargetView(uint32 index) const;
    
    inline D3D12_CPU_DESCRIPTOR_HANDLE GetBackBufferView() const;

private:

    Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> renderTargetViewHeap;
    
    uint32 renderTargetViewHeapSize = 0;
    
    D3D12_CPU_DESCRIPTOR_HANDLE renderTargetViewHandle[SWAP_CHAIN_BUFFER_COUNT];
    
    boost::shared_ptr<class SwapChain> swapChain;

};

#endif

 

DescriptorHeap.cpp

#include "DescriptorHeap.h"
#include "SwapChain.h"

void DescriptorHeap::Init(const Microsoft::WRL::ComPtr<ID3D12Device>& device, 
        		  const boost::shared_ptr<SwapChain>& swapChain)
{
    this->swapChain = swapChain;
    
    // DX12의 Descriptor = DX11의 View
    // [서술자 힙]으로 RTV를 생성
    // 원래 DX11에서 RTV, DSR, CBV, SRV, UAV 등의 리소스를 따로 관리했으나
    // DX12로 넘어오면서 DescriptorHeap으로 한꺼번에 관리하게 바뀌었다.
    renderTargetViewHeapSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
    
    D3D12_DESCRIPTOR_HEAP_DESC renderTargetViewDescriptor;
    renderTargetViewDescriptor.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
    renderTargetViewDescriptor.NumDescriptors = SWAP_CHAIN_BUFFER_COUNT;
    renderTargetViewDescriptor.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    renderTargetViewDescriptor.NodeMask = 0;
    
    // Descriptor 저장을 위한 DescriptorHeap 생성 및 Handle에 등록
    device->CreateDescriptorHeap(&renderTargetViewDescriptor, IID_PPV_ARGS(&renderTargetViewHeap));
    
    D3D12_CPU_DESCRIPTOR_HANDLE renderTargetViewHeapBegin = renderTargetViewHeap->GetCPUDescriptorHandleForHeapStart();
    
    for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
    {
        renderTargetViewHandle[i] = CD3DX12_CPU_DESCRIPTOR_HANDLE(renderTargetViewHeapBegin, i * renderTargetViewHeapSize);
        device->CreateRenderTargetView(swapChain->GetRenderTarget(i).Get(), nullptr, renderTargetViewHandle[i]);
    }
}

inline D3D12_CPU_DESCRIPTOR_HANDLE DescriptorHeap::GetRenderTargetView(uint32 index) const
{
    return renderTargetViewHandle[index];
}

inline D3D12_CPU_DESCRIPTOR_HANDLE DescriptorHeap::GetBackBufferView() const
{
    return GetRenderTargetView(swapChain->GetCurrentBackBufferIndex());
}