How unpackHalf2x16 Works in GLSL

The GLSL built-in function unpackHalf2x16 unpacks a single 32-bit unsigned integer into a two-component 32-bit floating-point vector (vec2). It achieves this by treating the input integer as two contiguous 16-bit chunks, interpreting each chunk as an IEEE 754 half-precision floating-point number (binary16), and converting each into a standard 32-bit single-precision float (binary32). This process is essential in real-time graphics for decompressing packed data such as UV coordinates, normals, or half-precision framebuffers to save memory bandwidth.

Bit Splitting

The function takes a single uint v as its parameter. The 32-bit integer is divided into two separate 16-bit bitfields:

  1. Component X (Least Significant Bits): Extracted from bits 0 through 15 (v & 0xFFFFu).
  2. Component Y (Most Significant Bits): Extracted from bits 16 through 31 (v >> 16u).

The 16-Bit Float Layout (binary16)

Each extracted 16-bit value conforms to the IEEE 754 binary16 standard, structured as:

  • 1 Sign Bit (Bit 15): Determines if the value is positive (0) or negative (1).
  • 5 Exponent Bits (Bits 10–14): Encodes the exponent with a bias of 15.
  • 10 Mantissa/Fraction Bits (Bits 0–9): Encodes the fractional part with an implicit leading bit of 1 for normalized numbers.

Conversion to 32-Bit Float (binary32)

To produce standard GLSL float values for the returned vec2, the hardware converts each 16-bit float into a 32-bit float (1 sign bit, 8 exponent bits with a bias of 127, and 23 mantissa bits):

  • Sign Bit: The 1-bit sign is copied directly to bit 31 of the target 32-bit float.
  • Exponent: The 5-bit exponent is re-biased. Because the 16-bit bias is 15 and the 32-bit bias is 127, the exponent is adjusted by adding the difference (\(127 - 15 = 112\)) to normalized exponents.
  • Mantissa: The 10-bit mantissa is shifted left into the higher bits of the 23-bit target mantissa, filling the lower 13 bits with zeros.

Special Cases Handling

The conversion process automatically handles IEEE 754 floating-point edge cases:

  • Zero: If both the exponent and mantissa are zero, the result becomes a positive or negative 32-bit zero (+0.0 or -0.0).
  • Subnormal (Denormalized) Numbers: If the exponent is zero and the mantissa is non-zero, the value is normalized during conversion or flushed to zero depending on the GPU's floating-point handling mode.
  • Infinities and NaNs: If the 5-bit exponent is all ones (0x1F), it is converted to a 32-bit infinity (if the mantissa is zero) or a 32-bit NaN (if the mantissa is non-zero).

Result Output

Once both the lower and upper 16-bit segments are transformed into standard 32-bit floats, the function returns them combined as:

vec2 result;
result.x = convertHalfToFloat(uint16_lower);
result.y = convertHalfToFloat(uint16_upper);

This operation runs directly on GPU hardware using dedicated bit-manipulation and unpacking instructions, ensuring minimal runtime overhead.