Using atomicAdd with Floats in Modern GLSL
In modern GLSL, the atomicAdd function operating on
floating-point memory variables performs an atomic, thread-safe
read-modify-write addition directly on shared memory or Shader Storage
Buffer Objects (SSBOs). This article covers how floating-point
atomicAdd operates, the extensions required to unlock it,
its primary rendering and compute use cases, and the non-deterministic
precision behaviors developers must account for when using it.
Core Function and Mechanics
By default, core GLSL specifications only mandate atomic operations
for integer types (int and uint). When
extended to floating-point types, atomicAdd enables
multiple shader invocations (threads) across different workgroups or
fragments to safely add a 32-bit (or 64-bit) floating-point value to a
memory location without introducing race conditions.
The function signature generally follows this structure:
float atomicAdd(inout float mem, float data);When invoked, the GPU hardware performs the following steps as a single, uninterruptible transaction:
- Reads the existing floating-point value currently stored at
mem. - Computes the sum of that value and
data. - Writes the resulting sum back to
mem. - Returns the original value that was stored in
memprior to the addition.
Without atomic guarantees, concurrent threads reading and writing to the same memory address simultaneously would overwrite each other, causing lost updates and data corruption.
Enabling Floating-Point Atomics
Because standard GLSL does not natively include floating-point
atomics in its core specification, using atomicAdd with
float types requires modern driver support and specific
extensions. In OpenGL and Vulkan GLSL environments, you must explicitly
enable the corresponding extension at the top of your shader:
#version 460
#extension GL_EXT_shader_atomic_float : enableFor target hardware with older vendor-specific support,
GL_NV_shader_atomic_float provides similar functionality on
NVIDIA hardware. Under Vulkan, the driver must also support and enable
the VK_EXT_shader_atomic_float feature flag during device
creation.
Common Use Cases
Floating-point atomicAdd is widely utilized in
high-performance compute and graphics tasks where multiple parallel
threads contribute continuously to shared accumulations:
- Order-Independent Transparency (OIT): Accumulating weighted color and alpha values into a shared full-screen buffer without pre-sorting geometry.
- Global Illumination and Light Bouncing: Accumulating light energy, photon radiance, or path-traced samples into spatial grids or screen-space accumulation buffers.
- Physics and Particle Simulations: Splatting particle masses, densities, or velocities onto continuous Eulerian simulation grids.
- Scientific Computing and Histograms: Computing continuous distributions where bin values represent floating-point weights rather than discrete integer counts.
Key Considerations and Limitations
While floating-point atomics solve concurrency conflicts, they introduce unique challenges:
- Non-Deterministic Rounding: Floating-point addition according to IEEE 754 standards is commutative (\(A + B = B + A\)), but it is not associative (\((A + B) + C \neq A + (B + C)\)). Because thread scheduling order is unpredictable across different frames or runs, the order of additions will vary. This variance causes minute rounding differences that can produce subtle visual noise or non-deterministic simulation states.
- Hardware Serialization: If many threads within the same warp or across compute units target the exact same memory address simultaneously, hardware memory controllers serialize the operations. High contention on a single address can substantially degrade shader throughput.