GLSL Binary Additive Operations Evaluation Order
In the OpenGL Shading Language (GLSL), the evaluation order of
operand expressions in binary additive operations (+ and
-) is completely unspecified. Although the GLSL
specification strictly dictates left-to-right associativity to define
how operations are grouped mathematically, it does not guarantee whether
the left-hand or right-hand operand expression executes first.
Consequently, relying on a specific execution sequence across additive
operands—particularly when expressions involve side effects—results in
non-portable and unpredictable behavior across different GPU drivers and
hardware architectures.
Associativity vs. Evaluation Order
A common point of confusion in shader programming is the distinction between operator associativity and operand evaluation order:
- Associativity defines the syntactic grouping of
operations. Binary addition and subtraction in GLSL are
left-associative. This means an expression such as
a + b + cis parsed and grouped as(a + b) + c. - Evaluation Order defines the temporal sequence in
which the subexpressions themselves are computed. In an expression like
exprA() + exprB(), associativity dictates that the result ofexprA()is added toexprB(), but it does not mandate whetherexprA()orexprB()executes first.
The GLSL Specification Stance
According to the official GLSL specification (specifically within the section detailing expressions and operators):
- Unspecified Sequence: The order of evaluation of expressions is not fully defined. Compilers are free to evaluate the left operand before the right operand, the right operand before the left operand, or both concurrently if hardware resources allow.
- Compiler Reordering and Optimization: Shading
language compilers possess significant leeway to reorder, fold, or
vectorize arithmetic expressions, provided the mathematical outcome
adheres to IEEE floating-point and GLSL precision qualifiers. Compilers
can rearrange expressions algebraically unless strict floating-point
rules or qualifiers (such as
invariantorprecise) restrict them. - No Short-Circuiting: Unlike logical operators such
as
&&and||, binary additive operators do not introduce sequence points or short-circuit behavior. Both operands are evaluated without any sequencing guarantees between them.
Side Effects and Undefined Behavior
Because operand evaluation order is unspecified, expressions that
produce side effects can lead to critical bugs. A side effect occurs
when an expression modifies a variable, such as through post-increment
operations (x++), assignment within subexpressions, or
calls to functions with out or inout
parameters that modify shared or global state.
Consider the following example:
int x = 1;
int result = (x += 2) + (x * 3);Depending on the GPU vendor's compiler:
- If the left operand
(x += 2)is evaluated first,xbecomes3, and the right operand evaluates to3 * 3 = 9, resulting in3 + 9 = 12. - If the right operand
(x * 3)is evaluated first, it computes1 * 3 = 3, thenxbecomes3, resulting in3 + 3 = 6.
Both outputs are valid according to the GLSL specification, making the code non-deterministic.
Writing Robust GLSL Expressions
To prevent driver-specific compilation differences and hard-to-debug graphical artifacts:
- Isolate Side Effects: Never modify variables within the same additive expression where they are also read.
- Separate Expressions into Explicit Statements: When
using functions with
outorinoutparameters, invoke them as standalone statements on separate lines before combining their results with additive operators. - Do Not Rely on Subexpression Ordering: Treat all operand expressions in arithmetic operations as if they occur simultaneously or in arbitrary order.