El antipatrón "desenrollando switchs"

Las técnicas de desenrollado de bulces las usan los compiladores para optimizar el pipeline de los procesadores para aprovechar los huecos de las unidades funcionales disponibles y evitar que éstas estén ociosas y aumentando así el paralelismo del código consiguiendo de esta forma reducir el CPI.

Sin embargo nos empeñamos en una y otra vez todo ese conocimiento con código como este:

enum Axis
{
    XAxis,
    YAxis,
    ZAxis,
};
// code earlier in the function ensure that
// "axis" is always a valid axis
int newPosition;
switch (axis)
{
case XAxis:
     newPosition = m_position[XAxis] + amount;
     if (newPosition < m_minPosition[XAxis])
         newPosition = m_minPosition[XAxis];
     if (newPosition > m_maxPosition[XAxis])
         newPosition = m_maxPosition[XAxis];
     m_position[XAxis] = amount;
break;
case YAxis:
     newPosition = m_position[YAxis] + amount;
     if (newPosition < m_minPosition[YAxis])
         newPosition = m_minPosition[YAxis];
     if (newPosition > m_maxPosition[YAxis])
         newPosition = m_maxPosition[YAxis];
     m_position[YAxis] = amount;
break;
case ZAxis:
     newPosition = m_position[ZAxis] + amount;
     if (newPosition < m_minPosition[ZAxis])
        newPosition = m_minPosition[ZAxis];
     if (newPosition > m_maxPosition[ZAxis])
        newPosition = m_maxPosition[ZAxis];
     m_position[ZAxis] = amount;
break;
}

(me pregunto cuantas veces he escrito código así)

Empujados por saber que el código específico es más rápido que el código de propósito general, algunas veces no nos damos cuenta de que este mismo trozo de código se podría haber escrito de esta manera:

newPosition = m_position[axis] + amount;
if (newPosition &lt; m_minPosition[axis])
   newPosition = m_minPosition[axis];
if (newPosition &gt; m_maxPosition[axis])
   newPosition = m_maxPosition[axis];
m_position[axis] = amount;

Lo malo del switch primero que vimos es que no es fácil distinguir rápidamente si es un «error» del típico «copy&paste que queda nada para salir y me tengo que ir a casa» o hay algo escondido y sutil que hace de este código un ejemplo de libro.

Espero que os sirva.

Juan María Laó Ramos

Aliasing en formas geométricas

Aquí tenéis la octava entrega de la serie sobre aliasing

En el post de Multisampling vimos que la GPU «era lenta», así que el procesado de triángulos también. Las formas curvas complejas se aproximaban por un pequeño número de triángulos, donde cada uno de ellos era lo suficientemente grande como para cubrir varios píxeles de la pantalla.

 Esto creó una situación extraña en los primeros artículos sobre multisampling que se centraban en cómo suavizar los dientes que le salían a los triángulos. Nos acostumbramos tanto a esa forma de ver gráficos que hasta nos parecía hermoso, sin siquiera pararnos a pensar «¡Hey, la cabeza de este tipo sólo tiene 5 triángulos!»

Pero el hardware mejoró. El número de triángulos subió. Un juego moderno usa entre 4000 y 6000 triángulos para un personaje.

Fijaos en la situación:

  • El personaje se pinta de manera que ocupa 1/8 del alto de la pantalla.
  • El juego se renderiza a 720p, así que la imagen resultante es de 90 píxeles de alto.
  • Es decir que se cubren unos 2000 píxeles de la pantalla.
  • Suponiendo que la mitad de los triángulos están detrás de la cara.
  • Tenemos 3000 triángulos en total, cubriendo tan sólo 2000 píxeles de la pantalla.

Con tanto detalle en nuestros modelos, la geometría facial es algo del pasado. En su lugar tenemos un nuevo problema: ¡Hemos sido Nyquistados!

Recordad: para evitar el aliasing debemos tomar al menos dos veces más muestras de salida según la frecuencia más alta de la señal de entrada. Pero resulta que tenemos menos píxeles de salida que triángulos de entrada. Es bastante obvio que esto va a causar aliasing. Con menos píxeles que triángulos, algunos triángulos no aparecerán en la imagen de salida:

Juanma Pixelado

A medida que el objeto se mueva, los triángulos que sean afortunados y tengan un pixel irán cambiando aleatoriamente, así que la imagen se irá distorsionando a medida que los triángulos vayan apareciendo y desapareciendo.

En realidad existe un truco para suavizar el número de triángulos que un modelo debe contener. Para la mejor calidad, querremos que cada triángulo cubra exactamente dos píxeles de pantalla. Si los triángulos son más pequeños que esto, cruzaremos el límite de Nyquist y aparecerá el aliasing. Si son mayores, tu silueta saldrá «poligonizada».

NO es totalmente intuitivo que el truco sean dos píxeles por triángulo en lugar de uno, pero es verdad. Imaginad un círculo que es aproximado por una serie de segmentos de líneas. A medida que incrementamos el número de segmentos, el círculo se vuelve más y más perfecto. El momento en el que cada segmento va alcanza la longitud de dos píxeles, el círculo se convierte en perfecto. Por lo que añadir más segmentos a partir de aquí no hará ninguna mejora sobre la curva del círculo.

No es práctico tampoco mantener todos los triángulos con el mismo tamaño en la pantalla, ya que nuestro modelo se tiene que pintar a diferentes tamaños a medida que el jugador se mueve por el mundo.

Así que ¿cómo podemos pintar una geometría con muchos detalles sin tener aliasing?

Multisampling (o supersampling si podemos permitírnoslo) nos ayuda a alejar de nosotros el límite de Nyquist, pero sólo con eso no es suficiente para evitar el aliasing en geometrías.

Normalmaps puede ser una técnica poderosa. Tenemos bastantes opciones para evitar el aliasing cuando reconstruimos texturas, de manera que cada vez que podamos tendremos qie eliminar los detalles de nuestra geometría y reemplazarla con un normalmap, que nos ayudará a controlar el aliasing. Solemos pensar que estas técnicas de normalmaps son tan sólo una optimización del rendimiento (reemplazando geometrías caras con una textura) pero también pueden mejorar la calidad visual.

Por último, es importante considerar que el nivel de detalle del modelo puede variar. Cuando el objeto está muy lejos, cambiar un modelo de 6000 triángulos por uno más simple de 1000 o 500 triángulos es una opción que no sólo aumentará el rendimiento, sino que reducirá el efecto de aliasing en geometrías.

Moraleja: cuando hablamos de números de triángulos, más no siempre es lo mejor. Ten cuidado de no pintar más triángulos de los píxeles que ves en pantalla. Ese camino lleva al mundo del aliasing.

Artículo original.

P/D: Síguieme en twitter: @juanlao

Texturas base sin aliasing

Aquí tenéis la octava entrega de la serie sobre aliasing. Quizás sea demasiado obvio mencionarlo, pero aunque apliquemos los filtros bilineales o las técnicas más inteligentes, no servirán de nada si las imágenes originales tienen problemas de aliasing. Continúa leyendo Texturas base sin aliasing

Cómo obtener una señal sin aliasing

Éste es el cuarto post de la serie sobre aliasing. En el segundo post de la serie vimos porqué se producía aliasing cuando digitalizamos señales. Los peores escenarios ocurren cuando se reduce el número de muestras para representar la señal, o cuando la propia señal incluye datos a frecuencia muy altas. En concreto, hay un valor mágico conocido como el teorema de Nyquist, que indica la mitad de la velocidad de muestreo que debemos usar. Si la señal de origen tiene información a una mayor frecuencia que esa medida, tendremos problemas de aliasing.
Continúa leyendo Cómo obtener una señal sin aliasing

El juego de la vida en XNA para Windows Phone

Me ha parecido muy interesante el post de Shawn Hargreaves preguntándose si el SpriteBatch es una máquina de Turing, implementado para demostrarlo el juego de la vida y lo ha hecho en http://blogs.msdn.com/b/shawnhar/archive/2011/12/29/is-spritebatch-turing-complete.aspx.

Aquí tenéis la traducción: Continúa leyendo El juego de la vida en XNA para Windows Phone

¿Que significa que mi programa termine con el mensaje "This application has requested the runtime to terminate it in an unusual way"?

Estamos ejecutando nuestro programa, y de repente sale el mensaje «This application has requested Runtime to terminate in an unusual way». ¿Que ha pasado?
El mensaje lo imprime la función abort del runtime de C.
Un programa puede llamar a abort explícitamente, o puede ser llamado por el runtime.
  • La macro assert llama a abort cuando ocurre una aserción.
  • Por defecto, la función terminate llama a abort.

El estándar de C++ indica las condiciones bajo las que se llama a terminate, es una lista muy larga y no vamos a repetirla aquí. Para ver esa lista consulta tu copia favorita de estándares de C++ para verla. (la razón más común para este problema es que se ha lanzado una excepción y no se ha capturado).

Espero que sirva.
Juan María Laó Ramos.

Determinar programáticamente si un lenguaje es LTR ó RTL

LTR= Left To Right y RTL = Right To Left.

Dado un LCID en particular, ¿cómo podríamos saber dado un lenguaje determinado si se escribe de izquierda a derecha o de derecha a izquierda? Continúa leyendo Determinar programáticamente si un lenguaje es LTR ó RTL

Si protegemos una escritura con una critical section, seguramente querreis proteger la lectura.

Es normal tener una critical section en proyectos que hacen escrituras concurrentes en variables o en una colección de variables, y si no lo haces ya tienes una pista de porqué se cae tu sistema ;).

Sin embargo, si protegemos una escritura con una critical section, seguramente también querremos proteger la lectura, ya que si no, la lectura también luchará contra la escritura a la hora de acceder a la variable de la discordia. Continúa leyendo Si protegemos una escritura con una critical section, seguramente querreis proteger la lectura.