Search code examples
openglglslspir-v

Specialization constant used for array size


I'm trying to use a SPIR-V specialization constant to define the size of an array in a uniform block.

#version 460 core

layout(constant_id = 0) const uint count = 0;

layout(binding = 0) uniform Uniform
{
    vec4 foo[count];
    uint bar[count];
};

void main() {}

With a declaration of count = 0 in the shader, compilation fails with :

array size must be a positive integer

With count = 1 and a specialization of 5, the code compiles but linking fails at runtime with complaints of aliasing :

error: different uniforms (named Uniform.foo[4] and Uniform.bar[3]) sharing the same offset within a uniform block (named Uniform) between shaders
error: different uniforms (named Uniform.foo[3] and Uniform.bar[2]) sharing the same offset within a uniform block (named Uniform) between shaders
error: different uniforms (named Uniform.foo[2] and Uniform.bar[1]) sharing the same offset within a uniform block (named Uniform) between shaders
error: different uniforms (named Uniform.foo[1] and Uniform.bar[0]) sharing the same offset within a uniform block (named Uniform) between shaders

It seems the layout of the uniform block (the offset of each member) is not affected during specialization so foo and bar overlap.

Explicit offsets don't work either and result in the same link errors :

layout(binding = 0, std140) uniform Uniform
{
    layout(offset = 0) vec4 foo[count];
    layout(offset = count) uint bar[count];
};

Is this intended behavior ? An overlook ? Can a specialization constant be used to define the size of an array ?


Solution

  • This is an odd quirk of ARB_spir_v. From the extension specification:

    Arrays inside a block may be sized with a specialization constant, but the block will have a static layout. Changing the specialized size will not re-layout the block. In the absence of explicit offsets, the layout will be based on the default size of the array.

    Since the default size is 0, the struct in the block will be laid out as though the arrays were zero-sized.

    Basically, you can use specialization constants to make the arrays shorter than the default, but not longer. And even if you make them shorter, they still take up the same space as the default.

    So really, using specialization constants in block array lengths is just a shorthand way of declaring the array with the default value as its length, and then replacing where you would use name.length() with the specialization constant/expression. It's purely syntactic sugar.