HTML5

Juan Mellado, 19 Junio, 2010 - 11:07

WebGL Cheat SheetWebGL es la forma de acceder a OpenGL desde JavaScript. A diferencia de lo que inicialmente pudiera pensarse, no es una especificación de muy alto nivel que busca encapsular todo el trabajo duro detrás de una librería con un API simplificado. Todo lo contrario, es de tan de bajo nivel como pueda serlo el propio OpenGL. El famoso "canvas" es sólo la puerta de entrada, la forma de instanciar un contexto de trabajo gráfico. A partir de ahí sigue siendo necesario crear buffers, hacer binds, operar matrices, compilar shaders, ... y toda la parafernalia habitual de los programas que trabajan con OpenGL.

Gracias a que navegadores como Chrome o Firefox están empezando a implementar esta tecnología, más el primero que el segundo, se están empezando a ver algunas demos tecnológicas muy interesantes. En estos días me ha llamado particularmente la atención un visor de ficheros MD5 que muestra un modelo de una criatura de DOOM 3 con sus texturas aplicadas, e incluso con una animación. Muy espectacular, sobre todo porque el modelo en si mismo lo es. La demo es de Brandon Jones, el mismo que hizo el visor de ficheros Collada, mostrando en ese caso un modelo generado con "Spore Creature Creator".

He estado mirando la documentación, y para hacerme una idea de como estaba montado el API he acabado escribiendo una pequeña hoja de referencia (pulsar en la imagen que acompaña este post para agrandarla). No obstante, las funciones de WebGL no bastan por si solas, por lo que he añadido también un listado de referencia del "OpenGL ES Shading Language" necesario para escribir los shaders. Y es que como ya dije previamente, utilizar WebGL es como usar OpenGL. Aplican las mismas técnicas, por lo que es necesario escribir vertex shaders y fragment shaders. Se puede incluso desarrollar un motor utilizando Deferred Shading, las bases con las que se trabaja son las mismas.

Hay algunas diferencias y restricciones entre WebGL y su hermano mayor OpenGL, pero son bastantes técnicas, están en la documentación oficial, y creo que pueden obviarse en un primer momento. Lo que más está preocupando en estos momentos es la "descarga de contenidos". Es decir, la descarga de los modelos, texturas, animaciones, shaders, y toda esa serie de ficheros que luego se quieren mostrar por pantalla. En una distribución normal se empaqueta y se obliga al cliente a descargar y instalar. En una distribución web la cosa cambia un poco, ya que obliga a esperar que el navegador descargue todos los ficheros, y además normalmente son muchos ficheros sueltos que pueden estar, o no, en la cache del navegador. Los métodos tradicionales a base de referencias (links) puede que no sean lo más apropiado para este tipo de aplicaciones. Lo curioso es que si al final se llega a otra solución, más parecida a la distribución de las aplicaciones tradicionales, ¿no estamos entrando en una contradicción?

Una última nota, para probar las demos de los enlaces que he puesto es necesario tener un navegador que soporte WebGL. A día de hoy recomiendo Chrome. No obstante, la versión actual no tiene habilitado WebGL por defecto. Para activarlo hay que pasarle la cadena "--in-process-webgl --enable-webgl" como parámetro en la línea de comandos.

Temas: HTML5 Stratos
Juan Mellado, 5 Junio, 2010 - 11:06

Google I/O 2010En la web de la reciente Google I/O 2010 ya puede accederse a los vídeos individuales de cada una de las sesiones técnicas que se desarrollaron durante el evento.

Más concretamente, en la siguiente dirección:
http://code.google.com/intl/es-ES/events/io/2010/sessions.html

Los vídeos cubren las sesiones integras, de una hora de duración cada una, con un formato de 45 minutos de exposición y 15 de preguntas. Y teniendo en cuenta que hubo casi 100 de estas sesiones, pues la cantidad de información disponible es realmente grande.

Evidentemente las presentaciones son para promocionar productos y tecnologías de Google. Como Android por ejemplo, incluyendo un vídeo titulado "Writing real-time games for Android redux" que presenta un caso real de un juego desarrollado para esta plataforma. Desde aspectos puramente técnicos, hasta otros de tipo promocional y económico. O sea, como hacer un juego para que funcione en la mayoria de las plataformas existentes, que además vaya rápido (30 FPS), y que se venda bien. Eso sí, lo que no menciona por ningún lado es lo desesperadamente lento que va el emulador del SDK sobre un PC "normalito".

Sobre HTML5 hay unos cuantos vídeos, aunque la mayoria utilizados como excusa para promocionar Chrome, el navegador de Google. En "Developing With HTML5" pueden verse las palabras claves a seguir: el popular canvas (para acceder al área de dibujado), WebGL (la versión web de OpenGL), las etiquetas audio y video (de uso obvio), localStorage (para almacenar datos de forma local), Web SQL DB (para ejecutar queries SQL desde JavaScript), Worker Threads (para ejecutar hilos desde JavaScript), y unas cuantas cosas más, como mejoras en CSS (animaciones y drag and drop de forma nativa) o un sistema de notificaciones para alertar a los usuarios de un determinado evento.

En "GWT + HTML5 can do what?!" se explican detalles del "port" de Quake a HTML5 usando GWT. Muy técnica, y con un ritmo un poco lento, pero que diablos, ¡el Quake en un navegador!

El resto de vídeos están englobados dentro de servicios, productos y tecnologías como Google App Engine, Google Maps API (o cualquiera de las decenas de API de Google), Buzz o Wave, y más, mucho más.

Juan Mellado, 31 Mayo, 2010 - 09:08

Flash Apple Android Windows MobileSmokescreen es otro proyecto que trata de reproducir Flash en el navegador a través de un programa escrito completamente en JavaScript utilizando HTML5. En la web del proyecto se pueden ver algunas demos, e incluso un video con las demos ejecutándose sobre un iPad. Muy similar al proyecto Gordon, en bastantes aspectos.

Por el momento no resulta muy espectacular, ya que las películas Flash que requieren más proceso se ejecutan de una forma bastante lenta. Va requerir un poco más de optimización. Para lo que sirven en realidad este tipo de proyectos es para darse cuenta de que muchas cosas que se hacen "por inercia" con Flash se pueden hacer también con otras tecnologías. El principal problema es que no existen herramientas tan sencillas y productivas como Flash para hacerlas. Hay es donde se tienen que poner las pilas los desarrolladores. Y evidentemente falta que Internet Explorer empiece a soportar todas estas tecnologías. Microsoft debería tomar nota para la versión 9 de su navegador, que ya le toca. Silverlight y HTML5 pueden coexistir.

Según la web del proyecto, el código fuente de Smokescreen se liberará dentro de un tiempo. No obstante, a dia de hoy se puede analizar el JavaScript que se ejecuta en la página de las demos: http://smokescreen.us/demos/js/smokescreen.0.1.3-min.js

Leyendo entre líneas, se puede ver que han implementado un parser de ficheros SWF, de igual forma que se observa algunos nombres de clases del core del runtime de Flash para el player. Lo que no parece tener soporte el reproductor es para ActionScript, al menos en su versión 3, aunque teniendo sólo el código "minificado" es bastante complicado de saber a ciencia cierta.

En cualquier caso, si bien estos "experimentos" resultan interesantes, la guerra entre Flash y HTML5 se ha enfriado un poco con la liberación de la versión Flash 10.1 (beta) que ya se puede ejecutar en móviles con Android 2.2 (Froyo). Google lo tiene claro: dar soporte para Flash, y otros ingenios como Unity por ejemplo. Si los usuarios no quieren utilizarlo que no lo hagan, es algo opcional.

En la pasada Google I/O 2010 se pudieron ver bastantes cosas relativas a este tema, incluida una presentación de una aplicación de Adobe, aún en pañales, para generar animaciones directamente en HTML5 de forma similar a como se hace actualmente en Flash.

Juan Mellado, 8 Mayo, 2010 - 09:00

Después de haber conseguido parsear con éxito un fichero Flash mediante un programa en JavaScript, me he animado a programar una máquina virtual, también en JavaScript, que ejecute el código ActionScript 3 leído del fichero SWF sin utilizar el plugin de Adobe.

La ventaja de hacerlo en JavaScript es que ambos lenguajes se basan en la misma especificación ECMA-262, por lo que muchas cosas pueden traducirse directamente de un lenguaje a otro. Pero eso es en un mundo ideal, en la vida real ocurre que cada cual implementa la especificación de una forma ligeramente distinta, aparte de que la propia especificación deja margen para que ciertas partes se implementen de forma arbitraria. De hecho, es algo bastante conocido que la implementación del propio JavaScript varía de un navegador a otro.

Para simplificar estoy suponiendo que el comportamiento del código ejecutado en JavaScript es lo suficientemente parecido al de ActionScript 3. Siempre habrá tiempo para ajustar las pequeñas diferencias que aparezcan. Para el código estándar que no fuerce la mezcla de tipos de forma innecesaria, ni se base en efectos laterales, debería bastar. Y de hecho, ya he conseguido ejecutar sin mayor problema pequeños programas ActionScript 3 compuestos por operaciones matemáticas, comparaciones y bucles.

Siguiendo las especificaciones de Adobe, la ejecución de una función en ActionScript 3 requiere que la máquina virtual instancie una estructura de datos locales a dicha ejecución compuesta por una pila de operandos, una pila de ámbitos (scope) y un conjunto de registros.

LocalDataArea = function(){
  this.stack = [];
  this.scopeStack = [];
  this.registers = [];
};

Para empezar me he centrado en los opcodes que manipulan los registros o la pila de operandos, ya que normalmente son los más fáciles de implementar en cualquier tipo de emulador, y permiten tener en muy poco tiempo un prototipo ejecutando código.

La estructura principal de la máquina vitual es un simple switch que determina el código a ejecutar en función del opcode actual sobre el contador de programa.

switch(opcode){
...
case 0x02: //nop
  break;
...
case 0x28: //pushnan
  lda.stack.push(NaN);
  break;
...
case 0xd0: //getlocal_0
  lda.stack.push( lda.registers[0] );
  break;
...

Dejo para más adelante las consideraciones acerca del rendimiento, y realizar pruebas para ver es si mejor, o no, tener una función para cada opcode con el objetivo de agilizar la localización e invocación del código asociado a cada opcode. De momento me he centrado en implementar la mayor cantidad de opcodes posibles, unos cincuenta en estos momentos, sobre un total de aproximadamente ciento cincuenta con los que cuenta la especificación.

Una de las cosas "divertidas" de este proyecto es que me permite depurar código ActionScript 3 directamente desde el propio navegador mediante Firebug. Un fichero SWF generado en modo debug incluye opcodes de depuración con nombres de ficheros, números de línea y nombres de variable. Cuando se produce un error, como el intento de ejecución de un opcode no implementado, me basta con poner un punto de parada y ver que línea concreta del código fuente original ha generado el error, e incluso el nombre de las variables que contiene cada registro local en ese momento.

Buscando proyectos similares por Internet me he encontrado con un fork de Gordon por Brian McKelvey. Tiene implementado el parser de ficheros abcFile (que contienen el código ActionScript 3 compilado) aprovechando la clase base de lectura de Gordon, pero aún poca cosa de la máquina virtual en si misma. De hecho, me ha dado la impresión de lo que pretende no es construir una máquina virtual que interprete el código en tiempo de ejecución, sino trasladar los opcodes a código JavaScript directamente en forma de texto para luego evaluar la cadena resultante. Habrá que seguirle la pista.

Juan Mellado, 1 Mayo, 2010 - 10:54

A partir de su versión 9, los ficheros Flash admiten bloques de código escritos en ActionScript 3 embebidos en su interior. Aunque lo que almacenan esos bloques dentro de los ficheros no es el código fuente, sino un conjunto de descriptores y cadenas de opcodes resultantes de su compilación. La máquina virtual de ActionScript del plugin de Flash interpreta en tiempo de ejecución esa información y ejecuta el código.

Ha estado leyendo las especificaciones de la máquina virtual de Flash, y he acabado escribiendo un pequeño parser en JavaScript que lee las estructuras que contienen el código ActionScript de un fichero SWF dado. Aunque al final me ha interesado más la forma de encontrar el punto de entrada para su ejecución.

Un fichero SWF se compone de una serie de bloques con un tipo y unos datos que deben interpretarse en función de ese tipo. Los bloques con el código ActionScript son de tipo DoABC y contienen una estructura de datos llamada abcFile que almacena un pool de constantes, las definiciones de las clases y métodos, y los scripts en forma de cadenas de opcodes. Opcodes de bastante alto nivel, por cierto. Es decir, que no se limitan a operaciones básicas de acceso a memoria, manipulación de registros y control de flujo, como los mnemotécnicos de un microprocesador ordinario, sino que incluyen operaciones tales como instanciar un objeto de una clase o "escapar" un elemento XML.

Como resultado de ese procesamiento de alto nivel, una parte importante de la máquina virtual es la gestión de nombres. De paquetes, de clases, de métodos, de variables, ... Algunos conocidos en tiempo de compilación, otros resueltos en tiempo de ejecución, pero todos ellos englobados bajo el término multinames, compuestos por un namespace y un name. Por ejemplo, el multiname "com.inmensia.flash.test:Main" se compone del namespace "com.inmensia.flash.test" y el name "Main".

Para probar mi parser he generado un fichero SWF con FlashDevelop utilizando el siguiente código extremadamente sencillo:

package {
  import flash.display.Sprite;
  public class Main extends Sprite {
    public function Main():void {
    }
  }
}

En la siguiente imagen puede verse el objeto abcFile resultante de la ejecución del parser y capturado desde Firebug:

Puede verse como todas las estructuras están rellenas con los valores leidos del SWF. No obstante, el punto de entrada del código dentro un frame que viene indicado en otro bloque del fichero SWF. Más concretamente en uno de tipo SymbolClass. Este bloque almacena una lista de los símbolos de alto nivel almacenados en el fichero, con un id numérico y un nombre. El nombre del símbolo con id igual a 0 es el que sirve de punto de entrada para la ejecución del código contenido en el fichero.

La secuencia completa que he seguido para encontrar el primer opcode de la clase "Main" del ejemplo es la siguiente:
1) Buscar por texto el name dentro del pool de strings
2) Buscar por texto el namespace dentro del pool de strings
3) Buscar por id de string el namespace dentro del pool de namespaces
4) Buscar por id de name y namespace el multiname dentro del pool de multinames
5) Buscar por id de multiname la clase dentro del pool de instances
6) Buscar por id de método el cuerpo del método de entrada dentro del pool de method bodies

En el siguiente diagrama puede verse la secuencia de pasos seguida para localizar el punto de entrada saltando de estructura en estructura, además del contenido del bloque SymbolClass referido anteriormente:

He dejado que se vea el pool de strings completo, para que se aprecie la lista de cadenas que se monta a partir de un ejemplo tan sencillo, y la importancia de los nombres dentro de la máquina virtual.

Como se observa, se parte de "Main", o lo que es lo mismo, del namespace "" (vacío) y del name "Main". En el pool de strings se localiza el id 3 correspondiente al name, y en el pool de namespaces el id 1 correspondiente a un namespace con id de nombre 1 (vacío) y tipo 22 que indica que corresponde al nombre de un paquete. En la documentación existe una serie de constantes definidas para estos tipos. A continuación, con el id del name y del namespace se localiza el multiname dentro del pool de multinames. Y con el id de dicho multiname la clase asociada dentro del pool de instances. Finalmente, para cada clase existe un atributo iinit que es el id de su método de entrada (constructor), y que permite encontrar la lista de opcodes a ejecutar dentro del pool de method bodies.

Como comprobación final he decompilado los opcodes para verificar que se correspondían con el código ActionScript de partida:

208    getlocal_0
48     pushscope
208    getlocal_0
73  0  constructsuper (0)
71     returnvoid

Sin entrar en mucho detalle, se observa que el método se limita a guardar el scope (ámbito) en la pila, llamar al constructor de la clase base (flash.display.Sprite) sin argumentos, y retornar sin devolver ningún valor. Lo que concuerda con lo esperado.

Por último, comentar que este punto de entrada es para una clase concreta como la del ejemplo. El punto de entrada real del fichero [en su totalidad] está determinado por la propiedad init del último registro del pool de scripts dentro del objeto abcFile, que lo que hace precisamente es instanciar nuestra clase "Main" de ejemplo.