Help with GC crash when creating a material instance in a uwidget

Hello,

I get a crash in packaged game when i try to use a UMaterialInstanceDynamic inside a class inheriting from UWidget.

The UMaterialInstanceDynamic is declared with the UPROPERTY(), and created like this:


TextureImageMaterialInstance = UMaterialInstanceDynamic::Create(TexturesParentMaterial, this);

It works well in the editor, but when packaging the game, i get a crash at first Garbage Collection with the following message:
CrashYag.png

The log is then full of such messages:


[2021.01.22-21.58.26:004] 39]LogGarbage: Warning: Disregard for GC object YagUIImage /Game/Yag/UI/Tabs/BP_TabLinesFdP.BP_TabLinesFdP_C:WidgetTree.Image_Roll3DDice referencing MaterialInstanceDynamic /Game/Yag/UI/Tabs/BP_TabLinesFdP.BP_TabLinesFdP_C:WidgetTree.Image_Roll3DDice.MaterialInstanceDynamic_2147471025 which is not part of root set

I tried to change the outer object in the create function (world, player controller, this) but i always get the Disregard for GC crash.

Could anyone point me in the right direction to create a material instance to be used in a UWidget ?

Thanks
Cedric

Where are you creating it? You shouldn’t be doing something like that in a constructor, for example.

Hi,

I’m creating it in a setup function.


void UYagUIImage::SetupYagUIImage(FUIStyleStruct TargetUIStyleStruc, UWorld* ThisWorld)
{
if (!ThisWorld) return;
if (!ThisWorld->GetFirstPlayerController()) return;

//if (!TexturesParentMaterial) TexturesParentMaterial = LoadObject<UMaterialInterface>(NULL, TEXT("/Game/Yag/UI/White/Mat_PNG_HUD"));
//if (!TexturesParentMaterial) return;

if (!TextureImageMaterialInstance) TextureImageMaterialInstance = UMaterialInstanceDynamic::Create(TexturesParentMaterial, this);
if (!TextureImageMaterialInstance) return;

SetBrushFromMaterial(TextureImageMaterialInstance);

SetFrontTexture(ImageFrontTexture);

TextureImageMaterialInstance->SetScalarParameterValue(FName("FrontOpacity"), 1.f);
TextureImageMaterialInstance->SetVectorParameterValue(FName("FrontColor"), TargetUIStyleStruc.YagFontColor);

TextureImageMaterialInstance->SetScalarParameterValue(FName("BGOpacity"), 1.f);
TextureImageMaterialInstance->SetVectorParameterValue(FName("BGColor"), TargetUIStyleStruc.YagBorderBackgroundColor);
//TextureImageMaterialInstance->SetScalarParameterValue(FName("BGVignetteRadius"), .68f);
TextureImageMaterialInstance->SetScalarParameterValue(FName("BGVignetteRadius"), 1.f);

}

The parent material (TexturesParentMaterial) is currently created in the constructor, but i get the same error when creating it in the function (the commented code).

The whole idea is to allow the user to choose a custom UI skin through a structure FUIStyleStruct that contains various colors/opacities and other UI skin related parameters.

If i understood correctly the GC is creating a tree of safe objects (the root set) and the material instance is not part of it.

My custom image is created aside from this root set (hence it is classified as a "disregard for GC "object).

So my current understanding is that a “disregard for GC” object (my image) is referencing an object (the mat inst) that is outside the safe zone of CG (the root set) and could be deleted anytime, which creates a risk of crash as the image would reference a non-existant object.

I thought declaring the mat inst as a UPROPERTY() would do it but it doesn’t help.

So the current logic behind my code is to try to attach the material instance to an object belonging to the safe zone (GC root set) through the outer.

To complicate things, UWidget::GetWorld() seems to always return a nullptr, hence the UWorld parameter, to try things with the “real” UWorld.
And i’m confused about what is created in the editor and what is not (is there a world in the editor ? is there a player controller ?).

I tried various things with no success


UMaterialInstanceDynamic::Create(TexturesParentMaterial, this);
UMaterialInstanceDynamic::Create(TexturesParentMaterial, GetWorld());
UMaterialInstanceDynamic::Create(TexturesParentMaterial, ThisWorld);
UMaterialInstanceDynamic::Create(TexturesParentMaterial, ThisWorld->GetFirstPlayerController());


So all in all, after spending a lot of time trying to understand what’s going on and making a lot of tests, i’m left in complete confusion because:

  • i’m not even sure i correctly understand my problem
  • i don’t know what is and isn’t in the GC root set
  • I read on the net that since 4.25 the slate UI element are not treated by GC like other UObjects

Hence this post.

Cedric

I found a workaround that works in PIE and in a packaged game.

I wouldn’t call it a solution because it is inelegant and ugly, but at least this is something that works.
And frankly, after a whole week of struggling with trying (and failing) to understand how GC, Editor and objet life cycle work together, as poor as it is it comes as a relief.

Here’s the code for the function that works.
I’m not even sure my explanations are correct, this is just the state of my current undestanding after all my tests/trials/errors.
I call “static” what is made in the UMG editor (like adding my custom image class to a button manually), and “dynamic” what is created in code (=using NewObject() in for loops to create dynamic lists).

It is very important:

  • to use the native GetWorld() function for static images, because otherwise (if using a provided ThisWorld parameter) they get parented to an ingame object (first PC in this code) and that will both mess the editor and crash the game due to GC disregard.
  • to provide a world for dynamic images, because they never seem to have one

So this function has to be called with nullprt when dealing with static images:

And can be called with the world of the calling object (generally a UUserWidget) when dealing with dynamic image:

RebuildDynamic.png

Again, quite crappy but it works.
I’m really open to suggestions if anyone has a more elegant/clever way to deal with that nasty mix of GC + PIE/packaged + static/dynamic + Slate UI questions.
As for me, i throw in the towel.

Cedric