Blog

Juan Mellado, 25 Noviembre, 2011 - 12:38

V8 es la máquina virtual de JavaScript que lleva implementada Chrome. El lunes el equipo de desarrollo anunció un cambio en su política de gestión del Garbage Collector (GC) para mejorar el rendimiento de las aplicaciones interactivas, es decir, aplicaciones web y juegos HTML5 sobre todo.

Antes detenían completamente la ejecución de los programas hasta que el GC hubiera terminado de procesar "toda" la memoria, por lo que el tiempo de proceso del GC dependía del tamaño de la memoria ocupada. Ahora lo hacen de forma incremental, es decir, sólo una parte de la memoria cada vez, evitando así tener que parar los procesos por mucho tiempo y proporcionando una mejor interactividad.

Han creado una prueba de stress para poder medir de una forma objetiva el rendimiento. Con la versión actual de Chrome (16.0.912.41) he obtenido los siguientes resultados:


8000/320 16(max = 236) ms 3307 frames
Score 3
  0 -  10 ms =>  129
 10 -  20 ms => 3028
 20 -  30 ms =>    4
 30 -  40 ms =>   88
 40 -  50 ms =>   20
 50 -  60 ms =>    1
 80 -  90 ms =>    1
110 - 120 ms =>    1
120 - 130 ms =>   18
220 - 230 ms =>    1
230 - 240 ms =>   16

Con la versión Canary (17.0.949.0), es decir, la versión que se encuentra actualmente en desarrollo, he obtenido los siguientes resultados:


8000/320 16(max = 114) ms 3516 frames
Score 36
  0 -  10 ms =>    9
 10 -  20 ms => 3314
 20 -  30 ms =>   13
 30 -  40 ms =>  163
 40 -  50 ms =>    4
 50 -  60 ms =>    2
 60 -  70 ms =>    2
 70 -  80 ms =>    2
 80 -  90 ms =>    3
 90 - 100 ms =>    2
100 - 110 ms =>    1
110 - 120 ms =>    1

Lo que han intentado con esta mejora es reducir los picos de actividad del GC durante los cuales se detiene completamente la ejecución de los programas en JavaScript. Y como se observa, tras el cambio, el tiempo máximo por frame se ha reducido a la mitad, de 236 a 114 milisegundos. Los números "grandes" tienden a concentrarse en los intervalos "pequeños". Lo que quiere decir que los programas, y por ende los usuarios, obtendrán un mejor tiempo de respuesta que antes, en vez de estar esperando a que termine su labor el GC.

Curiosamente con la versión actual de Firefox (8.0.1) la prueba de stress va mucho mejor para un tiempo medio de frame prácticamente igual (16~17), son unos 10 frames que se van de tiempo en Chrome los que marcan la diferencia:


8000/320 17(max = 58) ms 3534 frames
Score 213
 0 - 10 ms =>    3
10 - 20 ms => 3516
20 - 30 ms =>    2
40 - 50 ms =>    1
50 - 60 ms =>   12

Lo del funcionamiento del GC era algo que ya había detectado cuando estuve haciendo análisis de rendimiento de mis proyectos en JavaScript. Aunque como siempre no se puede esperar la solución perfecta, todo tiene sus ventajas e inconvenientes. Al menos parece que con la nueva versión de V8 la batería de pruebas (versión 6) funciona del orden de un 5% mejor en mi ordenador con la nueva versión de Chrome, y un 50% peor con Firefox.

Juan Mellado, 24 Noviembre, 2011 - 15:07

A continuación una muestra de lo que sucede cuando no aplicas el filtro adecuado a la imagen adecuada. La de los ojos en blanco es particularmente perturbadora.


Las imágenes se están calculando "online" con JavaScript y dibujándose en un objeto canvas. Es un simple filtro de 3x3 que va cambiando de coeficientes en cada activación.

Juan Mellado, 23 Noviembre, 2011 - 17:18

DartDart es el nombre de un nuevo lenguaje de programación creado por Google que se plantea como una alternativa de futuro a JavaScript. Siempre he pensado que el atributo "type" de la etiqueta "script" estaba para algo más que tomar un valor por defecto, y parece que al fin alguien se lo ha tomado en serio.

¿Pero realmente necesitamos un nuevo lenguaje de programación? Pues como siempre la respuesta depende de como le duela a cada uno. Para validar formularios, realizar efectos o hacer alguna que otra librería independiente tenemos más que suficiente con JavaScript. Pero para desarrollos que impliquen una mayor complejidad, y sobre bastante miles de líneas de código, entonces estamos necesitando claramente otro lenguaje. En particular con las características deseables de un lenguaje orientado a objetos, en vez de estar siempre tratando de emular estas características en JavaScript. Y todo esto naturalmente partiendo de la base de que a futuro sigamos queriendo ejecutar (mucho) código en el cliente.

Cuestiones filosóficas aparte, siendo muy simplista, la idea es que Dart recupera para la web las palabras reservadas interface y class, con sus constructores, métodos y variables de instancia. Pero eso es sólo una pequeña parte, claro. También proporciona un punto de entrada a las aplicaciones a través de una clásica función main, la posibilidad de importar librerías con #import, incluir código con source, y colecciones que pueden tiparse como los templates de C++ o los generics de Java. Suena todo bastante familiar, ¿no?

main() {
  print('Hello, Dart!');
}

Dart es un lenguaje orientado a objetos basado en clases que soporta herencia simple, no múltiple, y que encapsula el código de forma modular en unidades a las que llama librerías. Proporciona mecanismos para declarar elementos públicos y privados, aunque curiosamente distinguidos por la nomenclatura de su nombre, no por un atributo modificador. Si empieza por "_" entonces es privado. Además soporta la concurrencia mediante un mecanismo propio de ejecución aislada (isolation) con intercambio de mensajes, sin memoria compartida. Y permite realizar una programación mixta en cuanto a comprobación de tipos se refiere. Pudiendo realizarse una versión inicial débilmente tipada e ir haciéndola más fuerte a medida que avanza el desarrollo. Pueden consultarse todos los detalles en el borrador actual que contiene las especificaciones del lenguaje.

Para probar Dart a día de hoy hay dos formas:

1) Utilizar el Dartboard, una pequeña aplicación web con un cuadro de texto donde se puede introducir código, compilarlo y ejecutarlo. Evidentemente el código se compila y ejecuta en un servidor remoto.

2) Utilizar el Dart Editor, una aplicación de escritorio basada en Eclipse que incorpora todas las características propias de este conocido entorno de desarrollo. En este caso el código se compila a JavaScript y se puede ejecutar desde una página HTML ordinaria directamente desde el navegador. Esta opción es similar a GWT, que acepta código en Java y genera JavaScript.

Por su parte, a futuro Dart se podrá ejecutar de dos formas:

1) Mediante un nuevo tipo MIME "application/dart" que se ejecutará de forma nativa en el navegador, de forma similar a como actualmente se ejecuta el código "application/javascript".

2) Mediante una máquina virtual en un PC local. Algo que en realidad ya puede hacerse hoy en día gracias a las herramientas de líneas de comandos ya disponibles.

Naturalmente hoy en día un lenguaje no es nada si no viene acompañado de unas buenas librerías, máxime si su ejecución ha de realizarse en un navegador donde se dispone de APIs ya establecidos. Dart en ese aspecto apenas tiene dos librerías básicas, una core y otra dom. La librería de acceso a DOM está lo suficientemente desarrollada para manipular una página web de forma dinámica, permitiendo ya actualmente acceso a los objetos de tipo canvas por ejemplo.

#library('example');
#import('dart:dom');

main() {
  new Example();
}

class Example {
  CanvasRenderingContext2D ctx;

  Example() {
    var doc = window.document;
    HTMLCanvasElement canvas = doc.getElementById("canvas");
    ctx = canvas.getContext("2d");

    render();
  }
 
  void render() {
    ctx.clearRect(0, 0, 800, 600);
...
  }
}

He estado probando el Dart Editor y compilando los ejemplos. Iba a subir uno de ellos a la web, pero finalmente he desistido porque la versión actual genera como resultado de la compilación ficheros JavaScript de varios megas, y no merece la pena.

¿Sobrevivirá este lenguaje y se popularizará? Pues no tengo ni idea. A priori resulta difícil aceptar que el resto de navegadores vaya a adoptar una especificación muy concreta de Google, y mucho menos hacer un desarrollo propio para soportarlo, pero peores cosas se han visto. La gran ventaja con la que cuenta Google en este sentido es que Dart es un proyecto de código abierto. En cualquier caso no es el primer intento por parte de Google de crear un nuevo lenguaje de programación, ahí está Go por ejemplo, cuya primera versión definitiva está prevista que aparezca el año que viene. El tiempo dirá.

Temas: Flash Stratos
Juan Mellado, 22 Noviembre, 2011 - 22:51

WebGL Cheat SheetEl lanzamiento de la versión definitiva de Flash 11 hace unas pocas semanas trajo consigo el tan esperado Stage3D, anteriormente conocido como Molehill. O lo que es lo mismo, la posibilidad de aprovechar las capacidades de las GPUs desde Flash. En principio es una alternativa más a otras opciones disponibles actualmente como WebGL, aunque el anuncio por parte de Adobe de renunciar a seguir desarrollando Flash para móviles en favor de su plataforma AIR nos ha dejado despistados a un montón de gente. Pero decisiones empresariales aparte, el API está ahí fuera y es hora de echarle un vistazo.

Siguiendo mi costumbre, he elaborado una pequeña cheat sheet para que me sirva de referencia, igual que la que hice para WebGL.

Lo primero que llama la atención es que es una API de muy bajo nivel. Los que hayan utilizado alguna vez OpenGL, WebGL o DirectX se sentirán cómodos. Pero sinceramente, me esperaba una jerarquía de clases que ofreciera una funcionalidad de más alto nivel para la gestión de escenas, cámaras, colisiones o carga de modelos. Pero no, las clases que hay son para el acceso a bastante bajo nivel. De hecho, ni siquiera han incluido en el runtime el compilador de shaders.

Pero vayamos por partes, las clases básicas de entrada al API son Stage3D y Context3D. La primera clase representa la superficie sobre la que se dibuja, de forma similar al Stage de toda la vida, y que permite además instanciar la segunda clase a través de un evento (para las situaciones de pérdida de contexto). El contexto 3D es el que permite instanciar al resto de clases como los buffers de índices, vértices, texturas o shaders.

public class Example extends Sprite{
  private var stage3D:Stage3D;
  private var context3D:Context3D;

  public function Example(){
    stage3D = this.stage.stage3Ds[0];
    stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
    stage3D.requestContext3D(Context3DRenderMode.AUTO);
  }

  private function onContext3DCreated(event:Event):void{
    context3D = Stage3D(event.target).context3D;
  }
...

Context3D es la clase base, muy similar a otras APIs, y permite, entre cosas, establecer el estado del render con las clásicas opciones de configuración del z-buffer, blending o stencil, y el envío al driver de la orden de dibujado de triángulos.

Otras clases básicas son VertexBuffer3D, IndexBuffer3D, Texture, CubeTexture y Program3D. Pero tienen muy poca funcionalidad, apenas dos o tres métodos para inicializar su contenido desde fuentes de distintos tipos como arrays o matrices. El resto de clases son básicamente enumerados con las constantes típicas como Context3DTextureFormat, Context3DVertexBufferFormat, Context3DBlendFactor o Context3DStencilAction. En total son unas nueve clases de este último tipo, pero no ofrecen mayor funcionalidad aparte de exponer las constantes.

var triangles:Vector.<uint> = Vector.<uint>( [ ... ] );
...
indexList = context3D.createIndexBuffer(triangles.length);
indexList.uploadFromVector(triangles, 0, triangles.length);
...           
context3D.setCulling(Context3DTriangleFace.BACK);
...

Por lo que respecta a los shaders, Adobe ha definido un lenguaje propio (otro más) de muy bajo nivel llamado AGAL (Adobe Graphics Assembly Language). Es realmente ensamblador, por lo que las operaciones son de muy bajo nivel. Las instrucciones son opcodes que manipulan directamente los distintos tipos de registros habituales (attribute, constant, temporary, output, varying, sampler).

private const VERTEX_SHADER:String =
  "mov v0, va1"; //Color (v0: varying 0 <= va1: attribute 1)
  "m44 op, va0, vc0 \n" + //Perspectiva (op: output position, vc0: constant 0)
       
private const FRAGMENT_SHADER:String =
  "mov oc, v0"; //oc: output color

El array de bytecodes correspondiente a un shader puede generarse en tiempo de diseño mediante Pixel Bender 3D, que es una ampliación de la versión 2D de Pixel Bender, una tecnología de Adobe para el procesamiento óptimo de imágenes y vídeos independiente de la plataforma hardware utilizada basada en el uso de ficheros XML. Aunque afortunadamente los shaders también pueden compilarse de una forma más conveniente en tiempo de ejecución mediante una clase de utilidad externa llamada AGALMiniAssembler.

private var vertexAssembly:AGALMiniAssembler = new AGALMiniAssembler();
private var fragmentAssembly:AGALMiniAssembler = new AGALMiniAssembler();
private var programPair:Program3D;
...
vertexAssembly.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER, false);
fragmentAssembly.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER, false);
...
programPair = renderContext.createProgram();
programPair.upload(vertexAssembly.agalcode, fragmentAssembly.agalcode);

Por lo que respecta al bucle principal habitual de Flash, no parece requerir modificación, pudiéndose seguir utilizando el evento ENTER_FRAME por ejemplo.

  ...
  this.stage.addEventListener(Event.ENTER_FRAME, render);
}

private function render(event:Event):void{
  ...
  context3D.drawTriangles(indexList, 0, 12);
  context3D.present();
}

En definitiva, una API más, una opción más a tener en cuenta a la hora de representar 3D en el navegador. Quizás de muy bajo nivel para el concepto que suele tenerse en la cabeza de Flash, percepción que la aparición de ActionScript 3 empezó a modificar y que Stage3D no hace más que confirmar.

Para terminar, revisando la web de Flash, he encontrado un enlace a un proyecto de la propia Adobe llamado Proscenium. Una librería gráfica de alto nivel, a modo de engine 3D, que están desarrollando sobre Stage3D. El inconveniente es que está aún en fase de desarrollo y no tiene soporte, aunque ya permite crear primitivas básicas, cargar modelos de objetos, e incluso gestionar colisiones. No sé como acabará, pero supongo que pretenderá ser una alternativa a motores desarrollados de forma independiente, como por ejemplo el popular Away3D y otros, que la propia Adobe parece estar apoyando.

Juan Mellado, 21 Noviembre, 2011 - 16:50

Opera lanzó hace unas semanas un build especial de su navegador que habilitaba el uso de la función getUserMedia de JavaScript. Esta función permite acceder a la webcam directamente desde JavaScript de forma nativa. He creado una nueva demo de js-aruco, mi detector de marcadores de realidad aumentada, usando esta versión para conseguir que todo el proceso sea 100% JavaScript evitando Flash para la captura de la webcam.


Demo online:
www.inmensia.com/files/aruco/getusermedia/getusermedia.html

Para que la demo funcione hay que realizar dos pasos:

1) Instalar la versión especial del navegador de Opera que se encuentra en el siguiente enlace:
http://labs.opera.com/news/2011/10/19/

2) Permitir el acceso del navegador a la webcam a través de las opciones de configuración a través del siguiente enlace:
opera:config#SecurityPrefs|AllowCameraToCanvasCopy

Por motivos de seguridad, el navegador debería pedir permiso a los usuarios antes de acceder a la webcam, pero este funcionamiento no está todavía implementado. Lo que si está desarrollado es que no se pueda acceder por código. Si se intenta acceder con la función getImageData al contenido del canvas se produce una excepción de seguridad.

Actualizado 28/02/2012: Ahora también se puede ejecutar con Chrome 18 o posterior usando el flag --enable-media-stream en línea de comandos.