std140 — самый платформонезависимый лейаут uniform-блоков. Если вы передаете параметры в шейдер структурами, а не по одному, то лучше использовать именно его. Конечно, у переносимости есть своя цена: стандарт предусматривает несколько ограничений, самое известное из которых — выравнивание по 16 байтам. То есть, все поля структуры должны быть по смещению кратны 16 байтам. Использование невыровненных данных приводит к тому, что в шейдере считываются неправильные значения, и это вызывает недоумение у начинающих.

Благодаря CTFE и статической интроспекции в D можно проверять структуры на соответствие этому правилу на этапе компиляции — все необходимое есть в std.traits:

bool isFieldsOffsetAligned(T, alias numBytes)()
{
    static if (is(T == struct))
    {
        static foreach(f; T.tupleof)
        {
            static if (f.offsetof % numBytes != 0)
                return false;
        }
        
        return true;
    }
    else return false;
}

alias isStd140Compliant(T) = isFieldsOffsetAligned!(T, 16);

Теперь можно делать так:

import dlib.math.vector;
import dlib.math.matrix;

struct Uniforms
{
    float someParameter;
    Matrix4x4f modelViewMatrix;
    Matrix4x4f normalMatrix;
    Vector4f worldPosition;
}

static assert(isStd140Compliant!Uniforms,
    Uniforms.stringof ~ " does not conform to std140 layout");

Возникает вопрос, почему бы просто не делать align(16)? Можно!

struct AlignedUniforms
{
  align(16):
    float a;
    Vector3f b;
    Matrix4x4f c;
}

Преимуществом такого подхода является право использовать любые типы для полей, но это неэффктивно: например, вместо того, чтобы впустую расходовать три байта после a, можно было бы упаковать a вместе с b в один 16-байтный вектор. Поэтому я лично не фанат автоматического компиляторного выравнивания — лучше это делать вручную, защитившись от ошибок необходимыми статическими проверками.

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *