sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[20 Січ 2013]

Використання Uniform Buffer Objects та Shader Uniform Blocks в OpenGL

Uniform Buffer Object (UBO) - буферний об'єкт, який використовується для встановлення значень групи uniform змінних (uniforms block) в GLSL шейдерах. Перша перевага UBO в тому, що такий буфер дозвляє встановити одразу багато значень uniform змінних. Це швидше ніж перемикання кожної uniform змінної поодинці. Друга перевага Uniform Buffer Object в тому, що UBO можна приєднати до багатьох шейдерних програм, і кожна шейдерна програма буде звертатися до тих самих даних. Тож замість встановлення uniform значень для кожної шейдерної програми, можна встановити один раз значення UBO, і всі підєднані шейдери будуть мати доступ до актуальних даних. Третя перевага в тому, що UBO дозволяє зберегти в ньому більшу кількість інформації ніж можна зберегти в простих uniform змінних.
Uniforms blocks в GLSL оголошується наступним чином:
uniform testUniformBlock1 {
   bool refUse;
   vec2 refPosition;
   float refRotation;
};
В такому випадку звертатися до значень в шейдері можна просто refUse, і тд.
Також uniform блок можна оголосити з явним імям:
uniform testUniformBlock2 {
   bool refUse;
   vec2 refPosition;
   float refRotation;
} uniBlock;
В цьому випадку в шейдері потрібно звертатися до полів uniBlock.refUse, а в OpenGL - testUniformBlock2.refUse.
Обмеженням uniform блоків та Uniform Buffer Object є те, що не можна до них включити samplers.
Розміщення даних в Uniform Buffer Object
Щоб заповнити Uniform Buffer Object потрібно дізнатися, як правильно записати дані в буфер. В GLSL є правила щодо того, як значення в uniform блоку повинні бути збережені в лінійній ділянці памяті буфера. Наприклад, вектор з трьох float значень (vec3) повинен починатися в памяті з вирівнюванням в 16 байт, а не 12 і тп. Для блоку uniform значень можна задати правило розміщення в памяті. Це робиться з допомогою наступної модифікації оголошення uniform блоку.
layout(std140) uniform testUniformBlock { ... }
В дужках вказується правило по якому проводиться розміщення змінних. Можливими є наступні значення:
  • shared - по замовчуванню. Блок може використовуватися в багатьох шейдерних програмах.
  • packed - розміщення в памяті оптимізоване і залежить від шейдера. Використання в багатьох шейдерних програмах не дозволене.
  • std140 - формат, для якого відомі правила розміщення. Встановивши цей формат можна вручну розрахувати позиції кожної змінної в буфері, і не робити запитів до шейдера. Можна використовувати в багатьох шейдерних програмах.
  • Додаткові модифікатори визначають як зберігаються в памяті матриці:
  • row_major - матриці зберігають в памяті по рядкам.
  • column major - матриці розміщаються в памяті по стовбцям.
  • Для shared та packed форматів необхідно зробити запит з OpenGL до шейдера про розміщення кожної змінної uniform блока відносно початку блока.
    Можна оголосити формат для усіх блоків uniform, що будуть далі у шейдері таким чином:
    layout(std140) uniform;
    Якщо використовується формат std140, то позицію кожної змінної в памяті можна розрахувати вручну. Змінні можуть починатися в пам'яті тільки на байті, порядковий номер якого є множником значення вирівнювання для змінної. Далі наведено правила типів даних в GLSL:
  • float, bool, int, uint - розмір і вирівнювання рівні розміру бaзового типу скаляра, наприклад sizeof(GLfloat)
  • vec2, ivec2, bvec2 - розмір і вирівнювання рівні базавому типу * 2
  • vec3, vec4, ivec3, ivec4, etc - розмір і вирівнювання рівні базовому типу * 4
  • Масиви (float[3]) - розмір і вирівнювання рівні розміру базового типу округленого до розміру vec4. Таким чином навіть елементи GLboolean, які займають один байт, в GLSL масиві будуть займати по 16 байт. Вирівнювання (можливий початок) масиву також рівне розміру vec4. Розмір масиву рівний вирівнюванню елемента масиву помноженого на кількість елементів в масиві.
  • Матриці (mat4) - Кожен рядок (чи стовпець) трактується як вектор і використовує попередні правила. Вектора не тісно спаковані. Можна дістати значення GL_UNIFORM_MATRIX_STRIDE - відстань від початку одного вектора в матриці до початку наступного.
  • Для того, щоб правильно використовувати один UBO в багатьох шейдерах - uniform блоки повинні мати однакову структуру і розміщення в памяті.
    Звязка Uniform Buffer Object і Uniforms Block
    Uniform блоки в GLSL повинні бути звязані Uniform Buffer Objects в OpenGL. Кожен блок може бути привязаний до певного UBO. Декілька різних блоків з різних шейдерів можуть бути привязані до одного і того самого буфера. Замість привязки до буфера можна використовувати точки привязки. Буфер підєднюється до точки привязки, і блок підєдньється до тієї ж точки привязки. Таким чином може відбуватися зєднання блоків та буферів.
    // індекс точки привязки. Інші шейдера повинні використовувати цю ж точку привязки
    GLuint bindingPoint = 1;
    // дістати індекс uniforms block з шейдера
    GLuint blockIndex = glGetUniformBlockIndex(shaderProgram, "testUniformBlock");
    // привязати блок до точки привязки
    glUniformBlockBinding(shaderProgram, blockIndex, bindingPoint);

    // створити новий буфер, який буде Uniform Buffer Object
    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_UNIFORM_BUFFER, buffer);
    // привязати буфер до точки привязки
    glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, buffer);
    Один UBO можна використовувати для багатьох різних uniform блоків. В такому разі привязку буфера до точок привязки треба робити через glBindBufferRange(). Дані повинні розміщуватися в буфері з врахуванням вирівнювання GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT.
    Обмеження Uniform Blocks
    Запит обмежень для Uniform Buffer Objects:
    GLint maxUniformBlockSize, maxUniformBlocksVertex, maxUniformBlocksVertex;
    // максимальний розмір uniform блоку
    glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
    // максимальна кількість uniform блоків в кожному шейдері (і для інших типів шейдерів)
    glGetIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &maxUniformBlocksVertex);
    // вирівнювання даних при призвязці блока до точки привязки через glBindBufferRange()
    glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &maxUniformBlocksVertex);
    Наприклад, 14 блоків і максимальнай розмір блоку 16Кб, вирівнювання - 16 байт на Intel i7-4800MQ
    Приклад роботи з Uniform Buffer Object
    Припустимо, що в шейдері є наступний uniform block:
    uniform testUniformBlock
    {
       bool refUse;
       vec2 refPosition;
       float refRotation;
    };
    В наступному прикладі показано як дістати данні про uniform buffer та як підєднати до нього Uniform Buffer Object, і як заповнити UBO даними. Ps. Також необхідно перевірити, що шукані поля в uniform block дійсно існують. Так як у випадку, якщо змінна з uniforms block не використовується в шейдері, то компілятор видаляє її зі скомпільованого шейдера.
    GLuint uboBuffer;
    GLuint ubIndex;
    GLint uboSize;
    std::unique_ptr cpuBuffer;

    // дістаємо індекс uniform блока по імені
    ubIndex = glGetUniformBlockIndex(shaderProgram, "testUniformBlock");

    // дістаємо розмір uniform блока
    glGetActiveUniformBlockiv(shaderProgram, ubIndex,
                      GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);

    if(uboSize > 0)
    {
       // просто індекси для прикладу, відповідають змінним у uniform блоці
       enum Uniforms{ eRefPosition, eRefRotation, eRefUse, eNumUniformsInBlock };

       // значення, які мають бути записані в uniform блок
       GLfloat refPositionValue[] = { 1, 2 };
       GLfloat refRotationValue[] = { M_PI };
       GLboolean refUseValue[] = { true };

       // назви uniform змінних у uniform блоці
       const char * names[eNumUniformsInBlock] = {
                "refPosition", "refRotation", "refUse"
          };

       GLuint indices[eNumUniformsInBlock];
       GLint size[eNumUniformsInBlock];
       GLint offset[eNumUniformsInBlock];
       GLint type[eNumUniformsInBlock];

       // дістати індекси uniform змінних з блока
       // PS. Тут потрібно перевірити чи всі індекси знайдені і не рівні GL_INVALID_INDEX
       glGetUniformIndices(shaderProgram, eNumUniformsInBlock, names, indices);

       // дістати розмір усіх uniform змінних
       glGetActiveUniformsiv(shaderProgram, eNumUniformsInBlock,
                         indices, GL_UNIFORM_SIZE, size);
       // дістати зсуви для усіх uniform змінних
       glGetActiveUniformsiv(shaderProgram, eNumUniformsInBlock,
                         indices, GL_UNIFORM_OFFSET, offset);
       // дістати тип для усіх uniform змінних
       glGetActiveUniformsiv(shaderProgram, eNumUniformsInBlock,
                         indices, GL_UNIFORM_TYPE, type);

       // тимчасовий буфер. Використовується для того, щоб сформувати UBO
       cpuBuffer = std::unique_ptr(new GLbyte[uboSize]);
       // записати дані у потрібних місцях
       memcpy(cpuBuffer.get() + offset[eRefPosition], &refPositionValue,
                size[eRefPosition] * sizeFromUniformType(type[eRefPosition]));
       memcpy(cpuBuffer.get() + offset[eRefRotation], &refRotationValue,
                size[eRefRotation] * sizeFromUniformType(type[eRefRotation]));
       memcpy(cpuBuffer.get() + offset[eRefUse],
                &refUseValue, size[eRefUse] * sizeFromUniformType(type[eRefUse]));

       // створити Uniform Buffer Object і скопіювати в нього дані
       glGenBuffers(1, &uboBuffer);
       glBindBuffer(GL_UNIFORM_BUFFER, uboBuffer);
       glBufferData(GL_UNIFORM_BUFFER, uboSize, cpuBuffer.get(), GL_STATIC_DRAW);
       // звязати Uniform Buffer Object і Uniform Block
       glBindBufferBase(GL_UNIFORM_BUFFER, ubIndex, uboBuffer);
    }

    // не забудьте видалити UBO glDeleteBuffers(1, &uboBuffer);
    Вивід усіх параметрів для Uniforms Blocks
    Наступний приклад виводить данні про усі uniform blocks у шейдері, і про усі uniform змінні у блоках.
    // дістати кількість uniform блоків в шейдері
    GLint numUniformBlocks;
    glGetProgramiv(shaderProgram, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);

    // дістати інформацію про кожен блок
    for(int uniformBlock=0; uniformBlock<numUniformBlocks; uniformBlock++)
    {
       // дістати довжину назви блока
       GLint nameLength;
       glGetActiveUniformBlockiv(shaderProgram, uniformBlock,
                            GL_UNIFORM_BLOCK_NAME_LENGTH, &nameLength);

       // дістати назву блока
       std::unique_ptr blockName(new GLchar[nameLength]);
       glGetActiveUniformBlockName(shaderProgram, uniformBlock,
                         nameLength, nullptr, blockName.get());

       Log::instance().log(qs("Uniform Block : ") + blockName.get());

       // дістати розмір блока в байтах
       GLint uboSize;
       glGetActiveUniformBlockiv(shaderProgram, uniformBlock,
                                  GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);
       Log::instance().log(qs(" Size : ") + qs::number(uboSize));

       // дістати кількість uniform змінних в блоці
       GLint numberOfUniformsInBlock;
       glGetActiveUniformBlockiv(shaderProgram, uniformBlock,
                            GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &numberOfUniformsInBlock);
       Log::instance().log(qs(" Members : ") + qs::number(numberOfUniformsInBlock));

       // дістати індекси uniform зміних в блоці
       std::unique_ptr uniformsIndices(new GLint[numberOfUniformsInBlock]);
       glGetActiveUniformBlockiv(shaderProgram, uniformBlock,
                      GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, uniformsIndices.get());

       // дістати параметри кожної uniform змінної
       for(int uniformMember=0; uniformMember<numberOfUniformsInBlock; uniformMember++)
       {
          if(uniformsIndices[uniformMember] > 0)
          {
             // індекс змінної для якої виводимо інформацію
             GLuint tUniformIndex = uniformsIndices[uniformMember];

             uniformsIndices[uniformMember];
             GLint uniformNameLength, uniformOffset, uniformSize;
             GLint uniformType, arrayStride, matrixStride;

             // дістати довжину назви uniform змінної
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                         GL_UNIFORM_NAME_LENGTH, &uniformNameLength);
             // дістати назву uniform змінної
             std::unique_ptr uniformName(new GLchar[uniformNameLength]);
             glGetActiveUniform(shaderProgram, tUniformIndex, uniformNameLength,
                            nullptr, nullptr, nullptr, uniformName.get());

             // дістати зсув uniform змінної відносно початку uniform block
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_OFFSET, &uniformOffset);
             // дістати розмір uniform змінної (кількість елементів)
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_SIZE, &uniformSize);
             // дістати тип uniform змінної (розмір в байтах визначається з типу)
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_TYPE, &uniformType);
             // зсув між початком елемента масиву до наступного елемента масиву
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_ARRAY_STRIDE, &arrayStride);
             // зсув між векторним елементом матриці і наступним векторним елементом
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_MATRIX_STRIDE, &matrixStride);

             // Розмір uniform змінної в байтах
             GLuint sizeInBytes = uniformSize * sizeFromUniformType(uniformType)

             // вивести дані
             Log::instance().log(qs("- ") + uniformName.get());
             Log::instance().log(qs("size ") + sizeInBytes);
             Log::instance().log(qs("offset ") + qs::number(uniformOffset));
             Log::instance().log(qs("type ") + textFromUniformType(uniformType));
             Log::instance().log(qs("array stride ") + qs::number(arrayStride));
             Log::instance().log(qs("matrix stride ") + qs::number(matrixStride));
          }
          else
          {
             Log::instance().log(qs("- Bad uniform"));
          }
       }
    }
    Вивід інформації про попередній uniform block:
    Uniform Block : testUniformBlock
       Size : 32
       Members : 3
          - refUse
             size 1
             offset 0
             type GL_BOOL
          - refPosition
             size 8
             offset 8
             type GL_FLOAT_VEC2
          - refRotation
             size 4
             offset 16
          type GL_FLOAT
    Розрахунок розміру uniform змінної
    GLuint sizeFromUniformType(GLint type)
    {
       GLuint s;
       
       #define UNI_CASE(type, numElementsInType, elementType) \
          case type : s = numElementsInType * sizeof(elementType); break;

       switch(type)
       {
          UNI_CASE(GL_FLOAT, 1, GLfloat);
          UNI_CASE(GL_FLOAT_VEC2, 2, GLfloat);
          UNI_CASE(GL_FLOAT_VEC3, 3, GLfloat);
          UNI_CASE(GL_FLOAT_VEC4, 4, GLfloat);
          UNI_CASE(GL_INT, 1, GLint);
          UNI_CASE(GL_INT_VEC2, 2, GLint);
          UNI_CASE(GL_INT_VEC3, 3, GLint);
          UNI_CASE(GL_INT_VEC4, 4, GLint);
          UNI_CASE(GL_UNSIGNED_INT, 1, GLuint);
          UNI_CASE(GL_UNSIGNED_INT_VEC2, 2, GLuint);
          UNI_CASE(GL_UNSIGNED_INT_VEC3, 3, GLuint);
          UNI_CASE(GL_UNSIGNED_INT_VEC4, 4, GLuint);
          UNI_CASE(GL_BOOL, 1, GLboolean);
          UNI_CASE(GL_BOOL_VEC2, 2, GLboolean);
          UNI_CASE(GL_BOOL_VEC3, 3, GLboolean);
          UNI_CASE(GL_BOOL_VEC4, 4, GLboolean);
          UNI_CASE(GL_FLOAT_MAT2, 4, GLfloat);
          UNI_CASE(GL_FLOAT_MAT3, 9, GLfloat);
          UNI_CASE(GL_FLOAT_MAT4, 16, GLfloat);
          UNI_CASE(GL_FLOAT_MAT2x3, 6, GLfloat);
          UNI_CASE(GL_FLOAT_MAT2x4, 8, GLfloat);
          UNI_CASE(GL_FLOAT_MAT3x2, 6, GLfloat);
          UNI_CASE(GL_FLOAT_MAT3x4, 12, GLfloat);
          UNI_CASE(GL_FLOAT_MAT4x2, 8, GLfloat);
          UNI_CASE(GL_FLOAT_MAT4x3, 12, GLfloat);
          default : s = 0; break;
       }
       return s;
    }
    Вивід назви типу uniform змінної
    qs textFromUniformType(GLint type)
    {
       qs s;
       switch(type)
       {
       case GL_FLOAT : s = "GL_FLOAT"; break;
       case GL_FLOAT_VEC2 : s = "GL_FLOAT_VEC2"; break;
       case GL_FLOAT_VEC3 : s = "GL_FLOAT_VEC3"; break;
       case GL_FLOAT_VEC4 : s = "GL_FLOAT_VEC4"; break;
       case GL_INT : s = "GL_INT"; break;
       case GL_INT_VEC2 : s = "GL_INT_VEC2"; break;
       case GL_INT_VEC3 : s = "GL_INT_VEC3"; break;
       case GL_INT_VEC4 : s = "GL_INT_VEC4"; break;
       case GL_UNSIGNED_INT : s = "GL_UNSIGNED_INT"; break;
       case GL_UNSIGNED_INT_VEC2 : s = "GL_UNSIGNED_INT_VEC2"; break;
       case GL_UNSIGNED_INT_VEC3 : s = "GL_UNSIGNED_INT_VEC3"; break;
       case GL_UNSIGNED_INT_VEC4 : s = "GL_UNSIGNED_INT_VEC4"; break;
       case GL_BOOL : s = "GL_BOOL"; break;
       case GL_BOOL_VEC2 : s = "GL_BOOL_VEC2"; break;
       case GL_BOOL_VEC3 : s = "GL_BOOL_VEC3"; break;
       case GL_BOOL_VEC4 : s = "GL_BOOL_VEC4"; break;
       case GL_FLOAT_MAT2 : s = "GL_FLOAT_MAT2"; break;
       case GL_FLOAT_MAT3 : s = "GL_FLOAT_MAT3"; break;
       case GL_FLOAT_MAT4 : s = "GL_FLOAT_MAT4"; break;
       case GL_FLOAT_MAT2x3 : s = "GL_FLOAT_MAT2x3"; break;
       case GL_FLOAT_MAT2x4 : s = "GL_FLOAT_MAT2x4"; break;
       case GL_FLOAT_MAT3x2 : s = "GL_FLOAT_MAT3x2"; break;
       case GL_FLOAT_MAT3x4 : s = "GL_FLOAT_MAT3x4"; break;
       case GL_FLOAT_MAT4x2 : s = "GL_FLOAT_MAT4x2"; break;
       case GL_FLOAT_MAT4x3 : s = "GL_FLOAT_MAT4x3"; break;
       default : s = "Unknown"; break;
       }
       return s;
    }
    Заповнення uniform block з форматом std140
    // Uniform block
    layout (std140) uniform testUniformBlock
    {
       bool refUse;
       vec2 refPosition;
       float refRotation;
    };

    // розрахунок розміщення змінних в uniform block з форматом std140
    bool refUse;
       curpos = 0; // це перший елемент, на початку буфера
       pos = 0; // це перший елемент, на початку буфера
       size = 1; // sizeof(GLboolean)
       align = 1; // sizeof(GLboolean)

    vec2 refPosition;
       curpos = 1; // поточна позиція після попередньої змінної
       align = 8; // 2 * sizeof(GLfloat)
       size = 8; // 2 * sizeof(GLfloat)
       pos = 8; // позиція 1 не підходить, не ділиться на 8, вирівнюємо до 8

    float refRotation;
       curPos = 16; // поточна позиція після попередньої змінної
       align = 4; // 1 * sizeof(GLfloat)
       size = 4; // 1 * sizeof(GLfloat)
       pos = 16; // позиція 16 підходить, так як ділиться на align
    ==================================
    TotalSize = 16 + 4 = 20;

    // записуємо дані в буфер
    GLbyte buffer[TotalSize];
    memcpy(buffer + 0, &refUseValue, sizeof(GLboolean) * 1);
    memcpy(buffer + 8, &refPositionValue, sizeof(GLfloat) * 2);
    memcpy(buffer + 16, &refRotationValue, sizeof(GLfloat) * 1);



    Sun and Black Cat- Ігор Дихта (igor dykhta email) © 2007-2014