1

So I've read about Storage Buffers being able to contain a variable length array at end:

SSBOs can have variable storage, up to whatever buffer range was bound for that particular buffer; UBOs must have a specific, fixed storage size. This means that you can have an array of arbitrary length in an SSBO (at the end, rather). The actual size of the array, based on the range of the buffer bound, can be queried at runtime in the shader using the length function on the unbounded array variable

I know how to pass a storage buffer as a struct with simple fields like this:

struct GPUStorage {
   glm::vec3 mesh_pos;
   glm::vec4 mesh_rot;
};

And know how to pass a storage buffer as an array of structs by shoving them into a vector and do memcpy on vector.data() with copy length as sizeof(GPUStorage) * vector.size().

But I haven't found anywhere how does the C++ syntax look for a struct containing a variable length array ?

struct GPUMesh {
    glm::vec3 mesh_pos;
    glm::vec4 mesh_rot;
};

struct GPUStorage {
    ???  // variable length array of GPUMesh 
};
  • `C++` does not have VLAs (except as non-standard compiler extensions). `C` does have VLAs. – Richard Critten Dec 06 '19 at 20:28
  • 1
    "*I know how to pass a storage buffer as a struct with simple fields like this:*" [Please stop using `vec3`.](https://stackoverflow.com/q/38172696/734069) – Nicol Bolas Dec 06 '19 at 20:46
  • `alignas` seems to work fine if you put it in front of a `struct`'s fields, also does that 16 byte alignment apply to storage buffers as well ? – Măcelaru Tiberiu Dec 07 '19 at 07:54

1 Answers1

4

You have tricked yourself into thinking of things in a very limited way. Namely, that the only way to use a resource buffer (UBO/SSBO) from C++ is to define a C++ object type whose layout matches that of the resource. It's not.

The layout of a buffer-backed interface block defines how GLSL will interpret the bytes of data provided by the buffer. How those bytes get in those positions is entirely up to you.

A storage block defined as:

layout(binding = 0, std430) buffer Data
{
  vec2 first;
  vec4 array[];
};

The layout of this data is such that the first 8 bytes represent two floating-point values. Following this is 8 bytes that are skipped, followed by some multiple of 16 bytes with each 16-byte datum representing 4 floating-point values.

How you create that in the buffer object is up to you. You could do this:

std::size_t numArrayEntries = <get number of array entries>;
glNamedBufferStorage(buff, 16 + (16 * numArrayEntries), nullptr, GL_DYNAMIC_STORAGE_BIT);

glm::vec2 first = <get first>;
glNamedBufferSubData(buff, 0, 8, glm::value_ptr(first));

for(std::size_t entry = 0; entry < numArrayEntries; ++entry)
{
  glm::vec4 entryValue = <get actual entry value>;
  glNamedBufferSubData(buff, 16, 16, glm::value_ptr(entryValue));
}

That's obviously heavy-handed and involving a lot of uploading, but it is serviceable. You could also get the data there via mapping the buffer:

std::size_t numArrayEntries = <get number of array entries>;
std::size_t buffSize = 16 + (16 * numArrayEntries)
glNamedBufferStorage(buff, buffSize, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);

unsigned char *data = (unsigned char*)glMapNamedBufferRange(buff, 0, buffSize, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);

glm::vec2 first = <get first>;
memcpy(data, glm::value_ptr(first), 8);

for(std::size_t entry = 0; entry < numArrayEntries; ++entry)
{
  data += 16
  glm::vec4 entryValue = <get actual entry value>;
  memcpy(data, glm::value_ptr(entryValue));
}

Or by building a temporary memory buffer first and doing the copy when you create the storage. Or any other technique.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    I was afraid of this. So basically ditch `struct` wrapper and compose what to transfer in a `void*` with `memcpy`'s – Măcelaru Tiberiu Dec 07 '19 at 08:20
  • 1
    @MacelaruTiberiu: Well presumably, the reason you want to have an unbounded array is because you don't know at compile-time how big that array is going to be, right? `struct`s have a size that is *fixed* at compile-time. So there was never going to be a way to just `memcpy(sizeof(some_struct))` or whatever. Also, my overall point is that you can get the bytes there however you want. If for some reason you want GLSL to have a variable-size, but you're fine with having a compile-time fixed size in C++, you can just do that. – Nicol Bolas Dec 07 '19 at 15:46
  • Well, if you were using C you could use a variable-length array at the end of a struct. But C++ lacks that nicety. – Andrea Dec 07 '19 at 20:58