Precise vs Invariant Qualifier in GLSL
In OpenGL Shading Language (GLSL), both the invariant
and precise qualifiers ensure numerical consistency to
eliminate visual artifacts, but they address different stages of the
compilation and execution pipeline. The invariant qualifier
guarantees that identical output calculations across different shader
programs yield the exact same bit-level results to prevent multi-pass
artifacts like Z-fighting. In contrast, the precise
qualifier disables specific compiler math optimizations, such as fused
multiply-add (FMA) contraction and algebraic reassociation, to ensure
consistent arithmetic evaluation order within shaders, primarily
preventing cracks along shared geometry edges in tessellation.
The Invariant Qualifier
The invariant qualifier is designed to solve multi-pass
rendering artifacts. In multi-pass techniques, geometry is rendered
multiple times using different shader programs. If vertex positions or
depth values vary by even a single bit between passes, depth testing can
fail, causing severe flickering known as Z-fighting.
How It Works
- Cross-Shader Guarantee: When an output variable is
declared
invariant, the GLSL compiler guarantees that if two different shader programs compute that variable using identical expressions, inputs, and state, the resulting values will be bit-for-bit identical. - Scope: It applies only to shader outputs (such as
gl_Positionor customoutvariables). - Compiler Behavior: The driver's shader compiler is restricted from applying optimizations that would produce different machine instructions for the tagged output in one shader compared to another shader with the same computation.
Syntax Example
// Make standard position invariant
invariant gl_Position;
// Or declare a custom output invariant
invariant out vec4 vColor;You can also apply the invariant behavior globally across an entire
shader stage using the preprocessor directive
#pragma STDGL invariant(all), though this can heavily
degrade performance by disabling optimizations globally.
The Precise Qualifier
Introduced in GLSL 4.00 (and via the GL_ARB_gpu_shader5
extension), the precise qualifier addresses floating-point
non-determinism caused by aggressive arithmetic optimizations. It is
most commonly used in tessellation evaluation shaders to prevent cracks
between adjacent patches.
How It Works
- Disables Algebraic Reassociation: Compilers often
rearrange mathematical expressions (e.g., treating
(a + b) + casa + (b + c)) to improve instruction pipelining. Because floating-point arithmetic is not strictly associative, this changes the result.preciseforces the compiler to evaluate expressions strictly according to the operator precedence and grouping defined in the source code. - Prevents Fused Multiply-Add (FMA) Contraction:
Modern GPUs often combine a multiplication and addition into a single
fma(a, b, c)instruction. While faster and typically more accurate, an FMA operation has different rounding characteristics than separate multiply and add instructions.preciseprevents implicit conversion to FMA unless explicitly called via thefma()built-in function. - Scope: It can be applied to local variables,
function return values, function parameters, and shader outputs. Any
expression contributing to the value of a
precisevariable inherits thepreciseevaluation rules.
Syntax Example
precise out vec3 tePosition;
void main() {
precise float a = b * c + d; // Guaranteed not to use FMA contraction
tePosition = evaluateEdge(a);
}Key Differences
| Feature | invariant |
precise |
|---|---|---|
| Primary Goal | Bit-identical output values across separate shader binaries. | Strict evaluation order and rounding rules within calculations. |
| Common Use Case | Preventing Z-fighting in multi-pass rendering. | Preventing mesh cracking on shared edges in tessellation. |
| Target Variables | Shader stage outputs (e.g.,
gl_Position, out variables). |
Any variable (local, parameter, return value, or output). |
| Compiler Action | Ensures identical generated code across programs for the same logic. | Disables FMA contractions and algebraic reordering/reassociation. |
| Introduced In | Early GLSL versions (OpenGL 1.20 / ES 1.0). | GLSL 4.00 /
GL_ARB_gpu_shader5. |
Summary
Use invariant when you have two separate shader programs
that must output the exact same coordinates or color components under
the same input conditions. Use precise within a single
shader when you need fine-grained control over floating-point arithmetic
order and rounding behavior to ensure boundary points evaluate
identically regardless of geometric orientation.