I avoided Unity’s Camera.Render(). Our game runs faster.

One of my pet peeves with our game is that it’s a 2D game but it’s rendering is horrendous. Say a game scene that looks like this:

perfscene

This doesn’t cover the game’s whole tilemap yet but it does already have lots of objects in it. When I run this through the profiler, this is what it looks like:

slowperf
Click here for enlarged image.

The timeline for Camera.Render hovers around 20-30ms. That’s a lot! Remember that you need to run a frame at 16ms at most to achieve 60fps. The rendering alone takes more than that. I don’t have the CPU budget anymore for game logic.

It’s not that I didn’t try to make the effort to optimize rendering and encourage batching. I did a lot. See here, here, here, and here. The game does perform well when the player zooms into the map, which means the camera sees less objects and majority are culled. However, for a simulation game like Academia, players would usually play zoomed out because they want to see how the school is doing. Thus, Camera.Render usually processes more objects and performs slower.

I keep thinking about those 3D games that renders meshes with hundreds to thousands of polygons and still run at 60fps. My hypothesis is that Camera.Render takes too much time calculating for culling and batching quad meshes. That got me into thinking that maybe I don’t need culling for this game. Modern GPUs should be able to chew thousands of polygons easily. I already have a tool that collects sprites into a single mesh and render that one mesh instead. There should be no problem if I just dump these monolithic meshes to the GPU for rendering.

So I made a separate project to test if my hypothesis is correct. I improved my custom sprite manager to handle static sprites more efficiently. I simulated the hypothetical maximum number of sprites in our game. I prepared 5 layers for static sprites that has 16,380 sprites per layer. That’s a total of 81,900 static sprites. I also added one layer for non static sprites that has 4,500 units. My custom renderer can render all these in 7-10ms.

testperf
The highlighted part (9.368ms) are my custom rendering systems. Click here for enlarged image.

Note that this is already the hypothetical maximum. The game scene I showed isn’t even half the maximum. My custom renderer should do much better than Camera.Render given the same scene.

I decided to integrate my custom renderer to our game. This required a lot of refactoring. I started the integration around late September 2018. I’m not finished yet. I haven’t completely applied it to all objects in the game but I did already cover the majority, especially those that are frequently used. I did another profiling of the game while loading the same scene and this is how it looks:

burstperf
The highlighted section (1.725ms) on the left side is where my custom renderer runs. Note that this also includes game specific systems (ECS). Click here for enlarged image.

You can see here that my custom renderer runs at 1.725ms. On the right, you can also see that I’ve reduced the running time of Camera.Render to 2.01ms. Most objects don’t use the standard Unity renderer like SpriteRenderer and MeshRenderer anymore so they’re no longer “processed” by the camera. Adding the running time of my custom renderer and Camera.Render, the total rendering time now hovers around 3-5ms. That’s a huge difference from the original 20-30ms.

Notes:

  • All profiling are done using a development build (not from editor)
  • My custom renderer uses Unity’s ECS with Burst compiler turned on
Advertisements

4 thoughts on “I avoided Unity’s Camera.Render(). Our game runs faster.

    1. That’s what you call mipmapping. The downside to that is you exchange memory for speed. Unfortunately, we’re also trying to reduce memory usage so that the game can support more agents. I didn’t opt for this solution.

      Liked by 1 person

  1. How much RAM are you using? I haven’t found RAM to be much of a limit for textures, especially if its mostly in video memory anyway. Is it because individuals in the game are drawn as unique sprites and not composited at runtime? Also worth checking your texture formats in texture memory is a concern. A lot of top down 2D games have alpha channels in textures where they don’t need any.

    Like

    1. It’s big. We’re using about 1.4GB of RAM now. I don’t specifically know how Unity manages textures in memory. I don’t even know that you can just park it in video memory (I’ll search about that). However, it does show up as the biggest usage as shown in their memory profiler. As for texture format, we’re using the DXT compressed format. We procedurally create the atlas during runtime and compress it to DXT.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s