4
\$\begingroup\$

I have some objects that I want to draw. I am not sure how I can create my architecture in a way where I can draw everything as fast as possible.

As example:

class MyObject
{
  float[] vertices;
  float[] colors;
  float[] textures;

  public MyObject()
  {
    //fill every buffer with data
  }

  public void Draw()
  {
    //set shader
    //set shader uniforms etc.

    //foreach buffer
    BindBuffer();
    VertexAttribPointer();

    DrawArrays();
  }
}

In this way, I can draw every object like myObject.Draw after it is constructed. The problem would be, that if I have 5000 objects, I have 5000 draw calls. I need to set the shader every time even if I loop over the 5000 objects because I don't know if they use the same shaders.

Another way:

class MyObject
{
  bool needsToBeChanged;

  public MyObject()
  {
    //set my edge points
    needsToBeChanged = true;
  }

  void ChangeMyObject() //e.g. I changed the height of my object
  {
    needsToBeChanged = true;
  }
}

class GLWindow
{
  float[] vertices;
  float[] colors;
  float[] textures;

  GameLoop()
  {
    foreach(MyObject myObj in allMyObjects)
    {
      if(myObj.needsToBeChanged)
      {
        //get objects points for drawing it and set the global buffers up

        myObj.needsToBeChanged = false;
      }
    }

    DrawArrays(); //draw only once (because every data is in the buffer)
  }
}

Only 1 draw call for everything. I guess this will be faster than first method. The problem on this code is the big buffer that contains everything. Let's imagine we have inherited from MyObject and want to draw objects in different shapes. Now the Loop gets more complicated. You may have to split it because you need another shader for example for text. Now you have 2 arrays... And with other changes the Loop becomes more and more complicated and harder to maintain.

What solutions (beside the both) are available for this kind of problem? Can I use the first way or is there a better way where I can reduce the draw calls? How is it solved in other applications?

\$\endgroup\$
3
\$\begingroup\$

Generally speaking, you will need a separate draw call for each set of objects that have a different material (by material I mean a different shader, texture or other shading parameters).

So the obvious approach is to group objects that have the same material, so that they can be rendered together, avoiding the expensive state changes. This is normally done by batching objects that share the same material. A data structure that helps achieving this is the scene graph.

Once you have batched objects with the same material you eliminate redundant state changes. The shader program and textures are now set outside and a batch is rendered. Conceptually, this would look like this:

for each batch in batches
    set common material properties
    for each object in batch
        draw object
    end for
end for

But you still have multiple draw calls within each batch. This could be resolved if all objects resided inside the same Vertex Buffer. Packing everything in the same VB is feasible and might be the best option in some cases. Modern OpenGL, however, provides instanced rendering (see a tutorial here), which is a very good solution to reducing the number of draw calls.

Overall, some parts of your rendering will probably be better optimized with simple batching or a scene graph, while other will be more efficient if implemented using instancing. You should profile and test to discover the ideal approach. But bare in mind that this is an optimization step, therefore, you should first worry about making the game/software work, then you optimize it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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