Порт dagon:imgui на SDL GPU

Рендер Dagon 2 еще не завершен, но я решил уже сейчас наскоком закрыть одну из самых нестандартных задач всего проекта — написать бэкенд ImGui для SDL GPU. Что интересно, эта работа вскрыла очередную проблему с фрейм-таймингом, на которую я раньше не обращал внимания.

ImGui — потрясающе хорошо спроектированная библиотека. Она рисует интерфейсы при помощи абстрактного низкоуровневого воркфлоу, путем формирования списка примитивов. Чтобы фактически вывести графику ImGui на экран, нужно написать код, который в реальном времени передает эти данные в графический API. Это не самая тривиальная задача, но, как ни парадоксально, для SDL GPU она решается даже несколько проще, чем для OpenGL. Меня вообще этот API зацепил именно тем, что он избавляет от неудобных и довольно запутанных идиом OpenGL, не порождая при этом кучу новых, как это делают другие. На SDL многие распространенные конструкции выглядят в разы короче, чем на OpenGL или Direct3D (и я уж молчу про чистый Vulkan). В общем, оказалось, что ImGui отлично дружит с SDL GPU.

Перебор примитивов, полученных через igGetDrawData, и их трансляция в вызовы отрисовки — довольно простой процесс. Управление вершинным и индексным буферами под это дело уже сложнее: особенность в том, что предельное количество входящих примитивов ImGui заранее не известно, поэтому буферы должны быть динамическими. Точнее, нужно их динамически пересоздавать с запасом, если текущего объема не хватает. Когда пишешь такое, ощущение как от системной разработки — кажется, будто создаешь какой-то свой X11 😅

Но мало написать графический бэкенд, нужна еще прослойка, обрабатывающая ввод из SDL, иначе интерфейс не будет реагировать на события мыши и нажатия клавиш. Ее тоже пришлось написать самостоятельно, чтобы не трогать внутренности cimgui.dll — в Dagon 1.0 я полагался на встроенный в библиотеку бэкенд под SDL2. Фактически эта задача сводится к переводу событий и клавиатурных кодов SDL в формат ImGui, а также небольшому хаку с функциями TextInput для того, чтобы правильно работал ввод текста:

if (io.WantTextInput)
{
    if (!SDL_TextInputActive(application.window))
        SDL_StartTextInput(application.window);
}
else
{
    if (SDL_TextInputActive(application.window))
        SDL_StopTextInput(application.window);
}

Наконец, последнее, что я сделал — слегка изменил логику рендера, а именно вынес получение текстуры свопчейна из PresentPass в Renderer, в начало кадра (это нужно, чтобы можно было рендерить в задний буфер несколькими проходами подряд). Не поверите, но это единственное, что пришлось изменить в ядре движка, чтобы добавить поддержку ImGui! Все остальное — исключительно надстройки.

Я было обрадовался, но потом заметил инпут-лаг (причем в Dagon 1.x он тоже есть, хотя и не такой сильный). Когда перетаскиваешь окошко ImGui, видно, что курсор его опережает, хотя, по идее, не должен, если движение синхронизировано со вводом. Это напрямую связано с VSync. Непосредственно в игре задержка ввода не так заметна, как в GUI, но она есть, и в некоторых случаях из-за этого возникает что-то сродни морской болезни. Вокруг этой темы сломано немало копий, холивары о том, зло ли VSync, бытуют еще чуть ли не с 90-х. Иногда, при конфликте с оконным менеджером ОС, включение VSync может привести к еще более неприятному артефакту в виде статтеринга, так что я все же склоняюсь к мысли, что классический VSync больше мешает, чем помогает.

Для решения этой проблемы в современных API придумали Mailbox. Это разновидность тройной буферизации, которая позволяет выводить кадры с минимальной задержкой на синхронизацию с монитором. На моей системе этот режим показывает себя на удивление хорошо, стабилизируя тайминг кадра так, что реакция на ввод срабатывает молниеносно. Естественно, я его добавил в Dagon 2. Он включается опцией vsync: 2 в settings.conf.

Dagon 0.24.0

Выпустил новую версию движка. Основное нововведение — рендеринг текста теперь является частью ядра движка в качестве модуля dagon.graphics.font (таким образом, FreeType становится зависимостью ядра). Расширение dagon:ftfont удалено. В связи с этим я решил добавить в Dagon встроенный простой GUI. В данный момент поддерживаются перетаскиваемые окна, вывод многострочного текста (TextView), строка ввода (TextInput) и консоль (Console). GUI-тулкит встроен в объект Scene — доступен как свойство Scene.ui.

Добавлен менеджер шрифтов (Application.fontManager). Он загружает шрифты по умолчанию (Liberation Sans и Liberation Mono, которые прилагаются к движку) и позволяет создавать новые.
Добавлена базовая поддержка библиотеки Assimp (расширение dagon:assimp). Поддерживаются меши, узлы и некоторые свойства материалов (диффузный цвет, текстуры).

Также реализована возможность изменить системный курсор мыши методом Application.setCursor. Добавлена поддержка логирования в буфер.

Встроенный UI в Dagon

Наконец-то дошли руки начать пилить свой уютный UI-тулкит! Убийцу ImGui я, конечно, не планирую — это будет набор базовых виджетов для добавления в игру простых отладочных инструментов. Уже готовы окна и TextView — на скриншоте ниже демонстрируется вывод лога приложения.

Докинг в ImGui

В тулките ImGui меня особенно радует поддержка докинга окон — это просто киллер-фича для создания сложных интерфейсов для различных редакторов, причем использовать ее очень легко, нужно только создать ImGuiIO со включенным докингом (io.ConfigFlags |= ImGuiConfigFlags.DockingEnable), а затем добавить в очередь объект DockSpace поверх вьюпорта:

ImGuiViewport* viewport = cast(ImGuiViewport*)igGetMainViewport();
ImGuiID mainDockSpaceId = igDockSpaceOverViewport(viewport, 
    ImGuiDockNodeFlags.PassthruCentralNode);

Затем можно создавать окна, как обычно — при перетаскивании окна появится виджет для его прикрепления к верхнему, нижнему, правому или левому краю вьюпорта. При наслоении окон друг на друга они отображаются в виде вкладок.

if (igBegin("Settings", null, ImGuiWindowFlags.NoCollapse))
{
    if (igCollapsingHeader("Post-processing"))
    {
        //...
    }
    
    igEnd();
}

Состояние докинга сохраняется в файл imgui.ini, и при повторном запуске приложения ваши окна будут закреплены уже изначально, что очень удобно.

Пример ImGui в Dagon

В Dagon 0.14 появилось расширение для работы с популярным UI-тулкитом ImGui — dagon:imgui. Это у меня уже второй инструмент для создания пользовательских интерфейсов после Nuklear, и во многом ImGui оказался проще и удобнее — хотя, конечно, оба тулкита имеют свои особенности, и нельзя сказать, что один однозначно лучше другого. Но ImGui на сегодняшний день является де-факто стандартом, поэтому его поддержка стала довольно важной вехой в развитии движка. Расширение основано на биндинге BindBC ImGui, модуль bindbc.imgui.ogl (ImGuiOpenGLBackend) оказался полностью совместим с Dagon.

(далее…)

Dagon 0.14.0

Выпустил новую версию движка. Основное нововведение — рефакторинг системы текстур: в частности, текстуры теперь могут быть загружены напрямую, без необходимости создавать объект SuperImage, что позволяет создавать текстуры любого формата, поддерживаемого OpenGL — таким образом, достигается лучшая совместимость с DDS. 2D-текстуры и кубические карты объединены в один класс Texture, и работать с ними стало проще. Появилась поддержка формата сжатия ASTC.

(далее…)

ImGui

В следующей версии Dagon появится экспериментальная интеграция популярного UI-тулкита ImGui на основе биндинга bindbc-imgui — очень интересный инструмент, я только начал его изучать, но уже многое нравится. Удивил большой выбор готовых виджетов.

Nuklear при этом никуда из движка не денется, оба тулкита будут сосуществовать в виде двух отдельных расширений.

Dagon 0.10.0 и dlib 0.16.0

Вышли новые версии движка Dagon и библиотеки dlib — 0.10.0 и 0.16.0 соответственно. Релиз Dagon один из самых крупных за всю историю проекта: он содержит 226 коммитов и труд четырех разработчиков (Тимур Гафаров, Mateusz Muszyński, Rafał Ziemniewski, dayllenger). Вот краткий список изменений:

  • Рендеринг ландшафтов, в том числе процедурных с использованием шума OpenSimplex.
  • Интеграция GUI-тулкита Nuklear.
  • Поддержка декалей для статических поверхностей.
  • Поддержка трубчатых источников света (tube area light) и «фонариков» (spot light).
  • Поддержка кубических карт.
  • Улучшенный HDR glow.
  • Множество новых функций для объектов Entity.
  • Поддержка твинов (tween) для анимации перемещения, поворота и масштаба объектов. Встроенный набор функций изинга включает linear, quad, back, bounce.
  • Улучшенный таймер.
  • Система конфигурации приложений.
  • Менеджер ввода (InputManager), позволяющий настраивать конфигурации клавиатуры и устройств ввода.
  • Поддержка отладочных сообщений от видеодрайвера (при помощи OpenGL-расширения GL_KHR_debug).

Полный список изменений смотрите на странице релиза. Также было обновлено и демонстрационное приложение.

Напоминаю: если вы заинтересованы в развитии этого проекта, то можете поддержать его на Patreon: https://www.patreon.com/gecko0307. Вы также можете сделать разовое пожертвование через PayPal: https://www.paypal.me/tgafarov. Заранее благодарен!