Regarding the "weird data transfer"
I encourage you to read the relevant part of the OpenGL 3.3 specification. Believe me, everything is fine there, it's not a bug it's a feature. Although it can be headaching to remember and apply the rules there.
In chapter 2.11.4 "Uniform Variables" under section "Standard Uniform Block Layout" it says:
By default, uniforms contained within a uniform block are extracted from buffer storage in an implementation-dependent manner. Applications may query the offsets assigned to uniforms inside uniform blocks with query functions provided by the GL.
This means that you cannot simply neatly pack your vec2's, vec3's and floats in a Java ByteBuffer according to the uniform block specification in your GLSL shader and then expect everything to work out of the box.
The members of a uniform block instead have certain alignment requirements that must be met and OpenGL expects some padding bytes here and there so that those alignment requirements are met. By the way, you might have noticed that the Demo33Ubo was in effect writing five vec4's instead of five vec3's (as specified by the uniform block in the shader) into the ByteBuffer, because of the alignment requirements that I want to explain here throughout this long post.
Now, because the default behaviour is implementation-dependent, the first thing we should do is adding a "layout(std140)" to your uniform block in the shader, like so:
layout(std140) Lights {
Light[1] lights;
};
This enables a standard layout that is specified as a numbered list of rules in the mentioned OpenGL 3.3 spec in chapter 2.11.4 "Uniform Variables" under section "Standard Uniform Block Layout":
https://www.opengl.org/registry/doc/glspec33.core.20100311.withchanges.pdf (PDF page 72).
Now, let's excercise the rules for your struct to see which struct members are going to end up at which byte offset in your ByteBuffer:
In your example, you have a vec2, then a vec3 and afterwards two floats. All those are packed in a struct, so rule 9 of the spec fires first, specifying how to go about structs. It says:
If the member is a structure, the base alignment of the structure is N, where N is the largest base alignment value of any of its members, and rounded up to the base alignment of a vec4.
This sentence is relevant if we have more members than this single struct in our uniform block (either sequentially written as individual members or as array elements).
Since for you this is not the case, let's read on:
The individual members of this substructure are then assigned offsets by applying this set of rules recursively, where the base offset of the first member of the sub-structure is equal to the aligned offset of the structure. The structure may have padding at the end; the base offset of the member following the sub-structure is rounded up to the next multiple of the base alignment of the structure.
This basically says: Now, we are going to apply the rules for each of the struct members.
So, let's begin with our first member, a vec2. We are now at offset 0, because this is our first member to consider. You always need to have that "offset" value in mind when going through your members and applying alignment and padding. So, our first vec2 starts at offset 0.
Now, we search for an alignment rule and find rule number 2:
If the member is a two- or four-component vector with components consuming N basic machine units, the base alignment is 2N or 4N, respectively.
Since we have a vec2, our base alignment is 2*float = 8 bytes. Now we need to know what to do with this "base alignment" value. This was mentioned in the introductory paragraph of the section:
A structure and each structure member have a base offset and a base alignment, from which an aligned offset is computed by rounding the base offset up to a multiple of the base alignment. The base offset of the first member of a structure is taken from the aligned offset of the structure itself.
This lays out a complicated formula for computing the actual offset of a struct/uniform block member. We always need to compute the "aligned offset", which is the effective byte position in our ByteBuffer where we need to store the member. In the case of the first member of our single struct, this is 0.
Next, we go to the second struct member, the vec3. Here, rule number 3 applies:
If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N.
Aha! So, a vec3 is basically treated as a vec4 in memory!
To compute the aligned offset of this vec3 we need to apply the complicated formula from above:
A structure and each structure member have a base offset and a base alignment, from which an aligned offset is computed by rounding the base offset up to a multiple of the base alignment. [...] The base offset of all other structure members is derived by taking the offset of the last basic machine unit consumed by the previous member and adding one.
So, the (actual) offset of "the last basic machine unit" (i.e. "byte") was 7 (i.e. the last byte of the second component of our first vec2).
We need to add one, which gives us 8. Now, we need to round that 8 up to a multiple of the base alignment. The base alignment for a vec3 is the same as for a vec4, namely 16 bytes. So, rounding up 8 to the next multiple of 16 gives us 16. And that is the offset at which we will store our vec3. *Phew....*
I will leave you with the (simple) computation of the remaining two floats, which are effectively just packed behind the vec3, so the first float starts at offset 28 and the second at 32.
Now that we've gone through all that trouble of computing the offsets manually, there is actually a simpler rule stated by many others to remember. You simply layout your members in such a way that they always fit into vec4 "slots". So, your first vec2 did fit into a vec4 slot, leaving two remaining components unused. Now, when we try to squeeze the next vec3 into there, it won't work, since we are short of the space for a single float-component.
So, we use "the next" vec4 slot for our vec3. The next member, the float, also fits into the last component of the second vec4 slot.
And the last float gets its own vec4 slot.
I hope that makes it all clearer!
All the best!
Kai