Midiendo el rendimiento de nuestros objetos canvas (Contador FPS)

Introducción

Preparando una aplicación basada íntegramente en canvas me topé con el blog del Proyecto Cyan, un interesante proyecto en español sobre la elaboración videojuegos en Javascript a través de un framework propio que han bautizado como piNgine.

Entre sus diversos y recomendables artículos, uno de los primeros trata sobre la medición del rendimiento de un objeto canvas según el número de FPS (imágenes por segundo) que es capaz de gestionar. Como es bien sabido en la industria del videojuego, éste es un valor esencial a la hora de buscar cuellos de botella y zonas pesadas que son susceptibles de mejorar.

Cómo va eso de los FPS

Cuando estamos trabajando con entornos gráficos, como puede ser el caso del canvas, las operaciones de redibujado son constantes: la sensación de animaciones o desplazamientos se resuelven mediante un ciclo de limpiado y renderizado de las zonas afectadas.

Este proceso conlleva un coste y es mesurable. Los FPS vienen a indicar la cantidad de veces que una zona, generalmente el viewport (la pantalla completa) se redibuja por segundo.

Suele considerarse que una tasa óptima está alrededor de los 40 fps. Valores por debajo de los 20-15 indican sobrecarga y deberían ser objeto de atención para un posible optimizado (reduciendo el número de objetos visibles, modificando el algoritmo de compresión, etc…).

Midiendo los FPS en un canvas

Para realizar esta medición, solo necesitamos aplicar una rutina que realice un conteo de los refrescos del objeto canvas sobre el que estamos trabajando.

Para facilitar la reutilización, lo ideal es crear un objeto que podamos cargar con un JS externo y que nos permita iniciarlo a voluntad para controlar los valores que arroje.

Me he permitido tomar el código de piNgine al que he realizado algunas modificaciones.

Su API es sumamente sencilla. Para comenzar el conteo, únicamente debemos iniciar el objeto:

fpsCounter.init( [ canvasID ] );

Donde:
canvasID (opcional) es el ID del objeto que deseamos monitorizar. Si omitimos este valor, el objeto busca un canvas con ID canvas (el más frecuente).

NOTA: He decidido que el objeto no busque los objetos canvas automáticamente en el DOM porque existen técnicas muy extendidas como los buffers que crean falsos canvas de sustitución para actuar como zonas de prerenderizado. Aplicar el conteo de FPS sobre esas zonas no tendría sentido por lo que es el desarrollador el que debe proporcionar el ID del objeto que desea estudiar.

El código es el siguiente:

var fpsCounter = ( function(){
 
  console.log('Modulo de FPS cargado...');
 
  // FPS máximos a los que queremos que se ejecute la aplicación.
  var maxfps = 32;
 
  // Variables necesarias para el recuento de FPS y el cálculo del delay.
  var frameCount = 0,
      currentFps = 0,
      drawInterval = 1 / maxfps * 1000,
      lastFps = new Date().getTime();
 
  // Variables para almacenar las referencias al elemento canvas.
  var canvas = null,
      ctx = null;
 
  // Método que utilizamos como constructor.
  var initCore = function( canvasID ){
    console.log('Inciando el contador de FPS...');
 
    // Cargamos el objeto canvas y su contexto
    canvas = document.getElementById( ( canvasID ) ? canvasID : 'canvas' );
 
    if ( canvas && canvas.getContext ){
      ctx = canvas.getContext('2d');
      // Inicializamos el intervalo a los FPS deseados.
      setInterval( function(){ startApp(); }, drawInterval );
    }else{
      console.warn('Objeto canvas no encontrado o navegador no soportado!');
    }
 
  };
 
  var startApp = function(){
    // Actualizamos y enviamos la petición de pintado.
    update();
    draw();
  };
 
  var update = function(){
    // Calculamos el tiempo desde el último frame.
    var thisFrame = new Date().getTime(),
        diffTime = Math.ceil((thisFrame - lastFps));
 
    if (diffTime >= 1000) {
      currentFps = frameCount;
      frameCount = 0.0;
      lastFps = thisFrame;
    }
 
    frameCount++;
 
  };
 
  var draw = function(){
    // Pintamos los datos en el canvas
    ctx.clearRect( 0, 0, canvas.width, canvas.height );
    ctx.save();
    ctx.fillStyle = '#000';
    ctx.font = 'bold 10px sans-serif';
    ctx.fillText( 'FPS: ' + currentFps + '/' + maxfps, 10, 15 );
    ctx.restore();
  };
 
  return{
    init : initCore
  }
 
} )();

NOTA: Durante el código, se realizan algunas llamadas a la consola (console.log); si nuestro navegador no dispone de esa funcionalidad, solo tenemos que comentar aquellas líneas en las que aparezca esa referencia.

Para aplicar este script, podemos crear una sencilla página de prueba:

<!doctype html>
<html lang="es">
<head>
  <title>Contador FPS</title>
  <script type="text/javascript" src="/js/fpsCounter.js"></script>
</head>
<body>
<canvas id="canvas" width="200px" height="200px" style="border:1px solid #000;">
  <p>¡Explorador no compatible!</p>
</canvas>
<script type="text/javascript">
fpsCounter.init();
</script>
</body>
</html>

Podemos ver el resultado aquí.

Si leemos el código, hemos establecido una tasa máxima de FPS por segundo:

var maxfps = 32;

Este valor es muy útil para garantizar que nuestra aplicación corre a la misma velocidad máxima con independencia de la potencia de la plataforma sobre la que la ejecutemos. Un valor de 32 suele ser suficiente pero, como hemos comentado anteriormente, podemos escoger otros más altos si necesitamos una mayor fluidez.

Por defecto, los FPS se muestran en la parte superior del canvas; si queremos cambiar esta localización, solo tendremos que tocar la función draw().

Conclusión

Sin duda, esta rutina puede resultar muy útil mientras trabajamos con canvas o estamos pensando en portar un proyecto al interesante sistema WebGL. Controlar la tasa de refresco es importante para identificar problemas de rendimiento y ponernos en sobre aviso.

Acerca de Carlos Benítez

Programador Web y arquitecto Javascript. Fundador de www.etnassoft.com y OpenLibra, la Biblioteca Libre Online
Esta entrada fue publicada en Javascript y etiquetada , , , , . Guarda el enlace permanente. Sígueme en Twitter

Últimos libros gratuitos añadidos a OpenLibra

Aún no tenemos debug!
Ningún marciano se ha comunicado.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Licencia Creative Commons 3.0

®Copyright 2016. Cotenido web bajo licencia Creative Commons 3.0

Códigos bajo licencias MIT y GPL. Ver página de licencias para más información