vec3 and floats in UniformBufferObjects using std140 layout

Started by karlchendeath, April 11, 2021, 15:19:44

Previous topic - Next topic

karlchendeath

Hi, so i have been playing around with uniform buffer blocks for a while know, but i always avoided vec3 by using vec4 because for them i understood that with vec4 every vector should have 16 bytes. Now iam accually need to work with vec3 and for my understanding in std140 everything is a multiple of 4 of its underlying type so i assumed that a vec3 should therefor have 16 bytes alloceted, but for some reason this doesn't work.

I create my UBO using this Code:

where layout is a String[] with something like this {"mat4 view", "mat4 proj"} for a simple uniformBlock with the view and projection matrix (in order)
and uniformOffsets is a HashMap<String, Integer>
        int uboByteSize = 0;
        for (int i = 0; i < layout.length; i++) {
            String[] split = layout[i].split(" ");
            if (split.length != 2) throw new AspectLayoutException("illegal ubo layout");
            final String varType = split[0];
            final String varName = split[1];

            int baseAlignment;
            int count;

            switch (varType) {
                case "mat4":
                    baseAlignment = 16;
                    count = 4;
                    break;
                case "vec4":
                case "vec3":
                    baseAlignment = 16;
                    count = 1;
                    break;
                case "float":
                    baseAlignment = 4;
                    count = 1;
                    break;
                default:
                    throw new AspectLayoutException(varType + " is not defined");
            }

            int aligned_offset = (int) Math.ceil(uboByteSize / baseAlignment) * baseAlignment;
            uboByteSize = aligned_offset + baseAlignment * count;

            uniformOffsets.put(varName, aligned_offset);
        }

        System.out.println(uniformOffsets);

        ID = glGenBuffers();
        glBindBuffer(GL_UNIFORM_BUFFER, ID);
        glBufferData(GL_UNIFORM_BUFFER, uboByteSize, GL_DYNAMIC_DRAW);
        glBindBuffer(GL_UNIFORM_BUFFER, 0);


when updating uniforms i use depending on the type. something like this:
        //load a matrix4f (mat4) in the uniform buffer.    mat4 is the Matrix4f to load in the ubo
        Integer offset = uniformOffsets.get(uniformName);
        if (offset == null)
            throw new AspectGraphicsException("can't uniform " + uniformName + " in this uniform block");
        float[] mat4_array = new float[16];
        mat4.get(mat4_array);
        glBindBuffer(GL_UNIFORM_BUFFER, ID);
        glBufferSubData(GL_UNIFORM_BUFFER, offset, mat4_array);
        glBindBuffer(GL_UNIFORM_BUFFER, 0);


So when i use this code with vec3 i get for my understanding the correct result of offets
for a ubo with std140 (16 bytes per vec3):

layout(std140, binding=3) uniform mat{
    vec3 albedo;
    float metallic;
    float roughness;
    float ao;
}


i would use the code above with layout = {"vec3 albedo", "float metallic", "float roughness" , float "ao"}
and when i would look at the uniformOffsets map i get this:
{roughness=20, metallic=16, ao=24, albedo=0}.

Maybe it's just my understanding about std140, but in the shader the stuff that load in the ubo is not correct
albedo is obviously right, but there the offeset is obvious. The rest metallic, roughness, ao have some weird values when running the shader.

If you need more information, just ask =^). Thank you

KaiHH

Understanding GLSL layout rules can be a bit tricky at first, yes. Generally, there is a difference between the size of a data type and the alignment requirements of the data type.
For example: The size of a vec3 is three floats, so exactly 12 bytes. But the alignment is 16 bytes.
The alignment dictates, at which multiples of a byte the next value for a given type may begin. The alignment of a vec3 is 16 bytes, so whenever you declare a vec3, it will be padded before the vec3 starts to make sure that the vec3 value begins at a multiple of 16 bytes.

Example:
struct {
  float a;
  vec3 b;
}


Here, the float will start at offset 0 bytes, but due to the alignment requirements for a vec3 (which is 16 bytes), there will be a 12 bytes padding after the float and before the vec3.
So, effectively, the float will become a vec4, of which only the first component is actually used for the float. And after the 16 bytes (4 bytes of float and 12 bytes of padding after the float), there will be the vec3 b.

So, in this particular case, it is more space-efficient to redeclare the vec3 b before the float a:
struct {
  vec3 b;
  float a;
}

In this case, the alignment requirements for all struct members' data types are naturally fulfilled: The vec3 starts at offset 0 (which is a multiple of 16, namely 0).
And the float b starts at offset 12 bytes, so directly after the vec3's size (which is 12 bytes).
Float itself has an alignment requirement of 4 bytes, so it can start right after the 12 bytes of the vec3.

Another example:
struct {
  vec3 a;
  vec3 b;
  float c;
}

Here, the vec3 a starts at byte 0. And, since vec3 has alignment requirement of 16 bytes, the vec3 b will start at byte offset 16. So, the vec3 a will be padded with an additional 4 bytes after it, because the next vec3 b cannot start at byte offset 12, since that would violate its alignment requirements. The float c itself has alignment requirements of 4 bytes, so can start right after the vec3, so at byte offset 16+12 = 28.

So, it's never about the size of a data type, but only about the alignment requirements (where the next instance of a particular member of that data type may start/begin).

karlchendeath

Okay i think i got it am i right in believing. That the start offset of any variable has to be a multiple of it's alignment.
one question for calarification about alignments
vec3 have a alignment of 16 the same as vec4
what is the alignment of a mat4 or mat3 and what's about vec2 i believe i saw somewhere that it should be vec2 is that correct?

KaiHH

QuoteAm I right in believing that the start offset of any variable has to be a multiple of its alignment?
Correct.

QuoteWhat is the alignment of mat4 and mat3 and what about vec2?

mat4 is treated as four separate vec4's. So, the alignment of a mat4 is like that of a vec4, which is 16 bytes. And the size of a mat4 is 4*sizeof(vec4) = 4*16 bytes.
mat3 is treated as three vec4's. So, alignment is 16 bytes and size is 3*16 bytes. So, you cannot pack a float as the w-component of the last column of a mat3.
vec2 has alignment 8 bytes and size 8 bytes.

See: https://sotrh.github.io/learn-wgpu/showcase/alignment/#alignments