GPU Buffers: Introduction to OpenGL 4.3 Shader Storage Buffers Objects

Tutorial: OpenGL 4.3 shader storage buffers

Article index:
1 – Introduction
2 – OpenGL Code
3 – Demo and References

1 – Introduction


In this tutorial, we meet Uniform Buffer Objects (or UBO). To sum up a little bit, UBOs are read-only GPU-accessible memory zones for a GLSL shader. The size of an UBO is somewhat limited: 64KB for AMD and NVIDIA GPUs and 16KB for Intel ones.

Limited size, read-only mode, humm… With all modern graphics cards and their tons of gigabytes of dedicated vram, we can do better than 64KB for a GPU buffer.

Shader Storage Buffer Objects (or SSBO) can be seen as unlocked UBOs: they are accessible in reading AND writing in a GLSL shader and their size seems to be limited by the amount of GPU memory available. On a GeForce GTX 660, it’s possible to allocate a 2GB of VRAM for a SSBO. Yeah!

The only drawback of SSBOs is… Mac OS X. Mavericks, the last version of OS X, supports OpenGL 4.1 only. Then no SSBOs on OS X before at least a decade 😉

On Windows and Linux (with NVIDIA or AMD closed-source drivers), SSBOs are available for all OpenGL 4 capable GPUs.

The SSBO bible can be found here: GL_ARB_shader_storage_buffer_object.

2 – OpenGL Code

The management of SSBOs is very similar to the management of UBOs.

Let’s take again our C/C++ data structure, used in the article about UBOs:

struct shader_data_t
{
  float camera_position[4];
  float light_position[4];
  float light_diffuse[4];
} shader_data;

Creation and initialization of a SSBO:

GLuint ssbo = 0;
glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(shader_data), &shader_data, GL_DYNAMIC_COPY);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

SSBO update: we get the pointer on the GPU memory and we copy our data:

glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
GLvoid* p = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_WRITE_ONLY);
memcpy(p, &shader_data, sizeof(shader_data))
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);

Like for UBOs, there is an equivalent to uniform blocks in GLSL shaders. For SSBOs, we have a storage block in the shader. The storage block describes the data structure a shader can read from or write to:

#version 430
...
layout (std430, binding=2) buffer shader_data
{ 
  vec4 camera_position;
  vec4 light_position;
  vec4 light_diffuse;
};
...
void main()
{
  ...
}

The uniform keyword of uniform block is replaced by the buffer keyword that shows the read-write feature of the buffer.

Like with UBOs, OpenGL holds a binding point table in each rendering context. This table stores a kind a reference on each SSBO. For a GeForce GTX 660, this table is available with 96 entries:

OpenGL 4.3, shader storage buffers, binding points table for a GeForce GTX 660

With a GeForce GTX 660, each type of shader (vertex, fragment, geometry, tessellation and compute) can have up to 16 storage blocks.

To be able to read from or write to a SSBO, the following steps are required:

1 – find the storage block index:

GLunit block_index = 0;
block_index = glGetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "shader_data");

2 – connect the shader storage block to the SSBO: we tell the shader on which binding point it will find the SSBO. In our case, the SSBO is bound on the point 2:

GLuint ssbo_binding_point_index = 2;
glShaderStorageBlockBinding(program, block_index, ssbo_binding_point_index);

Actually this last step is not required: the binding point can be hard coded directly in the GLSL shader in the buffer layout:

layout (std430, binding=2) buffer shader_data
{
  ...
}

glShaderStorageBlockBinding() allows to dynamically connect the shader storage block and the SSBO. The following line of code allows to connect the shader storage block to the SSBO bound on point 80:

glShaderStorageBlockBinding(program, block_index, 80);

And like with UBOs, the binding of a SSBO on a particular binding point is done with:

GLuint binding_point_index = 80;
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding_point_index, ssbo);

Pour conclude the article, here are some limits we can retrieve with glGetIntegerv():

NVIDIA GeForce GTX 660 limits (R337.50):
GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS = 96
GL_MAX_SHADER_STORAGE_BLOCK_SIZE = 2147483647
GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = 16
GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS = 16
GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS = 16
GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS = 16
GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS = 16
GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS = 16
GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS = 96
AMD Radeon HD 7970 limits (Catalyst 14.4):
GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS = 16
GL_MAX_SHADER_STORAGE_BLOCK_SIZE = 16777216
GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = 16
GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS = 16
GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS = 16
GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS = 16
GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS = 16
GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS = 16
GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS = 16

I hope I didn’t wrote too many mistakes. I quickly covered SSBO and I will try to write another article about those strange layout qualifiers we saw here and in UBO article: sdt140 and std430. They are actually very important if you want to update some parts of the buffer objects and not the whole buffer.

3 – Demo and References

I coded two demos with GLSL Hacker that make use of SSBOs. The first demo uses a SSBO to pass camera matrices to the shader. This demo is available in the host_api/gl-430-arb-shader-storage-buffer-object/ folder of the code sample pack.

GLSL Hacker demo: OpenGL 4.3 shader storage buffer demo

The second demo shows an example of SSBOs in read/write mode with particles. A compute shader (an OpenGL 4.3 feature) reads the position of a particle from a SSBO, updates it and writes the new position in the same SSBO. The SSBO is then used as a vertex source for particles rendering. The demo is available in the host_api/gl-430-arb-compute-shader_Particles_SSBO/ folder of the code sample pack.

Both demos use a new feature of GLSL Hacker 0.7.0+: gh_gpu_buffer, a new Lua/Python library. This low level lib allows to manages all kind of GPU buffers including uniform and shader storage buffers.

GLSL Hacker demo: OpenGL 4.3 compute shaders + SSBO

Original article (french)
Buffers GPU: Introduction aux Shader Storage Buffers Objects d’OpenGL 4.3