CPUからGPUが処理した結果を取得する

PCのようにGPUがCPUからトポロジー的に遠い存在で、且つ以前に説明したように、レンダリング命令の発行とその実行に大きな時間差があるケースでは、CPUからGPUが処理した結果を取得しようとすると、CPUとGPUの同期が発生し、大きな性能低下を招くケースが考えられます。

たとえば、GPUが使用するRenderTargetをCPUから読み出そうとすれば、RenderTargetへの描画完了を待った後、CPUから見えるメモリ空間にRenderTargetの情報をコピーすることで読み出すことが出来ます。
しかしほとんどの場合は、CPU上の描画命令の発行とGPU上での実行には大きな時間差があり、描画直後にRenderTargetの読み出しを行うと、CPUはGPUの処理が終了するまで待たなければなりません。
また、数フレーム前に描画が完了したRenderTargetを読み出す場合でも、GPUが他の描画処理を実行中の場合は、コピー動作を行うことが出来ず、(正確に言うと、RenderTaregetの読み出しに由来するコピー要求は、他の一連の描画命令の間に挟まってGPUに転送されることとなり、GPUはコピー要求命令の直前までの描画処理を処理した後でなければ、このコピー要求命令の処理に取り掛かることが出来ません。)コピー対象のRenderTargetを遅延させても全く効果が得られないケースがほとんどです。

Occlusion Queryを使う

Occlusion QueryやTimestampなどはGPUの情報を非同期的に手に入れる数少ない方法です。しかし、GPU上で生成された任意の値を転送するようには作られていません。
それでも、TimestampはともかくOcclusion Queryは、レンダリングを制御することで値のコントロールが可能なので、少ない情報ながらGPUからCPUに情報を伝達することは可能です。
OcclusionQueryで読み出すことが出来るのは、DX9の場合DWORDなので32bit値になります。ただしOcclusion Queryの値を増加させるためには、実際にPixelを描画しなくてはならないので、極端に大きな値まで使うと、この処理自身が長いレンダリング時間を使ってしまいます。ですから実際に使える値の範囲はもっと狭いです。

たとえばCPU側のでの制御が必要なTone mappingを行いたい場合に、RenderTargetの最大輝度と平均輝度情報を知りたいとします。
GPU側でRenderTargetの最大輝度と平均輝度を計算する処理については割愛します。これ等の値を1x2pixelのRenderTargetに格納できたとします。このような小さなターゲットでもCPU側から読み出せばCPU-GPU syncを招き、大きな性能低下につながる可能性が高いです。そのため、これ等の値をOcclusion Queryで読み出すことを考えてみます。輝度情報の分解能は1024程度あれば十分だと思います。あとは、時間経過に基づく補完などで、値の連続性を補うこととします。

  1. 1024×1のRenderTargetを準備します。
  2. VertexShaderで対象の値を読み出し、値を0~1に正規化し、これによってFullScreenQuadの描画プリミティブにX方向のスケーリングをかけます。
    プリミティブのX方向の大きさのみが問題になるので、Xnew = X * Val;を行います。
  3. PixelShaderは何もせずに適当なPixelを出力します。
  4. この描画の出力ピクセル数をOcclusionQueryで取得し、正規化情報に基づき復元します。

浮動小数点の様に値の範囲が大きいものを伝達する場合は、浮動小数点の指数部と仮数部に値を分けて2度のOcclusionQueryに分ければ、広範囲な値を伝達することが可能です。また、32bitの値を完全な形で伝えたい場合は、8bit(256)の分解能のQuery4回に分けることで、伝達が可能だと思います。
その他の場合でも、PixelShaderを用いて、単純な値の比較や、参照値をテクスチャからサンプリングしてXORを行うなど、どれも限定的な方法ですが、多少の情報ならば、GPUからCPUにOcclusionQueryを用いて伝えることが出来ます。

一点だけ注意が必要です

OcclusionQuery自体はGPU上で処理されるものなので、Queryの発行の直後に値の読み出しを行えば、これもCPU-GPU syncを招き、RenderTaregetの読み出し等と同じ状況が発生します。ただしQueryの場合は、処理自体がGPU上で完了すると、GPUからCPUに結果の転送が行われ、ドライバーがその値を保持します。既にドライバ側に転送されているQueryの値は、値を取得するAPIで即時に値が取得でき、CPU,GPU共にストールが発生しません。具体的に何フレーム前の値ならば即時に取得できるかは、そのPCの構成と設定に依存します。Frame Latencyがデフォルトの3のままであれば、3フレーム以上前のQueryならば即時に読み出しが出来るはずです。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中