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.lib
とdxgi.lib
をソースコード内の記述でリンクして、d3d12.h
とdxgi1_6.h
をインクルードする
// stdafx.h
// D3D12
#pragma comment(lib,"d3d12.lib")
#pragma comment(lib,"dxgi.lib")
#include <d3d12.h>
#include <dxgi1_6.h>
Graphics
フォルダにGraphicsDevice.h
とGraphicsDevice.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.Flags
はD3D12_COMMAND_QUEUE_FLAGS
列挙体のフラグを指定(詳細)
▶ cmdQueueDesc.NodeMask
は複数のGPUアダプターを利用するときに利用する(単一GPUの場合は0
▶ cmdQueueDesc.Priority
はD3D12_COMMAND_QUEUE_PRIORITY
列挙定数としてのコマンドキューの優先順位を指定(詳細)
▶ cmdQueueDesc.Type
はD3D12_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.h
とRTVHeap.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_HANDLE
はCPUディスクリプターハンドル(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();
}
}