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:

  1. Read: The memory controller reads the current value stored at the specified texel coordinates.
  2. Compare: The controller compares this current value against the compare parameter provided by the calling invocation.
  3. Conditional Write:
    • If the current value matches compare, the memory controller overwrites the texel value with data.
    • If the current value does not match compare, the texel value remains untouched.

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().