4

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?

2

1 Answer 1

5

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)

Sign up to request clarification or add additional context in comments.

3 Comments

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: No, why would it? Even with a 140 layout, padding would only be relevant for vec3, arrays and for members inside of struct.
@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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.