2023年5月1日月曜日

DirectX12 Multithread対応の続き

以前マルチスレッド対応をしたが、今回はその続き。

マルチスレッドに対応する際、参考にしたのがマイクロソフトのサンプルだけど、このサンプルって実は特殊なのでは無いかと思った。

複数のコマンドリストを用意して、それぞれを担当するスレッドがコマンドリストにコマンドを追加していく。
これはある1シーンのためにカスタマイズされた専用のプログラムで使う分には問題ないし効果も高そう。

ただ、自分が作っている汎用的な3Dエンジンの場合、ちょっと無理があるかもしれないと思い始めた。


今までは1つのコマンドリストにシーケンシャルにコマンドを追加していた。
必要なパラメータを順番に追加していけば描画はされた。

新しくマルチスレッド対応になった場合、シングルスレッドで追加するか、マルチスレッドで追加するかを切り替えられるようにした。シングルスレッドの場合は今までと同じ使用感。
マルチスレッドにした場合、最初のコマンドリストだけで実行するコマンド、全部のコマンドリストで同じ実行が必要なコマンド、パラレルに設定できるコマンドなど呼び側でかなりいろいろ考慮が必要で、投入コマンドがかなり多くないと寧ろ遅くなる結果になっていた。


DX12 Do's And Don'ts


nVIDIAのサイトでこんなドキュメントを見つけた。
全てきちんと理解できたらかなりクオリティが高くなりそうだけど、読み取れた一部でも改良していきたい。


ExecuteCommandListsとFence


以前コマンドリストのラッパを刷新した際、それまで対応してなかったReadbackも対応した。ExecuteCommandListsを呼び出したら、その後メインスレッドorクロージャスレッドでその実行完了を待つ。Readbackデータ読み取りたい場合は、メインスレッドで待ってその後参照する。読み取り不要な場合はクロージャスレッドで終わった段階で各種コマンドの開放を行うようにしていた。この時、フェンスを立てることになるが、このフェンスがパフォーマンスに多大な影響を与えてしまう。
PIXで見るとExecuteCommandLists単位で、隙間が開いている。
これが普通なのかと思っていたけど、全くダメな作りだった。


PSOの切り替え


シェーダが同じものをまとめて描画し、ExecuteCommandListsを呼び出していた。
それしかできないと思っていた。
勘違いしていたのが、Reset関数にPSOを指定するからなんだけど、Resetのタイミングで指定するのだから切り替えにはまたResetする必要があるのだろうと。だが実は違った。
SetPipelineStateという関数があり途中から設定できる。設定できるだけでなく切り替えも出来る。
これはnVIDIAのドキュメントに書いてあるわけではないけど、読んでいるうちにそういう事が出来るのだろうと、リファレンスを調べたらあった。
SetPipelineState、Set[Graphics|Compute]RootSignatureを呼べば途中でシェーダの切り替えも出来ることを知った。


コマンドリストは15~30


コマンドリストは15~30以下と書かれていて驚いた。
これをみて今までの考え方が根本から間違っているのではないかと思った。
シェーダを切り替える度にコマンドリストも切り替えて行くといいのではないか?
同時に複数のコマンドリストを扱うのではなく、常に1つのコマンドリストに対して追加し、切り替えのタイミングで次のコマンドリストに移る。ある程度コマンドリストが溜まったらExecuteCommandListsを実行する。


ドキュメントではすべてのコマンドリストを用意してから最後にExecuteCommandListsというのもだめパターンに書かれている。5回~10回に分けて、CPUとGPUが並行処理していく状態を作るのが望ましい。


マルチスレッド対応した際、コマンドリストに投入したいデータが出揃ったタイミングで、一斉にスレッドに追加させていた。
これもマイクロソフトのマルチスレッドのサンプルを最初に見てしまった弊害。
追加する度に別スレッドで並列にコマンドリストにも追加するように作り変えた。
全部のコマンドを作っていざ実行となったときに、今まではこのタイミングから追加し始めていたのが、裏で作るのと同時にコマンドリストに追加をしているため、待ち時間が短縮できた。

描画全体の時間(デバッグ)
揃ってからスレッド実行 :0.0958秒
追加と同時にスレッド実行:0.0707秒

描画全体の時間(リリース)
揃ってからスレッド実行 :0.0642秒
追加と同時にスレッド実行:0.0398秒

ただ残念なことに、マルチスレッドにせずメインスレッドで直接コマンドリストに追加した場合は下記となり、マルチスレッドが意味ないと思わせる結果となった。
デバッグ:0.0618
リリース:0.0289
コマンドリストに追加する重たい処理が少ないケースだからか?
改良版はそこまでシングルスレッドに負けてないので、マルチスレッドが勝つケースもあると信じる。


比較


シェーダを切り替える度にExecuteCommandListsを呼び出していたのを、まとめて1回で実行したものの比較。


シェーダ切り替え毎にExecuteCommandLists実行

Z Pre-passからDeferredRenderingまでをまとめて実行

修正前は、塊の間隔が空いていて無駄に待ってしまっている。全体は343usかかっている。
修正後は、まとめた部分が並列で動くようになっており、全体は297usに短縮している。
PIXでイベント名をつける単位がExecuteCommandListsの実行単位なので、今まできれいに分類されていたのが、1まとめになってしまったけど、そんなことはどうでもいいくらいにパフォーマンスがアップした。

拡張バリアにした結果、400us位かかっていたのが、350usぐらいになって、コマンドリストの実行をまとめた結果、350usから300usぐらいになった。
後半のEmissive部分がまだまとめる余地が残っているのでもう少し削れそう。








0 件のコメント:

コメントを投稿