Chrome

Juan Mellado, 22 Febrero, 2012 - 18:47

DartHoy he liberado los fuentes de dart-lzma, una versión para Dart del algoritmo de compresión LZMA. Es un nuevo proyecto que he empezado para ver en que estado está el lenguaje y las posibilidades que ofrece. Y la verdad es que me he encontrado que está todo en general en estado absolutamente alpha, aunque ya es posible editar, compilar y ejecutar programas.

El uso de la librería es muy sencillo, basta con importarla y llamar a la función de descompresión:

#import("lzma.dart", prefix: "LZMA");

LZMA.decompress(inStream, outStream);

Aunque Dart viene con librerías que incluyen varios tipos de streams, he decidido aislar un poco mi librería de los posibles cambios en la implementación y definir dos interfaces propias muy sencillas:

interface InStream {
  int readByte();
  int get length();
}

interface OutStream {
  void writeByte(final int value);
  int get length();
}

Llama la atención que en una interface haya definido un método get, pero parece que el lenguaje lo soporta.

He puesto un ejemplo completo en la página del proyecto, pero al final todo se reduce a instanciar dos objetos y llamar a una función:

final InStream inStream = new InStream(data);
final OutStream outStream = new OutStream();
   
LZMA.decompress(inStream, outStream);

La función de compresión está en desarrollo. He retrasado su desarrollo porque me ha desanimado bastante que el programa no funcione con la máquina virtual de Dart utilizando la línea de comandos. Eleva una excepción ".\vm\object.cc:7453: error: unimplemented code" por que hay código de la máquina virtual que aún no está desarrollado. La única forma de hacer las pruebas es realizando una compilación cruzada del código en Dart a JavaScript para poder llamarlo desde una página web en vez de ejecutarlo de forma nativa.

El entorno y el lenguaje tienen cosas curiosas, a ver si me animo a escribir un post con las cuatro cosas que he ido viendo.

Juan Mellado, 14 Febrero, 2012 - 13:32
Juan Mellado, 13 Febrero, 2012 - 18:05

Hoy he liberado los fuentes del último proyecto en JavaScript en el que he estado trabajando. Es un visor de modelos 3D almacenados en formato AC3D (.ac) que utiliza WebGL para renderizar. He estado utilizando modelos de aviones de FlightGear para probarlo, así que he decidido llamarlo Hangar.

He creado una pequeña página web con cuatro modelos escogidos de prueba. Tiene implementado un controlador a modo de trackball que permite rotar y ampliar los modelos utilizando el botón izquierda y la rueda del ratón. Necesita un navegador moderno como las últimas versiones de Chrome o Firefox para funcionar (Y no, IE 9 no es un navegador moderno)

Hangar

Demo online: http://inmensia.com/files/hangar/flight-gallery/index.html

El formato AC3D lo utilizan algunos programas como FlightGear, un simulador aéreo de código abierto, o Torcs, un simulador de conducción deportiva también de código abierto. Es un formato de texto plano que originalmente creó un programa de modelado 3D llamado, lógicamente, AC3D.

Mi idea era hacer algo sencillo, pero al final he acabado incorporando un montón de esas pequeñas características que pasan desapercibidas cuando funcionan bien.

AC3D
Los ficheros .ac tienen dos partes, la primera es una lista de materiales y la segunda una lista de objetos. Los materiales incluyen los típicos atributos para ambiente, emisiva, difusa, especular, brillo y transparencia. Los objetos son agrupaciones de entidades que reciben el nombre de superficies, aunque en realidad, además de polígonos, también pueden ser líneas simples o cerradas (el último punto se une con el primero). A mi me interesaban sólo los polígonos, pero al final he añadido soporte para representar las líneas también.

Los objetos están definidos de una manera jerárquica, de forma que un objeto puede tener hijos. Además, cada objeto tiene una matriz de rotación y un vector de rotación que aplica a sí mismo y a todos su hijos, por lo que hay que ir calculando la transformación resultante a medida que se baja por el árbol de descendencias.

Teselación
Debido a que para renderizar con WebGL es mejor tener las superficies divididas en triángulos, he implementado una versión en JavaScript de un algoritmo de teselación que soporta tanto polígonos convexos como cóncavos. Funcionó bastante bien casi a la primera, aunque haciendo pruebas detecté un problema con el orden en que devolvía los índices con modelos que tenían definidas las caras con los vértices en el sentido de la agujas del reloj, y que afortunadamente puede solucionar fácilmente. Me gustaría escribir en el futuro un post individual sobre este tema en particular.

Me he encontrado con modelos de todo tipo y definidos de casi cualquier forma. Al final he decidido ignorar los polígonos degenerados. Es decir, polígonos con menos de tres vértices, polígonos con coordenadas colineales, polígonos con vértices no contenidos dentro de un único plano, polígonos con aristas que se cruzan, ...

Normales
El formato del fichero no incluye normales, por lo que se calculan online mediante JavaScript. Por una parte la normal a cada superficie, para los sombreados planos. Y por otra parte la normal a cada vértice, para los sombreados más realistas.

La normal a un vértice concreto se calcula como la suma de todas las normales de todas las caras que comparten dicho vértice. Aunque a este respecto el fichero incorpora un parámetro por objeto llamado "crease". Este parámetro es un ángulo, y sirve para generar lo que habitualmente se conocen como bordes duros (hard edges). Si el ángulo que forman la normal de una cara y su vecina supera dicho parámetro entonces esa normal vecina no se tiene en cuenta para calcular la normal en el vértice compartido.

SGI
Bastantes modelos en formato AC3D utilizan texturas en formato SGI. Un formato antiguo que no soportan directamente los navegadores actuales. Una solución era transformar las texturas a .png, pero después de leer la especificación del formato me decidí a implementar también un lector de este tipo de imágenes en JavaScript, ya que el método de compresión que utilizan es un simple RLE. ¡Un proyecto dentro de otro proyecto!

Mi implementación soporta .sgi, .rgba, .rgb, .ra y .bw. Es decir, todas las combinaciones posibles, aunque he ignorado deliberadamente algunas características de la especificación, como la posibilidad de usar paletas de colores o escoger entre 8 ó 16 bits por canal. En la práctica todas las imágenes acaban siendo de cuatro canales y con 8 bits por canal en el clásico formato RGBA.

Loader
Para agilizar la carga y proceso de las texturas, que son binarias al contrario que los modelos que son de texto, he utilizado una de las nuevas características del objeto XMLHttpRequest de HTML5 que permite descargar un fichero directamente a un ArrayBuffer.

Muy útil.

Renderer
Como modelo de iluminación he utilizado una versión propia basada en el clásico de Phong con algunas modificaciones. Como los colores se me saturaban bastante debido a como están definidos por lo general los materiales he utilizado el valor de las texturas para ponderar la suma de emisiva, ambiente y difusa. De igual forma, para evitar los brillos exagerados he tomado sólo un 30% de la reflexión especular. Es más un modelo de prueba y error buscando un resultado agradable a la vista que uno realista. Es uno de mis puntos flacos.

El formato AC3D permite incluir luces en los modelos, pero las he ignorado a la hora de renderizar, utilizando en cambio una luz fija estática direccional sin factores de atenuación. Mi idea era representar modelos de objetos independientes, no escenas completas, por lo que no he encontrado sentido en incluirlas. Aparte de que acabado no fiándome de los modelos, con materiales un tanto extraños a mi parecer.

Para minimizar los cambios de estado de la tarjeta gráfica he agrupado todos los polígonos por programa, material, tipo, y todo lo que se me ha ocurrido. Por ello, a diferencia de otros proyectos anteriores, he decidido utilizar drawArrays en vez de drawElements para mi comodidad, aunque fuera a costa de repetir información al no utilizar índices.

Transparencias
Las transparencias han sido un dolor como de costumbre. Al final me he ceñido al guión. Primera pasada para los objetos opacos, con el blending desactivado. Segunda pasada para los objetos transparentes, con el blending activado y la escritura al depth buffer desactivada. Lo único que no he hecho es ordenar en profundidad, no me ha hecho falta en ninguna de las pruebas que he realizado.

Lo que me duele es que se supone, repito "se supone", que los materiales incluyen un atributo para indicar si tienen algún tipo de transparencia. Desgraciadamente esto no ocurre así en la práctica. Al final cuando algún modelo no se visualizaba correctamente, abría el fichero, localizaba el material y lo cambiaba a mano.

Esto es algo que en general creo que tendría que revisar con el objetivo de entender la manera en que interpretan los materiales programas como FligthGear o Torcs aprovechándome del hecho de que son de código abierto.

Autofit
Uno de los inconvenientes de hacer un visor genérico es que cada modelo viene posicionado y orientado según decidió su autor. Con centrarlos en el eje de coordenadas y alejarse una distancia prudencial prefijada no es suficiente, ya que depende del tamaño de cada modelo en particular. Para solventar este problema he implementado un auto-ajuste a la pantalla de los modelos basados en el tamaño de su bounding box.

De esto también me gustaría escribir un post individual en el futuro, ya que he encontrado distintas soluciones en Internet y no me he quedado del todo satisfecho con la que yo mismo he implementado.

TrackBall
Una vez dibujados los modelos lo lógico es poder interactuar con ellos. Ya había implementado anteriormente un simple controlador, cuando hice la demo de js-openctm, pero era un fake, así que esta vez he implementado un algoritmo de trackball de verdad. No quiero acordarme de las horas que habré perdido por culpa de esto. No es que no viera la solución, ¡es que no veía el problema!.

Resumiendo
¿Mucho tiempo invertido? Sí. ¿Suficiente? ¡Nunca!.

Una aproximación más práctica al problema hubiera sido utilizar un motor 3D ya existente, como el popular Three.js, y limitarme a hacer un conversor del formato .ac a algún formato que acepte el motor. Pero claro, ¡me hubiera perdido la parte más divertida!

Temas: Chrome
Juan Mellado, 13 Enero, 2012 - 14:58

Hará cosa de un año que me bajé los fuentes de Chrome y los estuve compilando. Muchas novedades ha habido en el navegador desde entonces, así que me he decidido a bajarme los fuentes actualizados y volver a compilarlos. Y si bien la primera vez utilice la versión 2008 Express de Microsoft Visual C++, esta vez he optado por utilizar la 2010 Express.

Chromium - Visual C++ 2010 Express

Esta es la secuencia de pasos que he seguido utilizando Windows 7 Ultimate 64 bits:

Instalación

1) Instalar Microsoft Visual C++ 2010 Express

2) Instalar Microsoft Windows SDK for Windows 7 and Framework .NET 4

3) Instalar Microsoft Visual Studio 2010 Service Pack 1

4) Instalar Microsoft Visual C++ 2010 SP1 Compiler Update for the Windows SDK 7.1

5) Instalar Microsoft Windows Driver Kit Version 7.1.0 en un directorio <DDK>

6) Instalar Microsoft DirectX SDK en un directorio <SDX>

7) Dar permiso de modificación y sustituir "v7.0A" por "v7.1" en los ficheros:

    C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Platforms\Win32\PlatformToolsets\v100\
Microsoft.Cpp.Win32.v100.props

    C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Platforms\x64\PlatformToolsets\v100\
Microsoft.Cpp.x64.v100.props

Configuración

8) Descargar depot_tools y descomprimir el .zip en un directorio <depot_tools>

9) Descargar chromium_tarball y descomprimir el .tar en un directorio <sources>

10) Añadir el directorio <depot_tools> al PATH del sistema

11) Ejecutar por primera vez las depot tools para actualizarlas:
    <depot_tools>\glclient

12) Editar el fichero <sources>\.glclient y añadir la url de una rama probada de los fuentes:
    "safesync_url" : "http://chromium-status.appspot.com/lkgr"

13) Actualizar los fuentes:
    <sources>\glclient sync

14) Crear la solución y los proyectos para Visual Studio 2010 Express:
    <sources>\set GYP_MSVS_VERSION=2010e
    <sources>\gclient runhooks

Compilación

15) Abrir solución de Visual Studio (ignorar mensaje de advertencia sobre properties):
    <sources>\chrome\chome.sln

16) Configurar el proyecto "chrome" como inicial

17) Añadir las siguientes rutas al principio de la lista de directorios de includes (Win32 y x64):
    <SDX>\Include

18) Añadir las siguientes rutas al final de la lista de directorios de includes (Win32 y x64):
    <DDK>\inc\atl71
    <DDK>\inc\mfc42

19) Añadir las siguientes rutas al principio de la lista de directorios de libs:
    Win32: <SDX>\Lib\x86
    x64: <SDX>\Lib\x64

20) Añadir las siguientes rutas al final de la lista de directorios de libs:
    Win32: <DDK>\lib\ATL\i386
    x64: <DDK>\lib\ATL\amd64

21) Lanzar el build sobre el proyecto "chrome"

El tiempo total en mi máquina para una compilación sólo del navegador Chrome para Win32 Debug sin los tests es de unos 40 minutos, lo que está bastante bien teniendo en cuenta la cantidad de proyectos y dependencias.

El orden de instalación que he puesto difiere un tanto de las instrucciones oficiales, pero es la mejor forma que he encontrado para evitar los errores con los que me he ido topando al instalar los distintos productos de Microsoft que necesitaba. La lista de pasos ahora la tengo clara, pero como de costumbre ha sido todo un proceso de prueba y error. Al final opté por bajarme las .ISO de cada instalación en vez de utilizar los instaladores webs, porque esperar por cada descarga cada vez era muy tedioso.

Con respecto a la instalación en particular del compilador, comentar que finalmente tuve que instalar primero el Visual C++, luego el SDK de Windows, luego el Service Pack 1 del Visual C++ y por último un parche para arreglarlo todo, aunque aún así es necesario modificar dos ficheros de manera manual, ya que se empeña en tomar la versión 7.0A del SDK de Windows en vez la más actualizada 7.1.

Finalmente, puede que sorprenda que haya tenido que instalar el SDK de DirectX, ya que este último pasó a formar parte del SDK de Windows hace tiempo, pero es necesario porque los fuentes de Chrome utilizan D3DX, que sólo está incluida en el SDK de DirectX. De igual forma, es necesario instalar el DDK porque la versión Express de Visual C++ no trae los includes y librería de ATL.

¡Feliz compilación a todos!

Temas: Chrome
Juan Mellado, 14 Diciembre, 2011 - 20:35

Más alternativas a la creación de contenido para la web. A ver, un repaso rápido a lo más popular que tenemos ahora mismo. Por una parte está el omnipresente Flash, que se resiste a morir, sobre todo porque Google no hizo nada para matarlo en su día, aunque Adobe ahora está reculando un poco y prefiere que la gente empiece a utilizar su plataforma AIR. Por otra parte está HTML5, aunque hoy en día es complicado hacerse un desarrollo entero en JavaScript, poder se puede, pero faltan las herramientas adecuadas para hacerlo de una forma productiva, además de otras lagunas en los navegadores que tardarán un tiempo en cubrirse. IMHO, Flash hoy en día le gana a HTML5 por goleada.

Mención aparte para Unity3D, que a pesar de ser un plugin siempre ha sido bien visto por la comunidad. Y de Silverlight mejor no hablamos, aunque posiblemente deberíamos estar hablando más de él. Y por último X3D, el sucesor de VRML, que parece que va a acabar siguiendo su mismo camino de estándar caído en el olvido.

Y los experimentos de conversión de Flash a HTML5 por ahora son sólo eso, experimentos.

Native Client (NaCl) es una tecnología que implementa Chrome y que viene a ser una "sandbox" donde se permite ejecutar código nativo no seguro. El navegador ejecuta el código capturando las llamadas que interactuan con el sistema evitando que se ejecute código potencialmente malicioso. Y todo ello con sólo una pérdida de rendimiento estimada del 5% según los técnicos de Google.

Esta semana esta tecnología ha cobrado un poco de relevancia por la publicación en la Chrome Web Store de Bastion, un juego RPG muy popular. Lo he estado probando, unos diez minutos o así, y la verdad es que funciona bastante bien. Me ha sorprendido muy gratamente.

Recuerdo haber estado leyendo documentación sobre Native Client hace unos meses y me gustó por el enfoque que le estaban aplicando. Concretamente porque estaban migrando algunas librerías como la popular SDL. No trataban tanto de vender su producto, sino más bien de conseguir que fuera útil. Hasta entonces sólo me parecía un plugin más, como ActiveX, pero cuando vi ese movimiento me gustó bastante. Hay muchos desarrollos antiguos, o actuales, que podrían cobrar una nueva vida gracias a eso.

La filosofía es un poco como la de Java, un sólo código que se ejecuta en todas partes. Lo bueno es que el código en este caso puede estar escrito en C o C++, por lo que se puede reaprovechar todo lo que se tiene actualmente. Por ejemplo, recuerdo haber visto hace un tiempo un port del popular emulador DOSBox a NaCl, lo que automáticamente permitía ejecutar todas nuestras queridas viejas aplicaciones directamente en el navegador. Una locura.