memoryBarrier vs groupMemoryBarrier in GLSL?

In GLSL compute and shader programming, understanding memory ordering is essential for avoiding race conditions. While both memoryBarrier() and groupMemoryBarrier() prevent the compiler and hardware from reordering memory access operations across synchronization boundaries, they differ in their operational scope and the types of memory they affect. This guide covers the key differences between these two built-in barrier functions, how they interact with cache hierarchies, and when to choose one over the other.

The Role of Memory Barriers in GLSL

Modern GPUs execute thousands of threads concurrently. To maximize throughput, the hardware and compiler frequently reorder memory load and store operations. When multiple shader invocations read from and write to shared storage, raw ordering cannot be assumed.

A memory barrier acts as an ordering constraint: all memory writes issued prior to the barrier function call must be committed and visible to the target invocations before any memory operations following the barrier can execute. Crucially, memory barriers in GLSL only order memory operations; they do not stall execution to synchronize thread timing unless paired with an execution barrier like barrier().

What is memoryBarrier()?

The memoryBarrier() function enforces memory ordering globally across all shader invocations across the entire GPU.

  • Memory Types Covered: Image variables (imageStore), shader storage buffer objects (SSBOs), and atomic counters.
  • Scope of Visibility: Global. It ensures that memory transactions issued before the call become visible to all other invocations on the device before operations after the call occur.
  • Shared Memory Handling: It does not order access to shared memory variables declared inside compute shaders.

Use memoryBarrier() when you are writing algorithms where invocations across different workgroups or different draw calls need to see updated buffer or texture writes in a specific order.

What is groupMemoryBarrier()?

The groupMemoryBarrier() function is designed specifically for compute shaders and restricts its synchronization scope to the local workgroup.

  • Memory Types Covered: All memory accesses performed by the invocation, including shared variables, SSBO buffer variables, image variables, and atomic counters.
  • Scope of Visibility: Local to the current workgroup. It guarantees that prior memory operations are visible to other invocations executing in the same workgroup before subsequent operations proceed.
  • Performance Impact: Because it only requires cache flushing and ordering relative to the local compute unit (workgroup), it can be substantially faster and less disruptive to GPU memory pipelines than a global barrier.

Key Differences Summary

Feature memoryBarrier() groupMemoryBarrier()
Visibility Scope Global (all workgroups across GPU) Local (current workgroup only)
Available Stages Compute and graphics stages Compute shaders only
Affects Shared Memory No Yes
Affects SSBOs / Images Yes Yes
Execution Sync No (ordering only) No (ordering only)

Pairing with Execution Barriers

A common misconception is that calling groupMemoryBarrier() halts thread execution until all threads in the workgroup reach that point. It only enforces the ordering of memory transactions issued by the calling thread.

To both guarantee memory visibility and synchronize execution timing across all invocations in a workgroup, you must use barrier():

// Write to shared or global memory
sharedData[gl_LocalInvocationIndex] = computedValue;

// Order memory writes and synchronize thread execution
barrier(); 

// Safely read values written by other invocations in the workgroup
vec4 neighborValue = sharedData[neighborIndex];

Inside a compute shader, calling barrier() internally enforces the equivalent of a groupMemoryBarrier() alongside thread execution synchronization.

Fine-Grained Alternatives

GLSL also provides specialized memory barriers when you only need to order specific memory resources:

  • memoryBarrierShared(): Orders only shared variable accesses within the workgroup.
  • memoryBarrierBuffer(): Orders only buffer variable accesses (SSBOs).
  • memoryBarrierImage(): Orders only image load and store operations.
  • memoryBarrierAtomicCounter(): Orders only atomic counter operations.

Selecting the most restrictive barrier suitable for your algorithm prevents unnecessary cache flushes and ensures optimal GPU pipeline efficiency.