GLSL Memory Alignment: int16_t vs float Vectors
In GLSL interface blocks, memory alignment dictates how data maps
between CPU memory and GPU storage buffers or uniform buffers. While
standard 32-bit float vectors adhere to standard 4-, 8-,
and 16-byte alignment rules, 16-bit integer (int16_t)
vectors operate on a base component size of 2 bytes, leading to smaller
2-, 4-, and 8-byte alignments under compact memory rules. Understanding
these differences across std140, std430, and
scalar memory layouts is essential to prevent unintended padding, buffer
mismatch bugs, and inefficient memory bandwidth usage.
Base Component Sizes and Alignment Rules
In standard GLSL packing rules, the base alignment of a vector is
defined by the size of its underlying scalar type multiplied by either 2
(for 2-component vectors) or 4 (for 3- and 4-component vectors). Because
a standard float is 4 bytes and an int16_t
(from extensions such as
GL_EXT_shader_explicit_arithmetic_types_int16) is 2 bytes,
the fundamental alignment requirements scale down proportionally.
| Type | Size (Bytes) | Base Alignment (Bytes) |
|---|---|---|
float |
4 | 4 |
vec2 |
8 | 8 |
vec3 |
12 | 16 |
vec4 |
16 | 16 |
int16_t |
2 | 2 |
i16vec2 |
4 | 4 |
i16vec3 |
6 | 8 |
i16vec4 |
8 | 8 |
Both 3-component vectors (vec3 and i16vec3)
round their alignment up to the size of a 4-component vector of that
type. Consequently, vec3 requires 16-byte alignment despite
using only 12 bytes of data, leaving a 4-byte padding hole. Similarly,
i16vec3 requires 8-byte alignment despite using only 6
bytes, leaving a 2-byte padding gap before the next aligned member.
Differences in std140 Layout
The std140 standard, commonly mandated for Uniform
Buffer Objects (UBOs), enforces aggressive alignment rules that can
eliminate the memory-saving benefits of 16-bit vectors:
- Array Strides: Under
std140, every element in an array is rounded up to the alignment of a 16-byte vector (vec4), regardless of the element type. An array offloathas an element stride of 16 bytes. An array ofint16_tori16vec2also gets padded to a 16-byte stride per element. - Structure Alignment: Any structure or nested
structure within
std140has its base alignment rounded up to a multiple of 16 bytes. - Individual Vectors: Non-arrayed
i16vec2andi16vec4members can still pack tightly at 4-byte and 8-byte offsets respectively, whereasvec2requires 8 bytes andvec4requires 16 bytes.
Differences in std430 Layout
The std430 layout rule, available in Shader Storage
Buffer Objects (SSBOs), eliminates the mandatory 16-byte array padding,
allowing 16-bit types to achieve significant bandwidth and footprint
reductions:
- Array Strides: Under
std430, array stride equals the base alignment of the array element itself. An array ofvec2has an 8-byte stride, and an array ofvec4has a 16-byte stride. In contrast, an array ofi16vec2has a 4-byte stride, and an array ofi16vec4has an 8-byte stride. - Three-Component Arrays: An array of
vec3has a 16-byte stride (aligned tovec4), while an array ofi16vec3has an 8-byte stride (aligned toi16vec4).
Impact of Scalar Block Layout
When using extensions like GL_EXT_scalar_block_layout
(standard in modern Vulkan pipelines), alignment constraints match the
underlying scalar type rather than vector dimensions:
floatvectors: Alignment drops to 4 bytes for all vector types (vec2,vec3,vec4). Avec3takes exactly 12 bytes with 4-byte alignment, leaving zero internal padding.int16_tvectors: Alignment drops to 2 bytes for all vector types (i16vec2,i16vec3,i16vec4). Ani16vec3takes exactly 6 bytes with 2-byte alignment.
Host-Side Struct Synchronization
To ensure CPU-GPU data parity when uploading data:
- In C++ using standard rules, a
vec4maps to a 16-byte aligned struct (alignas(16)), while ani16vec4maps to an 8-byte aligned struct (alignas(8)). - A 3-component
vec3requires 4 bytes of explicit tail padding on the CPU to prevent data misalignment instd140andstd430. Ani16vec3requires 2 bytes of tail padding to satisfy its 8-byte alignment footprint.