Wednesday, July 20, 2016

[DX12] 実例で学ぶ ID3D12Fence 基本編

DX11以前やOpenGLでは、基本的にCPUとGPU間でのいわゆるData Raceが生じないようにAPIレベルである程度保証してくれますが、DX12では同期を取る事は開発者に委ねられることになります。

そこで、GPUがどこまで実行したのか調べるのに使うフェンスですが、ID3D12Fence、HANDLE、ID3D12CommandQueue、それにUINT64型の変数と登場するオブジェクトが多くて分かり難いです。

結局、大雑把に何をするか書きだすと以下の2点に絞られます。

  • GPUが実行位置に合わせてフェンスの値をインクリメント
  • CPUはフェンスが期待した値になるまで待機する

GPUがフェンスの値をインクリメントと書きましたが、コマンドをGPUに送るのはCPUなのでその値もCPUから指定します。以下のようにコマンドリストの実行に混じって呼び出しているSignalがそれです。

A、B、Cがそれぞれ描画命令等が記録されたID3D12CommandListです。CPUから見て、フェンスの値が1になっていたらGPUでコマンドリストAの実行が終了しています。同様に2ならBが、3ならCの終了が保証されます。

GPUが設定したフェンスの値が指定値以上になるまで待つ関数はこんな感じです。

ここでvalueに1を指定した場合、Aの実行が完了していなければAが終わるまで待機します。Aの実行が終わっている状態、すなわちフェンスの値が1以上になったらすぐに戻ってきます。「1」ではなく「1以上」であることは重要です。Aが終了したかを知りたいためで、Bの実行終了時の「2」であってもやはりすぐ戻ってこなければいけないからです。

また、Signal呼び出しは常にvalueをインクリメントしながら行うのが定石で、その値を管理するためUINT64型の変数を用意することになります。

同期に使うHANDLEは使い捨てにしています。生成と破棄のオーバーヘッドが気になるなら保持しておく方法もありますが、何か意味のある状態をグローバルに保持するかのように見えるため可読性が落ちる気がします。個人的には使い捨てがおすすめです。
DirectX-Graphics-Samplesでは保持するように書かれています。

上のWaitFenceValue関数は必要最低限のコードでしたが、通常はGetCompletedValueを組み合わせて使うようです。待機する必要が無い場合を切り分けると少しパフォーマンスが上がる為と思われます。

No comments:

Post a Comment