いまさらDirect3D11入門

初めてグラフィックスAPIを触る人に向けて

ディファードコンテキスト

今パートではID3D11DeviceContextの1種であるディファードコンテキスト(英訳:Deferred Context)について見ていきます。

今までシェーダやリソースの設定などでID3D11DeviceContextを使ってきましたが、それらはイミディエイトコンテキスト(英訳:Immediate Context)と呼ばれています。 イミディエイトを訳すと「即時」といった意味になり、イミディエイトコンテキストを通じて行う設定など(以後、コマンドと呼びます)は直ちにコマンドバッファという一時的なストレージ空間へ積まれGPUの都合がいい時に実行されます。 またイミディエイトコンテキストはメインスレッド上からでしか使うことができません。 なので、マルチスレッドを利用している場合は何かしらの方法でGPUを実行するために必要な情報をメインスレッドに移す必要があります。

それに対してディファードコンテキストはディファードの意味である「延期」が表すように、それを通じたコマンドはディファードコンテキストに記録され実行はされません。 ディファードコンテキストが記録したコマンドは後でイミディエイトコンテキストを使って実行する必要がありますが、メインスレッド以外でもコマンドの記録が可能となっています。 つまり、マルチスレッド上でGPUで実行させるコマンドを作成して、あとはメインスレッドにディファードコンテキストを渡して実行するだけで済みます。 更に発展してGPUが動作している裏で次に実行させるコマンドを記録し、GPUの動作が終わったら裏で記録していたコマンドをすぐに実行させることもでき、 GPUを絶え間なく動作させることが出来ます。

概要

それではディファードコンテキストについて見ていきましょう。 対応するパートはPart13_DeferredContextになります。

作成

まず、ディファードコンテキストの作成について見ていきます。 といっても1行で作ることが出来ます。

// Scene::onInit関数の一部
//ディファードコンテキストの作成
this->mpDevice->CreateDeferredContext(0, this->mpDeferedContext.GetAddressOf());

ID3D11Device::CreateDeferredContextでディファードコンテキストを作成します。 第1引数には必ず0を渡してください。

ドキュメント:ID3D11Device::CreateDeferredContext (日本語) (英語)

コマンドの記録とその実行

コマンドを記録する方法はイミディエイトコンテキストと同じように関数を呼び出すだけです。 コマンドの記録を終える場合はID3D11DeviceContext::FinishCommandListを呼び出してください。 また、ディファードコンテキストで記録したコマンドは他のディファードコンテキストのID3D11DeviceContext::ExecuteCommandListに渡すことでコピーすることができます。

ドキュメント:
ID3D11DeviceContext::FinishCommandList (日本語) (英語)
ID3D11DeviceContext::ExecuteCommandList (日本語) (英語)

std::thread record([&]() {
  while (this->mIsRunRecordingThread) {
    Sleep(2);
    std::unique_lock<std::mutex> lock(this->mMutex);
    //記録したコマンドをクリアしている
    this->mpCommandLists.Reset();
    //別のディファードコンテキストが記録したものをコピーすることも出来る
    //グラフィックスパイプラインに関係するものは複数のディファードコンテキストにまたがって記録することはできないので注意
    this->mpDeferedContext->ExecuteCommandList(this->mpCLRBindTAndVP.Get(), false);
    //ビューポートの設定
    D3D11_VIEWPORT vp;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    vp.Width = static_cast< float >(this->width());
    vp.Height = static_cast< float >(this->height());
    vp.MinDepth = 0.f;
    vp.MaxDepth = 1.f;
    this->mpDeferedContext->RSSetViewports(1, &vp);
    //レンダーターゲットの設定
    std::array<ID3D11RenderTargetView*, 1> RTs = { {
      this->mpBackBufferRTV.Get()
    } };
    this->mpDeferedContext->OMSetRenderTargets(static_cast<UINT>(RTs.size()), RTs.data(), this->mpZBufferDSV.Get());
    //ピクセルシェーダ
    this->mpDeferedContext->PSSetShader(this->mpPixelShader.Get(), nullptr, 0);
    //入力アセンブラステージ
    std::array<ID3D11Buffer*, 1> ppVertexBuffers = { {
      this->mpTriangleBuffer.Get(),
    } };
    std::array<UINT, 1> strides = { { sizeof(Vertex) } };
    std::array<UINT, 1> offsets = { { 0 } };
    this->mpDeferedContext->IASetVertexBuffers(
      0,
      static_cast<UINT>(ppVertexBuffers.size()),
      ppVertexBuffers.data(),
      strides.data(),
      offsets.data());
    this->mpDeferedContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    this->mpDeferedContext->IASetInputLayout(this->mpInputLayout.Get());
    //頂点シェーダ
    this->mpDeferedContext->VSSetShader(this->mpVertexShader.Get(), nullptr, 0);
    //実行
    this->mpDeferedContext->Draw(3, 0);
    //ここまでのコマンドを記録する
    this->mpDeferedContext->FinishCommandList(true, this->mpCommandLists.GetAddressOf());

    this->mEnableExecuting = true;
    this->mEnableExecuteFlag.notify_one();
  }
});

記録したコマンドの実行はID3D11DeviceContext::ExecuteCommandListをイミディエイトコンテキストから呼び出します。 イミディエイトコンテキスト場合は第2引数でID3D11DeviceContext::ExecuteCommandListを呼び出す前の設定状況を復元するかを指定することが出来ます。

// Scene::onRender関数
std::unique_lock<std::mutex> lock(this->mMutex);
while (!this->mEnableExecuting) {
  this->mEnableExecuteFlag.wait(lock);
}
//記録したコマンドの実行
this->mpImmediateContext->ExecuteCommandList(this->mpCommandLists.Get(), false);
this->mEnableExecuting = false;

ディファードコンテキストについては以上になります。

まとめ

今回のパートではディファードコンテキストについてみていきました。 作成や使い方は簡単ですが、問題はどのように使うのかが一番の悩みとなるでしょう。 マルチスレッドプログラミングを行う必要があるので実装はイミディエイトコンテキストだけを使っている時と比べて設計的な意味で難しくなってしまいます。

また、Direct3D12ではID3D11DeviceContextの周りが大きく変わり、ID3D11DeviceContextが裏で行っていたことをこちらで制御する必要がでてきます。 実装の自由度が高くなった反面、扱いが難しくなっています。 効率的な実装にはGPUやマルチスレッドプログラミングへの知識もかなり重要となり、ますますグラフィックスAPIの敷居が高くなってしまいました。

単にCGを触りたいとなるとゲームエンジンを利用するの方が細かい部分を気にせず実装できるので、それらを利用するのがいいと思います。

<前 トップ 次>