
https://velog.io/@cuppizza/언리얼-UI-최적화
여기 이론에서 배운 내용을 실제 저의 프로젝트에서 적용해, 프로젝트를 개선해본 시도.
여러 UI 최적화 방법 중에서, DrawWindow 함수 관련 최적화 -> 그 중에서도 Visibility 관련 옵션으로 최적화하는 것을 시도했다.
Slate::DrawWindows() 최적화
대충 이러한 UMG 위젯 트리 구조다.
해상도 변경에 따라 배경 이미지는 비율을 잃지 않기 위해 size box 하위에 scale box 하위로 일괄 관리.
아래 코드에서 Window는 SelfHitTestInvisible로 하고, Img는 HitTestInvisible로 한 이유다.
SelfHitTestInvisible 을 선택했다.HitTestInvisible 을 선택했다.void UCMUserWidget::TurnWinWindow(bool IsTurnOn)
{
// 마우스 위치 해제 필요
ACMPlayerController* PlayerController = Cast<ACMPlayerController>(UGameplayStatics::GetPlayerController(GetWorld(), 0));
if(PlayerController)
{
PlayerController->SetPlayerInputMode(false);
}
if(IsTurnOn == true)
{
// 마지막 레벨이면 엔딩 띄우기
UCMGameInstance* GameInstance = Cast<UCMGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
if(GameInstance && GameInstance->GetGameLevel() == GameInstance->MaxLevel)
{
//******************************************************************
EndingWindow->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
EndingImg->SetVisibility(ESlateVisibility::HitTestInvisible);
//******************************************************************
}
else
{
//******************************************************************
WinWindow->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
WinImg->SetVisibility(ESlateVisibility::HitTestInvisible);
//******************************************************************
}
//******************************************************************
BlackImg->SetVisibility(ESlateVisibility::HitTestInvisible);
//******************************************************************
InGameWindow->SetVisibility(ESlateVisibility::Hidden);
}
else
{
WinWindow->SetVisibility(ESlateVisibility::Hidden);
WinImg->SetVisibility(ESlateVisibility::Hidden);
Black_Image->SetVisibility(ESlateVisibility::Hidden);
}
}
HitTestInvisible 옵션을 선택했다. 인게임에서는 마우스로 방향 전환을 해야하기 때문이다.void UCMUserWidget::ClickedPlayBtn()
{
// 버튼과 기존 이미지 Hidden
// 마우스 위치 고정 필요
// ..
InGameWindow->SetVisibility(ESlateVisibility::HitTestInvisible);
HitTestInvisiblevoid UCMUserWidget::ConvertGunUI(uint8 InIsLeft)
{
ESlateVisibility visibleLeft = (InIsLeft == 1) ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Hidden;
ESlateVisibility visibleRight = (InIsLeft == 0) ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Hidden;
LeftGunWindow->SetVisibility(visibleLeft);
RightGunWindow->SetVisibility(visibleRight);
}


Slate::DrawWindows()의 Inclusive Time과 Exclusive Time이 줄어들었다.
특히 Exclusive Time이 1.46%에서 0.07%로 크게 감소하였다.
(몇 번 더 테스트 해봤는데, 최대 0.58%, 1%를 더 이상 넘지 않는다.)
Inclusive Time과 Exclusive Time의 감소 원인:
DrawWindows() 함수 내에서 HitTestGrid 에 관련된 어떤 연산을 줄였는지 확인해보자.
void FWidgetRenderer::DrawWindow(
FRenderTarget* RenderTarget,
FHittestGrid& HitTestGrid,
TSharedRef<SWindow> Window,
FGeometry WindowGeometry,
FSlateRect WindowClipRect,
float DeltaTime,
bool bDeferRenderTargetUpdate)
{
FPaintArgs PaintArgs(nullptr, HitTestGrid, FVector2D::ZeroVector, FApp::GetCurrentTime(), DeltaTime);
DrawWindow(PaintArgs, RenderTarget, Window, WindowGeometry, WindowClipRect, DeltaTime, bDeferRenderTargetUpdate);
}
void FWidgetRenderer::DrawWindow(
const FPaintArgs& PaintArgs,
FRenderTarget* RenderTarget,
TSharedRef<SWindow> Window,
FGeometry WindowGeometry,
FSlateRect WindowClipRect,
float DeltaTime,
bool bDeferRenderTargetUpdate)
{
#if !UE_SERVER
FSlateRenderer* MainSlateRenderer = FSlateApplication::Get().GetRenderer();
FScopeLock ScopeLock(MainSlateRenderer->GetResourceCriticalSection());
if (LIKELY(FApp::CanEverRender()))
{
if (bPrepassNeeded)
{
// Ticking can cause geometry changes. Recompute
Window->SlatePrepass(WindowGeometry.Scale);
}
PaintArgs.GetHittestGrid().SetHittestArea(WindowClipRect.GetTopLeft(), WindowClipRect.GetSize());
//******************강조**********************
if (bClearHitTestGrid)
{
// Prepare the test grid
PaintArgs.GetHittestGrid().Clear();
}
//******************강조**********************
{
// Get the free buffer & add our virtual window
ISlate3DRenderer::FScopedAcquireDrawBuffer ScopedDrawBuffer{ *Renderer, bDeferRenderTargetUpdate };
FSlateWindowElementList& WindowElementList = ScopedDrawBuffer.GetDrawBuffer().AddWindowElementList(Window);
// Paint the window
int32 MaxLayerId = Window->Paint(
PaintArgs,
WindowGeometry, WindowClipRect,
WindowElementList,
0,
FWidgetStyle(),
Window->IsEnabled());
//MaxLayerId = WindowElementList.PaintDeferred(MaxLayerId);
DeferredPaints = WindowElementList.GetDeferredPaintList();
Renderer->DrawWindow_GameThread(ScopedDrawBuffer.GetDrawBuffer());
ScopedDrawBuffer.GetDrawBuffer().ViewOffset = ViewOffset;
FRenderThreadUpdateContext RenderThreadUpdateContext =
{
&(ScopedDrawBuffer.GetDrawBuffer()),
(FApp::GetCurrentTime() - GStartTime),
static_cast<float>(FApp::GetDeltaTime()),
(FPlatformTime::Seconds() - GStartTime),
static_cast<float>(FApp::GetDeltaTime()),
RenderTarget,
Renderer.Get(),
bClearTarget
};
MainSlateRenderer->AddWidgetRendererUpdate(RenderThreadUpdateContext, bDeferRenderTargetUpdate);
}
}
#endif // !UE_SERVER
}
DrawWindow() 함수 내에서 HitTestGrid를 지우고 재설정하는 연산을 생략하게 된다.
if (bClearHitTestGrid)
{
// Prepare the test grid
PaintArgs.GetHittestGrid().Clear();
}
UMG 위젯 트리의 깊이가 최대 6으로 깊은 편이어서, DrawWindow() 함수의 오버헤드를 줄여야 한다.
해상도와 상관없이 위젯 위치가 자동으로 정렬 되도록 의도하여 구현하다보니, 위젯의 트리가 깊어 위젯이 많고 이를 수정하기 곤란할 때.
Hit Test Grid에 위젯이 많이 추가 될 수록 DrawWindow() 함수 내에서 HitTestGrid를 지우고 재설정하는 연산이 증가한다.
다른 방법으로 DrawWindow() 함수의 오버헤드를 줄이기 위해서 상호작용이 필요 없는 위젯은 HitTestGrid에서 제거하는 옵션(HitTestInvisible, SelfHitTestInvisible)으로 수정한다.
Slate::DrawWindows()의 Inclusive Time과 Exclusive Time이 줄어들었다.
특히 Exclusive Time이 1.46%에서 0.07%로 크게 감소하였다.
Inclusive Time과 Exclusive Time의 감소 원인:
혼자 시도해 본 것이라서..
틀린 내용이 있거나 기타 의견이 있으신 경우, 편하게 댓글 남겨주세요!