2022年11月28日月曜日

定数バッファ

定数バッファでハマったのでそれについてのメモ。

定数バッファの種類


フレーム依存定数バッファ


最初に自作ライブラリで用意したのはフレーム数に依存した定数バッファ。
ダブルバッファならフレーム数は2で、定数バッファのサイズを256の倍数に補正したサイズをフレーム数倍した領域をCreateCommittedResourceしたもの。

D3D12_HEAP_TYPE_UPLOADでCreateCommittedResourceしたリソースから、Mapでマッピングしたポインタをリソース破棄まで保持する。
データの書き換えは、このポインタに書き込む。

これは毎フレーム頻繁に値の書き換えがあるような用途で、用意した前半部分と後半部分のメモリをフレームの切り替えで書き込むオフセットを制御する。

読み取り専用定数バッファ


シェーダの内容によってはパラメータとして設定はするんだけど、値自体は最初に設定した以降変わらないというパターンのものもある。
フレーム数に依存した定数バッファでこのパターンのシェーダを使う場合、毎フレームの更新は不必要なため、最初に1フレームと、2フレーム目に同じデータを設定して使うことになる。また、大抵の場合データは書き換えの必要がない。
データの領域がフレーム数倍必要になり、初期化時にフレーム数分書き込む必要がある。

これはかなり無駄なので、このパターンに対応するため次に用意したのは読み取り専用の定数バッファ。フレーム数分定数バッファを用意するのはマイクロソフトのサンプルがそうなっていたからで、常にフレーム数分用意しないといけないと思いこんでいたけど、値を書き換えないなら1つ分でも行けるかなと思い試してみたところ問題なく動作した。

領域は必要な定数バッファサイズを256の倍数に補正したサイズのみ。
作成時にテクスチャと同様、データをGPUに書き込んでしまう。
D3D12_HEAP_TYPE_DEFAULTでCreateCommittedResourceしたリソースに、一時的に作成したD3D12_HEAP_TYPE_UPLOADのリソースを使って、CopyBufferRegionでコピーする。
Uploadでマッピングしたポインタは必要ないのでUnmapする。

32Bit定数バッファ


いろいろなサンプルを見ている中で見つけた定数バッファ。
ルート署名内に「RootConstants( num32BitConstants=1, b0)」と書けば、別途リソースの準備をする必要なく定数バッファを扱える。

テクスチャにミップマップを生成するコンピュートシェーダを用意したとき、別途定数バッファリソースの準備が不要なので使ってみたのがきっかけ。
前回のぼかしのシェーダで、パラメータをこの定数バッファで渡そうとしたものの、エラーがでてうまくいかず、読み取り専用定数バッファでとりあえず済ませた。

その時のエラーがこれ。
D3D12 ERROR: ID3D12CommandList::SetGraphicsRoot32BitConstants: No root signature has been set, so setting root constants doesn't make sense and is invalid. [ EXECUTION ERROR #709: SET_ROOT_CONSTANT_INVALID]

ルート署名に32Bit定数が存在しているのはPIXで確認できる。

PIXのパイプライン情報

このエラー文や32Bit定数バッファにまつわるキーワードで検索しまくったが解決につながる情報は得られなかった。
ネットのサンプルでも普通に使われているし、ミップマップのコンピュートシェーダでは普通に使えている。
コンピュートシェーダで使っているのはSetComputeRoot32BitConstantsで、今回使おうとしているのはSetGraphicsRoot32BitConstants


万策尽きて諦めかけてたとき、ふと思いついた。

BeginRenderという関数を用意して、レンダリング開始時にコマンドアロケータ、コマンドリストをリセットしているが、うまく行ってるミップマップのシェーダでは、このBeginRender後にSetComputeRoot32BitConstantsを呼び出している。対して、うまく行ってないシェーダではBeginRender前に呼び出していた。

コマンドリストのResetから、Closeの間で呼び出す必要があったというのが原因。
定数バッファのリソースは存在せず、シェーダに付属しているイメージなので常に書き込めるわけではないようだ。

もう1つ分かったことは、32Bit定数はフレーム依存定数バッファと同じ使い方ということ。
前回のぼかしのシェーダでは、用途としては読み取り専用定数バッファだったため、ぼかし強度を変更したタイミングで定数バッファを書き換え、毎フレームは書き換えてはいなかった。32Bit定数バッファでも同じように1回だけ書き込んで、次回以降書き込まずにいたら、結果が思い通りにならない。前回書き込んだ値は保持されず、毎フレーム書き込まないといけない模様(GPUによる?)

2023/8/27追記

この頃は定数バッファと同様にバッファが独立してあって、そこに値を設定する関数がSet[Graphics|Compute]Root32BitConstantsだと思っていた。
この関数はコマンドリストに追記する関数なので、当然コマンドリストが記録状態になっていないと使えない。


フレーム毎の書き込み


フレーム依存定数バッファ:不要 ※最初に全フレーム分書き込んだ場合


ダブルバッファの場合、前回、前々回のデータは保持している。
フレーム単位に参照する場所を切り替える関係上、更新を止めるなら全フレームのデータが同じになっていないと動作がおかしくなる。
実質フレーム毎の更新は必要。

読み取り専用定数バッファ:不可


作成時にGPU側にデータをコピーして、仕組み上書き換えできなくしているのでフレーム毎の書き込みはできない。

32Bit定数バッファ:必須


前回の値が保持されないため、毎フレーム書き込む必要がある。


データサイズ


フレーム依存定数バッファ:256byte単位×フレーム数


一定以上のデータ量でフレーム毎に書き換えが必要な場合に選択する。
また、フレーム専用の領域を用意しているので、32Bit定数よりも高速に動作(して欲しい)

読み取り専用定数バッファ:256byte単位


GPU側に書き込んでいるため、大きいデータでも高速に参照ができる。

32Bit定数バッファ:32Bit(4Byte)単位


少量のデータを渡す場合に適している。
コンピュートシェーダの場合は読み取り専用と同じような使い方もできる。

0 件のコメント:

コメントを投稿