Препарируем движок MagicEngine часть 2

Подсистема вывода графики движка MagicEngine.
Изначально вывод графики в движке был ориентирован на строго одну платформу и несоклько раз полностью переписывался. Со временем возник интерес сделать систему с подключаемыми модулями(драйверами) вывода видео — что и используется в данный момент.
Исторически использовалось несколько подходов к отрисовке 2д графики в движке:
1. Прямой доступ к видео памяти/экранному буферу: данный метод использовался во времена DOS(защищенный режим), Windows98 (DirectDraw) огромным минусом такого подхода является необходимость написания довольно большого объема кода для управления выводом графики и  достаточно низкая производительность. Все данные хранятся в оперативной памяти и посылаются на отрисовку по готовности — а значит для разрешения 1024*768 отсылается 3 мегабайта данных на каждый кадр, что весьма сильно ограничивает скорость. К томуже все операции со спрайтами производятся на ЦПУ, который выполняет и другие задачи.
2. С переходом в ОС Linux данный метод был ликвидирован и ему на смену пришел кросплатформенный SDL в котором я также столкнулся с проблемами скорости, да и сама библиотека оказалась слишком монструозной.
В данный момент я остановился на модульной системе вывода графики и в качестве основного драйвера — использование OpenGL для отрисовки спрайтов. Конечно спрайты отрисованные таким образом могут получить некоторые дефекты — но с другой стороны можно получить гораздо большую скорость.

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

Драйвер GLFW.
Последней и единственной развивающейся версией драйвера на данный момент — является драйвер GLFW.
GLFW — это свободно распространяемая кросплатформенная библиотека для работы с окнами и пользовательскими событиями.
Данный драйвер использует также несколько вспомогательных библиотек — например GLEW которая позволяет достаточно простым способом работать с расширениями OpenGL

Цикл работы драйвера.
В целом в типичном приложении работа с видео драйвером выглядит примерно так:
Video::_init();
Video::_create_sprite(…);
Video::_add_sprite(..);
Video::_draw();

Устройство видео драйвера.
Видео драйвер умеет оперировать со следующими объектами:
1. Поверхность — битмап в формате RGBA в котором хранится некая картинка. (на самом деле это текстура, хранящаяся в видео памяти)
2. Спрайт — объект, содержащий в себе поверхность. Несколько спрайтов могут иметь одинаковую поверхность. Представляется в виде квада (GL_QUAD)
3. Частицы — набор частиц, имеют всегда общую поверхность.
4. Событие — объект содержащий информацию о событиях произошедших с приложением (нажатия кнопок, закрытие окна и т.д.)

При добавлении некоторого объекта на отрисовку (add_srite, add_particles) они добавляются в массив объектов которые надо рисовать. Все объекты хранятся внутри видео драйвера, поэтому добавляется только указатель на этот объект и при вызове _draw() по данному вектору происходит построение картинки на экране.

Отрисовка спрайтов.
Как уже было сказано — список спрайтов которые требуется нарисовать лежит в векторе и поидее можно просто идти последовательно по этому списку и делать
glBindTexture(sprite_surface);
glBegin(GL_QUADS);
… рисуем
glEnd();
Однако при использовании такого подхода (а он использовался) мы получим сильное падение производительности, потомучто BindTexture — достаточно медленная операция.
Поэтому в данный момент отрисовка происходит так:
С начала делается glBindTexture для текущего спрайта. Потом ищутся все спрайты, которые используют этуже текстуру и находятся в списке отрисовки. Это дает огромный прирост проивзодительности.

Отрисовка частиц.
Наверное отрисовка частиц — самое геморойная часть видео драйвера. Была главная задача — получить максимальную скорость.
Классическое решение — PointSprites. PointSprite — это большая точка на которую натянута текстура и которая для особой радости окрашена в какойнить цвет.
Однако с ними возникает проблема — не все видеокарты их умеют обрабатывать, а на некоторых АТИ картах с ними имеются крупные проблемы.
Второй вариант — использование квадов — в 4 раза медленней но надежно.
Я решил использовать оба этих варианта — если возможно — PointSprites если нет — GL_QUAD.

Дополнительная оптимизация.
Даже с учетом всех этих рекомендаций скорость всеравно оставляет желать много лучшего. Так на GF8800 GTS получается в районе 300fps при примерно 4000 спрайтов и частиц
Здесь на помощь приходит VBO. Суть его в том, чтобы хранить на видеокарте не только текстуры, но и все координаты и цвета. Так для каждой частицы создаются буферы для хранения их координат и цветов, а в некоторых случаях (если не используется PointSprites) и координат текстур.
Использование VBO повысило скорость работы на старой видеокарте в 5 раз. Разумеется это повышает системные требования, поэтому данная возможность (как и точечные спрайты) является опциональной.

События.
Обработка событий сделана в виде callback функций в библиотеке glfw. В нутри драйвера есть некая очередь в которую складываются все приходящие события. При запросе событий — отдается первое в очереди.


Comments are closed.