Должен ли я когда-либо использовать `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 чтобы получить другое значение. Смирись с этим.

Если вы хотите массивы vec3s, а затем сделать их массивами vec4s. То же самое касается матриц, которые используют 3-элементные векторы. Просто исключите всю концепцию трехэлементных векторов из ваших SSBO /UBO; Вы будете намного лучше в долгосрочной перспективе.

Есть две причины, почему вы должны избегать vec3:

Это не будет делать то, что делает C/C++

Если вы используете std140 макет, тогда вы, вероятно, захотите определить структуры данных в C или C++, которые соответствуют определению в GLSL. Это позволяет легко смешивать и сочетать между ними. А также std140 макет позволяет по крайней мере сделать это в большинстве случаев. Но его правила размещения не соответствуют обычным правилам размещения для компиляторов C и C++, когда дело доходит до vec3s.

Рассмотрим следующие определения 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. Что означает, что offsetofb будет 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,

Или вы можете просто не использовать vec3s в ваших шейдерах и избегайте всей этой дополнительной сложности.

Обратите внимание, что alignas(8)-основан vec2 не будет этой проблемы. C/C++ также не будет структурировать и использовать массивы, используя правильный спецификатор выравнивания (хотя у массивов меньших типов есть свои проблемы). Эта проблема возникает только при использовании голой vec3,

Поддержка реализации нечеткая

Даже если вы все делаете правильно, известно, что реализации неправильно vec3Странные правила компоновки. Некоторые реализации эффективно навязывают правила выравнивания C++ GLSL. Так что если вы используете vec3, он обрабатывает это так, как C++ будет обрабатывать 16-байтовый выровненный тип. На этих реализациях vec3 с последующим float будет работать как vec4 с последующим float,

Да, это вина разработчиков. Но так как вы не можете исправить реализацию, вы должны обойти это. И самый разумный способ сделать это - просто избежать vec3 в целом.

Обратите внимание, что для Vulkan компилятор GLSL в SDK делает это правильно, поэтому вам не нужно об этом беспокоиться.

Другие вопросы по тегам