In addition ot the read-only access to buffer object that is provided by uniform blocks, buffer objects can also be used for general storage from shaders using shader storage blocks.

difference tetween a uniform block
your shader can write into the shader storage block
and can even perform atomic operations on members of a shader storage block.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#version 430 core

struct my_structure
{
int pea;
int carrot;
vec4 potato;
};

layout (binding = 0, std430) buffer my_storage_block
{
vec4 foo;
vec3 bar;
int baz[24];
my_structure veggies;
};

The members of a shader storage block can be referred to just as any other variable.

Atomic Memory Operations

An atomic operation is a sequence of a read from memory potentially followed by a write to memory that must be uninterrupted for the result to be correct.


Atomic Operations on Shader Storage Blocks:

  • atomicAdd(mem, data)
  • atomicAnd(mem, data)
  • atomicOr(mem, data)
  • atomicXor(mem, data)
  • atomicMin(mem, data)
  • atomicMax(mem, data)
  • atomicExchange(mem, data)
  • atomicCompSwap(mem, comp, data)

Synchronizing Access to Memory

Memory hazards fall roughly into three categories:

  • A Read-After-Write(RAW)

    the read ends up being executed before the write is complete

  • A Write-After-Write(WAW)

    write to the same memory location twice in a row
    you might expect that whatever data was written last would overwrite the data written first and be the values that end up staying in memory.

  • A Write-After-Read(WAR)

    only occurs in parallel processing systems
    performs a write to memory after another thread believes that it has written to memory

Using Barriers in Your Application

1
void glMemoryBarrier(GLbitfield barriers);
  • barriers which allows you to specify which of OpenGL’s memory subsystems shoudl obey the barrier and which ones are free to ignore it and continue as they would have.
Value Mean
GL_ALL_BARRIER_BITS synchronize everything
GL_SHADER_STORAGE_BARRIER_BIT
GL_UNIFORM_BARRIER_BIT
GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT

The key to remember about glMemoryBarrier() is that the items included in barriers are the destination subsystems and that the mechanism by which you update the data isn’t relevat

Using Barriers in Your Shaders

Just as you can insert memory barriers in your application’s code to control the ordering of memory accesses performed by your shaders relative to your application, you can also insert barriers into your shaders to stop OpenGL from reading or writing memory in some order other thatn what your shader cdoe says.

The basic memory barrier function in GLSL :

1
void memoryBarrier();

If you call memoryBarrier() from your shader code, any memory reads or wirtes that you might ahve performed will complete before the function returns.

Atomic Counters

Atomic counters atre a special type of variable that represents storage that is shared across multiple shader invocations. This storage is backed by a buffer object, and functions are provided in GLSL to increment and decrement the values stored in the buffer. What is special about these operations is that they a atomic

If two shader invocations increment the same counter at the same time, OpenGL will make them take turns.


1
2
3
layout (binding = 0) uniform atomic_uint my_variable;

layout (binding = 3, offset = 8) uniform atomic_uint my_variable;
1
2
3
4
5
6
7
GLuint buf;
glGenBuffers(1, &buf);

glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buf);
glBufferData(GL_ATOMIC_COUNTER_BUFFER, 16 * sizeof(GLuint));

glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 3, buf);
1
2
3
4
5
6
7
8
9
10
11
12
13
glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buf);

glBUfferSubData(GL_ATOMIC_COUNTER_BUFFER, 2 * sizeof(GLuint), sizeof(GLuint), &size)

GLuint * data = (GLuint *)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, 16 * sizeof(GLuint),
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);

data[2] = 0;

glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);

glClearBufferSubData(GL_ATOMIC_COUNTER_BUFFER, GL_R32UI, 2 * sizeof(GLuint), sizeof(GLuint),
GL_RED_INTEGER, GL_UNSIGNED_INT, &zero);

  • to increment an atomic counter
1
uint atomicCounterIncrement(atomic_uint c);
  1. read the current value of the atomic counter
  2. adds one to it, write the new value back
  3. return the origin value it read
  • to decrement an atomic counter
1
uint atomicCounterDecrement(atomic_uint c);
  1. reads the current value of the atomic counter
  2. subtracts one from it, writes the value back
  3. return the new value

notice that this is the opposite of atomicCounterIncrement

  • simply get the value of an atomic counter
1
uint atomicCounter(atomic_uint c);
  1. return the value

Turn off writing to the framebuffer:

1
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

Trun on framebuffer writes:

1
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

Synchronizing Access to Atomic Counters

glMemoryBarrier() function supports a bit specifically for synchronizing access to atomic counters:

1
glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT)