How Do GLSL Array Declarations and Sizing Work?
Arrays in the OpenGL Shading Language (GLSL) allow developers to store ordered collections of data of the same type, subject to strict declaration syntax, memory layout rules, and compile-time or runtime sizing constraints. Understanding these rules ensures compatibility across different shader stages, hardware architectures, and GLSL language versions.
Array Declaration Syntax
GLSL supports two primary syntaxes for declaring arrays, both of which are valid in modern versions of the language.
- C-Style Syntax: The brackets follow the variable name.
float values[4];
vec3 positions[10];- Type-Style Syntax: The brackets follow the type specifier.
float[4] values;
vec3[10] positions;GLSL also supports multidimensional arrays (arrays of arrays). They can be declared by appending dimension specifiers:
mat4 transforms[2][4];Sizing Rules and Compile-Time Constants
In standard variable and uniform declarations, array sizes must be integral constant expressions evaluated at compile time.
- Explicit Sizing with Constant Expressions: Array
dimensions cannot depend on dynamic variables, shader inputs, or
non-constant runtime calculations. They must resolve to an integer
greater than zero using literals,
constvariables, or constant operations.
const int SIZE = 8;
vec4 data[SIZE * 2]; // Valid: evaluated at compile time- Implicit Sizing via Initializers: An array can be declared without an explicit size if it is immediately initialized using an array constructor. The compiler infers the dimension from the number of arguments provided.
int numbers[] = int[](1, 2, 3, 4, 5); // Inferred size of 5- Declaration Without Initial Size: An unsized array can be declared globally and later sized either by a subsequent redeclaration with an explicit size or by indexing with a constant integral expression before compilation finishes, provided this feature is supported by the target GLSL version.
Unsized Arrays in Shader Storage Buffer Objects (SSBOs)
A critical exception to the fixed compile-time size rule applies to Shader Storage Buffer Objects (SSBOs).
- Runtime-Sized Arrays: The last member of a shader
storage block can be declared as an unsized array
(
type name[];). - Runtime Determination: The actual length of this buffer-backed array is determined by the size of the OpenGL buffer bound at runtime.
- Length Query: Shaders can query this runtime size
using the built-in
.length()method.
layout(std430, binding = 0) buffer StorageBlock {
mat4 projection;
float dynamicData[]; // Valid only as the final member of an SSBO
};Array Constructors and Initialization
Arrays can be initialized using type-specific constructors. The constructor name consists of the element type followed by brackets indicating the array size.
vec2 offsets[3] = vec2[3](
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0)
);The number and types of arguments in the constructor must match the declared dimension and base type exactly.
Indexing Constraints
How arrays are accessed depends on the storage qualifier and shader stage:
- Compile-Time Constant Expressions: Required when indexing into arrays where the hardware or GLSL specification requires static routing (such as sampler arrays in older GLSL profiles).
- Dynamically Uniform Expressions: Required when indexing certain resource arrays (such as uniform buffers or texture samplers in modern GLSL) across all invocations within an execution group.
- General Dynamic Expressions: Standard local variables, global variables, and SSBO arrays permit general integer expression indexing computed dynamically per invocation.
The .length() Method
GLSL arrays provide an intrinsic .length() method that
returns an int representing the total number of
elements.
- For fixed-size arrays,
.length()evaluates to a compile-time constant expression. - For unsized arrays in SSBOs,
.length()executes at runtime to calculate capacity based on the bound buffer range.