1
$\begingroup$

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.

The setup

I have three SSBOs:

  1. List of objects (Position, radius). This list is created once and will not change.
  2. 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
  3. 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.

My question

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?

EDIT:

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_TessLevelOuter and 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.

Performance

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.

Conclusion

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.

$\endgroup$
3
  • $\begingroup$ Have you seen this? registry.khronos.org/OpenGL-Refpages/gl4/html/… $\endgroup$
    – pmw1234
    Jan 9, 2023 at 12:33
  • $\begingroup$ "I only want to render (instanced) the objects" Are they all the same object? Most scenes don't only contain copies of the same object. $\endgroup$ Jan 9, 2023 at 16:07
  • $\begingroup$ "I discard the object within the tessellation control stage." Never use tessellation for anything other than tessellation. Don't use it as a fancy way to discard vertices. $\endgroup$ Jan 9, 2023 at 20:57

1 Answer 1

2
$\begingroup$

The final mechanism you are wanting to employ is indirect rendering: the ability of the GPU to read the contents of one (or more) rendering commands directly from GPU memory. Your compute shader therefore needs to conform to the requirements of whatever indirect rendering mechanism you're trying to employ.

That is, "List of visible indices" is not the primary information your CS needs to generate. The GPU doesn't know what a "visible index" into your "List of objects" is. What the GPU needs are two things:

  1. One (or more) rendering commands, as formatted for the indirect rendering function you intend to call.
  2. A way to access the per-model data needed for each of those rendering commands.

So you're rendering a bunch of instances: copies of the same model with different per-model parameters. So that means you need to use one of two mechanisms for your shader to access these per-model parameters. You can bind your "List of objects" as instanced vertex arrays. Or your vertex shader can access gl_InstanceID and use that to access an SSBO containing the "List of objects".

You established that you don't want "List of objects" to change. That you're not going to add or remove things from this list. OK, but that doesn't mean your CS can't write that data elsewhere.

Instead of writing which indices in that list are visible, you should instead write the per-model data to another SSBO if it is visible. So you have your original "List of objects" SSBO, and a second SSBO containing the visible object data. This way, the visible list can be used directly by your instanced rendering operation without having to fetch an index to use to fetch the actual data.

And this brings us to the atomic counter. This counter needs to have its backing memory store be the number of instances for the instanced rendering command. The rest of the command can stay the same (number of vertices per model, etc). You just need to reset these bytes to 0 before starting to compute a new set of objects.

$\endgroup$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.