언리얼 엔진의 UMG 최적화 가이드라인 | 언리얼 엔진 5.4 문서 | Epic Developer Community
UI 최적화에는 다양한 방법이 있는데 이번 포스팅에서는 그 중 Invalidation에 대해 설명한다.
그 외의 방법들은 위 링크 참고
언리얼 엔진 슬레이트 및 UMG의 인밸리데이션 | 언리얼 엔진 5.4 문서 | Epic Developer Community
위젯을 페인팅하는 빈도를 제한하여 UI의 CPU 사용을 줄이는 시스템을 말한다.
슬레이트 위젯의 정보를 캐싱하여, 위젯에 변경 사항이 있을 경우에만 다시 페인팅하고 변경 사항이 없을 때는 캐싱된 정보를 사용한다.
Invalidation Box로 래핑된 위젯은 자식 위젯의 정보를 캐싱하고, 변경 사항이 없으면 Prepass, Tick, Paint되지 않는다.
이를 통해 슬레이트 렌더링 속도를 올릴 수 있다.
위젯에 어떤 변경사항이 있었는지에 따라 Invalidation이 다르게 동작한다.
Child Invalidation에 Layout Invalidation이, Paint Invalidation이 포함되고,
Layout Invalidation에 Paint Invalidation이 포함되어 있다고 볼 수 있다.
따라서 Paint -> Layout -> Child Invalidation 순으로 빠르다.
예시
void SProgressBar::SetFillColorAndOpacity(TAttribute< FSlateColor > InFillColorAndOpacity)
{
if(!FillColorAndOpacity.IdenticalTo(InFillColorAndOpacity))
{
FillColorAndOpacity = InFillColorAndOpacity;
Invalidate(EInvalidateWidget::Paint);
}
}
void SProgressBar::SetBorderPadding(TAttribute< FVector2D > InBorderPadding)
{
if(!BorderPadding.IdenticalTo(InBorderPadding))
{
BorderPadding = InBorderPadding;
Invalidate(EInvalidateWidget::Layout);
}
}
Color 변경 시에는 Paint Inavlidation, Padding과 같은 transform 변경 시에는 Layout Invalidation이 발생한다.
void SWidget::AssignParentWidget(TSharedPtr<SWidget> InParent)
{
// ...
ParentWidgetPtr = InParent;
// ...
if (InParent.IsValid())
{
InParent->Invalidate(EInvalidateWidgetReason::ChildOrder);
}
}
위젯이 특정 위젯에 attach되는 경우, 즉 위젯 트리가 변경된 경우 Child Invalidation이 발생한다.
void SWidget::Invalidate(EInvalidateWidgetReason InvalidateReason)
{
SLATE_CROSS_THREAD_CHECK();
SCOPED_NAMED_EVENT_TEXT("SWidget::Invalidate", FColor::Orange);
const bool bWasVolatile = IsVolatileIndirectly() || IsVolatile();
// Backwards compatibility fix: Its no longer valid to just invalidate volatility since we need to repaint to cache elements if a widget becomes non-volatile. So after volatility changes force repaint
if (InvalidateReason == EInvalidateWidgetReason::Volatility)
{
InvalidateReason = EInvalidateWidgetReason::PaintAndVolatility;
}
const bool bVolatilityChanged = EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::Volatility) ? Advanced_InvalidateVolatility() : false;
if (EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::ChildOrder) || !PrepassLayoutScaleMultiplier.IsSet())
{
InvalidatePrepass();
}
if(FastPathProxyHandle.IsValid(this))
{
// Current thinking is that visibility and volatility should be updated right away, not during fast path invalidation processing next frame
if (EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::Visibility))
{
SCOPED_NAMED_EVENT(SWidget_UpdateFastPathVisibility, FColor::Red);
TSharedPtr<SWidget> ParentWidget = GetParentWidget();
UpdateFastPathVisibility(ParentWidget.IsValid() ? !ParentWidget->bInvisibleDueToParentOrSelfVisibility : false, false, FastPathProxyHandle.GetInvalidationRoot()->GetHittestGrid());
}
if (bVolatilityChanged)
{
SCOPED_NAMED_EVENT(SWidget_UpdateFastPathVolatility, FColor::Red);
TSharedPtr<SWidget> ParentWidget = GetParentWidget();
UpdateFastPathVolatility(ParentWidget.IsValid() ? ParentWidget->IsVolatile() || ParentWidget->IsVolatileIndirectly() : false);
ensure(!IsVolatile() || IsVolatileIndirectly() || EnumHasAnyFlags(UpdateFlags, EWidgetUpdateFlags::NeedsVolatilePaint));
}
FastPathProxyHandle.MarkWidgetDirty(InvalidateReason);
}
else
{
#if WITH_SLATE_DEBUGGING
FSlateDebugging::BroadcastWidgetInvalidate(this, nullptr, InvalidateReason);
#endif
UE_TRACE_SLATE_WIDGET_INVALIDATED(this, nullptr, InvalidateReason);
}
}
SWidget::Invalidate
에서 Invalidation Type에 따라 다르게 처리해주는 것을 볼 수 있다.
모든 UI를 래핑하고 있는 SWindow에 Invalidation을 적용하며, 기타 다른 Invalidation Box는 비활성화된다.
Slate.EnableGlobalInvalidation
를 True로 변경하면 Global Invalidation을 활성화할 수 있다.
이러한 기능들을 통해 단일 프레임에서 UI가 생성하는 draw call의 수를 줄일 수 있다.
각 Retainer Panel마다 개별 위젯의 Invalidation 데이터를 사용하기 때문에, Retainer Panel은 다시 페인팅될 때 비용이 크며 Invalidation Box를 사용할 때보다 더 많은 메모리를 사용한다.
따라서 UI의 CPU 사용을 줄이려면 Invalidation Box를 사용하는 것이 좋다.