CyanPrime
¿Cómo verificaría la velocidad de fotogramas en mi código javascript? Estoy usando esto para hacer un bucle:
gameloopId = setInterval(gameLoop, 10);
Phrogz
El código de @Slaks te brinda solo el FPS instantáneo del último cuadro, que puede variar o ser engañoso con contratiempos. Prefiero usar un filtro de paso bajo fácil de escribir y calcular para eliminar los transitorios rápidos y mostrar un pseudopromedio razonable de los resultados recientes:
// The higher this value, the less the fps will reflect temporary variations
// A value of 1 will only keep the last value
var filterStrength = 20;
var frameTime = 0, lastLoop = new Date, thisLoop;
function gameLoop(){
// ...
var thisFrameTime = (thisLoop=new Date) - lastLoop;
frameTime+= (thisFrameTime - frameTime) / filterStrength;
lastLoop = thisLoop;
}
// Report the fps only every second, to only lightly affect measurements
var fpsOut = document.getElementById('fps');
setInterval(function(){
fpsOut.innerHTML = (1000/frameTime).toFixed(1) + " fps";
},1000);
La ‘vida media’ de este filtro (la cantidad de fotogramas necesarios para pasar a la mitad del valor anterior a un valor nuevo y estable) es filterStrength*Math.log(2)
(aproximadamente el 70% de la fuerza).
Por ejemplo, una fuerza de 20
se moverá a la mitad de un cambio instantáneo en 14 fotogramas, 3/4 del camino en 28 fotogramas, el 90 % del camino en 46 fotogramas y el 99 % del camino en 92 fotogramas. Para un sistema que se ejecuta a aproximadamente 30 fps, un cambio repentino y drástico en el rendimiento será obvio en medio segundo, pero aún así “desechará” las anomalías de un solo cuadro, ya que solo cambiarán el valor en un 5% de la diferencia.
Aquí hay una comparación visual de diferentes intensidades de filtro para un juego de ~30 fps que tiene una caída momentánea a 10 fps y luego acelera hasta 50 fps. Como puede ver, los valores de filtro más bajos reflejan más rápidamente los cambios “buenos”, pero también son más susceptibles a contratiempos temporales:
Finalmente, aquí está un ejemplo de usar el código anterior para comparar realmente un bucle de ‘juego’.
-
¡Muy buen gráfico! ¿Cómo hiciste eso?
– Código Benny
13 de febrero de 2013 a las 23:12
-
@BennyNeugebauer Lo anterior se creó en Excel, solo porque era un poco más fácil para una sola vez que usando HTML5 Canvas para gráficos IIR bonitos.
– Phrogz
14 de febrero de 2013 a las 3:42
-
¿Hay alguna razón por la que obtengo fps infinitos cuando uso el cuadro de animación de solicitud? ¿Estoy usando una intensidad de filtro de 1 para obtener los fps más precisos? solo sucede de vez en cuando (Infinite fps): jsfiddle.net/CezarisLT/JDdjp/10
– Kivilius
17 de diciembre de 2013 a las 22:23
-
@CezarisLT Infinite FPS == una devolución de llamada informando al mismo tiempo (
new Date
) como la convocatoria anterior. Puedes protegerte de esto rescatandoif (thisLoop==lastLoop)
. Sin embargo, tenga en cuenta que si está utilizando una intensidad de filtro de1
, no tiene sentido usar el filtro en absoluto. Solo usa la respuesta de @SLaks.– Phrogz
19 de diciembre de 2013 a las 1:49
-
@Phrogz Sé que llego un poco tarde a la fiesta… pero incluso cuando uso la protección sugerida contra el error de fps infinitos. sigo teniendo fps infinitos? ¿alguna sugerencia?
– FutureCake
15 de noviembre de 2017 a las 22:47
SLaks
En gameLoop
mira la diferencia entre new Date
y new Date
del último ciclo (almacenarlo en una variable).
En otras palabras:
var lastLoop = new Date();
function gameLoop() {
var thisLoop = new Date();
var fps = 1000 / (thisLoop - lastLoop);
lastLoop = thisLoop;
...
}
thisLoop - lastLoop
es el número de milisegundos que pasaron entre los dos bucles.
-
¿podría reemplazar la nueva Fecha por Fecha. ahora (), lo mismo, pero más correcto en mi opinión
– caub
10 de agosto de 2016 a las 17:16
-
¿Cómo actualizar la función gameLoop() según los fps reales? Porque cuando uso setInterval() puedo pasar el argumento del intervalo de tiempo solo una vez.
– Jacek Dziurdzikowski
25 de febrero de 2018 a las 11:35
-
@JacekDziurdzikowski: Llamar
setTimeout()
cada vez para programar el siguiente cuadro.– SLaks
25 de febrero de 2018 a las 15:38
-
@JacekDziurdzikowski usando
requestAnimationFrame
es mejor para el rendimiento que usar cualquierasetInterval()
osetTimeout()
ya que automáticamente ajusta el tiempo a los fps.–Greyson R.
27 de julio de 2019 a las 23:49
-
Si fuera por mí, preferiría usar algo como performance.now en lugar de fechas debido a sus ventajas en precisión y eficiencia.
– AlphaHowl
12 de noviembre de 2022 a las 11:08
NVRM
Mis 2 centavos:
Útil para mí para comparar optimizaciones. Queme un poco de recursos, por supuesto, solo para pruebas.
Idealmente, la velocidad de fotogramas de su aplicación siempre debe permanecer por debajo de los 50 ms por fotograma, en pleno uso, cuando se usan eventos, bucles, etc. Esto es igual a 20 FPS.
El ojo humano siente retrasos por debajo de 24 FPS, esto es 1000 / 24 = 41ms
Entonces, 41ms para un marco es la ventana de tiempo más pequeña, para mantener la fluidez natural. Más alto que eso debe evitarse.
let be = Date.now(),fps=0,info='';
requestAnimationFrame(
function loop(){
let now = Date.now()
fps = Math.round(1000 / (now - be))
be = now
requestAnimationFrame(loop)
if (fps < 35){
kFps.style.color = "red"
kFps.textContent = fps
} if (fps >= 35 && fps <= 41) {
kFps.style.color = "deepskyblue"
kFps.textContent = fps + " FPS"
} else {
kFps.style.color = "black"
kFps.textContent = fps + " FPS"
}
kpFps.value = fps;
info+=(''+new Date()+' '+fps+'\n');
}
)
<span id="kFps"></span>
<progress id="kpFps" value="0" min="0" max="100" style="vertical-align:middle"></progress>
<button onclick="try{console.clear();console.info(info)}catch{}">Statistics</button>
¡Solo un ciclo de prueba para tener una idea, intervalo de 50 ms, debería mantenerse sin problemas!
¿Ves la barra de progreso arriba de saltar? ^
Esas son pérdidas de cuadros, el navegador está tratando de mantenerse al día mediante el sacrificio, saltando al siguiente cuadro. Esos picos hay que evitarlos. El siguiente fragmento quema recursos (es decir, FPS):
let t
for (let i=0;i<99999;i++){
t = setTimeout(function(){
console.log("I am burning your CPU! " + i)
clearTimeout
},50)
}
Las versiones recientes de los depuradores tienen un contador de FPS, en el performance
pestaña, al grabar. Esto no es perfecto porque sobrecarga las pruebas.
Qué pasa requestAnimationFrame?
var before,now,fps;
before=Date.now();
fps=0;
requestAnimationFrame(
function loop(){
now=Date.now();
fps=Math.round(1000/(now-before));
before=now;
requestAnimationFrame(loop);
console.log("fps",fps)
}
);
Yo uso esto para calcular fps
var GameCanvas = document.getElementById("gameCanvas");
var GameContext = doContext(GameCanvas,"GameCanvas");
var FPS = 0;
var TimeNow;
var TimeTaken;
var ASecond = 1000;
var FPSLimit = 25;
var StartTime = Date.now();
var TimeBefore = StartTime;
var FrameTime = ASecond/FPSLimit;
var State = { Title:0, Started:1, Paused:2, Over:3 };
var GameState = State.Title;
function gameLoop() {
requestAnimationFrame(gameLoop);
TimeNow = Date.now();
TimeTaken = TimeNow - TimeBefore;
if (TimeTaken >= FrameTime) {
FPS++
if((TimeNow - StartTime) >= ASecond){
StartTime += ASecond;
doFPS();
FPS = 0;
}
switch(GameState){
case State.Title :
break;
case State.Started :
break;
case State.Paused :
break;
case State.Over :
break;
}
TimeBefore = TimeNow - (TimeTaken % FrameTime);
}
}
Sprites.onload = function(){
requestAnimationFrame(gameLoop);
}
function drawText(Context,_Color, _X, _Y, _Text, _Size){
Context.font = "italic "+ _Size +" bold";
Context.fillStyle = _Color;
Context.fillText(_Text, _X, _Y);
}
function doFPS()(
drawText(GameContext,"black",10,24,"FPS : " + FPS,"24px");
}
function doContext(Canvas,Name){
if (Canvas.getContext) {
var Context = Canvas.getContext('2d');
return Context;
}else{
alert( Name + ' not supported your Browser needs updating');
}
}