DirectX 11 part.1
ダイレクトアタック!ライフポイントは0!!djannです。
さて、今日から再び・・・数年越しにDirectX 11の学習を再開しようと思う。思い立ったが吉日ということで、早速まずは初期化から書いていく。
なお、ウィンドウの作成に関しては言うまでもないことなので、ここでは記述しないこととする。
DirectX 11だが、大抵のグラフィックスAPIと同様に、基本的には以下の流れが存在する。
1. 初期化
2. リソースの作成
3. 状態設定
4. 描画コマンド発行
5. 終了処理
1の後からは2, 3, 4を逐次繰り返しながら描画を行い、最後に5で締めることとなる。
1では描画先のフォーマットや反映のさせ方、反映させる範囲や頻度などを設定する。なお、ここでいくつかのリソースの作成を行うAPIもある。DirectX 11では描画先(レンダーターゲット)のリソースを作成する必要がある(DirectX 9では作成しない)。
さて、初期化に入っていくわけだが、まずはDevice(デバイス)とContext(コンテキスト)とSwapChain(スワップチェイン)を作成しよう。
DeviceはDirectX 11中で使用出来るリソースを作成するのに用いる。各デバイス(GPU)でのリソースの内容の違いを吸収する部分である。
Contextは様々な状態を維持する。分かりやすく言えばコマンドバッファのようなものだ。GPUへの命令(コマンド)を溜め込み、描画命令でGPUへコマンドを送信し、稼働させる。
SwapChainはGPUで描画した画像を実際の画面に反映させる機能、情報だ。GPUが描画する先は(大抵)今見ている画面そのものではなく、内部的に持っている仮想画面、裏画面、オフスクリーンに行われる。それをどのタイミングで、どのように実際の画面に反映させるかを担当している。
上記Device, Context, SwapChainは同時に一気に作れる関数(D3D11CreateDeviceAndSwapChain)が用意されているので、大抵はそれを用いることになるだろう。
さて、では実際にD3D11CreateDeviceAndSwapChainを呼び出し、DeviceやContext、SwapChainを作成するコードだが、内容としては以下の説明のようなものとなる。なお、細かすぎるメンバや特に指定する必要がないであろうと思うものは省いてある。
1. 描画先の縦横サイズ、フォーマット(形式)を設定
2. 描画先の更新頻度(60fpsだったり30fpsだったりというあれ)を分数形式で設定
3. 他、描画先の使用方法(まあここではDXGI_USAGE_RENDER_TARGET_OUTPUTだろう)、描画先ウィンドウのハンドル、ウィンドウモードか否か、MSAAのクオリティやカウント等を設定
4. ここまでがSwapChainのパラメータ設定。次にDevice、Contextの能力レベルを設定する。D3D_FEATURE_LEVEL_11_0でDirectX 11の基準を完全に満たしたGPU上で動作するレベルとなる。もし足りない場合は10_1 -> 10_0 -> 9_3 -> 9_2 -> 9_1と、徐々に下げていく等を行い、成功することを祈る。
さて、ここまでを以下に、実際のコードとして書いてみた。筆者の環境では当然ながらD3D_FEATURE_LEVEL_11_0で動いた。
#include <d3d11.h> // DirectX 11使用の為のヘッダ.
#include <iostream>
#pragma comment( lib, "d3d11.lib") // DirectX 11使用のためのライブラリリンク.
// 以下しばらくは単なるウィンドウ作成なので無視 -----------------------------------
LRESULT __stdcall proc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
{
if ( msg == WM_DESTROY ){
PostQuitMessage( 0 );
return 0;
}
return DefWindowProcA( hwnd, msg, wp, lp );
}
HWND create_window(){
WNDCLASSEXA wc = {};
wc.cbSize = sizeof wc;
wc.hInstance = GetModuleHandleA( nullptr );
wc.lpszClassName = "test window class";
wc.lpfnWndProc = proc;
if ( !RegisterClassExA( &wc ) ){ return nullptr; }
RECT r = { 0, 0, 1280, 720 };
AdjustWindowRect( &r, WS_OVERLAPPEDWINDOW, false );
return CreateWindowExA( 0,
wc.lpszClassName, wc.lpszClassName,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
r.right-r.left, r.bottom-r.top,
nullptr, nullptr,
wc.hInstance,
nullptr
);
}
// ここまで適当なウィンドウ作成。DirectX 11とは無関係 -----------------------------
int main()
{
HWND hwnd = create_window();
// SwapChainの情報指定.
DXGI_SWAP_CHAIN_DESC desc = {};
desc.BufferCount = 1; // オフスクリーンはいくつだ?.
// ここから1.
desc.BufferDesc.Width = 1280;
desc.BufferDesc.Height = 720; // ここまで縦横解像度.
desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 描画先の形式.
// ここから2 - Denomintorが分母.
desc.BufferDesc.RefreshRate.Numerator = 60;
desc.BufferDesc.RefreshRate.Denominator = 1;
// ここから3.
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0; // ここまでMSAAの設定.
desc.Windowed = true; // ウィンドウモードか?.
desc.OutputWindow = hwnd; // ウィンドウハンドル.
// ここからは、特に指定しなくてもいい(0初期化状態のままでいい)と思われるもの.
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // ストアアプリではDISCARDじゃなくSEQUENTIAL限定か?.
desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // フルスクリーン、ウィンドウを切り替えた際に自動変更されるようになる・・・いらないか.
// 他、desc.Flagsでは自動回転のオフやGDIの使用を可能にするなどの指定がある.
// ここから4.
// とりあえずレベル高い順に記述しておく.
D3D_FEATURE_LEVEL features[] = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};
// お待ちかねのCreateDeviceAndSwapChainだ。引数の隣に簡単な説明を書いていく.
// まずは入れ物としてDeviceとなるID3D11DeviceとContextとなるID3D11DeviceContext
// それとSwapChainとなるIDXGISwapChainのポインタ変数をそれぞれ用意する.
ID3D11Device *device;
ID3D11DeviceContext *context;
IDXGISwapChain *swap_chain;
D3D_FEATURE_LEVEL level;
// 戻り値はHRESULTで戻ってくるので、FAILEDマクロに食わせれば成否が分かる・・・マクロか.
if ( FAILED(
D3D11CreateDeviceAndSwapChain(
nullptr, // どのビデオアダプタを使用するか?既定ならばnullptrで、IDXGIAdapterのアドレスを渡す.
D3D_DRIVER_TYPE_HARDWARE, // ドライバのタイプを渡す。これ以外は基本的にソフトウェア実装で、どうしてもという時やデバグ用に用いるべし.
nullptr, // 上記をD3D_DRIVER_TYPE_SOFTWAREに設定した際に、その処理を行うDLLのハンドルを渡す。それ以外を指定している際には必ずnullptrを渡す.
0, // 何らかのフラグを指定する。詳しくはD3D11_CREATE_DEVICE列挙型で検索検索ぅ.
nullptr, // 実はここでD3D_FEATURE_LEVEL列挙型の配列を与える。nullptrにすることで上記featureと同等の内容の配列が使用される.
0, // 上記引数で、自分で定義した配列を与えていた場合、その配列の要素数をここに記述する.
D3D11_SDK_VERSION, // SDKのバージョン。必ずこの値.
&desc, // DXGI_SWAP_CHAIN_DESC構造体のアドレスを設定する。ここで設定した構造愛に設定されているパラメータでSwapChainが作成される.
&swap_chain, // 作成が成功した場合に、そのSwapChainのアドレスを格納するポインタ変数へのアドレス。ここで指定したポインタ変数経由でSwapChainを操作する.
&device, // 上記とほぼ同様で、こちらにはDeviceのポインタ変数のアドレスを設定する.
&level, // 実際に作成に成功したD3D_FEATURE_LEVELを格納するためのD3D_FEATURE_LEVEL列挙型変数のアドレスを設定する.
&context // SwapChainやDeviceと同様に、こちらにはContextのポインタ変数のアドレスを設定する.
)
) ){
// 失敗した場合・・・どうしようもない。DirectX 11が使えないかDXGI_SWAP_CHAIN_DESCに不適切な値が入っている・・・.
std::cout << "failed to initialize the directx 11." << std::endl;
std::cin.get();
return -1;
}
// 成功したんでもう試行する必要はない.
std::cout << "Success level : " << std::hex << level << std::endl;
// さて、各種破棄だ.
swap_chain->Release();
context->Release();
device->Release();
std::cin.get();
UnregisterClass( nullptr, GetModuleHandleA( nullptr ) );
// main関数には暗黙のreturn 0;が存在する.
}
本日はひとまずここまでにしておこう。以降、今更なので需要はなさそうだが、気が向いたと時に徐々に備忘録として書いていく予定だ。