GLSL Implicit Conversion Rules: Int, Uint, and Float

Modern OpenGL Shading Language (GLSL) version 4.00 and above—or systems supporting the GL_ARB_shader_implicit_conversions extension—allows automatic type promotion between basic numeric types. These implicit conversions eliminate the need for verbose explicit casting when mixing signed integers (int), unsigned integers (uint), and single-precision floating-point numbers (float). This article covers the hierarchy of implicit type conversions, the rules governing mixed-type expressions and assignments, and the limitations designed to prevent unintended precision loss.

The Type Hierarchy and Promotion Direction

Implicit conversion in GLSL follows a strict unidirectional hierarchy based on data representation and value preservation. Conversions are allowed only in the direction of increasing capacity or flexibility, moving strictly from left to right:

\[\text{int} \longrightarrow \text{uint} \longrightarrow \text{float}\]

  • int to uint: A signed integer can be implicitly promoted to an unsigned integer.
  • int to float: A signed integer can be implicitly promoted to a single-precision float.
  • uint to float: An unsigned integer can be implicitly promoted to a single-precision float.

GLSL strictly prohibits reverse conversions (narrowing). A float will never implicitly convert to an int or uint, and a uint will never implicitly convert to an int. Any attempt to assign a higher-ranked type to a lower-ranked variable without explicit casting (such as int(myFloat)) results in a compilation error.

Binary Operations and Arithmetic

When arithmetic, relational, or bitwise binary operators encounter operands of differing types, GLSL applies the type hierarchy to determine a common type before executing the operation:

  1. Mixed float and Integer (int or uint): The integer operand is promoted to float, and the operation evaluates to a float.
  2. Mixed int and uint: The int operand is promoted to uint, and the operation evaluates to a uint.

Examples:

int   i = 2;
uint  u = 4u;
float f = 1.5;

auto a = i + f; // 'i' becomes float(2.0); result is float (3.5)
auto b = u * f; // 'u' becomes float(4.0); result is float (6.0)
auto c = i + u; // 'i' becomes uint(2u);   result is uint (6u)

Care must be taken when mixing signed negative integers with unsigned integers; an int with a negative value will be reinterpreted via standard two's complement when implicitly promoted to uint, yielding a very large positive number.

Assignment and Initialization

Implicit conversions apply automatically during variable declaration, initialization, and assignment, provided the value on the right-hand side can be safely promoted to match the type of the left-hand side.

float f1 = 10;   // Valid: 'int' literal 10 promoted to 'float' (10.0)
float f2 = 10u;  // Valid: 'uint' literal 10u promoted to 'float' (10.0)
uint  u1 = 10;   // Valid: 'int' literal 10 promoted to 'uint' (10u)

int   i1 = 10u;  // Error: Cannot implicitly demote 'uint' to 'int'
int   i2 = 10.0; // Error: Cannot implicitly demote 'float' to 'int'
uint  u2 = 10.0; // Error: Cannot implicitly demote 'float' to 'uint'

Function Overload Resolution

When calling overloaded functions, the GLSL compiler selects the function whose signature most closely matches the supplied argument types:

  1. Exact Match: If an exact signature matches the parameter types without conversion, that overload is selected.
  2. Promotion Match: If no exact match exists, the compiler searches for overloads that can be satisfied using implicit conversions based on the promotion chain.
  3. Ambiguity: If an invocation matches multiple overloads equally well via conversions, the compiler raises a compilation error due to ambiguity.
void compute(float x);
void compute(uint x);

compute(5); // Ambiguous: 'int' can implicitly convert to both 'uint' and 'float'

In such ambiguous cases, the call must be disambiguated using an explicit cast, such as compute(float(5)) or compute(uint(5)).

Vector and Matrix Conversions

Implicit conversion rules extend component-wise to vectors containing these scalar types:

  • ivec implicitly converts to uvec or vec of matching dimensions (e.g., ivec3 to vec3).
  • uvec implicitly converts to vec of matching dimensions (e.g., uvec2 to vec2).

Vector conversions require strict dimension equality. An int cannot implicitly expand to a vec2, nor can an ivec2 implicitly convert to a vec3. All scalar-to-vector promotions or dimensional changes must be handled through explicit constructors.