Tuesday, March 12, 2013

Performance Anxiety

What little rationale there was behind building a 2D tile-bsed platformer in Unity went something like this: learn the tools and workflow on something small enough where you won't have to worry about optimizing for performance. This was naive.

The tile map implementation I described previously involves painting tiles in the scene view, each tile consisting of a textured mesh. When I picked my head up from my second layout of the opening level (wonder what number that will get up to) and pressed Play, Unity thought for awhile. Twenty-one seconds, to be exact. My gameplay and scenery grids combined were holding 8087 tiles, each of which contained a single quad.

Many things were vexing about this. For one, the stats window showed 384 verts comprising 192 triangles, which didn't seem like all that many to me. For another there was the fact that after that twenty second startup, Unity seemed pleased enough to hum along at a triple-digit FPS while the playfield was traversed; there was no in-game performance hit. Without a deeper understanding, or the CPU/memory profiler Unity offers for a mere fifteen hundred bucks (along with a Pro license), there was little I could do but conclude that I was just Loading Too Many Things.

Idea #1: "Well, how many is too many?"

My Test mind kicked in. I wanted to repro the issue, isolate it from its context. I made the scene shown above, with a total of 10180 red cubes, boasting 104,800 vertices making 52,400 triangles. The whole thing loads in less than three seconds. OK, interesting. Apparently 8087 tiles is not too many. What else is going on?

Idea#2: "It's the materials."

I was doing what I thought was a really swell thing by confining my entire city scene to one 256 x 256 texture made of tiles, which were arranged for display, uv-wise, by a material unique to each tile. Maybe that's bad? Really grasping here, not even mentioning stuff I thought of before the red cubes, like stitching all the meshes together at runtime (possible, but goodbye texture data and anyways I tested it and that would happen in script after my startup problem is already come and gone, so who cares?).

Anyway the point is I was desperate for a simple answer. I decoupled all the materials from all the prefabs, turning everything in the level pink. My start time somehow went up to 24 seconds.

Idea #3: "It's a script"

Since this was a one-time perf hit at startup, perhaps one of the scripts was looping over the tiles at startup in some poorly thought out way. I feel like I would have noticed that earlier than I did? Whatever though. Let's comment everything out. 24 seconds.

Idea #4: "Uhh maybe ... the tile editor's Instantiation method, isn't that deprecated?"

Ugh fine whatever. Let's rebuild the level from scratch, with the same tools, and figure out where and when the perf hit starts to happen. OK looks like when we get over about a thousand of these we start to bog down. With the new version of Instantiate, and without materials. These objects are piling up at around 1000, and the cubes were loading like butter into the quintuple digits.

Idea #5: "Something about a mesh as opposed to a cube?"

Turned off shadows. Tried a cube. Tried a sphere. 

PrefabUtility.InstantiatePrefeab and it's deprecated Editor cousin allow you to do things like paint in scene view, you're painting with prefab instances, so it's hard for me to conceptualize that you would need to Instantiate all those again at runtime, because I already did that, I'm looking at them, they're right there, but I guess what I'm looking at could be a preview of some kind. It would make sense that it would take some time and resources to bring all those cubes into being, and that's really the only thing left that seperates that tower of cubes from my level: the cubes aren't prefab instances.  

That points me toward really dreadful things like asynchronous level loading (another pro feature), and eventually I arrive at

Idea #5: "I guess we'll have to find a way to render a level without loading thousands of tiles"

I guess you could, you know, use a hundred or so tiles for sidewalks and platforms, then just draw some backgrounds and put them on big quads. Rather than, you know, making a skyscraper out of hundreds of individual meshes, each uv mapped to part of a single small texture, a "solution" that somehow joins the worst aspects of all possible approaches. I was so close, though! Throw that texture in and just paint the level, so convenient! Wait, hold on, if prefabs are the problem, why not

Idea #6: Alter the editor window script to stop instantiating prefabs and instead make meshes from scratch with the appropriate materials on them!

Oh lord. All right, worth a shot. Redid the tileMap code to make a primitive again, like the demo script, instead of instantiating. Used cubes as Unity has no Quad primitve type. Rotated the cubes. Updated the custom editor to paint materials instead of prefabs. Applied a variety of textures to simulate what I'm actually doing in the level.

Bam. 7956 cubes. Feels like between three and four seconds. Now, though, even that length of load feels kind of intolerable. Eh, I think if I do the buildings as scaled-up single quads and leave the tiles for the floor and platforms I'll be OK, shouldn't be more than a second. The lesson, as far as I can tell is: prefabs are slow. That is, unless after my third buildout of the level I find out the problem is actually something else...

No comments:

Post a Comment