El blog de Pablo Zurita.

Noviembre 8, 2007

Conclusiones EVA 07

Archivado en: General — Pablo Zurita @ 12:00 am

El 4 de Noviembre presente en la EVA 07 sobre arquitecturas multihilos para motores 3D. La charla fue basada justamente en el artículo que escribí el 26 de julio pero con más información. Entre otras cosas agregue información sobre el uso e implementación de thread pools y el trabajo de implementación en Win32 y Xbox 360 entre otras cosas. La presentación la pueden bajar de acá. La charla fue muy bien en su desarrollo pero la mayoría de las preguntas que me hicieron sobre el tema tenía más que ver con temas de implementación en las plataformas y no tanto sobre la arquitectura que en mi punto de vista es lo más importante. Creo también hubo mucha gente que no tenia este tema en sus radares y por lo tanto no pudieron entender la charla pero me parece que el articulo de julio 26 les va a ayudar a entender mas sobre el tema.

Dejando el tema de mi presentación de lado, quede muy conforme con esta EVA. La calidad de las presentaciones fueron mucho mejores que las EVAs anteriores, hubo más gente, y también tuve la oportunidad de charlar con diferentes personas sobre varios temas sobre programación de engines y herramientas. Obviamente como toda charla entre programadores los tópicos pasaron de un lado a otro pero igual fue interesante ver en que estaban trabajando otras personas. Lo único que me parece que tiene que cambiar para el año que viene es que se tiene que separar la conferencia en tracks. La cantidad de asistentes es tal que me parece que ya no tiene sentido mantener a todos juntos ya que los intereses son bastante distintos para todos. Eso obviamente no quiere decir que hay que hacer 15 tracks diferentes, pero como mínimo creo que hacen falta dos tracks, uno totalmente técnico encarado a los programadores y un track artístico para los que hacen arte, música y demás. Y otra cosa que tiene que haber para la próxima EVA es un pequeño aparatito llamado presenter que haría las presentaciones mucho mas dinámicas y si cortes cuando la persona que maneja la computadora se olvida de saltar de slide o salta demasiadas. Dejando eso de lado quede muy conforme con la conferencia, realmente valió la pena el viaje de ~700KM.

Octubre 31, 2007

Charla en la EVA 07.

Archivado en: General — Pablo Zurita @ 7:43 pm

Simplemente quería avisarles que voy a estar dando una de las charlas en la EVA 07 (Exposición de Videojuegos Argentina) que se va a realizar el 3 y 4 de noviembre. La charla va a ser sobre el último artículo que escribí pero con más información. Aprovecho también para avisarles que estoy en busca de trabajo nuevamente así que voy a estar con varias copias de mi curriculum en la EVA 07 y si no me pueden pedir una copia dejando un comentario en este post.

Julio 26, 2007

Arquitectura Multihilos Para Motores 3D

Archivado en: General — Pablo Zurita @ 5:06 pm

Abstract
“Los microprocesadores de múltiples cores ya están llegando a la masas y dejaron de ser algo exclusivos de los servidores y las supercomputadoras. La mayoría de las computadoras personales y consolas de video juegos usan microprocesadores con múltiples cores que permiten ejecutar de forma paralela varios hilos de ejecución en hardware. Por otra parte la velocidad de cada core se ha estabilizado y por lo tanto no es posible mejorar la performance de ejecución de una aplicación que se ejecuta en un solo hilo de ejecución simplemente agregando más hardware. Pero crear una arquitectura para aplicaciones interactivas en 3D que usen este hardware nuevo es un reto por la necesidad de interacción entre los diferentes subsistemas. En este artículo se muestra la arquitectura que se creó para el Chromaticity Engine, un motor 3D que fue pensado para aplicaciones interactivas. El objetivo final del articulo es mostrar los diferentes inconvenientes al crear un motor 3D multihilo para aplicaciones interactivas y como se solucionaron esos inconvenientes en el Chromaticity Engine.”
Palabras Clave
Motor 3D, Engine 3D, Computación Grafica, Arquitectura, Xenon, Cell, multihilo, multithread.

Introducción
La complejidad de los motores 3D y las aplicaciones que los utilizan hacen muy difícil la separación de un motor de un solo hilo en múltiples hilos. A nivel de la arquitectura los diferentes subsistemas de un motor dependen entre sí por lo tanto hay dos maneras de encarar la arquitectura de un motor 3D. La primera es usar la misma arquitectura que se usaba cuando había un solo core y un solo microprocesador con la diferencia que se trata de crear hilos en funciones especificas. El beneficio de este método es que se puede mantener bastante código sin modificar y usar APIs (Application Programming Interface o Interfaz de Programación de Aplicaciones) como OpenMP para hacer todo más fácil. El problema con esta solución reside en que se trata de mezclar dos modelos que son fundamentalmente diferentes, el modelo de hilo único y el modelo de múltiples hilos. Este factor hace prácticamente imposible aprovechar al máximo el hardware disponible para obtener buenas ganancias de performance. El otro modelo consiste en dejar de lado la actualización serial de cada subsistema y remplazarlo por un modelo donde cada subsistema se actualiza de manera independiente pero siempre manteniendo cierta coherencia en los tiempos transcurridos en la simulación. Este modelo permite utilizar mejor los recursos disponibles en un microprocesador de múltiples cores y con una arquitectura adecuada es posible especializar para un microprocesador en específico sin requerir grandes cambios al motor entero. Este último modelo es el utilizado en el Chromaticity Engine y es el que va a ser expuesto.

Es importante reconocer también el hecho de que las arquitecturas de los microprocesadores en las diferentes plataformas difieren de manera sustancial y por lo tanto es necesario que esta arquitectura se pueda moldear para adaptarse a las diferentes plataformas. Si observamos los tres microprocesadores más importantes para aplicaciones interactivas en 3D vamos a ver grandes diferencias. Los tres microprocesadores que vamos a observar son:

  • Cell Broadband Engine de Sony, Toshiba e IBM que es utilizado en servidores y la consola PlayStation 3.
  • Xenon de IBM que es utilizado en la consola Xbox 360 de Microsoft.
  • Core 2 de Intel que es utilizado en PCs y la última serie de Macs de Apple.

Teniendo en cuenta estas diferencias se va a exponer la arquitectura y van a evaluar los casos específicos para cada microprocesador.

Microprocesadores
Las diferencias en los microprocesadores que vamos a evaluar son de gran importancia. La primera diferencia se hace ver en la cantidad de cores en los microprocesadores y las capacidades computacionales de los diferentes cores.
En el caso del Core 2 de Intel vemos un microprocesador con dos o cuatro cores, un cache L1 de 64 KB por cada core, un cache L2 compartido de 4 MB cada dos cores, ejecución fuera de orden, y un hilo en hardware por core [1, 2, 3]. Este microprocesador que es fácil de programar ya mantiene la mayoría de las características de los microprocesadores de las generaciones anteriores. Lo más importante en este sentido es que cada core mantiene la ejecución fuera de orden y que el cache L2 compartido tiene un tamaño considerable. Si analizamos esta situación podemos ver que podríamos directamente asignar un core a uno o varios subsistemas del motor hasta utilizar los dos o cuatro cores.

En el caso del Xenon de IBM vemos que es un microprocesador con tres cores, con un cache L1 de 64 KB por cada core, un cache L2 de 1 MB compartido por los tres cores y el GPU, ejecución en orden, y dos hilos simétricos en hardware por cada core [4, 5]. Aquí es importante observar el hecho de que aunque parece muy diferente al Core 2 en la arquitectura, vamos a ver que las diferencias no son tan notables que las que hay con el Cell. De todas formas hay diferencias significativas que van a impactar a la arquitectura del motor. Primeramente esta la ejecución en orden de las instrucciones lo que va a causar que si uno no organiza bien las instrucciones a ejecutar es posible perder bastante performance mientras un hilo ejecuta una instrucción intensiva. En este caso vamos a tener en cuenta la salida del compilador para ver que instrucciones generamos y crear código que sea más amigable con este factor (un ejemplo es que hay que limitar de gran manera la ejecución de funciones virtuales). Por otra parte el tamaño del cache L2 es bastante más pequeño que el cache L2 en un Core 2 pero sigue estando en los parámetros aceptables como para no hacer decisiones de gran impacto basados en el tamaño del cache L2. En general podemos decir que se va a poder asignar un core a cada subsistema aunque en este caso hay que tener más cuidado al momento de manejar la información y el orden de ejecución por las consideraciones ya mencionadas.

Por último vemos el Cell Broadband Engine de Sony, Toshiba e IBM. Este es un microprocesador con un “Power Processor Element” (“PPE”) que es encargado de controlar seis u ocho “Synergistic Processing Elements” (“SPE”). El PPE tiene un cache L1 de 64 KB y un cache L2 de 512 KB, dos hilos de ejecución por hardware, este core esta hecho principalmente para controlar los SPEs y realizar operaciones que no se subdividen bien en los SPEs. Cada SPE tiene 256 KB de memoria sin cache, tiene un modelo de ejecución en orden y un solo hilo en hardware [6, 7, 8]. Este es el microprocesador más complejo para programar. Los SPE tienen un cache muy chico lo que implica que hay que separar todos los trabajos de manera muy fina para mantener a todos los SPEs ocupado y en este caso definitivamente no es posible asignar un SPE a cada subsistema porque la combinación de cache pequeño y ejecución en orden crearían estragos desde el punto de vista de la performance. Por lo tanto hay que darle especial importancia al planificador de tareas o scheduler del motor de tal formas que los SPEs se mantengan ocupados pero sin estancarse en una tarea en particular [9].

Paralelismo
Teniendo en claro las diferencias en las diferentes plataformas es importante encontrar un modelo de paralelismo de manera que las plataformas sean bien aprovechadas sin crear una arquitectura difícil de mantener y expandir. Esto implica ignorar la posibilidad de agregar paralelismo función por función ya que las posibles ganancias de performance son muy limitadas [10]. Por otro lado tenemos que tener en cuenta que la interacción entre los diferentes subsistemas en un motor 3D es constante por lo tanto no es posible simplemente separar cada subsistema en diferentes cores como si estuvieran en el vacio (Figura 1). Dada la naturaleza de las aplicaciones interactivas hay que buscar soluciones de paralelismo que permitan la interacción entre los diferentes subsistemas sin que esto implique excesiva complejidad en mantenimiento y costo en performance.

Figura 1
Figura 1: Ejemplo de cómo es la interacción entre diferentes subsistemas de un motor 3D para aplicaciones interactivas. Como se puede ver hay dependencia entre los diferentes subsistemas hasta el momento de renderizar el video o sonido.

Uno de los modelos de paralelismo posibles implica en mantener el modelo del bucle principal de un motor 3D para microprocesadores con un solo core y simplemente paralelizar los subsistemas que no interactúan entre si [11]. Por ejemplo si tenemos un subsistema simulador de partícula entonces lo podemos mantener paralelo a la inteligencia artificial y ejecutar de manera simultánea en diferentes cores los dos subsistemas (Figura 2). Pero este modelo tiene muy pocas aplicaciones en un motor 3D para aplicaciones interactivas ya que la cantidad de subsistemas que se pueden paralizar de esa manera son limitados. En este modelo procesadores como el Cell Broadband Engine quedan esperando instrucciones para ejecutar en los diferentes SPEs pero en definitiva el 80 por ciento de los SPEs van a quedar libres. Este modelo tampoco permite da control sobre cómo se van a utilizar los diferentes cores.

Figura 2
Figura 2: Ejemplo de paralización de subsistemas independientes entre sí. En este caso solo la inteligencia artificial y el simulador de partículas son independientes entre sí por lo tanto se ejecutan de manera paralela. Pero el resto del bucle permanece en un solo core tomando la mayoría del tiempo de ejecución.

El Chromaticity Engine utiliza un modelo hibrido de paralelismo donde los subsistemas se actualizan de manera totalmente paralela siempre trabajando con la última información disponible del resto de los subsistemas, y a la vez dentro de cada subsistema otro nivel de paralelismo es posible a través de la actualización de objetos independientes dentro del subsistema en sí.

En el primer nivel de paralelismo se busca tener todos los subsistemas sean de ejecución independiente del resto de los subsistemas (Figura 3). La dependencia entre los diferentes subsistemas sigue existiendo, pero en vez de que un subsistema termine de actualizarse para pasar al siguiente, todos los subsistemas se actualizan en paralelo basado en la última información disponible. El gran beneficio de paralelizar de esta manera el motor es que se puede escalar a procesadores con múltiples cores sin problemas, es posible mantener cada subsistema en su propio core.

Figura 3
Figura 3: Ejemplo del primer nivel de paralelismo en el Chromaticity Engine. En este caso podemos ver que los diferentes subsistemas están en cores diferentes y corren de manera totalmente independiente. Al momento de actualizarse cada subsistema, la última información disponible de cada subsistema se obtiene y se utiliza.

El segundo nivel de paralelismo va al nivel de los subsistemas en sí. Dentro de un subsistema ciertas operaciones no dependen entre si y por lo tanto es posible realizar estas operaciones en paralelo (Figura 4). Por ejemplo si tenemos un subsistema de animación que va a modificar la geometría de los objetos dinámicos en una escena, es posible animar esos dos objetos de manera paralela ya que un objeto no depende del otro. Este modelo de paralelismo no es muy útil en una arquitectura como la del Intel Core 2 ya que la cantidad de cores es igual o menor que la cantidad de subsistemas en un motor en la mayoría de los casos. Pero en cambio en un microprocesador como el Cell Broadband Engine este modelo es muy importante ya que los SPEs pueden manejar muy poca información y hay varios SPEs.

Figura 4
Figura 4: Ejemplo del segundo nivel de paralelismo en el Chromaticity Engine. En este caso la inteligencia artificial global y la inteligencia artificial de dos objetos (por ejemplo, 3 personajes en la escena) se actualizan de manera paralela. Los objetos no van a actualizar siempre en el mismo core necesariamente.

Una característica que todas las plataforma van a compartir desde el punto de vista de la ejecución paralela tiene que ver con la necesidad de crear puntos de sincronización cuando la ejecución de uno o más subsistemas va mas rápido o más lento que el resto de los subsistemas. Por ejemplo si el administrador de escena no actualiza lo suficientemente rápido la posición de un generador de sonidos entonces al momento de renderizar el sonido la salida va a ser totalmente inapropiada. Por eso es necesario definir un umbral de cuadros antes de crear un punto de sincronización para que todo vuelva a estar sincronizado.

Scheduler o planificador de tareas
Dejar que cada subsistema decida que core utilizar seria una muy mala decisión de diseño. La razón es que, o el subsistema no tiene información suficiente como para tomar una buena decisión sobre que core utilizar, o la lógica sobre que hilo usar se repite a través de cada subsistema creando un gran problema de mantenimiento y performance. Por esa razón en el Chromaticity Engine hay un scheduler o planificador que se encarga de manejar todas las operaciones que tienen que ser ejecutadas en los diferentes hilos. El scheduler va a ser diferente para cada tipo de microprocesador porque por ejemplo no sería efectivo aplicar las mismas políticas de scheduling basado en un Core 2 a un Cell con ocho SPEs. En el caso del Core 2 se puede dejar un o un par de subsistemas en cada hilo y obtener una performance más que aceptable, pero en el caso del Cell el scheduler va a tener un trabajo más complejo al ir asignando diferentes SPEs a cada mini proceso en cada subsistema. También hay que tener en cuenta que el scheduler no puede crear y destruir hilos constantemente ya que bajo ninguna de las plataformas la creación y destrucción de hilos es barata. Y con respecto a la destrucción de hilos, un hilo nunca tiene que ser matado sino que ese hilo tiene que cometer un “suicidio” porque matar un hilo es una de las operaciones más costosas en todas las plataformas [12, 13]. En caso de ser necesario mantener un solo modelo de paralelismo para todas las plataformas, entonces es necesario seguir el modelo para el Cell ya que es el denominador común.

La política de scheduling para el Intel Core 2 consiste en tener los subsistemas balanceados en los diferentes hilos. Para hacer el balanceo correcto es necesario recolectar métricas de los diferentes subsistemas y hay que acomodar los subsistemas en los diferentes hilos. Uno de los problemas para lograr el balanceo correcto a través de los diferentes cores es que no hay forma de especificar definitivamente en que core correr un hilo. En Windows se puede sugerir que core usar usando la función SetThreadAffinityMask pero esto no asegura que efectivamente un hilo se ejecute en el core que queremos. Pero incluso esta práctica no es efectiva porque el entorno donde se usan los Intel Core 2 hay muchos hilos en ejecución y por lo tanto es mucho mejor dejar al scheduler de Windows manejar a que core asignar cada hilo.

La política de scheduling para el Xenon es bastante similar a la del Intel Core 2, pero hay algunas diferencias. Primero es necesario asignar específicamente que hilo en hardware usar de lo contrario todos los hilos que creamos van a estar ejecutándose en el mismo hilo en hardware del hilo que lo creo. Esto se hace usando la función XSetThreadProcessor con el argumento 0 o 1 para el core uno, 2 o 3 para el core dos, y 4 o 5 para el core tres. Salvando esta diferencia, el scheduler va a hacer básicamente lo mismo que hace para el Intel Core 2. Los subsistemas van en hilos en hardware, cualquier hilo extra puede ser insertado en cualquiera de los cores pero hay que balancear la utilización de cores.

Por último la política de scheduling para el Cell es totalmente diferente y mucho más compleja. En este caso los trabajos enviados a cada SPE van a tener que ser mucho más pequeños que mandar todo lo que hace un subsistema. Por lo tanto el scheduler va a residir en el PPE y se va a tener que encargar de que el motor use todos los SPEs. Para hacer esto el scheduler va a necesitar información en tiempo real del tiempo de ejecución de un hilo en cada SPE. Básicamente el modelo a seguir un modelo mostrado en [14] donde el scheduler tiene un queue FIFO (First In First Out) de trabajos donde los trabajos se van ejecutando en los SPEs con menos carga (Figura 5). Es muy importante que los trabajos que agreguemos al queue sean lo más atómicos posible. La razón para esto reside en el cache y capacidad de procesamiento de los SPEs. Como se vio en la sección de microprocesadores los SPEs tienen un cache pequeño y además las instrucciones se ejecutan de manera ordenada por lo que ejecutar trabajos grandes causa problemas de performance rápidamente. Lo mejor es tener los trabajos más pequeños posibles de tal manera que el SPE se libere de trabajo lo antes posible.

Figura 5
Figura 5: Ejemplo del sistema de scheduling del Chromaticity Engine. El PPE va mandando los diferentes trabajos que tiene en el queue según van completando en los SPEs.

Portabilidad y Mantenimiento
Mantener un motor 3D para múltiples plataformas es una tarea complicada en sí mismo, por lo tanto es sumamente importante diseñar el motor teniendo en cuenta la portabilidad y mantenimiento. Los entornos de desarrollo y APIs disponibles para las diferentes arquitecturas de microprocesadores que analizamos son diferentes entre sí. Por lo tanto si aspiramos a mantener la cantidad de versiones del motor 3D al mínimo es necesario crear un diseño de motor donde el código especifico para cada plataforma se mantenga lo mas separado posible del resto del motor. Por ejemplo no sería bueno tener diferentes forks o usar sistemas de defines simplemente para crear un hilo de ejecución nuevo simplemente porque las plataformas usan APIs diferentes. Es necesario abstraer todas las operaciones específicas de tal manera que el scheduler pueda crear un hilo de ejecución de manera transparente sin necesidad de saber cómo crear este hilo en la plataforma donde el motor 3D se está utilizando. Cuando todo el código esta abstraído de una manera modular es más fácil mantener una sola versión del motor, portar a otras plataformas, e incluso hace testing unitario. Obviamente esto implica tener en cuenta las diferentes APIs para las diferentes plataformas, por eso por ejemplo el soporte para la creación de hilos de ejecución se baso en Boost Threads [15]. El resultado no es una clase que soporta todo lo que una librería tendría sino que es el conjunto de operaciones mínimas para obtener los resultados deseados en todas las plataformas. Una vez que tenemos todo abstraído el resto del motor va utilizar esta capa y en ninguno de los casos va a utilizar funciones específicas a una plataforma.

Un aspecto importante a tener el cuenta es que dada la ejecución en orden del Xenon como el Cell no se va a poder usar polimorfismo para abstraer todo ya que el costo es bastante algo y en realidad no agrega nada útil para el usuario. Por lo tanto es necesario definir una manera de crear y mantener una capa intermedia que todos los modelos de creación de threads para que sea fácil de mantener. Pero a la vez, todo esto se tiene que resolver en tiempo de compilación y no en tiempo de ejecución usando funciones virtuales.

Resultados
El Chromaticity Engine fue testeado bajo diferentes plataformas aunque no pudo ser probado en todas las plataformas que se nombraron. Para probar la performance se creó una escena con 524,288 objetos dinámicos cada uno con 18 triángulos. En ningún momento se renderizo ni video ni sonido para evitar influenciar los resultados con valores que fluctúan basados en el GPU. El motor en este caso tenía que mantener un grafo de escena con toda la geometría de la escena y además el motor realizaba consultas a un octree que subdividía todo el espacio.

En la versión de PC el motor fue probado en Windows Vista en un Core 2 Duo E6700, Core 2 Quad QX6600 y AMD Athlon 64 X2 6000+. En estos casos el ganador fue el Core 2 Quad QX6600 por el uso de los cuatro cores (Figura 6). Como se menciono anteriormente, no es posible especificar con seguridad que core utilizaron cada hilo creado.

Figura 6
Figura 6: Performance de los diferentes microprocesadores en Hertz. En azul es la performance del motor corriendo con múltiples hilos, y el rojo el motor corriendo en un solo hilo.

Las ganancias en velocidad fueron notables comparado con el motor corriendo en un solo core con un solo hilo. Como se ve la velocidad en un solo hilo llega a un techo como [10] argumenta.

En la versión para el Cell Broadband Engine, el motor se corrió en una PlayStation 3 sobre Yellow Dog Linux 5.0. En este caso la diferencia entre correr usando un hilo en el PPE y un SPE, contra usar dos hilos en el PPE y los SPEs fue notable. Esto tiene que ver con el hecho de que los SPEs son sumamente especializados y con ciertas limitaciones.

Figura 7
Figura 7: Performance del PlayStation 3 Cell en Hertz. En azul es la performance del motor corriendo en el PPE y los SPEs, y el rojo el motor corriendo en un solo hilo en el PPE y usando un solo SPE.

En la versión para el Xenon no pudimos probar realmente la performance ya que para desarrollar en el Xenon es necesario tener el XDK de Microsoft que esta solo disponible para los estudios de video juegos y los creadores de middleware.

Conclusión
El Chromaticity Engine es un motor 3D que fue escrito en respuesta a los cambios en las plataformas donde tiene que ejecutarse. El cambio fundamental se produjo en los microprocesadores utilizados en estas plataformas. Se paso de disponer un solo hilo en hardware a disponer como mínimo un hilo en hardware por core con dos cores. No solo esto sino que los cores mantuvieron la misma velocidad, esto implica que ya no vamos a obtener más ganancias de performances usando un solo hilo [11]. Por eso si hay que escribir un motor 3D para aplicaciones visuales es necesario crear una arquitectura multihilo que sea fácil de mantener y portar a diferentes plataformas. Gracias a los modelos de paralización y la arquitectura expuesta en si es posible escribir motores 3D que aprovechan las arquitecturas de los diferentes microprocesadores sin necesariamente crear un motor sumamente complejo y complicado de mantener.

A futuro va a ser necesario validar mejor la arquitectura ya que la arquitectura expuesta fue probada en una pequeña cantidad de plataformas. En especial hay que analizar el trabajo desde el punto de vista de las plataformas para visualización científica donde las necesidades de visualización no son necesariamente diferentes pero las plataformas donde corren son totalmente diferentes.

Referencias
[1] O. Wechsler. Inside Intel® Core™ Microarchitecture: Setting new standards for energy-efficient performance, 2006.
[2] J. Doweck. Inside Intel® Core™ Microarchitecture and Smart Memory Access: An in-depth look at Intel innovations for accelerating execution of memory-related instructions, 2006.
[3] R.M. Ramanathan. Intel® Multi-Core Processors: Making the move to Quad-Core and beyond, 2006.
[4] J. Brown. Application-customized CPU Design: The Microsoft Xbox 360 CPU story, 2005.
[5] J. Andrews, N. Baker. Xbox 360 System Architecture. IEEE Micro Volume 26, Issue 2, 2006.
[6] M. Gschwind, H. P. Hofstee, B. Flachs, M. Hopkins, Y. Watanabe, T. Yamazaki. Synergistic Processing in Cell’s Multicore Architecture. IEEE Micro Volume 26, Issue 2.
[7] J.A. Kahle. Introduction to the Cell Multiprocessor, 2005.
[8] D. Pham. The Design and Implementation of a First Generation Cell Processor, 2005.
[9] D.A. Brokenshire. Maximizing the Power of the Cell Broadband Engine Processor: 25 tips to optimal application performance, 2006.
[10] H. Sutter. A Fundamental Turn Toward Concurrency in Software. Dr. Dobb’s Journal, 2005.
[11] A. El Rhalibi, D. England, S. Costa. Game Engineering for a Multiprocessor Architecture, 2005.
[12] B. Dawson. Coding For Multiple Cores on Xbox 360 and Microsoft Windows, 2006.
[13] Cell Broadband Engine Programming Handbook, 2006.
[14] D. Mallinson, M. DeLoura. CELL: A New Platform for Digital Entertainment, 2005.
[15] W.E. Kempf. The Boost C++ Libraries: Boost Threads, 2003.

Octubre 4, 2006

Herramientas.

Archivado en: General — Pablo Zurita @ 8:01 pm

Desde esta generación de motores en tiempo real nos podemos dar cuenta que ya no es posible diferenciaros a nivel técnico. La llegada de GPUs programables con lenguajes de alto nivel nos pone a todos en una situación similar. Escribiendo shaders y manipulando sus parámetros logramos obtener el aspecto visual de casi cualquier aplicación creadas por programadores conocidos como John Carmack o Tim Sweeney. Ya no hay necesidad de crear complicadas estructuras de datos, hacks complicados, y demás para lograr un aspecto visual definido. Esto demuestra un par de cosas. Primero, nunca fue tan fácil trabajar en computación grafica. Y segundo, para diferencia nuestro producto del resto vamos a depender en nuestros artistas y por consecuencia de ello, vamos a depender en las herramientas que les demos a nuestros artistas. Por eso va a ser muy importante darles herramientas que sean flexibles, fáciles de usar, y hacer todo en un tiempo razonable.

En el pasado estábamos acostumbrados a crear herramientas muy limitadas porque los recursos eran limitados. Por ejemplo en la era del Quake 2 tenias editores simples como el Qoole para la edición de geometría, las texturas eran de 256 colores creadas con editores como Wally, y a nivel técnico el aspecto visual era determinado por una simple modulación entre las texturas difusas y los lightmaps generados por q2rad. Ahora con los GPUs programables y su gran velocidad ya no hay necesidad de limitar la tecnología y los artistas. Lentamente con el progreso de los GPUs fuimos dejando Wally por editores como Photoshop, los editores de geometría se volvieron más complejos, y los sistemas de materiales e iluminación pasaron a ser más extensos y a tener en cuenta otros componentes como nivel de especularidad y demás. Ahora nos encontramos con la posibilidad de mostrar mucha geometría, tener texturas de alta resolución (o sintetizar texturas si así lo deseamos), y los sistemas de materiales e iluminación pueden ser tan diferentes y complejos como los shaders que podemos escribir. Por eso tenemos que crear herramientas que sean flexibles para que los artistas puedan utilizar todos los recursos disponibles. Estas herramientas tienen que ser tan flexibles como los shaders que podemos correr y la geometría que podemos mostrar. Además nuestras herramientas tienen que estar muy integradas con nuestro motor para que los artistas puedan ver exactamente como el usuario va a ver el contenido. De esa manera, por ejemplo los editores de shaders como FX Componer y RenderMonkey quedan descartados. Mirando desde el punto de vista de los materiales no es posible darles un editor de texto para que escriban shaders porque eso crearía un gran gasto de tiempo y dinero en capacitación. Es necesario crear un entorno donde el artista pueda aprovechar las capacidades de los shaders pero a la vez que sea una herramienta visual que el artista pueda entender. Una posible solución es lo que hizo Epic Games con su editor de materiales. Los materiales y shaders en el Unreal Engine 3 son creados en un editor por módulos como el Reaktor. Los módulos son creados por los programadores y los materiales son creados por el artista conectando esos módulos. Si uno crea módulos bien chicos y específicos es posible darle la chance al artista de crear shaders complejos sin necesidad de ver una sola línea de HLSL o glSlang. Y a la vez esto nos da más tiempo a los programadores porque todo el trabajo recae en los artistas.

Reaktor
Síntesis de sonido por módulos en Reaktor.Unreal Material Editor.
Editor de materiales del Unreal Engine 3. Diferentes módulos son conectados para obtener el resultado final.

Por otra parte, es sumamente importante mantener las herramientas fáciles de usar. Como todo programa la usabilidad es sumamente importante. Si creamos una herramienta complicada (como el Sapien de Bungie) vamos a perder productividad, vamos a frustrar a los artistas, y se nos va a ser innecesariamente complicado integrar nuevos artistas a nuestro proyecto. También es muy importante mantener la calidad de la usabilidad durante todo el proyecto porque las posibilidades de un gran cambio de paradigma en la creación de contenido es muy poco probable en el futuro cercano. Personalmente no preveo un cambio importante en la creación de contenido hasta que pasemos a usar voxels en vez de polígonos. También es importante mantener un buen estándar de usabilidad porque generalmente nos vemos forzados a incluir nuevos artistas a nuestros proyectos que ya están en marcha por lo tanto es necesario que este nuevo integrante de nuestro equipo como miembro productivo del proyecto lo antes posible. Teniendo en cuenta esto nos podemos dar cuenta que hay un problema en general en la mayoría de los engines y soluciones actuales, y es que los estudios no están aprovechando las aplicaciones que sus artistas ya conocen. Por ejemplo, en vez de aprovechar las aplicaciones de edición de geometría como 3ds Max hay estudios que todavía siguen haciendo editores propios como D3Radiant o UnrealEd. Algunas generaciones atrás estos editores eran necesarios porque la tecnología donde corrían también era limitada, pero ahora nuestros motores son muchos mas flexibles y al final terminamos duplicando funcionalidad que ya esta disponibles en otros editores como 3ds Max o SoftImage. Esto significa que no solo los programadores pierden tiempo duplicando funcionalidad ya disponible en otros editores, sino que también los artistas pierden tiempo en capacitación para usar las herramientas nuevas. En cambio podríamos utilizar los sistemas de plug-ins para expandir estos editores según nuestras necesidades. Obviamente si tomamos esta decisión tenemos que crear los plug-ins de tal manera que mantengan la forma de trabajo del editor que estamos expandiendo. Con respecto a la usabilidad recomiendo el libro About Face 2.0 : The Essentials of Interaction Design de Alan Cooper y Robert Reimann (ISBN 0764526413).

Bugie's Sapien
Sapien de Bungie. Un test de usabilidad demostraría que esta interfaz sobrecarga al usuario con información.

Justamente la necesidad de realizar todo en un tiempo razonable puede ser la razón por la que expandir un editor existente puede ser una opción buena. Crear las funciones más básicas de edición implica un tiempo razonable, y después los sistemas más avanzados pueden ser más complicados (como por ejemplo la edición de geometría con bezier patches). En cambio realizando plug-ins toda esa funcionalidad esta disponible, por lo que nosotros no tenemos que encargar de desarrollar la funcionalidad que es especifica a nuestra tecnología. Algunos podrán decir que esto puede ser malo porque realmente no tenes el control de las herramientas, pero en definitiva tenemos que tener en cuenta cuales son las prioridades. Otros tendrán problemas con los SDKs de los diferentes productos (por ejemplo el SDK de 3ds Max es horrible) pero hay que ver desde el punto de vista de los beneficios. Y aunque nos tome tiempo adaptarnos a un SDK y entorno al que no estamos acostumbrado, ese costo siempre va a ser menor que crear una herramienta nueva e instruir a los artistas sobre su uso. El único problema con hacer esto es que si pensas hacer un producto modificable como los mods para Quake, se vas a poner una barrera. Los costos de estas aplicaciones de edición son bastante altos para que un usuario estándar.

Septiembre 5, 2006

Chromaticity Engine y Managed Code.

Archivado en: General — Pablo Zurita @ 5:09 pm

Este es el podcast número cinco de 19 minutos. En este podcast hablo sobre el estado actual del engine, y también hago uno comentarios sobre managed versus unmanaged code.

Pueden bajar el podcast desde http://www.pablo-zurita.com.ar/podcasts/PodcastManaged.mp3

Chromaticity Engine - Material test

Agosto 16, 2006

Podcast - Chromaticity Engine Kernel + Carmack @ QuakeCon.

Archivado en: General — Pablo Zurita @ 5:23 pm

Acá esta el podcast numero cuatro de 35 minutos, en este podcast hablo sobre el estado actual de engine, el kernel del Chromaticity Engine y unos comentarios sobre el speech que dio John Carmack en QuakeCon 06.

Pueden bajar el podcast desde http://www.pablo-zurita.com.ar/podcasts/ChromaStateAgile.mp3

PD: Para los que se preguntaban porque no había actualizado el blog antes, es porque estuve dos semanas de vacaciones en Cutral-Co, Neuquen, Argentina.

Julio 21, 2006

Podcast – Estado del Chromaticity Engine y Test Driven Development.

Archivado en: General — Pablo Zurita @ 7:42 pm

Acá esta el tercer podcast de 12 minutos, en este podcast hablo sobre el estado actual de engine y sobre el uso de Test Driven Development.

Pueden bajar el podcast desde http://www.pablo-zurita.com.ar/podcasts/PodcastTDD.mp3

PD: UnitTest++.

Julio 8, 2006

Podcast - Chromaticity Engine Documentacion.

Archivado en: General — Pablo Zurita @ 9:35 pm

Después de un buen éxito con el primer podcast, aquí esta el segundo podcast de 18 minutos en la pagina. En este caso hablo sobre como pienso realizar la documentación del Chromaticity Engine.

Pueden bajar el podcast de http://www.pablo-zurita.com.ar/podcasts/ChromaDocu.mp3

Junio 28, 2006

Podcast – Chromaticity Engine.

Archivado en: General — Pablo Zurita @ 11:36 pm

Decidí hacer un podcast sobre mi nuevo engine. Es un podcast de 32 minutos donde hablo sobre el diseño del engine nuevo y las diferencias con el anterior. Como es el primer podcast en la pagina es una prueba pero si hay interés voy a seguir haciendo podcast porque para mi es muy fácil de grabar y me es mucho mas fácil que escribir.
Pueden bajar el podcast de http://www.pablo-zurita.com.ar/podcasts/ChromaPodcast.mp3

Chromaticity Engine - Sponza Atrium Wireframe

Junio 12, 2006

Tone Mapping y Blooms

Archivado en: General — Pablo Zurita @ 6:26 pm

Últimamente dos de los agregados mas importantes que se le han dado a las escenas en tiempo real es el tonemapping y blooming. Pero por varias razones se ha estado realizando de manera demasiado incorrecta desde el punto de vista psicofísico. La idea de este artículo es analizar de manera superficial como ocurren estos efectos en la vida real y después analizar como simular en tiempo real.

El ojo humano tiene dos formas de células fotosensitivas en la retina, los conos y los bastones. Aunque los dos son similares en estructura, los dos son bastante diferentes e igualmente importantes para la visión. Los bastones son altamente sensibles a la luz lo que les permite responder en situaciones de iluminación baja. Los conos necesitan más luz para responder pero permiten ver un rango de luz más alto (y entre otras cosas permiten ver colores). Tampoco nos podemos olvidar de la pupila que ajusta su tamaño constantemente para mantener la entra de luz en un nivel mas o menos constante. Analizando un poco cada célula fotosensitivas podemos darnos cuenta que podemos ver en un gran rango de intensidades de luz. Estos rangos se los puede dividir en tres regiones. La región fotopica con rango de 10^1 cd/m^2 a 18^8 cd/m^2 y que estimula a los conos. La región escotópica con rango de 10^-1 cd/m^2 a 10^-6 cd/m^2 y que estimula a los bastones. La tercera región es la región mesópica, la cual estimula a los conos y bastones. Pero una parte muy importante es la pupila porque mediante su cambio constante de tamaño permite que nuestro sistema responda a diferentes intensidades de luz. Por ejemplo a bajas intensidades de luz la pupila se abre lo mas posible para dejar entrar luz, en niveles altos de intensidad la pupila se cierra para que menos luz entre y así mantener la visión en una región en la que los conos y bastones puedan generar una respuesta. Pero hay veces que no importa que haga la pupila, no va a haber forma de generar la respuesta esperada, lo que quiere decir que o la intensidad es tan baja que no genera ninguna respuesta o la intensidad es tan alta que la sensibilidad de la visión baja mas y mas. Esto es conocido como compresión de respuesta y es la parte vital para determinar el sistema de tone mapping correcto. Pero hay otro factor, aunque la sensibilidad del ojo cambia de acuerdo a la iluminación predominante, este proceso no es instantáneo. A este mecanismo se le llama adaptación. El periodo de adaptación pueden llegar a ser de hasta 15 minutos en los conos y 40 minutos en los bastones para la adaptación total en un cambio severo de iluminación. Teniendo todo esto claro podemos empezar a hablar del tone mapping en si.

La idea de los operadores de tone mapping es pasar una escena de High Dynamic Range (HDR) a un medio Low Dynamic Range (LDR). Estos operadores se pueden dividir en diferentes categorías según como intentan llevar la información HDR a un medio LDR. Los operadores que aplican la misma función sobre toda una imagen son conocidos como operadores globales. Estos algoritmos tienden a ser muy buenos en performance por su simplicidad, pero tienden a perder mucha información sobretodo si hay un buen espectro de luminancia. Ejemplos de operadores globales son el operador de Ward, Schlick, Tumblin y Drago. Los operadores que dependen de la información de píxeles contiguos son llamadores operadores locales. Los operadores locales son los mejores ya que pueden proveer una gran compresión de la información HDR sin perder detalles. Ejemplos de operadores globales son el operador de Pattanaik, Jonson, Chiu, etc. Como en nuestro caso queremos agregar mas realidad a nuestras escenas simulando el comportamiento del ojo humano, esto quiere decir que vamos a necesitar un operador que tenga en cuenta los conos y bastones, y los tiempos de adaptación.

Independientemente del operador que decidas usar hay cierta información que hay que obtener siempre como por ejemplo la intensidad global. El problema es que la mayoría de los operadores están pensados para que corran en CPU, pero cuando tenemos que hacer eso en GPU el tema es un poco diferente porque las restricciones son diferentes. Por ejemplo obtener la intensidad global en CPU es tan fácil como hacer un loop, vas sumando la intensidad en cada píxel y después dividís por una constante para obtener la intensidad global. Pero en GPU es un poco mas complicado sobretodo por dos problemas, primero es demasiado intensivo calcular la intensidad global en cada fragment (porque se ejecuta en un fragment shader), y segundo no podemos hacer un fetch del fragment anterior o posterior. Veamos por ejemplo como obtener la luminancia global y máxima de un frame. Teniendo en cuenta las restricciones anteriores, la forma de obtener la luminancia máxima y la global consiste en usar varios FBOs de diferentes resoluciones y ejecutar un shaders para obtener el valor. Podemos empezar con nuestra escena renderizada en un FBO de 512×512, de ahí lo que hacemos es usar el siguiente shader y poner el contenido en un FBO de 256×256:

varying vec2 vTexCoord;//Screen
uniform sampler2D alb_screenfbo; //FBO texture.
uniform float alb_screenfbo_width; //FBO Width.

void main (void)
{
vec4 finalCalc;
vec2 uvCoords;
float maxLum;
float avLum;
float currentLum;

uvCoords.x = vTexCoord.x - 0.5 / alb_screenfbo_width;
uvCoords.y = vTexCoord.y - 0.5 / alb_screenfbo_width;
currentLum = (0.30 * texture2D(alb_screenfbo, uvCoords).x) + (0.59 * texture2D(alb_screenfbo, uvCoords).y) + (0.11 * texture2D(alb_screenfbo, uvCoords).z);
avLum = log(currentLum + 1e-4);
maxLum = currentLum;

uvCoords.x = vTexCoord.x - 0.5 / alb_screenfbo_width;
uvCoords.y = vTexCoord.y + 0.5 / alb_screenfbo_width;
currentLum = (0.30 * texture2D(alb_screenfbo, uvCoords).x) + (0.59 * texture2D(alb_screenfbo, uvCoords).y) + (0.11 * texture2D(alb_screenfbo, uvCoords).z);
avLum = log(currentLum + 1e-4);
if(maxLum < currentLum)
maxLum = currentLum;

uvCoords.x = vTexCoord.x + 0.5 / alb_screenfbo_width;
uvCoords.y = vTexCoord.y - 0.5 / alb_screenfbo_width;
currentLum = (0.30 * texture2D(alb_screenfbo, uvCoords).x) + (0.59 * texture2D(alb_screenfbo, uvCoords).y) + (0.11 * texture2D(alb_screenfbo, uvCoords).z);
avLum = log(currentLum + 1e-4);
if(maxLum < currentLum)
maxLum = currentLum;

uvCoords.x = vTexCoord.x + 0.5 / alb_screenfbo_width;
uvCoords.y = vTexCoord.y + 0.5 / alb_screenfbo_width;
currentLum = (0.30 * texture2D(alb_screenfbo, uvCoords).x) + (0.59 * texture2D(alb_screenfbo, uvCoords).y) + (0.11 * texture2D(alb_screenfbo, uvCoords).z);
avLum = log(currentLum + 1e-4);
if(maxLum < currentLum)
maxLum = currentLum;

avLum = exp(avLum / (alb_screenfbo_width * alb_screenfbo_width));
finalCalc.x = avLum;
finalCalc.y = maxLum;
gl_FragColor = finalCalc;
}

Como se ve en el shader lo que hacemos es obtener el obtener la luminancia de los 4 fragments alrededor del sample. Esto lo hacemos varias veces hasta llegar a un FBO de 1×1, al final de todo el proceso vamos a tener la luminancia global en el canal red y la luminancia máxima en el canal green del FBO 1×1. Parte del proceso se puede ver en el siguiente diagrama.

HDR Chart

Ahora que tenemos nuestra escena lista podemos encargarnos del bloom. El bloom se ha hecho constantemente mal no tanto por el bloom en si sino por la información que usaban al momento de hacer el bloom. Lo correcto es que una vez que tenemos nuestra escena ajustada al nivel de luminancia, si hay algo que quedo fuera del rango LDR entonces desde esos fragments generamos el bloom (independientemente en cada canal). De esta manera nos diferenciamos con el modelo incorrecto, porque en general se tiende a hacer un bright-pass que no tiene nada que ver con la reacción del ojo y crean un blooming por todos lados cuando eso no pasa en la vida real. En vez de usar un bright-pass con una ecuación complicada y que no refleja lo que realmente pasa en la vida real, nuestro bright-pass simplemente consiste en los valores que están por encima de 1.0 después del tone mapping porque en ese momento los conos y bastones se vieron sobrepasados.

Bueno, espero que este artículo les ayude a elegir el operador de tone mapping apropiado, y también a hacer el blooming de manera correcta. Cualquier comentario sobre el tema lo pueden dejar aquí mismo y yo lo responderé a la brevedad.

Entradas siguientes »

Gestionado con WordPress