Теперь на retina экране редактор не выглядит мыльным, а при зуме в редакторе пайплайнов графика не расплывается и выглядит четкой, как в веб-приложении. Я все еще вижу лаги, но они единичные и на мой взгляд вполне устранимые.
Ну а самое главное: раньше я мог только мечтать о таком векторном рендере, а сегодня я его коммичу в репозиторий 🥳
Шрифты. Контур глифа берем из FreeType и прогоняем через ту же тесселяцию. Меш строится один раз на глиф и рисуется при любой высоте шрифта и любом зуме. Интересная альтернатива distance field.
Плюсы: текст уходит в один батч со спрайтами, вызовов отрисовки вдвое меньше, зум не растеризует глифы заново, а статичный кадр по CPU не дороже атласа. Минус жирный: глиф стоит от 800 до 4000 треугольников против двух в атласе. Статичному тексту пофиг, а вот меняющемуся каждый кадр - нет. Сцена с окном анимации, где за кадр перестраиваются три десятка подписей, рисуется 2.7 мс вместо 0.33 мс. Так что по умолчанию текст пока остается на атласе.
Вторая - целиком кадр редактора, отрисованный дважды: растровыми спрайтами и векторными. По кадру вышло от 99.28 до 99.89% совпадения на 15 сценах.
Забавный момент: векторные иконки упорно выглядели тоньше оригиналов. Оказалось, Illustrator при экспорте в растр закрашивает пиксель, если фигура его хотя бы касается. ИИ сам это раскопал по диффам, утолстил контуры на 0.125 пикселя - и картинки сошлись.
- дать ограничения, что делать нельзя. Как правило это касается кодстайла и каких-то явно тупиковых ветвей размышлений, которые вы можете знать заранее. Например, я сразу хотел именно триангуляцию, ИИ не тратил время на ресерч других решений. Можно сразу ставить ограничения по перфомансу - "рендер должен укладываться в 6мс, фреймрейт должен быть стабильно 60 FPS". Здесь ИИ сразу проведет профилирование и сразу соптимизирует лагающие моменты.
У меня условие по скорости было простое: не медленнее спрайтов. Вышло так: статичный кадр редактора рисуется за 1.02-1.09 от растрового времени, а обновление вместе с отрисовкой даже быстрее - 0.78-0.89. Это замеры в Debug-сборке на ноутбуке.
- дать возможность делать работу самостоятельно. Смотреть документацию, исходники, историю коммитов. Уметь запускать приложение, писать и читать лог, запускать юнит-тесты, снимать скриншоты. Это все убирает мясную прокладку (извините, вас) и позволяет делать свою работу быстрее. Да, они все это могут при необходимости, но главное заложить эти возможности в проект. Иначе любой тест, любая ручная проверка упрется в вас, а значит сожрет кучу времени.
В задаче на перевод редактора в вектор, я буквально с мобилы следил за процессом, периодически получая скриншоты попиксельных диффов и выжимки по перфомансу. Весь цикл разработки ИИ гонял на ноутбуке сам, постепенно приближаясь к финальному решению.
"Изи" - отвечу я вам, и вот в чем секрет. Чтобы ИИ работал эффективно нужно использовать его сильные стороны и нивелировать слабые:
- дать подумать самому. Не всегда точное описание реализации работает хорошо. С инженерными задачами "думающие" модели справляются неплохо. Если вы все еще среди тех кто этому противится, вот вам пример - растровый рендер UI переведен на векторный чисто с помощью ИИ.
- дать инструмент самоконтроля. Чтобы кремниевый не халтурил (а они склонны к этому через час работы), нужно ставить четкие критерии приемки задачи. Причем, циферные: пока метрика Х не будет Y, не заканчивай и продолжай итерироваться. В таких условиях ИИ легитимно зацикливается на самопроверке, и вауля - результат начинает нравиться.
Конкретно у себя я поставил такой критерий: попиксельное сравнение растровых исходников всех спрайтов не должны отличаться от их векторных отрендеренных аналогов более чем на 1%. Жесткие, но понятные и самое главное измеримые рамки. В последствии я снизил порог до 2-3%, тк визуально уже было сложно отличить, а результат хотелось поскорее.
По факту проверок получилось две. Первая - каждый из 452 спрайтов редактора против своего PNG-оригинала.
Ну и куда же интерфейс без растягиваемых рамок. 9-slice тут работает как у обычного спрайта: меш один раз режется по линиям бордеров, а дальше при любом размере углы остаются как есть, а середина тянется. Бордеры лежат в .meta ассета, так что все стили редактора переехали на вектор без изменений.
Единственное узкое место - fillrate видеокарты. Если растр мы рисовали одним проходом, то векторная триангулированная графика будет рисоваться в несколько слоев, и каждый пиксель будет рисоваться с альфа-каналом, мы ведь не знаем заранее, что будет прозрачно?
Здесь, кстати, есть идея для оптимизации. На самом деле мы ведь знаем какая геометрия будет прозрачная, а какая нет. На этапе триангуляции мы можем разделить все меши на 2 части: непрозрачная и прозрачная. Непрозрачными могут быть фоны, градиенты, линии. А прозрачными те самые края в 1 пиксель, любые меши с альфой отличающейся от единицы.
И такие меши можно рисовать в 2 прохода, используя Z-buffer. Действительно, почему бы не записать глубину непрозрачного пикселя, и не отсекать все остальное? Например, если полупрозрачный пиксель располагается на глубине заведомо большей, чем был отрисован непрозрачный - зачем его рисовать? Здесь я еще не экспериментировал, но будет интересно посмотреть результаты.
В первой версии я пошел простым путем: свой меш под каждый масштаб - 1x, 2x для retina, 4x при зуме. За кадр строится не больше одного нового меша, остальные спрайты пока рисуются старым - зум не фризит. А готовая геометрия лежит на GPU, и статичный кадр вершины вообще не гоняет.
После триангуляции в меше остается куча лишних вершин - их оставляет sweep-line и нарезка по градиентам. Отдельный проход выкидывает все, без чего картинка не меняется. На кнопке редактора это минус четверть треугольников, а разницы на глаз ноль.
Нарисовать несколько сотен тысяч треугольников - не проблема даже для самого убогого мобильного GPU. Перестраивать меш можно очень редко при увеличении, да и по сути можно его один раз запечь и не перестраивать.