Open1

D3D12_HEAP_TYPE_UPLOAD と D3D12_HEAP_TYPE_DEFAULT

LRIKILRIKI

これはなに?
拙作ゲームエンジン Lumino のコード内には、コメントとして書き散らされた技術調査記録が大量に残っています。リアルタイムグラフィックスを学ぶ方にとって有用な情報もある一方、整理する時間も取れないままコード上に残り続けており、無視できないノイズとなっています。そこでコード整理とラフな情報公開を目的として、Zenn の Scraps へ移動してみることにしました。

CPU-write-once, GPU-read-once とは

1つの大きなバッファに対して memcpy による書き込みや DrawXXXX() による読み取りが複数回発生してもよいが、
CPU 側は write する範囲、GPU 側は読み取る範囲が重なってはならない、ということ。(と解釈した)

結論としては今の DX12SingleFrameAllocator (DirectX-Graphics-Samples の LinearAllocator) の考え方で問題ない。

write 側が重なるのは論外としても、read 側が重なるとダメなのは、その時点で CPU までデータを取りに行くから、とのこと。

ダメなケース

これが響いてくるのは、CPU 側は1度の write でメッシュを作り、コマンドリストでそのメッシュを繰り返し描画する、という場合。

Sprite は特にそうだが Lumino の場合は Box や Sphere 等簡易的なプリミティブの描画・デバッグ描画など、
これらはシーンレンダリングの最初に頂点バッファを動的に構築してから描画を行う。
この頂点バッファは通常の描画の他、シャドウマップの生成など複数の描画パスで使われる。

頂点バッファの構築と描画が 1:1 であれば DEFAULT なバッファにデータを送信するのと差は無いのだが、
複数回描画が発生するとその分だけ GPU 側はデータをプルしようとする。

よさそうな方針

前述の質問には「CPU-write-once, GPU-read-once にならない場合、常に UPLOAD から DEFAULT へのコピーを実装するのはやりすぎか?」
というのがあるが、回答は「やりすぎではない。ただしあなたのアプリの目的に応じてプロファイリングしてね」とのこと。

実際のところ描画のたびに本当に GPU からのプルが走るのかは、GPU やドライバの実装によるものと考えられる。
少なくとも GPU のキャッシュが無効の場合は上記のように動くのが一般的。

ちなみに DirectX12 には Microsoft によるソフトウェアドライバの実装もあるので、実装は本当に様々。
なので MSDN にはあまり詳しく書かれていないのだろうか?

とりあえず Lumino だと、次のようなのがよさそう。

  • 3Dシーンの描画では UPLOAD から DEFAULT へのコピー
  • GUI (2D) の描画では UPLOAD だけでもかまわない

でも区別するの大変なので、当面は UPLOAD から DEFAULT へのコピーを基本にしてみる。

ちなみに

UPLOAD は使い勝手いいので全部 UPLOAD にしてしまえ、は論外。実行速度を考えないサンプルならいいけど…。

3Dモデルでこれをやると悲惨な描画速度になりそう。