ゲーム開発の備忘録

趣味のゲーム開発でのノウハウや、技術的に嵌ったポイントを忘れないように書き記しておくブログです。

DirectX12でのコンピュートシェーダを利用したテクスチャレンダリングについてのあれこれ

はじめに

 ここ最近、DirectX12のコンピュートシェーダを利用したテクスチャレンダリング周りで悪戦苦闘していたので、備忘の目的で記事を作っておきます。

コンピュートシェーダ上でのテクスチャ参照について

 テクスチャレンダリングを行い、その結果にコンピュートシェーダでポストエフェクトをかけて、その結果を最終的に画面出力することを考えます。この時、モザイク処理など、あるピクセルの周囲のピクセルを参照する場合は、入力テクスチャと出力テクスチャを異なるものにしないと正しい結果を得られません。ここで問題になるのは、ピクセル情報を更新しない入力テクスチャをTexture2Dにするのか、それともRWTexture2Dにするのかです。

 一見、参照するだけならわざわざRWTexture2Dにする必要はないのではと考えるかもしれませんが、コンピュートシェーダ内ではTexture2Dのテクスチャサンプリングが実行できないため、参照用途であってもRWTexture2Dとすることが必須です。コンピュートシェーダ内でTexture2DやSamplerStateの宣言ができるので、そのまま利用できるのではと考えてしまうかもしれませんが、サンプリングしようとするとエラーになります。なお、必然的にRWTexture2Dを2枚使う(レジスタu0、u1)ことになります。アプリケーション側で入力テクスチャと出力テクスチャを連続した領域に確保しなければならないので注意しましょう。

GPGPU用のコマンドリストを用いたテクスチャレンダリングについて

 描画命令用のコマンドリスト(D3D12_COMMAND_LIST_TYPE_DIRECT)でコンピュートシェーダのディスパッチ命令を実行できます。コンピュートパイプラインステート、コンピュートルートシグネチャは描画命令用のコマンドリストでも設定できるのです。

 テクスチャレンダリング用途以外でも言えることですが、コンピュートシェーダはGPGPU用のコマンドリスト(D3D12_COMMAND_LIST_TYPE_COMPUTE)を用意して、描画命令とは別に実行することでパフォーマンスが高くなると言われています。しかし、テクスチャレンダリングをコンピュートシェーダで行う際に、GPGPU用にコマンドリストを分けて実行する場合はエンジンの大規模な変更が必要になるため、エンジン開発初期から実装を組み入れておかなければなりません。

 なぜ、エンジンの大規模な変更が必要になるのでしょうか?その理由は、コンピュートシェーダ実行タイミングの違いにあります。以下の順番で描画することを考えてみましょう。

1. 入力テクスチャのレンダリングのためのコマンド発行
2. コンピュートシェーダでのポストエフェクト実施のためのコマンド発行
3. UIなど、上位の描画レイヤでのレンダリングのためのコマンド発行
4. ID3D12CommandQueue#ExecuteCommandLists()でのコマンド実行による、レンダリングの実際の反映

 描画命令用のコマンドリストでコンピュートシェーダを実行する場合、ID3D12CommandQueue#ExecuteCommandLists()でコマンドを順次実行できるため、上記の順番そのままで正しく描画を完了できます。しかし、GPGPU用のコマンドリストでコンピュートシェーダを実行する場合、描画命令用のコマンドリストとは分けて実行しなければなりません。そのため、上記手順2のタイミングでコンピュートシェーダを実行しても、前段の手順1が実際にはまだ未実行であるため、入力テクスチャが準備できていないことになります。正しく実行するには、描画命令用のコマンドリストを複数用意し、手順1,手順4それぞれのタイミングで実行しなければなりません。DirectX12でエンジンを作っている方ならなんとなく分かるかもしれませんが、ほぼエンジンができた後にこのための変更を行うのはかなり厳しいです……。GPGPU用のコマンドリストを使用したい場合は、エンジン開発初期に対応させるようにしましょう。