Performance is important to The Botanist. I don’t love premature optimization, but it’s important to understand the performance bottlenecks of the game on various platforms and in various browsers from an early stage, lest they become insurmountable issues later.

The biggest performance killer is the smooth-scrolling camera follow mode. Scrolling has to be perfectly smooth, running as close as possible to 60 FPS with little variation, otherwise it’s really noticable. Scrolling, in general, is a hard problem, and is in fact one of the biggest CPU hogs in everyday desktop computer usage as well. I wrote recently about a mobile-efficient camera scroll, which avoids smooth scrolling altogether and really improves the mobile experience, but I want to get smooth scrolling working really well on desktop platforms. The bulk of my testing so far is in Chrome and Firefox, so most of this post is about Firefox.

The problem is that smooth-scrolling camera follow is janky on Firefox. Chrome handles everything you can throw at it and runs at 60 FPS no problem, but Firefox struggles. My first observation was that the problem gets worse with a larger game canvas, so the first step in debugging was to really exaggerate the problem and increase the canvas size from my standard 768x512 to 1200x720. Chrome gets a little jittery at this size, but Firefox dips down to 15 FPS while scrolling. Let’s fix it!

WebGL is NOT (always) the Answer

By default, Phaser’s renderer is set to Phaser.AUTO which will choose WebGL if it’s available. Unfortunately, WebGL is pretty janky on every browser (including mobile browsers) except Chrome. Switching the renderer to Phaser.CANVAS really improves performance on most platforms. WebGL should be faster, better, and more efficient in theory, but the reality is that WebGL is younger than Canvas and still has its kinks to work out.

Here’s the difference:

  • Phaser.AUTO at 768x512 on FF: 45-55 FPS while scrolling
  • Phaser.CANVAS at 768x512 on FF: 55-60 FPS while scrolling
  • Phaser.AUTO at 1200x720 on FF: 15-20 FPS while scrolling
  • Phaser.CANVAS at 1200x720 on FF: 30-35 FPS while scrolling

I didn’t take thorough benchmarks on mobile, but I can say confidently that performance was greatly improved on mobile browsers as well. Only Chrome didn’t seem to care about the difference.

Dig Even Deeper

Doubling framerate by disabling WebGL is a huge step, but we’re still only running at 30 FPS on the big screen. Granted, our performance is pretty great now at our intended game size of 768x512, but I want some more breathing room to play with.

Here’s where Firefox’s Profiling Tool comes to the rescue. Let’s run the profiler while scrolling and see what happens:

Firefox Profiler

We see that Phaser.TilemapLayer.shiftCanvas is eating up a lot of cycles. Let’s look at that method and see if we can identify what the performance hog is.

Shift Canvas

Commenting out the globalCompositeOperation line improves performance hugely (breaking the game, of course, but still). It turns out that Firefox doesn’t love doing lots of global composite operations on the canvas. So we need to figure out some way to avoid doing this on FF. We discover that shiftCanvas is being called by renderDeltaScroll, and that renderDeltaScroll is being called here:

Render Delta Scroll

This section of the code, renderDeltaScroll and shiftCanvas, is intended to avoid redrawing the entire canvas when all the new content you need to draw is just the edge of the screen you’re walking towards. It’s an excellent approach – draw only new material, and just shift the existing stuff over on the canvas. Unfortunately, in practice globalCompositeOperation is actually more expensive than simply redrawing the whole canvas from scratch on Firefox. This situation is similar to WebGL’s – it’s great on paper, but the browser’s implementation of it is lacking.

So how do we avoid globalCompositeOperation? Happily, Phaser already has a built-in mechanism to avoid delta scrolling! All we have to do is set the tilemap layer’s renderSettings.enableScrollDelta to false and we’ll force redraws of the entire canvas every time. Here’s what that looks like in practice:

this.groundLayer = this.map.createLayer('GroundLayer');
this.groundLayer.renderSettings.enableScrollDelta = false;

That second line is new. And what a difference it makes! Here’s another comparison:

  • Delta Scroll Enabled at 728x512: 55-60 FPS
  • Delta Scroll Disabled at 728x512: 60 FPS
  • Delta Scroll Enabled at 1200x720 on FF: 30-35 FPS
  • Delta Scroll Disabled at 1200x720 on FF: 45 FPS

We’re still not running at 60 FPS on the big screen, but by disabling WebGL and delta scrolling I was able to triple smooth scrolling performance on Firefox for our absurdly large test screen. And now our intended game size runs consistently and smoothly at a steady 60 FPS, with no dips down to 55.

This experience has also taught me that I’ll have to create custom performance rules for different browsers. Since Chrome runs smoothly, using delta scroll is probably better since it’s less resource intensive. And I’m sure there are some platforms that WebGL works smoothly on, and that should be enabled where possible, too.

I’m certain there will be more performance tuning posts throughout the course of this year. I still want to get our big screen running at 60 FPS, and you’ll also see some more benchmarks from other browsers and mobile platforms in the coming months.

Moral of the story? Don’t be afraid to tinker, tweak, dig into source code, and play with the performance profiler!