GLSL Precise Qualifier: Disabling Optimizations
The precise qualifier in the OpenGL Shading Language
(GLSL) enforces strict evaluation rules on floating-point expressions to
guarantee consistent numerical results. By default, GPU optimizing
compilers aggressively transform math expressions to maximize
performance, often leading to slight rounding variations. This article
explains how the precise qualifier halts algebraic
reordering, prevents unwanted fused multiply-add contractions, and
ensures algorithmic invariance across different shaders and pipeline
stages.
Default Compiler Optimizations in GLSL
GPU compilers standardly treat floating-point arithmetic as if it follows the laws of pure algebra, assuming associativity and distributivity:
- Associativity:
(a + b) + cis treated as equivalent toa + (b + c). - Distributivity:
a * (b + c)is treated as equivalent toa * b + a * c.
In IEEE 754 floating-point arithmetic, these identities do not
strictly hold due to finite precision and intermediate rounding steps.
However, compilers frequently reorder these operations or merge separate
additions and multiplications into a single Fused Multiply-Add
(fma) instruction to minimize instruction count and
register pressure.
While advantageous for performance, this reordering introduces variations in the least significant bits of floating-point values depending on instruction scheduling.
How precise
Inhibits Reordering
Applying the precise qualifier to a variable or function
return value alters the compiler’s Intermediate Representation (IR)
generation and instruction-selection phases:
- Enforcing Strict Operator Associativity: The
compiler is prohibited from applying associative or distributive
transformations. The syntax tree of the expression is evaluated strictly
in the order dictated by operator precedence and parentheses. For
example, in the expression
r = (a + b) + c, the compiler cannot evaluateb + cfirst, even if doing so would reduce latency or reuse a cached value. - Preventing Unintended Fused Multiply-Add (FMA)
Operations: A standard multiplication followed by an addition
(
a * b + c) calculates an intermediate rounded product before addingc. Anfmainstruction computes(a * b) + cwith infinite precision before rounding once at the end. Because this yields a different bit-level result than two separate operations, theprecisequalifier forbids the automatic contraction of distinct multiply and add operations into anfma, unless the developer explicitly calls thefma()built-in function. - Backward Dependency Propagation: The restriction is
not limited merely to the final assignment. The
preciserule propagates backward through the entire expression graph contributing to the qualified variable. Any sub-expression that determines the value of aprecisetarget must also be evaluated strictly as written without algebraic reassociation.
Use Cases and Significance
The primary purpose of disabling these optimizations is invariance. In graphics pipelines, subtle differences in rounding can lead to visual artifacts:
- Tessellation and Seams: If two adjacent patches evaluate shared edge coordinates using expressions that are algebraically equivalent but scheduled differently by the compiler, cracks or gaps can appear along the boundary.
- Multi-Pass Rendering: Re-rendering geometry in separate passes (such as a depth pre-pass followed by a lighting pass) requires exact bit-level depth reproduction to prevent z-fighting.
By placing precise on key output variables, developers
force the compiler to generate identical machine code sequences for
common calculations, guaranteeing bit-exact invariance across distinct
shader stages at the cost of minor performance optimizations.