Performance tips

Obi has lots of parameters available, and there's often many possible ways to set up a simulation. This makes it very flexible and puts a lot of control in your hands. But, like a wise man once said "with great power comes great responsibility": it's pretty easy to shoot yourself in the foot if you're not careful.

Obi does everything in its hand to run as fast as possible: takes advantage of all processing cores in your hardware, making full use of vectorization thanks to Unity's Burst compiler. However, the kind of physical simulations performed by Obi require a lot of math-heavy calculations and this means that you should strive to skip any unnecessary work.

Without further ado, let's get into best practices to squeeze as much performance out of Obi as possible.

Make sure Burst is enabled, Jobs Debugger and safety checks are disabled.

This is the most common reason for bad performance out of the box. If Burst is disabled, your code won't be vectorized which means the cost of most math operations will noticeably increase. Having the Jobs Debugger and/or safety checks enabled will add extra checks troughout Obi's code, slowing it down.

You can enable Burst and disable the debugger/safety checks in Unity's "Jobs" menu. These have a huge impact on performance. Check the backends page to see how to properly set up Burst.

Profile!

When facing any performance problem, this should always be your first stop. If you're not very familiar with Unity's profiler, it can be of immense help when to determining what your performance bottleneck is. If you're unsure how to interpret profiling data, contact us and send a profiler pic (preferably in timeline mode), we'll help you extract valuable information from it.

Use as few substeps and iterations as you can get away with.

Substeps are Obi's main quality/performance tradeoff. They allow you to set the simulation's temporal resolution (that is, how often the simulation gets updated). In each substep, constraints are evaluated a number of iterations. Hence, the more substeps and iterations you use the more accurate your simulation will be and the worse it will perform.

As a rule of thumb: start by setting all constraint iterations to 1 in the ObiSolver, and the amount of substeps to 1 in the ObiFixedUpdater. Then, slowly increase the amount of substeps (going over 10 is seldom needed) until ropes/cloth aren't too stretchy, and fluids/softbodies aren't too bouncy. Finally, if you feel like a certain type of constraint needs an extra "oomph", try expending a few more iterations on it.

If you haven't already, you should take a look at this page as it contains a very in-depth explanation of how simulation works internally and the impact substeps and iterations have on it.

Disable constraints you're not using.

Each solver allows you to globally disable constraints for all actors managed by it. Some constraint types don't have a large impact on performance if you leave them enabled (even if no actor makes use of them), but some others like collisions and particle collisions do.

To be on the safe side, if you're not using a specific type of constraint, disable it. For instance, if your solver contains cloth flags waving around that do not collide with themselves or each other, disabling particle collision constraints and particle friction constraints will skip a lot of unnecessary calculations to determine potentially colliding particle pairs.

Don't use multiple updaters unless absolutely necessary.

Updaters (ObiFixedUpdater, ObiLateFixedUpdater, ObiLateUpdater) are in charge of "ticking" or updating the simulation of one or more solvers. All solvers in a updater are updated in parallel.

When using a single solver, it's not uncommon to place the updater component in the same GameObject. If you later need another solver, you might copy/duplicate an existing one: be careful not to duplicate both the solver and the updater. This will result in each solver being updated by its own updater, which means they will be updated sequentially: one solver first, then the second. Using the same updater for both allows Obi to update both at the same time, which makes much better use of your CPU's multithreading capabilities.

When you create a new solver using Unity's GameObject menu, Obi will automatically look for the first existing updater in your scene and add your solver to it. Only if there's no pre-existing updater in the scene, a new one will be created.

MeshColliders are extremely costly.

Meshes are made of many triangles, during collision detection each particle must examine all triagles to determine which ones it will collide against. This is specially bad if triangles are small in comparison to a particle, as that means many triangles can collide with each particle. Always consider distance fields as an alternative to MeshColliders, since they're much faster and also more robust.

Keep an eye on rendering too.

While simulating cloth, ropes, fluids, or softbodies is costly, rendering them can also be.

Cloth renderers have several available update modes, some are more expensive than others. Always use the cheapest one that fits the bill.

Ropes/Rods can be smoothed and decimated for rendering. Smoothing increases rendering cost, decimation reduces it. You can combine both at will. You also have multiple renderer components to choose from, always use the simplest you can get away with. If you're using the ObiRopeExtrudedRenderer, remember you can use a custom rope section with less segments to make it faster to render.

Fluid rendering happens in screen-space, which means your GPU's shading/fillrate capabilities have a large impact on performance. Increase the downsampling settings for cheaper (and slightly worse looking) fluid rendering. This is specially imporant in mobile devices.

Beware of death spiralling.

In many ways, death spiralling (aka the "well of despair") is the final boss of physics simulations: it has the potential to absolutely obliterate performance, making your game will run at a couple frames per second by exacerbating pre-existing performance issues.

The less powerful your hardware is, and the more complex your physics are, the more at risk your game is to develop a bad case of death spiralling. The first step to prevent it is to understand what it is and why it happens, this has close ties with how FixedUpdate() works in Unity and fixed timestepping in general. Explaining all of that here would require a lot of time/space, but there's some online resources that can help:


Detecting it is relatively easy: open up Unity's profiler and check whether FixedUpdate() is being called more than once per frame. If it is, then you should either reduce the complexity of your simulation, increase Unity's fixed timestep, or decrease Unity's max allowed timestep (both found under Edit->Project Settings->Time).

Reduce the amount of particles/constraints.

One of your last resorts is to just use less particles and constraints to represent your actors. This is done slightly differently depending on which kind of actor we're dealing with:

Cloth:
Just use a less dense mesh: reduce its polycount.

Ropes/Rods:
Reduce the blueprint's resolution. This will use less particles per length unit, which may open up gaps in the rope. If this becomes a problem for collision detection (other objects can pass between rope particles), try using surface collisions.

Fluids:
Reduce the blueprint's resolution. This will use less particles per volume unit.
Softbodies:
Reduce the blueprint's resolution. This will use less particles per volume/surface unit.