我如何计算画布游戏应用程序的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>
顺便问一下,我可以添加任何库来监督性能吗?
答案 0 :(得分:26)
您可以跟踪上次调用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;
}
答案 1 :(得分:23)
new Date()
此API有几个缺陷,仅对获取当前日期+时间有用。不适用于测量时间跨度。
Date-API使用操作系统的内部时钟,该内部时钟不断更新并与NTP时间服务器同步。这意味着,此时钟的速度/频率有时更快,有时比实际时间更慢 - 因此无法用于测量持续时间和帧速率。
如果有人更改系统时间(手动或由于夏令时),您至少可以看到一个框架突然需要一个小时的问题。或者是负面时间。但如果系统时钟与世界时间同步快20%,则几乎不可能检测到。
此外,Date-API非常不精确 - 通常远不到1毫秒。这使得它对于帧速率测量尤其无用,其中一个60Hz帧需要~17ms。
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
对于Windows:
[时间服务]调整本地时钟速率以允许它 收敛到正确的时间。 如果本地时钟和[准确时间样本]之间的时间差太大而无法通过调整本地来纠正 时钟频率, 时间服务将本地时钟设置为正确的时间。
https://technet.microsoft.com/en-us/library/cc773013(v=ws.10).aspx
答案 2 :(得分:17)
Chrome 有一个内置的fps计数器:https://developer.chrome.com/devtools/docs/rendering-settings
只需打开开发控制台( F12 ),打开抽屉( Esc ),然后添加&#34;渲染&#34;标签
在这里,您可以激活FPS-Meter叠加层以查看当前帧速率(包括漂亮的图形)以及GPU内存消耗。
跨浏览器解决方案 您可以使用JavaScript库stat.js获得类似的叠加层:https://github.com/mrdoob/stats.js/
它还为帧速率(包括图形)提供了很好的叠加,并且非常易于使用。
比较stats.js和chrome dev工具的结果时,两者都显示完全相同的测量值。所以你可以相信那个库实际上做了正确的事情。
答案 3 :(得分:8)
我有一个不同的方法,因为如果你计算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());
});
答案 4 :(得分:3)
只需检查AFR回调之间的时差。 AFR已将时间作为回调的参数传递。我更新了你的小提琴以显示它:http://jsfiddle.net/WCKhH/1/
答案 5 :(得分:3)
只是一个概念证明。非常简单的代码。我们所做的只是设置每秒帧数和每帧之间的间隔。在绘图功能中,我们从当前时间中扣除最后一帧的执行时间,以检查自上一帧起经过的时间是否大于我们的间隔(基于fps)。如果条件的计算结果为true,我们将设置当前帧的时间,该时间将是下一个绘图调用中的“最后一帧执行时间”。
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);
答案 6 :(得分:3)
这是另一个解决方案:
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()
以提高精确度(as covered in this answer)我更详细地写了这个解决方案on my website。
答案 7 :(得分:2)
实际上,没有一个答案足以满足我的需求。这是一个更好的解决方案,
代码:
// 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();
答案 8 :(得分:2)
我与 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>
答案 9 :(得分:1)
我缺少一个实现,可以针对平均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>
答案 10 :(得分:1)
我的fps计算使用 requestAnimationFrame()
及其匹配的 timestamp 参数作为其回调函数。
参见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);