Weird struct padding issue - what am I doing wrong?

Started by
13 comments, last by NathanRidley 9 years, 2 months ago
I've been learning to use shader storage buffers, instancing, and multitexturing and while I'm pretty sure I've got the basics mostly figured out, I'm currently hitting a weird issue that is not making any sense to me.

My demo renders a bunch of cubes to the screen, each with a random scale, rotation and position. Each one is textured, and tinted a certain shade of red. I've just figured out how to have multiple textures available in the same shader pass, so I was going have some of the cubes render with one texture, and some of them with the other. Here's the original instance structure I was using:

struct CubeInstance
{
    glm::vec4 rotation;
    glm::vec4 scale;
    glm::vec4 position;
};

...

// quick and dirty way to fill the array with random data

srand(clock());
for(int i = 0; i < cubesData->totalInstances; i++)
{
    float rotX = rand()%200-100;
    float rotY = rand()%200-100;
    float rotZ = rand()%200-100;
    float rotS = (rand()%190 + 10)/100.0f;
 
    float posX = (rand()%10000 - 5000)/100.0f;
    float posY = (rand()%10000 - 5000)/100.0f;
    float posZ = (rand()%10000 - 5000)/100.0f;
 
    float scale = (rand()%400 + 10.0f)/100.0f;
 
    cubesData->instances[i] = CubeInstance
    {
        glm::vec4(rotX, rotY, rotZ, rotS),
        glm::vec4(scale, scale, scale, 1),
        glm::vec4(posX, posY, posZ, 0)
    };
}

...

// here's the bit of code with which I'm attaching the instance data to my shader program:

glGenBuffers(1, &cubesData->instanceBufferId);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, cubesData->instanceBufferId);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(cubesData->instances), &cubesData->instances, GL_STREAM_DRAW);
GLuint blockIndex = glGetProgramResourceIndex(cubesData->shaderProgramId, GL_SHADER_STORAGE_BLOCK, "instance_data");
glShaderStorageBlockBinding(cubesData->shaderProgramId, blockIndex, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, cubesData->instanceBufferId);

And in my shader:

struct InstanceData
{
    vec4 rotation;
    vec4 scale;
    vec4 position;
};

layout (std430) buffer instance_data
{
    InstanceData instances[];
};
 
...

void main(void)
{
    InstanceData instance = instances[gl_InstanceID];
...


All good, that works fine. Here's what it looks like at this stage:

2015-02-14_1643.png


So then I went to introduce a fourth component to represent which texture each instance should use. Given that it starts on a 4-byte boundary, I figured it would be safe to just make it an int, in both the C++ code and the shader code. Obviously not, because I got a huge mess of screwed up rendering on the next run. Fair enough, so I added a padding vector of glm::vec3 at the end, and an equivalent padding vec3 in the shader code, and now for some really weird reason I'm ending up with a big stack of overlaid cubes rendering right at the origin for no apparent reason, in addition to the normal (albeit reduced-in-number) bunch of cubes normally found scattered around the screen.

My updated code has the following changes:

struct CubeInstance
{
    glm::vec4 rotation;
    glm::vec4 scale;
    glm::vec4 position;
    int material;       // ADDED THIS
    glm::vec3 _pad0;    // ADDED THIS
};

...

// added to my random data generation:

int material = rand()%2;  // ADDED THIS

cubesData->instances[i] = CubeInstance
{
    glm::vec4(rotX, rotY, rotZ, rotS),
    glm::vec4(scale, scale, scale, 1),
    glm::vec4(posX, posY, posZ, 0),
    material,           // ADDED THIS
    glm::vec3(0)        // ADDED THIS
};

and my updated shader code:

struct InstanceData
{
	vec4 rotation;
	vec4 scale;
	vec4 position;
	int material;       // ADDED THIS
	vec3 _pad0;         // ADDED THIS
};

And just from these changes, I now have a bunch of weird giant cubes appearing the centre of the scene.

2015-02-14_1611.png


Any ideas what I'm doing wrong?
Advertisement
You say it has random scale, but in your first image it doesn't look like there is any randomness in scale at all. If anything, the second one looks more correct, even if there are only two sizes. Check scale and see if any unintended integer division crept up in it.

You say it has random scale, but in your first image it doesn't look like there is any randomness in scale at all. If anything, the second one looks more correct, even if there are only two sizes. Check scale and see if any unintended integer division crept up in it.


No, the first one is correct, the scale variance is only slight though. Take another look at the first image - you can see smaller cubes in front of larger cubes. I have run numerous tests where I increase the range of randomness and the range of sizes that the cubes are rendered at increases accordingly. So I don't think that's related to the issue...

Note also that the second image has less cubes because the big cube at the centre is actually a whole lot of cubes overlaid together. These are cubes that would normally have appeared scattered around more freely, as per the first image.

EDIT: note that if I remove the int and vec3 fields and replace them with a vec4, everything renders correctly! So why can't I have an int, which should be 4 bytes, followed by a vec3, which should be 12 bytes? Is there some kind of data size mismatch somewhere that I'm not seeing?

EDIT 2: I left vec4 in on the C++ code side of things, and replaced the glsl struct code with int and vec3, which, in combination, should be the same size as the vec4, and the problem shows up again. I'm sure I must be misunderstanding something somewhere.

You say it has random scale, but in your first image it doesn't look like there is any randomness in scale at all.


Just as a quick follow up, to better demonstrate my earlier point, I adjusted the range of randomness, which you can see below, so that's not the issue.

2015-02-14_1840.png
By having it random you are only making it more difficult to debug, at least comment out the srand call...
Every time you make an assumption in your code, you need an assertion there to prove that the assumption is valid (and also document the assumption).
For the assumptions that your C++ struct matches your GLSL struct, you can use static_assert, offsetof and sizeof.
e.g.
static_assert( offsetof( CubeInstance, material ) == 48, "bad aligment assumption" );
static_assert( offsetof( CubeInstance, _pad0 ) == 52, "bad aligment assumption" );
static_assert( sizeof( CubeInstance ) == 64, "bad aligment assumption" );
static_assert( sizeof( glm::vec3 ) == 12, "bad GLM assumption" );
static_assert( alignof( glm::vec3 ) == 4, "bad GLM assumption" );
...
Hodgman, thanks, I tried that and my assumptions were correct. MSVC 2013 doesn't seem to support alignof it seems...

The problem though appears to be in the shader. Swapping out a single vec4 for an int and a vec3 causes the issue, though I have no idea why, and I don't know how to perform the glsl-equivalent of a static_assert to challenge my assumptions inside the shader.
Here's what it looks like with the scale multiplier hardcoded to 1.0 and the number of instances reduced to 200:

2015-02-14_1928.png
So I replaced the glm::vec3 padding with float[3] and the problem went away. I must say I am confused why this solved the problem, as I can't imagine why glm::vec3 would be anything other than 12 bytes, particularly given the static_assert to that effect. Oh well...

Have you tried swapping int and vec3? Vec3 in std layouts must be aligned to a 16 byte boundary.

From the spec https://www.opengl.org/registry/doc/glspec45.core.pdf#page=159

7.6.2.2 Standard Uniform Block Layout

If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N.

Furthermore, from https://www.opengl.org/wiki/Interface_Block_%28GLSL%29

Warning: Implementations sometimes get the std140? layout wrong for vec3? components. You are advised to manually pad your structures/arrays out and avoid using vec3? at all.

This topic is closed to new replies.

Advertisement