Выравнивание std140 для unform-блоков в шейдерах (OpenGL, GLSL)

99
20 июня 2019, 04:00

Похоже, я не совсем понимаю как делать правильное выравнивание при передаче uniform-буферов в шейдер.

Насколько я понял, если для передачи данных в шейдеры использовать не обычные unform-переменные, а uniform-буферы, то данные, которые записываются в эти буферы, должны быть выровнены. Один из форматов, по которому это выравнивание может быть осуществлено - std140.

Правила разметки std140 примерно следующие:

Скалярное значение (int, float, bool - занимает 4 байта). Вектора занимают либо 8 байт (vec1, vec2) либо 16 байт (vec3, vec4). Матрицы - это как несколько векторов (в зависимости от размерности). Размер структуры равен размеру всех ее членов (с учетом вышеописанных правил), при необходимости дополненный до кратности 16.

В шейдере я объявил следующую структуру:

// Структура описывающая параметры мапинга текстуры
struct TextureMapping
{
    vec2 offset;
    vec2 origin;
    vec2 scale;
    mat4 rotation;
};

Получается, что сама структура должна занимать 8 + 8 + 8 + 64 байт (88 байт) До кратности 16 (ближайшее значение кратное 16 это 96) не хватает 8 байт. Но об этом позже.

Далее я объявляю uniform-блок, где используются эти структуры:

// UBO-блок с параметрами маппинга текстур
layout(std140) uniform textureMapping
{
    TextureMapping diffuseTexMapping;
    TextureMapping specularTexMapping;
    TextureMapping bumpTexMapping;
};

Затем, уже в С++ коде, я создаю аналог этой структуры, для того чтобы передать данные в шейдер. Все это выглядит как-то так:

Сама структура

struct TextureBasicMapping
{
    glm::vec2 offset;    // Сдвиг текстуры
    glm::vec2 origin;    // Центральная точка
    glm::vec2 scale;     // Масштабирование
    glm::mat4 rotation;  // Поворот (используется только часть 2*2)
};

А затем, я создаю сам uniform-буфер, учитывая что в нем 3 таких структуры:

// Создать UBO-буфер для параметров маппинга текстуры и выделить память
glGenBuffers(1, &uboTextureMapping_);
glBindBuffer(GL_UNIFORM_BUFFER, uboTextureMapping_);
glBufferData(GL_UNIFORM_BUFFER, 3 * sizeof(TextureBasicMapping), nullptr, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

Но не забываем, что до кратности 16 структуре TextureBasicMapping не хватает 8 байт. Получается, чтобы корректно передать в шейдер данные, нужно в самой структуре где-то этот 8-байтовй padding добавить. Так вот, я не совсем понимаю где именно.

В результате многочисленных проб оказалось, что этот самый 8-байтовый padding, для корректной передачи данных, необходимо добавить перед полем glm::mat4 rotation. В итоге структура стала выглядеть так:

struct TextureBasicMapping
{
    glm::vec2 offset;    // Сдвиг текстуры
    glm::vec2 origin;    // Центральная точка
    glm::vec4 scale;     // Масштабирование (vec2 + 8 байт выравнивания)
    glm::mat4 rotation;  // Поворот (используется только часть 2*2)
};

Но почему именно там? Почему не в конце? Почему не в начале? Как это вообще работает? Буду рад подробному пояснению.

Answer 1

Дело в том, что вычислительный конвейер подразумевается заточенным под операции с векторными блоками из 2 или 4 машинных единиц (то бишь vec2 из 8 байт vec4 из 16 байт). Для эффективной организации операции блоками они должны быть выравнены по границам таких базовых векторов:

  • Блоки из 1 машинной единицы всегда начинаются либо в начале, либо в середине блока из 2 машинных единиц.
  • Блоки из 2 машинных единиц всегда начинаются либо в начале, либо в середине блока из 4 машинных единиц.
  • Блоки из 3 и более машинных единиц всегда начинаются в начале блока из 4 машинных единиц.

Заметьте, что размер нигде прямо не задается, просто к блоку может быть добавлено неиспользуемое место, чтобы выравнивание следующего за ним блока было соблюдено:

struct s1
{
    vec2 offset; // 0 +2 
    vec2 origin; // 2 +2
};               // 4
struct s2
{
    vec3 offset; // 0 +3
    float dd;    // 3 +1
    vec4 origin; // 4 +4
};               // 8
struct s3
{
    vec3 offset; // 0 +3
                 // 3 +1 иначе следующий блок будет не выровнен
    vec2 origin; // 4 +2
                 // 6 +2 иначе следующая структура будет не выровнена
};               // 8
struct s4
{
    float offset; // 0 +1
                  // 1 +3 иначе следующая структура будет не выровнена
};                // 4
struct s5
{
    float offset; // 0 +1
                  // 1 +1 иначе следующий блок будет не выровнен
    vec2 origin;  // 2 +2
};                // 4
struct s6
{
    float offset; // 0 +1
                  // 1 +3 иначе следующий блок будет не выровнен
    vec4 origin;  // 4 +4
};                // 8
struct TextureBasicMapping
{
    glm::vec2 offset;   // 0 +2
    glm::vec2 origin;   // 2 +2
    glm::vec2 scale;    // 4 +2
                        // 6 +2 иначе следующий блок будет не выровнен
    glm::mat4 rotation; // 8 +16
};                      // 24
READ ALSO
Сумма элементов массива между первым и вторым отрицательными элементами

Сумма элементов массива между первым и вторым отрицательными элементами

Всем доброго времени суток! Хотел бы попросить вас о помощиНе могу понять, как сделать следующее задание

128
Чем опасен выход за границы массива?

Чем опасен выход за границы массива?

Можно ли оставлять выход за границы массива в программах? Чем это грозит? Что происходит при выходе за его границы?

162
Найти по атрибуту и тегу атрибута данные внутри этого тега

Найти по атрибуту и тегу атрибута данные внутри этого тега

Пришлось полностью восстанавливать пример, у вас не хватаетgetNodeValue()

148
Работа с GridBagLayout()

Работа с GridBagLayout()

Подскажите пожалуйстаИмеется панель:

106