Tuesday, December 1, 2015

[IndieDev/EonAltar] 3200% Physics Performance Increase

There are certain numbers you pretty much never see in programming. You're often pretty pleased when you can get an algorithm performing 25% - 50% faster. Once or twice in your career, you might nail a mind-boggling number like 3200%. Not a typo.

Green wireframes indicate colliders.
In Eon Altar, we use physics colliders for just about every type of interaction you can think of. Actors and the movement cage to keep them on-screen; turning environment rendering occluders on/off; determining when actors are in melee range of other actors; quest triggers; snapping the movement marker to interactable objects; raycasting the movement marker to the ground; occluding ranged attacks; when to change the camera angle/position; and so on.

My list of systems that had physics colliders, and what they interacted with.
With the sheer number of things we use colliders for, the physics system can get really bogged down really quickly. Whereas the engine can cull geometry that's not visible in an active viewport--for example, when a statue goes off screen, the engine isn't spending time and resources rendering that statue: there's no point--that's not the case for physics objects. If the statue has any physics components, they must be calculated. Things off screen absolutely can and should still run in the physics simulation as they can possibly affect things on screen.

Colliders that don't touch other colliders, or don't move, don't cost much to maintain. However, the more contact points colliders have, the more expensive the calculations get. In Eon Altar, before optimizations, an average level would have upwards of 2000 active colliders, with over 8000 contact points every frame. This bogged the system down to about 10 FPS on a good machine. It dipped to 2 - 5 FPS on crappy machines.

This is a small piece of E1S2, The Portal Obstinate. You can see the actors, plus colliders all over the place.
So the end result was clear, we needed to reduce the number of active colliders at any given moment, and when colliders were active, we needed to reduce the number of contact points. Believe it or not, reducing the contact points was actually the easier of the two tasks.

Unity has a pretty handy feature where you can tell it to only have colliders on specific layers contact colliders on other specific layers. For example, the UI colliders need never interact with say, camera colliders because those two systems never interact with each other. Below you can see the default setting in Unity: everything colliding with everything else.

This is the default, and the default is dumb.
This is clearly not what we wanted. I told my boss, Ed, that I needed 3 days of alone time to break down the system and re-design it. At the end of the third day, if I hadn't received the kiss of True Love from my prince, I'd turn back into a mermaid.

However, making it system-wide that only certain layers interact with other layers was easy, once I figured out what layers had to interact with what other layers (which was seriously the hard part, as it required a lot of thinking/designing/researching code). Add to that code to ensure certain components are on specific layers to make them designer-proof--because you don't want to rely on the designers (or programmers for that matter) remembering "rules", that path leads to bugs and heartbreak--and boom, you've significantly reduced the number of contact points even for colliders that are overlapping, as they may not contact at all if their layers don't interact.

Interaction diagram. Note that some layers interact with themselves, but many don't. One layer may interact with a second layer, but the second layer may not interact with the first, which is a subtle but powerful feature for reducing collision calls.
A much more sparse physics layer setup. This is slightly evolved from the above diagram, so they may not match 1:1.
That alone netted us an 800% - 1600% increase in frame rate, depending on the level.

The second trick was to create "Encounter Zones". Giant colliders that turned a bunch of objects in the hierarchy on or off when the party encountered them. The idea being we don't need a bunch of AI or colliders running if the party is nowhere near them. So everything from enemies to traps to loot to interaction points get grouped up in these Encounter Zones, and when the party gets close enough, voila, everything appears!

This caused some other issues, like having to create a method to slowly load NPCs over time (because initializing an actor is really expensive, and if you initialize 15 actors in a single frame, that frame is going to last on the order of seconds), but for the most part it did the job rather admirably. 

Adding in that gave us another 200% - 400% performance increase depending on the level, so in theory, in the worst levels, we could have been looking at upwards of a 6400% total framerate increase, but in practice I measured a 3200% increase in some of the worst parts of some levels.


Conclusion

Granted, these are things we probably should have implemented off the bat, so it's not quite as impressive as I'm making it out to be, but that's sort of the breaks when you only had 2 - 3 programmers making an indie game and way too much to do to keep other people like design unblocked.

Sadly, these sorts of optimizations took a backseat to feature work until design was all, "WTF the game runs so slow!" and engineering was all, "Whelp, time to put features on hold/cut them and fix this issue instead."

Not that the situation was anyone's fault per se, but the reason we got into situations like that was partly because we had not enough programmers and not enough time to finish infrastructure versus feature work, and partly that nobody on the team at the time had shipped a product from scratch, so we didn't necessarily know we'd hit these situations--though I'll be honest, the physics overhaul had been on my list for months and it was just a matter of waiting until it was unbearable such that I could tell folks to leave me alone while I worked my magic. It also ended up being easier, I think, that most of the systems had been implemented and I could holistically solve the system instead of doing it piecemeal over time.

But at the end of the day, it got fixed up, and it's super solid now. Programmers have to remember that new components with colliders need to have their layers thought about (and we have a wiki with guidelines and instructions), but designers can just use the tools and it all does the right thing, which is pretty awesome.
#IndieDev #EonAltar #GameDevelopment

No comments:

Post a Comment