Ответ 1
НЕТ! Никогда не делай этого!
При объявлении UBOs/SSBOs притворитесь, что все трехмерные векторные и матричные типы не существуют. Представьте, что единственными типами являются скаляры, 2 и 4 вектора элементов (и матрицы). Если вы это сделаете, вы сэкономите себя очень много.
Если вам нужен эффект vec3 + float, вы должны его вручную упаковать:
layout(std140) uniform UBO
{
vec4 data1;
vec4 data2and3;
};
Да, вам нужно будет использовать data2and3.w
, чтобы получить другое значение. Поговорите с ним.
Если вы хотите массивы vec3
s, тогда создайте их массивы vec4
s. То же самое касается матриц, которые используют 3-элементные векторы. Просто изгоните всю концепцию 3-элементных векторов от ваших SSBOs/UBOs; вам будет намного лучше в долгосрочной перспективе.
Есть две причины, по которым вам следует избегать vec3
:
Он не будет делать то, что C/С++ делает
Если вы используете макет std140
, то вы, вероятно, захотите определить структуры данных на C или С++, которые соответствуют определению в GLSL. Это упрощает смешивание и совпадение между ними. А макет std140
делает это, по крайней мере, возможным в большинстве случаев. Но его правила компоновки не соответствуют обычным правилам компоновки для компиляторов C и С++, когда речь заходит о vec3
s.
Рассмотрим следующие определения С++ для типа vec3
:
struct vec3a { float a[3]; };
struct vec3f { float x, y, z; };
Оба они являются совершенно законными типами. sizeof
и расположение этих типов будут соответствовать размеру и макету, который требуется std140
. Но это не соответствует поведению выравнивания, которое налагает std140
.
Рассмотрим это:
//GLSL
layout(std140) uniform Block
{
vec3 a;
vec3 b;
} block;
//C++
struct Block_a
{
vec3a a;
vec3a b;
};
struct Block_f
{
vec3f a;
vec3f b;
};
В большинстве компиляторов С++ sizeof
для Block_a
и Block_b
будет 24. Это означает, что offsetof
b
будет 12.
Однако в макете std140 vec3
всегда совпадает с 4 словами. И поэтому Block.b
будет иметь смещение 16.
Теперь вы можете исправить это, используя функциональность С++ 11 alignas
(или C11 аналогичную функцию _Alignas
):
struct alignas(16) vec3a_16 { float a[3]; };
struct alignas(16) vec3f_16 { float x, y, z; };
struct Block_a
{
vec3a_16 a;
vec3a_16 b;
};
struct Block_f
{
vec3f_16 a;
vec3f_16 b;
};
Если компилятор поддерживает выравнивание по 16 байт, это будет работать. Или, по крайней мере, он будет работать в случае Block_a
и Block_f
.
Но в этом случае это не сработает:
//GLSL
layout(std140) Block2
{
vec3 a;
float b;
} block2;
//C++
struct Block2_a
{
vec3a_16 a;
float b;
};
struct Block2_f
{
vec3f_16 a;
float b;
};
По правилам std140
каждый vec3
должен начинаться с 16-байтовой границы. Но vec3
не потребляет 16 байт памяти; он потребляет всего 12. И поскольку float
может начинаться с 4-байтовой границы, a vec3
, за которой следует float
, будет занимать 16 байт.
Но правила выравнивания С++ не допускают такой вещи. Если тип привязан к границе X-байта, то использование этого типа будет потреблять кратное X байтам.
Таким образом, соответствие макета std140
требует, чтобы вы выбирали тип, основанный на том, где именно он используется. Если за ним следует float
, вы должны использовать vec3a
; если за ним следует какой-то тип, который выровнен более чем на 4 байта, вы должны использовать vec3a_16
.
Или вы можете просто не использовать vec3
в ваших шейдерах и избегать всей этой сложной сложности.
Обратите внимание, что на alignas(8)
на основе vec2
не будет этой проблемы. Также не будут построены структуры C/С++ и массивы с использованием соответствующего спецификатора выравнивания (хотя массивы меньших типов имеют свои собственные проблемы). Эта проблема возникает только при использовании голой vec3
.
Поддержка реализации нечеткая
Даже если вы все сделаете правильно, реализации, как известно, неправильно реализуют правила компоновки vec3
oddball. Некоторые реализации эффективно налагают правила выравнивания С++ на GLSL. Поэтому, если вы используете vec3
, он обрабатывает его, так как С++ будет обрабатывать 16-байтовый выровненный тип. В этих реализациях a vec3
, за которым следует float
, будет работать как a vec4
, за которым следует float
.
Да, это ошибка исполнителей. Но так как вы не можете исправить реализацию, вам нужно ее обойти. И самый разумный способ сделать это - просто избегать vec3
вообще.
Обратите внимание, что для Vulkan компилятор SDK GLSL получает это право, поэтому вам не нужно беспокоиться об этом.