OpenFL Next, Joshua Granick

Как всегда, видеоверсия доклада доступна на официальном сайте Haxe.

Последний раз Джошуа выступал с докладом об OpenFL 5 лет назад в Париже. За эти годы OpenFL прошел большой путь.

Итак, в 2013 году OpenFL был представлен сообществу как попытка собрать в себя лучшее из того, что было предпринято для создания кроссплатформенного фреймворка, который бы одинаково вел себя и на десктопе, и на мобильных платформах и в браузерах. Напомню, что OpenFL появился как форк NME, в котором упор в основном делается на мобильные и настольные операционные системы, а поддержка html5 в настоящее время не является приоритетной (хотя скомпилировать приложение в js возможно двумя способами).

Значительным отличием OpenFL от NME является то, что большая часть функционала была переписана на Haxe. Т.к. существенная часть кода NME была написана на C++, то для поддержки html5 необходима своя версия “бэкэнда”, у которой были свои собственные баги и немного другое поведение. Джошуа со своей стороны хотел избежать таких проблем, максимально унифицировав кодовую базу и переписав ее на Haxe.

Такое решение переписать код было обусловлено также архитектурными ограничениями. Примерами таких ограничений являлись:

  • механизм отображения текста, в котором отсутствовала поддержка юникода, и текст мог рисоваться только слева направо;
  • использование собственного софтварного рендера для отрисовки векторной графики. Данная реализация не всегда работала как нужно и ее было крайне сложно поддерживать. Теперь для этого используется библиотека Cairo. Использование Cairo не только решило проблему неоднородности поведения на разных платформах, но и позволило освободить ресурсы для решения других актуальных проблем.

Что же было достигнуто за прошедшие 5 лет:

  • на OpenFL работает множество выпущенных в продакшен проектов;
  • OpenFL - самая скачиваемая библиотека на haxelib;
  • на OpenFL работают игры, получившие различные награды. Так игра “Papers, please” получила награду “Игра года” (и много других наград);
  • на OpenFL работает официальная версия библиотеки Away3D;
  • также OpenFL, как организация, поддерживает собственные порты библиотек Starling и DragonBones, таким образом охватывается большая часть экосистемы Flash, что упрощает портирование существующих ActionScript-проектов на Haxe;
  • в этом году OpenFL стал доступен не только на haxelib, но и в npm (но об этом чуть позже).

Далее Джошуа перечислил главные изменения в кодовой базе OpenFL за прошедшие 5 лет:

  • создание и использование библиотеки Lime позволило изолировать высокоуровневое Flash API (вынесено в OpenFL) от его реализации для разных платформ (реализовано в Lime). Это позволило разрешить множество проблем, обусловленных высокой степенью связанности кода, и также позволяет гораздо быстрее добавлять новые фичи. Также если вам не нужен Flash API, то есть возможность использовать только Lime для своих проектов (например, игра Speebot использует только Lime);
  • благодаря этому в самой библиотеке OpenFL теперь нет какого-либо платформозависимого кода, и сам OpenFL полностью переписан на Haxe;
  • добавлена реализация Stage3D API (от компании Zynga), что позволяет использовать AGAL-шейдеры не только для Flash, но и на других платформах (автоматически конвертируя их в GLSL-шейдеры);
  • реализованы фичи, которые раньше не поддерживались, а также повышена консистентность поведения на разных платформах (Windows, MacOS, Linux, iOS, Android, HTML5, Flash, AIR);
  • стабильный рендерер, который может работать на OpenGL, Flash, Cairo, Canvas и DOM.

Как и ожидалось, Джошуа анонсировал и выпустил во время конференции восьмую версию OpenFL. Что же в ней нового?

Во-первых, в OpenFL 8 был переработан класс Tilemap, предназначенный для батчинга при отрисовке квадов. За счет этого работа с ним упростилась, а скорость значительно повысилась (примерно в 3 раза по сравнению с предыдущей версией).

Для группировки и построения иерархий тайлов добавлен класс TileContainer (аналог DisplayObjectContainer).

Однако работа над этим API еще не завершена, и Джошуа ищет пути его возможного улучшения, например, рассматривает возможность добавления метода hitTest() для отдельных тайлов, а также методов для автоматической “нарезки” изображений на тайлы.

Tilemap работает на всех платформах и со всеми рендерами, используя наиболее эффективные для них способы отрисовки.

Новыми свойствами объектов типа Tilemap, влияющими на скорость отрисовки, являются tileAlphaEnabled и tileColorTransformEnabled (по-умолчанию они включены).

Однако, в случае, если вам не нужна поддержка цветовых трансформаций тайлов, то установка tileColorTransformEnabled в false может дать ~30% прироста скорости (благодаря тому, что на GPU будет передаваться меньше данных), отключение tileAlphaEnabled также даст небольшой прирост.

В качестве обязательных параметров конструктора Tilemap необходимо передать его размеры (позже их можно будет изменить) - на платформах, где используется софтварный рендер, под объект Tilemap будет создан “холст” (BitmapData), на который будут рисоваться тайлы.

У Tilemap есть ряд методов для манипуляции иерархией тайлов, добавляемых в него. Эти методы как по названию, так и по поведению похожи на методы DisplayObjectContainer.

Тайлам для отрисовки изображений необходимы 2 свойства:

  • tileset - это обертка для объекта BitmapData с дополнительными методами для определения областей, которые тайлы могут отрисовывать
  • id - идентификатор тайла, добавленного в tileset (первый параметр конструктора тайла). Его можно менять и после создания тайла.

Помимо этих свойств у тайлов есть такие свойства как matrix, scaleX, scaleY, x, y, rotation, alpha, colorTransform и даже shader (но использование шейдеров “ломает” батчинг).

Так что Tilemap API довольно сильно изменился в лучшую сторону по сравнению с тем, каким он был год назад.

Новой фичей OpenFL 8 является новый метод drawQuads(), который предлагается как замена старому drawTiles() (хотя по моему мнению drawQuads() не сможет заменить его полностью, т.к. имеет меньше возможностей, но для этого у Джошуа были веские причины).

drawQuads() также может использоваться для батчинга, но за счет того, что он не поддерживает иерархии квадов, цветовые трансформации для отдельных квадов, и даже прозрачность, то работает он еще быстрее, чем Tilemap. Так что для простых случаев, когда вам нужна только трансформация квадов (положение, поворот, масштаб), то можно воспользоваться этим методом.

Данный метод также работает на всех платформах со всеми рендерами.

Как видно drawQuads() в чем-то похож на метод drawTriangles(): вы задаете метод заливки и вызываете данный метод, передавая ему массивы данных о прямоугольниках, индексы этих прямоугольников для отрисовки, а также трансформации отдельных квадов. При этом если для каждого квада в массиве transforms передавать по 2 значения, то они будут интерпретироваться как данные о положении квадов, если по 6 значений - то как коэффициенты матрицы трансформации, а можно просто не передавать этот массив - тогда квады будут отрисованы в том же положении, как и в исходном атласе.

Для сравнения скорости работы новых API Джошуа провел ряд тестов на своей машине, которые показали следующие результаты в BunnyMark с 200 тыс. кроликов:

  • текущая версия PixiJS - 8 fps;
  • Tilemap старой версии - 4 fps;
  • Tilemap из OpenFL 8 - 31 fps;
  • drawQuads - 33 fps.

Так что результаты явно в пользу нового OpenFL.

В OpenFL 8 также была переработана система шейдеров, в которой:

  • у шейдеров теперь нет обязательных uniform полей, что должно упростить добавление собственных шейдеров;
  • классы шейдеров теперь могут быть строго типизированными. Генерация кода классов осуществляется с помощью макроса на основе GLSL-кода шейдера. Это позволяет сразу найти опечатки в коде, тогда как в прошлой версии классы шейдеров могли быть только динамическими, и нахождение ошибок в таком коде занимало больше времени. Но если хочется, то можно использовать шейдеры и старым способом;
  • механизм шейдеров также стал гибче, позволяя устанавливать значения как для отдельных вершин, так и для всей геометрии в целом;
  • в шейдерах появилась поддержка нескольких текстур;
  • теперь появился прямой доступ к шейдерам у объектов типа DisplayObject, Graphics и у фильтров. Например, у Спрайтов появилось свойство shader, устанавливая которое вы задаете шейдер, который будет использоваться при отрисовке вложенных в Спрайт объектов. У Graphics появился метод beginShaderFill(), который задает шейдер для методов drawQuads() и drawTriangles() (пока что поддерживаются только эти методы, но ведется работа по расширению списка поддерживаемых методов);
  • от шейдеров теперь можно наследоваться, сокращая тем самым количество кода, который раньше приходилось копировать.

Пример наследования для шейдеров, с сохранением типизации кода:

Как видно у шейдеров есть 2 поля, заданных с помощью метаданных - @:glVertexSource и @:glFragmentSource. Используя эти метаданные, макрос создает типизированный код шейдера, такой код на С++ будет работать быстрее.

Для уменьшения дублирования кода в шейдерах добавлены директивы #pragma header и #pragma body. Макрос при нахождении этих директив подставит вместо них соответствующий код из родительского шейдера, таким образом сохранится совместимость с ним. Ну а далее вы можете добавить и собственный код. На слайде в коде шейдера модифицируется цвет, получаемый на выходе из него. Также видно, что в CustomShader не переопределяется метаданные @:glVertexSource, это значит, что код для вершинного шейдера будет взят из родителя - из DisplayObjectShader.

Пример наследования шейдеров с использованием свойств glVertexSource и glFragmentSource - такой метод также возможен, но теряется типизация кода - все становится “динамиком”.

Как уже говорилось, шейдеры можно можно использовать для DisplayObject’ов и Graphics.beginShaderFill(), а также для фильтров. Однако при использовании шейдеров в первых двух случаях отрисовка происходит в один проход, а при использовании фильтров с шейдерами всегда происходит сначала отрисовка в промежуточную текстуру, а затем отрисовка с использованием шейдера из фильтра.

Новой фичей OpenFL 8 является возможность вызывать собственный код при отрисовке объектов (пока что находится в состоянии беты, так что API может поменяться). Для этого необходимо подписаться на одно из следующих событий у DisplayObject’а: RenderEvent.RENDER_CAIRO, RenderEvent.RENDER_CANVAS, RenderEvent.RENDER_DOM или RenderEvent.RENDER_OPENGL.

При этом в событии RenderEvent приходит необходимая для отрисовки объекта информация: сам рендерер, матрица трансформации и цветовая трансформация объекта.

Предполагается, что новое API заменит собой OpenGLView.

Пример использования нового API: как видно, мы подписываемся на событие отрисовки спрайта с помощью Canvas.

Также Джошуа еще раз отметил, что начиная с 7 версии OpenFL стал доступен в NPM, таким образом OpenFL можно использовать и в JavaScript и в Dart проектах. При этом NPM-версия идентична haxelib-версии библиотеки (в контексте JS, конечно же).

Джошуа надеется, что публикация OpenFL в NPM поможет популяризовать не только сам OpenFL, но и Haxe, например, в случаях когда потребуется портировать приложение с OpenFL JS на нативные платформы без использования electron и подобных решений.

Также на сайте OpenFL появился раздел с документацией по использованию OpenFL в NPM - http://www.openfl.org/learn/npm/getting-started/

Плюсом NPM-версии OpenFL по мнению Джошуа является поддержка hot reloading - при изменении кода он автоматически пересобирается и страница с проектом перезапускается. И Джошуа надеется, что данную фичу можно будет перенести и в haxelib-версию библиотеки.

Разработка OpenFL не останавливается и основными ее целями являются:

  • унификация поведения на разных платформах;
  • продолжение сотрудничества с разработчиками новых и существующих библиотек, работают с OpenFL;
  • улучшение поддержки swf-ассетов;
  • улучшение процесса работы с библиотекой (тот же hot reloading);
  • добавление новых фич, но не ради их добавления, а с учетом их влияния на производительность и сложность дальнейшей поддержки;
  • поддержание работоспособности библиотеки при выходе обновлений операционных систем и SDK (в первую очередь это касается iOS и Android).

Также продолжается работа по поддержке консолей. Примерами игр на OpenFL, которые вышли на консолях, являются “Papers, Please” (PS Vita) и “Defenders quest” (игра уже вышла на XBox One, PS4, PS Vita, и в данный момент ведется портирование на Nintendo Switch).

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

За деталями можно обратиться к Lars Doucet.

И, наконец, вышла версия HaxeFlixel 4.4.0, которая теперь совместима с OpenFL 8. Джошуа сильно помог в этом процессе.