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 uint

Without 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 signed int performs an arithmetic right shift (preserving the sign bit by sign-extension). Shifting a uint performs a logical right shift (filling high-order bits with zeroes). Using u or U ensures 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. Specifying u guarantees 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.