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?
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)