DirectX12の描画処理 #3 [Devlog #004]

Table of Contents

DirectX12を使った描画処理


DirectX12の環境構築

初期化

Direct3D (D3D)

Direct3D(D3D)とは、DirectXの中の3DグラフィクスAPIの一部であり、多様なグラフィクスハードウェア上でハードウェアアクセラレーションを活用したグラフィクスの表示や操作を行うことができる

D3D12Deviceは、D3D12(Direct3D 12)のインターフェースの一つで、グラフィクスハードウェアへの低レベルのアクセスを提供し、リソースの作成やコマンドのキューの管理などの主要な機能をもつ

DirectX Graphics Infrastructure (DXGI)

DXGIとは、DirectXのコンポーネントの一つであり、アダプター(グラフィクスカード)、モニター、フレームバッファなどのリソース管理や、スワップチェーンの操作などのタスクを行う
DXGIは、Direct3Dが多様なグラフィクスハードウェア上で一貫して動作するための基盤となる

DXGIFactoryは、DXGIの中でも主要なインターフェースの一つで、アダプター(グラフィクスカード)やモニターの情報を取得したり、スワップチェーンを作成するためのメソッドを提供する

デバイスの初期化

D3D12DeviceとDXGIFactoryは初期化に最低限必要になる

リンクするライブラリファイルd3d12.libdxgi.libをソースコード内の記述でリンクして、d3d12.hdxgi1_6.hをインクルードする

// stdafx.h

// D3D12
#pragma comment(lib,"d3d12.lib")
#pragma comment(lib,"dxgi.lib")

#include <d3d12.h>
#include <dxgi1_6.h>

GraphicsフォルダにGraphicsDevice.hGraphicsDevice.cppを追加

dev-dx12-202308(プロジェクト名)
└─ Source
   ├─ Application
   │  ├─ Application.h
   │  └─ Application.cpp
   ├─ Graphics
   │  ├─ GraphicsDevice.h
   │  └─ GraphicsDevice.cpp
   ├─ System
   │  ├─ Utility
   │  │  ├─ Utility.h
   │  │  └─ Utility.cpp
   │  ├─ Window
   │  │  ├─ Window.h
   │  │  └─ Window.cpp
   │  └─ System.h
   ├─ stdafx.h
   └─ stdafx.cpp

GraphicsDevice.h

GraphicsDeviceクラス

初期化を行うInit()関数を宣言し、GraphicsDeviceクラスオブジェクトを生成するInstance()関数を定義する(シングルトン)

ファクトリー関数CreateFactory()を宣言(DXGIFactoryの機能)

デバイス関数CreateDevice()を宣言(D3D12Deviceの機能)

ID3D12Device8はコマンド アロケーター、コマンド リスト、コマンド キュー、フェンス、リソース、パイプライン状態オブジェクト、ヒープ、ルート署名、サンプラー、および多くのリソース ビューを作成するために使用される
DirectX12のアプリケーションでのグラフィックスと計算タスクの中心的なインターフェース
詳細

IDXGIFactory6は全画面表示の遷移を処理するDXGIオブジェクトを生成するためのメソッドを実装するなど、グラフィックリソースの管理や設定を行うためのインターフェース 詳細

IDXGISwapChain4はダブルバッファリングのプロセス中のフロントバッファとバックバッファの間でのバッファ交換を管理するインターフェース 詳細

//GraphicsDevice.h

class GraphicsDevice {
public:
	bool Init();

	static GraphicsDevice& Instance() {
		static GraphicsDevice instance;
		return instance;
	}
private:
	bool CreateFactory();

	bool CreateDevice();

	enum class GPUTier {
		NVIDIA,
		Amd,
		Intel,
		Arm,
		Qualcomm,
		Kind,
	};

	ComPtr<ID3D12Device8>		m_pDevice = nullptr;
	ComPtr<IDXGIFactory6>		m_pDxgiFactory = nullptr;
	ComPtr<IDXGISwapChain4>		m_pSwapChain = nullptr;

	GraphicsDevice() {}
	~GraphicsDevice() {}
};

GraphicsDevice.cpp

GraphicsDevice::Init()関数

CreateFactory()CreateDevice()を実行

//GraphicsDevice.cpp

#include "GraphicsDevice.h"

bool GraphicsDevice::Init() {
	if (!CreateFactory()) {
		assert(0 && "ファクトリー作成失敗");
		return false;
	}

	if (!CreateDevice()) {
		assert(0 && "D3D12デバイス作成失敗");
		return false;
	}

	return true;
}
GraphicsDevice::CreateFactory()関数

CreateDXGIFactory2()関数は、モニターやグラボのスペックを取得するなど、DirectXアプリケーションでのグラフィックスリソースの管理や操作を行うためのDXGIファクトリインスタンス(IDXGIFactory2)を作成する
▶ 第一引数はDXGIのデバッグレイヤーを有効にするためのフラグ
▶ 第二引数と第三引数はインターフェースへのポインタ(IID_PPV_ARGSマクロを使ってうまくやってる)

//GraphicsDevice.cpp

bool GraphicsDevice::CreateFactory() {
	UINT flagsDXGI = 0;
	flagsDXGI |= DXGI_CREATE_FACTORY_DEBUG;
	auto result = CreateDXGIFactory2(flagsDXGI, IID_PPV_ARGS(m_pDxgiFactory.GetAddressOf()));

	if (result) {
		return false;
	}

	return true;
}
GraphicsDevice::CreateDevice()関数

IDXGIAdapterは、DXGIを通じてグラフィックスアダプタの情報取得や管理を行うためのインターフェース 詳細

DXGI_ADAPTER_DESCはDXGIに関連する構造体で、グラフィックスアダプタ(グラボなど)に関する詳細な情報を提供する
IDXGIAdapterインターフェースのGetDescメソッドを通じて取得される情報を格納するために使用される
詳細

1回目のfor文ではGPUドライバーを検索して、あればその情報を格納する

2回目のfor文では優先度の高いGPUドライバーを使用する

D3D_FEATURE_LEVELは、Direct3Dで使用される列挙型で、グラフィクスハードウェアがサポートするDirect3Dの機能セットを識別する 詳細

D3D12CreateDevice()は、指定されたアダプタと指定された機能レベルでDirect3D 12デバイスを作成する関数
▶ 第一引数はアダプタ(IDXGIAdapter)を指定するポインタ
▶ 第二引数はサポートすべき最小の機能レベル(D3D_FEATURE_LEVEL)を指定、
▶ 第三引数と第四引数は作成するインターフェースへのポインタ(IID_PPV_ARGSマクロを使ってうまくやってる)
詳細

//GraphicsDevice.cpp

bool GraphicsDevice::CreateDevice() {
	ComPtr<IDXGIAdapter> pSelectAdapter = nullptr;
	std::vector<ComPtr<IDXGIAdapter>> pAdapters;
	std::vector<DXGI_ADAPTER_DESC> descs;

	for (UINT index = 0; 1; ++index) {
		pAdapters.push_back(nullptr);
		HRESULT ret = m_pDxgiFactory->EnumAdapters(index, &pAdapters[index]);

		if (ret == DXGI_ERROR_NOT_FOUND) { break; }

		descs.push_back({});
		pAdapters[index]->GetDesc(&descs[index]);
	}

	GPUTier gpuTier = GPUTier::Kind;

	for (int i = 0; i < descs.size(); ++i) {
		if (std::wstring(descs[i].Description).find(L"NVIDIA") != std::wstring::npos) {
			pSelectAdapter = pAdapters[i];
			break;
		} else if (std::wstring(descs[i].Description).find(L"Amd") != std::wstring::npos) {
			if (gpuTier > GPUTier::Amd) {
				pSelectAdapter = pAdapters[i];
				gpuTier = GPUTier::Amd;
			}
		} else if (std::wstring(descs[i].Description).find(L"Intel") != std::wstring::npos) {
			if (gpuTier > GPUTier::Intel) {
				pSelectAdapter = pAdapters[i];
				gpuTier = GPUTier::Intel;
			}
		} else if (std::wstring(descs[i].Description).find(L"Arm") != std::wstring::npos) {
			if (gpuTier > GPUTier::Arm) {
				pSelectAdapter = pAdapters[i];
				gpuTier = GPUTier::Arm;
			}
		} else if (std::wstring(descs[i].Description).find(L"Qualcomm") != std::wstring::npos) {
			if (gpuTier > GPUTier::Qualcomm) {
				pSelectAdapter = pAdapters[i];
				gpuTier = GPUTier::Qualcomm;
			}
		}
	}

	D3D_FEATURE_LEVEL levels[] = {
		D3D_FEATURE_LEVEL_12_1,
		D3D_FEATURE_LEVEL_12_0,
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
	};

	D3D_FEATURE_LEVEL featureLevel;
	for (auto lv : levels) {
		if (D3D12CreateDevice(pSelectAdapter.Get(), lv, IID_PPV_ARGS(&m_pDevice)) == S_OK) {
			featureLevel = lv;
			break;
		}
	}

	return true;
}

※追記※
ビルドは通るのにプリコンパイルヘッダーでの定義がコーディング中にエラー表示される問題の解決方法
プリコンパイルヘッダーがインクルード対象パスに入っていればよいので、プロジェクトプロパティ->C/C++->全般->追加のインクルードディレクトリにプリコンパイルヘッダーがあるディレクトリ$(PrjectDir)Sourceを追加すれば解決する
参考記事1:https://freelifetech.com/memo-pre-compile-header/
参考記事2:https://solid.kmckk.com/SOLID/doc/latest/user_guide/add-include-directory.html

描画コマンドの初期化

GPUが実行する命令のリストコマンドリストを指定した順序でGPUに転送するための仕組みがコマンドキュー

GraphicsDevice.h

GraphicsDeviceクラス

コマンドリスト関数CreateCommandList()を宣言(D3D12Deviceの機能)

ID3D12CommandAllocatorはコマンドリストに必要なメモリ(コマンドの記録領域)を割り当てるオブジェクト 詳細

ID3D12GraphicsCommandList6はレンダリング用のコマンドリストをカプセル化し、コマンドを記述するオブジェクト 詳細

ID3D12CommandAllocatorはコマンドリストをGPUに送信するオブジェクト 詳細

参考記事:https://tositeru.blogspot.com/2019/01/direct3d122gpu.html

// GraphicsDevice.h

class GraphicsDevice {
public:
	// ~ //

private:
	// ~ //
	bool CreateCommandList();
	// ~ //
	ComPtr<ID3D12CommandAllocator>			m_pCmdAllocator = nullptr;
	ComPtr<ID3D12GraphicsCommandList6>		m_pCmdList = nullptr;
	ComPtr<ID3D12CommandQueue>				m_pCmdQueue = nullptr;
	// ~ //
}

GraphicsDevice.cpp

GraphicsDevice::Init()関数

CreateCommandList()を実行

GraphicsDevice::CreateCommandList()関数

CreateCommandAllocator()は、コマンドアロケータオブジェクトを作成する関数
▶ 第一引数は作成するコマンドアロケータの種類を指定
▶ 第二引数はコマンドアロケータへのインターフェースのGUIDを指定
詳細

CreateCommandList()は、コマンドリストを作成する関数
▶ 第一引数は複数のGPUアダプターを利用するときに利用する(単一GPUの場合は0)
▶ 第二引数は作成するコマンドリストの種類を指定
▶ 第三引数はコマンドアロケーターオブジェクトへのポインタを指定
▶ 第四引数はパイプライン状態オブジェクトへのポインタを指定
▶ 第五引数はコマンドリストインターフェースのGUIDを指定
詳細

D3D12_COMMAND_QUEUE_DESCは、コマンドキューを記述する構造体
cmdQueueDesc.FlagsD3D12_COMMAND_QUEUE_FLAGS列挙体のフラグを指定(詳細
cmdQueueDesc.NodeMaskは複数のGPUアダプターを利用するときに利用する(単一GPUの場合は0
cmdQueueDesc.PriorityD3D12_COMMAND_QUEUE_PRIORITY列挙定数としてのコマンドキューの優先順位を指定(詳細
cmdQueueDesc.TypeD3D12_COMMAND_LIST_TYPEの列挙定数を指定(詳細
詳細

CreateCommandQueue()は、コマンドキューを作成する関数
▶ 第一引数はコマンド キューを記述するD3D12_COMMAND_QUEUE_DESCを指定
▶ 第二引数はコマンドキューインターフェイスのGUIDを指定
▶ 第三引数はコマンドキューのID3D12CommandQueueインターフェイスへのポインターを受け取るメモリ ブロックへのポインタを指定
詳細

// GraphicsDevice.cpp

bool GraphicsDevice::Init() {
	// ~ //
	if (!CreateCommandList()) {
		assert(0 && "コマンドリストの作成失敗");
		return false;
	}
	// ~ //
}

// ~ //

bool GraphicsDevice::CreateCommandList() {
	auto hr = m_pDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_pCmdAllocator));

	if (FAILED(hr)) {
		return false;
	}

	hr = m_pDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_pCmdAllocator.Get(), nullptr, IID_PPV_ARGS(&m_pCmdList));

	if (FAILED(hr)) {
		return false;
	}

	D3D12_COMMAND_QUEUE_DESC cmdQueueDesc = {};
	cmdQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;				// タイムアウトなし
	cmdQueueDesc.NodeMask = 0;										// アダプターを1つしか使わないときは0でいい
	cmdQueueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;	// プライオリティは特に指定なし
	cmdQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;				// コマンドリストと合わせる

	// キュー生成
	hr = m_pDevice->CreateCommandQueue(&cmdQueueDesc, IID_PPV_ARGS(&m_pCmdQueue));

	if (FAILED(hr)) {
		return false;
	}

	return true;
}

スワップチェーンの初期化

フレームバッファの表示側と描画側を切り替えるための仕組みがスワップチェーン

GraphicsDevice.h

GraphicsDeviceクラス

スワップチェーン関数CreateSwapChain()を宣言(DXGIFactoryの機能)

IDXGISwapChain4はダブルバッファリングのプロセス中のフロントバッファとバックバッファの間でのバッファ交換を管理するインターフェース 詳細

Init()関数に引数を追加(スワップチェーンの作成のために必要)

// GraphicsDevice.h

class GraphicsDevice {
public:
	bool Init(HWND hWnd, int w, int h);
	// ~ //

private:
	// ~ //
	bool CreateSwaphcain(HWND hWnd, int width, int height);
	// ~ //
	ComPtr<IDXGISwapChain4>		m_pSwapChain = nullptr;
	// ~ //
}

GraphicsDevice.cpp

GraphicsDevice::Init()関数

GraphicsDevice::Init()関数に引数を追加

CreateSwapchain()を実行

GraphicsDevice::CreateSwapchain()関数

DXGI_SWAP_CHAIN_DESC1はスワップチェーンについて説明する構造体
swapchainDesc.Widthは解像度の幅を表す値
swapchainDesc.Heightは解像度の高さを表す値
swapchainDesc.FormatはピクセルフォーマットをDXGI_FORMAT列挙で指定(DXGI_FORMAT_R8G8B8A8_UNORMはカラー チャネルごとに8ビットと8ビットアルファをサポートする4コンポーネントの32ビット符号なし正規化整数形式を表す)(列挙の詳細
swapchainDesc.SampleDesc.Countはピクセルあたりのマルチサンプル数をマルチサンプリングパラメーターを記述するDXGI_SAMPLE_DESC構造体で指定(構造体の詳細
swapchainDesc.BufferUsageはバックバッファの使用制限について(サーフェス使用量とCPUアクセスオプション)をDXGI_USAGEフラグで指定(フラグの詳細
swapchainDesc.BufferCountはスワップするバッファの数を指定
swapchainDesc.SwapEffectはスワップ方法を指定(列挙の詳細
swapchainDesc.Flagsはスワップチェインの動作のオプションを指定(列挙の詳細
詳細

CreateSwapChainForHwnd()は、スワップチェーンを作成する関数
▶ 第一引数は作成したコマンドキューのポインタ
▶ 第二引数はウィンドウハンドル
▶ 第三引数はDXGI_SWAP_CHAIN_DESC1構造体へのポインタ
▶ 第四引数はDXGI_SWAP_CHAIN_FULLSCREEN_DESC構造体のアドレス(オプション)
▶ 第五引数はIDXGIOutputインタフェースのポインタ(オプション)
▶ 第六引数はDXGISwapChain1インタフェースのポインタを格納する変数のアドレス
詳細

// GraphicsDevice.cpp

bool GraphicsDevice::Init(HWND hWnd, int w, int h) {
	// ~ //
	if (!CreateSwapchain(hWnd, w, h)) {
		assert(0 && "スワップチェインの作成失敗");
		return false;
	}
	// ~ //
}

// ~ //

bool GraphicsDevice::CreateSwapchain(HWND hWnd, int width, int height) {
	DXGI_SWAP_CHAIN_DESC1 swapchainDesc = {};
	swapchainDesc.Width = width;
	swapchainDesc.Height = height;
	swapchainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapchainDesc.SampleDesc.Count = 1;
	swapchainDesc.BufferUsage = DXGI_USAGE_BACK_BUFFER;
	swapchainDesc.BufferCount = 2;
	swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
	swapchainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

	auto hr = m_pDxgiFactory->CreateSwapChainForHwnd(m_pCmdQueue.Get(), hWnd, &swapchainDesc, nullptr, nullptr, (IDXGISwapChain1**)m_pSwapChain.GetAddressOf());

	if (FAILED(hr)) {
		return false;
	}

	return true;
}

Application.cpp

Application::Execute()関数

GraphicsDevice::Init()関数に引数を追加

// Application.cpp

void Application::Execute() {
	// ~ //
	if (!GraphicsDevice::Instance().Init(m_window.GetWndHandle(), width, height)) {
		assert(0 && "グラフィックスデバイス初期化失敗。");
		return;
	}
	// ~ //
}

Window.h

Application::Execute()関数

GetWndHandle()関数でウィンドウハンドルを取得

// Window.h

class Window {
public:
	HWND GetWndHandle() const {
		return m_hWnd;
	}
	
	// ~ //

private:
	// ~ //
};

スワップチェーンのレンダーターゲットビュー

スワップチェーンのバッファをどのように使うのかを指示するビューを作成する

ビューを作成するためにはディスクリプタヒープが必要となる

ディスクリプタは、リソース(テクスチャ、バッファなど)やビュー(レンダーターゲットビュー、デプスステンシルビューなど)に関する情報を格納するためのデータ構造

ヒープ領域を確保し、ビューの情報を書き込んでいく

GraphicsフォルダにRTVHeapフォルダを作成し、RTVHeap.hRTVHeap.cppを追加

dev-dx12-202308(プロジェクト名)
└─ Source
   ├─ Application
   │  ├─ Application.h
   │  └─ Application.cpp
   ├─ Graphics
   │  ├─ GraphicsDevice.h
   │  ├─ GraphicsDevice.cpp
   │  └─ RTVHeap
   │     ├─ RTVHeap.h
   │     └─ RTVHeap.cpp
   ├─ System
   │  ├─ Utility
   │  │  ├─ Utility.h
   │  │  └─ Utility.cpp
   │  ├─ Window
   │  │  ├─ Window.h
   │  │  └─ Window.cpp
   │  └─ System.h
   ├─ stdafx.h
   └─ stdafx.cpp

RTVHeap.h

RTVHeapクラス

ヒープを作成する関数Create()を宣言
▶ 第一引数はデバイスのポインタ
▶ 第二引数は使用個数

レンダーターゲットビューを作成する関数CreateRTV()を宣言
▶ 第一引数はバッファーのポインタ

D3D12_CPU_DESCRIPTOR_HANDLECPUディスクリプターハンドル(CPUからアクセスできるディスクリプタの位置を示すために使用される)について説明する構造体で、リソースやパイプラインステートオブジェクトなどのグラフィックスオブジェクトを参照するために使用されるハンドルを表す 詳細

レンダーターゲットビューのCPU側アドレスを返す関数GetRTVCPUHandle()を宣言
▶ 第一引数は登録番号

ID3D12Deviceは仮想アタプターを表し、コマンド アロケーター、コマンド リスト、コマンド キュー、フェンス、リソース、パイプライン状態オブジェクト、ヒープ、ルート署名、サンプラー、および多くのリソース ビューを作成するために使用される
DirectX12のアプリケーションでのグラフィックスと計算タスクの中心的なインターフェース
詳細

ID3D12DescriptorHeapはディスクリプタヒープを表すインターフェースで、DirectX 12ではこれを使用してリソースとビューを効率的に管理してレンダリングパイプラインを設定する
詳細

// RTVHeap.h

class RTVHeap {
public:
	bool Create(ID3D12Device* pDevice, int useCount);
	int CreateRTV(ID3D12Resource* pBuffer);
	D3D12_CPU_DESCRIPTOR_HANDLE GetRTVCPUHandle(int number);

private:
	ID3D12Device* m_pDevice = nullptr;
	ComPtr<ID3D12DescriptorHeap> m_pHeap = nullptr;
	int m_useCount = 0;
	int m_incrementSize = 0;
	int m_nextRegistNumber = 0;
};

RTVHeap.cpp

RTVHeap::Create()関数

D3D12_DESCRIPTOR_HEAP_DESCはディスクリプタヒープについて説明する構造体
heapDesc.Typeはヒープ内のディスクリプタの種類を指定
heapDesc.NodeMaskマルチアダプターシステムへの対応(単一アダプター操作の場合は0に設定)
heapDesc.NumDescriptorsはヒープ内のディスクリプタの数
heapDesc.Flagsはヒープのオプションを指定する(列挙の詳細
詳細

CreateDescriptorHeap()はディスクリプタヒープオブジェクトを作成する関数
▶ 第一引数はヒープを記述するD3D12_DESCRIPTOR_HEAP_DESC構造体へのポインタ
▶ 第二引数はディスクリプタヒープインターフェイスのGUID(IID_PPV_ARGSの詳細
詳細

GetDescriptorHandleIncrementSize()は指定された種類のディスクリプタヒープのハンドルインクリメントのサイズを取得する
▶ 第一引数はハンドルインクリメントのサイズを取得するディスクリプタヒープの種類を指定(列挙の詳細
詳細

// RTVHeap.cpp

bool RTVHeap::Create(ID3D12Device* pDevice, int useCount) {
	D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};

	heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	heapDesc.NodeMask = 0;
	heapDesc.NumDescriptors = useCount;
	heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;

	auto hr = pDevice->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&m_pHeap));

	if (FAILED(hr)) {
		return false;
	}

	m_useCount = useCount;
	m_incrementSize = pDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
	m_pDevice = pDevice;

	return true;
}
RTVHeap::CreateRTV()関数

GetCPUDescriptorHandleForHeapStart()関数はヒープの開始を表すCPUディスクリプタハンドルを取得する
詳細

D3D12_RENDER_TARGET_VIEW_DESCはレンダーターゲットビューを使用してアクセスできるリソースのサブリソースについてを説明する
rtvDesc.Formatは表示形式を指定するDXGI_FORMA型指定された値(列挙の詳細
rtvDesc.ViewDimensionはリソースへのアクセス方法を指定する
詳細

CreateRenderTargetView()はリソースデータにアクセスするためのレンダーターゲットビューを作成する
▶ 第一引数はレンダーターゲットを表すID3D12Resourceオブジェクトへのポインタ(インターフェースの詳細
▶ 第二引数はレンダーターゲットビューを記述するD3D12_RENDER_TARGET_VIEW_DESC構造体へのポインタ
▶ 第三引数は新しく作成されたレンダーターゲットビューが存在する宛先を表すCPUディスクリプタハンドル
詳細

// RTVHeap.cpp

int RTVHeap::CreateRTV(ID3D12Resource* pBuffer) {
	if (m_useCount < m_nextRegistNumber) {
		assert(0 && "確保済みのヒープ領域を超えました。");
		return 0;
	}

	D3D12_CPU_DESCRIPTOR_HANDLE handle = m_pHeap->GetCPUDescriptorHandleForHeapStart();
	handle.ptr += (UINT64)m_nextRegistNumber * m_incrementSize;
	D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
	rtvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
	m_pDevice->CreateRenderTargetView(pBuffer, &rtvDesc, handle);
	return m_nextRegistNumber++;
}
RTVHeap::GetRTVCPUHandle()関数

m_pHeap->GetCPUDescriptorHandleForHeapStart()はヒープの開始を表すCPUディスクリプタハンドルを取得

// RTVHeap.cpp

D3D12_CPU_DESCRIPTOR_HANDLE RTVHeap::GetRTVCPUHandle(int number) {
	D3D12_CPU_DESCRIPTOR_HANDLE handle = m_pHeap->GetCPUDescriptorHandleForHeapStart();
	handle.ptr += (UINT64)m_incrementSize * number;
	return handle;
}

System.h

レンダーターゲットビューの作成に必要なRTVHeap.hをインクルード
※依存関係を考慮してGraphicsDevice.hよりも先にインクルードする

// System.h
#pragma once

#include "Utility/Utility.h"

#include "../Graphics/RTVHeap/RTVHeap.h"	// GraphicsDevice.hよりも先にインクルード

#include "../Graphics/GraphicsDevice.h"

GraphicsDevice.h

RTVHeapの変数とスワップチェーンレンダーターゲットビューの作成関数を追加と、レンダーターゲットビューの作成

GraphicsDeviceクラス

スワップチェーンレンダーターゲットビューを作成する関数CreateSwapchainRTV()を宣言

スマートポインタunique_ptrを用いてRTVHeapの変数を定義
スマートポインタの詳細

ID3D12ResourceはCPUとGPUの一般化された機能をカプセル化して、物理メモリまたはヒープの読み取りと書き込みを行うインターフェース
インターフェースの詳細

// GraphicsDevice.h

class GraphicsDevice {
public:
	// ~ //

private:
	// ~ //
	bool CreateSwapchainRTV();
	// ~ //
	std::array<ComPtr<ID3D12Resource>, 2>	m_pSwapchainBuffers;
	std::unique_ptr<RTVHeap>	m_pRTVHeap = nullptr;;
	// ~ //
}

GraphicsDevice.cpp

GraphicsDevice::Init()関数

m_pRTVHeap->Create()を実行

CreateSwapchainRTV()を実行

GraphicsDevice::CreateSwapchainRTV()関数

m_pSwapChain->GetBuffer()はスワップチェーンのバックバッファのいずれかにアクセスする関数
▶ 第一引数は0から始まるバッファインデックス
▶ 第二引数はバッファの操作に使用されるインターフェイスの型
詳細

m_pRTVHeap->CreateRTV()はレンダーターゲットビューを作成する関数
▶ 第一引数はバッファーのポインタ

// GraphicsDevice.cpp

bool GraphicsDevice::Init(HWND hWnd, int w, int h) {
	// ~ //
	m_pRTVHeap = std::make_unique<RTVHeap>();
	if (!m_pRTVHeap->Create(m_pDevice.Get(), 100)) {
		assert(0 && "RTVヒープの作成失敗");
		return false;
	}

	if (!CreateSwapchainRTV()) {
		assert(0 && "スワップチェーンRTVの作成失敗");
		return false;
	}
	// ~ //
}

// ~ //

bool GraphicsDevice::CreateSwapchainRTV() {
	for (int i = 0; i < (int)m_pSwapchainBuffers.size(); ++i) {
		auto hr = m_pSwapChain->GetBuffer(i, IID_PPV_ARGS(&m_pSwapchainBuffers[i]));

		if (FAILED(hr)) {
			return false;
		}

		m_pRTVHeap->CreateRTV(m_pSwapchainBuffers[i].Get());
	}

	return true;
}

デバッグレイヤーの実装

エラー内容を出力するデバッグレイヤーを実装する

GraphocsDevice.h

GraphicsDeviceクラス
// GraphicsDevice.h

class GraphicsDevice {
public:
	// ~ //

private:
	// ~ //
	void EnableDebugLayer();
	// ~ //
}

GraphicsDevice.cpp

GraphicsDevice::Init()
// GraphicsDevice.cpp

bool GraphicsDevice::Init(HWND hWnd, int w, int h) {
	// ~ //
		if (!CreateFactory()) {
		assert(0 && "ファクトリー作成失敗");
		return false;
	}

#ifdef _DEBUG
	EnableDebugLayer();
#endif

	if (!CreateDevice()) {
		assert(0 && "D3D12デバイス作成失敗");
		return false;
	}
	// ~ //
}
GraphicsDevice::EnableDebugLayer()
// GraphicsDevice.cpp

void GraphicsDevice::EnableDebugLayer() {
	ID3D12Debug* pDebugLayer = nullptr;

	D3D12GetDebugInterface(IID_PPV_ARGS(&pDebugLayer));
	pDebugLayer->EnableDebugLayer();		// デバッグレイヤーを有効にする
	pDebugLayer->Release();
}

バックバッファの描画

バックバッファはオフスクリーンのメモリ領域で、ダブルバッファリングやトリプルバッファリングといったテクニックを使用して画面のちらつきや遅延を減らすことができる

GraphicsDevice.h

GraphicsDeviceクラス

画面の切り替えを行うScreenFlip()関数を宣言

コマンドキューの同期待ちを行うWaitForCommandQueue()関数を宣言

コマンドキューとリソースの同期を管理するFenceの作成を行うCreateFence()関数を宣言

リソースとして引数に渡したバッファの扱いを変更するSetResourceBarrier()関数を宣言
▶ 第一引数は指定バッファ(インターフェースの詳細
▶ 第二引数は現在の状況(D3D12_RESOURCE_STATES列挙の詳細
▶ 第三引数は新しい状況(D3D12_RESOURCE_STATES列挙の詳細

ID3D12Fenceはフェンスを表し、CPUと1つ以上のGPUの同期に使用されるオブジェクト
詳細

// GraphicsDevice.h

class GraphicsDevice {
public:
	// ~ //
	void ScreenFlip();

	void WaitForCommandQueue();
	// ~ //

private:
	// ~ //
	bool CreateFence();

	void SetResourceBarrier(ID3D12Resource* pResource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after);
	// ~ //
	ComPtr<ID3D12Fence>		m_pFence = nullptr;
	UINT64		m_fenceVal = 0;
	// ~ //
}

GraphicsDevice.cpp

GraphicsDevice::Init()

CreateFence()を実行

// GraphicsDevice.cpp

bool GraphicsDevice::Init(HWND hWnd, int w, int h) {
	// ~ //
	if (!CreateFence()) {
		assert(0 && "フェンスの作成失敗");
		return false;
	}
	// ~ //
}
GraphicsDevice::ScreenFlip()
リソースバリアのステートをレンダーターゲットに変更

m_pSwapChain->GetCurrentBackBufferIndex()はスワップチェーンの現在のバックバッファーのインデックスを取得する 詳細

SetResourceBarrier()を実行

レンダーターゲットをセット

m_pRTVHeap->GetRTVCPUHandle()を実行

m_pCmdList->OMSetRenderTargets()はレンダーターゲットと深度ステンシルのCPUディスクリプタハンドルを設定する
▶ 第一引数はrtvH配列内のエントリの数
▶ 第二引数はレンダーターゲットディスクリプタのヒープの開始を表すCPUディスクリプタハンドルを記述するD3D12_CPU_DESCRIPTOR_HANDLE構造体の配列を指定
▶ 第三引数について、Falseはハンドルが第一引数のハンドルの配列の最初であることを意味する
▶ 第四引数は深度ステンシルディスクリプタを保持するヒープの開始を表すCPUディスクリプタハンドル
詳細

セットしたレンダーターゲットの画面をクリア

m_pCmdList->ClearRenderTargetView()はレンダーターゲット内のすべての要素を1つの値に設定する
▶ 第一引数はレンダーターゲットがクリアされるヒープの開始を表すCPUディスクリプタハンドルを記述
▶ 第二引数は塗りつぶす色を表す4成分配列
▶ 第三引数は第四引数のパラメーターが指定する配列内の四角形の数を指定
▶ 第四引数はリソースビューでクリアする四角形のD3D12_RECT構造体の配列を指定し、NULLの場合はリソースビュー全体をクリアする
詳細

リソースバリアのステートをプレゼントに戻す

SetResourceBarrier()を実行

コマンドリストを閉じて実行

m_pCmdList->Close()はコマンドリストへの記録が完了したことを示す
詳細

ID3D12CommandListはGPUが実行するコマンドの順序付きセットを表すインタフェース
詳細

m_pCmdQueue->ExecuteCommandLists()は実行するコマンドリストの配列を送信する
▶ 第一引数は実行するコマンドの数
▶ 第二引数は実行するID3D12CommandListコマンドリストの配列 詳細

コマンドリストの同期を待つ

WaitForCommandQueue()を実行

コマンドアロケータとコマンドリストを初期化

m_pCmdAllocator->Reset()はコマンドアロケーターに関連付けられているメモリを再利用することを示す

m_pCmdList->Reset()は新しいコマンドリストが作成されたかのように、コマンドリストを初期状態に戻す

スワップチェーンにプレゼント

m_pSwapChain->Present()はレンダリングされたイメージをユーザーに表示する
▶ 第一引数はフレームのプレゼンテーションを垂直空白と同期する方法を指定する整数値
▶ 第二引数はスワップチェーンプレゼンテーションオプションを含む整数値
詳細

// GraphicsDevice.cpp

void GraphicsDevice::ScreenFlip() {
	auto bbIdx = m_pSwapChain->GetCurrentBackBufferIndex();
	SetResourceBarrier(m_pSwapchainBuffers[bbIdx].Get(),
		D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);

	auto rtvH = m_pRTVHeap->GetRTVCPUHandle(bbIdx);
	m_pCmdList->OMSetRenderTargets(1, &rtvH, false, nullptr);

	float clearColor[] = { 1.0f, 0.0f, 1.0f, 1.0f };
	m_pCmdList->ClearRenderTargetView(rtvH, clearColor, 0, nullptr);

	SetResourceBarrier(m_pSwapchainBuffers[bbIdx].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);

	m_pCmdList->Close();
	ID3D12CommandList* cmdlists[]{ m_pCmdList.Get() };
	m_pCmdQueue->ExecuteCommandLists(1, cmdlists);

	WaitForCommandQueue();

	m_pCmdAllocator->Reset();
	m_pCmdList->Reset(m_pCmdAllocator.Get(), nullptr);

	m_pSwapChain->Present(1, 0);
}
GraphicsDevice::WaitForCommandQueue()

m_pCmdQueue->Signal()は指定した値にフェンスを更新する
▶ 第一引数はID3D12Fenceオブジェクトへのポインタ
▶ 第二引数はフェンスを設定する値を指定
詳細

m_pFence->GetCompletedValue()はフェンスの現在の値を取得する
詳細

CreateEvent()はイベントハンドルを取得する

m_pFence->SetEventOnCompletion()はフェンスが特定の値に達したときに発生するイベントを指定する
▶ 第一引数はイベントが通知される場合のフェンス値を指定
▶ 第二引数はイベントオブジェクトへのハンドルを指定
詳細

WaitForSingleObject()は指定したオブジェクトがシグナル状態であるか、タイムアウト間隔が経過するまで待機してイベントが発生するまで待つ
▶ 第一引数はオブジェクトへのハンドルを指定
▶ 第二引数はタイムアウト間隔(ミリ秒単位)を指定
詳細

CloseHandle()は開いているオブジェクトハンドルを閉じる
詳細

// GraphicsDevice.cpp

void GraphicsDevice::WaitForCommandQueue() {
	m_pCmdQueue->Signal(m_pFence.Get(), ++m_fenceVal);

	if (m_pFence->GetCompletedValue() != m_fenceVal) {
		auto event = CreateEvent(nullptr, false, false, nullptr);		// イベントハンドルの取得
		if (!event) {
			assert(0 && "イベントエラー、アプリケーションを終了します");
		}
		m_pFence->SetEventOnCompletion(m_fenceVal, event);
		WaitForSingleObject(event, INFINITE);		// イベントが発生するまで待ち続ける
		CloseHandle(event);							// イベントハンドルを閉じる
	}
}
GraphicsDevice::CreateFence()

m_pDevice->CreateFence()はフェンスオブジェクトを作成する
▶ 第一引数はフェンスの初期値を指定
▶ 第二引数はフェンスのオプションを指定(列挙の詳細
▶ 第三引数はフェンスインターフェイスID3D12FenceのGUIDを指定
詳細

// GraphicsDevice.cpp

bool GraphicsDevice::CreateFence() {
	auto hr = m_pDevice->CreateFence(m_fenceVal, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_pFence));

	if (FAILED(hr)) {
		return false;
	}

	return true;
}
GraphicsDevice::SetResourceBarrier()

D3D12_RESOURCE_BARRIERはリソースバリア(リソース使用の遷移)について説明する構造体
barrier.Transitionはサブリソースの使用の前後を指定する(構造体の詳細
barrier.Transition.pResourceは遷移で使用されるリソースを表すID3D12Resourceオブジェクトへのポインタを指定
barrier.Transition.StateAfterはサブリソースの"after"使用法指定(列挙の詳細
barrier.Transition.StateBeforeはサブリソースの"before"使用法を指定(列挙の詳細
詳細

m_pCmdList->ResourceBarrier()はリソースへの複数のアクセスを同期する必要があることをドライバーに通知する
詳細

// GraphicsDevice.cpp

void GraphicsDevice::SetResourceBarrier(ID3D12Resource* pResource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) {
	D3D12_RESOURCE_BARRIER barrier = {};
	barrier.Transition.pResource = pResource;
	barrier.Transition.StateAfter = after;
	barrier.Transition.StateBefore = before;
	m_pCmdList->ResourceBarrier(1, &barrier);
}

Application.cpp

Application::Execute()

while文の中でGraphicsDevice::Instance().ScreenFlip()を実行

// Application.cpp

void Application::Execute() {
	// ~ //
	while (true) {
		// ~ //
		GraphicsDevice::Instance().ScreenFlip();
	}
}