GPUデバイスがロストしたさいのエラーハンドリングについて

お世話になっております。

<br/>

現在アプリケーションを描画しているGPUのデバイスがロストした際に、そのその旨を表示するエラーを出力し、直ちにアプリケーションを終了させる処理を実装したいと考えています。

<br/>

こちら現状の挙動のために、GPUを二枚刺し、デバイスマネージャからアプリケーションを描画しているGPUを意図的に無効化したところ、数分アプリケーションの応答が止まったのち、エラーが出力され、終了するという挙動が確認できました。

この場合であると、GPUがロストしてからエラーまでに時間差があるため、直ちにアプリケーションを落とすという目的が達成できないため、その方法を探しています。

<br/>

UEのソースコードを確認し、RHIGetPanicDelegateという、RHIスレッドに問題が生じた場合コールバックを返すデリゲートが確認できたため、こちらにコールバックを流すように実装しましたが、結果は変わりませんでした。

このため、RHIGetPanicDelegateの呼び出し箇所を確認したところOpenGL関連のコードでしか呼び出しが行われていないように見えるのですが、RHIGetPanicDelegateの正しい利用用途を共有いただくことは可能でしょうか。

<br/>

また、上記GPUロストのタイミングを取得できる方法が、別にあればその方法も共有いただけると助かります。

<br/>

お手数をおかけいたしますが、ご確認よろしくお願いいたします。

お世話になっております。

手元で二枚刺しの環境は用意できないのですが、エディタ及びShippingパッケージで確認した限りでは直ちに(約2秒ほどの画面暗転が終わる頃には) エラーダイアログが表示されアプリケーションがシャットダウンするようでした。

まずはプロジェクトの設定等によるものであるのかハードウェア等環境の違いによるものであるのかを切り分けるところかと思いますが、グラフィックボードが一枚であっても症状は同じでしょうか。

また、ご使用の RHI が DirectX12 であれば FD3D12DynamicRHI::TerminateOnGPUCrash がデバイスロスト時の終了処理を担当することになっているはずです。

[Content removed]

手元でエディタをデバイスロストにより停止させた場合は以下のような経路でこの関数が呼び出されました。

[Image Removed]こちらの呼び出されるタイミングやコールスタックをご確認いただけますでしょうか。

また、デバイスロスト後、FD3D12Thread::Run(), FD3D12DynamicRHI::ProcessInterruptQueue() については呼び出されていますでしょうか。

>UEのソースコードを確認し、RHIGetPanicDelegateという、RHIスレッドに問題が生じた場合コールバックを返すデリゲートが確認できたため、こちらにコールバックを流すように実装しましたが、結果は変わりませんでした。

>このため、RHIGetPanicDelegateの呼び出し箇所を確認したところOpenGL関連のコードでしか呼び出しが行われていないように見えるのですが、RHIGetPanicDelegateの正しい利用用途を共有いただくことは可能でしょうか。

ご指摘の通りこちらのデリゲートは OpenGL 関連の部分でしか呼び出されていないようですので、RHI として OpenGL を選択していない限りは本件には無関係かと思います。

よろしくお願いいたします。

すみません、こちらに関してですが、取り急ぎ一旦グラフィックボードが一枚の環境でGPUDebugCrash hungを実行したところ、ご提示いただいたコールスタックが確認できました。

また、こちらが、​レジストリキーのTdrDelayの設定に応じて遅延することも確認でき、設定がなければ即座にクラッシュすることも確認しています。

このことから、物理的にデバイスロストさせた場合での環境に問題があると考えるため、コールスタックについては確認が取れ次第改めて回答いたします

デバイスロストさせた場合についての追記ですが、デバイスマネージャーからゲームをレンダリングしているデバイス無効化したところ明確なコールスタックは確認できませんでしたが、ログの状況から、RenderingThread.cppのHandleRenderTaskHang(uint32 ThreadThatHung, double HangDuration)にて終了命令が呼ばれているようです。

当該の現象が発生した関連個所のログを添付いたしますので​併せてご確認お願いいたします。

すみません、こちらの遅延についてですが、挙動、およびコードの確認により、GPUデバイスロスト時にレンダースレッドが待っていることが遅延の原因であることが分かりました。

またこちらのタイムアウト時間も以下のコンソールコマンドによって制御できることを確認しています。

> g.TimeoutForBlockOnRenderFence

このことから、いただいた回答の中ではAに該当するものと考えており、実際にはデバイスが停止した瞬間にエラーは吐いていますが、レンダースレッドが待ちの状態になっている物かと考えられます。また、こちらの値の調整により、遅延についてはある程度解消されることを確認できました。

ロストを含むGPU不具合が発生した場合、FD3D12DynamicRHI::TerminateOnGPUCrash を通るという認識でしたが、今回のケースでは別ルートでゲームが強制終了しているため、何らかの条件が揃わず、TerminateOnGPUCrashを呼び出す前にタイムアウトで終了してしまっている物かと思われます。

TerminateOnGPUCrash関数についてですが、上記挙動のようにGPUに問題があった際、どのような流れで呼ばれるか共有いただくことは可能でしょうか。

以上、ご確認お願いいたします。

ご回答ありがとうございます。

こちら詳細を確認したところ、UE5.4以前とUE5.5以後で当該の処理が変わっており、​UE5.5以後のパッケージでは g.TimeoutForBlockOnRenderFenceに設定した時間だけ待つ挙動にかわっていたようです。

このため、表題の件については解決で問題ありませんが、このように変更された経緯について、可能であればお答えいただくことはできますでしょうか。

参考に、5.4.4と5.5.4のバニラエンジンで作成したパッケージを添付いたします。

ご確認お願いいたします。​

ご回答ありがとうございます、gitの方確認させていただきました。

この規模の変更ですと以前の即時終了の状態に戻すのは厳しいと判断したため、​プロジェクトの方で仕様の調整という形で対応を検討させていただきます。​

ご対応ありがとうございました。​

クローズ後に申し訳ありません

こちらの件で、追加で一件だけ確認させていただきたいのですが、

以下のタイムアウト待ちのコンソールコマンドのg.TimeoutForBlockOnRenderFenceですが、デフォルトでは待ち時間が120000msとなっており、2分程度の待ち時間を設けられております。

こちらに関して、デフォルト値がこのようになっている理由がもしありましたら、共有いただけると助かります。

以上、よろしくお願いします。

デフォルト値について回答ありがとうございます。

特に強い意味合いはないということで、こちら、適宜プロジェクトの方で変更させていただきます。

ご対応ありがとうございました。​

ログの添付をありがとうございます。

確認いたしました。

①GetTimestampFrequency がエラー(DXGI_ERROR_DEVICE_REMOVED)を返し FD3D12DynamicRHI::TerminateOnGPUCrash が実行される

(ログの3行目の部分)

②RenderThread タスクが終了しないことを GameThread (GameThreadWaitForTask->HandleRenderTaskHang) で検出、コールスタックをログ出力

(ログの7行目からの部分)

という順序になっているようです。

ログに記載された時刻の差は3秒ほどですので、デバイスロストからシャットダウンまでの時間がかかるという問題に②は関係なさそうです。

添付して頂いたログは二枚差し環境にてデバイス無効化操作後しばらくしてから出力されるものという認識で良いでしょうか。

(とりあえずそうであると仮定して続けます)

デバイスが停止したことでこれに依存する API の呼び出し(今回のログにおいては GetTimestampFrequency )はエラーを返すことになりますが、

これについて

A:デバイスを停止した瞬間からエラーを即座に返す

B:デバイスを停止した瞬間からエラーを返す(ただし返ってくるのに TdrDelay に応じた時間がかかる)

C:TdrDelay に応じた遅延のあとエラーを返すようにならう(それまではエラーを返さない)

いずれであるかを確認する必要があるかと思います。

A の場合、デバイスを停止したことを API 呼び出し以外の何らかの方法で検出し API の呼び出し自体を避けるフローになっているということになると思いますが、今のところそれらしい部分は見つかっていません。

B の場合、他のスレッドからの監視による検出が論理的に可能のはずですが、GameThreadWaitForTask は一見まさにそのような対応になっているように思えますので少し不思議な状況です。

C の場合はデバイスの停止を検出できるタイミング自体が遅れてくるということになりますので、対処は難しそうです。

こちらでは状況を再現できていないため確認できず恐縮ですが、

デバイス停止以降の FD3D12DynamicRHI::ProcessInterruptQueue -> FD3D12Device::GetTimestampFrequency の呼び出し状況を調べて頂くことで問題を絞り込めるかもしれません。

よろしくお願いいたします。

>TerminateOnGPUCrash関数についてですが、上記挙動のようにGPUに問題があった際、どのような流れで呼ばれるか共有いただくことは可能でしょうか。

呼び出しの経路は一つではないのですが、先程のログを出力しているのは FD3D12Device::GetTimestampFrequency 関数内の以下の部分です。

VERIFYD3D12RESULT(Queues[(uint32)QueueType].D3DCommandQueue->GetTimestampFrequency(&Frequency));このマクロは括弧内に書かれた API 呼び出しの結果(HRESULT型)が失敗であった場合に該当のコードをログ出力した上で、

先程のケースであれば

VerifyD3D12Result

-> FD3D12DynamicRHI::HandleFailedD3D12Result

-> TerminateOnGPUCrash (分岐がありますが、Interrupt スレッドかつエラーの種類が DXGI_ERROR_DEVICE_REMOVED 等である場合)

のような流れで TerminateOnGPUCrash を呼び出すことになるかと思われます。

> g.TimeoutForBlockOnRenderFence

こちらの設定を参照している GameThreadWaitForTask は原因に関わらず RenderThread 自体のタイムアウトをチェックします。

デバイスロスト以外の場合についてもキャッチすることになるはずですが、今回のような状況への対処として適切かもしれません。

よろしくお願いいたします。​

こちらの手元では停止の仕方について挙動の差を確認できませんでしたが、GameThreadWaitForTask によるタイムアウトのチェック方法自体には大きな変更が無いように見えます。​

UE5.4 においては別の箇所で停止しているかもしれません。

UE5.4~5.5 の期間で GameThreadWaitForTask に関する変更は以下の二件が見受けられます。

https://github.com/EpicGames/UnrealEngine/commit/10cdd4a111829b1ec04738249b25dcf929955126

レンダリングの並列化に関する大きな変更のようですので、この件について影響を与えている可能性がありそうです。

詳しくはコミットの説明を御覧ください。

他の多くのファイルについても変更があります。

https://github.com/EpicGames/UnrealEngine/commit/27b07480776d0e165c60169c98e39a1b4e97db06

こちらはタイムアウト検出時の動作についての変更ですので、待ち時間とは無関係かもしれません。

よろしくお願いいたします。​

ご確認ありがとうございます。

本件はクローズいたします。

また何かありましたらお問い合わせ下さい。​

120000ms という時間に特別な意味は無いと思われます。

時間のかかる処理に対して意図しない停止があまり起こらない程度の値ということなのでしょうが、場合によってはより大きな値に設定する必要があるようです。

https://x.com/tempkinder/status/1519328010272268296

DefaultEngine.ini 内に以下を追記することによってタイムアウトまでの時間を変更できます(ここでは 60000ms にしています)。

[ConsoleVariables]

g.TimeoutForBlockOnRenderFence=60000

よろしくお願いいたします。