Uniform Buffer Object

From OpenGL Wiki
Revision as of 08:57, 16 August 2011 by Alfonse (talk | contribs) (Shader Specification)
Jump to: navigation, search
Uniform Buffer Object
Core in version 4.6
Core since version 3.1
ARB extension ARB_Uniform_Buffer_Object

A Buffer Object that is used to store uniform data for a shader program is called a Uniform Buffer Object. They can be used to share uniforms between different programs, as well as quickly change between sets of uniforms for the same program object.

The term "Uniform Buffer Object" refers to the OpenGL buffer object that is used to provide storage for uniforms. The term "uniform blocks" refer to the GLSL language grouping of uniforms that must have buffer objects as storage.

Uses

Uniform buffers have several uses.

Switching between uniform buffer bindings is typically faster than switching dozens of uniforms in a program. Therefore, uniform buffers can be used to quickly change between different sets of uniform data for different objects that share the same program.

Also, uniform buffer objects can typically store more data than non-buffered uniforms. So they can be used to store and access larger blocks of data than unbuffered uniform values.

Lastly, they can be used to share information between different programs. So modifying a single buffer can effectively allow uniforms in multiple programs to be updated.

Limitations

Each shader stage has a limit on the number of separate uniform buffer binding locations. These are queried using glGetIntergerv with GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_GEOMETRY_UNIFORM_BLOCKS, or GL_MAX_FRAGMENT_UNIFORM_BLOCKS.

There is also a limitation on the available storage per uniform buffer. This is queried through GL_MAX_UNIFORM_BLOCK_SIZE. This is in basic machine units (ie: bytes).

Shader Specification

Uniform blocks are defined using the following syntax:

uniform BlockName
{
  vec3 blockMember1, blockMember2;
  float blockMember3;
};

This will give the uniform block the name "BlockName". This is the name you will use to refer to this block in the OpenGL API.

This declares the member uniforms at global scope. So this code is illegal:

uniform BlockName
{
  vec3 blockMember1, blockMember2;
  float blockMember3;
};

uniform vec3 blockMember1;  //Redefining variable.

If you want to scope the uniforms, you must declare the GLSL scope name after the uniform block declaration:

uniform BlockName
{
  vec3 blockMember1, blockMember2;
  float blockMember3;
} glslBlockName;

uniform vec3 blockMember1;  //Now fine

To refer to the members of the block, use "glslBlockName.blockMember1" and so forth.

You can also declare an array of blocks with the same definition:

uniform BlockName
{
  vec3 blockMember1, blockMember2;
  float blockMember3;
} multiBlocks[3];

To access an element of the array, you simply use "multiBlocks[2].blockMember1". The array index field must be a compile-time constant. In the OpenGL API, each element of the uniform block array has a separate block binding index. This also means that each individual array element counts towards the limit on the number of available uniform blocks in a single shader stage.

Uniform memory layout

Because uniform buffers are loaded directly by the user, rather than by an API, the layout needs to be specified. The layout describes how to access the individual components within a uniform block, and therefore how to load this data into a uniform buffer. Layout must be one of the following: std140, packed, or shared.

The packed layout type means that the implementation entirely determines how the fields are laid out in the block. The OpenGL API can be used to query the layout for a particular block. Each uniform member of a block will have a particular byte offset, which you can use to determine how to upload its data. Also, members of a block can be optimized out if they are found by the implementation to not affect the result of the shader. Therefore, the active components of a block may not be all of the components it was defined with.

The last limitation makes it difficult to share uniform buffers between different programs. Therefore, the shared type exists. It works essentially the way packed does; you have to query offsets for each uniform. However, shared has two major differences. First, it guarantees that all of the uniforms defined in the block are considered active. Second, it guarantees that, so long as all programs use the exact same block definition, the same uniform buffer can be used between all programs that share this same uniform definition.

Because of these guarantees, uniform blocks declared shared can be used with any program that defines a block with the same elements in the same order. OpenGL guarantees that all of the offsets and alignments will match between two shared uniform blocks that define the same members in the same order. In short, it allows the user to share uniform buffers between multiple programs.

In both of the previous layout types, the user must query the offset for each uniform of interest within the block. If you want to have a uniform layout that is know without having to query this information, you may use std140. The OpenGL Specification goes into great detail as to exactly how each element of this layout type is placed within a uniform block. This layout also has the same properties as shared, in that all uniforms will be available, and that programs can share uniforms of this layout with each other.

The downside to std140 is that the packing of uniforms within the blocks will likely not be optimal in terms of overall size.

Matrices can be defined as having row-major or column-major layout, using the layout identifiers row_major or column_major. Note that this does not affect how GLSL uses the matrices; this is simply about how the matrix uniforms are stored in the buffer object.

The default layout types are shared and column_major. If you wish to change the defaults, you can use the following syntax:

layout(std140) uniform;

From this point forward in the shader, the std140 layout will be the default for uniform blocks.

Here are examples of laying out specific uniform blocks:

layout(std140) uniform FirstBlock
{
  vec3 first;
  mat4 second; //Uses the default, which is column_major layout.
};

layout(row_major) uniform; //Row major is now the default for matrices.

//No need for the layout qualifier; the defaults will be used for this block. It will be row_major and shared.
uniform SecondBlock
{
  mat4 myFirst;            //Uses the default, which is now row_major layout.
  layout(column_major) mat4 mySecond;    //This particular matrix uses column_major.
};

Version 4.20 binding

The uniform block binding index can be set through GLSL, using GLSL version 4.20, or if ARB_shading_language_420pack is defined. This is done with the binding layout qualifier:

#version 420
//#extension GL_ARB_shading_language_420pack: enable  Use for GLSL versions before 420.

layout(std140, binding=5) uniform SomeBlock
{
  ...
};

This is the equivalent of calling glUniformBlockBinding with 5 for that uniform block. This only sets the initial block binding; you may change the block binding after linking the program.

OpenGL Usage

Buffer objects are associated with a program's uniform block similarly to the way that texture objects are associated with sampler uniforms. The context has GL_MAX_UNIFORM_BUFFER_BINDINGS binding locations for uniform buffers. This value is usually the sum of the maximum uniform block count for all 3 possible stages. This list of uniform binding locations is analogus to the set of texture image unit binding points.

Each active uniform block in GLSL has a corresponding active uniform block in the linked program. You can get the uniform block index (similar to a uniform location) with this function:

 GLuint glGetUniformBlockIndex( GLuint program, const char *uniformBlockName );

The uniformBlockName is the name of the uniform block, not the name of the GLSL scope for the uniform. This function returns GL_INVALID_INDEX if the block name could not be found.

The uniform block index is used to set what uniform buffer binding location that this uniform block uses. You can call this function to associate this uniform block with a uniform buffer binding location:

 void glUniformBlockBinding( GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding );

This caused the uniformBlockIndex index in the program to be bound to the uniform buffer binding location uniformBlockBinding.

Once the program is bound to the context, all you need to do is bind the appropriate uniform buffer object(s) to those binding locations using glBindBufferRange or glBindBufferBase, as appropriate.

Layout queries

If you did not use the std140 layout for a block, you will need to query the byte offset for each uniform within the block. The OpenGL specification explains the storage of each of the basic types, but not the alignment between types. Struct members, just like regular uniforms, each have a separate offset that must be individually queried.

Retrieving this information uses the standard GLSL uniform querying API.

To get the byte offset from the beginning of the block, use the GL_UNIFORM_OFFSET enum.

Uniform arrays in a buffer are stored sequentially. But they are not guaranteed to be tightly packed. Therefore, for any array uniforms in a block, you must query a stride value with GL_UNIFORM_ARRAY_STRIDE. This stride is the byte distance from the beginning of one element to the beginning of the next.

Vector elements are stored sequentially and tightly packed. How matrices are stored depends on the row/column major status of the matrix. A row major matrix is stored as an array of row vectors, with an array length of the number of columns in the matrix. A column major matrix is stored as an array of column vectors, with an array length of the number of columns in the matrix.

Recall that the row/column major status of the matrix's layout only affects the in-memory layout of the matrices. It changes nothing about how GLSL uses the matrix data. So a mat4x2 is a 4-column, 2-row matrix even if it is defined with row-major as its layout.

Therefore, a column-major mat4x2 is stored in a buffer object as an array of 2 vec4's. A row-major mat4x2 is stored as an array of 4 vec2's.

The storage for vector elements is sequential and tightly packed, as previously mentioned. However, the spacing between vector elements of a matrix is not. It is a value queried with GL_UNIFORM_MATRIX_STRIDE. This stride is the byte distance from the beginning of one vector to the beginning of the next.

Again, the querying is not necessary when using the std140 layout for a uniform block. There, these values are well-defined by the specification. Section 2.11.4 of the OpenGL 3.2 core specification describes this layout in great detail. You can design C structs that obey the layout conventions.

Data storage

If you bind a uniform buffer with glBindBufferRange, the offset field of that parameter must be a multiple of GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT (this is a global value, not a per program or per uniform one). Thus, if you want to put the data for multiple uniform blocks in a single buffer object, you must make sure that the data for each matches this alignment.

Legacy

An older form of this functionality exists through the GL_EXT_bindable_uniform. This extension differs in many substantial ways. Essentially, it is a way of providing buffer object storage for a single uniform.

The "single uniform" is allowed to be a struct, array, or array of structs rather than just a single basic type. So one could store multiple component members easily enough.

This extension only provides storage for this uniform. It does not expose layout. Where UBO requires the user to use standard buffer object manipulation to fill the buffer object, bindable uniform uses the same glUniform API to change the values in the bound uniform.

This also means that sharing buffers is not guaranteed. Implementations can still optimize away unused uniforms, so you cannot guarantee that you can share the same buffer object between different programs. The fact that you have to use the program object API to change the buffer object's data also means that you have to use a program object to change the stored uniform data.

Since the same hardware that supports GL_EXT_bindable_uniform is just as capable of supporting GL_ARB_uniform_buffer_object, and UBO is a core extension, you are advised to prefer UBOs over bindable uniform. This is only problematic on Mac OSX, because (at the time this was written), that platform exposes bindable uniform for appropriate hardware but not UBO or any other GL 3.x extensions.