GLSL Integer Suffixes: How u and U Change Literal Types
In the OpenGL Shading Language (GLSL), integer literals default to
the signed 32-bit integer type (int). Appending the
lowercase u or uppercase U suffix directly
alters the scalar literal's compile-time type, transforming it into an
unsigned 32-bit integer (uint). This article covers how
this suffix functions within the GLSL grammar, why it is critical for
preventing type-mismatch compilation errors, and how it alters
arithmetic, bitwise logic, and function resolution in shader
pipelines.
Default Integer Typing vs. The Unsigned Suffix
When an integer literal is written without a suffix or decimal point
in GLSL (such as 10 or 0xFF), the compiler
interprets it as a signed 32-bit integer (int), bounded by
the range [-2,147,483,648, 2,147,483,647].
Appending u or U (such as 10u
or 0x80000000u) explicitly tells the compiler's lexer and
parser to treat the token as a uint. This shifts the range
of the literal to [0, 4,294,967,295]. If a literal value
exceeds 2,147,483,647 without the u or
U suffix, the compiler will trigger an integer overflow
error, because the value cannot fit into a standard signed 32-bit
integer.
Strict Type Enforcement and Implicit Conversions
GLSL is a strictly typed language. While later versions of the
specification introduced implicit conversions from int to
uint, earlier versions (prior to GLSL 4.00) or strict
profile configurations require explicit type matching.
Consider the following assignment:
uint index = 0; // May fail in GLSL versions lacking implicit promotion
uint index = 0u; // Guaranteed valid across all GLSL versions supporting uintWithout the u suffix, assigning an int
literal to a uint variable creates a type mismatch. Using
0u ensures the literal matches the variable's type directly
without relying on implicit widening or conversion rules.
Similarly, binary operations require matching operand types:
uint mask = 0xFFu;
uint result = mask & 1; // Error: bitwise AND between 'uint' and 'int'
uint result = mask & 1u; // Valid: both operands are 'uint'Impact on Function Overloading
GLSL supports function overloading based on exact parameter types. When a function has both signed and unsigned variants, the literal suffix dictates which implementation the compiler invokes:
void process(int value);
void process(uint value);
process(42); // Calls process(int)
process(42u); // Calls process(uint)Omitting the suffix calls the signed version. If only the
uint version exists and the GLSL version in use does not
support implicit conversion from int to uint,
passing an unsuffixed literal will result in a compile-time error.
Bitwise and Shift Operations
The data type of a literal dictates how bitwise shifts and arithmetic operations evaluate:
- Right Shift (
>>): Shifting a signedintperforms an arithmetic right shift (preserving the sign bit by sign-extension). Shifting auintperforms a logical right shift (filling high-order bits with zeroes). UsinguorUensures that bits shifted into the most significant positions are always zeroes. - Division and Modulo (
/,%): Signed integer division truncates toward zero and handles negative values differently than unsigned division. Specifyinguguarantees unsigned mathematical operations.
In summary, the u and U suffixes in GLSL
explicitly assign the uint scalar type to numeric literals.
This eliminates ambiguity in function overloading, ensures compatibility
with uint variables and expressions, enables representation
of values beyond \(2^{31}-1\), and
enforces unsigned semantics for bitwise and mathematical operations.