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:

  1. 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.

  2. Single Unsized Array per Block: A single interface block can contain at most one unsized array.

  3. 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.

  4. 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 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 float or int) or vectors are aligned to their natural size. For example, in float elements[], each element has an alignment and stride of 4 bytes. In vec2 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 a vec4 (16 bytes), regardless of the base type. A float elements[] under std140 allocates 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.