2023年3月5日日曜日

リソース管理リファクタリング

今まで何回かリソースに追加、修正を加えてきていろいろなリソースが増えてきた。

なにか新しくリソースを増やす場合、リソース自体の内部構造、横断的に利用している部分の修正など複雑になってしまっている。

一旦データをすべて整理して、もっと汎用的にあまりなんのリソースかを意識しなくてもいいように作れないかを考えた。


リソースのデータの構造


CreateCommittedResourceで確保する実体のバッファをベースに、そのバッファをどう参照するかの各種Viewで構成されている。
1つだけではなくフレーム数分用意する場合もある。
各種Viewは今のところ下記の6つ。

読み込み

・Shader Resource View(SRV)
・Constant Buffer View(CBV)
・SaMPler(SMP)

書き込み

・Unordered Access View(UAV)
・Render Target View(RTV)
・Depth Stencil View(DSV)

SRV、CBV、SMPは読み込み用で、UAV、RTV、DSVは書き込み用のView。
例えば中間レンダーターゲットは1つのバッファに対して2つのViewを持ち、レンダリングするときにはRTV、テクスチャとして参照するときにはSRVを利用する。

Viewはスロットとして、読み込み用、書き込み用の2スロット持てるようにする。
(今までは1スロットのみで管理していて、拡張データに別スロットを持つ形で対応していた。そのため、使う場面でリソースのタイプをみてどっちのスロットを使うのかなどの分岐が生まれて複雑になる要因だった)

リソースを使う場面で、I/Oを指定出来るようにしてどちらのスロットを参照するか汎用的に処理するようにした。
これにより2つのスロット(View)を持つ中間レンダーターゲットの場合でもリソースタイプを参照せずに、引数のI/Oでどちらのスロットを利用するのか判断ができシンプルになった。
リソースタイプが増えても、この部分にソースの修正は不要になる。

フレーム依存のリソースの場合、バッファをフレーム数分持つことになる。
これも可変配列で管理して汎用的にした。
定数バッファと読み取り専用の定数バッファがいままではリソースタイプとして分かれ個別に管理していた。定数バッファはフレーム依存でスロットを2つ持つ。読み取り専用の方はデータが書き換わらないのでスロットは1つのみ。
フレーム依存のフラグを持たせて、依存するリソースの場合内部的に2つバッファがあるので、今のバックバッファインデックスに従って参照するバッファを制御する。ブレームに依存しない場合は、インデックス0を参照する。

上記のような修正の結果、リソース対応は大幅に減って下記になった。

・頂点バッファ
・インデックスバッファ
・構造化バッファ
・定数バッファ
・テクスチャ
・レンダーターゲット
・中間レンダーターゲット
・深度バッファ
・サンプラ



フレーム依存リソース


今回の修正で同じリソースタイプのフレーム依存、独立バージョンを簡単に用意することが出来るようになった。
前からいまいち納得が出来ていないフレーム依存のリソースを本当に2つ用意しないと行けないのか?1つだけにして実行するとどうなるのか試してみた。
定数バッファ(CBV)、構造化バッファ(SRV)を1つ分に減らして実行してみると、結果は何も変わらない。

両方ともD3D12_HEAP_TYPE_UPLOADで、mapしっぱなしのリソースで、マップで得られたポインタにデータを毎フレーム書き込む。
書き込んだ時点でGPUにUploadされて、その後呼び出すShaderで値が参照できているとすれば、フレーム毎にバッファを用意する必要はないはず。


前回やった視錐台カリングで、よく見るとオブジェクトが消えるとき別のオブジェクトがちらつく現象が起こっていた。

ログを出しながら確認したり色々していく内、PIXで複数フレームキャプチャして原因を突き止めた。

フレーム0

フレーム1

フレーム2

フレーム1で書かれるべきマップのオブジェクトが消えてしまっている。
構造化バッファで渡しているWorld Matrixのデータがおかしくなっている可能性があるので確認してみる。

フレーム0、1のWorld Matrix

フレーム2のWorld Matrix

同じ3つのマップオブジェクトをX座標をずらして表示している。
World Matrixのデータはフレーム2の時点で0番目のオブジェクトがカリングされて、1番目と2番目のデータがずれて、0,1の部分に反映されている。

フレーム0のDrawコール

フレーム1,2のDrawコール

Drawコールを見てみると、フレーム0がインスタンス数3で、フレーム1,2がインスタンス数が2で指定されている。
ということはWorld Matrixがまだ反映されていないのでフレーム1はインスタンス数3で呼ぶ必要があったと言うことだ。

CPU側の判定としてはフレーム1の時点でオブジェクト0番がカリング対象になって、オブジェクト1、2番だけが表示される状態になった。
構造化バッファに2つ分書き込みを行ったが実際にはまだ反映はされておらず、Drawコールのインスタンス数だけが最新状態で指定してしまっている。

修正は表示されるオブジェクト数自体もフレーム依存データのようにフレーム数分保持するようにして、ワンテンポ遅れさせることで解決した。


このことからUploadでは即座に反映されず次のフレームになることは確認できたが、1つ分のバッファに減らしても動作が変わらない疑問が残る。
テストプログラムで1フレームに殆ど時間が掛かっていないから、問題が起きていないだけなのか?上記の理屈からすると、1つしかバッファがないので次フレームで 実際にバッファを参照する前に書き換えてしまうと結果がおかしくなる。
Uploadする際のCPU側のメモリと、GPU側のメモリの2つがあるので今はその反映タイミングに助けられているだけなのかと推測する。


0 件のコメント:

コメントを投稿