How Does GLSL fma Perform Fused Multiply-Add?
The fma function in OpenGL Shading Language (GLSL)
computes the fused multiply-add operation (a * b) + c as a
single floating-point operation. By performing the multiplication and
addition simultaneously with only a single rounding step at the end,
fma delivers higher numerical accuracy, prevents
intermediate underflow or overflow, and often executes faster on modern
graphics hardware. This article examines the internal mechanics of
fma, its hardware support, accuracy advantages over
separate operations, and practical use cases in shader programming.
Understanding the Fused Multiply-Add Concept
In standard floating-point arithmetic, computing an expression like
a * b + c requires two distinct steps. The GPU first
multiplies a and b, rounds the intermediate
product to fit the target floating-point precision (such as IEEE 754
32-bit float), and then adds c to this rounded product
before rounding the final result a second time.
The fma (Fused Multiply-Add) instruction eliminates the
intermediate rounding step. The calculation \(a \times b\) is computed with unbounded (or
double) internal precision, and \(c\)
is added to that precise product before a single, final rounding is
applied to produce the result.
// Standard separate operations (two roundings)
float resultStandard = a * b + c;
// Fused Multiply-Add (single rounding)
float resultFMA = fma(a, b, c);Syntax and Precision Support in GLSL
The fma function is available in GLSL starting with GLSL
4.00 (and via extensions like GL_ARB_gpu_shader5 in earlier
versions). It supports scalars and vector types across single-precision
(float) and double-precision (double)
types:
genType fma(genType a, genType b, genType c);
genDType fma(genDType a, genDType b, genDType c);When called on vector types, fma operates component-wise
across all elements of the vectors simultaneously.
Key Benefits of Using fma
1. Superior Numerical Precision
Because the intermediate product is not rounded, fma
avoids catastrophic cancellation—a common issue in numerical analysis
where subtracting two nearly equal numbers causes severe loss of
precision.
2. Elimination of Intermediate Underflow/Overflow
In traditional two-step evaluation, the product a * b
might exceed the dynamic range of the float format and overflow to
infinity, or drop below the minimum normal range and underflow to zero,
even if adding c would have brought the final number back
into representable range. fma preserves intermediate range,
computing valid results where separate operations fail.
3. Dedicated Hardware Execution
Modern GPUs include hardware-level Fused Multiply-Add units in their
Execution Units (EUs) / Streaming Multiprocessors (SMs). On many
architectures, an fma instruction executes in a single
clock cycle—the same throughput as an individual multiplication or
addition instruction.
Comparison: fma vs. Standard MAD
Older graphics hardware often utilized a Multiply-Add
(MAD) instruction. Unlike fma,
MAD does not guarantee a single rounding step and typically
rounds the intermediate product. In GLSL, using the explicit
fma() built-in guarantees standard IEEE 754-compliant fused
multiply-accumulate semantics if the underlying hardware supports
it.
| Feature | Standard (a * b) + c |
Legacy MAD |
GLSL fma(a, b, c) |
|---|---|---|---|
| Rounding Steps | 2 | 2 (implementation-dependent) | 1 (strictly single rounding) |
| Intermediate Precision | Truncated to type | Truncated to type | Infinite / Extended |
| Hardware Throughput | 2 operations (or unoptimized) | 1 cycle (approximate) | 1 cycle (exact IEEE 754) |
| Error Bound | Up to 1.0 ULP | Up to 1.0 ULP | Maximum 0.5 ULP |
Practical Applications in Shading and Compute
- Ray Tracing & Bounding Volume Hierarchies (BVH): Ray-box and ray-triangle intersection routines require tight numerical tolerances to avoid light-leaking and self-intersection artifacts.
- Matrix Transformations: Accumulating dot products and projecting coordinate frames benefit directly from reduced rounding drift.
- Polynomial & Spline Evaluations: Horner's
method for polynomial evaluation
(
p(x) = (...((a_n * x + a_{n-1}) * x + a_{n-2})...)) maps directly to chainedfmaoperations. - Fractal Generation: Deep-zoom fractals (such as Mandelbrot iterations) maintain visual fidelity at deeper zoom levels without transitioning to software emulated high-precision routines.