Sprites, BOBs  y otras criaturas mágicas (IV): EL BLITTER (II)

 

Tras el capítulo anterior, en el que comentamos algunos conceptos previos necesarios, nos metemos ya de lleno en el funcionamiento del Blitter. Comenzaremos aclarando algunos términos que manejaremos durante este artículo y continuaremos analizando en detalle el funcionamiento interno del Blitter. Vamos al lío… Pero, antes, un refrescante mensaje de uno de nuestros patrocinadores:

 

La bebida de los más amigueros.

 

Antes de empezar

En este punto, aprovecho para recomendaros que volváis a echar un vistazo a la primera y tercera entregas de esta serie. De todas formas, para los más vagos, aquí os dejo de nuevo la definición de BOB:

“BOB: se trata de elementos gráficos que se dibujan directamente sobre la memoria de vídeo con la ayuda del Blitter, uno de los coprocesadores del Amiga. En el caso de la máquina de Commodore, dicha zona de memoria se organiza en forma de bitplanes o planos de bits.”

Durante todo el artículo vamos a suponer que nuestros BOBs serán de 1 plano (2 colores) lo cual espero que simplifique en gran medida las explicaciones que vendrán a continuación.

 

Traducir coordenadas a dirección de memoria

Vamos a introducir otro concepto imprescindible para los siguientes apartados: cómo traducimos la posición de un BOB, dada por sus coordenadas (x, y), a la dirección de memoria del buffer de pantalla en la que tendríamos que copiar ese BOB con el Blitter para que apareciera en esa posición. En este punto hay que matizar que el Blitter del Amiga maneja words (o palabras), donde 1 word = 2 bytes = 16 bits.

Supongamos que nuestro buffer de pantalla, es decir, la zona de la memoria en la que “pintaremos” los gráficos empieza, por simplicidad, en la dirección 0 de la memoria. Supongamos también que nuestra pantalla tendrá 320 pixels de ancho por 256 de alto. Como trabajaremos con gráficos de un solo plano, cada bit de nuestro buffer de memoria se corresponderá directamente con un pixel de la pantalla. Sabiendo que 1 byte son 8 bits (o pixels en nuestro caso), cada línea de la pantalla serían 40 bytes en nuestro buffer: 320 / 8 = 40. Como curiosidad, si tenemos 256 líneas de alto, el tamaño total de nuestro buffer de pantalla sería: (320 / 8) * 256 = 10240 bytes = 10 Kb. Para que os hagáis una idea, un juego en 32 colores que esté en 320 x 256 necesitaría 5 veces más memoria para el buffer de pantalla, es decir, 50 Kb de los 512 disponibles en un Amiga 500. Veamos una representación gráfica de nuestro buffer de pantalla y el detalle de los 4 primeros bytes de éste:

 

Mapa de nuestro hipotético buffer de memoria.

 

En un hipotético juego, por ejemplo, todos nuestros elementos gráficos (la nave del jugador, los enemigos, las balas, etc.) tendrán almacenada su posición, en forma de coordenadas x (horizontal) e y (vertical). Al Blitter no podemos simplemente pasarle las coordenadas del objeto a dibujar, sino que tendremos que indicarle la dirección concreta de nuestro buffer de pantalla en la que tiene que copiar el gráfico para que éste aparezca en la posición deseada. Para determinar esta dirección hemos de hacer el siguiente cálculo:

 

Dirección destino = DIRECCIÓN_INICIAL_BUFFER_DESTINO + (BOB_X / 8) + (BOB_Y * BYTES_POR_LÍNEA)

 

Supongamos que tenemos un BOB (la nave del jugador, por ejemplo) que queremos pintar en la posición x = 16, y = 124. La dirección de destino que habría que indicarle al Blitter para que la nave apareciera en esa posición de la pantalla sería:

 

Dirección destino = 0 + (16 / 8) + (124 * 40) = 0 + 2 + 4960 = 4962

 

Tres fuentes y un destino

Como ya vimos en la entrega anterior, la función principal del Blitter (y la única que vamos a comentar en detalle en esta serie de artículos) es copiar datos de una zona a otra de la memoria chip, principalmente, gráficos. Para ello, el Blitter dispone de 4 canales de DMA, 3 de los cuales pueden ser usados como fuente de datos (A, B y C) y sólo 1 de ellos como destino (D). Un canal de DMA, en el caso del Amiga (y simplificando mucho),  podría definirse como una vía de comunicación a través de la cual los custom chips pueden leer de la memoria chip o escribir en ella. Para los blits más sencillos bastará con un canal de origen (A, B o C), para leer los datos del gráfico a dibujar, y el de destino (D) para escribir en el buffer de pantalla. Para blits más complicados (en caso de necesitar aplicar una máscara al gráfico original, por ejemplo) necesitaremos los 3 canales de origen y, una vez más, el de destino apuntando a una dirección concreta del buffer de pantalla.

Durante la configuración del blit, no sólo debemos indicar al Blitter qué canales queremos usar, sino como combinarlos entre sí mediante operaciones lógicas (AND y OR) para dar lugar a la salida, que será escrita en memoria a través del canal D.

 

Desplazamientos y máscaras

Los más aplicados seguro que recordarán que en el capítulo anterior mencioné una limitación del Blitter, que consiste en que éste sólo es capaz de leer y escribir en direcciones pares de la memoria chip. En nuestro caso, y para simplificar, las direcciones válidas serían: 0, 2, 4, 6, 8, etc., es decir, cualquiera que coincida con un número de byte par.  Cuando en el ejemplo anterior pintábamos la nave en la posición (x = 16, y = 124), la dirección de destino resultaba ser la 4962 que, al ser par, no supondría ningún problema para el Blitter. Pero ¿y si quisiéramos pintarla en la posición x = 12, y = 124, por ejemplo? La dirección de memoria en el buffer de pantalla se calcularía como antes:

 

Dirección destino = 0 + (12 / 8) + (124 * 40) = 0 + 1 + 4960 = 4961

 

Es decir, estaríamos intentando pintar la nave en una dirección de memoria impar, lo cual no es posible. En estos casos, el Blitter asume como dirección de destino la inmediatamente anterior, que sí que será par, es decir, la 4960 en nuestro ejemplo. ¡Bueno! Pues solucionado… ¿o no? Si pintamos la nave en esa dirección, como puede verse en la imagen siguiente, ésta aparecería en la posición x = 0, y = 124, 12 pixels a la izquierda de lo que pretendíamos. Aquí entra en juego otra de las capacidades del Blitter: desplazar los datos de entrada (de los canales A y B) entre 0 y 15 bits, normalmente, a la derecha (aunque también es posible desplazarlos a la izquierda). En este caso concreto lo que tendríamos que indicarle al Blitter, por tanto, es que desplace los datos de entrada 12 bits a la derecha. En resumen:

  • Queremos pintar la imagen en la posición x = 12, y = 124, que corresponde a la dirección de memoria 4961.
  • Como el Blitter no es capaz de trabajar con direcciones impares la convierte a la dirección par inmediatamente anterior, es decir, la 4960.
  • Si pintáramos la imagen en dicha dirección de memoria ésta aparecería en la posición x = 0, y = 124. Para solucionar esto, y que el gráfico se posicione en x = 12, empleamos la capacidad del Blitter para desplazar la entrada, en este caso, 12 bits.

 

En el siguiente gráfico se pueden ver los bytes de nuestro buffer de pantalla que se verían afectados para los dos ejemplos vistos, correspondientes a las posiciones (16, 124), que coincide exactamente con una dirección par del buffer de pantalla, y (12, 124), que no cumple esa condición. Supondremos que el gráfico de la nave es de 16×16.

 

Dos blits de nuestra nave: uno coincidiendo en byte par y otro no.

 

Nótese que, en el segundo caso, la nave comenzaría a pintarse en el bit (pixel) 12, contenido en el byte 4961 del buffer, que pasa a ser el 4960 (+ desplazamiento de 12 pixels a la derecha) por la incapacidad del Blitter de emplear direcciones impares. Como el ancho de la nave son 16 píxeles, ésta llegaría hasta el pixel con x = 27, contenido en el byte 4962 de dicho buffer. Como el Blitter sólo es capaz de copiar regiones cuyo ancho sea múltiplo de 16 (es un word Blitter, como comentamos antes), esto nos obliga a realizar un blit de 2 palabras (32 pixels), en lugar de 1 (16 pixels) como bastaba en el primer caso. El segundo blit será, por tanto, más costoso de realizar que el primero. Algo similar sucedería en x = 17, es decir: por un sólo pixel de diferencia en la coordenada x, tendríamos un blit el doble de costoso.

El Blitter dispone de otro “súper-poder” que consiste en aplicar una máscara a los primeros y a los últimos 16 bits de cada fila de datos leídos a través del canal A, es decir, ignorar algunos de esos bits para que no formen parte ni de los desplazamientos, ni de las operaciones lógicas posteriores. En el primer ejemplo, no es necesario hacer uso de esta máscara, puesto que el blit que haremos será de 16 pixels de ancho, coincidiendo con el ancho de la nave. Por el contrario, en el segundo caso, sería necesario enmascarar ciertos bits para que no participaran en el resultado final, puesto que el ancho del blit (32 pixels) es superior al de la nave (16 pixels).

 

Size does matter

Estamos casi en condiciones de lanzar un blit ¡qué emoción! Para terminar vamos a ver cómo especificamos el tamaño del blit y describiremos brevemente el concepto de módulo.

Con lo que hemos visto hasta ahora, el Blitter debería ya saber la dirección de los datos de origen, la dirección de destino y el desplazamiento y la máscara a aplicar. El tamaño de los datos a mover lo especificaremos indicando, simplemente, el número de líneas de alto y el número de palabras (2 bytes = 16 bits) de cada línea. Debido a esta forma de especificar el ancho, es habitual que los gráficos que van a ser mostrados como BOBs se almacenen en disco reservando el ancho necesario para llegar al siguiente múltiplo de 16. Es decir, supongamos que la nave para nuestro juego es de 25 pixels de ancho. Lo normal, sería que esta imagen estuviera en un fichero (junto a otras) pero en una celda imaginaria de 32 pixels de ancho, puesto que 32 es el primer múltiplo de 16 mayor que 25.

El módulo, para terminar, indica el número de bytes necesarios para saltar de una línea de los datos, leídos o escritos por el Blitter, a la siguiente. Supongamos que nuestra nave de 25 pixels de ancho (pero almacenada como si tuviera 32) está en un fichero junto con otros BOBS, cuyo ancho total es de 320 pixels. Como 32 pixels son 4 bytes (32 / 8) y 320 son 40 bytes (320 / 8), el módulo se calcularía como 40 – 4 = 36. Cada vez que el Blitter lea una línea del gráfico de la nave ha de saltar los siguientes 36 bytes para acceder a la línea siguiente. Algo equivalente es aplicable al buffer de memoria sobre el que copiaremos los datos, de hecho, es posible especificar un valor diferente del módulo para cada uno de los 4 canales del Blitter (A, B, C y D).

 

En el próximo capítulo…

¡Uf! Las dos últimas entregas de esta serie han ido realmente cuesta arriba en lo que a dificultad de los contenidos se refiere… Para compensar, en el siguiente capítulo dejaremos de lado los conceptos farragosos y las largas explicaciones teóricas para dar paso a ejemplos concretos acerca de cómo hacer (y cómo no hacer) uso del Blitter en un juego de Amiga. ¡Hasta entonces!

 

 

 

Un artículo publicado por:

Fernando Cabrera (@fcabrera_77)
Ingeniero informático. Nostálgico del Spectrum, adorador del Amiga y aficionado a los videojuegos y a la retro-informática. Ahora también Colaborador desde mi sección para Commodore Spain. 

 

 

 

 

 

2 Comentarios sobre “Sprites, BOBs y otras criaturas mágicas (IV): EL BLITTER (II)

  1. Alberto

    Buen trabajo!
    No te olvides se scotch blitter….yo no puedo estar sin el!

    • Fernando Cabrera

      Gracias! 🙂 Jajaja, sí! Y sin dejar de lado el “blit naranja escribe fino, blit cristal…”, bueno, basta! xD

Deja un comentario

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

clear formSubmit