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.

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.

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:

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