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\$
  • 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 '14 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 '14 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 '14 at 6:04
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\$
  • \$\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 '14 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 '14 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 '14 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\$ – MichaelHouse Dec 30 '14 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 '14 at 20:16

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.