GLSL imageAtomicExchange for Storage Images
The imageAtomicExchange function in GLSL is an atomic
built-in function designed to safely replace a texel's value in a
storage image while simultaneously returning the original value that was
stored there. In highly concurrent GPU environments where thousands of
shader invocations can access the same memory address at the same time,
this operation prevents data races and ensures deterministic execution.
This article explains the purpose, mechanics, and common use cases of
imageAtomicExchange when working with OpenGL and Vulkan
compute or fragment shaders.
The Purpose of Atomic Operations
In standard image operations, performing a read followed by a write
(such as imageLoad followed by imageStore) is
non-atomic. If multiple shader threads attempt to read and update the
same texel simultaneously, they can overwrite each other's changes,
leading to race conditions and corrupt memory states.
imageAtomicExchange solves this by executing a
read-modify-write sequence as a single, uninterrupted transaction at the
hardware level. No other thread can read or modify the target texel
during this operation.
How imageAtomicExchange Works
The function replaces the value at a specified texel coordinate with a new value and returns the value that existed immediately before the swap:
uint imageAtomicExchange(layout(r32ui) uimage2D image, ivec2 P, uint data);
int imageAtomicExchange(layout(r32i) iimage2D image, ivec2 P, int data);image: The storage image variable, typically qualified withcoherentto guarantee memory visibility across invocations.P: The coordinate of the texel being addressed (e.g.,ivec2for 2D images,ivec3for 3D images).data: The new value to be stored into the image texel.- Return Value: The old value that resided at
coordinate
Pbefore the operation took place.
Key Use Cases
1. Order-Independent Transparency (OIT) Linked Lists
The most common application of imageAtomicExchange is
constructing per-pixel linked lists for order-independent
transparency.
- A 2D storage image holds the "head pointer" (an index into a separate buffer) for each pixel on the screen.
- When a fragment is processed, it allocates an element in a global node buffer.
- The fragment shader calls
imageAtomicExchange(headPointersImage, pixelCoord, newFragmentIndex). - The returned value is the previous head pointer, which the shader immediately saves as the "next" pointer in its newly allocated node. This creates an unbroken singly-linked list for each pixel without needing complex locking mechanisms.
2. GPU Spinlocks and Mutual Exclusion
While generally avoided when possible due to performance
considerations, imageAtomicExchange can be used to
implement lightweight spinlocks or mutexes on the GPU:
- An image texel serves as a lock flag (e.g.,
0for unlocked,1for locked). - An invocation calls
imageAtomicExchange(lockImage, coord, 1). - If the return value is
0, the invocation acquired the lock. If the return value is1, another thread holds the lock, prompting the thread to wait and retry.
3. State Tracking and Flag Clearing
When coordinating multi-pass algorithms,
imageAtomicExchange can atomically read a status flag and
reset it back to zero or an initial state in a single step, preventing
redundant work from concurrent threads.
Technical Requirements
- Supported Formats: Atomic operations on storage
images are strictly restricted to 32-bit single-component integer
formats (
r32uiandr32i). Certain extensions (such asGL_EXT_shader_atomic_float) can enable floating-point atomics, butimageAtomicExchangeprimarily targets unsigned and signed integers. - Memory Coherency: Storage images modified through
atomic operations should generally use the
coherentmemory qualifier to ensure that changes made by one shader invocation become visible to other invocations. Memory barriers, such asmemoryBarrierImage(), are also required to serialize memory operations across distinct dispatch or draw phases.