4
\$\begingroup\$

As a followup from a previous post, I have been trying to track down some slowdown I am having when drawing a scene using Javascript and the canvas element. I decided to narrow down my focus to a REALLY barebones animation that only clears the canvas and draws a single image, once per-frame. This of course runs silky smooth in Chrome, but it still stutters in Firefox. I added a simple FPS calculator, and indeed it appears that my page is typically getting an FPS in the 50's when running Firefox.

This doesn't seem right to me, I must be doing something wrong here. Can anybody see anything I might be doing that is causing this drop in FPS?

<!DOCTYPE HTML>
<html>
<head>
</head>
<body bgcolor=silver>
<canvas id="myCanvas" width="600" height="400"></canvas>

<img id="myHexagon" src="Images/Hexagon.png" style="display: none;">

<script>
window.requestAnimFrame = (function(callback) {
    return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function(callback) {
                window.setTimeout(callback, 1000 / 60);
            };
})();

var animX = 0;
var frameCounter = 0;
var fps = 0;
var time = new Date();

function animate() {

    var canvas = document.getElementById("myCanvas");
    var context = canvas.getContext("2d");

    context.clearRect(0, 0, canvas.width, canvas.height);

    animX += 1;
    if (animX == canvas.width)
    {
        animX = 0;
    }

    var image = document.getElementById("myHexagon");
    context.drawImage(image, animX, 128);

    context.lineWidth=1;
    context.fillStyle="#000000";
    context.lineStyle="#ffffff";
    context.font="18px sans-serif";
    context.fillText("fps: " + fps, 20, 20);

    ++frameCounter;
    var currentTime = new Date();
    var elapsedTimeMS = currentTime - time;
    if (elapsedTimeMS >= 1000)
    {
        fps = frameCounter;
        frameCounter = 0;
        time = currentTime;
    }

    // request new frame
    requestAnimFrame(function() {
        animate();
    });
}

window.onload = function() {
    animate();
};

</script>
</body>
</html>

--EDIT--

I did some additional experimentation, and now I'm not sure that this issue really has anything to do with canvas at all.

I added a "frame duration" counter in miliseconds, and neither Chrome nor Firefox ever go above 1 ms, so the code I am running inside animate is certainly not exceeding the 60 FPS frame time. My current theory is that the Firefox browser just isn't giving me a consistent animation frame that is locked to V-sync; I had read some old posts about Firefox being capped in the 50's, and maybe this is still true?

So I tried a new method to compensate for the problem. I started "pooling" my update time, and running an updatge step for each 16.6666 milisecond increment in the pool. Frankly, this looks almost MORE jittery, but I am guessing that it at least maintains a more consistent timeline for a game. Blech though :( Is this really the best way to go?

\$\endgroup\$
9
  • \$\begingroup\$ Sidenote, you don't need to wrap one-function callbacks in anonymous functions, you can use them directly, e.g. requestAnimFrame(animate); \$\endgroup\$
    – Tapio
    Sep 19, 2012 at 5:54
  • \$\begingroup\$ jsfiddle: jsfiddle.net/eNYC5 I don't have a Firefox on this machine, so can't test now unfortunately. Btw, are you running the latest version of FF? \$\endgroup\$
    – Tapio
    Sep 19, 2012 at 6:02
  • \$\begingroup\$ 50 fps seems allright to me. \$\endgroup\$ Sep 19, 2012 at 9:32
  • \$\begingroup\$ If 50 fps is alright, how are we supposed to maintain smooth animation? Or is that just not quite an option yet for browser games? \$\endgroup\$
    – jujumbura
    Sep 19, 2012 at 15:44
  • 1
    \$\begingroup\$ Not sure if this will make a difference, but you should try moving the canvas, context, and image vars out of the animation function. Getting those each time could be expensive. They don't need to be inside the loop since they are static. \$\endgroup\$
    – jackrugile
    Sep 20, 2012 at 3:05

3 Answers 3

2
\$\begingroup\$

From what I've found out while working with canvas, Firefox is indeed a bit slower than Chrome. The most important problem that I also experience and haven't found a way around is the stuttering (which can sometimes add up to one second)... but that most probably comes from their JS engine, not canvas. Either way, if it works well in Chrome without any special hacks, it's a bug that needs to be reported.

\$\endgroup\$
2
\$\begingroup\$

From my experience with Canvas in Firefox, I can tell that setting/changing the font of the canvas context seems to be a costly operation. I have no idea why, I can only assume it has to do with the fact that the font property is a complex string that has to be interpreted each time it's assigned. That being said, try to avoid changing the font as much as possible, especially when unnecessary.

\$\endgroup\$
1
  • \$\begingroup\$ This was absolutely the culprit for me. My canvas game in Firefox was stuck in the 40s and 50s. When I moved the setting of the font outside of the main loop Firefox was able to hold at 60 fps just like Chrome, IE11, and Edge do. \$\endgroup\$ May 9, 2016 at 1:50
0
\$\begingroup\$

First thing : if your screen has a 60 fps refresh rate, no Browser can go beyond that point. I own a 120 fps screen, on which Chrome give me those 120 fps, while FireFox stays around 50 fps.

Why ?
Because FireFox tops the fps on every computer :
https://developer.mozilla.org/en-US/docs/DOM/window.requestAnimationFrame

Btw : Your polyfill can be improved a bit :

var myRequestAnimationFrame =  window.requestAnimationFrame ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function(callback) {
                  window.setTimeout(callback, 10);
               };
 window.requestAnimationFrame=myRequestAnimationFrame;

So you avoid one useless function call (...), and since setTimout rounds the times you have to choose between 10,11,12, or 13. I used 10 because most likely a Browser not handling rAF will have a 10 ms accuracy timer (or worse).
Rq : I do not assign directly window.requestAnimationFrame= ... || ... || because there once was a bug with that.

Btw 2 :
I don't know if you did it just for this small demo, but you have several calls to the DOM within your Animation, which must quite slow down things, especially on FireFox. Put all objects within a var (or an within an object). And re-call animate directly with requestAnimFrame(animate)... And several other small things :-) :

var myRequestAnimationFrame =  window.requestAnimationFrame ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function(callback) {
                  window.setTimeout(callback, 10);
               };
window.requestAnimationFrame=myRequestAnimationFrame;

var animX = 0;
var frameCounter = 0;
var fps = 0;
var startTime = -1;
// cache all DOM references
var canvas = document.getElementById("myCanvas");
var canvasWidth=canvas.width;
var canvasHeight=canvas.height;
var context = canvas.getContext("2d");    
var image = document.getElementById("myHexagon");
context.font="18px sans-serif";

function animate() {
    context.clearRect(0, 0, canvasWidth, canvasHeight);

    animX = (animX + 1) % canvasWidth;

    context.drawImage(image, animX, 128);

    context.lineWidth=1;
    context.fillStyle="#000000";
    context.lineStyle="#ffffff";
    context.fillText("fps: " + fps, 20, 20);

    frameCounter++;
    fps = (Date.now() -startTime) /frameCounter;
    // Date.now() is faster and does not create garbage.

    // request new frame
    requestAnimationFrame(animate);
}


window.onload = function() {
   startTime = Date.now();
   animate();
};

You also can have a noticeable gain in performance (30% if i remember well) by drawing the image to a new canvas, then using this canvas for the drawImage. I'll update this post if i find my lib which does that, but it's quite easy to figure out.

\$\endgroup\$
2
  • \$\begingroup\$ actually, your screen refresh rate does not limit the FPS the software can generate, it just won't show every frame update \$\endgroup\$
    – xorinzor
    Mar 31, 2014 at 0:07
  • \$\begingroup\$ A frame not displayed is not a frame, rather it is a framebuffer. Even if graphic card seller wants us to think otherwise, in the end only the frames that can be seen really matters. Anyway, in javascript, with a complex scene, 50 or 60Hz is already a not so easy target... \$\endgroup\$ Mar 31, 2014 at 10:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .