GLSL SSBO Unsized Array Syntax and Layout Rules
Shader Storage Buffer Objects (SSBOs) in the OpenGL Shading Language
(GLSL) provide the ability to bind dynamically sized data to shaders
using unsized arrays. This guide covers the syntax requirements for
declaring these runtime-sized arrays, the structural constraints that
govern their placement within an interface block, and the memory layout
qualifiers—specifically std430 and std140—that
determine element padding and memory alignment.
Basic Syntax
An unsized array is declared using empty square brackets
[] as the array dimension. It must reside inside a shader
storage block defined with the buffer keyword:
layout(std430, binding = 0) buffer DataBlock {
int count;
float factor;
vec4 elements[]; // Unsized array
};Placement and Structural Rules
GLSL enforces strict constraints regarding where and how unsized arrays can be declared within SSBOs:
Last Member Restriction: An unsized array can only appear as the very last member of a shader storage block. Placing an unsized array before any other member results in a compile-time error.
Single Unsized Array per Block: A single interface block can contain at most one unsized array.
No Unsized Arrays in Structs: Structures defined in GLSL cannot contain unsized arrays, meaning you cannot have an unsized array nested inside a struct member of a buffer. However, you can create an unsized array of structures, provided that the struct itself has a fixed size and the array is the final block member.
Runtime Length Query: You can query the number of elements in the array at runtime using the
.length()method:uint totalElements = elements.length();This value is computed dynamically by the driver based on the size of the OpenGL buffer object currently bound to the binding point.
Memory Layout Rules
The memory alignment and padding of unsized array elements depend on
the block's layout qualifier. SSBOs typically use either
std430 or std140.
The std430 Layout
(Recommended)
The std430 qualifier is designed specifically for SSBOs
and offers tighter packing for arrays compared to
std140:
- Scalar and Vector Arrays: Elements in an array of
scalars (such as
floatorint) or vectors are aligned to their natural size. For example, infloat elements[], each element has an alignment and stride of 4 bytes. Invec2 elements[], the stride is 8 bytes. - Struct Arrays: If the unsized array contains user-defined structs, each struct element is aligned according to the largest member alignment in the struct, rounded up to the nearest multiple of the base alignment.
The std140 Layout
While supported, std140 imposes strict alignment rules
that often waste memory:
- Forced 16-byte Alignment: In
std140, every array element is rounded up to the size of avec4(16 bytes), regardless of the base type. Afloat elements[]understd140allocates 16 bytes per float, leaving 12 bytes of padding between entries.
Unsized Array of Structures Example
When declaring an unsized array of user-defined types, ensure all nested types are fully defined with fixed sizes:
struct Particle {
vec3 position;
float lifetime;
vec3 velocity;
float mass;
};
layout(std430, binding = 1) buffer ParticleSystem {
uint activeCount;
Particle particles[]; // Unsized array of structs as the last member
};In this std430 layout, each Particle struct
aligns to 16 bytes (due to the vec3 requiring 16-byte
alignment), making the total stride of the particles array
32 bytes per element with no trailing unused space.