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以上に変更するができます。
まだ説明途中ですが、そろそろデバッグ表示機能が欲しくなってきたので、次に、そちらを実装したいと思います。