The vec3
type is a very nice type. It only takes up 3 floats, and I have data that only needs 3 floats. And I want to use one in a structure in a UBO and/or SSBO:
layout(std140) uniform UBO
{
vec4 data1;
vec3 data2;
float data3;
};
layout(std430) buffer SSBO
{
vec4 data1;
vec3 data2;
float data3;
};
Then, in my C or C++ code, I can do this to create matching data structures:
struct UBO
{
vector4 data1;
vector3 data2;
float data3;
};
struct SSBO
{
vector4 data1;
vector3 data2;
float data3;
};
Is this a good idea?
NO! Never do this!
When declaring UBOs/SSBOs, pretend that all 3-element vector types don't exist. This includes column-major matrices with 3 rows or row-major matrices with 3 columns. Pretend that the only types are scalars, 2, and 4 element vectors (and matrices). You will save yourself a very great deal of grief if you do so.
If you want the effect of a vec3 + a float, then you should pack it manually:
Yes, you'll have to use
data2and3.w
to get the other value. Deal with it.If you want arrays of
vec3
s, then make them arrays ofvec4
s. Same goes for matrices that use 3-element vectors. Just banish the entire concept of 3-element vectors from your SSBOs/UBOs; you'll be much better off in the long run.There are two reasons why you should avoid
vec3
:It won't do what C/C++ does
If you use
std140
layout, then you will probably want to define data structures in C or C++ that match the definition in GLSL. That makes it easy to mix&match between the two. Andstd140
layout makes it at least possible to do this in most cases. But its layout rules don't match the usual layout rules for C and C++ compilers when it comes tovec3
s.Consider the following C++ definitions for a
vec3
type:Both of these are perfectly legitimate types. The
sizeof
and layout of these types will match the size&layout thatstd140
requires. But it does not match the alignment behavior thatstd140
imposes.Consider this:
On most C++ compilers,
sizeof
for bothBlock_a
andBlock_f
will be 24. Which means that theoffsetof
b
will be 12.In std140 layout however,
vec3
is always aligned to 4 words. And therefore,Block.b
will have an offset of 16.Now, you could try to fix that by using C++11's
alignas
functionality (or C11's similar_Alignas
feature):If the compiler supports 16-byte alignment, this will work. Or at least, it will work in the case of
Block_a
andBlock_f
.But it won't work in this case:
By the rules of
std140
, eachvec3
must start on a 16-byte boundary. Butvec3
does not consume 16 bytes of storage; it only consumes 12. And sincefloat
can start on a 4-byte boundary, avec3
followed by afloat
will take up 16 bytes.But the rules of C++ alignment don't allow such a thing. If a type is aligned to an X byte boundary, then using that type will consume a multiple of X bytes.
So matching
std140
's layout requires that you pick a type based on exactly where it is used. If it's followed by afloat
, you have to usevec3a
; if it's followed by some type that is more than 4 byte aligned, you have to usevec3a_16
.Or you can just not use
vec3
s in your shaders and avoid all this added complexity.Note that an
alignas(8)
-basedvec2
will not have this problem. Nor will C/C++ structs&arrays using the proper alignment specifier (though arrays of smaller types have their own issues). This problem only occurs when using a nakedvec3
.Implementation support is fuzzy
Even if you do everything right, implementations have been known to incorrectly implement
vec3
's oddball layout rules. Some implementations effectively impose C++ alignment rules to GLSL. So if you use avec3
, it treats it like C++ would treat a 16-byte aligned type. On these implementations, avec3
followed by afloat
will work like avec4
followed by afloat
.Yes, it's the implementers' fault. But since you can't fix the implementation, you have to work around it. And the most reasonable way to do that is to just avoid
vec3
altogether.Note that, for Vulkan (and OpenGL using SPIR-V), the SDK's GLSL compiler gets this right, so you don't need to be worried about it for that.