morpheus
morpheus

Reputation: 20350

Is it possible to find out what is the monitor frame rate in javascript?

  1. First, Is it possible to find out what is the monitor frame/refresh rate in javascript (60Hz for most LCD monitors)?
  2. Second, is there any way to say execute a function after every X frames?

Several people asked why I need this. Here is the context: I have an animation (an endless loop that renders one frame after another). The output of each iteration needs to be synchronized with monitor refresh rate, otherwise tearing will happen. The way I am doing it right now is to use setTimeout(loop, 16) within the loop method. Its sort of working. The second parameter needs to be 1/(refresh rate), and that is why I asked this question.

Upvotes: 23

Views: 20061

Answers (6)

D.R.Bendanillo
D.R.Bendanillo

Reputation: 307

JavaScript don't exposed the screen refresh rate. But we can make workarounds. Here is my take on that.

const checkFrameTime = new Promise((resolve, reject) => {
        const checks = {
            frameRateChecks: 0,
            screenFrameTime: 0,
            prevTime: 0,
        };

        const frameTimeTable = (frameTime: number) => {
          // Feel free to modify
          // Cinematic refresh rates are excluded here
          if (frameTime > 15.5) return 16.66666666666667; // 60Hz
          if (frameTime > 12.5) return 13.33333333333333; // 75Hz
          if (frameTime > 10.5) return 11.111111111111111; // 90Hz
          if (frameTime > 9.5) return 10; // 100Hz
          if (frameTime > 8) return 8.33333333333333; // 120Hz
          if (frameTime > 6.5) return 6.944444444444444; // 144Hz
          if (frameTime > 5.5) return 6.0606060606060606; // 165Hz
          if (frameTime > 3.5) return 4.166666666666667; // 240Hz
          if (frameTime > 2.5) return 2.777777777777778; // 360Hz

          return 2; // 500Hz because wy not
        };

        const check = (now: number) => {
          const dt = now - checks.prevTime;
          checks.prevTime = now;

          if (dt > 100) {
            requestAnimationFrame(check);
            return;
          }

          if (checks.frameRateChecks < 20) { // Higher = more accurate but 20 is good.
            requestAnimationFrame(check);
            checks.frameRateChecks++;
            checks.screenFrameTime = checks.screenFrameTime === 0 ? dt : (checks.screenFrameTime + dt) / 2;
          } else {
            checks.screenFrameTime = frameTimeTable(checks.screenFrameTime);
            resolve(checks.screenFrameTime);
          }
        };
        requestAnimationFrame(check);
    });

const getScreenFrameTime: (callbackResult: (result: number) => void) => void = async (callbackResult: (result: number) => void) => {
    const value = await checkFrameTime;
    callbackResult(value);
};

getScreenFrameTime((result) => {
  console.log('Screen FPS:' + (1000/result).toFixed(0));
});

Upvotes: 0

Adam Sassano
Adam Sassano

Reputation: 273

As per some of the previous answer that explain to use requestAnimationFrame and measure the time difference between frames, here is a solution that uses requestAnimationFrame and the timestamp already sent to the callback. There is otherwise no reason to run a separate performance.now() function.

var previousTimestamp, divInterval, divFPS;

const raf = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

const rafLoop = timestamp => {
    let interval = timestamp - previousTimestamp;
    let fps = 1000 / interval;
    divInterval.innerHTML = `Interval: ${interval}`;
    divFPS.innerHTML = `FPS: ${fps}`;
    previousTimestamp = timestamp;
    raf(rafLoop);
};

divInterval = document.getElementById('interval');
divFPS = document.getElementById('fps');

// This is run first to set the previousTimestamp variable with an initial value, and then call the rafLoop function.
raf(timestamp => {
    previousTimestamp = timestamp;
    raf(rafLoop);
});
<div id="interval"></div>
<div id="fps"></div>

See also https://codepen.io/sassano/pen/wvgxxMp for another sample with animation from which this snippet was derived.

Upvotes: 1

Yuchen Huang
Yuchen Huang

Reputation: 311

Calculate time interval between repaint:

const getRepaintInterval = () => {
  return new Promise((resolve) => {
    requestAnimationFrame((t1) => {
      requestAnimationFrame((t2) => {
        resolve(t2 - t1);
      });
    });
  });
};

Or calculate FPS in the selected second:

const getFps = () => new Promise(resolve => {
    let repaint = 0;
    const start = performance.now();
    const withRepaint = () => {
        requestAnimationFrame(() => {
            if ((performance.now() - start) < 1000) {
                repaint += 1;
                withRepaint();
            } else {
                resolve(repaint);
            }
        });
    };
    withRepaint();
});

Or calculate FPS with start and end:

const fpsHandler = () => {
    let repaint;
    let stop;
    let ret;
    let start;
    const init = () => {
        ret = undefined;
        stop = false;
        repaint = 0;
        start = performance.now();
    };
    init();
    const withRepaint = () => {
        requestAnimationFrame(() => {
            if (!stop) {
                repaint += 1;
                withRepaint();
            }
        });
    };
    return {
        start: () => {
            init();
            withRepaint();
        },
        end: () => {
            stop = true;
            if (!ret) ret = repaint / ((performance.now() - start) / 1000);
            return ret;
        }
    }
};

const { start, end } = fpsHandler();

Upvotes: 4

Eejdoowad
Eejdoowad

Reputation: 1381

The solution below works by measuring the number of milliseconds between two consecutive animation frames.

Warning: It often returns an incorrect FPS because sometimes an animation frame is skipped when your CPU is busy with other tasks.

// Function that returns a Promise for the FPS
const getFPS = () =>
  new Promise(resolve =>
    requestAnimationFrame(t1 =>
      requestAnimationFrame(t2 => resolve(1000 / (t2 - t1)))
    )
  )

// Calling the function to get the FPS
getFPS().then(fps => console.log(fps));

Tips

Upvotes: 13

Tobiq
Tobiq

Reputation: 2657

This is robust method, using the requestAnimationFrame method.

function calcFPS(opts){
    var requestFrame = window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame;
    if (!requestFrame) return true; // Check if "true" is returned; 
                                    // pick default FPS, show error, etc...
    function checker(){
        if (index--) requestFrame(checker);
        else {
            // var result = 3*Math.round(count*1000/3/(performance.now()-start));
            var result = count*1000/(performance.now()- start);
            if (typeof opts.callback === "function") opts.callback(result);
            console.log("Calculated: "+result+" frames per second");
        }
    }
    if (!opts) opts = {};
    var count = opts.count||60, index = count, start = performance.now();
    checker();
}

The higher the value of count, the more accurate the value of the FPS, and the longer the FPS test will take.

Additional logic can be used to round to 15/12s, ie 24, 30, 48, 60 120... FPS.


Here's the compiled version (with rounding to 3 FPS):

function calcFPS(a){function b(){if(f--)c(b);else{var e=3*Math.round(1E3*d/3/(performance.now()-g));"function"===typeof a.callback&&a.callback(e);console.log("Calculated: "+e+" frames per second")}}var c=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame;if(!c)return!0;a||(a={});var d=a.count||60,f=d,g=performance.now();b()}

Used like so:

calcFPS(); // Only logs to console (you can remove the console log,
           // making this call redundant)

calcFPS({count: 30}); // Manually set count (the test should take 500ms
                      // on a 60FPS monitor

calcFPS({callback: useFPS}); // Specify a callback so you can use the
                             // FPS number value

var FPS = 0, err = calcFPS({count: 120, callback: fps => FPS = fps});
if (err) FPS = 30; 

Upvotes: 1

Alnitak
Alnitak

Reputation: 339856

You may have some luck on modern browsers using window.requestAnimationFrame with a trivial callback that measures the time between successive invocations and from that calculate the FPS.

You should also be able to easily skip your render function every nth invocation to reduce the desired frame rate.

I put a rough example at http://jsfiddle.net/rBGPk/ - the math may be slightly wrong but it should be enough to show the general idea.

Upvotes: 26

Related Questions