DirectX12で1からフレームワーク制作(2)
はじめに
こんにちは、はろ~です。
ゲーム開発と描画処理の研究をしています。
今回は、DirectX12初期化からレンダーターゲットセット&クリアまでしていきます。
開発環境はVisualStudio2022です。
Github:https://github.com/nakanoyui/DirectX12Framework
DirectX12の初期化
初期化に最低限必要なものは「D3D12Device」「DXGIFactory」です。
D3D12Deviceは基本的に作成関係を行う時に使います。
DXGIFactoryはGPUドライバーの検索、スワップチェインの作成で使います。
ということで実装していきます。と言いたいところですが、先にComPtrを使えるようにします。
ComPtr
ComPtrはC++のスマートポインタのようなものでデストラクタが呼ばれると自動でメモリ解放を行ってくれるもので、メモリリークのヒューマンエラーを抑制できます。
SyetemにUtilityフォルダを作成し、cpp/hを追加。Systemフォルダ以下にhを追加。
今後自身で作成してどこでも使いまわしたいプログラムに関してはSystem.hでインクルードして、System.hをPch.hでインクルードをしていきましょう。
Pch.hで#include <wrl/client.h>をしているので使えるのですが、Microsoft::WRL::ComPtr<>と長くなってしまうのでusingで短縮しちゃいましょう。
ComPtrは初期化関数と解放関数があるのでApplication.cppで両方呼んどいてあげましょう。
(ついでにメモリリークしていたら出力に出るようにしておきます。)
デバイスの初期化
では初期化していきましょう。まず、DirectX12を使うにあたってインクルードしないといけないものをインクルードしていきましょう。Pch.hに追加してください。
次に、Graphicsフォルダ以下にGraphicsDevice.cpp/hを追加してください。描画関係のデバイスは今後このクラスにアクセスして使うようにしていきます。
Graphics.hの実装は以下の通りです。
Graphics.cppの実装は以下の通りです。
※GPUドライバーの優先度に関しては個人の選択でも構いません。
GraphicsDeviceクラスをどこからでもアクセスできるようにSystem.hにインクルードしておきます。
Application.cppのExcute関数の中で初期化関数を呼んで実行し、エラーが起きないか確認してみてください。
描画コマンドの初期化
ここからは描画関係のクラスの初期化を行っていきます。
「コマンドリスト」「コマンドアロケーター」「コマンドキュー」「スワップチェイン」「レンダーターゲット」「ディスクリプタヒープ」があります。
・コマンドリストはデバイスが作成と前述しましたが、こちらは使用(Set)するために使います。(命令の関数を呼ぶだけで命令自体を行っていません。)
・コマンドアロケーターはコマンドリストで呼ばれた関数の内容のメモリ領域を貯めていくものです。
・コマンドキューはコマンドリストのコマンドをコンテナクラスのようにどんどん積んでいくものになります。
・スワップチェインは「ダブルバッファリング」をするための仕組みになります。
・レンダーターゲットは描画対象を指定する道のようなものです。
・ディスクリプタヒープはバッファのビューを登録するためのものです。
※この辺りは言葉で理解するより実際にコーディングして理解していくのをお勧めします。
まずは、GraphicsDevice.hに関数と変数を宣言しましょう。
GraphicsDevice.cppのInitでCreateCommandList関数呼んでおきましょう
関数の中の実装は以下の通りです。
実行を行い、エラーが出ないか確認しておいてください。
スワップチェインの初期化
先程スワップチェインの説明でダブルバッファリングという言葉が出てきたので、少しだけ説明を入れます。描画を行う時に漫画家を想像して欲しいですが、漫画を描く際に一瞬で背景からキャラクターまでを描くのは不可能です。ゲームの描画も同じでこのダブルバッファリングをしていないと一瞬背景がなかったりします。そこで二枚のキャンバスを使って完成した画像を最終的な描画テクスチャとして設定することでこの画面のちらつきを無くしています。
GraphicsDevice.hにスワップチェインの変数とCreate関数を宣言していきます。
CreateSwapchain関数の引数でウィンドウハンドルと横幅・縦幅が必要なのでGraphicsDeviceのInitの引数を追加しましょう。
GraphicsDevice.cppのInitの引数とSwapchainのCreate関数を追加します。
Create関数の実装は以下の通りです。
Application.cppで呼んでいるGraphicsDeviceのInitの引数を追加しましょう。
m_windowのウィンドウハンドルを取得する関数を作成していなかったので作成しましょう。
ここまで実装出来たら一度実行してエラーが出ないか確認をしてください。
スワップチェインのレンダーターゲットビュー(RTV)を作成
現在CreateSwapchain関数でスワップチェインのバッファを確保しましたが、それだけでは使えません。バッファはあくまでメモリの塊です。このバッファをどのように使うか指示をするのがビューです。今後定数バッファービューやシェーダーリソースビュー等が出てきますが、これらも感覚としては同じです。少し話が反れましたが、とりあえず今のままでは何もできないのでビューを作成していきたいのですが、ビューを作成するにはディスクリプタヒープと言うものが必要になります。
ディスクリプタヒープ
ディスクリプタヒープに関してはヒープ領域というものを確保し、それにビューの情報を書き込んでいくものです。ディスクリプタヒープで初期化時に大量のデータを確保し、ビュー情報をそのヒープに対して紐づけていく感覚です。※理解できなくてもコーディングをしながら覚えていきましょう。
と言う事でディスクリプタヒープを作成していきましょう。
Graphicsフォルダ以下にRTVHeapと言うフォルダを作成cpp/hを追加してください。
RTVHeap.hの実装は以下の通りです。
RTVHeap.cppの実装は以下の通りです。
RTVHeapはRTVを作成するときに必ず使うのでSystem.hでインクルードしちゃいましょう。
GraphicsDevice.hでRTVHeapの変数とスワップチェインのRTV作成関数を追加します。
GraphicsDevice.cppのInit関数で変数のインスタンス確保とCreateSwapchainRTV関数を呼び出します。
これでディスクリプタヒープの作成は完了しました。やっとRTVの作成をしていきましょう。
GraphicsDevice.hにスワップチェインのバッファを取得するための変数を宣言しましょう。
先程宣言したCreateSwapchainRTVの実装をしていきます。
ここまで実装できたら実行してエラーが起こらないか確認しておいてください。
バックバッファを描画
いよいよバックバッファを描画していきます。
GraphicsDevice.hにCreateFence,WaitForCommandQueue,SetResourceBarrierという関数と必要な変数を追加してください。
ここで新しく出てきたFenceという言葉ですが、Fenceは、CommandQueueの実行同期を取るためのものです。
各関数の実装は以下の通りです。
GraphicsDevice.cppのInitにCreateFence関数を呼び出しておきましょう。
描画処理のフローは以下の通りです。
- リソースバリアのステートをレンダーターゲットに変更
- レンダーターゲットをセット
- セットしたレンダーターゲットの画面をクリア
- リソースバリアのステートをプレゼントに戻す
- コマンドリストを閉じて実行する。
- コマンドリストの同期を待つ
- コマンドアロケーターとコマンドリストを初期化
- スワップチェインにプレゼント(送る)
ではScreenFlip関数を実装していきます。
ここまで実装出来れば画面が紫色になっているはずです。
デバッグレイヤー
最後に低レイヤー層を実装していると何でバグが起こったか分かりにくいという問題があります。
そこでバグが出ても出力にエラー内容を記載してくれるデバッグレイヤーを実装しましょう。
GraphicsDevice.hにEnableDebugLayer関数を宣言してください。
実装内容は以下の通りです。
GraphicsDevice.cppのInit関数の中で呼び出しておいてください。
※デバッグレイヤーは比較的重たい処理なのでDebug実行時だけ適用したいので、以下のようにしておいてください。
最後に
今後も続きを記事にするつもりですが、更新頻度に関しては不定期になります。(気が向いたら更新します)
著者は学生なので(保険)、多々間違える事があると思いますがその時は温かい目で見ていただき指摘してくださると助かります。
Discussion