サブレベルのUNLOADとLOADを繰り返すとアサートが発生します

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

弊社ではほぼ空のパーシスタントレベルにいくつかの場所のサブレベルを読み替えながらゲームを実行しているのですが、

(建物の廊下から部屋に入ってまた廊下に出てくる…のような感じです)

今のサブレベルのUNLOAD->次のレベルのLOADを繰り返しているとPC版で低い確率ですがアサートが出ます(PS5やXBOXでは問題ありません)。

Developビルドですが、エディターかパッケージかでソースの行数が若干が異なり、以下になります。

・エディター

Assertion failed: CaptureIndex < Scene->ReflectionSceneData.CubemapArray.GetMaxCubemaps() [File:D:\build\++UE5\Sync\Engine\Source\Runtime\Renderer\Private\ReflectionEnvironmentCapture.cpp] [Line: 1148]

ブレークポイント命令 (__debugbreak() ステートメントまたは類似の呼び出し) が UnrealEditor.exe で実行されました。

・パッケージ

Ensure condition failed: CaptureSceneStatePtr [File:ReflectionEnvironmentCapture.cpp] [Line: 1476] Reflection capture /Game/mygame/Maps/BG1_lit.BG1_lit:PersistentLevel.BoxReflectionCapture_1.NewReflectionComponent uploaded twice without reloading its lighting scenario level. The Lighting scenario level must be loaded once for each time the reflection capture is uploaded.

パッケージは確率が低い(10~20人でプレイして二日に一回ぐらい)ので確率の高いエディター(30分ぐらいで起こせます)で検証しているのですが、一応サードパーソンで再現プロジェクトを作ることができました。

アサートするときはFSceneの反射キャプチャデータの配列で不整合が起こっているようで、何となくスレッドセーフになっていないような感じがしております。

またUNLOAD後にFSceneInterfaceのResetReflectionCaptures()を呼び出すとアサートが起こらなくなるのは確認できています。

そこで質問なのですが、UNLOADの完了を安全に待つにはどうすれば良いでしょうか?サンプルではGetLevelStreamingStatus()で待っているのですが、他にも追加で何か必要でしょうか?

*LevelStreaming.cppに以下のようなコメントがあり、LEVEL_UnloadedButStillAround状態が取れていないようではあります

// We can’t use FindObjectFast() while the GC is running… Let’s treat the level as unloaded at this point.

また仮に安全に待つ方法がない場合、製品版でもResetReflectionCaptures()を使って大丈夫でしょうか?

SceneInterface.hに以下のコメントがあり、エディターでの使用を想定しているような感じもあります。

// instead of needing to restart the editor

他に試してみた事として、UNLOADを待つときに1秒のウェイトを入れてもほぼ同じ確率でアサートしますが、5秒のウェイトを入れるとかなり確率が下がりますが、やはりたまにアサートが出ます。

また以下のUDNの投稿の内容が割と近い感じがしております。

[Content removed]

エンジン改造でそれぞれの関数にUE_LOG()を追加したところ、そちらに出てくるOnRegisterとCaptureOrUploadReflectionCaptureの順番が逆になる現象が弊社ゲームでも起こっているようでした。

以上、お手数おかけしますがよろしくお願い致します。

*アサートで止まっているのでCrashesフォルダは無いようでした。VSの画像を添付します

[Attachment Removed]

すみません、いくつか補足します。

アサートする自分のPC環境は以下になります。

CPU:Intel Core Ultra7 265

GPU:GeForce 5070

メモリ:64GB

ドライブ:SSD

他に10台ぐらいでアサートしていますが、CPUがIntelの1世代前だったり、GPUもGeForce 4060だったりしますが似たような環境です。AMDのCPUとGPUはありません。

また製品版のパッケージで問題になっているのはセーブしてすぐ同じデータをロードすることで、同じサブレベルをロードし直すようなシチュエーションです。サンプルもこれに合わせてあります。

ただサンプルをSubLvBG&SubLvLitと、SubLvBG2&SubLvLit2を交互にLOADするように修正しても再現するため、同じサブレベルをロードすることが再現の条件では無さそうです。

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

[Attachment Removed]

すみません、もう1点補足します。

添付のサンプルでWindows版のパッケージを作り実行すると簡単にはアサートしなくなるのですが、そのまま放置したところ、3時間後ぐらいに以下でハングしていました。

>LogRendererCore: Error: GameThread timed out waiting for RenderThread after 120.00 seconds:

ただ弊社のゲームのパッケージですと以下のEnsureで止まるため、同じ状況と言えるかは分かりません。

>uploaded twice without reloading its lighting scenario level.

一応最後に出たログはエディターのハングと同じ以下でしたが…

>LogTemp: Warning: ######## --- LEVEL_Loading(SubLvLit)

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

[Attachment Removed]

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

本件再現プロジェクトをご用意いただきありがとうございます。UE5.6にて問題を再現確認させていただきました。

事前に調査頂いておりましたが、Reflection Captureに関しましては処理の競合によってデータが破損してしまう既知の不具合が存在し、

こちらの問題に対する修正対応(CL49645640)をマージすることで、再現プロジェクト上にてクラッシュが発生しなくなる事を確認しております。

お手数おかけしますが上記CLのマージをお試しいただけますと幸いです。

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

[Attachment Removed]

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

CLを適用したところ添付のサードパーソンサンプルでは全くアサートが出なくなったので解決したかと思ったのですが、弊社ゲームの実際のアセットだとまだアサートが出ました。

一応感覚的には1/3ぐらいにアサートが出る確率が減っているのでCLの効果は出ていると思うのですが、具体的には以下の感じでアサートがまだ出ます。

(1) SubLvLitにReflectionCaptureが11個あるため、AllocateEncodedHDRCubemapTexture()に私が追加したUE_LOGから以下のように出てくる

>UE_LOG(LogTemp, Warning, TEXT(“UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() %s”), *this->GetOwner()->GetActorNameOrLabel());

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_21

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_27

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture2

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_25

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_26

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() SphereReflectionCapture_25

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_24

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_23

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_22

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_20

LogTemp: Warning: UReflectionCaptureComponent::AllocateEncodedHDRCubemapTexture() BoxReflectionCapture_18

(2) FScene::CaptureOrUploadReflectionCaptureにもUE_LOGを追加しているため、上記と同じものが出てくる

>UE_LOG(LogTemp, Warning, TEXT(“CaptureOrUploadReflectionCapture() %s”), *CaptureComponent->GetOwner()->GetActorNameOrLabel());

LogTemp: Warning: CaptureOrUploadReflectionCapture() BoxReflectionCapture_21

LogTemp: Warning: CaptureOrUploadReflectionCapture() BoxReflectionCapture_27

・・・省略・・・

(3) 問題が無いときはUploadReflectionCapture_RenderingThread()->FindOrAllocateCubemapIndex()の順に呼ばれ、

if (CaptureSceneStatePtr)がtrueになっており、返り値のCubemapIndexも0-10の範囲に収まっている。

(4) アサートが起こる時は上記if文がfalseのため以下に進むが、

>CubemapIndex = Scene->ReflectionSceneData.CubemapArraySlotsUsed.FindAndSetFirstZeroBit();

返り値のCubemapIndexがINDEX_NONEなのでCubemapArraySlotsUsed.Add()が行われる。

そしてUploadReflectionCapture_RenderingThread()に戻った後、配列が増えているので以下のアサートに引っかかる

>check(CaptureIndex < Scene->ReflectionSceneData.CubemapArray.GetMaxCubemaps());

という感じになります。

弊社ゲームのアセットを送れば御社の方でも再現できると思うのですが、発売前のゲームのため会社の許可が必要ですぐには無理そうです。

非常に申し訳ないのですが、上記情報だけでも何か分かることはありますでしょうか?

以上、お手数おかけして申し訳ありませんが、よろしくお願い致します。

[Attachment Removed]

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

実際の弊社ゲームで​ResetReflectionCaptures​()を呼んで検証してみたのですが、それによるロード時間の増加はほぼ無いようでした。

ということで、そちらにて対応しようと思います。

詳しく調査して頂き有難うございました。

また何かありましたら、よろしくお願い致します。​

[Attachment Removed]

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

これからCLを適用してテストしてみます。少々お待ちください。

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

[Attachment Removed]

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

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

[Attachment Removed]

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

上記の件ですが、再現サンプルを作ることができましたのでアップロードしたいと思っております。

ただ念のため御社の方以外が見れないように送りたいのですが、何か方法がありますでしょうか?

(返答の添付ファイルだと他の人もダウンロードできる気がしますので)

[Attachment Removed]

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

CLを適用いただいたにもかかわらず問題が発生しているとのこと、

お手数をおかけしており申し訳ございません。

再現プロジェクトをご用意いただいているとのことですので、

アップロード先として、弊社契約のBoxへの招待をご登録いただいているメールアドレス宛にお送りいたしました。

お手数をおかけいたしますが、上記Box経由でアップロードいただけますと幸いです。

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

[Attachment Removed]

アップロードしました。どうでしょうか?

こちらをCL適用後のエンジンで開いてもらい、​エディターで再生した後1時間ほど放置すればアサートで止まるのでは…と思います。

(駄目でしたら一晩ぐらい様子を見ていただければと思います)​

補足ですが、今回用にBG3とLit3を追加しており、​BG3の子としてLit3を追加している状態になります。この状態でBG3を開いてライトビルドを行っています。

その後サードパーソンの​パーシスタントレベルでUGameplayStatics::LoadStreamLevel()を使ってロードしています。

この辺りの構成もひょっとしたらアサートに関係あるのかもしれません。

以上、よろしくお願い致します。 [Image Removed]​

[Attachment Removed]

Boxへのアップロード対応ありがとうございます。。再現プロジェクトの方確認させていただきました。

こちらご提供頂いたデータを元に調査を進めさせていただきます。

[Attachment Removed]

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

引き続き自分の方でも調べているのですが、お役に立つか分かりませんが、いくつか気になったことを書いておきます。

(1)再現サンプルですが、エディターやVisualStudioでログを出すと全体の動作が遅くなりアサートが出るまでの時間が伸びるようですので、放置するときは他のタブに切り替えてログは出さない方が良さそうです。

(2)ログを出さない状態だと手元ではだいたい45分程度でアサートが出るのですが、その前までは

・ReflectionEnvironmentCapture.cpp

>// Try to find an existing capture index for this component

>const FCaptureComponentSceneState* CaptureSceneStatePtr = Scene->ReflectionSceneData.AllocatedReflectionCaptureState.AddReference(Component);

のCaptureSceneStatePtrがnullptrなのですが、アサートする直前に有効なポインタが入るようです。

しかし次の以下の部分で空きbitが見つからず配列を拡張してアサートするようです。

>// Reuse a freed index if possible

>CubemapIndex = Scene->ReflectionSceneData.CubemapArraySlotsUsed.FindAndSetFirstZeroBit();

45分前後で挙動が変わる理由が分からないのですが、GCにゴミ?等がたまっているのかと思いとりあえず

>gc.ForceCollectGarbageEveryFrame 1

を有効すると3時間ほどいつものアサートは出なかったのですが、代わりに以下が出ました。

>Fatal error: [File:F:\UE5_6_1\Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectGlobals.cpp] [Line: 668]

>Illegal call to StaticFindAllObjectsFast() while serializing object data or garbage collecting!

>ブレークポイント命令 (__debugbreak() ステートメントまたは類似の呼び出し) が UnrealEditor.exe で実行されました。

ログにgarbage collectingと出ているので、反射のアサートは出なくなって別件なのか、それともやはり関係あるのか微妙ですが…

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

[Attachment Removed]

本件調査にお時間を頂いており申し訳ございません。

結論としましては、パフォーマンス上問題がなければResetReflectionCaptures()の利用を検討頂いた方が安定した動作が行える見込みです。

・発生状況と問題

再現プロジェクトを拝見させて頂いたところ、PlayerControllerのTickで背景やライトシナリオのロードアンロードと高速に繰り返し行われておりますが、

ReflectionCapture関連の更新は各レベルではなくFScene全体で集められて処理が行われるため、

FSceneでそれらの情報を管理するキャッシュやテーブルを含むReflectionSceneData(AllocatedReflectionCaptureState)が存在しています。

具体的にはRegisteredComponentMapBuildDataIdsやCaptureDataといったComponent(ポインタ)とMapBuildDataId(ID)を紐づけたMap、

キャッシュやFReflectionCaptureCacheEntry(ステート管理)があり、IDに関しては変更があった場合にリマップ処理が行われていますが、

この時ポインタがMapのKeyになっており、Componentをロードアンロードしてるうちにポインタが再利用されて衝突した場合に本来の組み合わせとは異なるIDが返ってきてしまい、

更にその後リマップ処理も適切に行えなくなり、CaptureData等が不一致となってしまう状況となっておりました。

またEditorの場合に発生しやすくなっている理由としまして、Editorの場合は1回のロードのうちLEVEL_MakingVisibleとLEVEL_Visibleで2回ReflectionCaptureのUpload処理が行われていました。

こちらは該当処理付近で分岐が行われていますが、本来OnDataUploadedToGPUFinalが一回呼び出されるとMapBuildDataの方は破棄する想定のようですが、

Editorの場合はシリアライズの関係上破棄せずにbUploadedFinalのフラグも立てないため、再度UploadReflectionCapture処理に入る形となります。

※この処理の中でFindOrAllocateCubemapIndex()から、AllocatedReflectionCaptureState.AddReference(Component)といった更新の処理がおこなわれています。

// After the final upload we cannot upload again because we tossed the source MapBuildData,
// After uploading it into the scene's texture array, to guaratee there's only one copy in memory.
// This means switching between LightingScenarios only works if the scenario level is reloaded (not simply made hidden / visible again)
if (!CaptureData->HasBeenUploadedFinal())
{
		UploadReflectionCapture_RenderingThread(RHICmdList, Scene, CaptureData, CaptureComponent);
		CaptureData->OnDataUploadedToGPUFinal();
}

上記のような状況のため、ResetReflectionCapturesを挟むことでReflectionSceneDataがリセットされて改善がみられている状況かと思われます。

キャッシュ等の仕組み自体の改善も検討できますが少々複雑な状態となってしまっているため、ResetReflectionCaptures()の利用をお試し頂けますと幸いです。

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

[Attachment Removed]

ご返答有難うございます。詳しく解説していただき、とても助かります。

では一度ResetReflectionCaptures​()を製品版で使ってみまして動作確認をしますので少々お待ちください(数日掛かると思われます)。

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

[Attachment Removed]

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

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

[Attachment Removed]

検証結果や対応方針についてご共有いただきありがとうございます。

本件はクローズとさせていただきますが、また何かございましたらお気軽にお問い合わせ頂ければと思います。

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

[Attachment Removed]