【DirectX12】コマンドキューとスワップチェインの作成【初期化】
目次
こんにちは、ここあです。
今日はコマンドキューとスワップチェインの作成に関して解説をしていきます。
Library & Includes
#include <d3dx12.h> #pragma comment(lib, "d3d12.lib") #include <dxgi1_4.h> #pragma comment(lib, "dxgi.lib") #include <D3Dcompiler.h> #pragma comment(lib, "d3dcompiler.lib") #include <DirectXMath.h> using Microsoft::WRL::ComPtr; using namespace DirectX;
コマンドキュ―の作成
コマンドキューとは
コマンドキューとは、コマンドリストというGPUが実行するべき命令のリストを、指定した順序で GPUに転送するための仕組みです。
コマンドリストやコマンドキューなどの関係については、違う機会に解説しようと思います。
コマンドキューを操作するには、ID3D12CommandQueueインタフェースを使います。
CreateCommandQueue関数
コマンドキューを作成する為には、ID3D12Device::CreateCommandQueue関数を使用します。
ID3D12Device::CreateCommandQueue method | Microsoft Docs
- CreateCommandQueue関数
- 第1引数:D3D12_COMMAND_QUEUE_DESC型の変数のポインタ
- 第2引数:COMオブジェクト各インタフェース固有のGUID
- 第3引数:ID3D12CommandQueueインタフェースポインタを取得した変数へのポインタ
D3D12_COMMAND_QUEUE_DESC構造体
CreateCommandQueue関数の、第1引数として渡す、D3D12_COMMAND_QUEUE_DESC型には、以下の4つのメンバが存在します。
今回は全てをデフォルトの値で初期化して使用しています。
D3D12_COMMAND_QUEUE_DESC | Microsoft Docs
- D3D12_COMMAND_QUEUE_DESC
- Type:コマンドキューの種類を示すD3D12_COMMAND_LIST_TYPE列挙型の値。コマンドキューの初期化に使用できるのはBUNDLEを除いた以下の3種類
- Direct:汎用。全てのコマンドリストを実行可能
- COMPUTE:コンピュートシェーダーを使用する為のもの。COMPUTEとCOPYコマンドリストが実行可能
- COPY:データのコピー専用。COPYコマンドリストのみ実行可能
- Priority:複数キューを作成した場合の実行優先度を設定。デフォルトはD3D12_COMMAND_QUEUE_PRIORITY_NORMAL。ひとつしか作成しない場合は使用しない
- Flags:キューの特別な挙動がある場合に設定。デフォルトはD3D12_COMMAND_QUEUE_FLAG_NONE
- NodeMask:複数のGPUを使用する場合、どのGPUに向けたコマンドキューなのか指定。ひとつのGPUのみ使用する場合0を指定
- Type:コマンドキューの種類を示すD3D12_COMMAND_LIST_TYPE列挙型の値。コマンドキューの初期化に使用できるのはBUNDLEを除いた以下の3種類
今回はFlagsに、D3D12_COMMAND_QUEUE_FLAG_DISABLE_GPU_TIMEOUTを定義しています。
これはGPUが長時間応答を返さない場合でも実行完了を待ち続ける必要がある場合に設定するものです。
事前定義
ComPtr<ID3D12Device> g_device; // デバイス ComPtr<ID3D12CommandQueue> g_commandQueue; // コマンドキュー
実装
const D3D12_COMMAND_QUEUE_DESC commandQueueDesc = {}; if (FAILED(g_device->CreateCommandQueue(&commandQueueDesc , IID_PPV_ARGS(&commandQueue)))) { return false; }
スワップチェインの作成
CreateSwapChainForHwnd関数
スワップチェインの作成には、IDXGIFactory2:: CreateSwapChainForHwnd 関数を使います。
IDXGIFactory2::CreateSwapChainForHwnd method | Microsoft Docs
- CreateSwapChainForHwnd関数
- 第1引数:作成したコマンドキューのポインタ
- 第2引数:ウィンドウハンドル
- 第3引数:DXGI_SWAP_CHAIN_DESC1構造体のポインタ
- 第4引数:DXGI_SWAP_CHAIN_FULLSCREEN_DESC構造体のアドレス
- 第5引数:IDXGIOutputインタフェースのポインタ
- 第6引数:DXGISwapChain1インタフェースのポインタを格納する変数のアドレス
DXGI_SWAP_CHAIN_DESC1構造体
上記で解説した、CreateSwapChainForHwnd関数の第三引数として渡す、DXGI_SWAP_CHAIN_DESC1構造体は以下のメンバが存在します。
DXGI_SWAP_CHAIN_DESC1 | Microsoft Docs
- DXGI_SWAP_CHAIN_DESC1構造体
- Width,Height:フレームバッファの幅と高さ。通常はウィンドウの描画領域と同じ大きさを設定
- Format:フレームバッファのピクセルフォーマット
- Stereo:立体視を使用するか有無を指定
- SampleDesc.Count:AAの為のサンプリング回数を指定。AAを使用しない場合、1を指定。
- SampleDesc.Quality:サンプリングの品質を指定。AAを使用しない場合、0を指定。
- BufferUsage:フレームバッファの使用方法を指定。スワップチェインの作成で使用できるのはDXGI_USAGE_RENDER_TARGET_OUTPUTか、DXGI_USAGE_SHADER_INPUTのどちらか。ウィンドウにスワップチェインを出力する場合、DXGI_USAGE_RENDER_TARGET_OUTPUTを指定。
- Scaling:フレームバッファとウィンドウのサイズが異なる場合の表示方法を指定
- DXGI_SCALING_STRETCH:ウィンドウのサイズに引き伸ばす(デフォルト)
- DXGI_SCALING_NONE:引き伸ばしを一切行わない
- DXGI_SCALING_ASPECT_RATIO_STRETCH:縦横の比率を維持して引き伸ばす
- SwapEffect:画面を切り替えた後、描画画面になったフレームバッファの扱い方を指定
- AlphaMode:フレームバッファの内容をウィンドウに表示する際のアルファ値の扱い方を指定
- DXGI_ALPHA_MODE_UNSPECIFIED:GPUに扱い方を任せる(デフォルト)
- DXGI_ALPHA_MODE_PREMULTIPLIED:事前乗算済みアルファとして扱う
- DXGI_ALPHA_MODE_STRAIGHT:通常のアルファ合成を行う
- DXGI_ALPHA_MODE_IGNORE:アルファ値を無視
- Flags:DXGI_SWAP_CHAIN_FLAG列挙型の値の論理和を指定。通常は使わないので0を指定。
- IDXGISwapChain1:下記に詳細を記載
※AA = アンチエイリアス
アンチエイリアス - Wikipedia
最後のIDXGISwapChain1についてですが、インタフェースを取得した後、ComPtr::As関数を使うことでIDXGISwapChain3インタフェースを取得します。
なぜIDXGISwapChain1に対して、わざとIDXGISwapChain3を取得しています。
これには理由があり、最後に使用するIDXGISwapChian3::GetCurrentBackBufferIndex関数がIDXGISwapChain3からしか呼び出せない為です。
ComPtr::As メソッド
そして、最後にIDXGISwapChian3::GetCurrentBackBufferIndex関数を使い、描画側フレームバッファのインデックスを取得しています。
IDXGISwapChain3::GetCurrentBackBufferIndex method (Windows)
関数名にあるバックバッファですが、これはどのフレームバッファなのかを差す用語になります。
用語 | 解説 |
---|---|
フロントバッファ | 表示側のフレームバッファ |
バックバッファ | 描画側のフレームバッファ |
実際にユーザーの目に触れる(舞台上)からフロント(前面)、触れさせない(舞台裏)からバック(背面)という認識で良いと思われます。
事前定義
const UINT WINDOW_WIDTH = 1920; // ウィンドウの幅 const UINT WINDOW_HEIGHT = 1080; // ウィンドウの高さ HWND hwnd; // ハンドルウィンドウ ComPtr<IDXGISwapChain3> g_swapChain; // スワップチェイン ComPtr<ID3D12CommandQueue> g_commandQueue; // コマンドキュー UINT g_frameIndex = 0; // 描画側フレームバッファのインデックス ComPtr<IDXGIFactory4> dxgiFactory // ファクトリ
実装
// DXGI_SWAP_CHAIN_DESC1構造体の設定 DXGI_SWAP_CHAIN_DESC1 swapChainDesc= {}; scDesc.Width = WINDOW_WIDTH ; scDesc.Height = WINDOW_HEIGHT ; scDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; scDesc.SampleDesc.Count = 1; scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scDesc.BufferCount = frameBufferCount; scDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // スワップチェインの作成 ComPtr<IDXGISwapChain1> tmpSwapChain; if (FAILED(dxgiFactory->CreateSwapChainForHwnd(commandQueue.Get(), hwnd, &swapChainDesc, nullptr, nullptr, &tmpSwapChain))) { return false; } // スワップチェインをキャスト swapChain.As(&g_swapChain); // バックバッファのインデックスを格納 g_frameIndex = g_swapChain->GetCurrentBackBufferIndex();
終わりに
お疲れさまでした。今回はかなり情報密度の濃い回となりました。
DirectXの関数や構造体などは項目が多いので、説明するにも見やすさとの兼ね合いが難しいですね。
読みにくかった場合は、申し訳ありません。
今回の実装内容はGitHubに上げてあります。
解説が間違っていたり、より良い方法などがあった場合は教えて頂けるとありがたいです。
今回でフレームバッファを制御する準備が出来たので、次回はフレームバッファを作成していきたいのですが、その前にデスクリプタヒープと呼ばれるものを作成しなければなりません。
なので、次回はデスクリプタヒープの作成について解説していきたいと思います。
それでは、今回はこの辺りで。
【DirectX12】Direct3Dデバイスの作成【初期化】
こんにちは、ここあです。
昨日記事を上げる事が出来なかったので、今日二つ上げようと思っていたのですが、思った以上に帰ってくるのが遅くなってしまったため、明日二つ上げることにしました。
今日はDirect3Dデバイスの作成について解説していきます。
Library & Includes
#include <d3dx12.h> #pragma comment(lib, "d3d12.lib") #include <dxgi1_4.h> #pragma comment(lib, "dxgi.lib") #include <D3Dcompiler.h> #pragma comment(lib, "d3dcompiler.lib") #include <DirectXMath.h> using Microsoft::WRL::ComPtr; using namespace DirectX;
Direct3Dデバイスの作成
初期化の一番初めにやらなければいけない事が、Direct3Dデバイスの作成です(以下D3Dデバイス)。
Windowsには通常、複数のD3Dデバイスが存在し、GPUを複数PCに積んでいる場合には、それぞれがD3Dデバイスとなります。なので、最初にその中からどのデバイスが目的に合ったものなのかの選択を最初に行います。
DXGIファクトリの生成
IDXGIFactory4を使用します。IDXGIFactory4は生成されたファクトリを操作するためのインタフェースクラスです。
CreateDXGIFactory1関数でDXGIファクトリを生成します。
IID_PPV_ARGSマクロを使うことで、引数から自動的に2つのパラメータを作成してもらう事で、もしプログラムで使いたいインタフェースが変わった場合の、変更箇所を1ヶ所にすることで間違いを起こしにくくします。
CreateDXGIFactory1
ComPtr<IDXGIFactory4> dxgiFactory; // DXGIファクトリの生成 if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)))) { return false; }
デバイスを列挙する為に、IDXGIFactory1::EnumAdapters1関数を使います。この関数はインターフェイスが取得できなかった場合にDXGI_ERROR_NOT_FOUNDを返すので、それが返されるまでループを回して目的のデバイスを探していきます。
IDXGIFactory1::EnumAdapters1
IDXGIAdapter1::GetDesc1関数でデバイスの情報を取得できます。ここでは、この情報を、ハードウェアではないデバイスを除外するために使用しています。
IDXGIAdapter1::GetDesc1 method | Microsoft Docs
デバイスを作成できるか確認
ハードウェアデバイスである確認が取れたものは、D3D12CreateDevice関数で実際に作成できるかどうかを調べます。この関数はインタフェースポインタを格納する変数のアドレスとして、nullptrを渡した場合、作成できるかどうかを調べることが出来ます。今回はnullptrを渡さなければならないのでIID_PPV_ARGSマクロは使えません。
D3D12CreateDevice機能|マイクロソフトドキュメント
- D3D12CreateDevice関数
機能レベルとは、GPUがどの世代のDirectXに対応しているかを示すためのものです。IDXGIAdapter1の情報がこの条件を満たさない場合、作成に失敗します。
ComPtr<IDXGIFactory1> dxgiAdapter; //デバイス取得用 int adapterIndex = 0; //列挙するデバイスのインデックス bool adapterFound = false; //目的のデバイスを見つけたか // 目的のデバイスを探索 while(dxgiFactory->EnumAdapters1( adapterIndex, &dxgiAdapter) != DXGI_ERROR_NOT_FOUND) { DXGI_ADAPTER_DESC1 desc; dxgiAdapter->GetDesc1(&desc); // デバイスの情報を取得 // ハードウェアのみ選ぶ if (!(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) { if (SUCCEEDED(D3D12CreateDevice(dxgiAdapter.Get(), D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr))) { adapterFound = true; break; } } ++adapterIndex; }
WARPデバイスの作成
ループが終わっても、作成可能デバイスが見つからなかった場合、WARPデバイスの作成を試みます。
IDXGIFactory4::EnumWarpAdapter関数を使い、使用可能なWARPデバイスがあるかを調べます。
見つからない場合、Direct3Dは使えないので、falseを返します。
最後にD3D12CreateDevice関数の呼び出しで、実際にデバイスを作成します。
ComPtr<ID3D12Device> g_device; //global変数
// デバイス情報を取得できているか if (!adapterFound) { if (FAILED(dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&dxgiAdapter)))) return false; warp = true; } // if (FAILED(D3D12CreateDevice(dxgiAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&g_device)))) { return false; }
終わりに
これでデバイスを作ることが出来ました。
明日はDirectX12になって追加されたコマンドキューの作成とスワップチェーンの作成について解説したいと思います。
今回のデバイス作成を実装したコードはGitHubに上げています。
それでは、今回はこれで。
もし解説が間違っていたり、より良い方法などがあった場合は教えて頂けるとありがたいです。
github.com
【DirectX12】DirectX開発にComPtrのススメ
こんにちは、ここあです。
今日DirectXの勉強をしていて思ったことがありました。
日本語でDirectX12に関して初期化の先まで解説しているサイトがほぼ無い気がする! そうだ、どうせ自分で開発していくんだから記事を書いていけばいいじゃないか!
※もし説明してくださっているサイトさんがあった場合はごめんなさい
というわけで、私は初期化して三角形を描画するだけではなく、さらに色々実装していく予定ですので書いていくことにしました。
C++のコーディングなどに関しては、機会を見て別で書いていこうかなとも思っているので、主にDirectX12に関係する部分を書いていきたいと思います。
別に特別な実装が出来たとか進捗があったとかの場合は、そっち優先で書いていく予定です。
※過去バージョンのDirectXをよく知らないので、被る説明をする可能性もあります。
東京から返ってくる間も、スマホの機内モードでDirectX12の資料を読んだりしてたのですが、絶対に忘れると思ったので、備忘録的にも扱っていきたいと思います。
DirtectXにはComPtrを
ComPtrとは
ComPtrはスマートポインタというもので、ポインタをラップして、ポインタ型に応じて適切な処理を行う機能を追加したクラスの総称です。
一般的にポインタに関連付けられたオブジェクトの管理をする為にを使うようです。
私は今回の開発でこれを使って、初期化処理などを実装しました。なので、なぜ使ったのかに関して解説していきたいと思います。
DirectXはその大部分をCOMオブジェクトで実装しているようです。
COMオブジェクトについてはこちらを参照。
COMオブジェクトの作成と削除
そして、これが今回の記事を書いた一番の理由なのですが、COMオブジェクトの場合、Microsoftが便利な物が用意されていて、それがMicrosoft::WRL::ComPtrです。
ComPtrは、いちいちプログラマがRelase関数を呼ばなくても、必要なときに自動的に呼んでくれます。
DirectX12の初期化には多くのCOMオブジェクトを作成しなくてはなりません。なので、個別でいちいちRelease関数を呼び出すのはコードを書く上でとても面倒くさい大変なので、これを使うというわけです。
DirectXでCOMオブジェクトを作成するためには、一般的なC++のオブジェクト生成の様に変数を宣言したり、newするのではコンパイルエラーになります。
どうすれば作成できるのかというと、用意された専用の関数を呼び出すことで作成できます。例えば、ID3D12Deviceの場合、ID3D12CreateDevice関数を呼び出すことで作成できます。
また、COMオブジェクトを削除する場合も、生成と同じく専用のRelease関数を呼び出します。
COMオブジェクトの成功・失敗判定
COMオブジェクトの多くの関数がHRESULT型の32bit整数を返すので、DirectXではこの型を使った、成功か失敗かの判定に使用することが多いみたいです。
関数名 | TRUE | FALSE |
---|---|---|
SUCCEEDED | 成功 | 失敗 |
FAILED | 失敗 | 成功 |
ComPtr<ID3D12Device> device; if(FAILED(D3D12CreateDevice(/*引数*/)){ // 処理 } device->Release();
DirectXの関数は引数が多いので、ここでは割愛させていただきます。
こうすることで処理を一文で書くことが出来るので、私はこの方法で記述しています。
これは、人の好みだとは思うのですが、以下の様な書き方をする方もいるようです。
HRESULT hr =FAILED(D3D12CreateDevice(/*引数*/) if(hr){ // 処理 }
私個人の意見としては、デバッグするときに結果が明確にわかりやすい変数に入っているので、そういった場合に良いのかなと思っています。
終わりに
今回はDirectXを使う上でとても楽になるであろうCOMオブジェクトの作成について、解説しました。
明日以降はDirectX12で使っていく各インタフェースのCreate関数について解説していきたいと思います。
それでは、今回はこのあたりで。
DeNAサマーインターン アルムナイに参加してきました!
こんにちは、ここあです。
今日ははるばる北海道から、飛行機に乗って東京まで飛んできました。
なぜかといえば、タイトルの通りサマーインターンの同窓会に参加する為!!
DeNAのクリエイターの方々と、色々とお話を聞かせてもらい、自分の今やっている方向性は間違っていなかったと、自分なりに自信を持てた事も大きな成果のひとつだったかなと思います。
今回話した内容の中で、固まった方針があったので、忘れないように書き記していきたいなと思います。お酒が入っていたので、少し抜けがあるかもしれないけど……その時は、先に進む未来の自分を信じて、察してくれることを期待することにしましょう。
完全に私的な印象として、話した方々の反応として大きかったり、話題が広がったりしたものを抜粋してみました。
好印象に取ってもらえた事
・東京ゲームショウに出展したレーシングゲーム「AbsoluteSpeed」の開発でリーダーを経験した事。
・TwitterやFacebook、ブログなどで自分の情報を発信していること
・C++やDirectX12を勉強していること
・クラス図を作って整理しながら勉強していること
話した中で良いねと言われた事で覚えてる限りだとこんな感じだったはず!
その中でも、自分が意識していなかった部分で評価して頂けたのが、リーダーとしての経験でした。
私が担当したのは、チームリーダー、ゲームループに関わる全体設計、UI等の制御、見た目周りのブラッシュアップでした。
その中でも、
・各メンバーの工数の把握
・残件の整理
・上記から分かる、ブラッシュアップ期間の把握
・優先度の優劣の決定
・チーム全員を纏めて一つの作品をつくりあげた事
この辺りの事を聞いて、良い評価を頂けたようでした。
現状と課題
再認識できたこと
・ゲーム作りがしたい
・プログラムを書くのが好き
・新しいことに挑戦するのが好き
・物事を客観的に見ている……気がする?
今後やること
・DirectX12のリファレンス確認と挙動の検証
・テクスチャ読み込み
・3Dモデルの頂点取得
・バッファの使い方の考察、検証
・レンダーテクスチャの実装
・HLSL基礎
個人的にやりたいこと
・アーマード・コアのようなゲーム性で弾幕的な演出を作ってみたい
・パズルゲームで配列の操作習熟
・面白いの言語化を詰める
と言った感じで、自分としても色々と整理が出来て、明確にやることが決まり、モチベもグングンと上がるいい事尽くしのものでした。
今は羽田空港にいるので、開発再開は明日以降ですが、少しでも時間を無駄にしないように勉強しておくつもりです。
文章も長くなってしまったので、今回はこの辺りで……それではでは
Vtuberハッカソンに参加しました!
こんにちは、ここあです。
さて、今回題名の通り、VTuberハッカソンに参加してきました。
なんと、私のチームもロマン賞という、賞に選んでいただけて、賞品としてドローンをいただきました!前から飛ばしてみたかったので、正直にうれしいかったです。
今回私は使うことが出来なかったのですが、VR機器はもちろんのこと、パーセプションニューロンなどの機器もあり、参加していた他のチームの方々の作品も面白いものが多く、開発のモチベーションをさらに上げるいい機会にもなりました。
後の懇親会では、それぞれのチームの方といろいろ話すこともできました。
今後の勉強の参考になりそうな話も多く聞けたので、さっそく記事などを見てたりします。その中でも「毎日ブログ書くといいよ」との助言をいただけたので、今日から毎日書いていく所存です。
というわけで、今回はこれくらいです。
短くても、継続は力なりといった形で続けていこうと思います。
【DirectX12】使うリソースを一つのクラスに分けました
こんにちは、ここあです。
ここ数日、クラス分けなどをしてLINKエラーが大量に発生したりで、リファクタリングが大幅に遅れてしまいました。
実際にクラス分けをしてみた結果がこちらです。
Mainがゲームループとしてのメインスレッドとして使うクラスですが、今回はまだ三角形を表示させるだけなので、MainでTriangleのインスタンスを作成しています。
それをObjectManagerに渡すことで描画の為のバックバッファを作成する形で作ってみました。
このままだとレンダリングパイプラインが一つしか作られない為、複数のオブジェクトの描画処理を行う場合に、処理として何らかの問題が出てきてしまうのではと思いました。
次の課題は四角形の描画とテクスチャの貼り付けですが、この問題を解決させる為に更にリファクタリングする必要がありそうです。
それでは、今回はここまでです。
ここまでの実装は 私のGitHubに上げました。
C++及びDirectX12は完全に手探りの状態で進めているので、ここはこうした方が良い等の指摘などありましたら、ご教授いただければ幸いです。
C++とDirectX12の勉強を開始しました。
こんにちは、ここあです。
タイトルの通り、C++を使ってDirectX12(以下DX12)の勉強を始めました。
実際には少し前から始めていたのですが、今までゲーム開発で使用していたUnity C#と比べて、C++のコーディングに慣れるのに大分時間がかかってしまいました。
少し慣れてきたので、本格的にDX12を触り始めると……
・初期化処理がやたら長い!
・プログラム初心者のようなミス連発!
・ほぼ初めて、まともに触るVisualStudioの環境構築に悪戦苦闘!
こんな感じで、自分でもバカなのではと思うほどに出てくる問題の数々。
思い出せば、プログラムの触り始めもこんな感じだったような……?
まぁ、それはさておき本題に入りましょう。
DX12ですが、勉強を始めて思ったこととして、2015年に出たとはいえ、やっぱりまだ資料が少ないです。英語で書かれた海外のプログラマの方々が書いて下さっていますが、英語が若干分かるとはいえ、やっぱり読むのは辛かったり……
それでも、資料が少ないので頑張って色々読んで理解を深めていきました。
C++も初めて書くという暴挙も相まって、C#のように上手くクラスのインスタンスを作れなかったり、ポインタの使い方をいまいち自分の中に消化できなかったりと苦戦もしました。
そうこうして、ようやくMicrosoftのDX12のワーキングサンプルを参考にDX12の初期化に成功しました。
現在、作ったソースコードが初期化から描画まで、同じcppファイルに記述しているので、リファクタリングをして、しっかりと分けた後にソースコードを備忘録も兼ねた、自分なりの解説も交えて書こうかと思います。
正直、この記事を書いている間にも、C++を理解できていない部分が多いせいで大量のエラーと格闘してたりしますが、その辺のことも書きたいですね……。