Pintando pixeles: Introducción a la memoria de video Como ya vimos en el tutorial anterior Rellenando la pantalla de color en c y en ensamblador (Pasmo, z88dk y sdcc), el Amstrad cpc tiene tres modos de video: "Mode 0" 160×200 píxeles con 16 colores, "Mode 1" 320×200 píxeles con 4 colores y "Mode 2" 640×200 píxeles con 2 colores. La memoria de video está situada entre las direcciones C000 a FFFF, es decir, tiene un tamaño de 3FFF (16383) bytes. En función del modo, cada byte representa 2, 4 o 8 pixeles de la pantalla. Pero ¿la memoria de video es lineal? es decir, ¿el primer byte es la esquina superior izquierda de la pantalla y el ultimo es la inferior derecha?. Pues me temo que no. A este respecto el diseño del Amstrad CPC nos dejó una enorme sorpresa que nos complica (y a la vez divierte) su programación grafica. Para ver como está estructurada la memoria de video, nada más fácil que escribir un programa que la rellena para ver como se van distribuyendo las líneas por la pantalla. Código fuente para sdcc (al final de tutorial se puede descargar un zip): /////////////////////////////////////////////////////////////////////// // pixel01.c // Fillin the screen with random colors // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> void main() { unsigned char *pScreen = (unsigned char *)0xC000; unsigned int nByte = 0; //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; for(nByte = 0; nByte < 0x3FFF; nByte++) pScreen[nByte] = (unsigned char)(rand() % 256); //KM_WAIT_CHAR __asm call #0xBB06 __endasm; } Compilamos y generamos dsk con los siguientes comandos (pixel01.bat): sdcc -mz80 --code-loc 0x6038 --data-loc 0 --no-std-crt0 crt0_cpc.rel pixel01.c hex2bin pixel01.ihx CPCDiskXP -File pixel01.bin -AddAmsdosHeader 6000 -AddToNewDsk pixel01.dsk Ejecutamos en el emulador y obtenemos esto: Esta secuencia de lineas nos es muy familiar si recordamos la carga de la pantalla de presentación de muchos juegos (especialmente de cinta). Como vemos las líneas se rellenan 'linealmente' de izquierda a derecha, pero al terminar la línea, en vez de pasar a la siguiente inferior, se salta 8... En esta tabla (extraída de Amstrad CPC Firmware Manual) podemos apreciar la organización de la memoria de video:
Los tres modos de video tienen 200 lineas de alto, por lo que estas direcciones son fijas para los tres modos. Cada una de estas 200 lineas tiene 80 bytes de tamaño, que representan los 160, 320 o 640 pixeles de ancho en función del modo. Como ya dijimos antes, cada byte representa 2, 4 o 8 pixeles de la pantalla en función del modo, pero para complicar aun mas la programación, los bits de cada pixel están salteados en el byte de la siguiente forma:
Viendo todos estos datos y este desorden uno sólo puede acordarse "con cariño" de todos los familiares ascendentes del que diseño esto... Ejemplos practicos:
Si nos fijamos en la tabla de direcciones veremos que existe una relación entre ellas, agrupadas de 8 en 8 líneas, con saltos de 2048 bytes entre ellas y de 80 bytes respecto al grupo de 8 siguiente. Gracias a esta relación podemos sacar fácilmente la dirección de cualquier línea con la siguiente formula: Dirección = 0xC000 + ((Linea / 8) * 80) + ((Linea % 8) * 2048) Vamos a aplicar lo que acabamos de aprender sobre el programa anterior, para rellenar la pantalla de arriba a abajo. Código fuente para sdcc (al final de tutorial se puede descargar un zip): //////////////////////////////////////////////////////////////////////// // pixel02.c // Calculating the addresses of the display // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> unsigned char *GetLineAddress(unsigned char nLine) { return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048); } void main() { unsigned char *pScreen = (unsigned char *)0xC000; unsigned int nByte = 0; unsigned int nLine = 0; //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; for(nLine = 0; nLine < 200; nLine++) { pScreen = GetLineAddress(nLine); for(nByte = 0; nByte < 80; nByte++) pScreen[nByte] = (unsigned char)(rand() % 256); } //KM_WAIT_CHAR __asm call #0xBB06 __endasm; } Compilamos y generamos dsk con los siguientes comandos (pixel02.bat): sdcc -mz80 --code-loc 0x6038 --data-loc 0 --no-std-crt0 crt0_cpc.rel pixel02.c hex2bin pixel02.ihx CPCDiskXP -File pixel02.bin -AddAmsdosHeader 6000 -AddToNewDsk pixel02.dsk Ejecutamos en el emulador y obtenemos esto: Y ahora más dificil todavía, vamos a rellenar pixel a pixel la pantalla con columnas de izquierda a derecha y para ello ya tenemos que programarnos una función que rellene los bits correspondientes para cada pixel y color. Código fuente para sdcc (al final de tutorial se puede descargar un zip): //////////////////////////////////////////////////////////////////////// // pixel03.c // Filling the screen from left to right // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> unsigned char *GetLineAddress(unsigned char nLine) { return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048); } void SetMode0PixelColor(unsigned char *pByteAddress, unsigned char nColor, unsigned char nPixel) { unsigned char nByte = *pByteAddress; if(nPixel == 0) { nByte &= 85; if(nColor & 1) nByte |= 128; if(nColor & 2) nByte |= 8; if(nColor & 4) nByte |= 32; if(nColor & 8) nByte |= 2; } else { nByte &= 170; if(nColor & 1) nByte |= 64; if(nColor & 2) nByte |= 4; if(nColor & 4) nByte |= 16; if(nColor & 8) nByte |= 1; } *pByteAddress = nByte; } void main() { unsigned char *pScreen = (unsigned char *)0xC000; unsigned int nLine = 0; unsigned int nColumn = 0; unsigned char nColor = 0; unsigned char nPixel = 0; //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; for(nColumn = 0; nColumn < 160; nColumn++) { nColor = (nColor + 1) % 16; nPixel = nColumn % 2; for(nLine = 0; nLine < 200; nLine++) { pScreen = GetLineAddress(nLine) + nColumn / 2; SetMode0PixelColor(pScreen, nColor, nPixel); } } //KM_WAIT_CHAR __asm call #0xBB06 __endasm; } Compilamos y generamos dsk con los siguientes comandos (pixel03.bat): sdcc -mz80 --code-loc 0x6038 --data-loc 0 --no-std-crt0 crt0_cpc.rel pixel03.c hex2bin pixel03.ihx CPCDiskXP -File pixel03.bin -AddAmsdosHeader 6000 -AddToNewDsk pixel03.dsk Ejecutamos en el emulador y obtenemos esto: Para terminar, vamos a ver un ejemplo, con una función PutPixelMode0 completa, con un pixel rebotando por la pantalla. Código fuente para sdcc (al final de tutorial se puede descargar un zip): //////////////////////////////////////////////////////////////////////// // pixel04.c // Bouncing pixel // Mochilote - www.cpcmania.com //////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> void SetMode0PixelColor(unsigned char *pByteAddress, unsigned char nColor, unsigned char nPixel) { unsigned char nByte = *pByteAddress; if(nPixel == 0) { nByte &= 85; if(nColor & 1) nByte |= 128; if(nColor & 2) nByte |= 8; if(nColor & 4) nByte |= 32; if(nColor & 8) nByte |= 2; } else { nByte &= 170; if(nColor & 1) nByte |= 64; if(nColor & 2) nByte |= 4; if(nColor & 4) nByte |= 16; if(nColor & 8) nByte |= 1; } *pByteAddress = nByte; } void PutPixelMode0(unsigned char nX, unsigned char nY, unsigned char nColor) { unsigned char nPixel = 0; unsigned int nAddress = 0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 2); nPixel = nX % 2; SetMode0PixelColor((unsigned char *)nAddress, nColor, nPixel); } void main() { unsigned char *pScreen = (unsigned char *)0xC000; int nX = 81; int nY = 101; char nYDir = 1; char nXDir = 1; //SCR_SET_MODE 0 __asm ld a, #0 call #0xBC0E __endasm; while(1) { nX += nXDir; nY += nYDir; if(nX < 0) { nX = 0; nXDir = 1; } if(nX >= 160) { nX = 159; nXDir = -1; } if(nY < 0) { nY = 0; nYDir = 1; } if(nY >= 200) { nY = 199; nYDir = -1; } PutPixelMode0(nX, nY, rand() % 16); } } //////////////////////////////////////////////////////////////////////// Compilamos y generamos dsk con los siguientes comandos (pixel04.bat): sdcc -mz80 --code-loc 0x6038 --data-loc 0 --no-std-crt0 crt0_cpc.rel pixel04.c hex2bin pixel04.ihx CPCDiskXP -File pixel04.bin -AddAmsdosHeader 6000 -AddToNewDsk pixel04.dsk Ejecutamos en el emulador y obtenemos esto:
Podéis bajar un zip con todos ficheros (codigo fuente, bat's para compilar, binarios y dsk's) aquí: Pintando_pixeles_introduccion_a_la_memoria_de_video.zip |
www.CPCMania.com 2012 |