TONC: Curso programacion GBA III

  • En esta lección se explicarán los modos 'bitmap'. También se verá un ejemplo de lo que es posible realizar en el modo 3 y un poco del cambio de página de modo 4 (que permite una animación más suave). Por último se explicará como lidiar con los datos y la memoria en general, ya que la programación de la GBA es muy cercana al hardware es necesario saber estas cosas.


Contenido

El bitmap

  • Un 'bitmap' o mapa de bits es como si fuera una rejilla rectangular de pixeles coloreados. Para poder usar los 'bitmaps' en un programa primero debemos saber como están dispuestos en memoria. En la siguiente figura se puede apreciar la rejilla de pixeles (con unos numeritos) superpuesta en la imagen de Link.


Bitmap TONC.png


  • Un 'bitmap' es una matriz wxh (width=ancho;height=alto) de colores (o índices de colores), donde w es el número de columnas y h es el número de filas.
  • El eje de las y en la GBA apunta hacia abajo, por lo que el pixel (0,0) es el que está en la esquina superior izquierda de la imagen. En memoria, las lineas del "bitmap" están dispuestas de forma secuencial, por lo que tal y como vimos en las lecciones pasadas, el pixel (x,y) es el pixel (y*w+x).
  • En la figura de arriba el bitmap es de 24x24, a 8bpp (bits por pixel = 1 bytes). Los números en amarillo indican las localización en memoria.
  • Otro concepto importante es el pitch que se puede definir como el número de bytes en una scanline. Para imágenes a 8bpp, el 'pitch' por lo general es el mismo que el ancho de la imagen, pero para por ejemplo imágenes a 16bpp (2 bytes por pixel), el 'pitch' es el ancho elevado al cuadrado.

Los modos bitmap

  • Los modos 'bitmap' son los modos de video 3, 4 y 5. Para usarlo hay que poner 3, 4 o 5 en los bits bajos de REG_DISPCNT{0-2} y activar el fondo 2 (BG2). Los modos 'bitmap' son demasiado lentos para poder ser usados en la mayoría de juegos de la GBA. La única vez que será beneficiario usar los modos 'bitmap' será para pantallas muy estáticas o en pantallas muy dinámicas (juegos en 3D como el Starfox o el Doom).
modo ancho alto colores bpp tamaño cambio de pagina notas
3 240 160 32768 16 1x 12C00h No No usa la paleta. Dos bytes están asociados a cada pixel. No soporta transparencia. El 'bitmap' empieza o en la dirección 0x06000000.
4 240 160 256 8 2x 9600h Si Usa la paleta. Un byte asociados a cada pixel. El color 0 es transparente. El 'bitmap' empieza o en la dirección 0x06000000 o en 0x0600A000, dependiendo del bit 4 de REG_DISPCNT{4}.
5 160 128 32768 16 2x A000h Si No usa la paleta. Dos bytes están asociados a cada pixel. No soporta transparencia. El 'bitmap' empieza o en la dirección 0x06000000 o en 0x0600A000, dependiendo del bit 4 de REG_DISPCNT{4}.
  • Los modos 'bitmap' son 'utiles para aprender algunas funcionalidades básicas, pero para fines prácticos son mucho mejor los modos 'tiled'.
  • El principal problema de los modos 'bitmap' es la velocidad. Incluso el dibujar simples representaciones gráficas pueden llevar mucho tiempo, especialmente si no están bien implementadas.
  • A parte de la velocidad, los modos 'bitmap' solo usan un fondo y no tiene desplazamiento (scroll) por hardware. El modo 5 tiene cambio de página pero solo usa una parte de la pantalla.
  • Básicamente, los modos 'bitmap' no son para juegos. Están bien para hacer una introducción a las distintas secciones de la GBA debido a que es más fácil trabajar con ellos que con los modos 'tiled' pero no sirven para juegos debido a que la GBA simplemente no puede poner pixeles tan rápido.

El modo 3

  • Ya se ha visto en lecciones pasadas como dibujar pixeles en la pantalla en el modo 3, ahora llega el momento de dibujar lineas y rectángulos. Las lineas horizontales son faciles debido a que los pixeles están dispuestos memoria contiguamente. Las lineas verticales tambien son sencillas ya que si bien los pixeles no están el uno al lado del otro, tienen un desplazamiento fijo entre ellos (llamado pitch). Los rectángulos son básicamente multiples lineas horizontales.
  • Las lineas diagonales son algo más complicadas debido a varias razones. Afortunadamente ya tenemos resuletos los problemas que presentan gracias a que alguien se lo había figurado antes. Se usarán los algoritmos de Bresenham Midpoint.

Ejemplo

  • Puedes encontrarlo en el directorio /code/basic/m3_demo. En este ejemplo se pretende dibujar rectángulos, lineas y marcos en un lienzo a 16bpp (como en los modos 3 y 5). Algunos parámetros en el modo 3 que no cambian:
  • El puntero base al lienzo es dstBase = (u16*)MEM_VRAM).
  • El pitch es dstPitch = 240*2.
  • El resultado:


Ejemplo m3 demo TONC.png


  • El código:
#include <tonc.h>

int main()
{
	int ii, jj;

	REG_DISPCNT= DCNT_MODE3 | DCNT_BG2;

	// Rellena la pantalla de gris
	m3_fill(RGB15(12, 12, 14));

	// Rectángulos:
	m3_rect( 12,  8, 108,  72, CLR_RED);
	m3_rect(108, 72, 132,  88, CLR_LIME);
	m3_rect(132, 88, 228, 152, CLR_BLUE);

	// Marcos de rectángulos:
	m3_frame(132,  8, 228,  72, CLR_CYAN);
	m3_frame(109, 73, 131,  87, CLR_BLACK);
	m3_frame( 12, 88, 108, 152, CLR_YELLOW);

	// Lineas en el marco superior derecho:
	for(ii=0; ii<=8; ii++)
	{
		jj= 3*ii+7;
		m3_line(132+11*ii, 9, 226, 12+7*ii, RGB15(jj, 0, jj));
		m3_line(226-11*ii,70, 133, 69-7*ii, RGB15(jj, 0, jj));
	}

	// Lineas en el marco inferior izquierdo:
	for(ii=0; ii<=8; ii++)
	{
		jj= 3*ii+7;
		m3_line(15+11*ii, 88, 104-11*ii, 150, RGB15(0, jj, jj));
	}

	while(1);

	return 0;
}

Explicación del código:

  • La librería tonclib nos proporciona funciones optimizadas para el dibujo de primitivas en modo 3 como rectángulos, lineas, marcos, etc.
void M3_CLEAR ()
Limpia la pantalla del modo 3.
INLINE void m3_fill (COLOR clr)
Rellena el fondo del modo 3 con un color.

clr: color de 15 bits.

INLINE void m3_plot (int x, int y, COLOR clr)
Dibuja un pixel coloreado en la posición (x,y) de la pantalla en el modo 3.

x: posición horizontal.

y: posición vertical.

clr: color de 15 bits.

INLINE void m3_hline (int x1, int y, int x2, COLOR clr)
Dibuja una linea horizontal en la pantalla en el modo 3.

x1: posición horizontal del comienzo de la linea.

y: posición vertical de la linea.

x2: posición horizontal del final de la linea.

clr: color de 15 bits.

INLINE void m3_vline (int x, int y1, int y2, COLOR clr)
Dibuja una linea vertical en la pantalla en el modo 3.

x: posición horizontal de la linea.

y1: posición vertical del comienzo de la linea.

y2: posición vertical del final de la linea.

clr: color de 15 bits.

INLINE void m3_line (int x1, int y1, int x2, int y2, COLOR clr)
Dibuja una linea en la pantalla en el modo 3.

x1: posición horizontal del comienzo de la linea.

y1: posición vertical del comienzo de la linea.

x1: posición horizontal del final de la linea.

y2: posición vertical del final de la linea.

clr: color de 15 bits.

INLINE void m3_rect (int left, int top, int right, int bottom, COLOR clr)
Dibuja una rectangulo coloreado en la pantalla en el modo 3.

left: lado izquierdo del rectángulo.

top: lado superior del rectángulo.

right: lado derecho del rectángulo.

bottom: lado inferior del rectángulo.

clr: color de 15 bits.

INLINE void m3_frame (int left, int top, int right, int bottom, COLOR clr)
Dibuja una marco en la pantalla en el modo 3.

left: lado izquierdo del rectángulo.

top: lado superior del rectángulo.

right: lado derecho del rectángulo.

bottom: lado inferior del rectángulo.

clr: color de 15 bits.

El modo 4

  • El modo 4 es otro modo 'bitmap'. Al igual que el modo 3 tiene un 'frame-buffer' de 240x160 pero en vez de 16bpp (bits por pixel) utiliza 8bpp. Estos 8 bits son el indice a la paleta del fondo localizada en la posición 0500:0000. El color que se ve en la pantalla es el color encontrado en esa localización de la paleta.
  • Solo podemos tener 256 colores a la vez (frente a los 32678 en el caso de 15bpp). Aunque tenemos la ventaja de poder manipular los colores de muchos pixeles simplemente cambiando el color en la paleta. Una 'frame-buffer' de 8bpp toma la mitad de la memoria que con un buffer de 16bpp por lo que a parte de que es mas rapido llenarlo, también deja espacio para un segundo 'buffer', lo que permite el cambio de página.
  • Hay un gran inconveniente a la hora de usar el modo 4 debido a una limitación hardware. Con 8-bit por pixel debería tener sentido el mapear la VRAM como un array de bytes, pero ¡la VRAM no te permite escribir bytes! (no se pueden escribir en la VRAM trozos del tamaño de un byte). La escritura debe hacerse en trozos de 16-bit o 32-bit, ya que si se escribe en bytes en la VRAM (o en la PALRAM o en OAM), la mitad de la palabra a la que se accede terminará con ese byte en ambos bytes por lo que estarías poniendo dos pixeles a la vez.

Por lo que para poder poner un solo pixel necesitas leer la media palabra (16 bits) completa a la que se intenta acceder, enmascarar los bits que no quieres sobrescribir, insertar los pixeles y volver a escribirla.

  • El código:
#define M4_WIDTH    240     // Ancho en el modo 4
16 *vid_page= vid_mem;     // Puntero al actual 'frame buffer'

INLINE void m4_plot(int x, int y, u8 clrid)
{
    u16 *dst= &vid_page[(y*M4_WIDTH+x)/2];  // División por 2 debido al desajuste del puntero u8/u16
    if(x&1)
        *dst= (*dst& 0xFF) | (clrid<<8);    // pixel impar
    else
        *dst= (*dst&~0xFF) |  clrid;        // pixel par
}
  • Funciones para dibujar primitivas en el modo 4 (todas dibujan en el 'backbuffer', para dibujar en el vid_mem_front mirar las funciones sbmp8 de tonclib):
void M4_CLEAR ()
Limpia la pantalla del modo 4.
INLINE void m4_fill (u8 clrid)
Rellena el fondo del modo 4 con un color.

clrid: indice del color.

INLINE void m4_plot (int x, int y, u8 clrid)
Dibuja un pixel coloreado en la posición (x,y) de la pantalla en el modo 4.

x: posición horizontal.

y: posición vertical.

clrid: indice del color.

INLINE void m4_hline (int x1, int y, int x2, u8 clrid)
Dibuja una linea horizontal en la pantalla en el modo 4.

x1: posición horizontal del comienzo de la linea.

y: posición vertical de la linea.

x2: posición horizontal del final de la linea.

clrid: indice del color.

INLINE void m4_vline (int x, int y1, int y2, u8 clrid)
Dibuja una linea vertical en la pantalla en el modo 4.

x: posición horizontal de la linea.

y1: posición vertical del comienzo de la linea.

y2: posición vertical del final de la linea.

clrid: indice del color.

INLINE void m4_line (int x1, int y1, int x2, int y2, u8 clrid)
Dibuja una linea en la pantalla en el modo 4.

x1: posición horizontal del comienzo de la linea.

y1: posición vertical del comienzo de la linea.

x1: posición horizontal del final de la linea.

y2: posición vertical del final de la linea.

clrid: indice del color.

INLINE void m4_rect (int left, int top, int right, int bottom, u8 clrid)
Dibuja una rectangulo coloreado en la pantalla en el modo 4.

left: lado izquierdo del rectángulo.

top: lado superior del rectángulo.

right: lado derecho del rectángulo.

bottom: lado inferior del rectángulo.

clrid: indice del color.

INLINE void m4_frame (int left, int top, int right, int bottom, u8 clr)
Dibuja una marco en la pantalla en el modo 4.

left: lado izquierdo del rectángulo.

top: lado superior del rectángulo.

right: lado derecho del rectángulo.

bottom: lado inferior del rectángulo.

clrid: indice del color.

El modo 5

  • El modo 5 es otro modo 'bitmap' de 16bpp (al igual que el modo 3) pero con una resolución menor (160x128) lo que le permite tener espacio para un segundo 'buffer' para el cambio de página (al igual que el modo 4). El bit 4 del registro REG_DISPCNT{4} es el que decide el comienzo del 'frame buffer'. Si está a 0 es la dirección 0x06000000 y si está a 1 es 0x600A000.
  • Como en el modo 3, tampoco se utiliza la paleta por lo que escribimos directamente en la VRAM el color deseado.
  • Funciones para dibujar primitivas en el modo 5:
void M5_CLEAR ()
Limpia la pantalla del modo 5.
INLINE void m5_fill (COLOR clr)
Rellena el fondo del modo 5 con un color.

clr: color de 15 bits.

INLINE void m5_plot (int x, int y, COLOR clr)
Dibuja un pixel coloreado en la posición (x,y) de la pantalla en el modo 5.

x: posición horizontal.

y: posición vertical.

clr: color de 15 bits.

INLINE void m5_hline (int x1, int y, int x2, COLOR clr)
Dibuja una linea horizontal en la pantalla en el modo 5.

x1: posición horizontal del comienzo de la linea.

y: posición vertical de la linea.

x2: posición horizontal del final de la linea.

clr: color de 15 bits.

INLINE void m5_vline (int x, int y1, int y2, COLOR clr)
Dibuja una linea vertical en la pantalla en el modo 5.

x: posición horizontal de la linea.

y1: posición vertical del comienzo de la linea.

y2: posición vertical del final de la linea.

clr: color de 15 bits.

INLINE void m5_line (int x1, int y1, int x2, int y2, COLOR clr)
Dibuja una linea en la pantalla en el modo 5.

x1: posición horizontal del comienzo de la linea.

y1: posición vertical del comienzo de la linea.

x1: posición horizontal del final de la linea.

y2: posición vertical del final de la linea.

clr: color de 15 bits.

INLINE void m5_rect (int left, int top, int right, int bottom, COLOR clr)
Dibuja una rectangulo coloreado en la pantalla en el modo 5.

left: lado izquierdo del rectángulo.

top: lado superior del rectángulo.

right: lado derecho del rectángulo.

bottom: lado inferior del rectángulo.

clr: color de 15 bits.

INLINE void m5_frame (int left, int top, int right, int bottom, COLOR clr)
Dibuja una marco en la pantalla en el modo 5.

left: lado izquierdo del rectángulo.

top: lado superior del rectángulo.

right: lado derecho del rectángulo.

bottom: lado inferior del rectángulo.

clr: color de 15 bits.

El cambio de página

  • El cambio de página es una técnica que elimina los feos artefactos como la ruptura de imagen en la animación. Hay dos cosas que ocurren a la vez en la animación:
  • Poner los pixeles en el 'bitmap' (escritura).
  • Dibujar el 'bitmap' en la pantalla (visualización).
  • El software se encarga de la escritura, actualizar la posición de los personajes, etc; el hardware realiza la visualización (toma el 'bitmap' y lo copia en la pantalla). El problema que existe es que ambos procesos llevan tiempo y lo que es peor, ambos ocurren a la vez.
  • Con el cambio de página en vez de usar un solo 'bitmap' para escribir y visualizar, usamos dos. Cuando un 'bitmap' es siendo visualizado, escribimos todo lo que necesitemos en el segundo 'bitmap' (el 'back-buffer'). Cuando hayamos terminado le decimos al hardware que muestre el segundo 'bitmap' y preparamos el siguiente 'frame' en el primero.
  • Este procedimiento funciona perfectamente pero hay algunas trampas:
  • Se dan los punteros a ambas páginas, página1 y página2. Se visualiza la página1 y la página2 se prepara. Hasta ahí bien, pero cuando cambiamos a la segunda página hace que la segunda pagina se muestre pero tenemos que cambiar manualmente para hacer la pagina1 la de escritura. Este problema se soluciona usando un puntero 'write-buffer'.
  • La manera convencional de hacer la animación es dibujar el objeto en el 'frame1' y borrar el objeto antiguo y dibujar su nuevo estado en el 'frame2'. Sin embargo esto no funciona para el cambio de página debido a que el 'frame2' es escrito enteramente en un 'bitmap' diferente al del 'frame1'. Necesitamos borrar el objeto de hace dos 'frames' atrás (tambien funcionaria borrar todo el 'frame' de cada vez pero es una perdida de tiempo).
  • La segunda página se encuentra en la localización 0600:A000h.
  • Función de tonclib para realizar el cambio de página:
u16* vid_flip (void)
Cambia la página que se muestra en REG_DISPCNT{4} y pone vid_page para que apunte al 'back-buffer'.

devuelve: el puntero al actual 'back-buffer'.

Ejemplo

  • Puedes encontrarlo en el directorio /code/basic/pageflip. En el se ve como se realiza un cambio de página en el modo 4 y como copìar datos a la memoria mediante el uso de la rutina memcpy de C.
  • El resultado:


Ejemplo pageflip TONC.png Ejemplo pageflip2 TONC.png


  • El código:

pageflip.c

#include <string.h>

#include <tonc.h>
#include "page_pic.h"

void load_gfx()
{
	int ii;
	for(ii=0; ii<16; ii++)
	{
		memcpy(&vid_mem_front[ii*120], &frontBitmap[ii*144/4], 144);
		memcpy(&vid_mem_back[ii*120], &backBitmap[ii*144/4], 144);
	}

	// No es necesario que lo hagas todo con memcpy.
	// Para bloques pequeño quizás sea mejor mediante bucles.
	// Simplemente has de tener cuidado con los tipos.
	u32 *dst= (u32*)pal_bg_mem;
	for(ii=0; ii<8; ii++)
		dst[ii]= frontPal[ii];
}

int main()
{
	REG_DISPCNT= DCNT_MODE4 | DCNT_BG2;
        load_gfx();

	int ii=0;

	while(1)
	{
		while(KEY_DOWN_NOW(KEY_START)) ;	// pausa pulsando "start".

		vid_vsync();

                // Cuenta 60 'frames' y luego cambia las páginas.
		if(++ii == 60)
		{
			ii=0;
			vid_flip();
		}
	}

	return 0;
}

page_pic.h

#ifndef __FRONT__
#define __FRONT__

#define frontPalLen 32
extern const unsigned int frontPal[8];

#define frontBitmapLen 2304
extern const unsigned int frontBitmap[576];

#endif // __FRONT__

#ifndef __BACK__
#define __BACK__

#define backBitmapLen 2304
extern const unsigned int backBitmap[576];

#endif // __BACK__

page_pic.c

const unsigned int frontPal[8]=
{
	0x00100000,0x7FE00300,0x00007C18,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
};

const unsigned int frontBitmap[576]=
{
	0x01010101,0x01010101,0x01010101,0x01010101,0x01010101,0x01010101,0x01010101,0x01010101,
        ...
        0x01010101,0x01010101,0x01010101,0x01010101,0x01010101,0x01010101,0x01010101,0x01010101,
};

const unsigned int backBitmap[576]=
{
	0x02020202,0x02020202,0x02020202,0x02020202,0x02020202,0x02020202,0x02020202,0x02020202,
        ...
	0x02020202,0x02020202,0x02020202,0x02020202,0x02020202,0x02020202,0x02020202,0x02020202,
};

Explicación del código:

  • Lo primero que hacemos es activar el modo 4 y el fondo 2 de la GBA. Luego llamamos a la función loadgfx() para cargar nuestro 'bitmap' y la paleta.
  • Lo que tenemos que hacer es cargar los datos que se mostrarán en las dos páginas. Se usa la rutina estandar de C memcpy() para la copia (luego se verá que tonclib dispone de rutinas más seguras y rápidas). Cargar un 'bitmap' es muy simple en teoría, pero los 'bitmaps' que se usan en este ejemplo son solo de 144x16, mientras que la VRAM tiene un 'pitch' (número de pixeles por 'scanline') de 240px. Esto significa que deberemos copiar cada 'scanline' por separado (de ahí el bucle for).
  • La localización de la primera página es vid_mem_front y de la segunda vid_mem_back.
  • Debido a que estamos en el modo 4, también será necesaria una paleta. En esta ocasión en vez de usar memcpy() para hacer la copia a la memoria de la paleta, usamos un array de 32-bit.
  • En la linea while(KEY_DOWN_NOW(KEY_START)); pausamos el programa si se detecta que permanece pulsado el botón Start (se verá en la próxima lección).
  • Luego esperamos al VBlank antes de hacer nada mediante vid_vsync() y contamos 60 'frames' (1 segundo) antes de hacer el cambio de página.

Los datos y como usarlos

  • Lo primero que hay que decir es que no hay ficheros. Entiéndase por fichero a los datos que en los juegos de PC se separan del ejecutable: gráficos, música, script, ficheros de texto, etc. Todo esto funciona bien en un PC, pero no en la GBA ya que no hay sistema de archivos. Esto significa que no se pueden usar rutinas estándar de E/S (fscanf(), fread(), etc) para leer los datos debido a que no hay ficheros de los que leer.
  • Todos los datos de los juegos tienen que ser añadidos directamente al binario. Hay varias formas de hacer esto:
  • La forma mas común de hacerlo es convertir la archivos binarios en bruto (raw) a arrays en C, luego compilarlos y enlazarlos al proyecto (es la forma que se usará en este tutorial).
  • Ensamblar arrays (necesitas saber como trabajar con el ensamblador).
  • Por último, aunque la GBA no tiene sistema de ficheros, siempre puedes escribir tu uno. Uno muy común es el GBFS realizado por tepples. Este es el método recomendado.
  • Hay que puntualizar que no habían ficheros en el pasado, pero en Julio del 2006, Chishm nos proporciona libfat, el cual es un sistema de archivos parecido a FAT para la GBA y la NDS.

Conversión de datos

  • La mayoría de los archivos siguen un determinado formato para decir de que se trata y como usarlo. Para los 'bitmaps' esto por lo general es la altura, el ancho, la profundidad del color y algunos campos mas. La cuestión es que no son utilizables directamente. No puedes adjuntar un archivo BMP por ejemplo a tu proyecto y copiarlo directamente a la VRAM y pensar que eso funcionará. No, lo que debes hacer es convertirlo a un formato usable por la GBA, esto es es posible hacerlo:
  • Internamente (en la propia GBA).
  • Externamente (en el PC y adjuntar los datos convertidos al proyecto). Esta forma es mucho mas eficiente y es la que se usará.
  • Hay muchas herramientas de conversión, algunas mas potentes que otras. Una muy buena y recomendable es gfxgba 0.13 por Markus (no confundir con otra que lleva el mismo nombre). También existe un frontend.
  • Otra muy potente es Usenti realizada por Jasper Vijn (cearn), creador de TONC (este tutorial) y tonclib. Es un editor de 'bitmap' con opciones para exportarlo. Permite diferentes tipos de archivos, diferentes profundidades de color, diferentes archivos de salida, todos lo modos, etc. La herramienta de exportación también se proporciona de forma separada llamada (win)grit la cual viene con una interfaz de linea de comando (grit) y con una GUI (wingrit). Desde Junio del 2007 forma parte de la distribución de devkitPro.
  • Ejemplo de uso tanto de gfx2gba como de grit mediante la linea de comandos para convertir un 'bitmap' (foo.bmp) en un array en C para los modos 3, 4 y 5 (para conocer mas opciones mirar los readme que viene con ellos):
# gfx2gba
# modos 3, 5 (C array; u16 foo_Bitmap[]; foo.raw.c)
	gfx2gba -fsrc -c32k foo.bmp
# modo 4 (C array u8 foo_Bitmap[], u16 master_Palette[]; foo.raw.c, mastel.pal.c)
	gfx2gba -fsrc -c256 foo.bmp
# grit
# modos 3, 5 (C array; u32 fooBitmap[]; foo.c foo.h)
	grit foo.bmp -gb -gB16
# modo 4 (C array; u32 fooBitmap[], u16 fooPal[]; foo.c foo.h)
	grit foo.bmp -gb -gB8
  • En el código de abajo puedes ver una parte del archivo "modes.c", que contiene el 'bitmap' y la paleta usados en el ejemplo "bm_modes" que se verá después el cual ha sido exportado por Usenti. Solo es parte del archivo debido a que son cerca de 2700 lineas. Tienes que tener cuidado con la endianidad (endianness) de los tipos. En un esquema 'little endian', los bits menos significativos van antes y en un 'big endian' los bits mas significativos van antes. La GBA es una máquina 'little endian' por lo que la primera palabra del array "modeBitmap", 0x7FE003E0 es la media palabra 0x03E0 (verde) seguida de la media palabra 0x7FE0 (cian).
//======================================================================
//
//  modes, 240x160@16, 
//  + bitmap not compressed
//  Total size: 76800 = 76800
//
//  Time-stamp: 2005-12-24, 18:13:22
//  Exported by Cearn's Usenti v1.7.1
//  (comments, kudos, flames to "daytshen@hotmail.com")
//
//======================================================================

const unsigned int modesBitmap[19200]=
{
    0x7FE003E0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,
    0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,
    0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,
    0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,
// ...
// 2500 lineas más como estas.
// ...
    0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,
    0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,
    0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,0x080F080F,
    0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,
};

const unsigned int modesPal[8]=
{
    0x7FE07C1F,0x03FF0505,0x03E00505,0x7C000000,0x0000080F,0x00000000,0x00000000,0x080F0000,
};

memcpy()

  • Hay varias maneras de copiar datos en esta plataforma. En este tutorial se utiliza memcpy() en los anteriores ejemplos por varias razones:
  • Es parte de la librería estándar de C, luego los programadores de C debería estar familiarizados con ella.
  • Esta algo optimizada.
  • Sin embargo hay algunas trampas con esta rutina:
  • La primera es la alineación de los datos. Si la fuente o el destino no estan 'word-aligned' estamos en problemas.
  • Si el número de bytes a copiar es demasiado pequeño (<16 bytes), también estamos en problemas.
  • Tonclib proporciona rutinas parecidas que hacen lo mismo pero mejor, son memcpy16() y memcpy32(). También están memset16() y memset32() para llenar la memoria.

Ejemplo

  • Puedes encontrarlo en el directorio /code/basic/bm_modes. Este programa es un ejemplo de como los mismos datos dan resultados diferentes dependiendo de como se interpreten (en el modo 3, 4 y 5). En el código se aprecia como solo se hace una copia en la VRAM y se cambia entre los modos usando Izquierda y Derecha.
  • Se han dispuesto los datos del 'bitmap' de tal manera que el nombre del actual modo pueda ser leído claramente. Debido a que los datos destinados a los otros modo están todavía presentes, pero no interpretados a proposito, esa parte del 'bitmap' no se mostrará correctamente. Y esa es la intención de este ejemplo: cuando rellenes la VRAM, necesitas saber como la GBA usará esos datos.
  • El resultado:


Ejemplo bm modes TONC.png Ejemplo bm modes2 TONC.png Ejemplo bm modes3 TONC.png


  • El código:

bm_modes.c

#include <tonc.h>
#include "modes.h"

int main()
{
	int mode= 3;
	REG_DISPCNT= mode | DCNT_BG2;

	// Copia los datos y la paleta a la dirección correcta.
	memcpy(vid_mem, modesBitmap, modesBitmapLen);
	memcpy(pal_bg_mem, modesPal, modesPalLen);

	while(1)
	{
		// Espera al VBlank antes de hacer nada.
		vid_vsync();

		// Comprueba los botones para el cambio de modo.
		key_poll();
		if(key_hit(KEY_LEFT) && mode>3)
			mode--;
		else if(key_hit(KEY_RIGHT) && mode<5)
			mode++;

		// Cambia el modo.
		REG_DISPCNT= mode | DCNT_BG2;
	}

	return 0;
}

modes.h

#ifndef __MODES__
#define __MODES__

#define modesBitmapLen 76800
extern const unsigned int modesBitmap[19200];

#define modesPalLen 32
extern const unsigned int modesPal[8];

#endif // __MODES__

modes.c

const unsigned int modesBitmap[19200]=
{
	0x7FE003E0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,
        ...
        0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,0x7FE07FE0,
};

const unsigned int modesPal[8]=
{
	0x7FE07C1F,0x03FF0505,0x03E00505,0x7C000000,0x0000080F,0x00000000,0x00000000,0x080F0000,
};

Explicación del código:

  • En el archivo modes.c lo que primero hacemos es poner la GBA en modo 3 y activar el fondo 2. Luego con dos rutinas copiamos en la VRAM el 'bitmap' y la paleta. Si queremos usar memcpy16() o memcpy32() deberemos poner:
memcpy16(vid_mem, modesBitmap, modesBitmapLen/4);
memcpy32(vid_mem, modesBitmap, modesBitmapLen/8);
  • Al contrario que en el ejemplo del cambio de página aquí la imagen ocupa toda la pantalla y no parte de ella.
  • Luego simplemente comprueba si se han pulsado los botones Izquierda o Derecha y cambia de modo dependiendo de en que modo estuviera y el botón pulsado (esto se vera en la próxima lección).
  • Los archivos modes.h y modes.c han sido creados automáticamente usando la herramienta Usenti. Para hacer que el fondo tenga el mismo color en todos los modos, los dos bytes del color 16-bit del fondo en el modo 3 y 5 han servido como indice a la paleta del modo 4. En este caso, el color es 0x080F. Los bytes son 08 (8) y 0F (15) por lo que en esas posiciones de la paleta debe ir ese mismo color (0x080F).

Enlaces relacionados