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:

  1. Reads the existing floating-point value currently stored at mem.
  2. Computes the sum of that value and data.
  3. Writes the resulting sum back to mem.
  4. Returns the original value that was stored in mem prior 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 : enable

For 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.