Direct3D12アプリを書いてみる(3)

Direct3D12は、描画コマンドをコマンドリストにバッファリングして、まとめて実行するという形式となります。 毎フレーム実行する処理を簡易的に書くと以下のようになります。

{
    // コマンドリストの開始
    commandAllocator->Reset();
    commandList->Reset(commandAllocator.Get(), pipelineState.Get());

    // 様々な描画コマンドを実行して、コマンドリストを作成
    ...

    // バッファリングされたコマンドリストを実行
    commandList->Close();
    ID3D12CommandList* commandLists[] = { commandList.Get() };
    commandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);

    // スワップチェーンの表示
    swapChain->Present(1, 0);
}

ExecuteCommandListsでGPUが描画開始するのか、1フレーム分バッファリングしてからPresentでまとめて描画開始するのかは、 GPUのアーキテクチャやドライバの実装方針によって決まります。例えば、タイルベースレンダリングの場合は、 1フレームバッファリングしてから描画開始する必要があります。

次に、Presentに関してです。このAPIは抽象的で分かりにくいと思いますが、重要なAPIです。

内部的にどんな処理が行われているかというと、以下のような処理になります。

  • 前フレームの描画完了を待つ(WaitDrawDone)
  • 描画完了したフレームバッファを次のVSyncのタイミングでフロントバッファと入れ替える(SwapBuffer)
  • 第1引数で指定された回数分、VSyncを待つ(WaitVSync)

これも基本的にドライバ依存の処理なので、Presentの中で処理しないこともあります。

なお、引数によってはVSyncを待たないという設定もできますが、ティアリングを起こさずに正しく描画するためには、 以下の2つの設定しか選択肢はありません。

  • ダブルバッファでVSyncを待つ
  • トリプルバッファでVSyncを待たない

つまり、2枚のバッファで描画するにはVSyncを待つことが必要であり、VSyncを待たないためにはバッファが3枚必要となります。

VSyncは正確に説明すると長くなるので省略しますが、簡単に言うと、ディスプレイが指定されたフレームバッファを表示し終わり、 次のフレームバッファの表示を開始する前のタイミングとなります。ディスプレイにフレームバッファの切り替えを要求した場合に、 実際に切り替えが行われるのがVSyncのタイミングとなります。

ただ、Windowsの場合は、ウィンドウモードとフルスクリーンモードがありますが、ウィンドウモードの場合は、VSyncを待たない という設定にしたとしても、画面全体ではVSyncを待つようになっているので、正しく動作しないようです。 フルスクリーンモードの場合は、VSyncを待たない設定にすることで、描画ループを60fps以上に変更するができます。

まだ説明途中ですが、そろそろデバッグ表示機能が欲しくなってきたので、次に、そちらを実装したいと思います。