Help end child hunger

GLSL Tutorial – Uniform Blocks

 
Prev: Uniform Variables Next: Inter shader comm.
 

Uniform blocks are a very convenient feature for two reasons:

  • Allow uniform sharing between programs – set once, use many times
  • Allow setting multiple values at once

The GLSL syntax is pretty straight forward. For instance to declare a block with two colors in a shader we could write something as follows:

uniform ColorBlock {
	vec4 diffuse;
	vec4 ambient;
};

The code above declares a block called ColorBlock, with two vec4 variables in it. In the specification these blocks are referred to as named blocks, and all the uniforms outside these named blocks belong to a default block.

Inside the shader’s functions we just work with both uniforms, without any need to use the block’s name.

...
out vec4 outputF;

void main() {
	outputF = diffuse + ambient;
}

The default storage for a block is implementation dependent. However, other options are available, and we can specify a storage mode for the block with a layout qualifier. The available options are:

  • std140: The packaging of the variables follows rules defined in the OpenGL specification. Blocks with this layout can be shared among shaders.
  • shared: The storage is implementation dependent, but the compiler will ensure that the block is still shareable among different shaders
  • packed: The compiler will optimize the block’s storage, possibly removing any variables that are not used in the shader. These type of blocks should not be shared.

The major advantage of using std140, is that the storage rules are known apriori, which can help us setting the block as a whole, but we’ll get back to this later.

OpenGL Setup

Blocks in shaders are connected to OpenGL buffers through binding points. The idea is simple, each block has an index, which can be bound to a binding point. The buffers also have an id, which can be bound to the same binding point, hence establishing a connection between the buffer’s data store and the shader’s block.

In the diagram below, blocks A1 and B1 share the same binding point, hence share the same data. For two blocks to share the same buffer they must have the same layout, and should have the same data definition.

 

When a shader is linked (in some cases having a link command issued might be sufficient, see the specs), each block is assigned an index. To get the block’s index we can use function glGetUniformBlockIndex.

[stextbox]GLuint glGetUniformBlockIndex(GLuint program, const GLchar * uniformBlockName);

Params:

  • program: the handle to the program
  • uniformBlockName: the name of the uniform block

Return: the index of the block, or INVALID_INDEX if the name does not correspond to an active block.[/stextbox]

As mentioned before, blocks relate to buffers through binding points. Both, buffer and block, must be assigned the same binding point. To assign a block to a binding point we use the following function:

[stextbox]void glUniformBlockBinding(GLuint program, GLuint uBlockIndex, GLuint uBlockBinding);

Params:

  • program: the handle to the linked program
  • uBlockIndex: the index of the uniform block
  • uBlockBinding: the binding point

[/stextbox]

We also need to create a buffer, in this case a GL_UNIFORM_BUFFER, create its data store, and assign it to the same binding point as the block. To create the buffer’s data store we can use glBufferData. The last step can be accomplished with glBindBufferBase.

[stextbox]void glBufferData(GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage);

Params:

  • target: in this context it must beGL_UNIFORM_BUFFER
  • size: the size of the data store
  • data: a pointer to the data, or NULL
  • usage: a hint. This data should be readily available, but can change often, so GL_DYNAMIC_DRAW is probably a good option

[/stextbox]

[stextbox]void glBindBufferBase(GLenum target, GLuint bindingPoint, GLuint bufferName);

Params:

  • target: in this context it must beGL_UNIFORM_BUFFER
  • bindingPoint: the binding point
  • bufferName: the buffer name

[/stextbox]

The following code snippet puts it all together. It assumes that p is a GLSL program to which a link command has been issued.

// the binding point must be smaller than GL_MAX_UNIFORM_BUFFER_BINDINGS
GLuint bindingPoint = 1, buffer, blockIndex;
float myFloats[8] = {1.0, 0.0, 0.0, 1.0,   0.4, 0.0, 0.0, 1.0};

blockIndex = glGetUniformBlockIndex(p, "ColorBlock");
glUniformBlockBinding(p, blockIndex, bindingPoint);

glGenBuffers(1, &buffer);
glBindBuffer(GL_UNIFORM_BUFFER, buffer);

glBufferData(GL_UNIFORM_BUFFER, sizeof(myFloats), myFloats, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, buffer);

After these steps, the buffer is linked to the block. To feed values to the shader’s block, all that is required is to copy data to the buffer’s data store.

Setting and Updating the Buffer

Lets consider the following block declared in a shader:

layout (std140) uniform ColorBlock {
	vec4 diffuse;
	vec4 ambient;
};

To set individual variables we need to get their offset relatively to the beginning of the buffer. Actually getting this data is a little hard. We need two functions to find out what is inside a block, assuming we have the block’s index, which, as seen before, can be retrieved with glGetUniformBlockIndex.

[stextbox]void glGetActiveUniformBlockiv(GLuint program, GLuint uBlockIndex, GLenum pname, GLint *params);

Parameters:

  • program: the handle to the program
  • uBlockIndex: the block’s index
  • pname: what we want to retrieve
  • result: this is where the data is returned

[/stextbox]

Regarding the parameter pname we may use GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS to retrieve the number of uniforms in the block, and then GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES to find the indices of those uniforms. Once we have the indices we can use glGetActiveUniformsiv to get the uniforms data.

[stextbox]void glGetActiveUniformsiv(GLuint program, GLsizei ucount, const GLuint *uIndices, GLenum pname, GLint *params);

Parameters:

  • program: the handle to the program
  • ucount: the number of indices
  • uindices: the list of indices
  • pname: the information we want to retrieve
  • result: this is where the data is returned

[/stextbox]

Regarding the parameter pname we can use the following fields to help us determine where a particular uniform is located in the buffer data store, and how its memory is packed: GL_UNIFORM_TYPE, GL_UNIFORM_OFFSET, GL_UNIFORM_SIZE, GL_UNIFORM_ARRAY_STRIDE, GL_UNIFORM_MATRIX_STRIDE.

Lets look at a particular example. Consider the above block, named ColorBlock with two vec4. We can use VSGLInfoLib to obtain information about a uniform block. For the above block we would get:

ColorBlock
    Size 32
    Block binding point: 0
    Buffer bound to binding point: 0 
    {
       ambient
            GL_FLOAT_VEC4
            offset: 16
            size: 16
       diffuse
            GL_FLOAT_VEC4
            offset: 0
            size: 16
    }

As can be seen each variable takes 16 bytes in size, which is to be expected as each float takes 4 bytes, and a vec4 has 4 floats. We can also see that, in this case, the offsets match the sizes, i.e. the second uniform starts where the first one ends.

To set the uniform values we have to fill the buffer’s data store. A function which can be used for this purpose is:

[stextbox]void glBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data);

Parameters:

  • target: GL_UNIFORM_BUFFER is the value we want in this context
  • offset: the offset in bytes relative to the start of the data store
  • size: the amount of bytes we want to copy to the data store
  • data: the data we want to copy to the data store

[/stextbox]

So we could set uniform ambient as follows:

float color[4] = {0.3, 0.0, 0.0, 1.0};
GLuint offset = 16;
glBindBuffer(GL_UNIFORM_BUFFER, buffer);
glBufferSubData(GL_UNIFORM_BUFFER, offset , sizeof(color), color);

To fill the entire buffer we can still use the same function. For instance we could use the following code to set the values for both diffuse and ambient.

float color[8] = {0.8, 0.0, 0.0, 1.0,     0.3, 0.0, 0.0, 1.0};
glBindBuffer(GL_UNIFORM_BUFFER, buffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(color), color);

Now, lets consider a slightly different block:

layout (std140) uniform ColorBlock2 {
	vec3 diffuse;
	vec3 ambient;
};

Using VSGLInfoLib to retrieve the block’s info we get:

ColorBlock2
    Size 28
    Block binding point: 0
    Buffer bound to binding point: 0 
    {
       ambient
            GL_FLOAT_VEC3
            offset: 16
            size: 12
       diffuse
            GL_FLOAT_VEC3
            offset: 0
            size: 12
    }

As can be seen from the description of the block above, the offset for ambient is 16 bytes, although the size of diffuse, the first uniform in the block, is only 12 bytes. This is because with layout std140 vec3s are aligned as vec4s. The full set of rules is available in the spec.

So, getting back to our particular example of ColorBlocks2, to fill the whole buffer’s data store, we need to consider not only the sizes but also the offsets. To set the buffer we need to create an array with 28 bytes, the size of the block, or 7 floats. To get the diffuse color to be (0.8, 0.2, 0.2) and ambient as (0.4, 0.1, 0.1), the first three floats will be the diffuse color, a float for alignment purposes to get the right offset for the second variable, and another three floats for the ambient component.

GLuint bindingPoint = 1, buffer, blockIndex;
float myFloats[7] = {0.8, 0.2, 0.2,     0.0,   0.4, 0.1, 0.1};

glGenBuffers(1, &buffer);
glBindBuffer(GL_UNIFORM_BUFFER, buffer);

glBufferData(GL_UNIFORM_BUFFER, sizeof(myFloats), myFloats, GL_DYNAMIC_DRAW);

The same care must be taken when using matrices and arrays. For instance a float 3×3 matrix requires 4 floats for each column for alignment purposes. This information, the matrix stride, can be retrieved with glGetActiveUniformsiv with param GL_UNIFORM_MATRIX_STRIDE. For arrays the param is GL_UNIFORM_ARRAY_STRIDE. Read the specs to fully grasp the principles behind the memory layout, and use VSGLInfoLib to confirm if you’re really getting it.

 

Prev: Uniform Variables Next: Inter shader comm.
 

  4 Responses to “GLSL Tutorial – Uniform Blocks”

  1. Very nicely explained.

  2. Finally I understand uniform blocks. Thanks for this excellent explanation.

  3. Thanks for the tutorials on GLSL Core, there’s not that many websites that are up to date 🙂
    Small error though: the arguments you describe for glBindBufferBase are the wrong ones.

Leave a Reply to Robinson Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: