Profile Physics Calculation Times in ammo.js

This article explores whether ammo.js contains built-in methods to profile its internal physics calculation times. While the underlying C++ Bullet Physics engine features robust internal profiling systems, these are not exposed by default in the Emscripten-compiled JavaScript/WebAssembly port. We will examine the limitations of ammo.js’s built-in profiling, how to measure physics step times using native JavaScript tools, and how to leverage browser developer tools for deeper analysis.

The Availability of Built-In Profiling in ammo.js

The original C++ Bullet Physics engine includes a built-in profiling utility called CProfileManager (and the btQuickprof clock). This system tracks how much time the engine spends on specific sub-tasks, such as broadphase collision detection, narrowphase collision detection, and constraint solving.

However, ammo.js does not expose these internal profiling methods by default.

Because ammo.js is a port generated via Emscripten using WebIDL bindings, only the classes and methods explicitly defined in the ammo.idl interface file are accessible in JavaScript. The internal profiling classes are excluded from this interface to keep the library file size small and execution speed optimized.

Consequently, there is no direct, out-of-the-box JavaScript API in ammo.js to query internal sub-step calculation times.

How to Profile ammo.js Performance

To accurately profile your physics simulation in ammo.js, you must use external measurement techniques. Below are the two most effective methods.

1. Manual Step Timing (JavaScript Performance API)

The most straightforward way to measure physics execution time is to wrap the stepSimulation call with the high-resolution timestamp API, performance.now(). This measures the total time spent in the WebAssembly execution context for a single frame.

// Start the timer
const startTime = performance.now();

// Step the physics world (dt is the time delta since the last frame)
physicsWorld.stepSimulation(dt, 10);

// End the timer
const endTime = performance.now();
const physicsTimeMs = endTime - startTime;

console.log(`Physics step took ${physicsTimeMs.toFixed(2)} ms`);

This approach provides the overall execution time of the physics loop, which includes collision detection, rigid body updates, and constraint solving combined.

2. Browser Performance Profilers

To break down where the WebAssembly module is spending its time without writing custom C++ wrapper code, you can use browser-based developer tools.

  1. Open your browser’s Developer Tools (F12).
  2. Go to the Performance (or Profiler) tab.
  3. Start a recording, run your ammo.js simulation for a few seconds, and stop the recording.
  4. Look at the Flame Chart.
  5. If you are using a build of ammo.js compiled with WebAssembly debugging symbols (or source maps enabled), you will be able to see the specific C++ function names (e.g., btCollisionWorld::performDiscreteCollisionDetection) inside the WebAssembly call stack.

This method allows you to visually identify whether bottlenecks are caused by collision resolution, broadphase overhead, or solver iterations.

Advanced: Exposing Bullet’s Internal Profiler

If you require access to the exact timing of Bullet’s internal subsystems programmatically in JavaScript, you must rebuild ammo.js from source:

  1. Locate the ammo.idl file in the ammo.js source repository.
  2. Add bindings for CProfileManager, CProfileIterator, and related profiling classes.
  3. Recompile ammo.js using Emscripten.

Once compiled with these bindings, you can traverse the profile tree directly in JavaScript to get precise timing breakdowns for internal physics operations.