[英]Calculate FPS in Canvas using requestAnimationFrame
我如何计算 canvas 游戏应用程序的 FPS? 我看过一些例子,但没有一个使用 requestAnimationFrame,我不确定如何在那里应用他们的解决方案。 这是我的代码:
(function(window, document, undefined){ var canvas = document.getElementById("mycanvas"), context = canvas.getContext("2d"), width = canvas.width, height = canvas.height, fps = 0, game_running = true, show_fps = true; function showFPS(){ context.fillStyle = "Black"; context.font = "normal 16pt Arial"; context.fillText(fps + " fps", 10, 26); } function gameLoop(){ //Clear screen context.clearRect(0, 0, width, height); if (show_fps) showFPS(); if (game_running) requestAnimationFrame(gameLoop); } gameLoop(); }(this, this.document))
canvas{ border: 3px solid #fd3300; }
<canvas id="mycanvas" width="300" height="150"></canvas>
顺便说一句,有没有我可以添加的库来监督性能?
new Date()
这个 API 有几个缺陷,仅用于获取当前日期 + 时间。 不用于测量时间跨度。
Date-API 使用操作系统的内部时钟,该时钟不断更新并与 NTP 时间服务器同步。 这意味着,该时钟的速度/频率有时比实际时间快,有时慢 - 因此不能用于测量持续时间和帧率。
如果有人更改了系统时间(手动或由于 DST),如果单个帧突然需要一个小时,您至少可以看到问题。 或者负时间。 但是,如果系统时钟与世界时间同步的速度快 20%,则几乎无法检测到。
此外,Date-API 非常不精确 - 通常远小于 1 毫秒。 这使得它对于帧率测量尤其无用,其中一帧 60Hz 帧需要约 17 毫秒。
performance.now()
Performance API 专为此类用例而设计,可等效于new Date()
。 只需采用其他答案之一并将new Date()
替换为performance.now()
,您就可以开始了。
资料来源:
与 Date.now() 不同的是,Performance.now() 返回的值始终以恒定速率增加,与系统时钟无关(可能手动调整或由 NTP 等软件倾斜)。 否则,performance.timing.navigationStart + performance.now() 将大约等于 Date.now()。
https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
对于窗户:
[时间服务] 调整本地时钟频率,使其向正确的时间收敛。 如果本地时钟与【准确时间样本】的时差过大无法通过调整本地时钟速率进行校正,时间服务会将本地时钟设置为正确的时间。
https://technet.microsoft.com/en-us/library/cc773013(v=ws.10).aspx
Chrome有一个内置的 fps 计数器: https : //developer.chrome.com/devtools/docs/rendering-settings
只需打开开发控制台( F12 ),打开抽屉( Esc ),然后添加“渲染”选项卡。
在这里,您可以激活 FPS-Meter 叠加层以查看当前帧速率(包括漂亮的图表)以及 GPU 内存消耗。
跨浏览器解决方案:您可以使用 JavaScript 库 stat.js 获得类似的覆盖: https : //github.com/mrdoob/stats.js/
它还为帧率(包括图形)提供了很好的叠加,并且非常易于使用。
比较 stats.js 和 chrome 开发工具的结果时,两者都显示完全相同的测量结果。 因此,您可以相信该库实际上会做正确的事情。
您可以跟踪上次调用 requestAnimFrame 的时间。
var lastCalledTime;
var fps;
function requestAnimFrame() {
if(!lastCalledTime) {
lastCalledTime = Date.now();
fps = 0;
return;
}
delta = (Date.now() - lastCalledTime)/1000;
lastCalledTime = Date.now();
fps = 1/delta;
}
这是另一个解决方案:
var times = [];
var fps;
function refreshLoop() {
window.requestAnimationFrame(function() {
const now = performance.now();
while (times.length > 0 && times[0] <= now - 1000) {
times.shift();
}
times.push(now);
fps = times.length;
refreshLoop();
});
}
refreshLoop();
这通过以下方式改进了其他一些:
performance.now()
用于Date.now()
以提高精度(如本答案所述)我在我的网站上更详细地描述了这个解决方案。
我有一个不同的方法,因为如果你计算 FPS,你会在返回数字时看到这个闪烁。 我决定计算每一帧并每秒返回一次
window.countFPS = (function () {
var lastLoop = (new Date()).getMilliseconds();
var count = 1;
var fps = 0;
return function () {
var currentLoop = (new Date()).getMilliseconds();
if (lastLoop > currentLoop) {
fps = count;
count = 1;
} else {
count += 1;
}
lastLoop = currentLoop;
return fps;
};
}());
requestAnimationFrame(function () {
console.log(countFPS());
});
我缺少一个允许为平均 FPS 值自定义样本大小的实现。 这是我的,它具有以下功能:
const fps = { sampleSize : 60, value : 0, _sample_ : [], _index_ : 0, _lastTick_: false, tick : function(){ // if is first tick, just set tick timestamp and return if( !this._lastTick_ ){ this._lastTick_ = performance.now(); return 0; } // calculate necessary values to obtain current tick FPS let now = performance.now(); let delta = (now - this._lastTick_)/1000; let fps = 1/delta; // add to fps samples, current tick fps value this._sample_[ this._index_ ] = Math.round(fps); // iterate samples to obtain the average let average = 0; for(i=0; i<this._sample_.length; i++) average += this._sample_[ i ]; average = Math.round( average / this._sample_.length); // set new FPS this.value = average; // store current timestamp this._lastTick_ = now; // increase sample index counter, and reset it // to 0 if exceded maximum sampleSize limit this._index_++; if( this._index_ === this.sampleSize) this._index_ = 0; return this.value; } } // ******************* // test time... // ******************* function loop(){ let fpsValue = fps.tick(); window.fps.innerHTML = fpsValue; requestAnimationFrame( loop ); } // set FPS calulation based in the last 120 loop cicles fps.sampleSize = 120; // start loop loop()
<div id="fps">--</div>
实际上,没有一个答案对我来说是足够的。 这是一个更好的解决方案:
代码:
// Options
const outputEl = document.getElementById('fps-output');
const decimalPlaces = 2;
const updateEachSecond = 1;
// Cache values
const decimalPlacesRatio = Math.pow(10, decimalPlaces);
let timeMeasurements = [];
// Final output
let fps = 0;
const tick = function() {
timeMeasurements.push(performance.now());
const msPassed = timeMeasurements[timeMeasurements.length - 1] - timeMeasurements[0];
if (msPassed >= updateEachSecond * 1000) {
fps = Math.round(timeMeasurements.length / msPassed * 1000 * decimalPlacesRatio) / decimalPlacesRatio;
timeMeasurements = [];
}
outputEl.innerText = fps;
requestAnimationFrame(() => {
tick();
});
}
tick();
只是一个概念证明。 非常简单的代码。 我们所做的就是设置每秒的帧数和每帧之间的间隔。 在绘图函数中,我们从当前时间中减去上一帧的执行时间,以检查自上一帧以来经过的时间是否大于我们的间隔(基于 fps)。 如果条件评估为真,我们为当前帧设置时间,这将是下一次绘图调用中的“最后一帧执行时间”。
var GameLoop = function(fn, fps){
var now;
var delta;
var interval;
var then = new Date().getTime();
var frames;
var oldtime = 0;
return (function loop(time){
requestAnimationFrame(loop);
interval = 1000 / (this.fps || fps || 60);
now = new Date().getTime();
delta = now - then;
if (delta > interval) {
// update time stuffs
then = now - (delta % interval);
// calculate the frames per second
frames = 1000 / (time - oldtime)
oldtime = time;
// call the fn
// and pass current fps to it
fn(frames);
}
}(0));
};
用法:
var set;
document.onclick = function(){
set = true;
};
GameLoop(function(fps){
if(set) this.fps = 30;
console.log(fps);
}, 5);
只需检查 AFR 回调之间的时间差即可。 AFR 已经将时间作为回调的参数传递。 我更新了你的小提琴来展示它: http : //jsfiddle.net/WCKhH/1/
我的 fps 计算使用requestAnimationFrame()
及其回调函数的匹配时间戳参数。
请参阅https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame和https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp 。
不需要new Date()
或performance.now()
!
其余部分的灵感来自该线程中的其他答案,尤其是https://stackoverflow.com/a/48036361/4706651 。
var fps = 1; var times = []; var fpsLoop = function (timestamp) { while (times.length > 0 && times[0] <= timestamp - 1000) { times.shift(); } times.push(timestamp); fps = times.length; console.log(fps); requestAnimationFrame(fpsLoop); } requestAnimationFrame(fpsLoop);
我与performance.now()
使用的最佳方式
简单我在gameLoop
函数上传递 TIME 并计算 fps
fps = 1 / ( (performance.now() - LAST_FRAME_TIME) / 1000 );
(function(window, document, undefined){ var canvas = document.getElementById("mycanvas"), context = canvas.getContext("2d"), width = canvas.width, height = canvas.height, fps = 0, game_running = true, show_fps = true, LAST_FRAME_TIME = 0; function showFPS(){ context.fillStyle = "Black"; context.font = "normal 16pt Arial"; context.fillText(fps + " fps", 10, 26); } function gameLoop(TIME){ //Clear screen context.clearRect(0, 0, width, height); if (show_fps) showFPS(); fps = 1 / ((performance.now() - LAST_FRAME_TIME) / 1000); LAST_FRAME_TIME = TIME /* remember the time of the rendered frame */ if (game_running) requestAnimationFrame(gameLoop); } gameLoop(); }(this, this.document))
canvas{ border: 3px solid #fd3300; }
<canvas id="mycanvas" width="300" height="150"></canvas>
我必须创建一个 function 来设置 animation 应该在哪个 fps 上运行,因为我有一个 240hz 的显示器,我的屏幕上的动画比其他屏幕上的动画快得多,所以我的最终项目在其他显示器上总是比较慢
function setFPSandRunAnimation(fps, cb) {
let frameCount = 0;
let fpsInterval, startTime, now, then, elapsed;
runAnimating(fps);
function runAnimating(fps) {
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
animate();
}
function animate(timestamp) {
requestAnimationFrame(animate);
now = Date.now();
elapsed = now - then;
if (elapsed > fpsInterval) {
then = now - (elapsed % fpsInterval);
const sinceStart = now - startTime;
const currentFps = Math.round(1000 / (sinceStart / ++frameCount) * 100) / 100;
const elapsedTime = Math.round(sinceStart / 1000 * 100) / 100;
cb(timestamp, currentFps, elapsedTime)
}
}
}
这是你如何使用它setFPSandRunAnimation(fpsSpeedYouWant, cbFunctionWhereYouGet timestamp, currentfps and elapsedTime)
。
在 cb function 内部,您可以运行任何您将在 animation function 中运行的代码
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.