std140: статическая валидация структур
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-байтный вектор. Поэтому я лично не фанат автоматического компиляторного выравнивания — лучше это делать вручную, защитившись от ошибок необходимыми статическими проверками.