中間レンダーターゲットや、深度バッファなどはある程度大きいデータなので、64KBというアライメントでもそんなに問題ない。
問題ありなのは定数バッファだ。
例えばメッシュのマテリアル情報は現在52バイト利用している。
今サンプルで使ってるデータは1メッシュに付き6つのマテリアル情報を持っていて、実質のデータサイズは52×6=312バイトなのに対して、64KBアライメントだとするとVRAMは64KB×6=384KBも消費していることになる。
今まで定数バッファのアライメントは256バイトだと思っていて、52バイトだけど256に補正して無駄な領域ができてるなぁと思っていたけど、そんなレベルではなかった。
256のアライメントは、CBVで連続したメモリを配置する際のアライメントだ。
リソースの管理の仕方が根本的に間違っているっぽい。
Bufferのアライメント
小さいテクスチャは条件を満たすと4KBアライメントにすることが出来る。
Bufferは無条件で64KBになってしまうと書かれていたが、本当なのか?
QueryVideoMemoryInfo関数を使うと、利用しているVRAMのサイズを調べることが出来るので実験をしてみた。
サイズを取得した後に、CreateHeapを呼んで、再度取得して増えたサイズを確認してみる。
サイズを64KBで作成すると、消費は64KB。
4KBで作成すると、消費は64KB。
8KBで作成すると、消費は64KB。ここまでは予想通り。
問題はこの後。
64KB+1バイトで作成すると、消費は64KB+4KB。
4KB+1バイトで作成すると、消費は4KB+4KB。
4KB-1バイトで作成すると、消費は4KB。
8KB+1バイトで作成すると、消費は8KB+4KB。
8KB-1バイトで作成すると、消費は8KB。
4KB、8KBピッタリの場合の挙動は謎だけど、それ以外は4KB単位で確保されるっぽい。これは確保されるサイズであってアライメントとは別なのか?
ということでマテリアル情報の定数バッファは384KBまでは使っておらず、4KB×6=24KBで済んでるということになる。でも大きい。
1つのHeapに複数の定数バッファをまとめる
1つのHeapに対して、複数のリソースを配置できるっぽいのでこれを利用すれば定数バッファの問題は解決するのではないか?
ある程度のサイズのHeapを確保して、CreatePlacedResourceで256バイトずつずらしてリソースを作成しようとしたところ、1つ目は問題ないが2つ目からエラーが発生する。
D3D12 ERROR: ID3D12Device::CreatePlacedResource: This resource cannot be created on this heap, due to unsatisfied resource alignment requirements. The resource must be aligned to 65536. The heap must also be aligned to a value greater to or equal than the resource. The heap is aligned to 65536, and the resource offset in the heap is 256. [ STATE_CREATION ERROR #638: CREATERESOURCEANDHEAP_INVALIDHEAPPROPERTIES]
ヒープのアライメントは64KBで、256のオフセット単位ではリソースは作れないらしい。
ダメ元でオフセットを4KBにしてみたがやっぱりダメ、CreateHeapのD3D12_HEAP_DESCのAlignmentに4KBを設定してもエラーになる。実質の消費サイズが4KBになっているにも関わらず、0以外を設定すると怒られる模様。
次に考えたのが、1つのHeapで複数のCBV。
これはすでに必要サイズを256単位に補正したサイズ×フレーム数分のサイズを確保したHeapに対してフレーム数分のCBVを用意して、フレーム毎に参照するCBVを変えて更新していくということをやっているので実績あり。
Heap管理
ヒープの管理戦略にはいくつか方法があるみたいだ。
そのなかに「何も管理しない」というものがあり、それが今の実装だった。
CreateCommittedResourceを使って、一切Heap管理をしない方法。
サンプルプログラムやプログラムの説明に用いる場合、かなり便利でその範囲ではまともに動作する。
これが製品レベルのプログラムになるとそうもいかないらしい。
Segregated free listsとかBuddy systemとか、名前がついたアルゴリズムがあるみたいだけど、今回実装したのはSegregated free listsに近いのか?
Heapの利用用途で作成時にフラグを指定するが、自分の使い方として下記の5つに分類した。
Upload Buffer
D3D12_HEAP_TYPE_UPLOADで作る。用途は定数バッファやDefaultバッファのコピー用。
Readback Buffer
D3D12_HEAP_TYPE_READBACKで作る。用途はComputeShaderの結果受け取り用。
Default Buffer
D3D12_HEAP_TYPE_DEFAULTで作る。用途はマテリアルなどの定数バッファ。
Default SRV
D3D12_HEAP_TYPE_DEFAULTで作る。用途はテクスチャや構造化バッファ。
Default RTDS
D3D12_HEAP_TYPE_DEFAULTで作る。用途はレンダーターゲット、深度バッファ。
確保したいサイズを64KB単位に補正して、ツリーを検索。見つからない場合は新しくHeapを確保する。このとき、大きいサイズが余っていても使わない。ぴったり一致するサイズが余ってる場合だけ利用する。
開放時は確保サイズをキーとしてツリーに戻す。
ツリーは64KB単位のサイズキーで構成され、似たようなサイズを取り扱うだろうという想定の割り切り仕様。分割もマージもしないので管理上の断片化はしない。
欠点はレアなサイズを利用すると、確保したまま再利用されなくなる。
シーンの切り替え処理の最後で使ってないHeapの開放など入れれば解決できそう。
4KBアライメントテクスチャ対応
前回の記事で扱った4KBアライメントのリソースは、1つのHeapに対して複数のリソースを載せる。
サイズをキー、値にオフセットをツリー管理する。指定サイズギリギリ上回るサイズから切り取って、サイズを減らし、オフセットをずらす。
開放時は、元のオフセットと確保サイズを元にマージできるノードを検索。見つかった場合はマージして再帰する。マージできない場合は単純に追加する。
定数バッファリソース対応
冒頭の定数バッファ用のリソース確保の仕組み。
64KBのHeapを確保&256分割して、1Heapに付き256スロット用意する。
スロット数をキー、値にオフセットをツリー管理する。
確保する際は256バイト単位に補正して、256で割った数が必要スロット数。
必要なスロット数をギリギリ上回るノードを探して、スロット数を減らして、オフセットをずらす。
リソース自体は同じものを参照して、オフセットを元にCBVを用意する。
開放時は、元のオフセットと確保スロット数を元にマージできるノードを検索。見つかった場合はマージして再帰する。マージできない場合は単純に追加する。
Aliasingバリア
マイクロソフトのリファレンスにはCreatePlacedResourceで出来たリソースは非アクティブ状態なので、Aliasingバリアによってアクティブ化する必要があると書いてある。
今回の対応により、すべてのCreateCommittedResourceをCreatePlacedResourceに置き換えたが、Aliasingバリアは未使用。DebugLayerにも何も表示されていない。
最初はリソースを重ねなければ、バリア不要かと思っていたけど、マイクロソフトのSmallResourceのサンプルでは、重ならない小さな4KBアライメントテクスチャをAliasingバリアしている。サンプルのバリア部分をコメントにしても問題なく動く。
具体的な問題が出るまでは、このまま行ってみる。
0 件のコメント:
コメントを投稿