I'm using the following to record the frame rate of an application:
let _lastCalledTime;
let _fps;
let _frame = 0;
let _csv = 'Frame,Timestamp,FPS';
const _refreshLoop = () =>
window.requestAnimationFrame((timestamp) => {
if (!_lastCalledTime) {
_lastCalledTime = timestamp;
_fps = 0;
} else {
const delta = (timestamp - _lastCalledTime) / 1000;
_lastCalledTime = timestamp;
_fps = 1 / delta;
}
_csv += `\n${_frame++},${timestamp},${_fps}`;
_refreshLoop();
});
_refreshLoop();
Which is a variation of some code I found here: https://www.growingwiththeweb.com/2017/12/fast-simple-js-fps-counter.html
Every time a frame is rendered, the elapsed time since the last frame is calculated using the timestamp parameter passed to the callback. This is used to calculate the FPS and the values logged as a CSV.
I have a MacBook and a Raspberry Pi 3, both running at FPS, and I want to calculate the performance of the application. The MacBook reports a very precise value and, once stable, reports a value very close to 60 fps:
| Frame | Timestamp (ms) | FPS |
|---|---|---|
| 0 | 188.835 | 0 |
| 1 | 238.833 | 20.000800032001283 |
| 2 | 255.499 | 60.00240009600385 |
| 3 | 272.165 | 60.002400096003754 |
| 4 | 338.829 | 15.000600024000963 |
| 5 | 405.493 | 15.000600024000963 |
| 6 | 422.159 | 60.00240009600385 |
| 7 | 438.825 | 60.00240009600385 |
| 8 | 455.765 | 59.03187721369541 |
| 9 | 472.431 | 60.00240009600385 |
| 10 | 489.097 | 60.00240009600385 |
| 11 | 505.763 | 60.00240009600385 |
| 12 | 522.429 | 60.00240009600385 |
| 13 | 539.095 | 60.002400096003655 |
| 14 | 555.761 | 60.00240009600405 |
The Raspberry Pi has a less-precise reading for timestamp (1 ms) leading to a stable frame rate of 62.5/58.8 fps:
| Frame | Timestamp (ms) | FPS |
|---|---|---|
| 0 | 1303 | 0 |
| 1 | 1394 | 10.989010989010989 |
| 2 | 1411 | 58.8235294117647 |
| 3 | 1428 | 58.8235294117647 |
| 4 | 1444 | 62.5 |
| 5 | 1461 | 58.8235294117647 |
| 6 | 1477 | 62.5 |
| 7 | 1689 | 4.716981132075472 |
| 8 | 2321 | 1.5822784810126582 |
| 9 | 2443 | 8.19672131147541 |
| 10 | 2455 | 83.33333333333333 |
| 11 | 2487 | 31.25 |
| 12 | 2505 | 55.55555555555556 |
| 13 | 2521 | 62.5 |
| 14 | 2537 | 62.5 |
The bit that is confusing me is that the Raspberry Pi sometimes reports intervals of less than 16 ms, suggesting frame rates of much more than 60 fps, e.g.:
| Frame | Timestamp (ms) | FPS |
|---|---|---|
| 106 | 4378 | 40.00 |
| 107 | 4380 | 500.00 |
| 108 | 4397 | 58.82 |
| 109 | 4412 | 66.67 |
| 110 | 4428 | 62.50 |
| 111 | 4450 | 45.45 |
| 112 | 4462 | 83.33 |
| 113 | 4478 | 62.50 |
So my question is: how can this be? My initial thought was that multiple callbacks might be being called for the same frame, but in that case they would receive the same value for timestamp (per the spec). My two other suspicions are that, either timestamp is very inaccurate, or requestAnimationFrame() is not actually locked to the display's refresh rate and is sometimes executing faster.