FSkeletonRemapping::RetargetBoneRotationToTargetSkeletonでのクラッシュに関しまして

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

FSkeletonRemapping::RetargetBoneRotationToTargetSkeleton関数でクラッシュが発生します。

レベルへの遷移時に発生しており、毎回発生している訳ではなく、日に一度くらいの頻度でたまに発生している状態です。

発生時は、

以下の様なメッセージが表示されています。

Assertion failed: (Index >= 0) & (Index < ArrayNum) [File:D:/project/cnt/Engine/Source/Runtime/Core/Public/Containers/Array.h] [Line: 1067] Array index out of bounds: 142 into an array of size 0

以下の症状と酷似しており、

[Content removed]

同様の問題ではないかと考えております。

- 既に対応済のようですが、Engineバージョン 5.6.1 を使用しておりパッケージ対応が必要で、

Engine更新は行えないため、問題回避のための方法や、コミットをご教示いただけますと幸いです

- Engineバージョン、5.7.4 では、問題は発生しますでしょうか

- Issue​ and Bug Tracker では、Engineバージョン 5.8 修正ですが、認識あっていますでしょうか

Unreal Engine Issues and Bug Tracker (UE-353766)

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

[Attachment Removed]

クラッシュ時のコールスタックは以下です

​[インライン フレーム] Win64-Test.exe!TArray<TTuple<UE::Math::TQuat<double>,UE::Math::TQuat<double>>,TSizedDefaultAllocator<32>>::RangeCheck(int) 行 1067 C++

[インライン フレーム] Win64-Test.exe!TArray<TTuple<UE::Math::TQuat<double>,UE::Math::TQuat<double>>,TSizedDefaultAllocator<32>>::operator[](int) 行 1158 C++

Win64-Test.exe!FSkeletonRemapping::RetargetBoneRotationToTargetSkeleton(int TargetSkeletonBoneIndex, const UE::Math::TQuat<double> & SourceRotation) 行 95 C++

Win64-Test.exe!UE::Anim::Decompression::DecompressPose(FCompactPose & OutPose, const FCompressedAnimSequence & CompressedData, const FAnimExtractContext & ExtractionContext, FAnimSequenceDecompressionContext & DecompressionContext, const TArray<UE::Math::TTransform<double>,TSizedDefaultAllocator<32>> & RetargetTransforms, const FRootMotionReset & RootMotionReset) 行 253 C++

Win64-Test.exe!UAnimSequence::GetBonePose(FAnimationPoseData & OutAnimationPoseData, const FAnimExtractContext & ExtractionContext, bool bForceUseRawData) 行 1865 C++

Win64-Test.exe!UAnimSequence::GetAnimationPose(FAnimationPoseData & OutAnimationPoseData, const FAnimExtractContext & ExtractionContext) 行 1682 C++

Win64-Test.exe!FAnimNode_SequencePlayerBase::Evaluate_AnyThread(FPoseContext & Output) 行 137 C++

Win64-Test.exe!FPoseLink::Evaluate(FPoseContext & Output) 行 426 C++

Win64-Test.exe!FAnimNode_BlendListBase::Evaluate_AnyThread(FPoseContext & Output) 行 318 C++

Win64-Test.exe!FPoseLink::Evaluate(FPoseContext & Output) 行 426 C++

Win64-Test.exe!FPoseLink::Evaluate(FPoseContext & Output) 行 426 C++

Win64-Test.exe!FAnimNode_StateMachine::Evaluate_AnyThread(FPoseContext & Output) 行 1001 C++

Win64-Test.exe!FPoseLink::Evaluate(FPoseContext & Output) 行 426 C++

Win64-Test.exe!FAnimNode_Slot::Evaluate_AnyThread(FPoseContext & Output) 行 91 C++

Win64-Test.exe!FPoseLink::Evaluate(FPoseContext & Output) 行 426 C++

Win64-Test.exe!FAnimNode_Slot::Evaluate_AnyThread(FPoseContext & Output) 行 91 C++

Win64-Test.exe!FPoseLink::Evaluate(FPoseContext & Output) 行 426 C++

Win64-Test.exe!FAnimNode_ConvertLocalToComponentSpace::EvaluateComponentSpace_AnyThread(FComponentSpacePoseContext & OutputCSPose) 行 96 C++

Win64-Test.exe!FComponentSpacePoseLink::EvaluateComponentSpace(FComponentSpacePoseContext & Output) 行 494 C++

Win64-Test.exe!FAnimNode_SkeletalControlBase::EvaluateComponentPose_AnyThread(FComponentSpacePoseContext & Output) 行 137 C++

Win64-Test.exe!FAnimNode_SkeletalControlBase::EvaluateComponentSpace_AnyThread(FComponentSpacePoseContext & Output) 行 173 C++

Win64-Test.exe!FComponentSpacePoseLink::EvaluateComponentSpace(FComponentSpacePoseContext & Output) 行 494 C++

Win64-Test.exe!FAnimNode_SkeletalControlBase::EvaluateComponentPose_AnyThread(FComponentSpacePoseContext & Output) 行 137 C++

Win64-Test.exe!FAnimNode_SkeletalControlBase::EvaluateComponentSpace_AnyThread(FComponentSpacePoseContext & Output) 行 173 C++

Win64-Test.exe!FComponentSpacePoseLink::EvaluateComponentSpace(FComponentSpacePoseContext & Output) 行 494 C++

Win64-Test.exe!FAnimNode_ConvertComponentToLocalSpace::Evaluate_AnyThread(FPoseContext & Output) 行 43 C++

Win64-Test.exe!FPoseLink::Evaluate(FPoseContext & Output) 行 426 C++

Win64-Test.exe!FAnimNode_Inertialization::Evaluate_AnyThread(FPoseContext & Output) 行 448 C++

Win64-Test.exe!FPoseLink::Evaluate(FPoseContext & Output) 行 426 C++

Win64-Test.exe!FAnimInstanceProxy::EvaluateAnimationNode_WithRoot(FPoseContext & Output, FAnimNode_Base * InRootNode) 行 1509 C++

Win64-Test.exe!FAnimInstanceProxy::EvaluateAnimation_WithRoot(FPoseContext & Output, FAnimNode_Base * InRootNode) 行 1426 C++

Win64-Test.exe!UAnimInstance::ParallelEvaluateAnimation(bool bForceRefPose, const USkeletalMesh * InSkeletalMesh, FParallelEvaluationData & OutEvaluationData) 行 909 C++

Win64-Test.exe!USkeletalMeshComponent::EvaluateAnimation(const USkeletalMesh * InSkeletalMesh, UAnimInstance * InAnimInstance, bool bInForceRefPose, UE::Math::TVector<double> & OutRootBoneTranslation, FBlendedHeapCurve & OutCurve, FCompactPose & OutPose, UE::Anim::FHeapAttributeContainer & OutAttributes) 行 2375 C++

Win64-Test.exe!USkeletalMeshComponent::PerformAnimationProcessing(const USkeletalMesh * InSkeletalMesh, UAnimInstance * InAnimInstance, bool bInDoEvaluation, bool bInForceRefPose, TArray<UE::Math::TTransform<double>,TSizedDefaultAllocator<32>> & OutSpaceBases, TArray<UE::Math::TTransform<double>,TSizedDefaultAllocator<32>> & OutBoneSpaceTransforms, UE::Math::TVector<double> & OutRootBoneTranslation, FBlendedHeapCurve & OutCurve, UE::Anim::FMeshAttributeContainer & OutAttributes) 行 2462 C++

Win64-Test.exe!USkeletalMeshComponent::ParallelAnimationEvaluation() 行 4672 C++

Win64-Test.exe!FParallelAnimationEvaluationTask::DoTask(ENamedThreads::Type CurrentThread, const TRefCountPtr<FBaseGraphTask> & MyCompletionGraphEvent) 行 369 C++

Win64-Test.exe!TGraphTask<FParallelAnimationEvaluationTask>::ExecuteTask() 行 707 C++

[インライン フレーム] Win64-Test.exe!UE::Trace::FChannel::operator|(const UE::Trace::FChannel & Rhs) 行 30 C++

[インライン フレーム] Win64-Test.exe!TaskTrace::FTaskTimingEventScope::{ctor}(unsigned __int64) 行 302 C++

Win64-Test.exe!UE::Tasks::Private::FTaskBase::TryExecuteTask() 行 528 C++

[インライン フレーム] Win64-Test.exe!UE::Tasks::Private::FTaskBase::Init::__l2::<lambda>() 行 184 C++

[インライン フレーム] Win64-Test.exe!LowLevelTasks::FTask::Init::__l13::<lambda>(const bool) 行 499 C++

[インライン フレーム] Win64-Test.exe!Invoke(LowLevelTasks::FTask::Init::__l13::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &, UWorld *, FOutputDevice &) &) 行 47 C++

[インライン フレーム] Win64-Test.exe!LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48>::TTaskDelegateImpl<`LowLevelTasks::FTask::Init<`UE::Tasks::Private::FTaskBase::Init’::`2’::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &, UWorld *, FOutputDevice &)>‘::`13’::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &, UWorld *, FOutputDevice &),0>::Call(void *) 行 162 C++

Win64-Test.exe!LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48>::TTaskDelegateImpl<`LowLevelTasks::FTask::Init<`UE::Tasks::Private::FTaskBase::Init’::`2’::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &, UWorld *, FOutputDevice &)>‘::`13’::void <lambda>(const TArray<FString,TSizedDefaultAllocator<32>> &, UWorld *, FOutputDevice &),0>::CallAndMove(LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48> & Destination, void * InlineData, unsigned int DestInlineSize, bool <Params_0>) 行 171 C++

[インライン フレーム] Win64-Test.exe!LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48>::CallAndMove(LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48> &) 行 309 C++

Win64-Test.exe!LowLevelTasks::FTask::ExecuteTask() 行 627 C++

[インライン フレーム] Win64-Test.exe!FCpuProfilerTrace::FEventScope::{ctor}(unsigned int &) 行 197 C++

Win64-Test.exe!LowLevelTasks::FScheduler::ExecuteTask(LowLevelTasks::FTask * InTask) 行 364 C++

[インライン フレーム] Win64-Test.exe!LowLevelTasks::FScheduler::TryExecuteTaskFrom(LowLevelTasks::Private::FWaitEvent *) 行 665 C++

Win64-Test.exe!LowLevelTasks::FScheduler::WorkerLoop(LowLevelTasks::Private::FWaitEvent * WorkerEvent, LowLevelTasks::Private::TLocalQueueRegistry<1024,1024>::TLocalQueue * WorkerLocalQueue, unsigned int WaitCycles, bool bPermitBackgroundWork) 行 724 C++

[インライン フレーム] Win64-Test.exe!LowLevelTasks::FScheduler::WorkerMain(LowLevelTasks::Private::FWaitEvent * WorkerEvent, LowLevelTasks::Private::TLocalQueueRegistry<1024,1024>::TLocalQueue * WorkerLocalQueue, unsigned int WaitCycles, bool bPermitBackgroundWork) 行 783 C++

Win64-Test.exe!LowLevelTasks::FScheduler::CreateWorker::__l2::<lambda>() 行 188 C++

[インライン フレーム] Win64-Test.exe!UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::TFunctionStorage<1>,void __cdecl(void)>::operator()() 行 471 C++

Win64-Test.exe!FThreadImpl::Run() 行 69 C++

Win64-Test.exe!FRunnableThreadWin::Run() 行 159 C++

Win64-Test.exe!FRunnableThreadWin::GuardedRun() 行 71 C++

kernel32.dll!00007fff7fe5e8d7() 不明

ntdll.dll!00007fff8164c53c() 不明

[Attachment Removed]

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

お問い合わせありがとうございます。

当方で再現が取れているわけではないため、関連チケットで示された情報や再現状況、コールスタックからの推測となりますが、おそらく本クラッシュはリターゲット情報 FSkeletonRemapping への複数スレッドからの非同期アクセスによる競合と思われます。

スケルタルメッシュのロード時に、ロード処理を行うスレッドが該当スケルトンのリターゲットテーブルを再構築(一旦空にしてから再作成)するのですが、このテーブルが再構築を終えるまでの極めて僅かなタイミングに、別スレッドからこのテーブルが参照されますと、範囲外アクセスが生じるというものです。

そのため、以下の3条件が同時に揃ったときに発生するのではないかと考えております。

・Compatible Skeletonsでのリターゲットを使用

・その(マップ先)スケルトンを使うメッシュを、プレイ中にロード→リターゲットテーブルの再構築開始

・そのリターゲットテーブルの再構築の最中に、同じマッピングを使う(すでにロード済みの)別メッシュが、並列アニメ評価でテーブルを参照する

(※同一スケルトンを共有する複数のメッシュのうちの1体がゲームで動いている最中に、別の1体をストリームインする、といった状況)

> - Engineバージョン、5.7.4 では、問題は発生しますでしょうか

> - Issue and Bug Tracker では、Engineバージョン 5.8 修正ですが、認識あっていますでしょうか

この問題は、5.7.4でも発生すると思われます。また、大変恐れながら、UE-353766は社内で再現不可とされ、UE5.8におきましても、実際の修正は行われておりません。

ワークアラウンドとして、SkeletonRemapping.h の都合4つの関数に手を加え、リターゲットテーブルの構築が終わっていない場合にはソース情報を戻すことでクラッシュを回避するコードを用意しました。従来クラッシュが発生していた、その問題の1フレームだけ、リターゲットが期待通り行われないという副作用があります。ただし、次のフレームで回復するため、発生頻度を考えますと、容認できないレベルの不具合には至らないのではないかと考えております。

Engine/Source/Runtime/Engine/Public/Animation/SkeletonRemapping.h

 	FVector RetargetBoneTranslationToTargetSkeleton(int32 TargetSkeletonBoneIndex, const FVector& SourceTranslation) const
 	{
 		// Compute the translation part of FTransform(Q1) * Source * FTransform(Q0)
+		if (!RetargetingTable.IsValidIndex(TargetSkeletonBoneIndex))
+		{
+			return SourceTranslation;
+		}
 		const TTuple<FQuat, FQuat>& QQ = RetargetingTable[TargetSkeletonBoneIndex];
 		return QQ.Get<0>().RotateVector(SourceTranslation);
 	}

 	FQuat RetargetBoneRotationToTargetSkeleton(int32 TargetSkeletonBoneIndex, const FQuat& SourceRotation) const
 	{
 		// Compute the rotation part of FTransform(Q1) * Source * FTransform(Q0)
+		if (!RetargetingTable.IsValidIndex(TargetSkeletonBoneIndex))
+		{
+			return SourceRotation;
+		}
 		const TTuple<FQuat, FQuat>& QQ = RetargetingTable[TargetSkeletonBoneIndex];
 		return QQ.Get<0>() * SourceRotation * QQ.Get<1>();
 	}
 
 	FVector RetargetAdditiveTranslationToTargetSkeleton(int32 TargetSkeletonBoneIndex, const FVector& SourceTranslation) const
 	{
 		// Compute the translation part of FTransform(Q0.Inverse) * Source * FTransform(Q0)
+		if (!RetargetingTable.IsValidIndex(TargetSkeletonBoneIndex))
+		{
+			return SourceTranslation;
+		}
 		const TTuple<FQuat, FQuat>& QQ = RetargetingTable[TargetSkeletonBoneIndex];
 		return QQ.Get<0>().RotateVector(SourceTranslation);
 	}
 
 	FQuat RetargetAdditiveRotationToTargetSkeleton(int32 TargetSkeletonBoneIndex, const FQuat& SourceRotation) const
 	{
 		// Compute the rotation part of FTransform(Q0.Inverse) * Source * FTransform(Q0)
+		if (!RetargetingTable.IsValidIndex(TargetSkeletonBoneIndex))
+		{
+			return SourceRotation;
+		}
 		const TTuple<FQuat, FQuat>& QQ = RetargetingTable[TargetSkeletonBoneIndex];
 		return QQ.Get<0>() * SourceRotation * QQ.Get<0>().Inverse();
 	}

一度、ご確認いただけますと幸いです。

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

[Attachment Removed]

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

Engineバージョン 5.6.1でのパッケージ対応では、ご提案いただいたワークアラウンドを試し、様子を見てみようと思います。

しかしながら、原因がデータレースである場合、上記対応により発生確率を下げることはできても、問題が起きうる可能性があると懸念しております。

可能であれば、ロックガードに関して 実装の参考になる例や資料等がございましたら、ご教示いただけますと幸いです。

また、EPSを検索したところ、以下の情報を見つけました。

本件で発生している現象と関連がありますでしょうか

[Content removed]

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

[Attachment Removed]

ご返答およびパッチのご提供、誠にありがとうございます。

重ねてのご質問となり恐縮ですが、

上記のEPSスレッドにて、CL:50892441 のバックポートも勧められていますが、

[Content removed]

こちらのCLについてもバックポートを実施した方がよろしいでしょうか。

ご教示いただけますと幸いです。

恐れ入りますが、何卒よろしくお願いいたします。

[Attachment Removed]

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

CL48060137を適用のうえ、しばらく様子を確認いたします。

[Attachment Removed]

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

EPSでの情報の検索をありがとうございます。本来当方がこちらの情報を見つけておかなければならなかったところを、誠に申し訳ございません!

また、前回の回答では「修正は行われていない」とお伝えしましたが、これは当方の確認漏れによる誤りで、実際には同一の不具合に対する修正が別チケット UE-353078 として存在しておりました。重ねてお詫び申し上げます。

ご提示いただいたEPSのチケットおよびCL48060137は本件と直接関連しており、根本修正にあたる可能性が非常に高いです。

CL48060137をバックポートすれば、スケルタルメッシュのロード時に行われていた(不要な)リターゲット情報の再構築が行われなくなるため、データレースの発生自体を取り除くことができるはずです。

UE5.6.1にも容易にバックポートできる変更でしたので、一度お試しいただけば幸いです。その際、先日の回答でお伝えした暫定回避のコードは元に戻していただいて構いません。

参考までに対 UE5.6.1 への .patch 方式のファイルも添付いたします。

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

[Attachment Removed]

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

本クラッシュの解消には先にお送りした CL48060137 のみで必要十分と考えておりますが、CL50892441についても、適用いただいて問題ございません。

CL50892441 は、リファレンススケルトンの再構築を Serialize から PostLoad へ移すものでして、より安全なタイミングに処理を移動させたといえます。リスクのある変更とは考えておりませんが、純粋に不要な再構築の呼び出しを抑制したCL48060137と比べると、相対的に大きな処理の変更であるため、適用後は念のため動作のご確認をお願いできますと幸いです。

なお、バックポート自体は容易に行えます。ご参考までに、UE5.6.1向けにバックポートした .patch を添付いたします。

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

[Attachment Removed]

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

それでは何か問題がございましたらご連絡ください。

※案件管理の都合上、一旦、チケットステータスをPending(お客様からの連絡待ち)とさせていただきます。

引き続き、よろしくお願いいたします。

[Attachment Removed]