libVLC и видеотекстуры

Я давно искал удобное решение для воспроизведения видео в играх, и вот, наконец, оно найдено — libVLC, библиотека, на которой основан всеми любимый плеер VLC. В отличие от FFMPEG, это высокоуровневый API, который позволяет буквально в несколько строк добавить видеоплеер в любое окно по HWND (или X window ID под Linux). Но самое интересное, конечно, декодировать видео во внеэкранный буфер, чтобы потом передать его в OpenGL-текстуру — libVLC это также позволяет с минимальными телодвижениями. Я уже добавил в репозиторий Dagon экспериментальное расширение dagon:video, которое предоставляет класс Video, обертку над libvlc_media_t* и libvlc_media_player_t*. Использовать его очень просто — сначала создается VideoManager, общий для всего приложения (лучше хранить его в классе, наследующем от Game):

VideoManager videoManager = New!VideoManager(this);

Затем, уже в сцене, создается источник видео:

Video video = New!Video(videoManager, 1920, 1080, assetManager);
video.open("media/video.mp4");

auto videoMaterial = addMaterial();
videoMaterial.baseColorTexture = video.texture;
videoMaterial.alphaTestThreshold = 0.0f;

video.play();

Можно использовать видеотекстуру для любых целей — показать заставку в начале игры, натянув на screen-aligned плоскость, или применить к мешу для создания внутриигрового экрана. Можно даже воспроизводить 360-градусные ролики в равнопромежуточной проекции для отображения на панорамном фоне.

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. Добавлена поддержка логирования в буфер.

Ретроспектива: Bud, DSSS и другие старые системы сборки

До появления DUB в 2012 году в сообществе D-шников не было единой системы автоматизации сборки. Все использовали что-то свое — от классического Make до кастомных скриптов. Расскажу о нескольких таких проектах, которые застал лично.

Build/Bud

Согласно deadalnix, первой известной системой сборки для D была программа Build (впоследствии переименованная в Bud), которую написал Дерек Парнелл в середине нулевых. Является ли название DUB отсылкой к этому проекту, я точно сказать не могу, но вероятность такая есть) В 2011 году Bud 3 была портирована на D2, ее даже можно скачать по ссылке ниже.

Как и все ранние системы сборки, Bud использовала собственный формат конфигов, похожий на INI-файлы. Конфигурация была до крайности громоздкой и неудобной, и альтернативы стали появляться, как грибы после дождя.

Страница Build/Bud на DSource: https://dsource.org/projects/build

D Shared Software System (DSSS)

Довольно мощный сборщик, написанный Грегором Ричардсом в качестве альтернативы Build. DSSS, строго говоря, был комплектом из сборщика Rebuild и пакетного менеджера DSSS. Для конфигурации он использовал файлы *.conf, концептуально тоже близкие к INI — наборы пар «ключ=значение». Интересной фичей DSSS была поддержка нескольких таргетов в одном конфиге: конфиг мог быть поделен на секции, каждая из которых описывала сборку отдельного таргета, ассоциированного с указанным главным модулем:

[example.d]
target=example_binary

Можно было собирать один и тот же модуль в разных конфигурациях:

[example.d+withfeature]
target=example_binary_withfeature
buildflags=-version=withfeature

Также поддерживались хуки (prebuild, postbuild, preinstall, postinstall и др.), которые впоследствии были реализованы и в DUB. Другая концепция родом из DSSS — возможность установки собранного ПО в систему, аналогично dub fetch/dub run.

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

Страница оригинального DSSS на DSource: https://dsource.org/projects/dsss

Форк для D2 на GitHub: https://github.com/apriori/dsss

xfBuild

Тоже проект родом из нулевых, система сборки от легендарного h3r3tic. Одно время была перспективной альтернативой DSSS, но, к сожалению, тоже исключительно под D1, да еще под Tango — поэтому подробно о ней ничего сказать не могу, я ей никогда не пользовался.

Существует порт xfBuild на D2: https://github.com/SiegeLord/xfbuild

Cook

Был период в истории D, когда все пользователи поделились на два лагеря — сторонники Bud и любители DSSS, и эта ситуация создавала серьезные сложности для мейнтейнеров публичных проектов. Затем Парнелл и Ричардс пропали из сообщества, обе системы утратили активную поддержку, и вновь наступили «темные века». Именно в это время я открыл для себя D. Отсутствие сборщика под D2 побудило меня написать сначала простой скрипт для запуска через RDMD, а потом полноценную программу под названием Cook.

Программа автоматически сканировала структуру модулей проекта и кэшировала зависимости между модулями, так что циклы пересборки были очень быстрые. В отличие от DUB, Cook следовала идее инкрементальной сборки: все D-модули компилировались в отдельные объектные файлы, и пересобирались только те модули, которые были изменены (плюс все зависящие от них, так как в D нет деления на интерфейсные файлы и файлы реализации — если изменились шаблонные сущности, то нужно пересобирать код, который их использует).

В Cook я тоже использовал собственный формат conf-файлов — JSON в те времена был еще не так популярен, да и не очень он удобен для конфигов, если честно. Cook могла работать без конфига, если проект не использует специфичные настройки тулчейна. В конфиге можно было указать пути к компилятору и линкеру, указать флаги (cflags, lflags), переопределить команды компиляции и линковки и многое другое — громадным преимуществом Cook была возможность запускать разные версии компилятора. Вот как выглядел мой типичный кроссплатформенный конфиг:

windows.compiler_dir: "C:/dlang/dmd-2.067/windows";
linux.compiler_dir: "/opt/dlang/dmd-2.067/linux";
linux.compiler: "%compiler_dir%/bin32/dmd";
linux.linker: "%compiler_dir%/bin32/dmd";
windows.compiler: "%compiler_dir%/bin/dmd";
windows.linker: "%compiler_dir%/bin/dmd";
windows.obj.path: "o_windows";
linux.obj.path: "o_linux";

Формат конфигов представлял собой, по сути, мини-DSL с поддержкой любых кастомных переменных (не такой мощный, как в CMake, но все же получше, чем INI). Для переменных поддерживались платформ-префиксы. Подобно другим старым сборщикам, Cook поддерживала мультитаргетные проекты. Еще она могла загружать зависимости из git-репозиториев и подключать к проекту зависимости, хранящиеся локально. Ее можно было даже настроить для кросс-компиляции windows-программ под Linux при помощи Wine!

Получилась до того удобная штука, что я использовал Cook аж до конца десятых, предпочитая ее DUB, и прекратил разработку лишь в 2019 году, когда уже стало ясно, что DUB теперь полноправный стандарт. Cook прекрасно работает до сих пор — можете скачать, собрать и протестировать: https://github.com/gecko0307/cook2. Есть определенные задачи, где она, имхо, подходит лучше, чем DUB — например, при сборке в экзотические целевые платформы с использованием LDC.

Не могу сказать, что я доволен DUB на 100%, есть задачи, которые он не покрывает (впрочем, как и альтернативные сборщики). Например, до сих пор нет инструментария для автоматической интеграции в проект библиотек, написанных на других языках. Но эта проблема, судя по всему, в общем случае нерешаема.

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

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

Обновления

Dagon 0.23.1

Небольшое обновление Dagon 0.23. Добавлена поддержка переключения отдельных рендер-таргетов в PassTerrain — то есть, для материалов ландшафта работают свойства outputColor, outputNormal и др. Движок теперь использует dlib 1.3.2. Добавлены новые уроки — 9, 13 и 15.

К следующему релизу готовлю поддержку Assimp, о которой уже писал, а также перенос расширения dagon:ftfont в ядро движка, что позволит реализовать различные встроенные UI-компоненты.

BindbC-Assimp

Биндинг к Assimp 5 размещен на GitHub и доступен в качестве DUB-пакета. К сожалению, название bindbc-assimp занято заброшенным и несуществующим ныне проектом, поэтому пришлось зарегать как bindbc-assimp5.

Оптимизация блога

Я настроил объектный кэш на основе Redis, а также добавил HTML-кэш и заголовок Cache-Control, что заметно ускорило загрузку страниц блога. Было исправлено множество мелких проблем верстки. Все архивные игры теперь размещены на одном сервере с блогом.

Тема, которую я использую, теперь также доступна на GitHub. Это форк WPEX Blogger 1.2 от WPExplorer, в котором я внес исправления для совместимости с PHP 8, исправил поиск в мобильном меню, добавил новые переводы на русский, а также внес патч, исправляющий совместимость с плагином Code Syntax Block.

Поддержка Assimp

Библиотека Assimp — относительно тяжеловесное, но самое функциональное решение для загрузки 3D-моделей. У меня наконец-то дошли руки добавить поддержку Assimp 5+ в Dagon как расширение dagon:assimp, благодаря чему можно будет использовать в движке модели форматов FBX, Collada, 3DS и многих других. Пока загружаются только меши, но в планах добавить поддержку материалов, узлов и анимации.

Перед загрузкой модели можно задать кастомные флаги постпроцессора:

AssimpAsset aModel;

override void beforeLoad()
{
    aModel = this.addAssimpAsset("assets/cacodemon.fbx");
    aModel.loaderOption =
        aiPostProcessSteps.Triangulate | aiPostProcessSteps.FlipUVs;
}

После этого можно использовать меши из массива aModel.meshes для рендеринга:

auto e = addEntity();
e.drawable = aModel.meshes[0];

Новый сайт Dagon

Серьезно обновил https://gecko0307.github.io/dagon — реализован новый движок на основе рендеринга Markdown-документов, сайт переверстан в более современном дизайне, добавлены уроки, появилась отдельная страница со списком фич и скриншотами. Напомню, справка по API теперь живет на том же сайте: https://gecko0307.github.io/dagon/doc/dagon.html.

Написал новые уроки:

Также обновил урок Tutorial 11. Exporting Assets from Blender — там теперь подробности по экспорту из Blender и загрузке в Dagon моделей glTF.

Dagon 0.23.0

Обновил Dagon. Релиз не содержит нововведений и, в основном, оптимизирующий: теперь объекты Shader хранят прямые ссылки на параметры и обращаются по ним вместо строковых имен, когда записывают данные в методе bind. Индексы субрутин GLSL также извлекаются один раз при инициализации шейдера. Эта оптимизация дала весьма ощутимый прирост производительности на стороне CPU.

Удалено расширение dagon:stbi, так как стандартный загрузчик текстур Dagon полностью его заменяет. bindbc-sdl обновлен до версии 1.5.2.

К движку наконец-то появилась онлайн-документация — она доступна здесь. Документация генерируется из исходников. Полностью задокументированы пакеты dagon.core, dagon.graphics, dagon.resource, dagon.game и dagon.ui.

Лаунчер для Electronvolt

Майские праздники не прошли даром: на пару дней я основательно засел за Python и написал собственный лаунчер. Зачем? Он нужен для того, чтобы не привязывать игру к конкретной игровой платформе. В моем случае, я использую GameJolt, но такой подход работает для любого сервиса. Лаунчер служит посредником между игрой и API площадки. Все, что нужно делать игре — это сообщать лаунчеру о событиях наподобие «игрок получил такую-то ачивку». Лаунчер, в свою очередь, передает эти данные игровому сервису. Если надо переехать на другой сервис, достаточно просто обновить лаунчер, а игру патчить не придется — это удобно и экономит массу времени.

(далее…)

Dagon 0.22.0

Вышла новая версия движка! Как уже было упомянуто, добавил поддержку скелетной анимации glTF — спасибо denizzzka за реализацию модуля dagon.resource.gltf.animation с необходимыми классами GLTFAnimationSampler, GLTFAnimationChannel и GLTFAnimation. Скелетная анимация постепенно будет интегрирована в ядро движка: уже добавил абстрактный класс Pose для хранения матриц костей и необходимую функциональность для их передачи в вершинные шейдеры во всех стандартных пайплайнах Dagon. Вычисление этих матриц зависит от конкретного анимационного воркфлоу, для glTF предусмотрены GLTFPose и GLTFBlendedPose.

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

В движок добавлен встроенный логгер — dagon.core.logger, который позволяет делать записи функциями logInfo, logDebug, logError и logFatalError. Лог по умолчанию выводится в консоль, можно также включить вывод в файл. Функции логирования работают аналогично writeln — то есть, поддерживают вариативные параметры любых типов. Можно отключить записи ниже минимального уровня, изменив глобальную переменную logLevel (по умолчанию — LogLevel.All).

Улучшения в dagon.core.event: горячее подключение игрового контроллера и поддержка вибрации (EventManager.gameControllerRumble).

Добавлен новый примитив — конус (ShapeCone).