How Does GLSL Invariant Stop Z-Fighting?
In multi-pass rendering pipelines, subtle floating-point variations
across different shader programs can cause identical geometry to produce
slightly different depth coordinates, resulting in severe visual
artifacting known as Z-fighting. The invariant qualifier in
GLSL addresses this by instructing the shader compiler and GPU driver to
guarantee bit-for-bit identical evaluation of targeted output variables
across different shader objects. By enforcing exact computational
repeatability on gl_Position, developers can run depth
pre-passes, lighting passes, and post-effects using
GL_EQUAL depth tests without surface flicker or
non-deterministic depth test failures.
The Cause of Multi-Pass Depth Inconsistencies
Multi-pass rendering relies on drawing the exact same geometric mesh
multiple times across distinct shader programs. A standard pattern
involves rendering depth in an initial baseline pass, followed by
subsequent passes that compute forward lighting, ambient occlusion, or
volumetric effects. To avoid redundant depth writing and preserve
performance, secondary passes typically configure the depth function to
GL_EQUAL or GL_LEQUAL.
Even when separate vertex shaders execute mathematically identical matrix transformations—such as transforming an object-space vertex by identical model, view, and projection matrices—the resulting floating-point values can diverge:
- Instruction Reordering: Different surrounding shader code alters register pressure, prompting compiler optimizers to rearrange mathematical operations.
- Fused Multiply-Add (FMA) Optimizations: The
compiler might fuse a multiplication and addition into an
FMAinstruction in one shader while leaving them as distinct operations in another. - Algebraic Simplifications: Commutative and associative transformations that are algebraically equivalent in pure mathematics are not bit-identical in IEEE 754 floating-point arithmetic.
Because the depth buffer operates at high precision, a discrepancy of a single Unit in the Last Place (ULP) causes fragments to fail depth equality tests, producing noisy rendering artifacts and missing geometry.
How the Invariant Qualifier Enforces Bit-Exact Precision
The invariant qualifier acts as a binding contract with
the GLSL compiler. When an output variable—most commonly the built-in
gl_Position—is marked as invariant, the compiler is
restricted from applying optimizations that compromise deterministic
output.
Specifically, the compiler ensures:
- The exact sequence of machine instructions generated for computing the marked variable is consistent across separate shader binaries.
- Target calculations bypass aggressive floating-point contraction or tree-balancing that depends on variable lifetime or surrounding context.
- The generated output coordinate matches identically across draw calls, provided the source inputs and order of calculations are consistent.
Syntax and Implementation Patterns
GLSL allows applying the invariant qualifier either to
specific variables or globally across the entire compilation unit.
Selective Variable Invariance
The standard and most performant approach is to mark specific vertex outputs. You can declare invariance directly at output definition or retroactively declare built-in variables:
#version 330 core
// Retroactively marking the built-in position output as invariant
invariant gl_Position;
uniform mat4 u_ModelViewProjection;
layout(location = 0) in vec3 a_Position;
void main()
{
gl_Position = u_ModelViewProjection * vec4(a_Position, 1.0);
}This ensures that any other shader in the pipeline using identical
transformation logic for gl_Position will produce the exact
same depth value.
Global Invariance Pragma
GLSL also supports forcing invariance on all shader outputs via a preprocessor pragma:
#pragma STDGL invariant(all)While convenient, applying global invariance is generally discouraged. Forcing invariance across every color, normal, and texture coordinate disables global compiler optimizations, which can degrade execution efficiency.
Requirements for Guaranteed Determinism
The invariant qualifier prevents compiler-induced
non-determinism, but it cannot fix application-level discrepancies. For
invariance to successfully eliminate Z-fighting, the host application
must satisfy several criteria:
- Identical Expressions: The sequence of operations
used to compute the invariant variable must be identical in source code
across all participating shaders. Writing
P * (V * (M * v))in one shader and(P * V * M) * vin another breaks determinism regardless of the qualifier. - Identical Uniform Inputs: Uniform values used in the transformation must contain identical binary float data across passes.
- Matching Precision Qualifiers: In GLSL ES,
floating-point precision qualifiers (such as
highp) for all variables contributing to the invariant output must match exactly.
In modern OpenGL (GLSL 4.0+) and Vulkan (via SPIR-V), developers
often supplement or replace invariant with the
precise qualifier, which provides fine-grained control over
intermediate operation order, preventing FMA contraction on arbitrary
variable assignments.