GL_EXT_shader_16bit_storage SSBO Memory Layout

The GL_EXT_shader_16bit_storage extension allows shaders to directly access 16-bit data types, such as half-precision floating-point values, within Shader Storage Buffer Objects (SSBOs) without requiring manual bitfield unpacking. This article outlines how the extension defines alignment, size, and layout rules for 16-bit floats under standard layout qualifiers (std430 and std140) to ensure proper CPU-GPU memory interoperability.

Enabling 16-Bit Types and Storage

Using half-precision floats in SSBOs requires both the storage extension and the explicit arithmetic type extension in GLSL:

#extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require

With these enabled, the shader can use native 16-bit types: scalar float16_t, vectors f16vec2, f16vec3, and f16vec4, as well as corresponding matrices.

Base Alignment and Size for 16-Bit Floats

Under GL_EXT_shader_16bit_storage, the memory footprint and base alignment of half-float types are scaled down proportionally to their 32-bit counterparts:

  • float16_t: Size is 2 bytes; base alignment is 2 bytes.
  • f16vec2: Size is 4 bytes; base alignment is 4 bytes.
  • f16vec3: Size is 6 bytes; base alignment is 8 bytes (aligned to the size of a 4-component vector of the base scalar type).
  • f16vec4: Size is 8 bytes; base alignment is 8 bytes.

Layout Rules: std430 vs. std140

The memory packing behavior inside an SSBO depends strictly on the layout qualifier assigned to the buffer block.

The std430 qualifier eliminates unnecessary array padding, allowing optimal packing for 16-bit floats:

  • Scalar Arrays: An array of float16_t values has an array stride equal to the base scalar alignment (2 bytes). Elements are placed contiguously with no padding between them.
  • Vector Arrays:
    • f16vec2[] has a stride of 4 bytes.
    • f16vec4[] has a stride of 8 bytes.
    • f16vec3[] has an 8-byte stride because the base alignment of a 3-component vector rounds up to the size of a 4-component vector, resulting in 2 bytes of implicit padding per element.
layout(std430, binding = 0) buffer DataBuffer {
    float16_t scalarArray[]; // Stride = 2 bytes (tightly packed)
    f16vec2   vec2Array[];   // Stride = 4 bytes
    f16vec4   vec4Array[];   // Stride = 8 bytes
};

std140 Layout

If an SSBO uses std140, legacy uniform buffer alignment rules apply unless overridden by additional scalar layout extensions:

  • Array elements and structure members have their stride rounded up to a multiple of 16 bytes (the size of a standard 32-bit vec4).
  • A float16_t[] array inside a std140 block consumes 16 bytes per element, with 14 bytes of padding between each value, negating the memory-saving benefits of 16-bit data.

Matrices and Structs

  • Matrices: Matrices are treated as arrays of vectors (columns by default). In std430, a column-major f16mat2x2 consists of two f16vec2 columns, requiring 8 bytes total (4 bytes per column). A f16mat4x4 requires 32 bytes (8 bytes per column).
  • Structures: The base alignment of a struct containing 16-bit members is equal to the largest base alignment of any member inside the struct. The struct's total size is padded to a multiple of its base alignment.