I am trying to compare performance for 3d applications on mobile devices. I have a 3d solar system set up in webGL and im trying to record or at least display the FPS. So
I created an object oriented version of Barış Uşaklı's answer. It also tracks the average fps over the last minute.
Usage:
global Variable:
var fpsCounter;
Create the object somewhere when starting your program:
fpsCounter = new FpsCounter();
Call the update method in your draw() funktion & update the fps-displays:
function drawScene() {
fpsCounter.update();
document.getElementById('fpsDisplay').innerHTML = fpsCounter.getCountPerSecond();
document.getElementById('fpsMinuteDisplay').innerHTML = fpsCounter.getCountPerMinute();
// Code
}
Note: I only put the fps-display updates in the draw function for simplicity. With 60fps it gets set 60 times per second, even though once a second is enough.
FpsCounter Code:
function FpsCounter(){
this.count = 0;
this.fps = 0;
this.prevSecond;
this.minuteBuffer = new OverrideRingBuffer(60);
}
FpsCounter.prototype.update = function(){
if (!this.prevSecond) {
this.prevSecond = new Date().getTime();
this.count = 1;
}
else {
var currentTime = new Date().getTime();
var difference = currentTime - this.prevSecond;
if (difference > 1000) {
this.prevSecond = currentTime;
this.fps = this.count;
this.minuteBuffer.push(this.count);
this.count = 0;
}
else{
this.count++;
}
}
};
FpsCounter.prototype.getCountPerMinute = function(){
return this.minuteBuffer.getAverage();
};
FpsCounter.prototype.getCountPerSecond = function(){
return this.fps;
};
OverrideBuffer Code:
function OverrideRingBuffer(size){
this.size = size;
this.head = 0;
this.buffer = new Array();
};
OverrideRingBuffer.prototype.push = function(value){
if(this.head >= this.size) this.head -= this.size;
this.buffer[this.head] = value;
this.head++;
};
OverrideRingBuffer.prototype.getAverage = function(){
if(this.buffer.length === 0) return 0;
var sum = 0;
for(var i = 0; i < this.buffer.length; i++){
sum += this.buffer[i];
}
return (sum / this.buffer.length).toFixed(1);
};
I assume you are calling drawScene repeatedly but if you are setting x
only once then it will not update every time drawScene is called. Also what you are storing in Time
is elapsed time and not frames per second.
How about something like the below? The idea is to count the number of frames rendered and once one second has passed store that in the fps variable.
<script>
var elapsedTime = 0;
var frameCount = 0;
var lastTime = 0;
function drawScene() {
// draw scene here
var now = new Date().getTime();
frameCount++;
elapsedTime += (now - lastTime);
lastTime = now;
if(elapsedTime >= 1000) {
fps = frameCount;
frameCount = 0;
elapsedTime -= 1000;
document.getElementById('test').innerHTML = fps;
}
}
lastTime = new Date().getTime();
setInterval(drawScene,33);
</script>
<div id="test">
</div>
Displaying FPSs is pretty simple and has really nothing to do with WebGL other than it's common to want to know. Here's a small FPS display
const fpsElem = document.querySelector("#fps");
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then; // compute time since last frame
then = now; // remember time for next frame
const fps = 1 / deltaTime; // compute frames per second
fpsElem.textContent = fps.toFixed(1); // update fps display
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<div>fps: <span id="fps"></span></div>
Use requestAnimationFrame for animation because that's what it's for. Browsers can sync to the screen refresh to give you buttery smooth animation. They can also stop processing if your page is not visible. setTimeout on the other hand is not designed for animation, will not be synchronised to the browser's page drawing.
You should probably not use Date.now()
for computing FPS as Date.now()
only returns milliseconds. Also using (new Date()).getTime()
is especially bad since it's generating a new Date
object every frame.
requestAnimationFrame
already gets passed the time in microseconds since the page loaded so just use that.
It's also common to average the FPS across frames.
const fpsElem = document.querySelector("#fps");
const avgElem = document.querySelector("#avg");
const frameTimes = [];
let frameCursor = 0;
let numFrames = 0;
const maxFrames = 20;
let totalFPS = 0;
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then; // compute time since last frame
then = now; // remember time for next frame
const fps = 1 / deltaTime; // compute frames per second
fpsElem.textContent = fps.toFixed(1); // update fps display
// add the current fps and remove the oldest fps
totalFPS += fps - (frameTimes[frameCursor] || 0);
// record the newest fps
frameTimes[frameCursor++] = fps;
// needed so the first N frames, before we have maxFrames, is correct.
numFrames = Math.max(numFrames, frameCursor);
// wrap the cursor
frameCursor %= maxFrames;
const averageFPS = totalFPS / numFrames;
avgElem.textContent = averageFPS.toFixed(1); // update avg display
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { font-family: monospace; }
<div> fps: <span id="fps"></span></div>
<div>average fps: <span id="avg"></span></div>
Using a rotating array can do better. with dom element:
<div id="fps">
the following script do the trick:
var fpsLastTick = new Date().getTime();
var fpsTri = [15, 15, 15]; // aims for 60fps
function animate() {
// your rendering logic blahh blahh.....
// update fps at last
var now = new Date().getTime();
var frameTime = (now - fpsLastTick);
fpsTri.shift(); // drop one
fpsTri.push(frameTime); // append one
fpsLastTick = now;
fps = Math.floor(3000 / (fpsTri[0] + fpsTri[1] + fpsTri[2])); // mean of 3
var fpsElement = document.getElementById('fps')
if (fpsElement) {
fpsElement.innerHTML = fps;
}
}
Since none of the other answers addressed the "in WebGL" part of the question, I'll add the following important details when measuring FPS in WebGL correctly.
window.console.time('custom-timer-id'); // start timer
/* webgl draw call here */ // e.g., gl.drawElements();
gl.finish(); // ensure the GPU is ready
window.console.timeEnd('custom-timer-id'); // end timer
For simplicity I used the console timer. I'm trying to make the point to always use WebGLRenderingContext.finish() to ensure the correct time is measured as all WebGL calls to the GPU are asynchronous!