1. UI 성능
1. Paint
Paint 함수는 Unreal Engine에서 UI 요소를 화면에 그리기 위해 호출됩니다. 이 함수는 위젯의 렌더링 정보를 Slate Element Batcher에 전달합니다. 위젯 트리의 상속 구조에서 Paint 함수는 모든 자손 위젯에 대하여 재귀적으로 호출되며, 각 위젯의 BP 함수 호출 및 Tick 함수를 호출합니다. 이 함수들은 Hit Test Grid(쿼드 트리로 위젯의 Hit Testing과 Navigation을 수행)의 재 빌드를 유발하며, 성능에 많은 영향을 끼칩니다. 더불어, 위젯 트리의 과도한 상속 구조는 CPU의 잦은 Vtable 접근을 유발하며, 캐시 미스가 발생합니다. *Unreal Insight의 Draw Window를 통해 확인 가능합니다.
2. Slate Prepass
Slate Prepass는 UI 렌더링 과정에서 실제 렌더링 전에 수행되는 준비 단계로, UI 요소의 레이아웃, 크기, 그리고 위치등을 계산하고 최적화하는 Geometry Entry와 관련된 작업을 수행합니다. Slate Prepass는 위젯 트리에서 재귀적으로 수행되며, 계층 구조의 각 위젯에 대하여 Geometry Entry 계산 작업을 수행합니다.
3. Invalidation Box
Invalidation Box는 자식 위젯들의 레이아웃 및 페인팅 갱신을 제어하는 컨테이너 위젯입니다. 이 위젯은 자식 위젯들의 변경 사항을 캐싱하고, 필요한 경우에만 업데이트를 수행함으로써 UI 성능을 최적화합니다. 하지만, Invalidation Box는 가끔 자식 위제들의 Invalidate을 자동적으로 수행하지 않는 문제가 발생합니다.
2. Pooling
1. 개념
Widget 객체의 생성을 위한 "CreateWidget" 작업의 반복은 메모리 사용량 증가, 런 타임 오버헤드, SlatePrepass(Widget의 Geometry Entry 계산)의 반복, 랜더링을 위한 드로우 콜 증가 등의 문제가 발생합니다. 따라서, 이를 방지하기 위해 최초 게임 시작 시점에 CretaeWidget을 통해 생성된 Widget들을 Pooling 하여 재활용합니다.
2. AddToViewport/RemoveFromViewport
#include "MyGameMode.h"
#include "Kismet/GameplayStatics.h"
AMyGameMode::AMyGameMode()
{
// 위젯 클래스를 설정합니다. 실제 프로젝트에서는 에디터에서 설정할 수 있도록 UPROPERTY를 사용할 수 있습니다.
static ConstructorHelpers::FClassFinder<UUserWidget> WidgetClassFinder(TEXT("/Game/UI/WBP_MyWidget"));
if (WidgetClassFinder.Succeeded())
{
WidgetClass = WidgetClassFinder.Class;
}
}
void AMyGameMode::CreateAndShowWidget()
{
if (WidgetClass && !CurrentWidget)
{
// 위젯을 생성합니다.
CurrentWidget = CreateWidget<UUserWidget>(GetWorld(), WidgetClass);
if (CurrentWidget)
{
// 생성된 위젯을 뷰포트에 추가합니다.
CurrentWidget->AddToViewport();
// 필요한 경우, 플레이어 컨트롤러의 입력 모드를 UI로 설정합니다.
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(this, 0);
if (PlayerController)
{
FInputModeUIOnly InputMode;
PlayerController->SetInputMode(InputMode);
PlayerController->bShowMouseCursor = true;
}
}
}
}
void AMyGameMode::RemoveWidget()
{
if (CurrentWidget)
{
// 위젯을 뷰포트에서 제거합니다.
CurrentWidget->RemoveFromViewport();
// 위젯 포인터를 null로 설정합니다.
CurrentWidget = nullptr;
// 필요한 경우, 플레이어 컨트롤러의 입력 모드를 게임으로 되돌립니다.
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(this, 0);
if (PlayerController)
{
FInputModeGameOnly InputMode;
PlayerController->SetInputMode(InputMode);
PlayerController->bShowMouseCursor = false;
}
}
}
Widget 생성을 위해 CreateWidget을 호출하고, 이를 화면에 그리기 위해 AddToViewport를 호출합니다. 그리고, 해당 위젯을 화면에서 숨기기 위해 RemoveFromViewport/Parent를 호출합니다. RemoveFromViewport/Parent 함수는 해당 위젯의 Slate 요소를 뷰포트의 위젯 트리에서 제거하고, 가시성을 FSlateVisibility::Collapsed로 설정하고, Z-order를 재설정합니다. 더불어, RemoveFromViewport/Parent는 위젯 객체가 할당 받은 메모리 공간을 유지하고, UI 업데이트 사이클에서 제외됩니다.
3. Collapsed vs RemoveFromViewport
RemoveFromViewport:
위젯을 뷰포트의 위젯 트리에서 완전히 제거합니다. 렌더링 및 업데이트 사이클에서 제외됩니다. 입력 이벤트를 받지 않습니다. 메모리에는 여전히 남아있습니다. NativeDestruct() 이벤트를 트리거합니다. 특정 외부 이벤트들에 바인딩된 콜백 함수들은 여전히 정상적으로 호출됩니다. 따라서, 해당 위젯을 완전히 파괴하는 작업에서 NativeDestruct() 혹은 BeginDestroy() 등의 소멸 관련 생명 주기 함수들에서 바인딩을 명시적으로 해제해 주는 작업이 필요합니다. 결과적으로, 위젯을 장시간 숨기거나 성능이 중요하다면 RemoveFromViewport를 활용합니다.
SetVisibility(ESlateVisibility::Collapsed):
위젯은 여전히 뷰포트의 위젯 트리에 남아있습니다. 렌더링되지 않지만, 업데이트 사이클에는 여전히 포함될 수 있습니다. 상황에 따라 일부 입력 이벤트를 여전히 받을 수 있습니다. 위젯의 상태가 유지됩니다. NativeDestruct()가 호출되지 않습니다. 결과적으로, 자주 토글되며 상태 유지가 중요한 위젯의 경우 Collapsed만 설정해 줍니다.
3. Hit Test Grid 빌드 작업 방지
1. 개념
Paint 함수 호출은 위젯 트리에 대하여 BP 함수 혹은 Tick 함수의 호출을 재귀적으로 수행합니다. 그리고, 이 함수들은 Hit Test Grid(쿼드 트리로 각 위젯의 Hit Testing과 Navigation 수행)의 재 빌드를 유발하여 성능에 영향을 끼칩니다. 따라서, 위젯 트리의 깊이를 얕게 설계하여 이러한 작업을 방지하는 최적화 작업이 가능합니다. 더불어, 각 Hit Test가 필요 없는 위젯(HP, MP, SP, Quick Slots)의 Visibility 설정을 Visible이 아닌, HitTestInvisible 혹은 SelfHitTestInvisible로 설정하여 Hit Testing에서 제외하는 것도 UI 최적화에 도움이 됩니다. 마지막으로, 당연한 내용이지만 각 위젯 내부의 Tick 함수를 지워주는 것 또한 중요합니다.
4. Collapsed 활용
1. 개념
Slate Prepass는 Widget을 화면에 그리기 전 준비 단계를 수행합니다. 위젯 트리의 계층 구조에서 각 Widget에 대한 Geometry Entry를 캐싱하는 작업을 재귀적으로 수행합니다. 따라서, 위젯트리의 각 위젯에 대하여 '숨김' 작업을 'Hidden'으로 설정하지 않고, 'Collapsed'로 설정하여 Geometry Entry 관련 계산 작업에서 제외하는 최적화 작업이 가능합니다. 가시성이 'Hidden'으로 설정된 위젯의 경우 여전히 스크린 내 Geometry 정보를 갖고, 해당 위젯 주변의 Geometry에 지속적으로 영향을 끼칩니다. 따라서, Slate Prepass는 설령 몇몇 위젯들의 가시성이 'Hidden' 설정되어 숨겨져도, 여전히 해당 위젯들에 대한 Geometry Entry 관련 캐싱작업을 재귀적으로 수행합니다. 따라서, 위젯을 숨기기 위해선 'Hidden' 대신, 'Collapsed'로 설정하는 것이 UI 최적화에 도움이 됩니다.
'게임개발 > Unreal C++' 카테고리의 다른 글
[Unreal]#Delegate 바인딩 시 추가 매개변수(인자 바인딩) (0) | 2024.10.04 |
---|---|
[Unreal]#FInputMode (2) | 2024.09.08 |
[Unreal]#UFUNCTION 매크로 (0) | 2024.08.28 |
[Unreal]#비동기 프로그래밍 (0) | 2024.08.21 |
[Unreal]#ESlateVisibility, UI 가시성 (0) | 2024.08.21 |