GLSL std140 Layout Padding Rules for vec3
Understanding the memory layout of uniform buffer objects is critical
for preventing subtle data alignment bugs between host code and shaders.
In OpenGL Shading Language (GLSL), the std140 layout
standard enforces explicit sizing and alignment rules for all uniform
block members. This article details the specific padding and alignment
behavior applied to the three-component floating-point vector
(vec3) under the std140 specification.
The Base Alignment Rule for vec3
According to the OpenGL specification for std140,
members of a uniform block are aligned according to their base
alignment:
- A scalar (e.g.,
float,int,uint) has a base alignment of 4 bytes. - A two-component vector (
vec2) has a base alignment of 8 bytes. - A three-component vector (
vec3) has a base alignment of 16 bytes. - A four-component vector (
vec4) has a base alignment of 16 bytes.
Even though a vec3 only occupies 12 bytes of active data
(three 32-bit floats), its base alignment is rounded up to 16
bytes—identical to the base alignment of a vec4.
Offset and Boundary Placement
Because the base alignment of a vec3 is 16 bytes, any
vec3 declared inside a std140 uniform block
must begin at an offset that is an exact multiple of 16 bytes.
If preceding data does not cleanly end on a 16-byte boundary, the
compiler automatically inserts padding bytes immediately before the
vec3 to push its starting offset to the next 16-byte
multiple.
Example: Alignment Padding Before vec3
layout (std140) uniform ExampleBlock {
float a; // Offset: 0, Size: 4 bytes
// Padding: 12 bytes (offsets 4 to 15)
vec3 b; // Offset: 16, Size: 12 bytes (offsets 16 to 27)
};In this example, 12 bytes of padding are placed after
float a so that vec3 b can align to byte
offset 16.
Trailing Padding and Packing
While a vec3 aligns to 16 bytes, its raw size remains 12
bytes. If another variable with a smaller alignment requirement follows
the vec3, it can occupy the remaining 4 bytes of that
16-byte chunk, provided its alignment constraints are satisfied.
layout (std140) uniform PackedExample {
vec3 a; // Offset: 0, Size: 12 bytes (offsets 0 to 11)
float b; // Offset: 12, Size: 4 bytes (offsets 12 to 15)
vec4 c; // Offset: 16, Size: 16 bytes (offsets 16 to 31)
};Because float b requires only 4-byte alignment, it fits
directly into offset 12 without additional padding. However, if another
vec3 or vec4 follows vec3 a, it
cannot use offset 12 because both types require 16-byte alignment. In
that case, 4 bytes of implicit padding are added after
vec3 a.
Arrays of vec3
Under std140, array elements are always aligned to
multiples of 16 bytes. Consequently, an array of vec3 does
not tightly pack elements into sequential 12-byte blocks. Instead, each
element has a stride of 16 bytes:
layout (std140) uniform ArrayBlock {
vec3 vectors[2];
};vectors[0]: Offsets 0 to 11 (Offsets 12 to 15 are padding)vectors[1]: Offsets 16 to 27 (Offsets 28 to 31 are padding)
Each vec3 in an array wastes 4 bytes as trailing
padding.
Matching the Layout in C++
When mirroring a GLSL uniform block in host code such as C++, you must explicitly account for these padding rules to maintain alignment:
struct ExampleBlock {
alignas(16) float a;
alignas(16) float b[3]; // vec3 represented with 16-byte alignment
};
struct ArrayBlock {
struct Vec3Padded {
float x, y, z;
float padding; // Explicitly fills the 4-byte gap to reach 16 bytes
};
Vec3Padded vectors[2];
};To avoid complex manual padding and potential data mismatches, using
vec4 instead of vec3 in std140
blocks is widely considered a best practice in graphics programming.