Control Rig & RigLogic Nodes - CacheBones Can be Very Expensive

Hello,

After upgrading to 5.6 from 5.4, we noticed that Control Rig and Rig Logic AnimNodes in our graphs can be extremely expensive when running CacheBones. This seems to be due to URigHierarchy::LinkPoseAdapter which has caused hitches of up to 40ms. This wasn’t happening in 5.4. Do you know of the best way to solve for this?

Locally, we’re testing out removing the call to `ShrinkHierarchyStorage()` inside `FControlRigPoseAdapter::PostLinked` which certainly helps due to not reallocating memory. But we’re not sure about knock-on effects.

Any advice is appreciated. Thanks!

-Nathaniel

[Image Removed]

[Attachment Removed]

Hi Nathaniel,

The pose adapter code was added a while back as a way to mitigate per-frame performance issues that we were seeing copying data in/out of control rigs owned by anim blueprints. The idea is that the pose adapter allows the hierarchy data to be shared between anim bp/control rig to avoid a load of copies every frame.

I haven’t seen other licensees running into such drastic performance issues with the initialization of the pose adapter. Is your control rig hierarchy particularly complex? How many elements does FControlRigPoseAdapter::PoseIndexToElementIndex contain?

I think that we could make the implementation of FControlRigPoseAdapter::UnlinkTransformStorage more efficient to help here, since currently it’s performing two allocations for each element in the hierarchy. You could try changing it to the following to see if this helps:

void FControlRigPoseAdapter::UnlinkTransformStorage()
{
	DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
 
	if (URigHierarchy* Hierarchy = GetHierarchy())
	{
		if (PoseIndexToElementIndex.Num() > 0)
		{
			TArray<TTuple<FRigElementKeyAndIndex, ERigTransformType::Type, ERigTransformStorageType::Type>> Data;
			Data.Reserve(PoseIndexToElementIndex.Num() * 2);
 
			for (const int32& TransformElementIndex : PoseIndexToElementIndex)
			{
				if (TransformElementIndex != INDEX_NONE)
				{
					const FRigElementKeyAndIndex KeyAndIndex = Hierarchy->GetKeyAndIndex(TransformElementIndex);
					Data.Emplace(KeyAndIndex, ERigTransformType::CurrentLocal, ERigTransformStorageType::Pose);
					Data.Emplace(KeyAndIndex, ERigTransformType::CurrentGlobal, ERigTransformStorageType::Pose);
				}
			}
 
			RestoreTransformStorage(Data, false);
		}
	}
 
	ElementIndexToPoseIndex.Reset();
	PoseIndexToElementIndex.Reset();
}

Heads up that I haven’t tested that code yet, but I’m going to spend some time doing that this afternoon. I’m also going to discuss this with the dev team.

The other option you could go for here is to turn off the pose adapter codepath temporarily until we can work out why the performance is so slow in the case of your specific rig. You would do that by setting ‘ControlRig.EnableAnimNodePerformanceOptimizations’ to 0. But you’ll then go back to the pre-5.6 codepath where there’s a copy cost each frame for each bone to rig element.

Thanks,

Euan

[Attachment Removed]

Just following up since I spent a bit of time testing these changes today, and they seem okay so far. Assuming that they help to mitigate the problem, I would recommend testing them thoroughly on your end if you decide to integrate them, just in case. I also spoke to the dev team, and the aim is to refactor all of this code to potentially get rid of the pose adapter and access the transform data directly on the required bones buffer within the anim instance, although there’s no timeline for when that work will be integrated yet.

I’d also be interested in getting more information about your rig as I mentioned yesterday, how many bones, controls, curves, etc, as we’d like to test against something similar (assuming the hierarchy size is the issue here). Also, it’d be useful to see deeper into the performance capture you included in the original screenshot to see exactly where within FRigReusableElementStorage::Allocate the time is going.

[Attachment Removed]

Thanks, if those changes help then let me know and I’ll see if we can integrate them prior to any refactor happening.

In terms of the MH heads, do you happen to know which rigs specifically are running on those meshes? I’m not that familiar with the MH setup, so while I know that we ship some rigs that have over 1000 elements but I’m not sure that those are actually used at runtime vs editor time for anim in engine.

[Attachment Removed]

Perfect, thank you! I haven’t had a chance to pull a few assets together, but it’s basically heavy MetaHuman heads correlated with changing LODs that causes the spike. If we have several heads, and they change on a single frame, this gets really slow. I’ll pass along your code to the team and see if that helps :slight_smile:

[Attachment Removed]