Guide to GLSL packHalf2x16 Function

This article provides an overview of the packHalf2x16 built-in function in the OpenGL Shading Language (GLSL), explaining how it converts two 32-bit floating-point values into 16-bit half-precision floats packed into a single 32-bit unsigned integer. You will learn the mechanics behind this data compression, its primary role in optimizing graphics memory bandwidth, and how it is applied in modern rendering pipelines.

What is packHalf2x16?

The packHalf2x16 function is a built-in GLSL utility introduced in OpenGL 4.2 (and GLSL 4.20), also available in OpenGL ES 3.0. Its primary purpose is data packing: it takes a two-component vector of standard 32-bit single-precision floats (vec2) and converts each component into a 16-bit half-precision float (conforming to the IEEE 754-2008 binary16 format). It then packs both 16-bit representations into a single 32-bit unsigned integer (uint).

The function signature is defined as:

uint packHalf2x16(vec2 v);

How It Works

When packHalf2x16(vec2 v) executes, the conversion and bit placement occur in a defined order:

  1. Downcasting: The component v.x (or v[0]) is converted from a 32-bit float to a 16-bit float. The same conversion is applied to v.y (or v[1]).
  2. Bit Allocation:
    • The 16 bits of the first component (v.x) are written to the least significant bits (bits 0 through 15) of the returned unsigned integer.
    • The 16 bits of the second component (v.y) are written to the most significant bits (bits 16 through 31).

The resulting uint contains the entire binary payload of both half-precision values side by side.

The Role of packHalf2x16 in Shaders

The primary role of packHalf2x16 is data compression to optimize GPU memory footprint and memory bandwidth. Modern rendering workloads are frequently memory-bandwidth limited. Compressing floating-point data directly on the GPU offers several practical advantages:

  • G-Buffer Optimization in Deferred Rendering: In deferred shading pipelines, geometries write attributes like normals, tangent vectors, or texture coordinates into multiple render targets (G-buffers). By packing two attributes into a single 32-bit channel using packHalf2x16, shaders reduce the number of render target attachments or channel widths required.
  • Shader Storage Buffer Objects (SSBOs): When passing large arrays of data between compute shaders or subsequent render passes, packing pairs of floats reduces buffer size by 50%, improving cache utilization and throughput.
  • Image Load/Store Operations: When writing to raw storage textures or buffers where 16-bit float formats might not support atomic writes or direct formats, packing into standard R32UI textures allows for efficient read/write access.

Unpacking Data

To retrieve the original values, GLSL provides the inverse function, unpackHalf2x16:

vec2 unpackHalf2x16(uint v);

This function extracts bits 0–15 and bits 16–31, converting each 16-bit half-float back into a standard 32-bit float. Because half-precision floats have lower precision (1 sign bit, 5 exponent bits, and 10 mantissa bits) compared to single-precision floats (1 sign bit, 8 exponent bits, and 23 mantissa bits), the conversion is lossy for values requiring high numeric precision or large dynamic ranges.