Slate? Garbage collection crash during map tranistion

We are seeing an occasional garbage collection crash when transitioning from our game play maps back to our front end maps during the garbage collection after everything is destroyed

UE4Editor-Engine.dll!internalCheckObjectPointer(UObject * p, UClass * pClass)
UE4Editor-SlateRHIRenderer.dll!FSlateBaseUTextureResource::AccessRHIResource()
UE4Editor-SlateRHIRenderer.dll!FSlateRHIRenderingPolicy::DrawElements(FRHICommandListImmediate & RHICmdList, FSlateBackBuffer & BackBuffer, const FMatrix & ViewProjectionMatrix, const TArray & RenderBatches, bool bAllowSwitchVerticalAxis)
UE4Editor-SlateRHIRenderer.dll!FSlateRHIRenderer::DrawWindow_RenderThread(FRHICommandListImmediate & RHICmdList, const FSlateRHIRenderer::FViewportInfo & ViewportInfo, FSlateWindowElementList & WindowElementList, bool bLockToVsync, bool bClear) 
UE4Editor-SlateRHIRenderer.dll!TGraphTask<`FSlateRHIRenderer::DrawWindows_Private'::`33'::EURCMacro_SlateDrawWindowsCommand>::ExecuteTask(TArray & NewTasks, ENamedThreads::Type CurrentThread)
UE4Editor-Core.dll!FNamedTaskThread::ProcessTasksNamedThread(int QueueIndex, bool bAllowStall)
UE4Editor-Core.dll!FNamedTaskThread::ProcessTasksUntilQuit(int QueueIndex)
UE4Editor-RenderCore.dll!RenderingThreadMain(FEvent * TaskGraphBoundSyncEvent)
UE4Editor-RenderCore.dll!FRenderingThread::Run()
UE4Editor-Core.dll!FRunnableThreadWin::Run()
UE4Editor-Core.dll!FRunnableThreadWin::GuardedRun()

We are looking into this but wondering if this looks familiar?

Hmm looks like I’ve narrowed this down to the loading screen which we just brought over from the shooter game example on like version 4.9 and haven’t touched since.

Well I have narrowed the problem down to this function when slate paints

FSlateShaderResourceProxy* FSlateRHIResourceManager::FindOrCreateDynamicTextureResource(const FSlateBrush& InBrush)

Specifically line 660

TSharedPtr TextureResource = DynamicResourceMap.GetUTextureResource(TextureObject);

It looks like the DynamicResourceMap will find a stale entry for my texture object. I’m not exactly sure why though as all the Keys display as STALE in the debugger. But it possibly could be a side effect of the Garbage Collection that is occuring while the map is transitioning

I’m wondering if 2 stale/invalid weak pointers should be considered equal. The concept seems…not right. They could be completely different invalid/stale weak pointers that used to hold completely different things. Does invalidating both now make them equal?

Hi Eric,

It’s possible that this is an issue stemming from your resource UObject being managed in your loading screen thread. Try removing the FGCObject inheritance, and calling AddToRoot right after creating the ResourceObject to ensure it won’t be garbage collected. You’ll then need to call RemoveFromRoot once the loading screen is destroyed.

Let me know if that works for you, otherwise we’ll keep digging.

Best,

Cody

Hi Eric,

Sorry for the delay, I’m looking in to this for you. Can you post the code that you’re using to invoke the loading screen? Are you using seamless travel? It does sound like something is causing GC to wipe out textures being used by your loading screen.

Hi Cody,

The code is virtually a copy and paste from the shooter game except we are not using a separate module for the loading screen. But anyways I’m registering for preload map

void UOurGameInstance::Init()
{
    FCoreUObjectDelegates::PreLoadMap.AddUObject(this, &UOurGameInstance::BeginLoadingScreen);
}
    
void UOurGameInstance::BeginLoadingScreen(const FString& MapName)
{
    FLoadingScreenAttributes LoadingScreen;
    LoadingScreen.bAutoCompleteWhenLoadingCompletes = false;
    LoadingScreen.WidgetLoadingScreen = SNew( SOurLoadingScreen );
     
    GetMoviePlayer()->SetupLoadingScreen(LoadingScreen);
}

The loading screen is defined like so

struct FOurLoadingScreenBrush : public FSlateDynamicImageBrush, public FGCObject
{
	FOurLoadingScreenBrush( const FName InTextureName, const FVector2D& InImageSize )
		: FSlateDynamicImageBrush( InTextureName, InImageSize )
	{
		ResourceObject = LoadObject( NULL, *InTextureName.ToString() );
	}

	virtual void AddReferencedObjects(FReferenceCollector& Collector)
	{
		if( ResourceObject )
		{
			Collector.AddReferencedObject(ResourceObject);
		}
	}
};

class SOurLoadingScreen : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SOurLoadingScreen) {}
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

private:
	EVisibility GetLoadIndicatorVisibility() const
	{
		return EVisibility::Visible;
	}

	/** loading screen image brush */
	TSharedPtr LoadingScreenBrush;
};

What I’m finding though is FOurLoadingScreenBrush::AddReferencedObjects isn’t initially being called until after FSlateRHIResourceManager::FindOrCreateDynamicTextureResource. I assume the calls are made on different threads. So on that first frame the texture object in the slate brush is falsely considered invalid. So it picks the first invalid object in the DynamicResourceMap as it’s resource. I assume AddReferencedObjects does some registration with GC so it knows about objects held with shared pointers. This seems to be coming too late and the object is considered invalid by GC and subsequently by “weak pointer == comparisons”, which is the keys used by the DynamicResourceMap. FYI, I can really only get this to appear when running the editor in development with the -game flag