spraff
spraff

Reputation: 33395

Dynamic-length arrays as Shader Storage Buffer Objects

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?

Upvotes: 4

Views: 9738

Answers (1)

BDL
BDL

Reputation: 22167

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)

Upvotes: 5

Related Questions