Did I Stutter ?

Tue, Jun 7, 2011

This is a request for help…

I’ve been experimenting with the HTML5 canvas for simple games like pong or animating starfields and I’m currently working on a breakout style game.

They all work well, but they all have a slightly stuttering animation loop.

There has been a lot of talk about smoothing out browser based animation using requestAnimationFrame but I have yet to see an example that is truly smooth and without stutter, and I have not been able to see any real improvement in my own examples using this over a more traditional setTimeout or setInterval loop.

You can see the problem right here:

And you can find the source code on github.

It starts off with initialization:

  var canvas     = document.getElementById(id);
  var ctx        = canvas.getContext('2d');
  var width      = canvas.width  = canvas.offsetWidth;
  var height     = canvas.height = canvas.offsetHeight;
  var color      = { background: '#EEE', ball: '#111' };
  var size       = 20;
  var x          = 50;
  var y          = 50;
  var dx         = 0.1;
  var dy         = 0.1;
  var maxx       = width  - size;
  var maxy       = height - size;
  var timestamp  = function() { return Date.now(); }

The render function in this example is trivial:

  var render = function() {
    ctx.save();
    ctx.fillStyle = color.background;
    ctx.fillRect(0, 0, width, height);
    ctx.fillStyle = color.ball;
    ctx.fillRect(x, y, size, size);
    ctx.restore();
  }

The update function is a standard bouncing ball:

  var update = function(dt) {

    x = x + (dx * dt);
    y = y + (dy * dt);
    
    if ((dx < 0) && (x <= 0))
      dx = -dx;
    else if ((dx > 0) && (x >= maxx))
      dx = -dx;
    
    if ((dy < 0) && (y <= 0))
      dy = -dy;
    else if ((dy > 0) && (y >= maxy))
      dy = -dy;

  }

And the game loop uses requestAnimationFrame:

  var last = timestamp();
  var frame = function(now) {
    update(now - last);
    render();
    last = now;
    requestAnimationFrame(frame);
  };
  requestAnimationFrame(frame);

I’ve refactored this game loop to use different strategies, using setInterval, or setTimeout instead of requestAnimationFrame, and experimented with various theories:

  • Incorrect use of requestAnimationFrame?
  • Incorrect calculation of dt time delta?
  • Garbage collection interference?
  • Need to interpolate between ‘previous’ and ‘next’ states during render()?
  • Rounding issues of indeterminate intervals - 100060 = 16.6666, steps are either 16ms or 17ms?
  • Fundamental limitation of low-resolution browser timers?
  • Browser bugs?

The code on github is down to its bare bones and I am reaching out looking for suggestions, fixes, or, quite possibly, somebody to explain to me that what I’m looking for is simply not possible in today’s browsers with their low fidelity timing mechanisms.

Or, better yet, fork the github project, fix it, and send me a pull request. That would be fantastic!

I’m testing in

  • Chrome 11 & 12
  • Firefox 3.6 & 4
  • IE 9

So far, unusually, IE9 seems to be the smoothest… and it doesn’t support requestAnimationFrame !

Any suggestions ?