Last week I discussed some of the basics of how rendering works in Unity. As I mentioned, all of that was setup for this week’s blog post. Since I’m working on rendering optimization now, I figured it would be a great time to go over the debugging tools Unity provides in order to aid rendering performance. Online resources can be a little scarcer for rendering than they are for other aspects of coding, so hopefully anyone who’s working on their own game might glean some useful information from this post. And even if you’re not working on anything right now, I hope you follow along and maybe learn a bit!
Unity is a nice little game engine, and, as such, it does a lot of the work for you. For the most part, when making a game, you don’t have to worry about the nitty-gritty stuff like rendering. When building for mobile, however (especially when you have specific graphics/lighting customization), you might have to descend into shader-land. Fortunately, Unity provides a few tools that can help you to deal with optimizing your rendering pipeline.
The first step in fixing rendering performance issues is to know about them. The best way to do that is with the Profiler window (Window -> Profiler). While you’re running your game, the Profiler keeps track of a lot of incredibly useful information, like how long each frame takes to render, split up by category. For instance, the Profiler will tell me that a frame took 60 milliseconds to run, 40 of which were due rendering and 15 from script execution, etc. This is the first place you should check when trying to improve performance – there’s no point in optimizing your rendering if it’s actually your scripts that are running slowly!
So much information!
For the purposes of rendering, there’s an entire Profiler section! The Rendering Profiler keeps track of the number of batches, setPass calls, triangles, and vertices in each frame. Looking here for inconsistencies, spikes, and just high numbers in general is a good way to get an idea of why your game is taking so long to render. The Profiler also has a lot of other info that’s useful for diagnosing and debugging performance problems. I really recommend profiling your game and thoroughly looking through the results to get as much information about how your game is running as possible.
Android Debug Bridge
While profiling in the editor is pretty useful, it doesn’t tell us much – of course our game will be fast on a great big computer, but how does it run on a crappy phone?
The is where ADB, or the Android Debug Bridge, comes in. ADB allows your computer to communicate with your Android phone about all sorts of stuff. Specifically (for our use cases), it allows you to profile your game while it’s running on a device. If you plug your phone into your computer, build the game directly to your phone, and open the profiler, you should see some results. This is the information we want, because it tells a much truer story about how your game runs on a phone.
Where Shadows Slumber, for instance, runs at ~200 fps in the Unity editor. When I plug my phone (the Google Pixel 2) into the profile, I get a framerate of ~60 fps. This is pretty good, so I know our game can run on newer devices. However, when I plug in my old phone (a broken HTC One M8), I get closer to ~12 fps. Looking at the profile during this run will give me much more useful information about what I should fix, since this is the device where performance is actually suffering. If you’re making any big decisions or changes based on profiler results, make sure those results come from your actual targeted device, and not just from the editor.
ADB usually comes with the Android SDK – if you have the Android SDK set up with Unity (which allows you to build to Android devices), then you should be able to use ADB with the profiler pretty painlessly.
I should also mention that there might be an equivalent tool for iOS debugging, but, as I do all of my development on a Windows machine, and all of my testing on an Android phone, I wouldn’t know what it is. Sorry!
The next most important tool for rendering performance is the Frame Debugger (Window -> Frame Debugger). While the Profiler tells us a lot about what’s happening during rendering as a whole, it still treats the rendering process as a black box, not letting us see what’s actually happening. The is where the Frame Debugger comes in – it allows us to see, step by step, exactly what the GPU is doing to render our scene.
As I mentioned last week, the GPU renders the scene through a bunch of draw calls. The Frame Debugger allows us to see what each of those draw calls is drawing. This allows us to determine which materials/shaders are causing the most draw calls, which is one of the biggest contributors to rendering lag. It also provides a bunch of information about each draw call, such as the properties passed to the shader or geometry details. The important thing that it tells you is why this draw call wasn’t batched with the previous draw call.
All of this happens in a single frame
Batching is Unity’s first defense against rendering lag, so it makes sense to batch as much stuff into a single draw call as possible. Because rendering is such a complex process, there are a lot of reasons why draw calls can’t be batched together – certain rendering components simply can’t be batched, meshes with too many vertices or negative scaling can’t be batched, etc. The frame debugger will tell you why each draw call isn’t batched with the previous one, so you can determine if there are any changes you can make that might reduce the number of draw calls, thereby improving rendering performance.
For example, in Where Shadows Slumber, we re-use meshes in certain places. Sometimes, if we require a “mirrored” look we’ll reuse a mesh, and then set the scale to -1. This was before we really looked into rendering performance, and, unfortunately, it causes problems – a mesh with negative scaling can’t be batched with a mesh with positive scaling, so this ends up creating multiple draw calls. Rather than setting the scale of the object to -1, we simply import a new, mirrored mesh and update the object, allowing these draw calls to be batched and improving performance.
That’s it for the heavy-hitters; between the Profiler, Frame Debugger, and ADB, you should be able to get a pretty good idea of what’s going on in render-land. Unfortunately, digging through them can take a while – sometimes you just want a quick indicator of what’s going on in your scene. Enter the Stats window.
The Stats window (click “Stats” in the Game View) is a small overlay in the game view which gives you a quick rundown of various rendering indicators in real time. It’s not as in-depth, but it gives a much quicker picture of performance.
That’s a lot of batches!
While it sounds like the stats window doesn’t add much – after all, the Profiler can give you the same information – I’ve found it to be very useful. The Profiler is probably better when you’re actively debugging rendering performance, but the stats window allows you to notice places where rendering performance might take a hit, even when you’re doing other things.
When I’m testing some other part of the game on my computer, I’m not going to notice any rendering lag, because my computer is so much more powerful than a phone. I’m also not going to be looking at the Profiler or Frame Debugger, because I’m not worrying about rendering at the moment. However, if I have the stats window open and I notice that the number of draw calls is in the hundreds, then I know something is going on. At that point I can get out the Profiler and see what’s happening – but I wouldn’t even have known there was anything amiss if it weren’t for the stats window.
Scene View Draw Mode
As we get further and further down the list, we’re moving from “debugging all-star” to “it’s useful, but you probably won’t use it much”. Scene View Draw Modes fall into this category, but they’re still good to know about. You can access different Scene View Draw Modes by clicking the drop down menu at the top right of the scene view window.
The Scene View in Unity is one of the main windows that you use to make your game – it shows everything in the scene, allowing you to move around through the scene and select, move, rotate, scale, etc., any game objects. Usually the Scene View just displays the objects exactly as they would be displayed in the game. However, it has a bunch of other modes, and some of them are actually pretty useful. The two that I find the most useful when considering rendering concerns are listed below, although they’re all worth checking out:
Shaded Wireframe: This is my default draw mode, as it looks pretty similar to the normal shaded mode. The difference is that it also shows all of the triangles and vertices that you’re drawing. This is useful because certain shader operations are performed once for every vertex. Decreasing the number of vertices in your scene can give you a bit of a performance boost, and the shaded wireframe draw mode helps you see when you might have too many vertices.
The shaded wireframe shows that there are too many polys.
Overdraw: This mode draws each object as a single transparent color. This makes it very easy to see when multiple objects are being drawn in the same spot on the screen. Since the GPU has to draw every pixel of each object (even if that pixel will be overwritten later), it ends up wasting some calculations. Areas that are very bright will waste even more calculations. Switching to this draw mode every so often lets you know if there are any places where you might want to remove some meshes.
It should pretty much go without saying, but one of your best resources for debugging performance is the internet. Unfortunately, when it comes to rendering in Unity, the information out there is pretty scarce. Unlike with normal imperative coding, where you can simply Google “how to pathfinding” and get 30 implementations, you have to work a bit harder with rendering stuff. I find it’s best to do what you can and only resort to the internet with very specific questions. That said, there is still a lot of helpful information out there. You just have to know going in that only one of every three stack overflow questions makes any sense, and only one of every four Unity forum threads are using the most recent APIs. It’s like “Googling: Nightmare Mode”!
For anyone reading this post who is actually working on rendering stuff – I’m very, very sorry. I hope that this post and the tools I discussed help to shed at least a little bit of light in the dark underworld that is shader-land, and I hope you can achieve your rendering goals and make it back to the mortal realm before your soul is forever lost.
For everyone else who hasn’t done any rendering stuff, I hope you learned a bit, and that maybe I inspired you to get involved with some rendering code! It’s really not that bad, I promise!
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
If you didn’t already have a working knowledge of rendering, I hope this post helped! If you do know about rendering stuff, I hope you don’t hate me too much for my imprecision! You can always find out more about our game at WhereShadowsSlumber.com, find us on Twitter (@GameRevenant), Facebook, itch.io, or Twitch, join the Game Revenant Discord, and feel free to email us directly with any questions or feedback at contact@GameRevenant.com.
Jack Kelly is the head developer and designer for Where Shadows Slumber.