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:
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.
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.
Original article (french)
– Buffers GPU: Introduction aux Shader Storage Buffers Objects d’OpenGL 4.3