UI 성능 최적화
Invalidation Box, Tick 최적화, 프로파일링 도구, 그리고 UE5 UI 성능 모범 사례를 학습합니다.
UI 성능의 주요 병목
Slate가 매 프레임 수행하는 작업과 비용
| 병목 영역 | 원인 | 영향 |
|---|---|---|
| Tick / Paint | 매 프레임 위젯 순회 | CPU 시간 증가 |
| 레이아웃 계산 | 깊은 위젯 트리, 잦은 레이아웃 변경 | CPU 스파이크 |
| 함수 바인딩 | 매 프레임 폴링 | 불필요한 CPU 사용 |
| 위젯 생성/파괴 | 동적 UI 빈번한 생성 | GC 부하, 메모리 단편화 |
| 텍스처 전환 | 배칭 깨짐 | Draw Call 증가 |
| Visibility | Hidden vs Collapsed 혼동 | 불필요한 레이아웃 비용 |
Collapsed는 위젯을 레이아웃에서 완전히 제거하고, Hidden은 보이지는 않지만 레이아웃 공간을 차지합니다. HitTestInvisible은 보이지만 입력을 받지 않습니다. 불필요한 위젯은 Collapsed로 설정하여 레이아웃 계산 비용을 절약하세요.
Invalidation Box
정적 UI의 렌더링 결과를 캐싱하여 성능 개선
UInvalidationBox는 자식 위젯의 렌더링 결과를 캐싱하여 변경이 없으면 매 프레임 재렌더링하지 않습니다. 자주 변하지 않는 복잡한 위젯 그룹에 적용하면 큰 효과를 볼 수 있습니다.
// Widget Blueprint에서 InvalidationBox로 정적 UI를 감쌈
// InvalidationBox
// └── 배경 이미지
// └── 장식 텍스트
// └── 프레임 이미지
// → 이 영역은 변경 없으면 캐시 사용
// C++에서 InvalidationBox 제어
UPROPERTY(meta = (BindWidget))
UInvalidationBox* StaticUICache;
// 내용 변경 시 수동으로 캐시 무효화
void UMyWidget::OnDataChanged()
{
// InvalidationBox 내 위젯이 변경되면 무효화 필요
StaticUICache->InvalidateCache();
}
// Global Invalidation: 프로젝트 전체에 적용
// DefaultEngine.ini 또는 콘솔:
// Slate.EnableGlobalInvalidation 1
// 모든 위젯에 대해 자동 Invalidation 추적
Slate.EnableGlobalInvalidation을 활성화하면 개별 InvalidationBox 없이 전체 Slate 트리에 자동 Invalidation이 적용됩니다. UE5에서는 이 기능이 크게 개선되어 대부분의 프로젝트에서 활성화할 것을 권장합니다.
Tick 최적화
NativeTick 사용을 최소화하는 전략
// 방법 1: Tick 비활성화 (가장 효과적)
UMyWidget::UMyWidget(const FObjectInitializer& OI)
: Super(OI)
{
// 위젯의 Tick을 완전히 비활성화
SetDesiredTickEnabled(false);
}
// 방법 2: 이벤트 기반으로 전환
// Tick에서 매 프레임 폴링하던 것을 델리게이트로 교체
void UMyHUD::NativeConstruct()
{
Super::NativeConstruct();
// Tick 대신 이벤트 구독
HealthComp->OnHealthChanged.AddDynamic(
this, &UMyHUD::UpdateHealthBar);
}
// 방법 3: Tick 주기 조절 (불가피한 경우)
// 매 프레임이 아닌 일정 간격으로만 업데이트
void UMyWidget::NativeTick(const FGeometry& Geo, float DT)
{
Super::NativeTick(Geo, DT);
AccumulatedTime += DT;
if (AccumulatedTime < UpdateInterval) return;
AccumulatedTime = 0.f;
// 여기서 실제 업데이트 수행
UpdateUI();
}
// 방법 4: 머티리얼 기반 애니메이션 (GPU)
// 루핑 글로우, 스크롤 효과 등은 머티리얼로 구현
// → CPU Tick 비용 0, GPU에서 처리
프로파일링 도구
UI 성능을 측정하고 병목을 찾는 방법
// Slate 성능 통계
stat Slate // Slate 전체 성능 (Paint, Tick, Layout 시간)
stat SlateMemory // Slate 메모리 사용량
// 위젯 수 확인
stat UMG // UMG 위젯 통계
// 배칭 효율 확인
Slate.Debug.ShowBatching 1 // Draw Call 시각화
// Invalidation 디버그
Slate.Debug.InvalidationRoot 1 // Invalidation 루트 표시
// Widget Reflector
// Window > Developer Tools > Widget Reflector
// 위젯 트리, 히트 테스트, 포커스 확인
// Unreal Insights (상세 프로파일링)
// -trace=slate 파라미터로 실행
// → Slate 이벤트를 Trace 파일에 기록
핵심 지표
- - Slate Paint 시간: < 2ms 목표
- - 활성 위젯 수: 최소화
- - Draw Call 수: 배칭 효율 확인
- - Tick 비활성 위젯 비율: 최대화
최적화 체크리스트
- - 함수 바인딩 제거, 이벤트 기반 전환
- - 불필요한 Tick 비활성화
- - 정적 UI에 InvalidationBox 적용
- - Collapsed로 불필요한 위젯 숨김
핵심 요약
- UI 성능 병목은 Tick/Paint, 레이아웃 계산, 함수 바인딩, 위젯 생성/파괴, 배칭 깨짐에서 발생합니다
InvalidationBox와 Global Invalidation으로 변경 없는 위젯의 재렌더링을 방지합니다- Tick 비활성화와 이벤트 기반 업데이트가 가장 효과적인 CPU 최적화 방법입니다
stat Slate, Widget Reflector, Unreal Insights로 UI 성능을 측정하고 병목을 진단합니다- 머티리얼 기반 애니메이션은 CPU 비용 없이 GPU에서 처리되므로 루핑 효과에 적합합니다
도전 과제
배운 내용을 직접 실습해보세요
기존 HUD 위젯 트리를 분석하여 정적 영역(배경, 프레임)과 동적 영역(숫자, 게이지)을 분리하고, InvalidationBox를 전략적으로 배치하세요. stat slate로 최적화 전후 성능을 비교합니다.
불필요하게 Tick이 활성화된 위젯들을 찾아 bCanEverTick = false로 설정하고, 이벤트 기반 업데이트로 전환하세요. Tick 비활성화 전후 CPU 비용 변화를 프로파일링합니다.
Unreal Insights + stat slate + WidgetReflector를 조합하여 UI 성능 병목을 체계적으로 진단하는 워크플로우를 구축하세요. 문제 발견 -> 원인 분석 -> 최적화 적용 -> 검증의 사이클을 문서화합니다.