I've a lots of objects, which have the same geometry. Right now I am using instanced rendering to render these objects. This is working very well. But I am trying to increase the performance of the program.
I only want to render objects, which are in front of my camera. So I simply added a compute shader, which makes an intersection test of camera frustum and the object (position and bounding radius). Afterwards I only want to render (instanced) the objects, which are visible by indirect rendering.
I have three SSBOs:
- List of objects (Position, radius). This list is created once and will not change.
- List of visible indices (each object, which is inside the view frustum will add its index (from list 1) to this list. The position to add the index is given by SSBO three
- One integer (atomic counter). While the visibility test is executing, each visible object executes an "atomicAdd(+1)" on this integer to receive the position to store its index (list 1 index) to position in list 2.
So far so good. But how can I execute a draw call which is instanced and indirect? I don't want to load the third SSBO to CPU to execute an instanced draw call with its number. (I am trying to avoid GPU->CPU->GPU).
Alternatively, I can make an instanced draw call of all objects and read the atomic counter in SSBO three. When the gl_InstanceID is less than this value I can render the object. Else I discard the object within the tessellation control stage. This way is very slow, because each object has a lot of vertices which will be executed during the vertex and the tessellation control stage which is completely useless.
How to execute an instanced indirect draw call? (Element draw call) Is there a command for that?
If so, How do I need to store my third SSBO, so that the number of the atomic counter many objects will be drawn?
It seems like that the idea of the three SSBOs are not well understood. So I'll try to explain it in more detail:
The first SSBO stores the information of all objects (position, radius). These objects will not move or be changed at all.
The second SSBO will be filled by the compute shader. (before starting the compute shader this list will be cleared.) The length of this list is the number of all objects (which have the same geometry). The compute shader will test, which of the objects in the first SSBO is in front of the camera. And stores the index of the first SSBO with help of an atomic counter (SSBO three) separately in the second list, in case it is visible.
The third SSBO (the atomic counter) will be cleared before the compute shader starts. When the compute shader has finished its work, this counter gives me the number of objects which are visible.
Right now I can choose two ways to only render the visible objects:
1: (the first way I can render)
Instance rendering all objects (visible and invisible). In the vertex shader stage check of the
gl_InstanceID is less than the value in SSBO three (the atomic counter). In case it is less than the value, the object can be rendered by reading the second SSBO at index [
gl_InstanceID] to get the information of which object is this vertex belonging to. Then read the objects parameters from the first SSBO at index [value of SSBO 2].
In case the
gl_InstanceID is equal or larger than the atomic counter from SSBO three, this object do NOT need to be rendered => discard this object.
The rendering program of the objects have the following stages: Vertex-, tessellation (control- and evaluation-) and fragment- shader.
The tessellation stage adds geometry detail depending on the distance to the camera. In case this object should be discarded (not visible) the
gl_TessLevelInner values will be set to 0. So the this face will be discarded.
2: (the second way I can render)
Alternatively to the first way, I can load the information from the atomic counter (SSBO three) to only instance render the correct number of objects. BUT: here I have to load the information to CPU. So: GPU->CPU->GPU communication.
When rending this way, the tessellation control shader does NOT need to discard anything, because only the visible objects will be rendered. The rest of the shader program code is the same as in the first way to render.
The first way to render does not need to load the third SSBO from GPU to CPU. So the communication (GPU->CPU->GPU) is NOT used. This is a performance boost. BUT: The vertex and tessellation control stages are executed way more often than in the second way to render. The vertex shader stage needs to load the atomic counter SSBO value to check if this vertex is relevant which might be causing branching. These are performance losses.
The second way to render need to load the third SSBO from GPU to CPU. So the communication (GPU->CPU->GPU) is used, which is a performance loss. BUT: the vertex and the tessellation control stages are executed ONLY for those vertices / faces, which belong to objects, which are visible. Therefor this is a performance boost.
Both ways have performance positive and negative characteristics. In case the geometry of the objects which will be rendered is complex (high amount of vertices / faces) the performance of the first way to render will drop more and more.
The optimal way to render
When analyzing the two ways to render, this third way to render only has the positive performance effects, without having negative performance effects. So I am trying to do this:
Indirect instance render the objects (SSBO three times). The shader program does not need to be changed. No Vertex / tessellation control of objects outside the frustum (invisible objects) will be executed. No check is required, if an objects is visible during the render stages.
BUT: I don't know if this is possible... I would like to indirect execute the
glDrawElementsInstanced command (SSBO three times). This is the only thing which stops me right now.
Which command can I use, to achieve this indirect instanced rendering? And how (format) do I need to fill the third SSBO which will be bound later as
GL_DRAW_INDIRECT_BUFFER? And yes, all objects do have the same geometry.