14

I know a few questions have been asked like this one before, such as this: Check FPS in JS? - which did work to some degree, I was able to find out how long each loop took to complete.

What I am looking for though is something more readable and controllable. I want to be able to set the refresh rate for the FPS counter to make it slow so it is human readable or as fast as the application can run, so I can use it on some kind of speedometer.

Anyway so here is the code I have right now:

var lastLoop = new Date().getTime();

function updateStage()
{   
    clearCanvas();
    updateStageObjects();
    drawStageObjects();     

    var thisLoop = new Date().getTime(); 
    var fps = (thisLoop - lastLoop);

    $('#details').html(fps);

    lastLoop = thisLoop;
    iteration = setTimeout(updateStage, 1);
}
  1. Am I right to be setting the setTimeout function to a speed of 1 millisecond? I was thinking this will just make it loop as fast as it possibly can.

  2. Should I count every 100 frames or so, find out how many milliseconds it took to run 100 frames then make a calculation to find out how many frames it would have done if the milliseconds were 1000? What would this calculation be?

  3. To make the result more accurate I am guessing I need to display averages as one frame can vary a significant amount, how should I do this?

Any tips are greatly appreciated.

Thanks.

Community
  • 1
  • 1
Sabai
  • 1,579
  • 5
  • 24
  • 40

5 Answers5

23
  1. Note that the faster you update your output, the more you will affect your measurement. Although minimal, I try to update my fps output once per second or less unless it's necessary to go faster.

  2. I like to have a low-pass filter on my results so that a temporary hiccup doesn't affect the values too strongly. This is easier to compute and write than a moving average, and doesn't have the problem of an overall average where your 'current' readings are affected by total performance over the entire run (e.g. anomalous readings during startup).

Put together, here's how I usually measure FPS:

var fps = 0, now, lastUpdate = (new Date)*1;

// The higher this value, the less the FPS will be affected by quick changes
// Setting this to 1 will show you the FPS of the last sampled frame only
var fpsFilter = 50;

function drawFrame(){
  // ... draw the frame ...

  var thisFrameFPS = 1000 / ((now=new Date) - lastUpdate);
  if (now!=lastUpdate){
    fps += (thisFrameFPS - fps) / fpsFilter;
    lastUpdate = now;
  }

  setTimeout( drawFrame, 1 );
}

var fpsOut = document.getElementById('fps');
setInterval(function(){
  fpsOut.innerHTML = fps.toFixed(1) + "fps";
}, 1000); 
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • Hey, thanks for your answer, that all seems to make good sense. For some reason though, my output is NaN, I've pasted my adapted code here just so you can see: http://jsfiddle.net/ksgSg/. – Sabai Feb 22 '11 at 14:22
  • @Henryz How embarrassing, that will occur if now == lastUpdate on the first pass. I've edited it with a guard for this. (I normally have a setTimeout on my first drawFrame call, I guess.) – Phrogz Feb 22 '11 at 18:07
  • This seems to work better, but it still sometimes equates to NaN if left running for 5 - 10 seconds. I'm not sure why, I haven't quite been able to figure it out. – Sabai Feb 23 '11 at 10:02
  • The NaN issue doesn't happen if the fpsFilter is set to a low number, like 10. So I have it working to that degree. Please if you have time could you give me a quick explanation how this works : (fps += (thisFrameFPS - fps) / fpsFilter;). I completely understand the rest, I just can't figure out why that produces accurate results. Thanks! – Sabai Feb 23 '11 at 10:39
  • I cannot think of any reason that code should ever give you `NaN`, and there are even less reasons than zero that the factor you choose could affect that. Can you paste your actual code? Anyhow, what that does is slowly adjust `fps` by the difference between it and the current frame rate. If your factor is 1, then the formula is `fps = fps + thisFrameFPS - fps` which would just set it to `thisFrameFPS`. If your factor is 2 and the frame rate jumps from, say, a stable rate of 10 to a stable rate of 20, then first it will add 5 to the fps, then 2.5, then 1.25, and so on. – Phrogz Feb 24 '11 at 08:53
  • @Henryz In case it helps, here is a [real-world example using the above technique](http://phrogz.net/tmp/image_move_sprites_canvas.html). Although it happens to have a filter value of `10`, it works just as well for me with a factor of `1000`. – Phrogz Feb 24 '11 at 22:36
  • Hi thanks for getting back to me, yes I understand how the filter works now, thanks for that. I have FTP'd my little script so you can see it working (hit refresh a few times if you just get a blank page at first). When you view source the stuff is located in script.js. As I said it does work well, I just have this NaN problem which is more frequent when the fpsFilter is higher. Thanks again! - http://www.henry.brown.name/experiments/fps – Sabai Feb 25 '11 at 15:51
  • @Henryz Thanks for sharing the code. I never see it reach `NaN`, even with your very-high filter setting of 1000. (See [my answer here](http://www.henry.brown.name/experiments/fps/) for more details on the effects of the filter strength; at 1000 it will take 700 frames to move from an initial fps of 0 to _half_ of the steady-state fps.) What OS/browser/version are you seeing NaN on? – Phrogz Feb 25 '11 at 16:01
  • Hi sorry I haven't got back to you sooner. I am using OS X, Firefox 3.6. I will go ahead and test further on other browsers and let you know my results. – Sabai Mar 02 '11 at 15:21
  • The correct link is [my answer here](http://stackoverflow.com/questions/4787431/check-fps-in-js/5111475#5111475) for my above comment; had bad data in the clipboard, it seems. – Phrogz Mar 02 '11 at 15:45
  • FYI - If @Henryz has : lastUpdate = (new Date); (Notice the missing *1) he will see the NaN. I saw that issue in my code. – raddevus Nov 13 '14 at 21:46
3

Ive tried something out,

If you change the

lastUpdate = now

to

lastUpdate = now * 1 - 1;

Your NaN problem is solved! This is also used where the lastUpdate is defined. Probably because it is not able to convert the date to unix timestamp.

The new result will be:

var fps = 0, now, lastUpdate = (new Date)*1 - 1;

// The higher this value, the less the FPS will be affected by quick changes
// Setting this to 1 will show you the FPS of the last sampled frame only
var fpsFilter = 50;

function drawFrame(){
  // ... draw the frame ...

  var thisFrameFPS = 1000 / ((now=new Date) - lastUpdate);
  fps += (thisFrameFPS - fps) / fpsFilter;
  lastUpdate = now * 1 - 1;

  setTimeout( drawFrame, 1 );
}

var fpsOut = document.getElementById('fps');
setInterval(function(){
  fpsOut.innerHTML = fps.toFixed(1) + "fps";
}, 1000); 
Niels
  • 48,601
  • 4
  • 62
  • 81
2

I've taken the solution(s) posted and enhanced them a little. Have a look here - http://jsfiddle.net/ync3S/

  1. I fixed that NaN error by using Date.now() instead of constructing a new date object each time and trying to reference it. This also prevents some garbage collection necessity.
  2. I neatened up the variable and function names a bit and added some extra commenting - not necessary but nice to have.
  3. I included some drawing code for testing.
  4. I added fpsDesired as a test var for the engine loop.
  5. I started fpsAverage at fpsDesired so with the fpsFilter it doesn't work up from 0 to the real FPS, rather starting at the desired FPS and adjusting from there.
  6. Drawing now blocks incase it already was drawing, and this can be used for pausing and other control functions.

The main block is as follows:

var fpsFilter = 1; // the low pass filter to apply to the FPS average
var fpsDesired = 25; // your desired FPS, also works as a max
var fpsAverage = fpsDesired;
var timeCurrent, timeLast = Date.now();
var drawing = false;

function fpsUpdate() {
    fpsOutput.innerHTML = fpsAverage.toFixed(2);
}

function frameDraw() {
    if(drawing) { return; } else { drawing = true; }

    timeCurrent = Date.now();
    var fpsThisFrame = 1000 / (timeCurrent - timeLast);
    if(timeCurrent > timeLast) {
        fpsAverage += (fpsThisFrame - fpsAverage) / fpsFilter;
        timeLast = timeCurrent;
    }

    drawing = false;
}

setInterval(fpsUpdate, 1000);
fpsUpdate();

setInterval(frameDraw, 1000 / fpsDesired);
frameDraw();

Going to have a tinker and see if I can come up with something smoother, as this thread is near the top in Google results.

Let's see what we can all come up with as a team, and I think it's always neat to not use 3rd party libraries, making the code portable for anyone :)

-Platima

1

Just set a interval that is resetting the fps counter every second.

var fpsOut, fpsCount;

var draw = function () {

    fpsCount++;

    ..Draw To Canvas..


    ..Get the fps value: fpsOut

    requestAnimationFrame(draw);

};
setInterval(function () {

    fpsOut = fpsCount;
    fpsCount = 0;

}, 1000);

draw();
Gustav G
  • 459
  • 3
  • 10
0

If you want real-time updates, consider making it loop again and again in real time. To make it affect the performance less, only update the controlled variable, in this case, the FPS. You can have optional Frame Latency, which I will put here, just in case. Just copy, paste and tweak the code to your needs.

Take note that a single frame lasts for 16.66 miliseconds.

setInterval(function(){var latencybase1 = parseFloat(new Date().getTime());
var latencybase2 = parseFloat(new Date().getTime());
var latency = latencybase2-latencybase1;
var fps = Math.round(1000/latency);

if (latency<16.66) 
{document.getElementById("FPS").innerHTML = fps+" 
FPS";}
else {document.getElementById("FPS").innerHTML = ""+fps+" FPS";}
document.getElementById("Latency").innerHTML = latency+" ms";}, 0);
Dev60
  • 9
  • 1
  • I may be misreading, but it seems this code only measures the time it took to call the second `new Date().getTime())`, nothing more. Also beware, 60Hz is not the only refresh rate out there, setInterval has a minimum 4ms delay after the 5th iteration, and setting innerHTML more than once per rendering frame just kills trees for nothing. – Kaiido Oct 11 '20 at 01:28