How imageAtomicCompSwap Works in GLSL
The imageAtomicCompSwap function in the OpenGL Shading
Language (GLSL) provides a hardware-level atomic compare-and-swap (CAS)
operation for modifying image texels without race conditions. This
article covers the function's syntax, its execution flow at the memory
controller level, the return values it generates, and how it is used
within CAS loops to implement arbitrary atomic operations on textures in
compute and fragment shaders.
Function Syntax and Supported Formats
The imageAtomicCompSwap function performs an atomic
read-modify-write operation on an individual texel. Its signatures in
GLSL are defined as follows:
uint imageAtomicCompSwap(coherent IMAGE_TYPE image, ivec coordinate, uint compare, uint data);
int imageAtomicCompSwap(coherent IMAGE_TYPE image, ivec coordinate, int compare, int data);To use this function, the target image must be bound with an
atomically compatible internal format, typically r32ui
(unsigned 32-bit integer) or r32i (signed 32-bit integer).
The image variable declaration usually requires the
coherent memory qualifier to ensure writes are visible
across different shader invocations.
The Compare-and-Swap Mechanism
The compare-and-swap operation is executed atomically by the GPU's memory subsystem at the target texel's memory location. The operation follows a strict three-step sequence that cannot be interrupted by other shader invocations:
- Read: The memory controller reads the current value stored at the specified texel coordinates.
- Compare: The controller compares this current value
against the
compareparameter provided by the calling invocation. - Conditional Write:
- If the current value matches
compare, the memory controller overwrites the texel value withdata. - If the current value does not match
compare, the texel value remains untouched.
- If the current value matches
Regardless of whether the write succeeds or fails, the function
returns the original value that was stored in the texel before the
operation took place. The calling invocation determines whether the swap
was successful by testing if returnValue == compare.
Implementing CAS Loops for Complex Operations
Hardware-supported atomics on images are typically limited to basic
operations like addition, subtraction, bitwise logic, and min/max. When
an algorithm requires an unsupported atomic operation—such as atomic
floating-point addition or conditional
thresholding—imageAtomicCompSwap is executed inside a
loop.
In this pattern, the shader repeatedly attempts the update until it succeeds:
layout(r32ui, binding = 0) uniform coherent uimage2D targetImage;
void atomicCustomUpdate(ivec2 coords, uint targetAdjustment) {
// Read the initial state
uint expected = imageLoad(targetImage, coords).r;
while (true) {
// Compute the new value based on the expected current value
uint newValue = expected + targetAdjustment; // Or any complex operation
// Attempt to swap
uint actual = imageAtomicCompSwap(targetImage, coords, expected, newValue);
// If the value hasn't changed since the load, the swap succeeded
if (actual == expected) {
break;
}
// Another thread modified the texel first; retry using the updated value
expected = actual;
}
}Because imageAtomicCompSwap returns the actual value
read during the atomic step, failed attempts immediately yield the most
up-to-date value, reducing redundant read requests across the memory bus
during contention.
Memory Consistency Considerations
When coordinating shared image data among workgroups, the atomic
operation guarantees ordering only at the specific memory address being
modified. If other invocations need to read the updated texel through
non-atomic operations (such as standard imageLoad), you
must enforce execution and memory visibility by using
memoryBarrierImage() or
groupMemoryBarrier().