GLSL 16-Bit and 32-Bit Type Promotion Rules
This article outlines the rules governing implicit type promotion in GLSL when combining 16-bit and 32-bit arithmetic operands. It details how the OpenGL Shading Language resolves bit-width disparities, handles signed and unsigned integer interactions, balances floating-point and integer conversions, and applies these rules across scalar and vector operations under modern explicit type extensions.
The Explicit Type Hierarchy
In core GLSL, precision qualifiers such as lowp,
mediump, and highp do not define distinct data
types; they represent storage precision hints without triggering
standard implicit type conversions. True 16-bit arithmetic is introduced
via extensions such as
GL_EXT_shader_explicit_arithmetic_types (or
GL_AMD_gpu_shader_half_float), which provide concrete types
including float16_t, int16_t, and
uint16_t.
When these extensions are enabled, GLSL establishes a strict type conversion hierarchy based on rank:
double(64-bit float)float/float32_t(32-bit float)float16_t(16-bit float)uint64_t/int64_t(64-bit integers)uint32_t/int32_t(32-bit integers)uint16_t/int16_t(16-bit integers)uint8_t/int8_t(8-bit integers)
Bit-Width Widening
When an arithmetic operation involves a 16-bit type and a 32-bit type of the same category (e.g., both floating-point or both signed integers), the operand with the smaller bit width is widened to match the 32-bit operand.
- Floating-Point Widening: A binary operation between
a
float16_tand afloatpromotes thefloat16_toperand tofloat. The operation executes at 32-bit precision, and the resulting expression evaluates tofloat. - Same-Sign Integer Widening: An operation between
int16_tandint32_tpromotes theint16_toperand toint32_t. Similarly,uint16_tmixed withuint32_tpromotes touint32_t.
Mixing Signed and Unsigned Integers
When mixing signed and unsigned integer types across 16-bit and 32-bit widths, the conversion behavior depends on whether the larger type can safely represent all values of the smaller type:
- Unsigned 16-Bit and Signed 32-Bit: A
uint16_toperand mixed with anint32_tpromotes directly toint32_t. Because a 32-bit signed integer can represent all possible values of a 16-bit unsigned integer (\(0\) to \(65,535\)), preserving signedness of the wider type introduces no value loss. - Signed 16-Bit and Unsigned 32-Bit: An
int16_toperand mixed with auint32_tpromotes touint32_t. The signed 16-bit integer is converted to a 32-bit unsigned integer, which reinterprets negative values according to standard two's complement rules.
Mixing Integers and Floating-Point Types
Floating-point types consistently take precedence over integer types regardless of bit-width differences, following the rank hierarchy:
- 16-Bit Float and 32-Bit Integer: An operation
mixing
float16_tandint32_t(oruint32_t) promotes the 16-bit float to a 32-bit float (float), and converts the 32-bit integer to a 32-bit float. The operation evaluates using standard single-precision floating-point arithmetic. - 16-Bit Integer and 32-Bit Float: An operation
mixing
int16_t(oruint16_t) andfloatimplicitly converts the 16-bit integer directly tofloat.
Vector and Matrix Promotion
Implicit promotions apply component-wise to vectors and matrices. If
an operation occurs between a 16-bit vector (such as
f16vec3) and a 32-bit vector of the same dimension
(vec3), the 16-bit vector components are promoted to 32-bit
floats, yielding a vec3 result.
In scalar-to-vector operations—such as adding a float
scalar to an f16vec4 vector—the scalar does not demote to
16 bits. Instead, the 16-bit vector components widen to 32 bits,
producing a standard 32-bit vector result (vec4).