Ammo.js Sub-step Count CPU Performance Impact
This article examines how drastically increasing the sub-step count in the ammo.js physics engine affects CPU performance. It explains the relationship between sub-stepping and CPU load, details why simulation complexity multiplies with higher step counts, and offers practical alternatives for maintaining physics stability without degrading application performance.
Understanding Sub-stepping in Ammo.js
Ammo.js, a direct JavaScript port of the Bullet Physics engine,
updates its simulation using the stepSimulation method.
This method takes three primary parameters: the elapsed time, the
maximum number of sub-steps, and the fixed time step size.
When the elapsed time of a frame exceeds the fixed time step (typically 1/60th of a second), ammo.js divides the frame into multiple “sub-steps” to maintain mathematical stability. Drastically increasing the maximum sub-step count allows the engine to run more mini-simulations per frame, preventing fast-moving objects from passing through barriers (tunneling) and keeping complex constraint systems stable.
The Direct Impact on CPU Load
Increasing the sub-step count has a direct, often linear-to-exponential, negative impact on CPU performance. Each additional sub-step forces the CPU to execute the entire physics pipeline repeatedly within a single frame. This pipeline consists of several computationally heavy stages:
- Broadphase Collision Detection: The engine must recalculate the bounding boxes of all active rigid bodies to determine which pairs are potentially colliding.
- Narrowphase Collision Detection: For every potential pair identified in the broadphase, the CPU performs precise mathematical algorithms (like GJK and EPA) to calculate exact collision points, penetration depths, and contact manifolds.
- Constraint Solving: Ammo.js uses a sequential impulse solver to resolve collisions, hinges, sliders, and joints. The solver runs a set number of iterations per sub-step. If your solver is configured for 10 iterations and the engine runs 10 sub-steps, the CPU must compute constraint equations 100 times for every single constrained object in that frame.
- Integration: The engine updates the linear/angular velocities and positional coordinates of every active rigid body.
When the sub-step count is drastically increased, the CPU time spent on these four stages multiplies. If the CPU cannot complete these calculations within the budget of a single frame (e.g., 16.6ms for 60 FPS), it causes a performance drop, resulting in stuttering, frame rate drops, and unresponsive user interfaces.
The “Spiral of Death”
A critical risk of high sub-step configurations in WebGL and browser environments is the “spiral of death.”
If a complex physics frame takes too long to compute, the next
frame’s elapsed time (timeStep) will be larger. To
compensate for this larger elapsed time, ammo.js attempts to run even
more sub-steps in the subsequent frame. This further increases the CPU
load, causing the next frame to take even longer. This feedback loop
quickly drags the frame rate down to single digits and can freeze the
browser tab entirely.
Alternatives to High Sub-step Counts
Instead of drastically increasing sub-steps to solve physics instabilities, developers can use more CPU-efficient techniques:
- Continuous Collision Detection (CCD): For fast-moving objects, enable CCD on the specific rigid bodies. This projects a swept sphere to detect collisions before they happen, avoiding the need to increase sub-steps for the entire scene.
- Optimized Fixed Time Step: Instead of increasing
the sub-step count, reduce the
fixedTimeStepsize (e.g., from1/60to1/120) only when high precision is required, while keeping the maximum sub-step limit strictly capped. - Rigid Body Deactivation: Ensure that objects that are not moving are properly put to “sleep” (deactivated). Ammo.js will skip collision detection and integration for deactivated bodies, freeing up CPU cycles for the active sub-steps.