Multidimensional arrays are added to GLSL via either
GL_ARB_arrays_of_arrays
extension or GLSL 4.30. I've had a couple people tell me that the
multidimensional array syntax is either wrong or just plain crazy. When
viewed from the proper angle, it should actually be perfectly logical to any
C / C++ programmer. I'd like to clear up a bit of the confusion.
Staring with the easy syntax, the following does what you expect:
vec4 a[2][3][4];
If a
is inside a uniform block, the memory layout would be the same as in
C. cdecl would call this, "declare a as array 2 of
array 3 of array 4 of vec4".
Using GLSL constructor syntax, the array can also be initialized:
vec4 a[2][3][4] = vec4[][][](vec4[][](vec4[](vec4( 1), vec4( 2), vec4( 3), vec4( 4)),
vec4[](vec4( 5), vec4( 6), vec4( 7), vec4( 8)),
vec4[](vec4( 9), vec4(10), vec4(11), vec4(12))),
vec4[][](vec4[](vec4(13), vec4(14), vec4(15), vec4(16)),
vec4[](vec4(17), vec4(18), vec4(19), vec4(20)),
vec4[](vec4(21), vec4(22), vec4(23), vec4(24))));
If that makes your eyes bleed,
GL_ARB_shading_language_420pack
and GLSL 4.20 add the ability to use C-style array and structure
initializers. In that model, a
can be initialized to the same values by:
vec4 a[2][3][4] = {
{
{ vec4( 1), vec4( 2), vec4( 3), vec4( 4) },
{ vec4( 5), vec4( 6), vec4( 7), vec4( 8) },
{ vec4( 9), vec4(10), vec4(11), vec4(12) }
},
{
{ vec4(13), vec4(14), vec4(15), vec4(16) },
{ vec4(17), vec4(18), vec4(19), vec4(20) },
{ vec4(21), vec4(22), vec4(23), vec4(24) }
}
};
Functions can be declared that take multidimensional arrays as parameters. In the prototype, the name of the parameter can be present, or it can be omitted.
void function_a(float a[4][5][6]);
void function_b(float [4][5][6]);
Other than the GLSL constructor syntax, there hasn't been any madness yet.
However, recall that array sizes can be associated with the variable name or
with the type. The prototype for function_a
associates the size with the
variable name, and the prototype for function_b
associates the size with
the type. Like GLSL constructor syntax, this has existed since GLSL 1.20.
Associating the array size with just the type, we can declare a
(from
above) as:
vec4[2][3][4] a;
With multidimensional arrays, the sizes can be split among the two, and this
is where it gets weird. We can also declare a
as:
vec4[3][4] a[2];
This declaration has the same layout as the previous two forms. This is usually where people say, "It's bigger on the inside!" Recall the cdecl description, "declare a as array 2 of array 3 of array 4 of vec4". If we add some parenthesis, "declare a as array 2 of (array 3 of array 4 of vec4)", and things seem a bit more clear.
GLSL ended up with this syntax for two reasons, and seeing those reasons
should illuminate things. Without GL_ARB_arrays_of_arrays
or GLSL 4.30,
there are no multidimensional arrays, but the same affect can be achieved,
very inconveniently, using structures containing arrays. In GLSL 4.20 and
earlier, we could also declare a
as:
struct S1 {
float a[4];
};
struct S2 {
S1 a[3];
};
S2 a[2];
I'll spare you having to see GLSL constructor initializer for that mess.
Note that we still end up with a[2]
at the end.
Using typedef
in C, we could also achieve the same result using:
typedef float T[3][4];
T a[2];
Again, we end up with a[2]
. If cdecl could handle this (it doesn't grok
typedef
), it would say "declare a as array 2 of T", and "typedef T as
array 3 of array 4 of float". We could substitue the description of T
and, with parenthesis, get "declare a as array 2 of (array 3 of array 4 of
float)".
Where this starts to present pain is that function_c
has the same
parameter type as function_a
and function_b
, but function_d
does not.
void function_c(float[5][6] a[4]);
void function_d(float[5][6] [4]);
However, the layout of parameter for function_e
is the same as
function_a
and function_b
, even though the actual type is different.
struct S3 {
float a[6];
};
struct S4 {
S3 a[5];
};
void function_e(S4 [4]);
I think if we had it to do all over again, we may have disallowed the split syntax. That would remove the more annoying pitfalls and the confusion, but it would also remove some functionality. Most of the problems associated with the split are caught at compile-time, but some are not. The two obvious problems that remain are transposing array indices and incorrectly calculated uniform block layouts.
layout(std140) uniform U {
float [1][2][3] x;
float y[1][2][3];
float [1][2] z[3];
};
In this example x
and y
have the same memory organization, but z
does
not. I wouldn't want to try to debug that problem.