2
\$\begingroup\$

I've created a practice application for rendering triangles/squares/cubes using VBOs. This is mainly so I can get it right before upgrading a 3D block world game I've written that currently is using display lists to cache block rendering.

I have a 3D cube rendering with an interleaved VBO and I recreated a basic chunk/block system that does the following:

  • Loads chunks in a 10x10 area
  • Each chunk creates its own float buffer and adds 16x16x16 cube vertices, colors, texture coordinates.
  • Each chunk then is rendered via its own VBO.

The VBO rendering code, called per-chunk, looks like this:

interleavedBuffer.flip();

glGenBuffersARB(ib);
int vHandle = ib.get(0);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glBindBufferARB(GL_ARRAY_BUFFER_ARB, vHandle);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, interleavedBuffer, GL_STATIC_DRAW_ARB);

glVertexPointer(3, GL_FLOAT, 32, 0);
glColorPointer(3, GL_FLOAT, 32, 12);
glTexCoordPointer(3, GL_FLOAT, 32, 24);

glDrawArrays(GL_QUADS, 0, vertexCount);

glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

ib.put(0, vHandle);
glDeleteBuffersARB(ib);

clear();

This method renders everything perfectly, however it suffers from performance problems. A few chunks of 16x5x16 are fine, 60 fps. Increasing them to 16^3 and loading 10x10 chunks, the performance drops to 8fps.

I'm preparing to use VBOs on a game that loads at most 20x20 chunks of 16x128x16 dimensions so I need resolve this performance issue.

What are my choices?

  • I have yet to look into interfaced/indexed VBOs. I've heard those are good choices when rendering many of the same thing?
  • My game currently uses display lists to cache chunk rendering, and I only refresh that cache when a block change is made that requires it. Is there something similar I can do with VBOs?

For reference, my code for the chunk loading and chunk rendering can be perused.

\$\endgroup\$
  • 3
    \$\begingroup\$ FPS is a poor metric, since it is nonlinear. You should measure actual time per frame instead. (60 FPS is 0.016 seconds per frame, 8 FPS is 0.125 seconds per frame.) \$\endgroup\$ – Josh Dec 7 '13 at 20:22
  • 5
    \$\begingroup\$ You're creating and destroying GPU resources (in this case vertex buffers) at runtime - yes, that's going to be slow and is the most likely cause of your performance loss. Have a think about strategies to create them once-only at startup, then reuse already created resources at runtime. \$\endgroup\$ – Maximus Minimus Dec 7 '13 at 21:06
  • \$\begingroup\$ I'll have to measure where the bottlenecks are but I know that it's related to how I'm building or rendering the VBOs. The game is already running at acceptable performance using the display lists, aside from initial delay for the noise values to be calculated. This experiment is much simpler in how land/chunks are created as well. \$\endgroup\$ – BotskoNet Dec 7 '13 at 21:35
4
\$\begingroup\$

My hunch is that it's the creation of the VBO each time. If you data is truly dynamic you could try not using the VBO at all and just passing interleavedBuffer to glVertexPointer, etc. That leaves what happens in the hands of the driver. Probably it will copy your data into a buffer somewhere internally, but in a more optimal way since it has more information and can pool buffers.

If the data doesn't actually change then you should just create the VBO and call glBufferDataARB once at startup.

Alternatively, if your data does change you could create the VBO once at startup with GL_DYNAMIC_DRAW usage and a NULL data ptr then later fill it's contents once per frame with glBufferSubData or glMapBuffer/glMapBufferRange (to generate the data in-place for one less copy). This is probably the "modern OpenGL" way of doing it currently, but it all depends on what your target platform is and at the end of the day what's faster there.

\$\endgroup\$
  • \$\begingroup\$ Currently, each chunk is just rendered once, and if a player makes a block change in that chunk, we clear the cache and re-render the chunk. We only then just transform what the player is looking at as they walk around. \$\endgroup\$ – BotskoNet Dec 7 '13 at 22:44
  • \$\begingroup\$ This answer was essentially correct, in the sense that the code was creating the VBO and loading the data each time. By exploding the idea of the buffer IDs more, I realized they work like the display lists in that I don't need to re-build the VBO every render call. By building the VBO data when it changes, and just binding that ID each render call, performance is pretty much back to 60fps. It drops a little now and then for an unknown reason, but using the buffer ID properly worked wonders. \$\endgroup\$ – BotskoNet Dec 8 '13 at 0:45
1
\$\begingroup\$

There are ways to improve your experimentation to get more useful result data. In hunting down your performance culprit, you should expand the number of cases you test, and measure the results incrementally. Computer scientists are allowed to use the scientific method, afterall.

For example, you know that a few chunks at 16x16x5 is fine, and 100 chunks at 16x16x16 is slow. Obviously, you need more data points. If your goal is to use 16^3 chunks, then start with one VBO at that size. Measure the draw time as you go from 1 to 100. Does time increase linearly, quadraticly, cubicly? Is there a point where a big discontinuity happens, and draw time increases suddenly?

In a completely separate series of experiments, vary the size of the VBO (perhaps include data creation time, if that is relevant to your game), and render the same number of chunks. Cover this range for a few different quantities of VBOs, using your previous results as a guide. If possible, perform these tests on multiple configurations of hardware.

Agreeing with Josh's comment that you should measure the actual time to draw rather than FPS, it is possible that you might run into multiple distinct bottlenecks. Your draw code and your update code have to share 16ms in order to get a net 60fps. There could be one scenario where your game can't update quickly enough to even start drawing (CPU-bound), and there may be another point where the drawing blocks the game's ability to update often enough. These effects will be difficult to distinguish purely by fps. You should measure the time to run update code as well, so that you can quantify both the time to draw and the time to update.

\$\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.