2025年6月9日月曜日

テクスチャの無制限配列

これまで、シェーダーによる描画では、テクスチャを1枚ずつ個別に指定していた。

文字列描画の際、スプライト描画の要領で使うテクスチャは1つで、描画は数百文字同時に一括指定などで効率的に描画していた。
ただし、テクスチャ自体が複数枚で、それを一括で描画する仕組みを用意していなかったためテクスチャ毎に描画命令を呼び出していた。

これを改善しようと思う。


テクスチャの無制限配列指定


HLSL変数宣言


Texture2D Tex[] : register(t0) ;
こんな感じでテクスチャを指定して、Tex[n]のように使いたい。
定義自体は可能で、Shader Model 5.1以降で使える模様。


RootSignature宣言


RootSignatureのテクスチャ指定部分は下記のようにする

DescriptorTable( SRV(t0, flags=DESCRIPTORS_VOLATILE, numDescriptors=unbounded)...

flagsにはDATA_STATICか、DATA_STATIC_WHILE_SET_AT_EXECUTEを指定していたけど、無制限の場合はDESCRIPTORS_VOLATILEになるらしい。
numDescriptorsにはunboundedを指定する。

問題はデスクリプタヒープをどう指定するか。
現状初期化時に、CBV、SRV、UAV用と、サンプラ用の大きなヒープで作成しており、描画毎に同じヒープをCommandList->SetDescriptorHeapsに指定している。

テクスチャ部分はTex[0]~描画指定数分利用することになるが、SetDescriptorHeapsに配列順になっているデスクリプタヒープを渡す必要がある。
今までのデスクリプタヒープを静的なものとすると、今度は動的なデスクリプタヒープを用意することになる。


Dynamic Descriptor Heap


静的デスクリプタヒープと同様にな動的デスクリプタヒープを初期化時に用意する。
今まで渡していた静的デスクリプタヒープに加えて、動的デスクリプタヒープをSetDescriptorHeapsに渡そうとしたがエラーになる。同じタイプ(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)のものを渡せないらしく、動的デスクリプタヒープを渡したい場合は静的デスクリプタヒープは渡せなくなる。
静的デスクリプタヒープに加えて、配列分のデータを動的デスクリプタヒープで渡そうとしたけど、サンプラ以外はすべて動的デスクリプタヒープで渡すことになる。
そのため、静的デスクリプタヒープの初期化でFlagsに指定していたD3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLEをD3D12_DESCRIPTOR_HEAP_FLAG_NONEに変更して、動的デスクリプタヒープのFlagsにD3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLEを指定する。

実際描画する際、静的デスクリプタヒープと動的デスクリプタヒープのD3D12_CPU_DESCRIPTOR_HANDLEを用意して、Device->CopyDescriptorsSimple関数でコピーする。静的デスクリプタヒープでは空いている部分をアサインして、利用が終わったら返却しているため、使うリソースの順番はばらばらだったけど、動的デスクリプタヒープを使って、順番を指定できるようになったため、配列で使用できることになる。



定数バッファ(CB)や、構造化バッファ(SB)はサイズを増やせば複数描画分をまとめて渡すことが元々出来た。テクスチャも動的デスクリプタヒープを利用することで、使用するテクスチャを順番に並べることができるようになる。

これで複数枚のテクスチャ全部描画する際、複数回描画命令を実行していたのが1回の描画命令でまとめて描画が可能になった。
ある状況下で比べてみるとCPU使用率が約半分になって、GPU使用率はほぼ変わらずだった。


Barrier

以前拡張バリアの対応を行った。

それまで結構バリア処理で悩んでいて複雑になったになってきたので、修正を機に全部リセットして作り直した。
その際、通常のバリア処理はなくして拡張バリアのみにし、その結果Agility SDKに対応している環境でしか動かなくなった。

というのが今までの状況。

そのうち一般公開されるだろうと思いながら待っていたけど一向にされない。
NvidiaのGPU環境では問題なく動く。
AMDのGPU環境では、Agility SDK対応ドライバを選択して入れてあれば動く。
IntelのGPU環境では、ドライバは対応されているようなことが書いてあるが、少なくとも自分の環境ではそのドライバをインストールに失敗し、拡張バリアはサポートされない状態。

個人的には問題ないんだけど、諸事情でいろいろな環境で動かす必要が出てきたのでAgility SDKなしでも動くようにする。


通常のバリア処理



拡張バリアサポートチェック



起動時に拡張バリアのサポートチェックをして、対応してなかった場合はそこで処理をストップしていた。通常バリアから拡張バリアに完全に切り替えたのでこれで問題なかったが、
これを拡張バリアに対応していない場合でも処理を継続し、両方の環境で動くようにする。

ID3D12Device10::CheckFeatureSupport関数でD3D12_FEATURE_D3D12_OPTIONS12をチェック。
D3D12_FEATURE_DATA_D3D12_OPTIONS12.EnhancedBarriersSupportedの値を保持して、処理を切り替えるようにする。


リソース作成



ID3D12Device10::CreatePlacedResource2でリソースを作っていたけど、通常バリアのモードの場合はID3D12Device::CreatePlacedResourceに切り替える。
D3D12_RESOURCE_DESC1とD3D12_BARRIER_LAYOUTが、D3D12_RESOURCE_DESCとD3D12_RESOURCE_STATESになる。

今のライブラリはCBV、バッファ利用、SRV、テクスチャ利用に分かれているが、CBV、バッファの方は、特に問題なく用意できそう。
昔利用していた時は、リソース種類別に初期のステータスや実行コマンド毎にステータス遷移の制御を行っていたが、バッファに関しては一切の制御を止めて、D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESSに任せることで単純になった。通常のバリアでもそれは同様で、初期ステータスはD3D12_RESOURCE_STATE_COMMONのまま、状態遷移は何もしないようにした。

問題はレンダーターゲットと深度バッファで、このリソースにはD3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESSが指定できないため、自分で制御する必要がある。


コピー時



レンダリングして、結果をテクスチャにコピーするような場合、COPY_SOURCEに遷移してコピー後に、RENDER_TARGETやDEPTH_WRITEに戻しているが、通常バリアでも同様に遷移させる。コピー先に関してはCOPY_DESTで作成するようにしているため遷移不要で、コピー後も拡張バリアではALLOW_SIMULTANEOUS_ACCESSのおかげで何もしなくて済んでいた。
通常バリアではALLOW_SIMULTANEOUS_ACCESSを指定していても多少制御が必要で、コピー後に、ALL_SHADER_RESOURCEに遷移させないとデバッグレイヤにエラーが出力され続ける。


ミップマップ作成時



1.UAV付のテクスチャを用意して、画像をコピー。
2.各ミップマップをCSで縮小コピー。
3.UAV無しのスタティックテクスチャにコピー。
という手順でミップマップを作成している。
ミップマップ作成過程で拡張バリアを使っている箇所で、通常バリアを指定して実行してみるとデバッグレイヤにエラーが表示される。
試行錯誤の結果、通常バリアの場合1から2、2から3の間に、状態遷移を挟む必要があった。
1から2の間には、D3D12_RESOURCE_STATE_COPY_DESTから、D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCEの状態遷移を設定。
2から3の間には、D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCEから、D3D12_RESOURCE_STATE_COPY_SOURCEの状態遷移を設定。
拡張バリアの場合はD3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESSの指定で、かなり自動になったけど、通常バリアの場合は若干制御が増える。