6
\$\begingroup\$

I'm asking this question because I haven't found a definitive answer to it.

Let me first of all state a few things about the game and what I have already done. The game is a going to be a RTS set in a procedurally generated world using Simplex noise. The world consists of chunks which are 16 x 16 in size made of sprites that are 64 x 64. I've managed to load and unload chunks dynamically, which works just fine. The world is going to look a bit like Rimworld, so top-down with different layers of sprites (terrain first, transition sprites, trees, decals etc). Newly generated worlds may contain entities that may affect the environment (eg. a village that has become a town) and thereby the chunk. I'm sure this can be calculated using some sort of function, but it's something to take note of.

The main issue I have is when I zoom out, more and more tiles are drawn which severely affects the performance. At about 30000 sprites, the draw section takes 8 ms which is half of what is required to run at 60 FPS. And that's just the terrain. I am using texture atlases to limit draw counts (30000 sprites drawn in 6 counts).

The goal is to be able to zoom out from town/village/city level all the way to being able to see an entire country. This has to be done dynamically (eg. not by clicking on a minimap icon, but just scroll back just like in Supreme Commander).

I've read many articles about this issue, but I haven't found or seen a clear example of where it would work. Here's a list of techniques I have found that are supposed to work:

  • Dirty rectangles, as described here, where you only draw new stuff and keep the rest in the backbuffer. This makes a lot of sense, but I am clueless as to how to implement this in Monogame.
  • My preffered choice: make extensive use of RenderTargets, as described here, where you draw to a RenderTarget and then save it as a texture. In my case a 16 x 16 chunk consisting of 64 x 64 would create a 1024 x 1024 texture. I really doubt this will work in terms of performance, but the result would consist of highly detailed textures and is also great to use considering the fact that most of it is static (terrain/trees etc), and doesn't change as much. But this also means that everytime a change is made to a chunk, that Texture2D has to be changed using SetData, which from what I've experienced is quite CPU intensive. However if the textures were 16 x 16 it may actually work, and it would also cut down own memory and disk usage.
  • Til textures by specifying the SourceRectangle in SpriteBatch. For huge grasslands/oceans this is a plus, since only one sprite is drawn. However for detailed terrain with different colours and different sprites mixed together (biomes and biome transitions) I'm afraid it won't make a huge difference.
  • My own solution, which compromises detail, was to use a standard white 64 x 64 tile, give it the colour of the surrounding 4 tiles and then scale it so it covers the 4 previous tiles. The difference here (besides the tile having a plain colour) is that the landscape visibly changed. I should also mention that forests still have to be drawn individually since they aren't perfectly square.

If anyone has any idea on how to tackle this problem, even if it means compromising certain things (such as detail, or using 16 x 16 sprites), I would love to hear it.

\$\endgroup\$
  • \$\begingroup\$ Don't let the user zoom out that much \$\endgroup\$ – Bálint Mar 18 '17 at 21:56
  • \$\begingroup\$ I've also experienced performance issues with my procedurally generated MonoGame project. My solution was to limit what was rendered (limit zoom out range and only draw what's on the screen). It seems like MonoGame is designed with a "draw everything every frame" mantra, but I hope someone more experienced with MonoGame will correct me. \$\endgroup\$ – Logical Fallacy Mar 19 '17 at 1:09
  • \$\begingroup\$ How do you render the tiles? More specifically - do you use SpriteBatch and how do you use it? \$\endgroup\$ – loodakrawa Mar 27 '17 at 3:34
2
\$\begingroup\$

Graphics cards are optimised to draw a few things lots of times rather than the other way around.

In your case, you have a lot of things (different textures) that you want to draw a lot of times (number of tiles).

IMO, the simplest optimisations you can do to get significant performance gains are:

  1. Combine your texture into atlases. This should significantly speed things up since your graphics card is now only drawing one (or a couple) of textures instead of a lot of small ones.
  2. Batch your tiles with SpriteBatch in SpriteSortMode.Texture mode. This will sort your sprites by texture and minimise the number of texture switches to the actual number of different textures. The downside of this approach is that your sprites won't get sorted by depth. Which is presumably fine since your tiles are all at the same depth. I assume you have other stuff as well so it makes sense to have separate a SpriteBatch for tiles and another one for everything else (set to SpriteSortMode.BackToFront).

I would recommend doing both things. Good luck.

\$\endgroup\$
0
\$\begingroup\$

I found out that SpriteBatch is not suitable for drawing tiles. One reason is that sprites are meant for dynamic objects and are used for multiple purposes. Another reason is that it's incredibly CPU-bound, eg. as explained in the description drawing 30.000 objects costs 8 ms (while my chipset maxed out at 30%). Also, up to 3000 sprites may be drawn before a new draw call has to be made to the GPU (batched and all).

The solution was to draw my own tiles using textured quads. This in combination with a VertexBuffer allowed me to draw up to 500.000 textured tiles in 1 draw call where the draw method consumed roughly 1/20th of a millisecond. Of course I probably won't have to draw that many, but either way I finally got my GPU to have a workload of 75% at a steady 60 fps.

\$\endgroup\$
  • \$\begingroup\$ Sounds very good. I'd like to learn more about that - did you follow any tutorials or do you have any links with a more detailed description of this process? \$\endgroup\$ – loodakrawa Apr 5 '17 at 12:34
  • 1
    \$\begingroup\$ I learned most of it about everywhere that mentioned XNA/Monogame and textured quads. However, riemers.net helped me a lot since it's aimed at people who just want to set up something quickly that works. You'll want to specifically follow his 3D terrain tutorial since it also covers VertexBuffers and IndexBuffers. Turning it into a tile type of terrain shouldn't be an issue. \$\endgroup\$ – Guguhl Pluhs Apr 5 '17 at 13:56
  • \$\begingroup\$ If that's what answer the most your question, you're more than welcome to mark it as accepted :) \$\endgroup\$ – Alexandre Vaillancourt Apr 8 '17 at 1:28

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.