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).