Переписав свои обои на libGDX, грех было не оценить : “А насколько все это теперь быстрее работает?” Понятно, что openGL уделает канву… но насколько? Перейдем сразу к делу. У нас в распоряжении 2 версии обоев, функция “Method Profiling” и Kindle Fire с CM7 на борту…
Что меряем?
Во-первых, я расскажу из каких элементов состоит каждый кадр обоев. Смотрим на рисунок ниже.
Графика состоит из:
- Фон. Заполняется бесшовной текстурой.
- Надпись “МИРЭА”. Хранится в виде спрайта. Просто выводится поверх фона.
- Кольца. До 5 колец одновременно. Будем их назвать Кольцо0-4. 0 – самое маленькое, 4 – самое большое.
Во-вторых, нужно понять, что мы меряем. А мерить мы будем время выполнения нашего кода анимации на ЦП (центральный процессор). Измерять мы его будем с помощью функции “Method Profiling”, которую нам предлагает SDK. Она показывает сколько времени код определенного потока занимал ЦП. Следуем помнить, что в случае OpenGL отрисовкой занимается графический чип (aka видеокарта), а не ЦП, ресурсы которого расходуются на растеризацию, наложение текстур и др. в случае использовании канвы. К сожалению, я не нашел способа как сравнить потребление ресурсов графического чипа канвой и openGL… но в рамках данной статьи не это является целью исследования, поэтому переживем.
Ну и в-третьих, запускать все будем на Kindle Fire с прошивкой CM7. Экран 1024 x 600, двухъядерный 1ГГц процессор OMAP4 и простенькое графическое ядро PowerVR SGX540 (он же графический чип и видеокарта).
Канва.
Начнем анализ производительности с версии написанной на канве. В Eclipse выбираем вкладочку DDMS, в списке процессов ищем наш процесс рисующий графику, нажимаем кнопочку “Start Method Profiling”, ждем некоторое время пока соберется статистика, жмем “Stop Method Profiling” и смотрим красивую картинку, интересующий наш кусочек которой приведен ниже.
По картинке мы видим, что наша программа выполняется в рамках одного потока. Визуализация хода работы потока состоит из повторяющегося шаблона, так как каждый кадр рисуется практически одинаково. Сразу можно выделить несколько больших зон в шаблоне, которые я обозначил Z0-Z6. Наведя мышкой на зоны можно посмотреть какой функции программы соответствует зона и сколько времени выполнялась функция (чем дольше, тем больше зона). Получаем следующую статистику:
- Z0. функция Canvas.native_drawRect. Это фон с бесшовной текстурой. Время выполнения: 23.6ms
- Z1-Z4. функция Canvas.native_drawRect. Это кольца. Z1 – самое большое кольцо (рисуется дольше других колец), Z4 – самое маленькое кольцо. Время выполнения: 6.6, 5.3, 2.7, 1.0ms соответственно.
- Z5. функция Canvas.native_drawBitmap. Это надпись “МИРЭА”. Время выполнения 2.8ms.
- Z6. функция Surface.UnlockCanvasAndPost. Отправляет наше нарисованное на канве изображение на вывод на экран. Время выполнения: 8.9ms.
Кроме крупных зон есть еще разная мелочевка, но она погоды не делает. Общее время, которое тратиться на отрисовку одного кадра: 54ms… миллисекунды? Это же очень много? Такая простая графика занимает 54ms? Это максимум 1000/54 = 18.5 кадров в секунду… а если на рабочем столе есть еще пара тяжелых виджетов, то плавность анимации будет ни к черту… печаль. Но это ожидаемый результат, все действия с изображением, включая масштабирование, наложение текстур, растеризацию, создание линий и др. выполняются на ЦП, который для этого мягко говоря не создавался. Не маловажно еще и то, что под все графические ресурсы и под буфер для нового кадра, приходится отводить драгоценную память приложения, что не есть хорошо.
OpenGL/libGDX.
Теперь посмотрим, сколько будет потреблять ресурсов также самая анимация, но написанная на openGL с использованием библиотеки libGDX. Должно работать побыстрее. Смотрим на рисунок.
А картинка стала совсем другой. По-прежнему на графике видим повторяющийся шаблон (каждый кадр делаем одно и тоже), но зоны стали другими. Посмотрим, что внутри:
- A0. Всякая мелочевка libgdx. Включая код вывода фона и надписи “МИРЭА”. Время выполнения: 4ms
- A1-A5. Розовые зоны. Расчет координат окружностей. Cамые продолжительные зоны. Оно и понятно, тут вычисляются координаты треугольников для триангуляции колец. Каждое кольцо состоит из 192х треугольников, координаты которых каждый кадр вычисляются с помощью медленных функций Math.sin и Math.cos. Я специально не стал пока оптимизировать этот кусок кода, чтобы картинка была нагляднее. Время выполнения зон A1-A5: 9ms.
Итого имеем: время отрисовки одного кадра 12ms, из которых 6.5ms уходит на то, чтобы обновить координаты колец. Т.е. на управление выводим графики мы тратим всего 5-6ms, чисто теоретически можно успевать рисовать 1000/6 = 166 кадров к секунду. Неплохо. Стоит заметить, что в это время не входят затраты графического чипа, который занимается рендерингом и выводом графики на экран. ЦП посредством команд openGL лишь дает команды видеокарте, типа “выведи вот эту текстуру вот в эти координаты и увеличь ее в 2 раза”. Благодаря этому драгоценные ресурсы ЦП можно пустить на расчет логики вашего приложения, а графикой будет заниматься специально созданный для этого чип, который делает это на порядок быстрее и менее энергозатратно чем ЦП. Стоит также заметить, что текстуры и многие другие данные, хранятся в памяти видеокарты и не расходуют лимитированную память приложения.
Заключение.
Я не пытался получить точные цифры и не делал 1000 экспериментов и не смотрел гистограмму распределения результатов и не считал ее “среднее”. Однако полученные цифры позволяют качественно понять разницу между применением openGL и canvas. Ну а теперь выводы:
- openGL на порядок меньше расходует ресурсов ЦП. Следовательно дольше живет батарейка, можно делать плавную и сложную анимацию. На один кадр рассматриваемой анимации libGDX тратит 6ms. Еще 6ms тратим мы сами для расчета координат колец. Canvas на ту же анимацию тратит 54ms, то есть в 4-5 раз больше.
- если в вашем приложении нет ресурсоемкой логики, то его производительность упирается в графический чип устройства. ЦП практически не заметит разницу между выводом на экран 10 или 1000 текстур, а вот видеокарты может с какого-то момента не хватить.
- canvas довольно шустрый, но только на маленьких площадях. Проблема канвы в том, что ее скорость работы пропорциональна площади рисунка. Поэтому залить фон или вывести большой по площади объект довольно затратно. А вот нарисовать, что-то мелкое и простое, получается довольно шустро.
- рисовать полноэкранную плавную анимацию с помощью канвы не получится. В лучшем случае удастся выжать 15-20 кадров, при условии, что параллельно нам не работает другое ресурсоемкое приложение. А вот нарисовать анимацию на небольшой кнопке или “песочные часы” – всегда пожалуйста.
5 комментариев
Лев Сообщает:
11.02.2013 на 10:43 дп (UTC 0 )
Можете пожалуйста сделать туториал, как с этой библиотекой создать простые обои?
PandaСoder Сообщает:
11.02.2013 на 6:36 пп (UTC 0 )
Хороший туториал делать слишком долго, исходники лежат на гитхабе.
Лев Сообщает:
12.02.2013 на 4:57 дп (UTC 0 )
О, спасибо, не заметил!
Лев Сообщает:
12.02.2013 на 5:14 дп (UTC 0 )
Только там не все классы есть. Например, нету MireaWallpaperEngine, LibdgxWallpaperListener
Лев Сообщает:
12.02.2013 на 5:32 дп (UTC 0 )
И где теперь искать LibdgxWallpaperService? Кажется, в новой версии libgdx, gdx-backend-android-livewallpaper был объединен с основной библиотекой.