I'm making a 2D Tile based game in OpenTK at the moment. Performance was really bad (laggy and slow) with direct drawing, so I switched to VBOs. The performance hasn't changed, it might even be slower than it was.

I render the tiles from a list, but use a single VBO.

public void DrawTiles()
            foreach(Tile t in Tiles)
                if(t != null)
                    GL.BindTexture(TextureTarget.Texture2D, Global.LoadTexture(t.TileTexture.Path, 1));
                    var i = (CellNum * CellSize) / 2;
                    GL.Translate((t.Position.X * CellSize) - i, 0, (t.Position.Z * CellSize) - i);
                    var x = CellSize;
                    verts = new Vector3[8]
                        new Vector3(0, 0, 0), new Vector3(0, 0, 0),
                        new Vector3(x, 0, 0), new Vector3(1, 0, 0),
                        new Vector3(x, 0, x), new Vector3(1, 1, 0),
                        new Vector3(0, 0, x), new Vector3(0, 1, 0)



                    GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
                    GL.BufferData<Vector3>(BufferTarget.ArrayBuffer, (IntPtr)(Vector3.SizeInBytes * CellNum), verts, BufferUsageHint.StaticDraw);
                    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

                    GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
                    GL.VertexPointer(3, VertexPointerType.Float, Vector3.SizeInBytes * 2, 0);
                    GL.TexCoordPointer(3, TexCoordPointerType.Float, Vector3.SizeInBytes * 2, Vector3.SizeInBytes);
                    GL.DrawArrays(PrimitiveType.Quads, 0, verts.Length / 2);

That method is called in OnRenderFrame, CellSize and CellNum tell me how big the tiles are supposed to be and how many there are each side of the grid. Even if I run say a 20 tile grid, my program is incredibly laggy.

Does any one know a more efficient way to render these tiles, or better yet, speed up my current method?


1 Answer 1


The important thing to realise, as you've discovered, is that VBOs aren't a magic wand that makes everything run faster under every circumstance. Specifically, you're only going to see a meaningful performance improvement with VBOs if you're bottlenecked on getting your vertex data from the CPU to the GPU. If you're not so bottlenecked, then the potential performance improvement that VBOs offer doesn't apply to you.

It's quite safe to assume that drawing 20 quads is not so bottlenecked; glBegin/glEnd code is perfectly fine for this kind of workload, unless you have an actual reason to do otherwise (such as wanting to use a core profile). Remember: Quake used glBegin/glEnd code in 1996, drew several hundred polygons per frame, and ran plenty fast enough on hardware of that time.

You've also discovered that a bad VBO implementation (and your's is one) can run slower than glBegin/glEnd code. The way to use a VBO properly is to batch up a lot of vertices then submit them all in a single draw call. So rather than filling a VBO with a single quad at a time, instead you pre-allocate a large VBO (how large? depends on your program, but several hundred or even thousand quads would not be unreasonable), fill it up, then make a single glDrawArrays call with it.

If the data doesn't need to change then you just reuse the VBO from the previous frame. There's definitely no need to recreate it.

So given that you've not got a bottleneck that's adressable by a VBO, yet you're still running slow, it's obvious that you must be bottlenecked elsewhere.

Looking over your code, I can see a number of things that are candidates for improvement, but the main one that jumps out at me is your Global.LoadTexture call. Are you reloading textures from disk every frame? Yes, that would make it slow.

A second thing is your use of the old matrix stack. For your use case, this gets you nothing in terms of performance - don't get the idea that just because it's a GL call it means it runs on the GPU, because the old matrix stack doesn't. Furthermore, you're inserting state changes between each draw call that will prevent the driver from being able to batch up work (you've another case of this I'll come to later). Get rid of that and just put the final positions directly into your glVertex calls (or VBO); your GPU will thank you for it.

Thirdly, that glFlush call is completely unnecessary and is once again preventing the driver from batching up work. Get rid of that too.

So, sort these out first, retaining your old glBegin/glEnd code, and then you'll be able to determine if glBegin/glEnd code is actually a problem for you or not before doing anything else.

  • \$\begingroup\$ Wow, that has to be one of the best answers to any question I've asked. Upvotes for this one guys. Thanks though man, I'll get on to these thing now. \$\endgroup\$
    – user81509
    Nov 27, 2016 at 1:44
  • \$\begingroup\$ Okay, I removed the things you said, but the major one was Global.LoadTexture. When removed, my program ran just as fast as not rendering anything at all. Sir, you are a god among men. \$\endgroup\$
    – user81509
    Nov 27, 2016 at 1:51
  • \$\begingroup\$ It's also good to note that even Minecraft still uses immediate mode for its rendering. Each chunk is compiled into a display list after being triangulated and optimized, and rendered via occlusion culling. Hell, my current game uses the same principle, in that rooms segments are combined together into a single display list. The performance I am seeing is phenomenal, too. \$\endgroup\$
    – Krythic
    Nov 27, 2016 at 5:21
  • \$\begingroup\$ The problem is that the basic buffer object API - the one introduced in GL_ARB_vertex_buffer_object and promoted to core in GL 1.5 - is just so bad that unless your data is absolutely static you're probably better off using client-side arrays or immediate mode instead. The D3D equivalent doesn't have these problems, and the GL API has been patched many times with extensions and core revisions to fix it's original limitations: VAOs, attrib binding, map buffer range and buffer storage all make it look like the API it should have been from the beginning. \$\endgroup\$ Nov 27, 2016 at 11:05

You must log in to answer this question.