Профилирование Unity UI: кто портит мой батчинг?

в 8:32, , рубрики: canvas rebuild, UI, unity, unity canvas, unity3d, unty ui, интерфейс, интерфейсы, оптимизация программ, разработка игр
Профилирование Unity UI: кто портит мой батчинг? - 1

Вы потратили бесконечное количество времени на оптимизацию Unity UI. Но для того, чтобы вызвать торможения, достаточно небольшой модификации крошечного атрибута почти невидимого элемента UI Canvas. И когда такое случается, даже профилирование Unity UI не спасёт вас от снижения FPS. Вы готовы долгому исправлению ошибок?

Именно это и произошло в моём последнем проекте...

Я упорно работал над оптимизацией нескольких панелей UI в порте нашей игры на Oculus Quest. В основном задача сводилась к снижению уровня перерисовки (overdraw) до приемлемых величин, чтобы GPU мог справляться с самым главным — реальным 3D-рендерингом.

Так я работал над оптимизацией Unity UI не меньше месяца, и со временем добился чертовски неплохого прогресса.

На каком-то этапе UI стал настолько оптимизированным, что едва влиял на тайминги GPU. Реализованные мной техники затемнения непрозрачного UI компенсировали большую часть перерисовок, вызванных наслоением UI (элементами, отрисовываемыми поверх других элементов).

Итак, у меня получилась сверхоптимизированная гибридная система UI, которая по сути перекрывала отрисовываемые под ней 3D-элементы. Стало очень легко отбрасывать рендеринг этих перекрытых фрагментов.

Однако работа ещё была далека от завершения...

Когда я подключил Unity UI Profiler, моё внимание привлекла одна вещь.

Я увидел, что перегруженный ЦП тратит в каждом кадре более 1 мс на рендеринг UI. Это куча времени для платформы, которая даёт тебе бюджет в 13 мс на выполнение всей игры: физики, логики, 3D-рендеринга, ввода, VR и сетевого кода.

И ведь бывали случаи, когда UI «убивал» производительность ЦП ещё сильнее.

Профилирование Unity UI: кто портит мой батчинг? - 2

Unity UI: затратные батчи сборки

И это говорит об одном: UI можно оптимизировать под GPU, но это не обязательно означает оптимизацию под ЦП.

На самом деле, при рендеринге Unity UI задачи ЦП и GPU сильно отличаются. Не удивительно, что я рекомендую подходить к оптимизации ЦП и GPU по-разному, и об этом говорилось в моём предыдущем посте про оптимизацию Unity UI.

Дальнейшее профилирование Unity UI выявило очевидную проблему: UI постоянно воссоздавался в каждом новом кадре, т.е. в каждом кадре происходила перестройка Canvas (Canvas Rebuild).

Постоянная нагрузка на ЦП длительностью 1 мс… ой-ёй.

Но зачем Unity так со мной поступает? Я думал, что Unity кэширует все Canvas интерфейса...

На самом деле да, так и есть. Unity эффективно кэширует Canvas, чтобы они собирались только один раз.

Однако проблема возникает, когда ты меняешь свойства любого из элементов UI в Canvas — цвет, позицию, и так далее.

То есть все любимые нами анимации, например, эффекты кнопок при наведении курсора, убивают производительность, а вы, возможно, этого и не знаете.

Когда происходит изменение свойства UI, Unity выполняет знаменитую Canvas Rebuild, которая рушит производительность игры.

Canvas Rebuild интерфейса заставляет движок Unity итеративно обходить все элементы UI этого Canvas для генерации оптимизированного списка вызовов отрисовки (множества вершин, цветов, материалов и т.д.). И на Canvas Rebuild уходит больше времени, чем требуется Seat Panda, чтобы разогнаться с нуля до 60 миль/ч.

Осознав, что мы постоянно страдаем от Canvas Rebuild, логично будет задать вопрос…

Почему мы страдаем от Canvas Rebuilds и что с этим можно сделать?

Чтобы ответить на этот простой вопрос, мне пришлось потратить больше 5 часов на изучение темы и работу с Unity UI Profiler.

Давайте разбираться.

1. Профилирование Unity UI: всё хорошо, пока...

Представим, что у нас есть простой UI.

Этот UI ничего не делает, просто находится на экране и раздражает игрока, пытающегося что-нибудь сквозь него разглядеть.

Как набор из 350 с лишним изображений в Grid Layout Group это выглядит так:

Профилирование Unity UI: кто портит мой батчинг? - 3

Пример профилирования Unity UI

И всё в порядке, даже несмотря на 350 с лишним изображений. В стандартном случае UI отрендерится всего за два вызова отрисовки (draw call), потому что в нём всего два уникальных изображения, не находящихся в спрайтовом атласе.

По сути, в профилировщике я могу увидеть, что со стороны ЦП практически нет никакой нагрузки. Бо́льшую часть времени на UI тратится меньше 0.01 мс, что чертовски здорово.

Профилирование Unity UI: кто портит мой батчинг? - 4

Профилирование Unity UI: загадочный всплеск

(… Бо́льшую часть времени)

Постойте-ка, а откуда взялся этот всплеск ресурсов ЦП в конце графика?

2. Профилирование Unity UI: появляется внезапная Canvas Rebuild!

Что же произошло в конце Unity Profile? Очень странно, всего за секунду затраты ЦП на Unity UI стали в два с лишним раза больше.

Я хочу сыграть в игру

Найдите два различия в показанных ниже примерах (возможно, чтобы увидеть лучше, придётся открыть изображения в отдельном окне).

Профилирование Unity UI: кто портит мой батчинг? - 5

Профилирование Unity UI: малозатратный Canvas

Профилирование Unity UI: кто портит мой батчинг? - 6

Профилирование Unity UI: Canvas Rebuild

Даю вам пять секунд, чтобы разобраться.

5, 4… ну ладно, вот подсказка, чтобы упростить задачу:

Профилирование Unity UI: кто портит мой батчинг? - 7

Профилирование Unity UI: излишние затраты на Canvas Rebuild

Ух ты!

PostLateUpdate.UpdateRectTransform и UGUI.Rendering.UpdateBatches сильно хотят стать главными звёздами этого шоу.

Что делают эти области?

Первая, UpdateRectTransform, даёт понять, что изменился transform какого-то объекта, а потому Unity приходится выполнять затратную логику, чтобы отразить визуальные изменения. Мы не знаем, была ли это позиция, поворот, масштаб или какое-то иное из свойств RectTransform.

Чёрт, да мы даже не знаем, был ли это всего один атрибут, или все сразу. Был ли это один объект или несколько? И какие именно? В этом-то и проблема: мы не знаем.

Второй затратный фрагмент, UpdateBatches, связан с тем, что необходимо перестроить всю геометрию Canvas. Именно этот процесс и называется Canvas Rebuild. Перестройка Canvas означает, что Unity обходит всю иерархию Canvas для генерации списка вызовов отрисовки. Вычисляются вершины, индексы, цвета, uv всех элементов, а затем выполняется проход батчинга, чтобы объединить максимально возможное количество вызовов отрисовки для снижения излишней нагрузки на ЦП, передающий её графическому драйверу.

Теперь мы вроде бы знаем, что происходит, и находимся на верном пути. Но как избежать этой перестройки Canvas? Что их вызывает?

Нам нужна более конкретная информация…

Подведём итог

  • Изменение атрибута в элементе UI помечает сам элемент как «грязный»
  • Элемент UI может быть полностью грязным, но может быть и частично: грязные вершины, грязное расположение, грязные материалы. Из частично грязных состояний восстановиться проще
  • Как только любые элементы Canvas помечаются как грязные, Unity перестраивает его полностью
  • Перестройка Canvas является затратной для ЦП, поэтому важнее всего избегать её

3. Ищем вредителя: политически некорректный способ грубого перебора

Нам по-прежнему нужно ответить на следующий вопрос:

Что вызывает эти Canvas Rebuild?

Оказывается, быстрого способа выяснить это не существует, особенно при большой иерархии Canvas.

Но для начала я покажу вам метод грубого перебора для нахождения причины Canvas Rebuild.

Профилирование Unity UI: кто портит мой батчинг? - 8

1. Пусть Unity UI Profiler продолжает записывать показания

Отфильтруем метрики, чтобы можно было сосредоточится на самом важном: рендеринге, скриптах и UI.

Отслеживайте исходную отметку, чтобы понимать затраты на текущую схему, в которую должны входить и затратные Canvas Rebuild.

Профилирование Unity UI: кто портит мой батчинг? - 9

2. Деактивируйте Game Objects из UI и сравните показания

Выберите группу game objects и деактивируйте их.

Сравните показатели производительности.

Если показания сильно не улучшились, то продолжайте деактивировать game objects, пока не увидите значительное улучшение.

Профилирование Unity UI: кто портит мой батчинг? - 10

3. Найдите, что изменяет его свойства

Нам удалось выделить объект, вызывающий Canvas Rebuild. Но что конкретно становится их причиной?

Возможно, его масштаб меняется через скрипт? Или его позиция меняется анимацией?

Удобно будет нажать правой клавишей на RectTransform и выбрать "Find References in Scene"

Когда вы узнаете, что вызывает перестройку Canvas, сделайте с этим что-нибудь, например, отключите анимации или преобразования.

Рубен, но как мне использовать этот метод при огромной иерархии UI? Не рассказывай мне чушь

Я говорил, что процесс не будет ни быстрым, ни интересным, но игроки будут вам благодарны.

В этом и дело. Во-первых, наличие огромной иерархии — неидеальная ситуация. Именно такие массивные и глубокие иерархии делают Canvas Rebuild такими затратными для ЦП.

Но большие и вложенные иерархии UI могут (и будут) возникать, поэтому ожидайте, что Canvas Rebuild будут сильнее всего бить по самому важному: по игровому процессу.

Хотя метод грубого перебора помогает найти источник перестройки Canvas, в долгосрочной перспективе он плохо масштабируется.

Став более профессиональным в оптимизации UI, я создал инструмент, дающий все необходимые мне ответы, чтобы проект соответствовал ожиданиям игроков

Профилирование Unity UI: кто портит мой батчинг? - 11

Профилирование Canvas Rebuild

4. Бонус: расширение возможностей Unity Profiler по оптимизации UI

Надеюсь, вам уже понятно, насколько частыми и мешающими могут быть Canvas Rebuild.

Эти перестройки, заразившие мою игру, отбирали целых 10% всего бюджета ЦП!

Как мы видели, существует метод грубого перебора для поиска источников Canvas Rebuild. Возможно, вы справитесь с ними, воспользовавшись стратегиями, перечисленными в моём посте об оптимизации Unity UI.

Но такой подверженный ошибкам процесс ни за что бы не удовлетворил настоящего гуру. Вы можете потратить несколько дней, борясь с Canvas Rebuild, но в самый неожиданный момент они вернутся, чтобы пропасть, как только вы подключите Unity UI Profiler.

Если вы разрабатываете игру для VR, это становится критически важным. Мы не хотим, чтобы Canvas перестраивались в UI мирового пространства. Если от перестройки не избавиться, то ваши игроки скорее всего этого не выдержат.

Ладно, я понял, нужно избавляться от Canvas Rebuilds. Но Unity Profiler не даёт по ним почти никакой информации! Что ты можешь посоветовать?

Рад что вы спросили. Оказывается, мы можем убедить Unity Profiler представлять нам полезную информацию о том, что мешает производительности UI.

Мы с вами можем расширить функциональность Unity UI Profiler. Для этого нужно модифицировать публично доступный исходный код Unity UI. Добравшись до исходного кода, нужно будет найти функции кода, в которых происходят Canvas Rebuilds. Затем нам потребуется API-магия с BeginSample и EndSample профилировщика.

Если вы работаете в Unity 2019.1 или более ранней версии, то исходный код Unity UI выложен в репозитории Bitbucket. Также там есть руководство по скачиванию, установке и модифицированию.

Что посоветую я? Пользоваться более новой версией Unity, не ниже 2019.2.0. Новые версии Unity по умолчанию поставляются с исходным кодом UI, поскольку система UI теперь является частью менеджера пакетов (package manager). Это самый беспроблемный способ.

Вот список частей кода, которые я обнаружил в процессе своих исследований, куда можно добавить вызовы Profiling API:

Профилирование Unity UI: кто портит мой батчинг? - 12

Unity UI: исходный код профилирования

Полезно? Да.

Удобно для художника/дизайнера? Нет.

Именно поэтом я написал небольшое Unity Extension с открытым исходным кодом, расширяющее возможности Unity Profiler.

Профилирование Unity UI: кто портит мой батчинг? - 13

Этот бесплатный инструмент позволит нам быстро переключаться между разными режимами профилирования, чтобы обеспечить максимальную производительность игры.

Что самое лучшее в этом расширителе Unity Profiler? Он работает за пределами редактора, по сути избавляя вас от всей головной боли при профилировании UI для Android и других платформ.

Вот он, вся его мощь управляется всего двумя кнопками:

  • Buff my Unity Profiler (Усилить Unity Profiler)
  • Nerf my Unity Profiler (Ослабить Unity Profiler).

Получить этот инструмент можно здесь.

Автор: PatientZero

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js