DX Work with DirectX device resources - yoshimune/LearningDirectX11 GitHub Wiki
Microsoft DirectX Graphics Infrastructure(DXGI) のルールを理解してください。DXGIは、低レベルグラフィックスとグラフィックスアダプターリソースの管理と設計に利用されるAPIセットです。これらなしにゲームグラフィックの描画はできません。
次のようにDXGIを考えてみましょう。直接GPUにアクセスし、そのリソースを管理するため、アプリケーションに記述する方法が必要です。GPUについて必要な情報で最も重要な点はピクセルを描画する場所です。これらのピクセルをスクリーンに送信できるようにするためです。典型的には、これは"back buffer"と呼ばれています。"flipped"または"swapped"させてリフレッシュシグナルで画面に送る、どこのピクセルに描画するのかを指定したGPUメモリの配置です。DXGIはその配置とバッファを使用する方法を取得できます。(swap chain と呼ばれています。)
このことを行うため、スワップチェーン書き込みのためアクセスする必要があります。そして、現在のスワップチェーンのバックバッファを表示するようウィンドウを操作します。また、この2つを接続して、OSが要求したときにバックバッファの内容でウィンドウを更新するようにする必要もあります。
スクリーンに描画するプロセスの全容は以下のとおりです。
- CoreWindowを取得する
- Direct3Dデバイスとコンテキストのインターフェイスを取得する
- CoreWindow内のレンダリングイメージを表示するためのスワップチェーンを作成する
- 描画するレンダーターゲットを作成し、ピクセルで描画する
- スワップチェーンを実行
最初にすることは、ウィンドウを作成することです。最初は、WNDCLASSのインスタンスを作成してウィンドウクラスを作成し。RegisterClassを使って登録します。ウィンドウクラスはウィンドウの重要なプロパティを含みます。アイコン、静的メッセージの処理関数、ウインドウクラスの一意の名前などです。
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
HRESULT hr = CoInitializeEx(nullptr, COINITBASE_MULTITHREADED);
if (FAILED(hr)) {
return 1;
}
HICON hIcon = NULL;
WCHAR szExePath[MAX_PATH];
GetModuleFileName(NULL, szExePath, MAX_PATH);
// Register class and create window
{
// Register class
WNDCLASS wndClass;
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = hIcon;
wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndClass.lpszMenuName = nullptr;
wndClass.lpszClassName = L"SimpleWindowClass";
if (!RegisterClass(&wndClass))
{
DWORD dwError = GetLastError();
if (dwError != ERROR_CLASS_ALREADY_EXISTS)
{
return HRESULT_FROM_WIN32(dwError);
}
}
}
return 0;
}
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Windows procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);
}
次に、ウィンドウを作成します。ウィンドウサイズ情報とウィンドウクラスの名前を提供する必要があります。CreateWindowを呼び出したとき、HWNDと呼ばれる不透明ポインタが返されます。HWNDポインタを保持しておくことと、必要なときに参照できるようにしておくことが必要です。削除、再生成、特に重要なのが、DXGIスワップチェーンの作成です。
int x = CW_USEDEFAULT;
int y = CW_USEDEFAULT;
// This example uses a non-resizable 640 by 480 viewport for simplicity.
RECT rc;
rc.top = 0;
rc.left = 0;
rc.right = static_cast<LONG>(640);
rc.right = static_cast<LONG>(480);
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
// Create the window for our viewport.
HWND hwnd = CreateWindow(
L"SimpleWindowClass",
L"Cube11",
WS_OVERLAPPEDWINDOW,
x, y,
(rc.right - rc.left), rc.bottom - rc.top,
0,
nullptr,
hInstance,
0
);
if (hwnd == nullptr)
{
DWORD dwError = GetLastError();
return HRESULT_FROM_WIN32(dwError);
}
ウィンドウデスクトップアプリモデルはWIndowsメッセージループのフックを含んでいます。"StaticWindowProc"関数を記述してウィンドウイベントを処理し、メインプログラムのループをループからはずす必要があります。これは必ず静的関数である必要があります。Windowsは外部クラスのコンテキストからこの関数を呼ぶためです。以下は、極力簡単にしたstatic message processing functionの例です。
// Windows procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Direct3Dを使用する最初のステップはDirect3Dハードウェア(GPU)のインターフェイスを取得することです。ID3D11DeviceとID3D11DeviceContextのインスタンスに代表されます。前者はGPUリソースの仮想表現であり、後者はデバイスに依存しない、レンダリングパイプラインとプロセスの抽象表現です。ここでは、簡単な方法を考えます。ID3D11Deviceは、稀に呼ばれるグラフィックスメソッドを含みます。通常はレンダリング発生前に、描画を開始するためにひつようなリソースの要求と設定を行います。
一方、ID3D11DeviceContextは毎フレーム呼ばれます。バッファのロード、別のリソースの参照、合成方法の変更、ラスタライザステート、シェーダーの管理、これらのリソースがステートとシェーダを通過した結果を描画する、などです。
これらは、このfeature levelを設定するプロセスで最も重要なパートのうちの一つです。feature levelはDirectXにサポートする最小のハードウェアレベルを通知します。
Direct3Dデバイスとデバイスコンテキスト両方の参照(ポインタ)を入手し、それらをclass-level変数としてDeviceResourcesインスタンス(ComPtr)に格納してください。これらは、いつでも必要なときにDirect3Dデバイスかデバイスコンテキストを参照できます。
ウィンドウが描画されました。そして、データ送信とGPUにコマンドを送るためのインターフェイスを用意しました。次はデータ・コマンドを持っていく方法です。
最初に、スワップチェーンのプロパティの使用する値をDXGIに伝えます。この操作はDXGI_SWAP_CHAIN_DESC構造体を使います。デスクトップアプリにとって重要なフィールドが6つあります。
- Windowed スワップチェーンがフルスクリーンであるかウインドウクリップであるかを指示します。これにTRUEをセットすると、作成したウィンドウ内にスワップチェーンを配置します。
- BufferUsage これにDXGI_USAGE_RENDER_TARGET_OUTPUTにセットします。これはスワップチェーンが描画面になり、Direct3Dレンダーターゲットとして使用できることを示します。
- SwapEffect これにDXGI_SWAP_EFFECT_FLIP_SEQUENTIALをセットします。
- Format DXGI_FORMAT_B8G8R8A8_UNORMフォーマットは32bitカラー(RBGAカラーチャンネルにそれぞれ8bit割当)を指定します。
- BufferCount 伝統的な"ダブルバッファ"方式だと2を設定します。もしグラフィックスコンテントが、1フレームをレンダリングする際に1つより多くのモニター更新サイクルを使う場合は、バッファカウントを3にセットします。(例えば60Hzの場合、閾値は16msを超えます)
- SampleDesc このフィールドはマルチサンプリングを操作します。フリップモデルスワップチェーンのCountに1を、Quality0をセットします。(マルチサンプリングをフリップモデルと共に使用すると、別々にマルチサンプリングされたレンダーターゲットに描画し、レンダーターゲットが表示される直前にそのターゲットスワップチェーンを解決します。Multisampling in Windows Store appsを参照してください)
スワップチェーンの設定が完了した後、スワップチェーンを作成するために、Direct3Dデバイス(とデバイスコンテキスト)を作成した同じDXGIファクトリを使用しなくてはなりません。
IDXGIDevice3にアップキャストして、IDXGIDevice::GetAdapterを呼び、DXGIアダプタを要求します。IDXGIFactory2::GetParent を呼び出し、アダプタのために親のファクトリを取得してください。これでCreateSwapChainForHwndを呼び出すことによってスワップチェーンを作成するファクトリを使うことが出来るようになります。
初めて設定する場合は、このように設定するのが良いでしょう。今の時点で、既に前のバージョンのDirectXに詳しい場合は、こう聞きたくなるかもしれません。「なぜこれらのクラスを全て取得させるのではなく、デバイスとスワップチェーンを同時に作成しなかったのでしょうか?」その答えは、効率です。スワップチェーンはDirect3Dデバイスリソースです。そしてデバイスリソースはとくにこれらを作成したDirect3Dデバイスと密接に結びついています。もし、新しいデバイスを新しいスワップチェーンとともに作成したとすると、新しいDirect3Dデバイスを使って全てのデバイスリソースを再生成しなくてはなりません。そのため、スワップチェーンを同じファクトリとともに作成することで、スワップチェーンの再生成と既にロードしたDirect3Dデバイスリソースの使用を継続することが可能になります。
OSよりウインドウ、GPUとそのリソースにアクセスする方法、スワップチェーンを取得することができました。残りは全てを結ぶことです。
シェーダーパイプラインは、内部にピクセルを描画するリソースが必要です。このリソースを作成する一番簡単な方法は、ピクセルシェーダーが描画するバックバッファとしてID3D11Texture2Dリソースを定義することです。そしてこのテクスチャをスワップチェーン内に読み込むことです。
これをすると、レンダーターゲットビューを作成します。Direct3Dでは、ビューは特定のリソースにアクセスする方法です。今回のケースでは、ビューは、ピクセルシェーダーがテクスチャのそれぞれのピクセル全てに対して書き込むことを可能にします。
スワップチェーンにDXGI_USAGE_RENDER_TARGET_OUTPUTをセットしたとき、表面描画として使われるDirect3Dリソースが基礎となることが可能になります。よって、レンダーターゲットビューを取得するには、スワップチェーンからバックバッファをの取得し、バックバッファリソースにバインドされたレンダーターゲットビューを作成するだけです。
また、デプス・ステンシルバッファを作成します。デプス・ステンシルバッファは単なる特定のID3D11Texture2Dリソースフォームです。典型的には、ピクセルがラスタライゼーション時に、カメラとオブジェクトの距離に応じた描画優先順位の決定に使われます。デプス・ステンシルバッファはまたステンシルエフェクトに使用することもできます。これは特定のピクセルはラスタライゼーション時に描画を破棄するという効果です。このバッファはレンダーターゲットと同じサイズでなくてはなりません。フレームバッファにデプス・ステンシルテクスチャを読み込むまたはレンダリングすることはできません。なぜならば、デプス・ステンシルバッファは最後のラスタライゼーションより前のシェーダーパイプラインによって独占的に使用されるためです。
また、ID3D11DepthStencilViewとしてデプス・ステンシルバッファのビューを作成してください。このビューはシェーダーパイプラインにどうやってID3D11Texture2Dの基礎となっているかを判断するのかを通知します。よって、もし、このビューを供給しないと、ピクセル単位のデプステストは実行されませんし、シーン内のオブジェクトがちょっと内側に見えるかもしれません。
最後のステップは、ビューポートを作成することです。これはバックバッファがスクリーン上に表示した見える長方形を定義します。ビューポートのパラメータを変更することによって、スクリーンに表示されているバッファの一部を変更できます。このコードはウィンドウサイズちょうど、またはスクリーン解像度を目標としています。このケースではフルスクリーンスワップチェーンです。楽しみのため、与えられた座標の値を変更し、結果をみてみましょう。