Вычислительные шейдеры в Dagon

С переходом на OpenGL 4.3 в движке появляется возможность использовать вычислительные шейдеры. Они довольно удобно легли поверх уже существующией шейдерной системы — добавились всего два новых класса, ComputeProgram (аналог ShaderProgram) и ComputeShader, наследующий от Shader. Механизм привязки параметров целиком остается тот же самый, никаких изменений, за исключением способа привязки текстур — для этого есть метод ComputeShader.bindImageTexture. Привязываются обычные текстуры Dagon, как-то специально их готовить не нужно. Чтобы создать свой шейдер, нужно наследовать от ComputeShader, создать программу и параметры, а в bindParameters, как обычно, задать им значения:

class TestComputeShader: ComputeShader
{
   protected:
    String cs;
    ShaderParameter!Color4f _fillColor;

   public:
    Texture outputTexture;
    Color4f fillColor = Color4f(1.0f, 1.0f, 1.0f, 1.0f);

    this(Owner owner)
    {
        cs = Shader.load("data/test.comp.glsl");
        ComputeProgram p = New!ComputeProgram(cs, this);
        super(p, owner);

        _fillColor = createParameter!Color4f("fillColor");
    }

    ~this()
    {
        cs.free();
    }

    override void bindParameters(GraphicsState* state)
    {
        _fillColor = fillColor;

        if (outputTexture)
            bindImageTexture(0, outputTexture, TextureAccessMode.Write);

        super.bindParameters(state);
    }

    void run()
    {
        if (outputTexture)
            super.run(outputTexture.width, outputTexture.height);
    }
}

TestComputeShader cs = New!TestComputeShader(assetManager);
cs.outputTexture = myTexture;
cs.fillColor = Color4f(0.0f, 0.5f, 1.0f, 1.0f);
cs.run();

Пример шейдера, который заполняет текстуру «синусной плазмой» заданного цвета:

#version 430
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;

layout(rgba8, binding = 0) writeonly uniform image2D outputTexture;

uniform vec4 fillColor;

void main()
{
    ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
    const float scale = 0.25;
    float value = 0.5f + 
        0.25f * cos(coord.x * scale) + 
        0.25f * cos(coord.y * scale);
    vec3 color = fillColor.rgb * value;
    imageStore(outputTexture, coord, vec4(color, 1.0));
}

ComputeShader.run уже ставит барьер памяти, в приложении это делать не нужно. Есть методы dispatch и barrier для запуска вручную.

GScript3

В D все очень плохо со встраиваемыми скриптовыми языками. Перепробовал много вариантов — Lua, Python, AngelScript — везде боль, все делается через неимоверно сложные API, где элементарно привязать к скрипту свою функцию — это целый квест. Чтобы, например, использовать биндинг dangel, нужно патчить рантайм языка для поддержки соглашения вызовов функций D, без этого там ничего не будет работать. Под Lua-биндинги очень сложно найти нужную версию библиотеки, чтобы приложение не крашилось. Pyd, привязка Python — вообще какой-то фантастически запутанный фреймворк из compile-time костылей. Нативные языки, написанные на D, по большей части устаревшие, неподдерживаемые и тупо не компилируются.

Поэтому я уже много лет назад начал пилить свой язык — GScript. Изначально это был больше учебный проект, у меня все не хватало времени довести до ума виртуальную машину — первые два варианта GScript были просто прототипами. Третья итерация, кажется, приобретает уже законченный вид.

(далее…)

Не используйте std.variant!

Собственно сабж. Оказывается, Variant, стандартная реализация tagged union в Phobos, плоховато подходит для вычислений в реальном времени. Не знаю, что там наворотили, но бенчмарки, которые я сделал при разработке GScript3, показали ускорение в 900%, когда я заменил Variant на кастомный динамический тип. Я замерял выполнение скриптового счетчика от 0 до 100000000, и версия на Variant завершилась за 45 секунд, версия на моем GsDynamic — всего за 5!

О самом языке GScript3 расскажу в ближайшее время — я решил актуализировать этот старый проект и уже сделал много интересного.

Dagon 0.30.0 и 0.31.0

Выпустил подряд две версии движка. В ядро Dagon внесен фреймворк многопоточности и обмена сообщениями, о котором я подробно писал в предыдущем посте. EventManager.userEventQueue переименовано в EventManager.outboxEventQueue, EventManager.numUserEvents — в EventManager.numOutboxEvents. Также теперь рекомендуется использовать EventManager.queueEvent вместо EventManager.addUserEvent, EventManager.queueFileChangeEvent вместо EventManager.generateFileChangeEvent, EventManager.queueLogEvent вместо EventManager.asyncLog.

Заметно улучшен пакет dagon.collision, хотя он пока и далек от продакшн-уровня. Исправлены баги в модуле BVH, добавлена реализация GeomTriangle.boundingBox, а также экспериментальный алгоритм проверки столкновений GJK (dagon.collision.gjk). EPA пока не поддерживается, так что функция gjkTest не возвращает информацию о контакте — основным алгоритмом проверки столкновений остается MPR. Метод CollisionShape.supportPointGlobal теперь просто CollisionShape.supportPoint.

В Dagon 0.31.0 я продолжил улучшение пакета core. Добавлены свойства Application.path и Application.directory — соответственно, полный путь к исполняемому файлу и папка, в которой он лежит. Под Windows доступно свойство Application.hwnd для получения дескриптора окна игры. VFS теперь монтирует в качестве последнего источника данных папку, где хранится приложение, а не рабочую папку. Благодаря этому можно в командной строке запускать приложение не из текущей папки.

Экспериментальная фича: поддержка ввода с графических планшетов (пока только под Windows через Wintab). Абстрактный интерфейс InputDevice для добавления в EventManager кастомных устройств ввода. Новые типы событий EventType.PenMotion, EventType.JoystickAxisMotion, EventType.LocaleChange.

В deferred-рендер добавлена поддержка перспективных теневых карт (PSM) для конусных источников света.

Dagon 0.31.0 является последней версией, использующей OpenGL 4.0 — со следующей движок переходит на 4.3, что позволит добавить поддержку вычислительных шейдеров.

Конкурентное программирование в Dagon

Идея добавить в EventManager поддержку конкурентности/асинхронности не давала мне покоя еще со времен DGL, и вот, наконец, мне это удалось. Точнее, асинхронный слой удобно лег поверх стандартной событийной шины, и для этого не пришлось менять в ней практически ничего.

(далее…)