Okay, so I’m not going to get dressed in a sheet and speak in a made-up language while someone projects images from my childhood on me. I’ll leave that to the qualified people. Instead I’m going to talk about performance art in the sense of art that is performant. Having finished SP1 and looked very intensively at art and performance, saying that I’ve learned a lot is an understatement. Having witnessed and worked on so many new systems for FSX (the new shader system, incredibly dense and varied autogen, new water, living world traffic, etc.) I should have looked at performance much differently. We took the traditional route of “Let’s get it all functioning and then we’ll fix performance in the end.” Unfortunately, when you’re dealing with a whole new shader system and an engine that is extremely draw call intensive, the art must be created up front with performance in mind.
Hopefully, through this blog and other venues, I can help people who make content for Flight Simulator X start off with performance in mind. I will admit openly that we still have a bit of performance work left to do. Hopefully, a lot of it will come in with the DX10 update (including more performance work for DX9) and the Xpack. A lot of it will be how we author art content and a lot of it will be actual engine changes to draw larger batches with fewer draw calls. However, despite this performance work, add-ons are a huge part of our product and they are notoriously bad on performance. And we often get blamed for poor authoring choices that result in badly performing 3rd party content. (We also share the blame for not calling out performance in the SDK and also for issuing too many draw calls to start with.)
The number one killer for us (and just about every single DX9 game out there) is draw calls. There is a really cool little app called NVPerfHUD that will let you step through the scene’s draw calls as well as profile a whole bunch of different performance metrics (sorry, it works in debug only). When we load up FSX in NVPerfHUD on a decent graphics card (6800-ish) the graphics card is 100% idle most of the time. Unless you get really close to an object with an expensive shader, the graphics card is never taxed. This is because it is always waiting for the CPU to process and send the draw calls, which it handily processes with ease.
In DX9, every time you have a material switch or texture change, you are issuing a new draw call. So if you change a single constant on the FSX material –> new draw call. If you use a different texture –> new draw call. So for that third party aircraft with 11 1024×1024 textures. That’s a guaranteed 11 draw calls (not to mention something like 50 or 60 megs of texture space used up with the night, spec and normal maps). Then, if each of those textures get split into 3 different materials (glass, metal and rubber), that’s 33 draw calls. Then if that aircraft becomes an AI plane and shows up 10 times in the scene, that’s 330 draw calls, a full third of the recommended draw call budget. If you are adding an autogen 2 object to the scene, if you have more than one draw call per object, performance is going to suffer significantly.
So how do you reduce draw calls? Well, we are taking steps right now to try to batch up all objects with like materials. We already do this with some of the autogen and the road traffic. We are looking to do this with everything we can (ripping out the fixed function pipeline is going to be a huge help for this). This will significantly reduce draw calls on a lot of objects. However, it still won’t fix poorly authored content (which believe me, we have plenty of on our own already and are working hard to fix). If you’re authoring autogen content or buildings, try to pack as many objects onto 1024 or smaller sheets and make sure that those objects all have the exact same FSX material constants. Also make sure that each object is referencing a single texture sheet. This will guarantee that each object is only a single draw call. And when material batching comes in, dissimilar objects that reference the same texture sheet and the same material constants will get batched and drawn together.
Addtionally, as often as possible, use the textures to change the shader values as opposed to using the shader constants themselves. If you control reflection in the diffuse alpha and specular in the specular color and specular alpha, then you should be able to set the reflection and specular power term constants at max and use the texture values to adjust accordingly. You should be able to get rubber, metal, plastic and other surfaces in a single draw call just by adjusting the various portions of the textures. The only thing you should need new material constants for is something like glass, chrome or an object that is transparent or glows.
Also, make sure that all of the material textures are grouped. Put all the chrome on one sheet, because then you can have a single chrome material and a single chrome draw call. Do the same with glass, metal, etc. If you have 11 1024×1024 textures, and each of them has a chrome section, then you are forcing 11 new draw calls when you could have one.
Over the next few weeks, I’ll talk more about draw calls and how to reduce them as well as other performance bottlenecks and pitfalls to avoid.