3

Let's say I have a dynamic number of "balls" which I want to access in my OpenGL shaders. In C++ the data might be like this:

struct Ball
{
    glm::vec3 position;
    glm:vec3 colour;
    float size;
};

std::vector<Ball> all_balls;

If I want to iterate over all_balls in my fragment shader, I believe I will need a Shader Storage Buffer Object.

This documentation touches on arrays, but is notably incomplete.

I assume I can send the data to the buffer like this

glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, all_balls.size() * sizeof(Ball), &(all_balls[0]), usage);

In GLSL, how do I specify that the buffer is an array, and how does my shader know the size of this array?

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
spraff
  • 32,570
  • 22
  • 121
  • 229
  • 1
    Further read [Should I ever use a `vec3` inside of a uniform buffer or shader storage buffer object?](https://stackoverflow.com/questions/38172696/should-i-ever-use-a-vec3-inside-of-a-uniform-buffer-or-shader-storage-buffer-o) – Rabbid76 Sep 01 '19 at 09:45
  • 1
    @Rabbid76 that wiki page is incomplete, it literally says "TODO: This section needs to be filled in" just as it starts to talk about this topic. How does my shader know the size of this array? – spraff Sep 01 '19 at 09:49

1 Answers1

3

When working an array with a length that is not a compile time constant, one can declare a member of a SSBO Interface Block to be of undetermined length.

Assuming that there exists a GLSL structure that fits to the C++ ball struct, the code can look somehow like this:

struct GLSLBall {...};

layout(std430, binding = 0) buffer BallBuffer
{
    GLSLBall ball_data[];
}

You can than iterator over all elements like this:

for (int i = 0; i < ball_data.length(); ++i)
{
    GLSLBall currentBall = ball_data[i];
}

When the number of elements changes very often, then I suggest not to resize/reallocate the SSBO every time, but to reserve a large enough buffer once and pass the number of elements actually used to the shader. This can either be an independent uniform variable (uniform uint ballCount;), or you can pack it into the SSBO itself like this:

struct GLSLBall {...};

layout(std430, binding = 0) buffer BallBuffer
{
    uint ball_length;
    GLSLBall ball_data[];
}

Then you can allocate the memory only once:

glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, ENOUGH_MEMORY_FOR_ALL_CASES, null, usage);

and upload the data every time the content changes like this:

glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(unsigned int), (unsigned int)all_balls.size());
glBufferSubData(GL_SHADER_STORAGE_BUFFER, sizeof(unsigned int), all_balls.size() * sizeof(Ball), &(all_balls[0]));

The glsl loop is then similar to

for (int i = 0; i < BallBuffer.length; ++i)
{
    GLSLBall currentBall = ball_data[i];
    ...
}

Please note, that you current C++ struct layout might cause some troubles with alignment due to the use of vec3. You might want to read Should I ever use a vec3 inside of a uniform buffer or shader storage buffer object? (thanks to Rabbid76 for the hint)

BDL
  • 21,052
  • 22
  • 49
  • 55
  • When you use `sizeof(unsigned int)` as the offset in `glBufferSubData`, I thought element offsets were rounded up to multiples of `sizeof(vec4)`. Is that right? – spraff Sep 01 '19 at 10:07
  • 1
    @spraff: No, why would it? Even with a 140 layout, padding would only be relevant for vec3, arrays and for members inside of struct. – BDL Sep 01 '19 at 10:20
  • @spraff I'm using vulkan but I've tried to acheive the same thing. I DID actually have to round up to a size(vec4) to make this work. (not sure what the difference is between vulkan/gl, but if anyone reads this, it might be useful – Chris Bamford Nov 24 '21 at 15:12