Тригонометрия в бенчмарках

«Неважно, что ты любишь больше:
косинус ли, синус ли…»

Тригонометрия — основа многих приложений, от компьютерной графики до научных симуляций. Все мы привыкли вызывать sin и cos, не задумываясь, как они реализованы. А реализации могут быть разные! Работая над математической библиотекой для dlib2, я провел интересное исследование — какая тригонометрия лучше? Конечно, есть функции из std.math, и в большинстве случаев подойдут именно они. Но не все так просто — все зависит от того, что именно вы разрабатываете.

Если вы собираете обычное приложение, то кажется, что беспокоиться не о чем. Но если вам, по тем или иным причинам, нельзя обращаться к Phobos? Тогда есть два основных пути — sin и cos из стандартной библиотеки C, либо кастомная реализация, если код собирается под голое железо (например, при создании ядра ОС или программировании встраиваемой электроники). Но если вы используете LDC, то ничто не мешает использовать интринсики LLVM — они, оказывается, работают быстрее, чем std.math!

Я провел ряд тестов для всех вариантов тригонометрии:

  • Тест на точность — вычисление синуса и косинуса для 200 аргументов от -π до +π. Замерялась максимальная погрешность — расхождение результата с std.math.sin и std.math.cos;
  • Тест на производительность — время вычисления синуса и косинуса 1000000 раз.

Во всех кейсах я использовал LDC 1.39.0 под Windows 10. Получилось следующее:

  • std.math.sin, std.math.cos:
    • Время выполнения: 4 мс
  • LLVM интринсики llvm_sin, llvm_cos:
    • Время выполнения: 2 мс
    • Точность: абсолютная (макс. погрешность для sin: 0, для cos: 0)
  • Функции sin, cos из стандартной библиотеки C:
    • Время выполнения: 21 мс
    • Точность: абсолютная (макс. погрешность для sin: 0, для cos: 0)
  • Моя кастомная реализация на таблицах:
    • Время выполнения: 33 мс
    • Точность: порядка 10-7 (макс. погрешность для sin: 2.97038e-07, для cos: 1.78188e-07)

Также я пробовал версию с ассемблерными вставками, но она получилась почему-то медленнее кастомной — видимо, при использовании инлайнового ассемблера компилятор не задействует какие-то оптимизации (а еще есть мнение, что x87 fsin, fcos на современных процессорах медленные сами по себе). Смысла в таком варианте реализации особо нет, так что я его не стал рассматривать для включения в библиотеку.

В итоге в dlib2 войдут четыре реализации с таким приоритетом:

  • Если используется LDC, то синус и косинус — это интринсики (то есть, кодогенератор сам выбирает оптимальную реализацию под нужную архитектуру);
  • Если используются другие компиляторы (DMD, GDC):
    • Если код компилируется с поддержкой Phobos, то используются функции из std.math;
    • Если код собирается в режиме version(NoPhobos), но не version(FreeStanding) (то есть, под Windows или Unix-подобную ОС), то используются функции рантайма C;
    • Если же идет компиляция в bare metal, то используется кастомная реализация на таблицах.

Итоги 2024 года

Декабрь — время традиционного подведения итогов за прошедший год:

  • Выпустил три версии Dagon — 0.16, 0.17 и 0.18. В движке появились подповерхностное рассеивание, зонды локального освещения среды, тональная компрессия AgX, улучшен встроенный контроллер персонажа. Добавлена поддержка Wayland под Linux и поддержка экранов с высокой частотой обновления. Наконец-то появились меш-группы в загрузчике OBJ, что позволяет рендерить загруженную модель по частям с различными материалами и настройками. Появился новый встроенный шейдер звездного неба для ночных сцен и игр с космической тематикой. Доступны новые примеры: веревка на основе интегрирования Верле и механика игры от третьего лица.
  • Dagon, в числе других примечательных проектов на языке D, был упомянут на конференции FOSDEM 2024 в Брюсселе, а позже на эту тему вышло интервью со мной в онлайн-издании «Вечерняя Казань».
  • Выпустил dlib 1.3.0, где был проделан ряд улучшений в математическом пакете и добавлен новый модуль dlib.math.random. Спасибо Александру Перфильеву aka aperfilev за багфиксы и поддержку GNU D Compiler в dlib.math.sse. В марте этого года библиотека побила все рекорды по скачиваниям за все время существования проекта — 14000 в месяц!

Ну и, конечно, небольшой список интересных событий в мире D и графического СПО:

  • Функция тональной компрессии AgX стала новым де-факто стандартом индустрии;
  • В языке Slang добавлена поддержка WGSL, что позволяет с большим удобством писать шейдеры для WebGPU-приложений;
  • На FOSDEM ’24 был представлен ознакомительный доклад о языке D и его преимуществах для разработки приложений компьютерной графики.
  • Upd: у GitHub Copilot появился бесплатный ограниченный доступ!

Dagon 0.18

Выпустил новую версию Dagon. В загрузчике OBJ наконец-то появилась поддержка групп — если в файле присутствуют группы, они будут доступны через свойство OBJAsset.groupMesh, так что вы можете использовать их в качестве отдельных мешей вместо единого OBJAsset.mesh. Также загрузчик теперь не падает на моделях с N-гонами, а выводит предупреждение, что они не поддерживаются.

Добавлен новый экстра-шейдер dagon.extra.starfieldsky — ночное небо со звездами. Он представляет собой улучшенную версию шейдера звездного неба из демки с планетой. Основное нововведение — поддержка мерцания звезд.

Слегка переделан NewtonCharacterComponent: появился новый параметр радиуса. Логика теперь следующая: контроллер персонажа, как и прежде, представлен двумя Newton-сферами, но их радиус теперь задается пользователем, а не вычисляется автоматически. Сферы располагаются сверху и снизу от барицентра на расстояниях, которые вычисляются исходя из радиуса и роста персонажа.

В dagon:iqm появились новые свойства для анимированных моделей: Actor.looping (зацикленность), Actor.state.finished (индикатор завершения текущей анимации). Исправлен баг с фейсгруппами при загрузке некоторых IQM.

Slang — универсальный шейдерный язык

Что выглядит читабельнее? Это:

struct VertexOutput
{
    @builtin(position) position: vec4<f32>,
    @location(0) fragmentPosition: vec4<f32>
};

@vertex
fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput
{
    let x = f32(i32(vertexIndex) - 1);
    let y = f32(i32(vertexIndex & 1u) * 2 - 1);
    var output: VertexOutput;
    output.position = vec4<f32>(x * 0.5, y * 0.5, 0.0, 1.0);
    output.fragmentPosition = 0.5 * (vec4<f32>(x, y, 0.0, 1.0) + 1.0);
    return output;
}

…или это:

struct VertexOutput
{
    float4 position: SV_Position;
    float4 fragmentPosition;
};

[shader("vertex")]
VertexOutput vertexMain(uint vertexIndex: SV_VertexID)
{
    let x = float(int(vertexIndex) - 1);
    let y = float(int(vertexIndex & 1u) * 2 - 1);
    VertexOutput output;
    output.position = float4(x * 0.5, y * 0.5, 0.0, 1.0);
    output.fragmentPosition = 0.5 * (float4(x, y, 0.0, 1.0) + 1.0);
    return output;
}

Второй листинг, на мой взгляд, выигрывает сравнение. Это код на Slang, HLSL-подобном языке, который позиционируется в качестве платформонезависимой основы для написания одних и тех же шейдеров под любые графические API, включая D3D12, Vulkan, Metal, OpenGL, а с недавних пор и WebGPU. Идея, конечно, не новая — если вы писали шейдеры в нулевые, то, наверное, помните Cg от NVIDIA — но с со времен попыток написать универсальный компилятор в низкоуровневые шейдерные языки утекло немало воды и сменилось несколько поколений графического железа. Сейчас, в эпоху Vulkan и SPIR-V, кросс-компиляция шейдеров актуальна как никогда: старичок GLSL уже сдает позиции, для новых стандартов создаются новые языки — индустрия переживает очередной этап фрагментации.

Я уже писал о своих впечатлениях от WGSL, встроенного шейдерного языка WebGPU, и многие его конструкции мне до сих кажутся спорными и неудобными. Особенно бесят типы вида vec4<f32>. Не так давно поддержка WGSL была добавлена в компилятор Slang, в связи с чем я теперь всерьез рассматриваю этот язык как основной для создания графического движка на WebGPU.

Главная киллер-фича Slang — это, конечно, модули. Причем, что интересно, есть и препроцессор а ля C с директивами #include, #ifdef и др. От HLSL и GLSL язык выгодно отличается большим набором фич, присущих современным высокоуровневым языкам: поддержкой вывода типов, пространств имен, функций-членов структур (с неизменяемым по умолчанию неявным this — по-моему, отличная идея!), а также конструкторов, геттеров/сеттеров и даже перегрузки операторов. Есть дженерики, интерфейсы, кортежи. Интересен тип Optional, который дополняет любой другой тип поддержкой значения none — чтобы можно было указать отсутствие какого-либо значения. Для SPIR-V и CUDA в языке есть ограниченная поддержка указателей. Очень полезный инструмент — декоратор ForceInline, который заменяет вызов функции подстановкой ее кода. Наконец, в языке есть автоматическое дифференцирование, которое используется в задачах машинного обучения.

Старую как мир проблему некоммутативности умножения матриц и векторов в Slang решили следующим образом: оператор * всегда означает произведение двух матриц, как принято в математике. Чтобы трансформировать вектор матрицей, нужно вместо m * v писать mul(v, m). Для такого старого ветерана OpenGL, как я, слегка непривычно, но жить можно 🙂

Репозиторий

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

Обновления

Вышел bindbc-wgpu 0.22.0, соответствующий wgpu-native 22.1.0.1. Обновилось много чего по мелочи, из важного: теперь нужно удалять WGPURenderPassEncoder после отрисовки, иначе у вас wgpuQueueSubmit будет падать с ошибкой.

Соответственно, обновил и демонстрационное приложение wgpu-dlang, плюс избавился там от библиотеки STB для загрузки изображений.

Стеганография в dlib

Моя статья 2016 года, изначально написанная для блога LightHouse Software. Приведенный код актуален и сегодня.

Думаю, нет необходимости лишний раз говорить о том, насколько в наше время важна защита личных данных и тайна переписки. Конечно, к нашим услугам имеются криптографические алгоритмы, но одного только шифрования порой бывает мало – иногда нужно не просто передать секретное сообщение, но и скрыть сам факт передачи. И здесь приходят на помощь алгоритмы стеганографии.

(далее…)

Пример игры от третьего лица

Добавил в папку примеров Dagon демонстрацию управления от третьего лица. Персонаж работает на Newton-контроллере, поэтому будет реагировать со всеми телами, которые вы создадите через Newton. Можно вращать камеру вокруг персонажа, двигаться (WASD) и прыгать (пробел). Пример также показывает, как загружать модели формата IQM и воспроизводить анимацию.

Скачать

Upd (15.08.24): улучшил управление и контроллер камеры — теперь все работает как в популярных играх: направление движения задается направлением камеры, которая управляется мышью, но, в то же время, игрок может стрейфиться — персонаж может ходить во все четыре стороны. Также добавил атаку (левая кнопка мыши) и звуки движений, в будущем планирую сделать врагов и простенькую демонстрационную боевую систему.

Интервью в «Вечерней Казани»

После упоминания на брюссельской конференции Dagon привлек внимание казанских журналистов: в «Вечерней Казани» сегодня вышло небольшое интервью, где я вкратце рассказал о проекте и о том, где и как он используется. По интересному совпадению, статью опубликовали в мой день рождения — получился своего рода подарок)

https://www.evening-kazan.ru/obshhestvo/articles/graficheskim-dvizhkom-kazanskogo-hudozhnika-polzuyutsya-geym-razrabotchiki-vsego-mira

Dagon на FOSDEM 2024

Классная новость: на прошедшей в феврале в Брюсселе FOSDEM ’24, европейской конференции по свободному программному обеспечению, американский профессор компьютерных наук Майк Шах (Mike Shah) представил доклад по языку D. В числе прочего, доклад включает информацию по Dagon. Отмечается, что это «существенный проект, который представляет интерес для графических разработчиков». Также упоминаются Dash, Hipreme Engine, BindBC и некоторые коммерческие игры, созданные с использованием D.

Ссылка на презентацию

Доклад представляет собой общее ознакомление с языком, обзор основных его особенностей и преимуществ в плане разработки СПО, а также некоторых библиотек и примечательных проектов, написанных на D.

Майк Шах — ученый в области компьютерной графики и визуализации, Ph.D. компьютерных наук, преподаватель Колледжа компьютерных наук Хури Северо-Восточного университета (Массачусетс).

Векторная графика в dlib

Моя статья 2017 года, изначально написанная для блога LightHouse Software. Приведенный код актуален и сегодня.

Коллекция библиотек dlib предоставляет функции для рисования отрезков и окружностей (см. dlib.image.render.shapes). Однако при построении сложной векторной графики не обойтись без рендеринга более интересных объектов – в этой статье я рассмотрел рисование закрашенных многоугольников и фигур Безье на их основе.

(далее…)