DirectX11 DeferredContextによるレンダリング処理の効率化

投稿日:

DirectX11のDeferredContextはマルチスレッドで並列にレンダリング処理(GPUコマンド生成)を行うことでレンダリング処理の時間を短縮する機能です。DeferredContextを使うには少しコツがいるため、使い方を解説をします。ImmediateContextと同じ感覚で使用すると大量のエラーログが出力されて何も描画されません。


マルチスレッドを使用したDirectX11描画処理の効率化

ID3D11Device::CreateDeferredContextで複数作成したDeferredContextは、それぞれ別々のスレッドで同時に描画処理を行うことができるため、描画処理の処理時間を短縮できます。DeferredContextでの描画命令はすぐにGPUへ発行されず、一旦コマンドリスト(D3D11CommandList)に保存され、ImmediateContextのExecuteCommandList関数で描画が実際に実行されます。

DeferredContextの使用例

DeferredContextは以前のレンダリング状態を引き継がないため、すべての状態を再設定する必要があります。状態にはRenderTargetとViewportも含まれます。ImmediateContextでは最初に1回だけ指定することが多いため、DeferredContextを同じような感覚で使用するとエラーになります。

ID3D11Device* pDevice = D3D11デバイス;
ID3D11DeviceContext* pImmediateContext = イミディエイトコンテキスト
ID3D11RenderTargetView* pRenderTagertView = 描画するターゲット;
ID3D11DepthStencilView* pDepthStencilView = 使用するデプスバッファ;
D3D11_VIEWPORT* pViewport = ビューポート情報;

//RenderTargetの設定
ID3D11RenderTargetView* rtv[1] ={ pRenderTagertView };
pImmediateContext->OMSetRenderTargets(1, rtv, pDepthStencilView);
pImmediateContext->RSSetViewports(1, pViewport);

//画面クリア
float ClearColor[4] ={ 0,0,0,1};
pImmediateContext->ClearRenderTargetView(pRenderTagertView, ClearColor);
pImmediateContext->ClearDepthStencilView(pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

//ディファード コンテキスト
ID3D11DeviceContext* pDeferredContext;
pDevice->CreateDeferredContext(0, &pDeferredContext);

//ここから別スレッドで並列処理
//ステートなどの描画状態を引き継げないため再設定
ThreadFunc(ID3D11CommandList** ppCommandList)
{
    //!!!!!!!!!!!!!!
    // これを忘れないこと
    // RenderTargetの設定
    ID3D11RenderTargetView* rtv[1] ={ pRenderTagertView };
    pDeferredContext->OMSetRenderTargets(1, rtv, pDepthStencilView);
    pDeferredContext->RSSetViewports(1, pViewport);
    //ピクセルシェーダの設定時にこの情報が必要です
    //!!!!!!!!!!!!!!

    // 定数バッファ更新
    pDeferredContext->UpdateSubresource(...);

    // 頂点バッファ、入力レイアウト
    pDeferredContext->IASetInputLayout(...);
    pDeferredContext->IASetVertexBuffers(...);
    pDeferredContext->IASetIndexBuffer(...);
    pDeferredContext->IASetPrimitiveTopology(...);

    //シェーダーと定数バッファ
    pDeferredContext->VSSetConstantBuffers(...);
    pDeferredContext->VSSetShader(...);
    pDeferredContext->PSSetConstantBuffers(...);
    pDeferredContext->PSSetShader(...);

    // シェーダーリソースビュー(テクスチャ)
    pDeferredContext->PSSetShaderResources(...);
    pDeferredContext->PSSetSamplers(...);

    // ステート
    pDeferredContext->RSSetState(...);
    pDeferredContext->OMSetBlendState(...);
    pDeferredContext->OMSetDepthStencilState(...);

    //ポリゴン描画
    pDeferredContext->DrawIndexed(...);

    //コマンドリスト作成
    pDeferredContext->FinishCommandList(FALSE, ppCommandList);
}

//スレッド処理終了後
//ppCommandListに作成したコマンドリスト

//ディファード コンテキストで作成したコマンドリストを実行
pImContext->ExecuteCommandList(pCommandList, FALSE);

// OMSetRenderTargetsやRSSetViewportsを設定しないと
// こんなエラーが大量に出ます
D3D11 WARNING: ID3D11DeviceContext::DrawIndexed: The Pixel Shader expects a Render Target View bound to slot 0, but none is bound. This is OK, as writes of an unbound Render Target View are discarded. It is also possible the developer knows the data will not be used anyway. This is only a problem if the developer actually intended to bind a Render Target View here. [ EXECUTION WARNING #3146081: DEVICE_DRAW_RENDERTARGETVIEW_NOT_SET]
D3D11 WARNING: ID3D11DeviceContext::DrawIndexed: The Pixel Shader expects a Render Target View bound to slot 0, but none is bound. This is OK, as writes of an unbound Render Target View are discarded. It is also possible the developer knows the data will not be used anyway. This is only a problem if the developer actually intended to bind a Render Target View here. [ EXECUTION WARNING #3146081: DEVICE_DRAW_RENDERTARGETVIEW_NOT_SET]
D3D11 WARNING: ID3D11DeviceContext::DrawIndexed: The Pixel Shader expects a Render Target View bound to slot 0, but none is bound. This is OK, as writes of an unbound Render Target View are discarded. It is also possible the developer knows the data will not be used anyway. This is only a problem if the developer actually intended to bind a Render Target View here. [ EXECUTION WARNING #3146081: DEVICE_DRAW_RENDERTARGETVIEW_NOT_SET]
D3D11 WARNING: ID3D11DeviceContext::DrawIndexed: The Pixel Shader expects a Render Target View bound to slot 0, but none is bound. This is OK, as writes of an unbound Render Target View are discarded. It is also possible the developer knows the data will not be used anyway. This is only a problem if the developer actually intended to bind a Render Target View here. [ EXECUTION WARNING #3146081: DEVICE_DRAW_RENDERTARGETVIEW_NOT_SET]

描画コマンド(GPUコマンド)事前作成による効率化

2017年7月13日更新
DeferredContextの使用方法を詳しく検証した結果、勘違いでした。コマンドの事前作成でそれほど処理時間が短縮されないどころか条件によっては遅くなります。速くなるという思い込みで間違った情報を公開してしまい、申し訳ありませんでした。

DeferredContextはGPUコマンドを作成し、ImmediateContextのExecuteCommandListでそのコマンドをCall(ジャンプ命令)する機能だと思っていたのですがExecuteCommandListではもっと複雑な処理をしているようです。Call(ジャンプ命令)であれば一定時間で処理が終了しますが、詳しく測定してみるとImmediateContextで実行した場合の80%ほどの時間がかかっているようです。オーバーヘッドがあるためDrawコール(DrawIndexedなど)回数が少ないと逆に遅くなります。

D3D11_FEATURE_DATA_THREADING::DriverCommandLists = TRUE
であれば状況が変わるかもしれません。GPUが未対応のため検証できませんでした。

まとめ

予想ですが、DeferredContextはGPUコマンドではなく、DeviceContextの関数呼び出しを保存してExecuteCommandListで実際の関数を実行しているような気がします。
DirectX11関数の処理時間は
DeferredContext + ExecuteCommandListの処理時間 > ImmediateContext処理時間
となるようです。DeferredContextは、複雑な処理の途中にDeviceContextの関数が混ざっている処理をマルチスレッドで実行可能にする機能のようです。DirectX11関数の処理時間が増加するため、マルチスレッドでの時間短縮が上回らないと使用する意味がないと思われます。

DeferredContextを使用したもう1つの効率化の方法です。DeferredContextはその場でGPUへ命令を発行せずにGPUコマンドデータに変換、D3D11CommandListに保存します。この機能を使用すれば、描画命令で生成されるGPUコマンドを事前に作成できるため、DeviceContextの描画命令の処理時間の削減になります。毎回フレーム実行される描画処理では、行列更新など変化のあるものだけ描画命令を実行、それ以外の変化のないものはExecuteCommandListを実行するだけになり、かなりの効率化となります。

マイクロソフトのDirectX11のページを見ても、このような使用例は見当たりませんでした。この方法はDirectX12でのCreateGraphicsPipelineStateと同じようなことDirectX11で再現する方法なので問題ないと思いますが、非推奨な使い方かもしれません。

GPUコマンド事前作成例

ポリゴンメッシュの姿勢行列の更新以外の描画処理を事前にGPUコマンドに変換するプログラムです。この方法で処理時間が約半分になります。意外でしたが、定数バッファを更新するUpdateSubresource関数が処理時間の半分を占めています。

まとめ

マルチスレッドでの効率化の検証のつもりでしたが、予想外にコマンドリストの事前作成が効率化に有利なことがわかりました。それとUpdateSubresourceが予想以上に時間のかかる処理ということも判明しました。3Dゲームでは、事前に作成できる描画情報が多いため、効率化がかなり期待できます。(あくまで個人の感想です。変化のある情報は位置や色情報、パーティクルなど動的に生成する頂点データなどそれほど多くないと思います。)
マルチスレッドとコマンドリスト事前作成の組み合わせが最強かも。
デメリットとして、詳しく計測していませんが事前作成するとコマンドリストの分だけメモリ使用量が増加します。

コメントを残す

メールアドレスが公開されることはありません。

認証:数字を入力してください(必須) * Time limit is exhausted. Please reload CAPTCHA.