DirectX11でのDDSロードの記事は
こちらです。
以前、
GetCopyableFootprintsの記事では手抜きで生成していたテクスチャですが、今回はミップマップやキューブマップも考慮して真面目に生成してみます。
ところで、テクスチャのアップロードは既にMicrosoftのD3DX12.hのUpdateSubresources関数があります。D3DX12.hが優れている所としては、必要なリソースはUpdateSubresourcesの引数を通してのみ取得しており、透明であること、マルチスレッド化が容易であることが挙げられます。反面、やや使いにくい点としては、D3DX12.hはDX12特有の概念であるリソースバリアやアップロードバッファの管理を呼び出し側に委ねています。使用者はDX12のアーキテクチャを理解した上で使わなければいけないので、手軽に使えるものではないです。また、DX12を隠蔽するものではないので、マルチプラットフォーム化を目指した設計の中では使いにくそうです。
今回はD3DX12.hを使用しないで実装します。
DDSのロード
DDSファイルをメモリ上にまるごとロードして解析します。
以前やったDX11版とほぼ同じです。
フォーマット、幅、高さ、配列数、ミップマップ、キューブマップかどうかの情報を取得してD3D12_RESOURCE_DESCを作り、CreateCommittedResourceを呼びます。また、サブリソース毎のピクセルデータの先頭位置とサイズをAFTexSubresourceData配列にまとめておきます。
テクスチャメモリへの転送
ここの処理の詳細は、
以前やったGetCopyableFootprintsの記事を見てもらうと分かり易いと思います。違いは、今回の実装はミップマップやキューブマップもサポートします。
GetCopyableFootprintsでアップロードバッファの大きさと各サブリソースの配置方法を取得します。forループ内ではサブリソースをアップロードバッファに配置し、CopyTextureRegionで転送させます。リソースバリアはCopyTextureRegionのforループの前後で全てのサブリソースに対して一括で発行します。その為にD3D12_RESOURCE_BARRIERを配列で作っておきます。forループ内でサブリソース毎に個別にResourceBarrierを呼び出しても動きますが、マイクロソフトの開発者の動画
https://youtu.be/Db2TaG49SRgによるとまとめて一回呼ぶほうが良いとのことです。ここは、
日本語で要約した記事も参考にしてください。
コマンドリスト実行前のID3D12Resourceの保護
最後に、アップロードバッファからテクスチャへの転送を行うコマンドリストの実行が終わるまで、転送元であるアップロードバッファと転送先であるテクスチャの双方が間違って解放されないように保護しておく必要があります。
AddIntermediateCommandlistDependentResourceがそれをやっていて、何をするかというとAddRefしてコンテナに追加しておくだけで、コマンドリストの終了を確認したら全てReleaseします。
アップロードバッファが保護されるべきであるのは分かり易い所ですが、「生成したばかりのテクスチャが即不要になる」というのは無さそうで有り得るケースです。GPUがピクセルを転送しようとしたら転送先のテクスチャがなくなっていて不正なメモリアクセスというケースはDX12では発生します。コマンドリストから参照されている以上、テクスチャも忘れずに保護しておきます。
ちなみに、GitHubにあるマイクロソフトのDirectX-Graphics-Samplesではその場でコマンドリストを実行してフェンスで待つようになっています。その為アップロードバッファを生成した同じ関数内で使用済みになりその場で解放できるメリットがありますが、CPUはGPUがテクスチャを作る間ブロックされています。
まとめと課題
DX12のテクスチャの生成で分かり難いのは、アップロードバッファやリソースバリアなどの存在があると思いますが、この辺を隠蔽してかつてのDirectX SDKの感覚でテクスチャを生成できるようにしてみました。ただし今回の実装は、関数の引数が簡潔な反面、コマンドリストやコマンドリストから参照中のアップロードバッファの保持などを外部のモジュールに依存しているので、透明ではなく、そのままマルチスレッド化できないものになってしまいました。テクスチャのアップロード1つ取ってもどんな設計を選択するか、これもまたDX12時代にゲームエンジン開発者に委ねられた課題となりました。