Должен ли я когда-либо использовать `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;
};

Это хорошая идея?


person Nicol Bolas    schedule 03.07.2016    source источник


Ответы (1)


НЕТ! Никогда этого не делайте!

При объявлении UBO / SSBO сделайте вид, что все трехэлементные векторные типы не существуют. Сюда входят матрицы главных столбцов с 3 строками или матрицы главных строк с 3 столбцами. Представьте, что единственными типами являются скаляры, двух- и четырехэлементные векторы (и матрицы). Если вы это сделаете, вы избавите себя от очень большого количества горя.

Если вам нужен эффект vec3 + a float, вам следует упаковать его вручную:

layout(std140) uniform UBO
{
  vec4 data1;
  vec4 data2and3;
};

Да, вам нужно будет использовать data2and3.w, чтобы получить другое значение. Смирись с этим.

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

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

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

Если вы используете макет std140, тогда вы, вероятно, захотите определить структуры данных на C или C ++, которые соответствуют определению в GLSL. Это позволяет легко смешивать и сочетать эти два понятия. И std140 layout делает это по крайней мере возможным в большинстве случаев. Но его правила компоновки не соответствуют обычным правилам компоновки для компиляторов 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. Это означает, что 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.

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

Обратите внимание, что у 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
comment
Вы можете создать макет вручную в glsl (и он будет явно выложен в spir-V, в который компилируется glsl) - person ratchet freak; 04.07.2016
comment
@ratchetfreak: Да, с offset и align вы можете сделать свой собственный макет. Что вы не можете сделать, так это нарушить основные правила компоновки; спецификация GLSL в этом отношении частично ясна. offset не может поместить переменную в предыдущий член блока. Теперь то, что делает эту полупонятную картину, - это то, что внутри означает. Занимает ли член пространство своего базового выравнивания или занимает только свое собственное пространство? С Vulkan все также неясно, поскольку правила компоновки SPIR-V говорят, что объекты не могут перекрываться (вроде), но опять же ничего не говорится о базовом выравнивании. - person Nicol Bolas; 04.07.2016
comment
Что ж, я недавно перепроектировал его и, по крайней мере, glslangValidator упаковал это плотно в SPIR-V, как ожидалось (смещения элементов 0, 16, 28 соответственно). Если речь идет только о выравнивании, то лучшим примером будет vec3 после скалярного числа с плавающей точкой. - person krOoze; 04.07.2016
comment
@krOoze: хотя бы glslangValidator плотно упаковывает это в SPIR-V, как и ожидалось (смещения элементов 0, 16, 28 соответственно). Что ж, это ошибка. KHR_vulkan_glsl не изменяет правила компоновки std140. Поэтому, если вы явно не объявляете смещения, он должен делать то, что говорит стандарт OpenGL. - person Nicol Bolas; 04.07.2016
comment
@NicolBolas Что ж, здесь есть двусмысленность (я думаю, вы это знаете, поскольку я считаю, что вы начали выпуск GitHub по этому поводу). В спецификации OGL это более четко, чем в VK (поскольку он, по крайней мере, определяет большинство используемых терминов), я бы сказал, что базовая единица машины, потребляемая предыдущим членом, все еще означает 3N B для vec3 (выравнивание не должно менять это - это не то, что слово означает). - person krOoze; 04.07.2016
comment
^ На самом деле здесь нет ошибок, я думаю: github.com/KhronosGroup/glslang/issues/201 - person krOoze; 04.07.2016
comment
@krOoze: Я просмотрел все это и обнаружил, что вы правы. Поэтому я изменил причину, по которой их избегаю. - person Nicol Bolas; 10.07.2016
comment
@NicolBolas, есть ли шанс, что мы сможем получить расширение к этому сообщению, связанное с std430? - person AzP; 01.12.2017
comment
@AzP: единственное изменение std430 правил компоновки - это базовое выравнивание для массивов и структур скаляров и двухэлементных векторов. Это ничего не меняет в vec3s, поскольку их базовое выравнивание всегда совпадает с выравниванием vec4. - person Nicol Bolas; 01.12.2017
comment
Важная вещь, которую я узнал, читая это: спецификация позволяет float сразу следовать за vec3 в памяти, так что они оба занимают в общей сложности 16 байт. Это, по-видимому, главная (если не единственная) отличительная черта vec3 против vec4 с точки зрения выравнивания. Т.е. size! = базовое выравнивание только в случае векторов с тремя компонентами, которые также не являются элементами массива. - person Philip Guin; 04.05.2018
comment
Разве vec3 + float не работает в этих обстоятельствах в примере? Выравнивание может быть 16, но смещение числа с плавающей запятой все равно 12 байтов, верно? - person Krupip; 18.02.2019
comment
@opa: Но для vec3 + vec3 это не сработает. Я хочу сказать, что никакое определение единственного типа не приведет к тому, что любая структура GLSL будет иметь тот же макет, что и эквивалент C ++. Цель состоит в том, чтобы не запоминать правила, а просто скопировать структуру GLSL и изменить типы членов на именованные типы C ++. - person Nicol Bolas; 18.02.2019
comment
Хорошо, я перепутал ваши два vec3 примера и vec3 + float пример - мой плохой. - person Krupip; 18.02.2019
comment
@NicolBolas Вы упомянули, что std430 ничего не меняет в vec3s, поскольку их базовое выравнивание всегда совпадает с выравниванием vec4. В выбранном ответе на другой вопрос (stackoverflow.com/questions / 29531237 /) Я читал, что в случае std430 трехкомпонентные векторы не округляются до размера четырехкомпонентных векторов. Я смущен. Любое из приведенных выше утверждений неверно? Что я неправильно понимаю? - person Fox42; 15.05.2020
comment
@Tom: Все примеры в этом ответе верны, но цитируемый текст не из спецификации OpenGL; это из книги. И эта цитата неверна. В спецификации указано, что для std430 правила округления для массивов и структур не применяются. Но проблема vec3 не связана с правилами округления; его выравнивание не основано на округлении массивов и структур. Базовое выравнивание vec3 напрямую указано равным 16 байтам. - person Nicol Bolas; 15.05.2020