views:

580

answers:

1

Hello,

I'm developing a 2D sidescrolling game and I need to optimize my tiling code to get a better frame rate. As of right now I'm using a texture atlas and 16x16 tiles for 480x320 screen resolution. The level scrolls in both directions, and is significantly larger than 1 screen (thousands of pixels). I use glTranslate for the actual scrolling.

So far I've tried:

  • Drawing only the on-screen tiles using glTriangles, 2 per square tile (too much overhead)
  • Drawing the entire map as a Display List (great on a small level, way to slow on a large one)

  • Partitioning the map into Display Lists half the size of the screen, then culling display lists (still slows down for 2-directional scrolling, overdraw is not efficient)

Any advice is appreciated, but in particular I'm wondering:

  • I've seen Vertex Arrays/VBOs suggested for this because they're dynamic. What's the best way to take advantage of this? If I simply keep 1 screen of vertices plus a bit of overdraw, I'd have to recopy the array every few frames to account for the change in relative coordinates (shift everything over and add the new rows/columns). If I use more overdraw this doesn't seem like a big win; it's like the half-screen display list idea.
  • Does glScissor give any gain if used on a bunch of small tiles like this, be it a display list or a vertex array/VBO
  • Would it be better just to build the level out of large textures and then use glScissor? Would losing the memory saving of tiling be an issue for mobile development if I do this (just curious, I'm currently on a PC)? This approach was mentioned here

Thanks :)

+2  A: 

Here's how I would do for coding a fast 2D tile engine:

First I would make a clean separation between dynamic tiles (characters, items..) and static ones (level).

Static Tiles:

For drawing the static ones (tiles that build up the whole level), I would use a static buffer (stored in a buffer object) that contains each tile position (x, y, layer) and an index to the atlas texture data (i). Since your texture atlas contains fixed-size tiles of 16x16 pixels, you could easily compute in the vertex shader texture coordinates for each vertices.

For drawing the level I would use a single draw call (using instancing) of a triangle strip forming a quad, vertex data is stored in a static VBO (made of 4 vertices) and index data in a static IBO (made of 4 indices), using per instance values for computing vertices attribute in the vertex shader.

This would give you almost "free" tile culling done on the GPU, since clipping hardware is very fast. Even if you have big number of tiles in your level, let's say 30*20 (tiles/in a screen), and about ~50 screen/level, it would make 30,000 tiles. I think it's still acceptable (even on low-end GPUs. BTW, are you targetting iPhone/Android? If yes instancing/shaders are not available on OpenGL ES 1.0 and OpenGL ES 2.0 has no instancing support but can do shaders, so you will have to explode tiles instance data in a VBO/ IBO, and use GL_TRIANGLES. You can explode less data and spare GPU memory, computing vertices attributes in a shader).

In any case you'd better not to duplicate texture tiles data and keep a texture atlas and a VBO and IBO.

Dynamic Tiles:

I would use a dynamic VBO (and an static IBO representing GL_TRIANGLES so 0,1,2, 2,1,3, 0+4,1+4,2+4..,) representing tile positions, texture coords, layers and update it with visible dynamic tiles in a screen via glBufferSubData and draw thoses tiles via glDrawElements.

Of course this means that you have a maximum number of dynamic tiles that you can draw per glDrawElements, so if you bump into this limit you'll have to do a second update/draw of the VBO.

If your OpenGL implementation has no support for VBO/IBO (like in OpenGL ES 1.0) use VA instead. I don't recommand usage of DL or immediate mode (no support for it on OpenGL ES).

Finally, use glOrtho for moving your camera across the level, zoom in/ zoom out etc. Good luck!

Stringer Bell
How would I go about instancing the data, I think I'm misunderstanding the VBO process? I've created a VBO from the texture atlas using `glBufferData(GL_ARRAY_BUFFER, texsz, texdata, GL_STATIC_DRAW)` followed by `glTexCoordPointer (2, GL_FLOAT, 0, texdata)`, and then tried to make a geometry vertex array/VBO plus an IBO array mapping to the texture atlas VBO. However, nothing shows up when I try to draw (tried `glDrawArrays` and `glDrawElements` variants on geom VBO). Buffers are generated/bound and the vertex pointer is set up, is there some step I'm missing? Thanks for the help so far.
Yeah, you have misunderstanding VBO usage. VBO is for storing vertices data (or a 1D collection of fully typed data. If you do so you don't use GL_ARRAY_BUFFER_ARB during creation that is for hinting driver that you are using it for storing vertices). You can only have one VBO bound at a time. So you have to put your texdata into a regular OpenGL 2D texture. Then put all tiles vertices and indices data into a VBO/IBO and draw them using glDrawElements.What I have described is without instancing/shaders. My advice: make it work the simplest way first.
Stringer Bell