Recording FPS in webGL

后端 未结 5 550
甜味超标
甜味超标 2020-12-18 12:54

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

相关标签:
5条回答
  • 2020-12-18 13:22

    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);
    };
    
    0 讨论(0)
  • 2020-12-18 13:23

    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>
    
    0 讨论(0)
  • 2020-12-18 13:29

    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>

    0 讨论(0)
  • 2020-12-18 13:34

    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;
      }
    }

    0 讨论(0)
  • 2020-12-18 13:48

    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!

    0 讨论(0)
提交回复
热议问题