Invalidation Box / Slate.EnableGlobalInvalidation not working UE 5

Hello,
I’m trying to optimise my UI.

In Unreal engine 4.27 it was super simple, all widgets (which are not refreshed every frame), just needed to be warpped inside an InvalidationBox.

I went from 30 FPS during big menus display to 50+ FPS.
All of this was done in just 5 minutes, it was very easy, and every time my UI was refreshed, animated, hovered, it was redrawn.

Now in Unreal 5, It’s not simple, not working, it’s confusing me :

I can see there are new modications to improve UI since 5.0 :

Slate and UMG Performance

In Unreal Engine 5.0, we have improved the performance of Slate and UMG. The main improvements are use of Global Invalidation and use of Slate Attributes, which reduce the need for updating TAttributes multiple times per frame. Detailed information about Slate Attributes can be found in Engine\Source\Runtime\SlateCore\Public\Types\SlateAttribute.h. Global Invalidation (introduced with 4.24) only updates/paints widgets that are invalidated.

It is now easier to switch your project to Global Invalidation, since SlateAttributes are updated correctly when Global Invalidation is enabled. For performance reasons, it’s better if you do not use SlateAttribute/Attribute for your game widgets. Global Invalidation is not enabled by default, and some minor SWidgets still need to be converted to the new system.

Multiple verification functions were added to test the integrity of your SWidget hierarchy. They can be enabled from the Widget Reflector.

In Unreal 5.4, I only see :

Rendering

  • Fix for HDR viewports not rendering properly when Slate.EnableGlobalInvalidation=1

Unreal 5+ in my project :

  • When I warp a widget inside an InvalidationBox, it is never refreshed anymore, even if his state changed.
  • When I Slate.EnableGlobalInvalidation to 0 or 1, nothing change, all my widgets are redrawn everytime.

I created a blank project in Unreal 5.4 to test, to be sure my widgets are not corrupted or something else. Project explaination :

  • I Created a main container widget, which has multiple children (=multiple functionnality widget), and each of them have one child (because they can be complex). So it kinda reproduces my UI architecture.
  • In the functionnality widget and in the child widget I override the OnPaint function to log when they are drawn.
  • The thing I did to optimize this UI, is inside the main container widget : I wrap all functionnality widgets inside an InvalidationBox.

Tests result :

  • 1 - With this setup, I understand the functionnality widgets should only trigger the OnPaint function every time they change :
    But they are not.
    They are visually updated, but I can’t see in the log the OnPaint log. Why ??? Does this means they are optimized ? (OnPaint is only triggered when I set them to Visible.) (This is NOT the same in my project)

  • 2 - If I desactivate all InvalidationBoxes and I enable Slate.EnableGlobalInvalidation 1 in the command line, I understand it should only trigger the OnPaint on my widgets when they really change :
    But it’s not.
    OnPaint is flooding the log. If I set : Slate.EnableGlobalInvalidation 1 or 0, it’s the same !
    Am I missing something ? (This is the same in my project)

  • 3 - I Understand I should MANUALLY call Invalidate Layout and Volatility EVERYWHERE in ALL my project’s widgets… (on all click/hover, animation, text/color/size/etc update), which will take me days !
    But how this is an upgrade ? It was fully setup in 5 minutes in Unreal 4.27 !

I can see I’m not the only one in this situation :

Does any one have an idea why we are many to have issue with those Invalidation UI things ?

1 Like

UE5.4, similar issues. Hope your post gets some traction.

I had the same problem. it is funct helped me:

  ForEachWidget(
    [](UWidget* Widget)
    {
      UInvalidationBox* InvBox = Cast<UInvalidationBox>(Widget);
      if(InvBox != nullptr)
      {
        //InvBox->InvalidateLayoutAndVolatility(); Not enough
        const auto& SafeWidget = InvBox->GetCachedWidget();
        if(SafeWidget.IsValid())
        {
          SafeWidget->Invalidate(EInvalidateWidgetReason::Prepass);
        }
      }
    }
  );

This function should be in the children UUserWidget. I am not calling widget on Tick. This is not called so often, only when the widget receives new parameters for rendering