How to render many tiles fast if zoomed out? (Monogame/xna)

I'm currently trying to optimize my tile engine. I used monogame with the spritebatch first, but that doesn’t works well. I have read many articles how I could optimize my rendering code.

I moved away from the normal spritebatch and used the vertexbuffer+indexbuffer stuff. Works very well, but not so good as I hoped. I have chunked my tilemap in 50*50 large chunks and render each with his own vertexbuffer. Additional I check whether the chunk is in view and only render it then. But things are getting very bad if I zoom out.

Let’s say the map is 500*500 tiles where a tile is 32*32px tall. Now I must render too many of the chunks to the same time. How can I fix that performance issue here?

I have attached a sample project if some one is interested in it. https://www.dropbox.com/s/2vbkng7ne6ey43e/TileEngineTestXnaMonoWpf.zip

• Have you tried varying your chunk size? And where is your profiling indicating that the bottleneck lies? – DMGregory Feb 27 '18 at 12:09
• I have tried that too. I have some PCs to test. One without graphics card, only intel hd graphics. One with a office sized card an gaming pc. The performance is very bad on the intel hd and a little better on my office pc. I have testet different map sizes too! 20*20 => until 1000*1000. Intel lags from 100*100, gaming pc 400*400. – EnemyArea Feb 28 '18 at 8:09
• One technique I've experimented with is drawing all the tiles for a chunk on a single quad, using an index map texture to look up the tile to draw at each spot. This keeps the cost roughly constant per fragment, so you can pack 4096x4096 tiles into a single draw call. – DMGregory Jun 2 '18 at 12:18
• sound very good. Got stable performance even on an very low system. great :D – EnemyArea Jun 3 '18 at 14:58

What you are describing is the main obstacle with tile-based game maps. You've got too many triangles being drawn in one call and having them zoomed doesn't help either.

There are several optimizations that can be done to work with a tile-based world.

LOD (level of detail).

A simple way to reduce the performance costs of a tile-based world is to draw less tiles. When zooming out you can switch from drawing your small 32x32 tiles to drawing a 64x64 tile that represents the 32x32x4 area. You can do this at discrete zoom distances multiple times.

This technique is quite effective though it can have a very large memory footprint and can be difficult to make look 'pretty'.

Here is a video of a prototype I made a while back that shows off this technique as well as chunk loading (unfinished).

Implementations of LOD vary greatly but the premise is mostly the same.

Combining Triangles

Another way to reduce the number of draw calls is to combine the vertices of any large swaths of the map that have the same texture. Think of a quadtree like structure.

Besides these two techniques, you need to ensure you aren't drawing any tiles that aren't visible, as well as making sure you aren't switching textures (use a spritesheet). Also, make sure you aren't drawing in immediate mode.

• But how can i draw 4 textures (from atlas) on a tile in a level of detail scenario? – EnemyArea Mar 21 '18 at 7:31
• Your idea of packing multiple tiles into one for LOD is interesting but doesnt seem easy to implement at all, wouldnt a "typical" LOD be much easier to implement? (IE say a 32px tile, when it drops below a certain threshhold switches out to a precalculated 16px version, etc) ? – TurtleKwitty Sep 3 '18 at 18:52

I'm not sure if this is ever done, and if not, why not. But; I think it would be possible to create a render target for those things in your viewport that are likely static. So your map, for instance, is not likely to change. Because of this you could draw your entire set of map tiles only once in a while (except when your camera moves.)

This would avoid walking all of those vertices, reapplying the texture in a way that matters, etc. You may have to keep a depth buffer floating around whenever you "settle" on a static background.

One can imagine extending this technique to Buildings (even if destructible) and simply only redrawing under the same conditions as the land or if a building has taken sufficient damage to require an update.

Aside from that, a few things you may want to check:

• Are all of your effects necessary at this LOD? Consider some effects that simply clip when the depth buffer is sufficiently distant.
• Are there any if statements in any of your effects? Don't do that. IIRC you can use a "clip" function to skip that pixel's calculation.
• Are you performing divides? Any divide that is with a constant can be re-imagined as a multiply (and should be.) For example x/100 is also x * 0.01; but performance between those on both GPU and CPU is dramatically different.
• Are you using floats for everything? If not, the GPU is highly optimized for floats and not for ints; contrary to CPU programming. Just like in CPU programming you avoid floats/doubles you should be avoiding ints/longs in GPU programming instead.
• Are you calculating things multiple times that don't need to be. The following segment:

float x = pos.x * lit.x * sin(theta) * (1 - 1/pos.y); float y = pos.y * lit.y * sin(theta) * (1 - 1/pos.y);

float mult = sin(theta) * (1 - 1/pos.y);