Nathan Ridley
Nathan Ridley

Reputation: 34396

How do I properly declare a uniform array of structs in GLSL so that I can point a UBO at it?

The following glsl code appears in my fragment shader. The struct definition causes no problems, but my attempt to use it as the type of a uniform array causes an "invalid operation" error, which is not particularly helpful.

struct InstanceData
{
    vec3 rotation;
    vec3 scale;
    mat4 position;
};

layout (std140) uniform InstanceData instances[100];

How do I structure this code correctly so that it compiles without error, and is thus ready for me to populate with data? Note that I am using core profile version 4.5.

Edit: It seems to be something to do with the use of layout (std140). Removal of that part allows the code to compile, though don't I need that to ensure that the glsl compiler packs the struct data in a predictable way?

Edit: Still not working. My entire vertex shader code looks like this:

#version 450

layout(location=0) in vec4 in_Position;
layout(location=1) in vec4 in_Color;
out vec4 ex_Color;
flat out int ex_Instance;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

// ------ preliminary addition of uniform block to be used soon ------
struct layout (std140) InstanceData
{
    vec3 rotation;
    vec3 scale;
    mat4 position;
};

layout (std140, binding = 0) uniform InstanceData
{
    InstanceData instances[100];
};
// -------------------------------------------------------------------

void main(void)
{
    gl_Position = (projectionMatrix * viewMatrix * modelMatrix) * in_Position;
    ex_Color = in_Color;
}

Note that I haven't written any code externally to populate the uniform buffer yet, nor, as you can see above, have I adjusted my code to make use of the data either. I just want it to compile and work initially as-is (i.e. declared but not used), at which point I will add additional code to start making use of it. There's no point going even that far if the program doesn't like the declaration to begin with.

Edit: Finally figured out the problem by using the shader info log, like so:

GLint infoLogLength;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &infoLogLength);
GLchar* strInfoLog = new GLchar[infoLogLength + 1];
glGetShaderInfoLog(id, infoLogLength, NULL, strInfoLog);

In a nutshell, as Anton said, I was using layout incorrectly, and my uniform block had the same name as my struct, creating all kinds of confusion for the compiler.

Upvotes: 1

Views: 15391

Answers (1)

Andon M. Coleman
Andon M. Coleman

Reputation: 43319

To do this portably, you should use a Uniform Buffer Object.

Presently, your struct uses 3+3+16=22 floating-point components and you are trying to build an array of 100 of these. OpenGL implementations are only required to support 1024 floating-point uniform components in any stage, and your array requires 2200.

Uniform Buffer Objects will allow you to store up to 64 KiB (minimum) of data; well exceeding the limitation above. However, you need to be mindful of data alignment when using UBOs and that is what the layout (std140) qualifier you tried to use is for.

struct InstanceData
{
    vec3 rotation;
    vec3 scale;
    mat4 position;
};

// Uniform block named InstanceBlock, follows std140 alignment rules
layout (std140, binding = 0) uniform InstanceBlock {
  InstanceData instances [100];
};

The struct above is not correctly aligned for std140, you will need to be careful when using it.

This is how the data is actually laid out:

struct InstanceData
{
  vec3  rotation;  // 0,1,2
  float padding03; // 3

  vec3  scale;     // 4,5,6
  float padding07; // 7

  mat4  position;  // 8-23
} // Size: 24 * sizeof (float)

vec3 types are treated the same as vec4 in GLSL and mat4 is effectively an array of 4 vec4, so that means they all need to begin on 4-float boundaries. GLSL automatically inserts padding to satisfy these alignment rules; the changes I made above are to show you the correct way to write this data structure in C. You have to account for 2 floats worth of implicit padding in your structure.


Regarding your edit, you do not have to worry about how GLSL packs a struct until you start using buffer objects.

Without using a Uniform Buffer Object, you have to use functions like glUniform3f (...) to set uniforms. Those functions do not directly expose the data structure to you, so packing does not matter. To set the values for an instance N you would call glUniform3f (...) using the location of "instances [N].rotation" and "instances [N].scale" and glUniformMatrix4fv (...) on "instances [N].position".

That would require 300 API calls to initialize all 100 instances of your struct, so you can see why UBOs are more practical (even ignoring the above mentioned 1024 limitation).

Upvotes: 8

Related Questions