НЕТ! Никогда этого не делайте!
При объявлении UBO / SSBO сделайте вид, что все трехэлементные векторные типы не существуют. Сюда входят матрицы главных столбцов с 3 строками или матрицы главных строк с 3 столбцами. Представьте, что единственными типами являются скаляры, двух- и четырехэлементные векторы (и матрицы). Если вы это сделаете, вы избавите себя от очень большого количества горя.
Если вам нужен эффект vec3 + a float, вам следует упаковать его вручную:
layout(std140) uniform UBO
{
vec4 data1;
vec4 data2and3;
};
Да, вам нужно будет использовать data2and3.w
, чтобы получить другое значение. Смирись с этим.
Если вам нужны массивы из vec3
s, сделайте их массивами из vec4
s. То же самое и с матрицами, в которых используются трехэлементные векторы. Просто прогоните всю концепцию трехэлементных векторов из ваших SSBO / UBO; вам будет намного лучше в долгосрочной перспективе.
Есть две причины, по которым вам следует избегать vec3
:
Он не будет делать то, что делает C / C ++
Если вы используете макет std140
, тогда вы, вероятно, захотите определить структуры данных на C или C ++, которые соответствуют определению в GLSL. Это позволяет легко смешивать и сочетать эти два понятия. И std140
layout делает это по крайней мере возможным в большинстве случаев. Но его правила компоновки не соответствуют обычным правилам компоновки для компиляторов C и C ++, когда дело касается vec3
s.
Рассмотрим следующие определения C ++ для типа 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;
};
В большинстве компиляторов C ++ sizeof
для Block_a
и Block_f
будет 24. Это означает, что offsetof
b
будет 12.
Однако в макете std140 vec3
всегда выравнивается по 4 словам. Следовательно, Block.b
будет иметь смещение 16.
Теперь вы можете попытаться исправить это, используя alignas
функцию C ++ 11 (или аналогичную _Alignas
функцию C11):
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-байтовой границе, vec3
, за которым следует float
, займет 16 байтов.
Но правила согласования C ++ этого не допускают. Если тип выровнен по границе X байтов, то использование этого типа потребляет несколько X байтов.
Таким образом, соответствие макета std140
требует, чтобы вы выбрали тип в зависимости от того, где именно он используется. Если за ним следует float
, вы должны использовать vec3a
; если за ним следует какой-то тип, выровненный более чем на 4 байта, вы должны использовать vec3a_16
.
Или вы можете просто не использовать vec3
s в своих шейдерах и избежать всей этой дополнительной сложности.
Обратите внимание, что у vec2
на основе alignas(8)
не будет этой проблемы. Структуры и массивы C / C ++ также не будут использовать правильный спецификатор выравнивания (хотя у массивов меньших типов есть свои проблемы). Эта проблема только возникает при использовании "голого" vec3
.
Поддержка реализации нечеткая
Даже если вы все делаете правильно, известно, что реализации некорректно реализуют правила разметки vec3
необычного макета. Некоторые реализации эффективно накладывают правила выравнивания C ++ на GLSL. Поэтому, если вы используете vec3
, он обрабатывает его так, как C ++ обрабатывает 16-байтовый выровненный тип. В этих реализациях vec3
, за которым следует float
, будет работать как vec4
, за которым следует float
.
Да, виноваты исполнители. Но поскольку вы не можете исправить реализацию, вам нужно обойти это. И самый разумный способ сделать это - вообще избегать vec3
.
Обратите внимание, что для Vulkan (и OpenGL, использующего SPIR-V) компилятор GLSL SDK делает это правильно, поэтому вам не нужно беспокоиться об этом для этого.
person
Nicol Bolas
schedule
03.07.2016