获取浏览器刷新率的一致方法
Consistent way to get a browser's refresh rate
我使用增量时间来测量循环中各帧之间经过的时间。 time
变量的值用作动画速度的乘数。这可确保拥有 60 Hz 显示器的用户将体验到与拥有 144 Hz 显示器的用户相同的动画速度(尽管帧速率不同)。如果没有这个倍增器,144 赫兹的用户体验到的动画效果会比使用 60 赫兹显示器的人快得多。我就是这样实现的:
let time;
let last = window.performance.now();
function loop(current) {
time = current - last;
last = current;
// run game logic and render
requestAnimationFrame(loop);
}
尽管这种测量效果很好,但它也有缺陷。浏览器不像本机应用程序那样一致,并且 time
变量的值在卡顿期间会有很大差异,从而导致“跳跃”动画。
有没有更好的方法来检测浏览器的帧速率而不依赖于 requestAnimationFrame 的不一致数字?
帧速率 运行 平均值。
由于浏览器上的计时器不是很准确,因此最好使用 运行 平均值。
一个运行均值是前n帧的平均值。
从运行均值我们可以通过选择高于均值
的closest帧率来估计帧率
例子
该示例通过对象 FrameRate
实现了 运行 的意思
创建 const rate = FrameRate(sample)
其中样本 os 要取平均值的样本数。
要添加示例,请将任何值分配给 属性 rate.tick
它使用 performance.now
来测量时间。
rate.rate
以 fps 为单位保存当前平均值。如果没有足够的样本,它将 return 为 1
rate.FPS
保存每秒计算的帧数。这是一个估计值,将在您获得该值时设置丢帧数。
rate.dropped
是在当前帧率下每个样本计数丢弃的估计帧数 returned by rate.FPS
注 dropped 应该在 [= 之后查询24=]
function FrameRate(samples = 20) {
const times = [];
var s = samples;
while(s--) { times.push(0) }
var head = 0, total = 0, frame = 0, previouseNow = 0, rate = 0, dropped = 0;
const rates = [0, 10, 12, 15, 20, 30, 60, 90, 120, 144, 240];
const rateSet = rates.length;
const API = {
sampleCount: samples,
reset() {
frame = total = head = 0;
previouseNow = performance.now();
times.fill(0);
},
set tick(soak) {
const now = performance.now()
total -= times[head];
total += (times[head++] = now - previouseNow);
head %= samples;
frame ++;
previouseNow = now
},
get rate() { return frame > samples ? 1000 / (total / samples) : 1 },
get FPS() {
var r = API.rate, rr = r | 0, i = 0;
while (i < rateSet && rr > rates[i]) { i++ }
rate = rates[i];
dropped = Math.round((total - samples * (1000 / rate)) / (1000 / rate));
return rate;
},
get dropped() { return dropped },
};
return API;
}
const fRate = FrameRate();
var frame = 0;
requestAnimationFrame(loop);
fRate.reset();
function loop() {
frame++;
fRate.tick = 1;
meanRateEl.textContent = "Mean FPS: " + fRate.rate.toFixed(3);
FPSEl.textContent = "FPS: " + fRate.FPS;
droppedEl.textContent = "Dropped frames: " + fRate.dropped + " per " + fRate.sampleCount + " samples" ;
requestAnimationFrame(loop);
}
body {user-select: none;}
<div id="meanRateEl"></div>
<div id="FPSEl"></div>
<div id="droppedEl"></div>
如果您获得一致的 FPS 90、120、144 或 240,并且您没有禁用帧限制 and/or Vsnyc 并且没有使用 FireFox,请发表评论。目前还不清楚有多少人将这些设置设为默认
示例 2
该示例让您设置 3 个帧速率。它通过调用多个帧请求来实现。对于 240FPS,它要求每次同步 4 帧
如果您在浏览器上关闭帧垂直同步,帧速率 120 和 240 将无法准确报告。要查看实际帧速率,只需使用 60FPS 设置。
您可以给框架添加负载。负载是所请求帧速率的帧时间的一小部分。
var forceRate = 60;
var current = 60;
var load = 0, loadTime = 0;
function FrameRate(samples = 20) {
const times = [];
var s = samples;
while(s--) { times.push(0) }
var head = 0, total = 0, frame = 0, previouseNow = 0, rate = 0, dropped = 0;
const rates = [0, 10, 12, 15, 20, 30, 60, 90, 120, 144, 240];
const rateSet = rates.length;
const API = {
sampleCount: samples,
reset() {
frame = total = head = 0;
previouseNow = performance.now();
times.fill(0);
},
set tick(soak) {
const now = performance.now()
total -= times[head];
total += (times[head++] = now - previouseNow);
head %= samples;
frame ++;
previouseNow = now
},
get rate() { return frame > samples ? 1000 / (total / samples) : 1 },
get FPS() {
var r = API.rate, rr = r | 0, i = 0;
while (i < rateSet && rr > rates[i]) { i++ }
rate = rates[i];
dropped = Math.round((total - samples * (1000 / rate)) / (1000 / rate));
return rate;
},
get dropped() { return dropped },
};
return API;
}
const fRate = FrameRate();
var frame = 0;
requestAnimationFrame(loop);
fRate.reset();
function loop() {
frame++;
fRate.tick = 1;
if (load) {
const pnow = performance.now() + loadTime;
while (performance.now() < pnow);
}
meanRateEl.textContent = "Mean FPS: " + fRate.rate.toFixed(3);
FPSEl.textContent = "FPS: " + fRate.FPS;
droppedEl.textContent = "Dropped frames: " + fRate.dropped + " per " + fRate.sampleCount + " samples" ;
if (current > forceRate) {
current /= 2;
} else {
requestAnimationFrame(loop);
if (current < forceRate) {
current *= 2;
requestAnimationFrame(loop);
}
}
}
options.addEventListener("click", (e) => {
if (e.target.dataset.load !== undefined) {
load = Number(e.target.dataset.load);
loadTime = (1000 / forceRate) * load;
loadEl.textContent = "Load " + loadTime.toFixed(3) + "ms " + (load * 100).toFixed(0) + "%";
return;
}
if (e.target.dataset.rate !== undefined && e.target.dataset.rate != forceRate) {
forceRate = e.target.dataset.rate | 0;
forcedRateEl.textContent = "Requested " + forceRate + "FPS";
loadTime = (1000 / forceRate) * load;
loadEl.textContent = "Load " + loadTime.toFixed(3) + "ms " + (load * 100).toFixed(0) + "%";
}
});
body {user-select: none;}
<div id="forcedRateEl">Requested 60FPS</div>
<div id="loadEl">Load 0ms 0% frame time</div>
<div id="meanRateEl"></div>
<div id="FPSEl"></div>
<div id="droppedEl"></div>
<div id="options">
Frame rate</br>
<button data-rate="240">240FPS</button>
<button data-rate="120">120FPS</button>
<button data-rate="60">60FPS</button><br>
Frame load</br>
<button data-load="0.0">0%</button>
<button data-load="0.25">25%</button>
<button data-load="0.5">50%</button>
<button data-load="0.75">75%</button>
<button data-load="0.90">90%</button>
<button data-load="1.1">110%</button>
<button data-load="1.5">150%</button>
<button data-load="2">200%</button>
</div>
用户体验
如果您的游戏经常承受重负载导致帧率下降(比如从 60 降到 30),用户会注意到,最好将帧率上限设置为 30,以便游戏保持稳定一致的速率和减速没有被注意到。
另请注意,将帧速率加倍 更多 而不是渲染场景所需的 CPU 时间减半。如果允许用户控制帧速率会导致游戏频繁地在不同速率之间跳跃,这可能会导致对游戏质量的负面看法,这在帧速率较高时更有可能发生。
98% 的用户无法分辨 60 和 30FPS 之间的区别,除非他们进行并排比较。甚至 os只要你有一个一致的帧率,并且它在人类视觉持久性之上,只要在游戏中添加一个虚假的帧率指示器,所有用户都可以被完全愚弄。
我使用增量时间来测量循环中各帧之间经过的时间。 time
变量的值用作动画速度的乘数。这可确保拥有 60 Hz 显示器的用户将体验到与拥有 144 Hz 显示器的用户相同的动画速度(尽管帧速率不同)。如果没有这个倍增器,144 赫兹的用户体验到的动画效果会比使用 60 赫兹显示器的人快得多。我就是这样实现的:
let time;
let last = window.performance.now();
function loop(current) {
time = current - last;
last = current;
// run game logic and render
requestAnimationFrame(loop);
}
尽管这种测量效果很好,但它也有缺陷。浏览器不像本机应用程序那样一致,并且 time
变量的值在卡顿期间会有很大差异,从而导致“跳跃”动画。
有没有更好的方法来检测浏览器的帧速率而不依赖于 requestAnimationFrame 的不一致数字?
帧速率 运行 平均值。
由于浏览器上的计时器不是很准确,因此最好使用 运行 平均值。
一个运行均值是前n帧的平均值。
从运行均值我们可以通过选择高于均值
的closest帧率来估计帧率例子
该示例通过对象 FrameRate
创建
const rate = FrameRate(sample)
其中样本 os 要取平均值的样本数。要添加示例,请将任何值分配给 属性
rate.tick
它使用performance.now
来测量时间。rate.rate
以 fps 为单位保存当前平均值。如果没有足够的样本,它将 return 为 1rate.FPS
保存每秒计算的帧数。这是一个估计值,将在您获得该值时设置丢帧数。rate.dropped
是在当前帧率下每个样本计数丢弃的估计帧数 returned byrate.FPS
注 dropped 应该在 [= 之后查询24=]
function FrameRate(samples = 20) {
const times = [];
var s = samples;
while(s--) { times.push(0) }
var head = 0, total = 0, frame = 0, previouseNow = 0, rate = 0, dropped = 0;
const rates = [0, 10, 12, 15, 20, 30, 60, 90, 120, 144, 240];
const rateSet = rates.length;
const API = {
sampleCount: samples,
reset() {
frame = total = head = 0;
previouseNow = performance.now();
times.fill(0);
},
set tick(soak) {
const now = performance.now()
total -= times[head];
total += (times[head++] = now - previouseNow);
head %= samples;
frame ++;
previouseNow = now
},
get rate() { return frame > samples ? 1000 / (total / samples) : 1 },
get FPS() {
var r = API.rate, rr = r | 0, i = 0;
while (i < rateSet && rr > rates[i]) { i++ }
rate = rates[i];
dropped = Math.round((total - samples * (1000 / rate)) / (1000 / rate));
return rate;
},
get dropped() { return dropped },
};
return API;
}
const fRate = FrameRate();
var frame = 0;
requestAnimationFrame(loop);
fRate.reset();
function loop() {
frame++;
fRate.tick = 1;
meanRateEl.textContent = "Mean FPS: " + fRate.rate.toFixed(3);
FPSEl.textContent = "FPS: " + fRate.FPS;
droppedEl.textContent = "Dropped frames: " + fRate.dropped + " per " + fRate.sampleCount + " samples" ;
requestAnimationFrame(loop);
}
body {user-select: none;}
<div id="meanRateEl"></div>
<div id="FPSEl"></div>
<div id="droppedEl"></div>
如果您获得一致的 FPS 90、120、144 或 240,并且您没有禁用帧限制 and/or Vsnyc 并且没有使用 FireFox,请发表评论。目前还不清楚有多少人将这些设置设为默认
示例 2
该示例让您设置 3 个帧速率。它通过调用多个帧请求来实现。对于 240FPS,它要求每次同步 4 帧
如果您在浏览器上关闭帧垂直同步,帧速率 120 和 240 将无法准确报告。要查看实际帧速率,只需使用 60FPS 设置。
您可以给框架添加负载。负载是所请求帧速率的帧时间的一小部分。
var forceRate = 60;
var current = 60;
var load = 0, loadTime = 0;
function FrameRate(samples = 20) {
const times = [];
var s = samples;
while(s--) { times.push(0) }
var head = 0, total = 0, frame = 0, previouseNow = 0, rate = 0, dropped = 0;
const rates = [0, 10, 12, 15, 20, 30, 60, 90, 120, 144, 240];
const rateSet = rates.length;
const API = {
sampleCount: samples,
reset() {
frame = total = head = 0;
previouseNow = performance.now();
times.fill(0);
},
set tick(soak) {
const now = performance.now()
total -= times[head];
total += (times[head++] = now - previouseNow);
head %= samples;
frame ++;
previouseNow = now
},
get rate() { return frame > samples ? 1000 / (total / samples) : 1 },
get FPS() {
var r = API.rate, rr = r | 0, i = 0;
while (i < rateSet && rr > rates[i]) { i++ }
rate = rates[i];
dropped = Math.round((total - samples * (1000 / rate)) / (1000 / rate));
return rate;
},
get dropped() { return dropped },
};
return API;
}
const fRate = FrameRate();
var frame = 0;
requestAnimationFrame(loop);
fRate.reset();
function loop() {
frame++;
fRate.tick = 1;
if (load) {
const pnow = performance.now() + loadTime;
while (performance.now() < pnow);
}
meanRateEl.textContent = "Mean FPS: " + fRate.rate.toFixed(3);
FPSEl.textContent = "FPS: " + fRate.FPS;
droppedEl.textContent = "Dropped frames: " + fRate.dropped + " per " + fRate.sampleCount + " samples" ;
if (current > forceRate) {
current /= 2;
} else {
requestAnimationFrame(loop);
if (current < forceRate) {
current *= 2;
requestAnimationFrame(loop);
}
}
}
options.addEventListener("click", (e) => {
if (e.target.dataset.load !== undefined) {
load = Number(e.target.dataset.load);
loadTime = (1000 / forceRate) * load;
loadEl.textContent = "Load " + loadTime.toFixed(3) + "ms " + (load * 100).toFixed(0) + "%";
return;
}
if (e.target.dataset.rate !== undefined && e.target.dataset.rate != forceRate) {
forceRate = e.target.dataset.rate | 0;
forcedRateEl.textContent = "Requested " + forceRate + "FPS";
loadTime = (1000 / forceRate) * load;
loadEl.textContent = "Load " + loadTime.toFixed(3) + "ms " + (load * 100).toFixed(0) + "%";
}
});
body {user-select: none;}
<div id="forcedRateEl">Requested 60FPS</div>
<div id="loadEl">Load 0ms 0% frame time</div>
<div id="meanRateEl"></div>
<div id="FPSEl"></div>
<div id="droppedEl"></div>
<div id="options">
Frame rate</br>
<button data-rate="240">240FPS</button>
<button data-rate="120">120FPS</button>
<button data-rate="60">60FPS</button><br>
Frame load</br>
<button data-load="0.0">0%</button>
<button data-load="0.25">25%</button>
<button data-load="0.5">50%</button>
<button data-load="0.75">75%</button>
<button data-load="0.90">90%</button>
<button data-load="1.1">110%</button>
<button data-load="1.5">150%</button>
<button data-load="2">200%</button>
</div>
用户体验
如果您的游戏经常承受重负载导致帧率下降(比如从 60 降到 30),用户会注意到,最好将帧率上限设置为 30,以便游戏保持稳定一致的速率和减速没有被注意到。
另请注意,将帧速率加倍 更多 而不是渲染场景所需的 CPU 时间减半。如果允许用户控制帧速率会导致游戏频繁地在不同速率之间跳跃,这可能会导致对游戏质量的负面看法,这在帧速率较高时更有可能发生。
98% 的用户无法分辨 60 和 30FPS 之间的区别,除非他们进行并排比较。甚至 os只要你有一个一致的帧率,并且它在人类视觉持久性之上,只要在游戏中添加一个虚假的帧率指示器,所有用户都可以被完全愚弄。