Up until recently, the playfield in my pinball game had been pretty sparsely populated, and performance had been ”fine”. But then I started really fleshing out the table and adding a lot more objects, and that’s when my computer started to sound like a 747 preparing for takeoff. To save my laptop and my eardrums, I decided to do some performance improvements.

Diagnosing The Problem

I began my digging expecting to find that the problem was somewhere in the graphics pipeline. Maybe I was making too many draw calls, or I had way too many polygons, or my fragment shaders were just doing too much work. Maybe rendering the scene an extra 6 times for every ball just to get those beautiful reflections was being too greedy. I opened up the performance tab in Chrome and after running the profiler for 20 seconds of gameplay, the culprit suprised me:

profiling results
It's Physics!

It turn out that about 85% of my cpu cycles were being spent on simulating physics, and about 85 of those 90% were being spent on collision detection. Looking deeper in the profiling results, there were three things that were sucking up our cpu-cycles:

  1. integrating bodies
  2. broadphase collision detection, and
  3. whatever world.internalStep was doing.
deeper profiling
Taking a closer look

My game uses a javascript physics engine called p2. Before doing anything too crazy, I decided to see if I could improve performance just by “turning the knobs” on p2. The first knob I turned was disabling Continuous Collision Detection (CCD) on the ball. With CCD enabled, rather than just checking at the beginning of every physics step if the ball is overlapping any objects, the engine will check to see if there are any bodies anywhere along the path that the ball will travel during this step. Initially I had enabled CCD on the ball because I assumed the ball would be moving really fast and there might be some fairly thin walls that I didn’t want it to clip through, but when stepping through the physics frames one-by-one, I realized that the ball was never actually moving nearly fast enough to start clipping through objects, or even having any noticable penetration. It turns out that CCD is pretty expensive, and by turning it off with just a change to a single line of code, I cut out around 30% of my physics calculation time.

The second change I made before getting too deep into the internals of p2 was reducing the number of bodies I was adding to the world. The curved walls on the table were approximated with a bunch of line segments, and each one had its own body. The Sweep And Prune algorithm that p2 uses to find collisions every frame scales at least linearly with the number of bodies in the world. By combing all the line segments into one body per contiguous wall, I reduced the number of bodies in the world from around 500 to around 50. Profiling showed that this had an even bigger impact than disabling CCD: it had approximately halved the already improved physics calculation time.

With these changes my framerates had increased pretty dramatically, but my laptop still got hot and loud when playing it. Physics were still taking 5 times as long as all of the rendering, so I knew there was still a lot of room for improvement.

Continued in Part 2