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_Position or custom out variables).
  • 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) + c as a + (b + c)) to improve instruction pipelining. Because floating-point arithmetic is not strictly associative, this changes the result. precise forces 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. precise prevents implicit conversion to FMA unless explicitly called via the fma() 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 precise variable inherits the precise evaluation 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.