dlib 1.0

Спустя 10 лет разработки моя библиотека общего назначения для геймдева наконец-то стабилизировалась — итог этой работы отражает первый мажорный релиз проекта, dlib 1.0.0.

Из наиболее важных нововведений могу отметить ускорение загрузки изображений (от 2 до 10 раз в зависимости от формата и веса файла) — оптимизация заключается в том, чтобы декодировать из буфера в памяти, а не напрямую из файлового потока. Добавлена валидация при создании POSIX-потоков, улучшен модуль dlib.math.interpolation.hermite — добавлена функция вычисления производной для сплайна Эрмита. Исправлено несколько важных багов в математическом и геометрическом пакетах.

В связи с этим знаковым релизом, а также тем, что в этом году dlib как публичному OpenSource-проекту исполняется ровно 10 лет, хочу немного рассказать об истории этой разработки.

dlib фактически начался как порт разнородных исходников с C++ на D. В 2010-11 годах, когда я познакомился с D, у меня был свой небольшой игровой движок на C++ (Phantom3D), а также библиотека общего назначения Sparx, и я решил портировать их на D2 — язык в те годы как раз стабилизировался. Начал, естественно, с векторной алгебры — так появился dlib.math, старейший и наиболее законченный из пакетов dlib. Сам 3D-движок, конечно, претерпел немало трансформаций и, в итоге, от старого кода почти ничего не осталось — мой следующий движок DGL был написан почти с нуля, хотя и начинался как полный порт Phantom3D и сначала носил то же название.

Sparx я поначалу использовал в D в виде динамически слинкованной библиотеки, через Derelict. Но это было не очень удобно, поэтому следующим этапом стало переписывание Sparx на D, в результате чего родился dlib.image — Sparx использовался в основном как загрузчик ресурсов (изображений PNG, JPEG, JPEG2000, TGA, BMP, DDS, моделей OBJ и MD5). Сейчас, правда, старого кода из Sparx в библиотеке очень мало — но, например, отдельные участки кода dlib.image, некоторые математические и геометрические функции напрямую унаследованы оттуда.

Сама идея создать такую библиотеку появилась в результате осмысления чужих открытых проектов, в особенности Давида Анри (чудо, что его сайт до сих пор существует!). Изучая чей-нибудь код, я часто замечал, что многие функции и классы можно сделать обобщенными и независимыми, что позволяет переиспользовать их в самых разных проектах — наверное, около половины кода любой игры можно вынести в библиотеку общего назначения. Это, в первую очередь, код, который не зависит от графического API и логики движка — линейная алгебра, вычислительная геометрия, работа с файловой системой, многопоточностью, взаимодействие с ОС, сетью и т.д. Главный принцип — скрыть платформозависимый код под абстрактным интерфейсом, так, чтобы приложению не приходилось взаимодействовать с операционной системой напрямую. Это делает код приложения на удивление простым.

Но вернемся к проектам Давида Анри. Меня очень впечатлили его загрузчики PNG, TGA и BMP — они очень сильно повлияли на мои собственные реализации декодеров этих форматов. Декодер JPEG — отдельная история, я написал его полностью самостоятельно, не заглядывая в готовые реализации — только читая спеки и техническую литературу (фактически, из заимствований в нем только ДКТ-преобразователь — хардкорная fixed-point математика, написанная темными магами). Модули для работы с векторами и матрицами — тоже отчасти влияние Анри, а именно его библиотеки Mathlib. Мои реализации, конечно, за 10 лет стали намного лучше — в dlib все алгебраические объекты обобщенные, одно и то же описание используется для векторов и квадратных матриц всех стандартных размерностей (2, 3 и 4). Выше 4 я поддерживать не стал, так как в компьютерной графике они практически не используются. Также у меня есть оптимизированные функции инвертирования и декомпозиции матриц, свизлинг векторов, огромное множество функций-фабрик и различных операторов — практически все, что может понадобиться для любых вычислений, связанных с трехмерными моделями.

Точная дата юбилея dlib — 28 сентября, так как именно в этот день я создал репозиторий на Google Code (я тогда еще использовал SVN, а не Git). Самый интересный этап в жизни проекта начался в 2013, когда разработка была перенесена на GitHub. Появился модуль dlib.core, к проекту примкнули новые разработчики: Martin Сejp провел огромную работу по реализации потоков ввода-вывода и абстрактной файловой системы (dlib.core.stream, dlib.filesystem), Eugene Wissner написал сетевой пакет (dlib.network) и аллокаторы памяти (dlib.memory), Nick Papanastasiou написал модуль комбинаторики, Вадим Лопатин (к слову, автор знаменитой читалки Cool Reader) помог улучшить декодер PNG, Роман Чистоходов и Андрей Пенечко внесли множество исправляющих патчей. Отдельное спасибо Роману за улучшенную поддержку BMP и TGA, а также ценные советы.

В 2015 году я начал рефакторинг, связанный с поддержкой ручного управления памятью. Эта грандиозная работа была полностью завершена в 2019 году. Динамическая память — это отдельная большая история, далеко выходящая за рамки D, я мог бы написать целую книгу на эту тему. Если теория вычислений разработана математиками и инженерами очень глубоко и досконально, то «теории памяти», в общем-то, до сих пор нет. Все, что мы имеем научного в этой сфере — это теория типов и ее высшее достижение, система типов Хиндли-Милнера. К сожалению, нет какого-то общего, формального подхода к управлению динамической памятью — сколько языков, столько и практик. Полагаю, что необходимо выделять парадигмы памяти, подобно тому, как существуют парадигмы программирования в целом.

В dlib я реализовал парадигму единственного владельца (single ownership), которая очень хорошо ложится на базовое ООП. Каждый объект может «владеть» другими объектами. У любого объекта бывает только один владелец, либо нет владельца вовсе (у корневых объектов). Когда удаляется владелец, удаляются и все объекты, которыми он владеет. Такой подход позволяет полностью избавиться от сборщика мусора и сделать высвобождение памяти детерминированным. Объектами в этом контексте может быть все, что угодно — парадигма не зависит от логики приложения — но, естественно, речь идет о данных, которые создаются один раз и надолго (в предельном случае — о неизменяемых наборах данных, которые создаются при старте приложения и удаляются при завершении работы). Эта модель не очень хорошо работает с алгоритмами, где нужно создавать и уничтожать объекты многократно, в цикле — но на то есть аллокация в стеке. Именно подход к работе с динамической памятью я считаю одним из главных достижений dlib — эту фичу, я думаю, можно и нужно нести в мир, который, кажется, сдался без боя сборщикам мусора.

Впрочем, рефакторинг, связанный со сменой парадигмы памяти, не привел к тому, что dlib стала @nogc-библиотекой (то есть, формально, по контракту гарантирующей отсутствие вызовов сборщика). Этой целью пришлось пожертвовать для сохранения обратной совместимости API. Но у меня есть план вернуться к этой проблеме в следующей версии, dlib 2.0 — расскажу о своих идеях в одном из будущих постов.

В 2016 году появился пакет dlib.audio, который может служить бэкендом для звуковых движков, плееров, DAW и т.д. Пока реализованы только базовые функции, но пакет будет развиваться. Также появился пакет dlib.network, который содержит независимую от Phobos поддержку сокетов и функции, связанные с вебом. В 2021 году работа над основной функциональностью библиотеки была завершена.

dlib — это, наверное, самый грандиозный мой проект за всю жизнь. Начавшись как маленький набор модулей «на все случаи жизни» для экспериментов с OpenGL, библиотека превратилась в один из самых популярных пакетов в реестре Dub. Сейчас количество загрузок dlib составляет 800-1000 ежемесячно. На основе dlib пишутся игровые движки, инструменты анализа изображений, GUI-тулкиты, даже синтезаторы и библиотеки генетических алгоритмов. О такой широкой области применения я, честно говоря, изначально даже не думал! К сожалению, D все еще остается довольно нишевым языком, и востребованность библиотек на нем ограничена востребованностью самого языка — но я счастлив уже тем, что сделал для сообщества что-то полезное. Накопление алгоритмов, достижений математической и инженерной мысли в одном месте, в удобочитаемом виде — я считаю, дело нужное. И, в конечном счете, это был интересный путь.

Как ускорить загрузку изображений

Совет пользователям dlib. Не декодируйте изображение напрямую из файлового потока, это слишком медленно. Вместо этого рекомендую загрузить файл в память целиком, создать поток массива (ArrayStream) и уже его передавать в функцию-декодер:

InputStream input = openForInput("image.jpg");
ubyte[] data = New!(ubyte[])(input.size);
input.fillArray(data);
ArrayStream arrStrm = New!ArrayStream(data);
SuperImage img = loadJPEG(arrStrm);
Delete(arrStrm);
Delete(data);
input.close();

Для JPEG, например, это дает ускорение в 5-10 раз, в зависимости от размера картинки.

Несколько новостей

  • Готовлю к выпуску dlib 1.0. Бета-релиз, скорее всего, состоится уже в январе — осталось довести покрытие до 50% (поставил такую цель несколько лет назад) и провести аудит некоторых модулей
  • Выложил все свои старые игры на этот сервер, ссылки в разделе Игры обновлены.

Новые статьи на Medium

Написал две новые статьи на английском:

  • dlib: Past, Present and Future — экскурс в историю dlib, о текущем статусе проекта и планах на будущее;
  • GitHub Actions and D — о том, как настроить тестирование на GitHub Actions для проектов на D.

Итоги 2020 года

Близится конец года, и это значит, что наступило время для традиционного подведения итогов по проектам.

  • У меня появился домен для личного бренда https://timurgafarov.ru, и, соответственно, блог переехал на новый адрес: https://gamedev.timurgafarov.ru.
  • Вышел Dagon 0.11.0. Движок был значительно улучшен, переработана структура модулей, практически полностью переписан рендер, исправлено множество проблем и узких мест производительности. Посмотреть движок в действии можно при помощи демки dagon-sandbox, а также на моем YouTube-канале. Также были дополнены уроки и примеры.
  • Вышли dlib 0.18, 0.19 и 0.20. У проекта появилась онлайн-документация, генерируемая из исходников при помощи Dub/ddox. В 2021 году dlib исполняется 10 лет!
  • Я опубликовал две новые статьи по D на Medium: Getting started with D и Const-correctness in D, а также небольшую вводную статью по WebGPU на CGWorld.
  • За этот год мне удалось собрать донатов на сумму $172,10. Огромное спасибо всем, кто перечислил деньги! Часть средств пошла на покупку аппаратного обеспечения — в частности, SSD (3590 руб.), наушников (790 руб.), USB-разветвителя (790 руб.), разветвителя для аудио (45,95 руб.). Также был приобретен графический софт: ArtRage (2423,38 руб.) и Armor Paint (1205,78 руб.). На оплату хостинга, на котором размещен этот блог, ушло 1447 руб. Кроме того, был куплен домен timurgafarov.ru за 199 руб. Итого израсходовано 10491,11 руб.

Ну и, конечно, не могу не назвать самые значимые для меня события в мире CG, СПО и геймдева:

  • Выход Blender 2.90 — очень впечатлила новая опция режима редактирования, позволяющая автоматически смещать UV-координаты синхронно с изменением геометрии. В целом Blender 2.80+ в моем восприятии превратился в полноценно рабочий инструмент, я начал использовать его в коммерческих проектах.
  • Выход бесплатной версии Unigine. Скачал, заценил — есть множество интересных фич, в частности понравился live reloading моделей и текстур при их обновлении внешними приложениями. Не понравилось, однако, то, что редактор нельзя запустить без входа в аккаунт (возможно, есть какой-то оффлайн-режим — не искал).
  • Открытие исходников NeoAxis. Пока детально не изучал этот движок, но в целом выглядит привлекательно.
  • Форк Dev-C++ от Embarcadero. До сих пор иногда пользуюсь этой IDE, поэтому новость для меня позитивная.

Документация по dlib

Наконец-то у dlib появилась онлайн-документация, генерируемая из исходников при помощи Dub: https://gecko0307.github.io/dlib/docs/dlib.html

Пока, разумеется, подробно задокументированы далеко не все модули, к большинству функций и классов есть только краткое описание — но я надеюсь, то, что есть, уже может кому-то пригодиться. Документирующие pull request’ы приветствуются (dlib использует синтаксис ddoc).

Временная шкала моих проектов

Приводя в порядок архивы и вспоминая прошлое, решил зафиксировать информацию о моих 3D-движках и других OpenSource-разработках в виде интерактивной временной шкалы при помощи time.graphics — возможно, кому-то будет интересно:

Итоги 2019 года

Пролетел еще один год, и это значит, что наступило время для традиционного подведения итогов по проектам.

  • Блог DLangGamedev переехал на новый адрес и движок: https://gamedev.timurgafarov.ru.
  • Был значительно улучшен движок Dagon: добавлена поддержка декалей, трубчатых источников света и «фонариков», кубических карт окружения. Благодаря сторонним разработчикам появилась поддержка рендеринга ландшафтов, в том числе процедурных с использованием шума OpenSimplex, а также интеграция GUI-тулкита Nuklear. Были полностью переписаны рендер и система постобработки в Dagon, упорядочена структура модулей движка, внесено множество оптимизаций производительности, реализован эффект объемного рассеяния света в атмосфере (volumetric light scattering) для направленных источников света. Mateusz Muszyński на основе Dagon и Nuklear написал клон Sokoban с редактором уровней.
  • Я начал работу по интеграции физического движка Newton Dynamics в Dagon в рамках проекта dagon-newton и биндинга bindbc-newton. Newton был выбран как наиболее функциональный физический движок с интерфейсом C.
  • Вышли dlib 0.16.0 и 0.17.0. Библиотека постепенно приближается к релизу версии 1.0.
  • Я написал bindbc-wgpu, биндинг к графической библиотеке wgpu.

Ну и, конечно, не могу не назвать самые значимые для меня события в мире CG, СПО и геймдева:

  • Появление WebGPU, нового веб-стандарта для высокопроизводительной графики.
  • Выход Blender 2.80 c новым вьюпорт-движком Eevee.
  • Открытие кода Mimalloc, быстрого и компактного аллокатора памяти от Microsoft.

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. Заранее благодарен!

Итоги 2018 года

Год подошел концу, а это значит, что наступило время для традиционного подведения итогов.

  • 2018-й ознаменовался множеством значительных улучшений в движке Dagon. Самые важные из них включают переход на OpenGL 4.0, HDR, отложенный рендеринг, новую систему шейдеров, поддержку карт окружения, улучшенные частицы, новые эффекты пост-обработки (SSAO, HDR glow, motion blur, хроматическая аберрация, улучшенный color grading), экспорт сцен из Blender. Также была улучшена поддержка джойстиков и реализована поддержка рулей.
  • Я написал цикл уроков по базовым возможностям Dagon и открыл англоязычный блог на Medium.
  • Вышло три релиза коллекции библиотек dlib — 0.13.0, 0.14.0, 0.15.0.
  • Я запустил краудфандинг своих проектов на Patreon и начал продавать 3D-модели на CGTrader.
  • На сайте LightHouse Software вышли две мои статьи по D — «Преобразование карт окружения при помощи dlib», «Музыкальный D: синтезатор в 100 строк».

Ну и, конечно, не могу не назвать самые значимые для меня события в мире CG, СПО и геймдева:

  • Прекращение поддержки OpenGL на macOS.
  • Покупка GitHub корпорацией Microsoft.
  • Открытие исходников PhysX под свободной лицензией.
  • Появление поддержки WebAssembly в компиляторе LDC, включение фронтенда D в GCC, портирование бэкенда DMD на D, отвязывание всех компиляторов D от линкера MSVS при сборке 64-битных приложений. Инструменты развиваются, язык не стоит на месте, и все это, конечно, не может не радовать. 
  • Выход BindBC, нового биндинг-фреймворка от автора Derelict (я стал одним из первых его пользователей).
  • Выход бета-версии Blender 2.80. Очень впечатлили возможности Eevee, но слегка разочаровал отказ от BGE — я-то надеялся, что игровой движок, напротив, будет улучшен и интегрирован с Eevee.
  • Бесплатный релиз движка Armory. Давно и с большим интересом слежу за этим проектом, хотя и не собираюсь отказываться от Dagon в пользу чего-то стороннего.
  • Krita 4.0 и GIMP 2.10.
  • RTX от NVIDIA — трассировка лучей на GPU с игровой производительностью — и, соответственно, выход первых потребительских видеокарт с полной поддержкой этой технологии (линейка GeForce RTX 2080 на базе архитектуры Turing)
  • C&D-письмо автору Spyro: Myths Awaken. Нападки корпораций на фанатское творчество не прекращаются, и это печально.