Files
UnrealEngine/Engine/Source/Runtime/Slate/Private/Widgets/SInvalidationPanel.cpp
2025-05-18 13:04:45 +08:00

322 lines
9.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Widgets/SInvalidationPanel.h"
#include "Rendering/DrawElements.h"
#include "Misc/App.h"
#include "Application/SlateApplicationBase.h"
#include "Styling/CoreStyle.h"
#include "Layout/WidgetPath.h"
#include "HAL/IConsoleManager.h"
#include "Framework/Application/SlateApplication.h"
#include "Types/ReflectionMetadata.h"
#include "Rendering/SlateObjectReferenceCollector.h"
#include "Widgets/SNullWidget.h"
DECLARE_CYCLE_STAT(TEXT("SInvalidationPanel::Paint"), STAT_SlateInvalidationPaint, STATGROUP_Slate);
void ConsoleVariableEnableInvalidationPanelsChanged(IConsoleVariable*);
/** True if we should allow widgets to be cached in the UI at all. */
static bool bInvalidationPanelsEnabled = true;
FAutoConsoleVariableRef CVarEnableInvalidationPanels(
TEXT("Slate.EnableInvalidationPanels"),
bInvalidationPanelsEnabled,
TEXT("Whether to attempt to cache any widgets through invalidation panels."),
FConsoleVariableDelegate::CreateStatic(ConsoleVariableEnableInvalidationPanelsChanged));
#if WITH_SLATE_DEBUGGING
static bool bAlwaysInvalidate = false;
FAutoConsoleVariableRef CVarAlwaysInvalidate(
TEXT("Slate.AlwaysInvalidate"),
bAlwaysInvalidate,
TEXT("Forces invalidation panels to cache, but to always invalidate."));
#endif // WITH_SLATE_DEBUGGING
SInvalidationPanel::SInvalidationPanel()
: HittestGrid(MakeShared<FHittestGrid>())
, bCanCache(true)
, bPaintedSinceLastPrepass(true)
, bWasCachable(false)
{
bHasCustomPrepass = true;
SetInvalidationRootWidget(*this);
SetInvalidationRootHittestGrid(HittestGrid.Get());
SetCanTick(false);
SetVolatilePrepass(GetCanCache());
LastIncomingColorAndOpacity = FLinearColor::White;
FSlateApplicationBase::Get().OnGlobalInvalidationToggled().AddRaw(this, &SInvalidationPanel::OnGlobalInvalidationToggled);
}
void SInvalidationPanel::Construct( const FArguments& InArgs )
{
ChildSlot
[
InArgs._Content.Widget
];
#if SLATE_VERBOSE_NAMED_EVENTS
DebugName = InArgs._DebugName;
DebugTickName = InArgs._DebugName + TEXT("_Tick");
DebugPaintName = InArgs._DebugName + TEXT("_Paint");
#endif
}
SInvalidationPanel::~SInvalidationPanel()
{
InvalidateRootChildOrder();
if (FSlateApplicationBase::IsInitialized())
{
FSlateApplicationBase::Get().OnGlobalInvalidationToggled().RemoveAll(this);
}
}
void ConsoleVariableEnableInvalidationPanelsChanged(IConsoleVariable*)
{
// If the cache changed, the parent's InvalidationRoot need to rebuild its list
//since InvalidationPanel cannot be nested in regular mode.
if (!GSlateEnableGlobalInvalidation)
{
FSlateApplicationBase::Get().OnGlobalInvalidationToggled().Broadcast(GSlateEnableGlobalInvalidation);
}
}
#if WITH_SLATE_DEBUGGING
bool SInvalidationPanel::AreInvalidationPanelsEnabled()
{
return bInvalidationPanelsEnabled;
}
void SInvalidationPanel::EnableInvalidationPanels(bool bEnable)
{
if (bInvalidationPanelsEnabled != bEnable)
{
bInvalidationPanelsEnabled = bEnable;
// If the cache changed, the parent's InvalidationRoot need to rebuild its list
//since InvalidationPanel cannot be nested in regular mode.
if (!GSlateEnableGlobalInvalidation)
{
FSlateApplicationBase::Get().OnGlobalInvalidationToggled().Broadcast(GSlateEnableGlobalInvalidation);
}
}
}
#endif
bool SInvalidationPanel::GetCanCache() const
{
return bCanCache && !GSlateEnableGlobalInvalidation && bInvalidationPanelsEnabled;
}
void SInvalidationPanel::OnGlobalInvalidationToggled(bool bGlobalInvalidationEnabled)
{
InvalidateRootChildOrder();
ClearAllFastPathData(true);
SetVolatilePrepass(GetCanCache());
Invalidate(EInvalidateWidgetReason::LayoutAndVolatility);
}
bool SInvalidationPanel::UpdateCachePrequisites(FSlateWindowElementList& OutDrawElements, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, int32 LayerId, const FWidgetStyle& InWidgetStyle) const
{
bool bNeedsRecache = false;
#if WITH_SLATE_DEBUGGING
bNeedsRecache = bAlwaysInvalidate;
#endif
// We only need to re-cache if the incoming layer is higher than the maximum layer Id we cached at,
// we do this so that widgets that appear and live behind your invalidated UI don't constantly invalidate
// everything above it.
if (LayerId > LastIncomingLayerId)
{
LastIncomingLayerId = LayerId;
bNeedsRecache = true;
}
if ( AllottedGeometry.GetLocalSize() != LastAllottedGeometry.GetLocalSize() || AllottedGeometry.GetAccumulatedRenderTransform() != LastAllottedGeometry.GetAccumulatedRenderTransform() )
{
LastAllottedGeometry = AllottedGeometry;
bNeedsRecache = true;
}
// If our clip rect changes size, we've definitely got to invalidate.
const FVector2D ClipRectSize = MyCullingRect.GetSize().RoundToVector();
if ( ClipRectSize != LastClipRectSize )
{
LastClipRectSize = ClipRectSize;
bNeedsRecache = true;
}
TOptional<FSlateClippingState> ClippingState = OutDrawElements.GetClippingState();
if (LastClippingState != ClippingState)
{
LastClippingState = ClippingState;
bNeedsRecache = true;
}
if (LastIncomingColorAndOpacity != InWidgetStyle.GetColorAndOpacityTint())
{
LastIncomingColorAndOpacity = InWidgetStyle.GetColorAndOpacityTint();
bNeedsRecache = true;
}
return bNeedsRecache;
}
void SInvalidationPanel::SetCanCache(bool InCanCache)
{
if (bCanCache != InCanCache)
{
bCanCache = InCanCache;
SetVolatilePrepass(GetCanCache());
InvalidateRootChildOrder();
}
}
FChildren* SInvalidationPanel::GetChildren()
{
if (GetCanCache())
{
return &FNoChildren::NoChildrenInstance;
}
else
{
return SCompoundWidget::GetChildren();
}
}
#if WITH_SLATE_DEBUGGING
FChildren* SInvalidationPanel::Debug_GetChildrenForReflector()
{
return SCompoundWidget::GetChildren();
}
#endif
int32 SInvalidationPanel::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
#if SLATE_VERBOSE_NAMED_EVENTS
SCOPED_NAMED_EVENT_FSTRING(DebugPaintName, FColor::Purple);
#endif
SCOPE_CYCLE_COUNTER(STAT_SlateInvalidationPaint);
bPaintedSinceLastPrepass = true;
SInvalidationPanel* MutableThis = const_cast<SInvalidationPanel*>(this);
const bool bCanCacheThisFrame = GetCanCache();
if (bCanCacheThisFrame != bWasCachable)
{
MutableThis->InvalidateRootChildOrder();
bWasCachable = bCanCacheThisFrame;
}
if(bCanCacheThisFrame)
{
// Copy hit test grid settings from the root
const bool bHittestCleared = HittestGrid->SetHittestArea(Args.RootGrid.GetGridOrigin(), Args.RootGrid.GetGridSize(), Args.RootGrid.GetGridWindowOrigin());
HittestGrid->SetOwner(this);
HittestGrid->SetCullingRect(MyCullingRect);
FPaintArgs NewArgs = Args.WithNewHitTestGrid(HittestGrid.Get());
// Copy the current user index into the new grid since nested hit test grids should inherit their parents user id
NewArgs.GetHittestGrid().SetUserIndex(Args.RootGrid.GetUserIndex());
check(!GSlateEnableGlobalInvalidation);
const bool bRequiresRecache = UpdateCachePrequisites(OutDrawElements, AllottedGeometry, MyCullingRect, LayerId, InWidgetStyle);
if (bHittestCleared || bRequiresRecache)
{
// @todo: Overly aggressive?
MutableThis->InvalidateRootLayout(this);
}
// The root widget is our child. We are not the root because we could be in a parent invalidation panel. If we are nested in another invalidation panel, our OnPaint was called by that panel
FSlateInvalidationContext Context(OutDrawElements, InWidgetStyle);
Context.bParentEnabled = bParentEnabled;
Context.bAllowFastPathUpdate = true;
Context.LayoutScaleMultiplier = GetPrepassLayoutScaleMultiplier();
Context.PaintArgs = &NewArgs;
Context.IncomingLayerId = LayerId;
Context.CullingRect = MyCullingRect;
const FSlateInvalidationResult Result = MutableThis->PaintInvalidationRoot(Context);
const bool bInheritedHittestability = Args.GetInheritedHittestability();
const bool bOutgoingHittestability = bInheritedHittestability && GetVisibility().AreChildrenHitTestVisible();
// add our widgets to the root hit test grid
if (bOutgoingHittestability)
{
Args.GetHittestGrid().AddGrid(HittestGrid);
}
return Result.MaxLayerIdPainted;
}
else
{
#if SLATE_VERBOSE_NAMED_EVENTS
SCOPED_NAMED_EVENT_TEXT("SInvalidationPanel Uncached", FColor::Emerald);
#endif
return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
}
}
void SInvalidationPanel::SetContent(const TSharedRef< SWidget >& InContent)
{
ChildSlot
[
InContent
];
InvalidateRootChildOrder();
}
bool SInvalidationPanel::CustomPrepass(float LayoutScaleMultiplier)
{
bPaintedSinceLastPrepass = false;
if (GetCanCache())
{
if (NeedsPrepass())
{
SetNeedsSlowPath(true);
}
ProcessInvalidation();
if (NeedsSlowPath())
{
FChildren* Children = SCompoundWidget::GetChildren();
Prepass_ChildLoop(LayoutScaleMultiplier, Children);
}
return false;
}
else
{
return true;
}
}
bool SInvalidationPanel::Advanced_IsInvalidationRoot() const
{
return GetCanCache();
}
const FSlateInvalidationRoot* SInvalidationPanel::Advanced_AsInvalidationRoot() const
{
return GetCanCache() ? this : nullptr;
}
TSharedRef<SWidget> SInvalidationPanel::GetRootWidget()
{
return GetCanCache() ? SCompoundWidget::GetChildren()->GetChildAt(0) : SNullWidget::NullWidget;
}
int32 SInvalidationPanel::PaintSlowPath(const FSlateInvalidationContext& Context)
{
return SCompoundWidget::OnPaint(*Context.PaintArgs, GetPaintSpaceGeometry(), Context.CullingRect, *Context.WindowElementList, Context.IncomingLayerId, Context.WidgetStyle, Context.bParentEnabled);
}