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 Jan 2013]

Uniform Buffer Objects and Shader Uniform Blocks in OpenGL

Uniform Buffer Object (UBO) is a buffer object, which is used to set values of group of uniforms (uniform block) in GLSL shaders. First advantage of UBO is that such buffer allows to set a lot of uniform variables at once. This is faster than to set each uniform variable one by one. Second advantage is that Uniform Buffer Object can be bound to many shader programs, and each shader program will have access to same data. So instead of setting uniform variables for each shader program, you can update UBO only for one time, and all connected shader programs will have access to updated data. Third advantage is that in most cases Uniform Buffer Objects allow to store more data in comparison to simple uniform variables.
You can define uniform block in GLSL like in the following code snippet:
uniform testUniformBlock1 {
   bool refUse;
   vec2 refPosition;
   float refRotation;
};
Just use name of uniform variable from the uniform block to access its value: refUse, etc. testUniformBlock1 is name of uniform block that is used by OpenGL to distinguish uniform blocks.
You can also define uniform block with access name:
uniform testUniformBlock2 {
   bool refUse;
   vec2 refPosition;
   float refRotation;
} uniBlock;
In this case, use uniform block's access name to access value of uniform variable in GLSL shader: uniBlock.refUse. But in OpenGL same variable will have name testUniformBlock2.refUse.
One of the disdvantages of Uniform Buffer Objects and uniform blocks is that they don't support samplers.
Data layout in Uniform Buffer Object
To fill Uniform Buffer Object you need to know how to correctly write data into the buffer. There are different formats for uniform buffer layouts in GLSL that determine how the data have to be saved in linear memory of the buffer. For example vector of floats with three components (vec3) should have alignment of 16 bytes, not 12, etc. But more on this a bit later. You can manually set a layout rule for how to place uniform variables in the buffer's memory. This is done by following modification of uniform block:
layout(std140) uniform testUniformBlock { ... }
The value in the brackets determines the layout rule. There are following possible rules:
  • shared - used by default. Block can be shared between many shader programs.
  • packed - memory layout is optimized and depends on shader. Can't be used to share uniform block among shader programs.
  • std140 - format with known layout rules. With this format you can manually calculate offsets and sizes of all uniform variables in the uniform block without querying of OpenGL.
  • Additional modifiers set rule for matrices layout:
  • row_major - save matrices by rows.
  • column major - seve matrices by columns.
  • For shared and packed formats you have to query shader program through OpenGL API about offset and size of each variable in the uniform block. This may be a bit inconveniently for large uniform blocks.
    You can also define global rule for all uniform blocks in shader:
    layout(std140) uniform;
    If you are using std140 format, then you can manually calculate offset for each uniform variable. Locations of uniform variables must be aligned in memory relative to the start of the buffer. If alignment for type of uniform variable is 16 bytes, then uniform variable can be placed only on bytes equal to multiple of 16 - 0, 16, 32, 48, etc. std140 layout rules for different types of data in GLSL:
  • float, bool, int, uint - size and alignment are equal to size of base type, e.g. sizeof(GLfloat).
  • vec2, ivec2, bvec2 - size and alignment are equal to size of base type times 2.
  • vec3, vec4, ivec3, ivec4, etc - size and alignment are equal to size of base type times 4.
  • Arrays (float[3]) - size and offset is equal to size of base type rounded to size of vec4. So even if you have array of GLbooleans, which have size of one byte each, in Uniform Buffer Object each GLboolean will take 16 bytes of memory (1 byte used, 15 not used). Alignment of array (possible first byte) is also equal to size of vec4. Size of the array is equal to alignment of element times number of elements in the array.
  • Matrices (mat4) - each row (or column - depends on rule) should be treated as vector, with similar rules. If you want you can query this value from OpenGL (GL_UNIFORM_MATRIX_STRIDE). OpenGL will return distance in bytes from a beginning of one vector to a beginning of another vector.
  • Uniform buffers should have similar layouts in order to be shared between shaders.
    Connection between Uniform Buffer Object and Uniform Blocks
    You can link GLSL uniform blocks and Uniform Buffer Objects with two OpenGL calls. Just connect uniform block with UBO and UBO to uniform block. But such connection won't be valid for other shaders, because bidning is done with help of index of uniform block, and it may be different for same uniform blocks in different shaders. In order to share UBO among many shaders you have to use binding points. Bind UBO to binding point, then bind all uniform buffers to same binding point. And that's it!
    // index of binding point. Other shaders with same uniform block should use this binding point
    GLuint bindingPoint = 1;
    // get index of uniform buffer from shader
    GLuint blockIndex = glGetUniformBlockIndex(shaderProgram, "testUniformBlock");
    // bind uniform block to binding point
    glUniformBlockBinding(shaderProgram, blockIndex, bindingPoint);

    // create new buffer for Uniform Buffer Object
    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_UNIFORM_BUFFER, buffer);
    // bind buffer to binding point
    glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, buffer);
    You can use one UBO for multiple different uniform blocks. In such case bind buffer to binding points through glBindBufferRange(). Place first byte for each uniform block with alignment equal to value of GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT query.
    Limitations of Uniform Blocks
    Query limitations and parameters of Uniform Buffer Objects and uniform blocks:
    GLint maxUniformBlockSize, maxUniformBlocksVertex, maxUniformBlocksVertex;
    // maximum size of uniform block
    glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
    // maximum number of uniform blocks in each shader (vertex, frag, etc)
    glGetIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &maxUniformBlocksVertex);
    // alignment for multiple uniform blocks in one UBO - glBindBufferRange()
    glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &maxUniformBlocksVertex);
    For example params for Intel i7-4800MQ: maximum number of blocks - 14, maximum size - 16Kb, alignment - 16 bytes.
    Example for Uniform Buffer Object and uniform blocks
    We have following uniform block as example:
    uniform testUniformBlock
    {
       bool refUse;
       vec2 refPosition;
       float refRotation;
    };
    Following example shows how to get information about uniform block, how to create and fill UBO, and finally how to connect UBO and uniform block. PS. Check whether all fields of uniform block are really present after compilation of shader, because uniform variable will be wiped out during optimization if it is doesn't used in the shader. And as result following example will crash.
    GLuint uboBuffer;
    GLuint ubIndex;
    GLint uboSize;
    std::unique_ptr cpuBuffer;

    // get index of uniform block by name
    ubIndex = glGetUniformBlockIndex(shaderProgram, "testUniformBlock");

    // get size of uniform block
    glGetActiveUniformBlockiv(shaderProgram, ubIndex,
                      GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);

    if(uboSize > 0)
    {
       // convenient variales, corresponds to variable in uniform block
       enum Uniforms{ eRefPosition, eRefRotation, eRefUse, eNumUniformsInBlock };

       // values for uniforms in uniform block
       GLfloat refPositionValue[] = { 1, 2 };
       GLfloat refRotationValue[] = { M_PI };
       GLboolean refUseValue[] = { true };

       // names of uniform variables in block
       const char * names[eNumUniformsInBlock] = {
                "refPosition", "refRotation", "refUse"
          };

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

       // get indices of uniform variables in uniform block
       // PS. Check whether all indices != GL_INVALID_INDEX
       glGetUniformIndices(shaderProgram, eNumUniformsInBlock, names, indices);

       // get size of all uniform variables in uniform block
       glGetActiveUniformsiv(shaderProgram, eNumUniformsInBlock,
                         indices, GL_UNIFORM_SIZE, size);
       // get offsets of all uniform variables in uniform block
       glGetActiveUniformsiv(shaderProgram, eNumUniformsInBlock,
                         indices, GL_UNIFORM_OFFSET, offset);
       // get type of all uniform variables in uniform block
       glGetActiveUniformsiv(shaderProgram, eNumUniformsInBlock,
                         indices, GL_UNIFORM_TYPE, type);

       // temporal CPU buffer to build Uniform Buffer Object
       cpuBuffer = std::unique_ptr(new GLbyte[uboSize]);
       // use offset, size and type arrays to correctly fill temp buffer
       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]));

       // create Uniform Buffer Object and copy data
       glGenBuffers(1, &uboBuffer);
       glBindBuffer(GL_UNIFORM_BUFFER, uboBuffer);
       glBufferData(GL_UNIFORM_BUFFER, uboSize, cpuBuffer.get(), GL_STATIC_DRAW);
       // copy UBO and uniform block. Note: use binding points if you want to share this UBO among shaders
       glBindBufferBase(GL_UNIFORM_BUFFER, ubIndex, uboBuffer);
    }

    // don't forget to delete UBO glDeleteBuffers(1, &uboBuffer);
    Enumerate Uniforms Blocks
    Following example enumerates all uniform blocks in shader, and all uniform variables in parent blocks.
    // get number of uniform blocks in shader
    GLint numUniformBlocks;
    glGetProgramiv(shaderProgram, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);

    // get information about each uniform block
    for(int uniformBlock=0; uniformBlock<numUniformBlocks; uniformBlock++)
    {
       // get size of name of the uniform block
       GLint nameLength;
       glGetActiveUniformBlockiv(shaderProgram, uniformBlock,
                            GL_UNIFORM_BLOCK_NAME_LENGTH, &nameLength);

       // get name of uniform block
       std::unique_ptr blockName(new GLchar[nameLength]);
       glGetActiveUniformBlockName(shaderProgram, uniformBlock,
                         nameLength, nullptr, blockName.get());

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

       // get size of uniform block in bytes
       GLint uboSize;
       glGetActiveUniformBlockiv(shaderProgram, uniformBlock,
                                  GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);
       Log::instance().log(qs(" Size : ") + qs::number(uboSize));

       // get number of uniform variables in uniform block
       GLint numberOfUniformsInBlock;
       glGetActiveUniformBlockiv(shaderProgram, uniformBlock,
                            GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &numberOfUniformsInBlock);
       Log::instance().log(qs(" Members : ") + qs::number(numberOfUniformsInBlock));

       // get indices of uniform variables in uniform block
       std::unique_ptr uniformsIndices(new GLint[numberOfUniformsInBlock]);
       glGetActiveUniformBlockiv(shaderProgram, uniformBlock,
                      GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, uniformsIndices.get());

       // get parameters of all uniform variables in uniform block
       for(int uniformMember=0; uniformMember<numberOfUniformsInBlock; uniformMember++)
       {
          if(uniformsIndices[uniformMember] > 0)
          {
             // index of uniform variable
             GLuint tUniformIndex = uniformsIndices[uniformMember];

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

             // get length of name of uniform variable
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                         GL_UNIFORM_NAME_LENGTH, &uniformNameLength);
             // get name of uniform variable
             std::unique_ptr uniformName(new GLchar[uniformNameLength]);
             glGetActiveUniform(shaderProgram, tUniformIndex, uniformNameLength,
                            nullptr, nullptr, nullptr, uniformName.get());

             // get offset of uniform variable related to start of uniform block
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_OFFSET, &uniformOffset);
             // get size of uniform variable (number of elements)
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_SIZE, &uniformSize);
             // get type of uniform variable (size depends on this value)
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_TYPE, &uniformType);
             // offset between two elements of the array
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_ARRAY_STRIDE, &arrayStride);
             // offset between two vectors in matrix
             glGetActiveUniformsiv(shaderProgram, 1, &tUniformIndex,
                            GL_UNIFORM_MATRIX_STRIDE, &matrixStride);

             // Size of uniform variable in bytes
             GLuint sizeInBytes = uniformSize * sizeFromUniformType(uniformType)

             // output data
             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"));
          }
       }
    }
    Example output:
    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
    Calculate size of uniform variable from type
    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;
    }
    Output name of uniform variable type
    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;
    }
    Fill uniform block object for uniform block with std140 format
    // Uniform block
    layout (std140) uniform testUniformBlock
    {
       bool refUse;
       vec2 refPosition;
       float refRotation;
    };

    // manually calculate offsets and sizes for std140 uniform block
    bool refUse;
       curpos = 0; // this is first element, beginning of the buffer
       pos = 0; // this is first element, beginning of the buffer
       size = 1; // sizeof(GLboolean)
       align = 1; // sizeof(GLboolean)

    vec2 refPosition;
       curpos = 1; // current position ptr after last variable
       align = 8; // 2 * sizeof(GLfloat)
       size = 8; // 2 * sizeof(GLfloat)
       pos = 8; // position 1 isn't valid, not multiple of 8, align to 8

    float refRotation;
       curPos = 16; // current position ptr after last variable
       align = 4; // 1 * sizeof(GLfloat)
       size = 4; // 1 * sizeof(GLfloat)
       pos = 16; // position 16 is valid for type with align 4
    ==================================
    TotalSize = 16 + 4 = 20;

    // fill buffer with data
    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 (igor dykhta email) 2007-2014