2
\$\begingroup\$

I have a quite simple loop that draws an 8px by 8px image many times in a canvas in a tiling fashion. Currently it's drawing 7500 images each loop.

See the jsfiddle.

var img = new Image();
img.src = 'http://i.imgur.com/3dzaMlv.png';

var W = 8;
var H = 8;
var R = 800/W;
var C = 600/H

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var fps = document.getElementById('fps');
var timePrev = new Date().getTime();
var fpsInterval = 30;
var i = 0;

window.setInterval(function() {

    ctx.clearRect(0, 0, 800, 600);

    for (var r=0 ; r<R ; r++) {
        for (var c=0 ; c<C ; c++) {
            ctx.drawImage(img, r*W, c*H);
        }
    }

    // fps
    if (i % fpsInterval == 0) {
        var timeNow = new Date().getTime();
        var spf = (timeNow - timePrev) / fpsInterval / 1000;
        fps.innerHTML = (1/spf).toFixed(2);
        timePrev = timeNow;
    }
    i++;

}, 1000/60);

On my I7-2700K (3.5GHz) processor and ATI Radeon HD7970, I am getting the following framerates:

  • Chrome 36.0.1985.143 m : ~40 FPS
  • Firefox 30.0 : ~55 FPS
  • IE 11.0.9600.17239 : ~35 FPS

Is there any way to improve FPS performance here, assuming I do actually need to draw this many tiles on each update?

To clarify, I'm requiring that they be drawn as individual tiles on each update. So, rendering them all to an off-screen canvas wouldn't work. I am essentially disappointed in the number of drawImage() calls that seems to perform well.

\$\endgroup\$
1

1 Answer 1

4
\$\begingroup\$

Edit: Only now did I notice your comment that you require the tiles to be drawn individually. Since I don't know exactly why that is (I can only review the code I can see), I can't offer you any other advice than what I've already written below.
If you can redraw larger rects using a tiling fill style, or only update some tiles per frame, it'll be an improvement over redrawing everything each frame. But if your exact requirements are "redraw every tile by itself on every frame" then your current code is as probably as good as it gets. But the end result is the same as the much more efficient methods outlined below.

Edit edit: Don't know why I didn't think of it sooner, but off-screen drawing could be an improvement. The link provides a nice simple implementation.


You can draw the background using a single rect with a tiling background, and not do any drawImage calls. The technique is outlined in this StackOverflow answer. In its simplest form:

ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fillRect(0, 0, canvas.width, canvas.height);

Here's a demo - it runs at the target 60fps (in Chrome at least).

Alternatively, you could just set the image as a repeating CSS background image, and not do any JS at all. Just:

canvas { background: url(...) repeat; }

And done. Though I won't guarantee how that'll work with whatever you intend to draw on top of it.

In terms of code review, I noticed a couple of things

  • Single-letter variables - always better to use full words (the exception being known conventions like the i variable in a loop)

  • Speaking of, you can also avoid the hard-coded values for R, C, W and H "constants". The canvas' width/height you can get as simply canvas.width and canvas.height. Similarly (once the image has been loaded; see below), you can get its dimensions the same way: image.width and image.height.

  • You can use Date.now() instead of new Date().getTime()

  • You don't actually wait for the image to be loaded. It's a tiny image, but the network may be slow. So (as shown above), you should wait for the load event to fire on the image object, before trying to use it:

    var image = new Image();
    image.src = "...";
    image.onload = function () {
      // use the image here
    };
    

In general, though, don't expect exceptional performance from canvas. It's good, but it's not compiled code being mainlined to the GPUs in parallel. You're writing in JS - an interpreted language with a single-threaded runtime - doing compositing on top of an already complex view (the web page). Your many calls to drawImage each require the computer to do a lot of extra work - your loop is not a single operation; it's many individual operations and - depending on how smart the browser is, how much hardware acceleration there is, etc. - it takes time to update the screen after each draw.

\$\endgroup\$
7
  • \$\begingroup\$ Thanks for all the extra information. What I am really looking at here is the bottleneck for drawImage() calls; the fact that they're all the same image is just for the test. And I appear to have reached it. I wonder if performance will improve in the future? \$\endgroup\$
    – Tony R
    Commented Sep 3, 2014 at 17:40
  • \$\begingroup\$ @TonyR Yeah, probably, but don't hold your breath. I mean, performance will automatically improve with hardware speeds (of course), but canvas is also a "young" addition to browsers compared to so much else, so I'm sure people will find more optimizations. Regardless, drawImage will always be a very high-level call; the tip of a very large iceberg of tech. You are about as far away from setting the actual pixels as you can be. \$\endgroup\$
    – Flambino
    Commented Sep 3, 2014 at 18:54
  • \$\begingroup\$ @TonyR Actually, one trick that might help you now would be off-screen rendering. The basic idea is to draw to a canvas that's not on the screen, and then transfer the composited content to the canvas the user sees. Haven't tried it myself, so I can't say how much of a difference it'll make. \$\endgroup\$
    – Flambino
    Commented Sep 3, 2014 at 18:56
  • \$\begingroup\$ So, I originally posted this on StackOverflow and got redirected here, but someone commented on that post about getting a small FPS boost by simply rendering offscreen first. I forked the jsfiddle to try that. It looks like I am getting about 7 more frames (40 -> 47 = 18% increase). \$\endgroup\$
    – Tony R
    Commented Sep 3, 2014 at 19:56
  • \$\begingroup\$ @TonyR Ah. Well, ~18% ain't nothing \$\endgroup\$
    – Flambino
    Commented Sep 3, 2014 at 20:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.