[UE 5.6] - Memory growth from InitDynamicSkelMeshObjectDataGPUSkin

Hi,

We’re looking at memory leaks in our project and I found a case that stood out to me in UE 5.6. I’m looking for clarification on how this should be working or if there are any known issues.

When we run the game on 5.6 and let it sit idle, memory insights reports leaking data from FDynamicSkelMeshObjectDataGPUSkin::InitDynamicSkelMeshObjectDataGPUSkin

I see that being called here:

	// create the new dynamic data for use by the rendering thread
	// this data is only deleted when another update is sent
	FDynamicSkelMeshObjectDataGPUSkin* NewDynamicData = FDynamicSkelMeshObjectDataGPUSkin::AllocDynamicSkelMeshObjectDataGPUSkin();
	NewDynamicData->InitDynamicSkelMeshObjectDataGPUSkin(
		InDynamicData,
		InSceneProxy,
		InSkinnedAsset,
		SkeletalMeshRenderData,
		this,
		LODIndex,
		InActiveMorphTargets,
		InMorphTargetWeights,
		PreviousBoneTransformUpdateMode,
		InExternalMorphWeightData);

	if (!UpdateHandle.IsValid() || !UpdateHandle.Update(NewDynamicData))
	{
		FGPUSkinCache* GPUSkinCache = InSceneProxy ? InSceneProxy->GetScene().GetGPUSkinCache() : nullptr;
		ENQUEUE_RENDER_COMMAND(SkelMeshObjectUpdateDataCommand)(UE::RenderCommandPipe::SkeletalMesh,
			[this, GPUSkinCache, NewDynamicData](FRHICommandList& RHICmdList)
		{
			FScopeCycleCounter Context(GetStatId());
			UpdateDynamicData_RenderThread(RHICmdList, GPUSkinCache, NewDynamicData);
		});
	}

That above call to UpdateDynamicData_RenderThread also looks to be what clears it - is this correct?

void FSkeletalMeshObjectGPUSkin::UpdateDynamicData_RenderThread(FRHICommandList& RHICmdList, FGPUSkinCache* GPUSkinCache, FDynamicSkelMeshObjectDataGPUSkin* InDynamicData)
{
	TRACE_CPUPROFILER_EVENT_SCOPE(GPUSkin::UpdateDynamicData_RT);

	SCOPE_CYCLE_COUNTER(STAT_GPUSkinUpdateRTTime);
	check(InDynamicData != nullptr);
	CA_ASSUME(InDynamicData != nullptr);

	bMorphNeedsUpdate = FDynamicSkelMeshObjectDataGPUSkin::IsMorphUpdateNeeded(DynamicData, InDynamicData);

	if (DynamicData)
	{
		FDynamicSkelMeshObjectDataGPUSkin::FreeDynamicSkelMeshObjectDataGPUSkin(DynamicData);
	}

But that code only fires if the below code returns false:

bool FSkeletalMeshUpdateChannel::Update(const FSkeletalMeshUpdateHandle& Handle, FSkeletalMeshDynamicData* MeshDynamicData)
{
	check(IsInGameThread() || IsInParallelGameThread());
	check(MeshDynamicData);
	check(Handle.Channel == this);

	if (GUseSkeletalMeshUpdater)
	{
		FOp Op;
		Op.HandleIndex = Handle.Index;
		Op.Type = FOp::EType::Update;
		Op.Data_Update.MeshDynamicData = MeshDynamicData;
	
		check(OpQueue);
		OpQueue->Queue.Enqueue(Op);
		OpQueue->NumUpdates.fetch_add(1, std::memory_order_relaxed);
		OpQueue->Num.fetch_add(1, std::memory_order_relaxed);
		return true;
	}
	return false;
}

What I’m unclear about is how this code could ever be getting called if a project is running with GUseSkeletalMeshUpdater enabled (which we are). Is there something else that should be freeing this data?

Steps to Reproduce
Launch into game, let it idle for a period of time, use memory insights to look for leaks and growth

Hi there,

I’ve started looking into this and haven’t had much luck reproducing it. Could you please share some more info on the context of this memory leak? Are you seeing this occur in new projects or only in your existing one? If the latter is the case, would it be possible to provide a minimal repro project?

Cheers,

Louis