UniformBuffer.addUniform: Arguments unclear

Hello together,
I am working on a rather complex shader setup and need to pass lots of data a shader and want to use Uniform buffers. I create a UBO containing an array where each elements is of type of a struct consisting of two vec4.

 struct ElementInBuffer { // I assume an element size of 8
    vec4 a; 
    vec4 b; 
  };

  layout(std140) uniform MyBuffer
  {
    ElementInBuffer elementsArray[2];
  } myBuffer;

I am adding this interface block to my UBO using addUniform. I assume that I need to call addUniform as follows:

    buffer.addUniform('elements', 8, 2); // 8 -> 2*vec4, 2 -> 2 elements in the arry

However, this leads to an error message of a buffer beeing to small. I tried a few other combinations that magically work, such as 16,2 or 8,4.

Looking into the source code:

I see that the case of size = 16 is handled differently, so this could be the reason why this works. The magic number of 16 only makes sense for me if we are counting bytes and then indeed 16 is the size of a vec4 assuming 32 bit floats.

I also tried to figure out whether the calculation of the buffer might go wrong for my cases but I cannot proof that anything goes wrong even if l. 377 look suspicious to me as size is actually substracted from perElementPadding and might get negative.

However, both of my examples lead to a correct buffer size of 16 if we assume the buffer size measured in full floats and not in bytes, so the problem must somewhere else:

size = 8, arraySize = 4:

// const totalPadding = perElementPadding * arraySize;
const totalPadding = -4 * 4; // -> -16
// size = size * arraySize + totalPadding;
size = 8 * 4 + -16; // 16

size = 8, arraySize = 2:

// const totalPadding = perElementPadding * arraySize;
const totalPadding = 2 * 4; // -> 8
// size = size * arraySize + totalPadding;
size = 8 * 2 + 8; // 16

I prepared a playground:

What you can do with it:

  • The offending line is 67: Here you can change 16 to elementSize and see that the program breaks.
  • You can see that the buffer elements are actually accessible using a stride of 8 by changing l. 31 and access either element 0 or 1 and either member a or member b and figure out than you can access all the colors added from l.73 to l.94.
    One might argue that a value of 16 actually makes sense as it is the total size of the buffer. However, I think this is just a coincidence as reducing the number of elements to the array to 1 and then reducing the array size to 8 as well will also break the application.

What do you think? Did I get something fundamentally wrong here or is there indeed a bug?

Best regards,
Axel

You could leverage two texture buffers perhaps?

What are you trying to accomplish, just trying to understand your end goal so I can point you in the right direction.

Also after reading the documentation it looks like you could do buffer.addUniform(“elements”, dataArray, itemCount)

The addUniform function only handles types like float, vec2, vec3, vec4, mat4, meaning size=1,2,3,4 or 16. It does not handle struct types.

What you can do is telling addUniform that the element is vec4 (so size=4) and pass in arraySize the total number of vec4 (4 in your case):

2 Likes

Hi @Evgeni_Popov ,
thank you for your solution. So my understand is basically right, however, there is no “proper” way to model my usecase on top of the current UBO API. However, passing a “4” to elementSize effectively disables any padding logic and, as internally, the buffer size is elementSize * arraySize, I need to multiply to the arraySize what I divided out before on the elementSize. This works for me, however I am willing to improve the situation:

I see that this might be a rare usecase, however, it is legal to do so in GLSL. I see two ways to work on this topic:

  • Add a little bit of documentation what this method is intended for, mention the limitations and how to work around them, basically the conclusion of this thread here.
  • Modify the API a little bit, e.g. allow a zero as the elementSize so that the user of the API can state that he wants to bypass the padding logic.

Discussion:

  • The most verbose option to model the GLSL struct also in javascript using a similar API as the UBO API (addFloat, etc.) and then use the constructed struct in addUniform seems complicated. In the end, the user will have to keep both definitions in sync. AFAIK, it is not possible to share a struct definition between javascript/typescript and GLSL. I have seen something like this only in the Metal API. The only way I can think of is creating the struct in JS and then create a GLSL include from that.

@Pryme8 I am porting scientific application from C++ to Javascript/babylon that uses shaders to do some physics calculations. Probably no standard usecase.

What do you think?

1 Like

I will let @Deltakosh / @sebavan decide what to do. My opinion is that in a first time we should simply update the documentation and see later if we should improve the API, depending on user request(s).

Note that even if the API is updated the user must still fill the array buffer correctly and add padding at the right places when/if necessary, meaning they must have some knowledge of UBO padding in GLSL (and in WebGPU if using this engine). To overcome this problem, we would need to model the struct in javascript as you described in the discussion paragraph, but that seems a bit overkill to me.

1 Like

I am all for this one knowing the underlying complexity and the rarity of the use case.

1 Like

I do agree. Let’s first document it (@sebavan ok to do it when you can?)

1 Like

Added more doc to the addUniform function.

1 Like