Optimizing and Building UI for AAA Games | Unreal Fest Online 2020
UI는 프레임당 8밀리초를 소모한다.
AHUD::Tick
AHUD는 HUD ‘액터’이며 Tick은 일반적인 액터와 같다. 여기서는 흔한 HUD 작업을 수행하게 되며 보통은 여기서 게임 코드와 통신하고 위젯 생성/제거 등의 작업을 수행한다. 지오메트리와 함께 움직이는 위젯을 움직일 때도 있다. 여기서 주의할 함수는 CreateWidgets이다. 새 위젯을 생성하려면 비용이 많이 들 수 있다. 이걸 방지하는 한 가지 방법은 풀링 시스템을 구축하는 것이다. 시작할 때 CreateWidgets를 반복적으로 호출해서 풀을 채운다. 그 후 런타임 때 풀에 채운 위젯을 요청하고 재활용하는 것이다.
이번에는 프로파일의 DrawWindow 부분을 살펴본다. DrawWindow는 재귀적으로 호출되며 각 Widget::Paint를 호출한다. 이 Paint 메서드는 내부적으로 블루프린트 위젯과 네이티브 Tick 메서드를 호출한다. 그리고 이 함수는 Hit test grid를 리빌드 한다. 그러므로 Hit test grid는 Hit test와 위젯 탐색을 수행하는 쿼드 트리가 된다. 페인트는 마지막으로 렌더러 정보를 슬레이트 엘리먼트 배처에 추가한다.
그래서 저희가 취한 최적화 방법 한 가지는 위젯 트리를 평탄화하는 것이었다. 페인트 함수는 재귀적으로 각 컨테이너에 호출된다. 즉, 컨테이너 안에 컨테이너를 넣고 그 컨테이너 안에 또 컨테이너를 넣으면 재귀적인 함수 호출 수가 늘어나게 됩니다. 따라서 실행할 코드가 더 많아질 뿐만 아니라 V 테이블 룩업 수도 늘어나면서 CPU 캐시 누락이 발생할 수 있다. 따라서 위젯 트리를 평탄화하면 이 부분이 최적화된다. 또 한가지 기억해둘 점이 있다. UMG UI 디자이너의 위젯 트리는 위젯의 코드를 처리하는 태스크 그래프를 직접적으로 나타냅니다. 이게 무슨 뜻이냐면 위젯 트리가 작을수록 호출하는 함수 수도 적어지며 위젯 트리가 평탄화되어 있을 수록 재귀 횟수고 줄어든다는 의미이다.
여기에 적용할 만한 또 다른 개선점이 있다. 그것은 Visible 대신 Hit test Invisible이나 Self Hit test Invisible을 사용하는 것이다. 이건 둘 다 Visibility 세팅이고 둘 모두 위젯이 보이게 만들지만 위젯을 Hit test grid에 추가하지는 않는다. Hit test Invisible이 Self Hit test Invisible과 다른 점은 Hit test Invisible의 경우 Hit test grid에 추가된 모든 위젯의 자손을 중지한다는 점이다. 따라서 이 세팅은 이 위젯 트리 부분에 Hit test가 필요 없는 경우에 적합하다. 화면을 보시면 많은 게임의 HUD에 등장하는 간단한 경험치, 쉴드, 체력 바가 있다.
이건 딱 봐도 사용자가 클릭하면 안되는 위젯이다. 선택해서도 안되고 이 위젯으로 이동해서도 안된다. 따라서 이 위젯은 Hit test grid에 있을 필요가 없다.
저희가 추가적으로 취한 최적화 방법은 tick 함수를 삭제하는 것이었다. 계속해서 틱을 실행하는 위젯 클래스도 있겠지만 그래도 나머지는 더 이상 틱을 실행할 수 없다. Tick 함수를 삭제했다고 반드시 퍼포먼스가 개선되는 것은 아니다. 아마도 틱으로 많은 작업을 하고 있을 수도 있고요. 틱을 삭제하는 진짜 목적은 사람들이 쉽다는 이유만으로 프레임마다 작업을 추가하는 일을 막기 위해서이다. 이로써 아티스트와 시간에 쫓기는 프로그래머는 퍼포먼스를 저해하는 보이지 않는 장애물을 만들 일이 없어지죠. 다시 체력 바 예시로 돌아와 보겠습니다. 이걸 손쉽게 구현하려면 보통 틱마다 플레이어의 체력을 쿼리하는 방법을 사용한다. 이게 가장 간단한 방법으로 특히 프로그래머가 프로토타입을 만들 때 자주 사용하는 방법이기도 하다. 하지만 틱을 삭제해 버렸으니 이제 사람들은 틱마다 뭔가를 쿼리하는게 아니라 항상 이벤트 기반의 접근법이나 퍼포먼스 위주의 접근법을 활용해야 합니다. 자 그러면 최적화의 가장 큰 부분을 차지하는 SlatePrepass를 살펴보도록 하자.
엔진 프로그래머가 질문을 하는 함수이기도 하다. 엔진 프로그래머는 이게 무슨 기능인지 바로 알기 어렵기 때문이다. 다른 이유는 보통 이 함수에는 프로파일러 장치가 매우 적다. 즉, 엔진 프로그래머에게는 하나의 프로파일이 주어지고 안에는 큰 작업 블록이 있는데 SlatePrepass라고 쓰여 있을 뿐이다. 그렇다면 과연 이 함수는 뭘까? SlatePrepass 함수는 각 위젯의 모든 캐시 지오메트리 엔트리를 리빌드한다. 이 함수는 계층구조의 바닥으로 내려갔다가 맨위까지 다시 올라온다. 따라서 위젯들의 지오메트리는 계층구조의 위아래 모두에 영향을 준다는 걸 알아두자. 예를 들어 테이블 셀의 크기를 좌우하는 건 콘텐츠뿐만이 아니다. 열과 행의 크기도 셀의 크기를 제한하는 역할을 한다. 따라서 이 함수는 각 프레임마다 위젯 트리 전체를 재계산한 캐시 지오메트리 값을 가져온다. 그러므로 여기서 가장 먼저 할 일은 위젯을 숨길 때 Hidden이 아니라 Collapsed를 사용하는 것이다. 왜냐하면 숨겨진 위젯은 여전히 화면 지오메트리를 가지고 있으므로 계속해서 주변 위젯의 지오메트리에 영향을 주기 때문이다. 그러면 다시 경험치, 쉴드, 체력 바를 살펴보자. 이 수직형 박스에는 여러 가지 위젯이 있다. 경험치, 쉴드, 체력 위젯이 이 수직형 박스에 들어가 있다. 여기서 쉴드 컴포넌트를 숨기고 숨겼다고 표시하더라도 숨겨진 위젯은 계속해서 프리패스에서 지오메트리를 재계산해야 한다. 하지만 이 위젯을 병합해 버리면 병합 위젯은 재계산을 하지 않는다. 물론 경우에 따라 Hidden이 화면 레이아웃에 더 적합할 때도 있다. 하지만 굳이 숨길 필요가 없다면 병합 즉, Collapsed를 선택하면 된다. 숨겨진 위젯의 지오메트리를 계산하려면 그 위젯 자손들의 지오메트리도 계산해야 하며 이는 자손들이 하나도 렌더링되지 않았더라도 마찬가지이다. SlatePrepass는 항상 leaf-most 노드로 내려간다. Collapsed는 이러한 재귀 작업을 중단하는 유일한 Visibility 세팅이다. 따라서 이 경우에 Collapsed를 사용하면 태스크 그래프의 브랜치 전체를 건너뛰게 된다. UMG는 Invalidation box라는 위젯을 제공한다. Invalidaton box는 간단한 컨테이너 위젯이지만 그 아래 트리의 레이아웃과 변동성을 캐싱한다. Invalidation box를 사용하면 퍼포먼스가 개선될 수 있지만 문제가 일어날 수도 있다. 경우에 따라서는 Invalidation box가 무효화를 자동으로 수행하지 않기도 한다. 그 결과 때때로 프로그래머만이 해결할 수 있는 레이아웃이나 애니메이션 버그가 나타날 수 있다. 저는 아티스트가 단순히 Invalidation box를 삭제해서 이 문제를 해결하는 모습을 종종 목격했다. 그러면 효과적으로 이 최적화가 없어지게 된다. 저는 사람들이 실수로 호출을 남겨둬서 모든 프레임을 무효화하는 경우도 목격했다. 당연히 이건 최선이 아닐 것이다.
지금까지 아트팀과 UI 프로그래머가 일반적으로 최적화할 수 있는 여러 가지 기법들을 살펴보았다. 이번에는 좀 더 극단적인 최적화에 대해 얘기하고 싶다. 그런 다음 현재 UMG에서 약간의 작업이 필요한 몇 가지 멋진 기능을 UI에 추가하는 방법을 얘기하겠다. 지금까지 저희는 다수의 새로운 위젯들을 구현했으며 이 위젯들은 내부적으로 SMesh 위젯 클래스를 구현한다. 이 위젯은 현재 UMG에 구현되어 있지 않으며 제가 알기로는 현재 사용하지 않는다. 이 위젯을 사용하면 원하는 2D 메시를 그에 대응하는 머티리얼로 직접 그리는 인터페이스를 제공한다. 따라서 이걸 이용해 버텍스/인덱스 버퍼에 레퍼런스와 함께 전달되는 버퍼를 슬레이트 엘리먼트 배처에 직접 생성할 수 있다. 렌더 배치에서는 렌더링 가능한 인스턴스 수를 포함하는 버퍼를 사용하여 메시의 수많은 인스턴스를 렌더링하고 해당 버퍼를 머티리얼 셰이더에 전달할 수 있다. 따라서 버퍼 하나로 수많은 메시 인스턴스를 렌더링할 수 있다. 이를 이용한 첫 번째 예는 파티클 이미터이다. 이것은 이 위젯의 전형적인 예로 수많은 오브젝트 인스턴스를 사용하고 수많은 오브젝트 인스턴스를 렌더링하며 퍼포먼스 기준을 만족시킨다. 이 특정 이미터는 200개의 파티클을 렌더링한다. 이것은 정상적인 슬레이트 드로잉 프로세스를 거치지 않고 GP 뷰의 머티리얼에 놓여져 배치된다. 그러면 이걸 이 위젯의 UMG 표현에 추가한다. 이렇게 하면 기본적인 파티클 이미터 컨트롤을 UMG 디자이너에 바로 추가할 수 있다. 그러면 아티스트는 프로그래머 없이도 이 컨트롤을 쉽게 사용할 수 있다. 이제 UMG 슬레이트 위젯이 되었기 때문에 다른 위젯에 레이어링할 파티클을 스폰할 수 있다. 이 예시에서는 파티클 이미터가 단순히 여기에 표시된 UMG 위젯이 되었으므로 UMG UI 디자이너의 위젯 트리에 순서대로 레이어링된다.
17:17
이미지 위에 텍스트를 레이어링하는 방식과 완전히 동일하다. 파티클 위젯은 이걸 이용하는 한 가지 용도에 불과하다. 이 기법을 사용하면 수많은 복잡한 위젯들을 만들 수 있으며 화면에서 수차례 그려지는 모든 것에 이용할 수 있다. 잠시 시간을 내어 게임의 HUD나 UI에서 유사한 오브젝트 인스턴스를 많이 그려내는 요소를 생각해보자. 가령 여기 이 맵은 다수의 유사 인스턴스를 그리는 위젯의 좋은 예이다.