Должен ли я когда-либо использовать `vec3` внутри объекта унифицированного буфера или буферного хранилища?
vec3
тип очень хороший тип. Это занимает всего 3 поплавка, и у меня есть данные, которые требуют только 3 поплавка. И я хочу использовать один в структуре в UBO и / или SSBO:
layout(std140) uniform UBO
{
vec4 data1;
vec3 data2;
float data3;
};
layout(std430) buffer SSBO
{
vec4 data1;
vec3 data2;
float data3;
};
Затем в моем коде на C или C++ я могу сделать это для создания соответствующих структур данных:
struct UBO
{
vector4 data1;
vector3 data2;
float data3;
};
struct SSBO
{
vector4 data1;
vector3 data2;
float data3;
};
Это хорошая идея?
1 ответ
НЕТ! Никогда не делай этого!
Объявляя UBO /SSBO, делайте вид, что все 3-элементные векторные и матричные типы не существуют. Представьте, что единственными типами являются скаляры, 2- и 4-элементные векторы (и матрицы). Если вы сделаете это, вы спасете себя от горя.
Если вы хотите получить эффект vec3 + float, то вам следует упаковать его вручную:
layout(std140) uniform UBO
{
vec4 data1;
vec4 data2and3;
};
Да, вам придется использовать data2and3.w
чтобы получить другое значение. Смирись с этим.
Если вы хотите массивы vec3
s, а затем сделать их массивами vec4
s. То же самое касается матриц, которые используют 3-элементные векторы. Просто исключите всю концепцию трехэлементных векторов из ваших SSBO /UBO; Вы будете намного лучше в долгосрочной перспективе.
Есть две причины, почему вы должны избегать vec3
:
Это не будет делать то, что делает C/C++
Если вы используете std140
макет, тогда вы, вероятно, захотите определить структуры данных в C или C++, которые соответствуют определению в GLSL. Это позволяет легко смешивать и сочетать между ними. А также std140
макет позволяет по крайней мере сделать это в большинстве случаев. Но его правила размещения не соответствуют обычным правилам размещения для компиляторов 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.
Теперь вы можете попытаться исправить это с помощью C++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-байтовой границе, vec3
с последующим float
займет 16 байтов.
Но правила выравнивания C++ не допускают такого. Если тип выровнен по границе X-байтов, то при использовании этого типа будет использоваться несколько X-байтов.
Так что соответствие std140
макет требует, чтобы вы выбрали тип в зависимости от того, где именно он используется. Если это сопровождается float
, вы должны использовать vec3a
; если за ним следует какой-то тип, который выровнен более чем на 4 байта, вы должны использовать vec3a_16
,
Или вы можете просто не использовать vec3
s в ваших шейдерах и избегайте всей этой дополнительной сложности.
Обратите внимание, что alignas(8)
-основан vec2
не будет этой проблемы. C/C++ также не будет структурировать и использовать массивы, используя правильный спецификатор выравнивания (хотя у массивов меньших типов есть свои проблемы). Эта проблема возникает только при использовании голой vec3
,
Поддержка реализации нечеткая
Даже если вы все делаете правильно, известно, что реализации неправильно vec3
Странные правила компоновки. Некоторые реализации эффективно навязывают правила выравнивания C++ GLSL. Так что если вы используете vec3
, он обрабатывает это так, как C++ будет обрабатывать 16-байтовый выровненный тип. На этих реализациях vec3
с последующим float
будет работать как vec4
с последующим float
,
Да, это вина разработчиков. Но так как вы не можете исправить реализацию, вы должны обойти это. И самый разумный способ сделать это - просто избежать vec3
в целом.
Обратите внимание, что для Vulkan компилятор GLSL в SDK делает это правильно, поэтому вам не нужно об этом беспокоиться.