В своем докладе Дэн рассказал об оптимизациях отрисовки графики, которые команда разработчиков игры Forge of Empires использовала при портировании с Flash на HTML5.
Видеоверсия доклада доступна на youtube.
Forge of Empires - многопользовательская браузерная онлайн-игра в жанре стратегии и градостроительства. Изначально игра была разработана на Flash, а в этом году стала доступна html5-версия. О процессе ее портирования с использованием Haxe и OpenFL на прошлогоднем Haxe Summit в Амстердаме был большой и интересный доклад.
Хотя большинство проблем при портировании игры на html5 было связано с автоматической конвертацией ActionSctipt-кода в Haxe, часть из них все же была связана и с отрисовкой графики:
При портировании игры вместо swf-библиотек с графическими ассетами стали использоваться графические атласы. Однако, изначально эти атласы использовались совершенно неэффективно с точки зрения потребления памяти и оптимизации отрисовки - для каждого игрового объекта создавалась новая текстура.
Для того, чтобы оптимизировать данный момент, стал использоваться прием, получивший название SubBitmapData
. Класс SubBitmapData
наследуется от класса BitmapData
, однако он представляет собой только некоторую область исходного изображения (ссылается на область из атласа). Поэтому SubBitmapData
может использоваться там же, где требуются объекты типа BitmapData
, но при этом новая текстура не создается. Данный прием мало влияет на число вызовов WebGL API, но позволил сэкономить CPU-ресурсы, которые тратились на копирование областей изображений из атласов, а также видеопамять, т.к. количество используемых текстур уменьшилось на порядок.
Следующей оптимизацией было использование батчинга при отрисовке графики. В целом, батчинг - это метод снижения взаимодействий между CPU и GPU ценой дополнительных вычислений на CPU для предварительной подготовки данных, посылаемых на GPU. То есть вместо того, чтобы отрисовывать каждый объект по-отдельности, мы специально подготавливаем данные об этих объектах и рисуем их с помощью одного вызова команд для отрисовки графики.
Проблема №1 с батчингом в OpenFL связана с тем, что в общем случае в OpenFL батчинг не работает (он реализован только для объектов типа Tilemap
). Примерно так выглядит код в OpenFL, ответственный за отрисовку объектов:
Проблема №2 - в OpenFL используется самая простая “наивная” реализация батчинга, в которой объекты рисуются с помощью одного вызова отрисовки графики только в том случае, если они используют одну и ту же текстуру:
В случае Forge of Empires такого подхода было недостаточно: игра была разработана под Flash без учета возможностей использования GPU, ассеты для игровых объектов были разбросаны по разным атласам, поэтому при отрисовке кадра постоянно происходило переключение между текстурами и смысл батчинга терялся, выигрыш от его использования сводился на нет.
Поэтому вместо “наивного” подхода к батчингу стал использоваться более сложный его вариант с одновременным использованием нескольких текстур:
Его основная идея заключается в том, что так как GPU одновременно может работать с несколькими текстурами, то и объекты в одном “батче” (наборе объектов, отправляемых на отрисовку с помощью одного вызова) также могут использовать не одну общую текстуру, а несколько разных текстур.
Как обычно работает отрисовка объектов в играх:
Шейдер, показанный на предыдущем слайде, является упрощенной версией шейдера, используемого в OpenFL для отрисовки объектов. В нем, как видно, используется всего одна текстура, и он может использоваться (и используется) для “наивной” реализации батчинга. Для этого вместо того, чтобы посылать данные о вершинах одного объекта, нужно посылать данные о вершинах объектов, объединенных в “батч”.
Батчинг с использованием нескольких текстур работает несколько иначе:
gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
Для использования батчинга с использованием нескольких текстур в OpenFL пришлось внести изменения в код, отвечающий за отрисовку объектов типа Bitmap
(отрисовка объектов типа Graphics
и TextField
осуществляется с помощью этого же кода, т.к. в OpenFL графическое представление объектов данных типов хранится в памяти как объекты BitmapData
). В итоге упрощенная версия кода для отрисовки объектов стала выглядеть так, как показано на следующем слайде:
Этот упрощенный код выглядит также как и “наивная” реализация батчинга, однако чтобы увидеть отличия, необходимо заглянуть в алгоритм работы батчера:
flush()
у батчера:
В итоге, использование текстурных атласов и более сложной версии батчера для отрисовки графики позволило значительно снизить число вызовов методов отрисовки графики и таким образом улучшить производительность игры даже на слабом железе.
И что немаловажно, для достижения таких результатов не потребовалось вносить изменения в код клиента игры - все изменения производились в переработке игровых ассетов и в доработке кода в OpenFL.
В качестве способа дальнейшего улучшения работы батчинга Дэн рассматривает возможность использования метода Instanced rendering, с помощью которого можно снизить дублирование данных, загружаемых в видеопамять (сейчас при загрузке данных дублируется информация о цвете вершин объекта и об используемой этим объектом текстуре).
К сожалению, из-за существенных изменений в OpenFL 8 (перевод отрисовки объектов на Stage3D API), перенос изменений, сделанных командой Forge of Empires в их форке OpenFL (они использовали OpenFL 7.1.1), является крайне трудоемкой задачей. Надеюсь, что когда-нибудь это произойдет.
В свою очередь я попытаюсь внести аналогичные изменения хотя бы для класса Tilemap
, т.к. активно использую его в работе.