GLSL memoryBarrier Function Guarantees Explained

In OpenGL Shading Language (GLSL), the memoryBarrier() function acts as a comprehensive ordering control for memory transactions issued by a single shader invocation. This article details the specific guarantees provided by memoryBarrier() across all supported GLSL memory spaces, how it affects the ordering of memory reads and writes, its interaction with the coherent qualifier, and how it differs from execution synchronization barriers like barrier().

The Core Guarantee: Ordering of Memory Transactions

The primary guarantee of memoryBarrier() is compile-time and run-time ordering, not execution synchronization. When an invocation calls memoryBarrier(), the GLSL specification guarantees that:

  1. All memory writes issued by that invocation prior to calling memoryBarrier() will complete and be committed to memory before any memory reads or writes issued after the call to memoryBarrier() are executed.
  2. Memory operations cannot be reordered by the compiler or the GPU memory architecture across the barrier boundary.

memoryBarrier() does not stall or pause other shader invocations, nor does it guarantee that other invocations have reached the same point in execution. It only controls the relative order of memory operations initiated by the calling invocation.

Memory Spaces Covered by memoryBarrier()

Calling memoryBarrier() serves as an umbrella operation. It provides the combined ordering guarantees of all specialized GLSL memory barriers in a single call, covering the following memory spaces:

  • Buffer Variables (SSBOs): Guarantees ordering for all reads and writes to Shader Storage Buffer Objects (equivalent to memoryBarrierBuffer()).
  • Image Variables: Guarantees ordering for memory operations initiated through image functions such as imageStore(), imageLoad(), and imageAtomic*() (equivalent to memoryBarrierImage()).
  • Atomic Counters: Guarantees ordering for atomic counter operations such as atomicCounterIncrement() and atomicCounterDecrement() (equivalent to memoryBarrierAtomicCounter()).
  • Shared Variables: In compute shaders, it guarantees ordering for accesses to variables declared with the shared storage qualifier (equivalent to memoryBarrierShared()).

Because it spans all these spaces simultaneously, a write to an SSBO before the barrier is guaranteed to complete before an image read after the barrier is executed, preventing cross-resource race conditions within the invocation.

Inter-Invocation Visibility and the coherent Qualifier

For writes ordered by memoryBarrier() to become visible to other invocations, the targeted resources must be declared with the coherent memory qualifier (which is the default for atomic counters).

  • Without coherent: Writes may remain in local invocation caches, and subsequent reads by other invocations may read stale cached values, regardless of memory barriers.
  • With coherent: memoryBarrier() forces pending writes from the calling invocation out of local caches toward shared visibility, and ensures that subsequent reads by the invocation invalidate stale cache entries to observe newly committed data.

memoryBarrier() vs. barrier()

It is vital to distinguish memoryBarrier() from barrier():

  • memoryBarrier(): Affects only memory access ordering within the calling invocation. It can be called inside conditional code and non-uniform control flow without causing undefined behavior or GPU deadlocks.
  • barrier(): An execution barrier for compute shaders and tessellation control shaders. It forces all invocations in the local workgroup to pause until all invocations reach the barrier, while also ordering shared and memory operations. Unlike memoryBarrier(), barrier() must be encountered uniformly by all invocations in a workgroup.