2
\$\begingroup\$

In my game, every bit of geometry is a textured quad billboard in 3D. I have many thousands of these things on screen at once, sometimes with overdraw. As I understand it, there are a few ways I can go about drawing these:

  1. Have a single VBO per type of quad (ie, unique set of texture coords). Batch the VBO and texture, but make multiple draw calls. Looks something like this:

    foreach (Quad quad in quadTypes)
    {
        // The VBO just contains 4-6 vertices.
        GraphicsDevice.SetVertexBuffer(quad._vertexBuffer);
        Shader.SetTexture(quad._texture);
    
        // Draw each instance using multiple draw calls
        foreach(QuadInstance instance in quad._instances)
        {
           Shader.SetWorldMatrix(instance._worldMatrix);
    
           foreach(ShaderPass pass in Shader._passes)
           {
               pass.Apply();
               GraphicsDevice.DrawPrimitive(TRIANGLES);
           }
        }
    }
    
  2. Make a single draw call by somehow combining all the triangles of all the instances of a quad type. Looks something like this:

    foreach (Quad quad in quadTypes)
    {
        Shader.SetTexture(quad._texture);
        // Probably can cache a vertex buffer for when quads are created/destroyed instead.
        VertexBuffer vertexBuffer = new VertexBuffer();
    
        foreach(QuadInstance instance in quad._instances)
        {
           // Add all of the (world transformed) triangles of this instance to
           // the vertex buffer
           vertexBuffer.AddAll(instance.CreateTriangles());
        }
    
        // Draw all the quads with a single draw call and vertex buffer.
        GraphicsDevice.SetVertexBuffer(vertexBuffer);
    
        foreach(ShaderPass pass in Shader._passes)
        {
            pass.Apply();
            GraphicsDevice.DrawPrimitive(TRIANGLES);
        }
    }
    
  3. True hardware instancing. This is currently how I do it in my (XNA HiDef profile) game, but I can't figure out a way to get it to work in Monogame. With true hardware instancing, each instance is represented as a vertex, and the GPU creates all the necessary duplicate vertices in a single draw call.

In general, which one of these is most desirable? 1) makes many draw calls, but saves the most memory. 2) has a single draw call, but requires creating a vertex buffer for all of the instances, which seems really inefficient, and 3) has the best of both worlds but is not widely supported. Is there a fourth way that I'm missing?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I did this in OpenGL. I used a single VBO for all quads. Each quad had a texture that it used, I then had a single draw call for each texture. This is kinda how XNA's sprite batch works. I've never done this in XNA, but you should be able to have only a single VBO, and minimize draw calls to a large extent. \$\endgroup\$
    – Ben
    Dec 30, 2014 at 17:16
  • \$\begingroup\$ How do you use a single VBO without creating and destroying it all the time? Doesn't that have a lot of overhead? \$\endgroup\$
    – mklingen
    Dec 30, 2014 at 17:30
  • \$\begingroup\$ I assume you mean when you want to add new quads? You'll have to resize the buffer, which might mean recreating it depending on what the graphics card wants to do. If you double the size of the buffer each time you want to expand it, this shouldn't be too much of an issue. \$\endgroup\$
    – Ben
    Dec 31, 2014 at 6:04

1 Answer 1

3
\$\begingroup\$

Your question title explicitly states "thousand of quads". That is really not a lot of geometry. I would have to say that unless you expect millions, or are targeting mobile, I suggest going with simple batching to reduce draw calls. It is easiest to implement and should do the job admirably.

If you really do need more geometry, read on...

Since Monogame genuinely does not support this yet, I can only suggesting extending it using the underlying OpenTK, or looking around on the Monogame issues to see if someone else has already made headway into hardware instancing and is likely to provide source that you can adapt.

I can say definitely that OpenGL 3 hardware instancing is not much harder to implement than rendering single geometries; however, integrating this into Monogame would probably be a lot harder to do right. If you do implement hardware instancing in OpenTK, allow me to save you some time by strongly suggesting instancing without using gl_InstanceId & using uniform arrays for instance data -- this is an outdated, inefficient, and limited approach. The best way is to use VAO + a vertex buffer containing all your per-instance data (e.g. model matrices) and uniforms for whatever is shared by all instances (e.g. view-projection matrix and textures).

Your approaches

It depends on cost. If calculating all transformations of all vertices on CPU is less than the cost of all the draw calls, it's definitely worth doing simplistic batching i.e. method 2. Otherwise you may be better off sticking with method 1 because you'll need to get per-instance matrices across without the cost of calculating them all on CPU. However, there is middle ground... see the below paragraph for more information on getting these across individually within a single draw call.

Best practices

Some sources outlining best practices (and their pros and cons) for where hardware instancing is not available:

Putting your per-instance matrices etc. in a texture can be very fast if you can wrangle it. You can also use a uniform array but I believe there is a fairly low limit on the number of individual uniforms you can have in a given shader program. Either way you are going to need to index into these in view of the lack of glAttribDivisor & gl_InstanceId -- this requires that you repeat the appropriate index for every single vertex of every instance, which is a bit costly but can't be avoided, and gets the job of reducing draw calls done.

\$\endgroup\$
9
  • \$\begingroup\$ Let me rephrase that as "Monogame does not support it". Right now I am porting my engine to Monogame from XNA, and support for true hardware instancing is lost. Maybe I can replicate the functionality using a shader? \$\endgroup\$
    – mklingen
    Dec 30, 2014 at 18:55
  • \$\begingroup\$ @mklingen I'm sure it must. Otherwise, can't you extend it to do so? Or is OpenTK an option? (I guess not as you're probably entrenched in XNA / Monogame by this stage.) \$\endgroup\$
    – Engineer
    Dec 30, 2014 at 18:56
  • \$\begingroup\$ If I could call openTK directly I would. AFAIK the version of monogame I'm using has openTK/SDL under the hood. Perhaps I will need to manually add this functionality to Monogame. \$\endgroup\$
    – mklingen
    Dec 30, 2014 at 19:00
  • \$\begingroup\$ This answer is kind of a comment right now. Maybe you can edit it to be in a more definitive answer form. \$\endgroup\$
    – House
    Dec 30, 2014 at 19:40
  • \$\begingroup\$ @NickWiggill Hardware instancing hasn't been a high priority feature for MonoGame contributors unfortunately. He could use one of the frequent dev MonoGame builds to try to extend OpenTK through there. \$\endgroup\$
    – ChrisC
    Dec 30, 2014 at 20:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .