Cuando en el número anterior se comentó como introducción el sistema BGI se nombraron sus desventajas, derivadas del hecho de ser una librería gráfica no especializada. Por otra parte, su principal ventaja era que nos permitía trabajar en cualquier modo de vídeo sin apenas trabajo de creación por nuestra parte.
Si pretendemos usar los distintos modos gráficos de que disponemos nos vemos en la necesidad de crear nuestro propio set de funciones (putpixel(), getpixel(), line(), etc...) para poder trabajar en el modo gráfico que deseemos. El problema es que cada modo de video (de todos los que vimos la anterior entrega, como 320x200, 640x480, etc...) se programa de una manera; es decir: cuando desarrollemos nuestra función putpixel() para poner un punto en pantalla, según para qué modo gráfico vayamos a realizar el programa o juego habrá que realizar en ella una serie de acciones u otras.
Esto implica que cada modo de video de los (en principio) 19 estándar existentes se gestiona de una forma diferente, y tendremos que crear una librería especializada para cada uno de ellos. Con esto se elimina la desventaja de los BGI: una librería específica implica que al crear las distintas funciones aprovecharemos las ventajas que ese modo nos proporcione para obtener un código más rápido y optimizado. Para ello comenzaremos con los modos más fáciles de programar (320x200x256) hasta llegar a mayores resoluciones y colores (SVGA), pasando por los modos planares y unchained (indocumentados) de la tarjetas VGA.
Como un ejemplo, el juego DOOM de ID-Software está integramente programado en Watcom C (para facilitar su portabilidad a otros sistemas), con tan sólo 3 rutinas en ensamblador: control del joystick, dibujo de franjas horizontales con texturas (para el trazado de suelos y techos) y dibujo de franjas verticales (para el trazado de los muros y bloques). Esto es así porque estas rutinas (sobre todo estas dos últimas) son las más llamadas del programa (de cientos a miles de veces para cada fotograma) y necesitan de la velocidad y control que proporciona el assembler para conseguir un juego tan rápido como este, así que aprovecharemos los cursos específicos de que disponemos en la revista para introducirnos en la programación gráfica usando ensamblador para el desarrollo de las rutinas.
Por supuesto, el rendimiento de cualquier programa será mayor bajo bases de programación estables tales como MS-DOS y assembler en contraposición a la disminución de velocidad que se nota bajo sistemas como entornos Windows y otros shells gráficos, y no necesita ningún comentario el resultado si empleamos en el desarrollo lenguajes visuales.
Como única desventaja, 320x200 puede ser una resolución algo baja para aplicaciones con muchos datos de texto o para programas que necesiten más área de trabajo que 320 pixels horizontales por 200 verticales. Esta resolución puede resultar reducida para bases de datos, juegos con mucha área de visión, etc..., pero lo más seguro es que, para empezar, este modo gráfico (conocido como 13h) cubra nuestras necesidades para la mayoría de programas y juegos.
Para programar gráficos en este modo de video principalmente hemos de saber hacer tres cosas:
1. Saber inicializar el modo de video 320x200 ó 13h. 2. Comprender el modo de direccionamiento lineal del 13h y su organización interna. 3. Realizar rutinas gráficas específicas para dicho modo.Veamos primero como inicializar cualquier modo de video (entre ellos el 13h) utilizando los servicios disponibles en la ROM-BIOS del PC.
La manera de llamar a una de estas interrupciones, en lenguaje assembler, consiste en preparar todos los parámetros que ésta requiera (cargar los registros del PC con los valores adecuados) y ejecutar la llamada a la interrupción. Es necesario por tanto saber mover valores a los registros AX, BX, CX, etc... (mov registro, valor) y llamar a la interrupción deseada (int numero_de_int).
Supongamos el ejemplo concreto de inicializar el modo de video 320x200 a 256 colores. Si miramos en la tabla 1 (funciones más importantes de la int 10h) veremos que la interrupción 10h (gestión de la tarjeta gráfica) posee un servicio para inicializar modos de video (servicio 0). Cargamos los registros tal y como los pide la interrupción ( AH = servicio, AL = Modo ), y efectuamos la llamada:
mov ah, 0 /* AH=0: Init VideoMode */ mov al, 13h /* modo: 13h */ int 10h /* llamada a int 10h */Simplemente conociendo los parámetros que necesita cada interrupción y servicio podemos utilizar cualquiera de las funciones de bajo nivel de que nos provee la BIOS del PC. Esta interrupción en concreto, la interrupción 10h, es la llamada interrupción de video, y nos permite inicializar modos de video (servicio 0), poner pixels en pantalla (servicio 0Ch), leer el valor de un pixel (servicio 0Dh), cambiar la paleta disponible (desde AX=1000h a 101Bh) y otros servicios a disposición del programador. Aparte de la interrupción 10h existen más interrupciones con sus correspondientes servicios (33h=servicios del ratón; 16h=servicios de teclado, etc...), que nos harán el proceso de creación mucho más sencillo. Por ello es recomendable seguir el curso de ensamblador de la revista para coger soltura en este lenguaje tan indispensable y comprender el funcionamiento de las interrupciones.
TABLA 1: Algunos servicios de la int 10h. ------------------------------------------------------------------------- | PARÁMETROS | SERVICIO | FUNCIÓN QUE REALIZA | ------------------------------------------------------------------------- | AH = 00h | Set Video Mode | Inicializa el modo de video especi- | | AL = Modo | | ficado en AL, segun los valores de | | | | la tabla 2. | ------------------------------------------------------------------------- | AH = 0Ch | Write Pixel | Dibuja el pixel (CX,DX) en pantalla | | CX = Coord. X | | con el color Al. El parámetro página| | DX = Coord. Y | | debe ser 0 en 13h ya que sólo hay 1 | | AL = Color | | página de video y esa es la 0. | | BH = Página | | | ------------------------------------------------------------------------- | AH = 0Dh | Read Pixel | Devuelve en Al el color del pixel | | CX = Coord. X | | de la posición (CX,DX). | | DX = Coord. Y | | | | BH = Página | | | | Devuelve: | | | | AL = Color | | | ------------------------------------------------------------------------- | AH = 0Fh | Get Video Mode | Devuelve en AL el modo de video | | Devuelve: | | actual. | | AL = Modo | | | ------------------------------------------------------------------------- | AX = 1001h | Set Border Color| Cambia el color del borde de la pan-| | BH = Color | | talla al color BH (por defecto es 0,| | | | equivalente al negro). | -------------------------------------------------------------------------En la tabla 2 disponemos del listado de los modos de video que pueden ser inicializados en una VGA estándar mediante el servicio 0 de la interrupción 10h. Podemos llamar a este servicio dentro de una función C, como puede verse en el siguiente código:
void SetVideoMode ( char modo ) { asm { mov ah, 0 mov al, [modo] int 10h } }
TABLA 2: Modos de vídeo de la BIOS. ---------------------------------------------------------------------- MODO TIPO RESOLUCIÓN COLORES VRAM SISTEMA ---------------------------------------------------------------------- 00h Texto 40x25 16 B800h CGA/MCGA/EGA/VGA 02h Texto 80x25 16 grises B800h CGA/MCGA/EGA/VGA 03h Texto 80x25 16 B800h CGA/MCGA/EGA/VGA 04h Gráfico 320x200 4 B800h CGA/MCGA/EGA/VGA 06h Gráfico 640x200 2 B800h CGA/MCGA/EGA/VGA 07h Texto 80x25 mono B000h MDA/Herc/EGA/VGA 0Dh Gráfico 320x200 16 A000h EGA/VGA 0Eh Gráfico 640x200 16 A000h EGA/VGA 10h Gráfico 640x350 16 A000h EGA/VGA (256Kb) 12h Gráfico 640x480 16 A000h VGA 13h Gráfico 320x200 256 A000h MCGA/VGA ------------------------------------------------------------------------Para inicializar ahora el modo de video 320x200x256 (llamado 13h porque éste es el valor que hay que introducir en el registro AL), bastaría con llamar a nuestra nueva función con este parámetro:
SetVideoMode( 0x13 );Para volver de nuevo al modo de texto 80x25, usaremos la orden SetVideoMode(3); ya que el modo 3 corresponde, según la tabla 2, al modo de video 80x25 a 16 colores.
Es muy importante ir agrupando todas las nuevas funciones que vayamos creando (como SetVideoMode) en librerías externas o ficheros .C o .H que más tarde podrán ser incluidos durante el proceso de compilación o creación en la línea de comandos o mediante el parámetro #include de C, tal y como se hace en los ejemplos de este curso.
En cierta manera, de toda la memoria del PC los primeros 1024 KiloBytes (1 MegaByte) están divididos en segmentos de 64kb (65.536 bytes) a los que se accede mediante un segmento y un desplazamiento u offset.
Dicho de otro modo, es como si el primer megabyte de memoria RAM estuviera compuesto por bloques de 64Kb cada uno. Cuando queremos escribir en una posición de memoria, le indicamos al ordenador en qué bloque está (segmento), y dentro de ese bloque cual es el byte que queremos modificar (offset o desplazamiento).
Esto es así porque en el 8086 los registros eran de 16 bits (sólo pueden adoptar valores entre 0 y 65.536), por lo que para acceder a la memoria idearon este método para, pudiendo utilizar tan sólo hasta el número 65.535, hacer referencia a posiciones de memoria más elevadas (cuando se creó el PC nadie se podía imaginar que algún día necesitaría más de 64Kb de RAM). Con este sistema de segmentación de memoria, escribir un byte en la posición 65.536 se reduce (de una manera intuitiva a modo de ejemplo) a escribirlo en el bloque 1, offset 0. Realmente en el PC las direcciones absolutas se construyen mediante:p>
Dir_Física = (Segmento*16)+OffsetEsto sólo ocurre en modo real, pues a partir de la aparición de micros de 32 bits (386+), con registros de este tamaño puede accederse a la memoria de manera lineal, en un nuevo modo del micro llamado modo protegido, permitiendo la manipulación de hasta 4 gigabytes.
El objetivo de la anterior introducción a la segmentación de memoria es el segmento 0A000h. Este segmento de memoria (64Kb, 65.536 bytes) es el segmento de VideoRAM, es decir, es donde la VGA guarda los datos de las imágenes gráficas que dibuja en el monitor. Si escribimos algún valor en este segmento, la próxima vez que la tarjeta gráfica redibuje la pantalla (lo hace entre 50 y 70 veces por segundo) el valor que hemos escrito aparecerá en pantalla en forma de punto. Pero veamos que es lo que hace la tarjeta gráfica con esta VideoMemoria.
En este proceso, llamado retrazado de pantalla, el haz de electrones se desplaza hasta la esquina (0,0) del monitor (incluyendo el borde) y comienza a leer bytes de la VideoRAM (segmento 0A000h), transformándolos en pixels y trazándolos en pantalla. Al llegar al final de una línea horizontal, el haz vuelve en a la siguiente línea (retrazado horizontal) y continúa con el proceso hasta llegar a la esquina inferior derecha, donde vuelve en diagonal a (0,0) para repetir el proceso. En la figura 1 puede verse el proceso con más claridad. El refresco de la pantalla consiste en un continúo retrazado actualizando la pantalla para ofrecernos la imagen contenida en la videomemoria (que en realidad constituye RAM de la tarjeta a la que se nos permite acceder tras el proceso de autoarranque del encendido).
Esto quiere decir que cada número del 0 al 255 se corresponde con un color. Por defecto, el 0 es el negro, el 1 el azul, y así hasta llegar al 255. Entre el 0 y el 255 disponemos de gamas de azules, verdes, amarillos, etc..., que componen la paleta por defecto de la VGA.
Que este modo gráfico sea de un byte por pixel significa que al escribir un byte en este segmento de memoria, su equivalente en pantalla será un pixel, que aparecerá automáticamente en cuanto el haz de electrones pase por esa posición al refrescar la imagen.
En la figura 2 tenemos una representación de cómo está organizada la VideoRAM en el modo 13h.
Como puede verse, al byte 0 le corresponde el pixel (0,0) (el primero de la pantalla); al byte 1 le corresponde el pixel (1,0), al byte número 320 le correspondería el pixel (0,1), (primer pixel de la línea 1, porque hay 320 pixels de resolución horizontal) y así hasta el byte 63.999 del segmento, que corresponde a la posición (319,199). Depende del offset en que coloquemos el byte, el punto aparecerá en distinta posición en el monitor (cada byte es un pixel individual en la pantalla).
El segmento de la VideoRAM se comporta en este modo de video como si fuera una larga línea de pixels de manera que al llegar al final de una línea horizontal de pantalla, el siguiente byte de la VideoMemoria es el que continúa en la siguiente línea de pantalla. De ahí el término direccionamiento lineal: es como si la pantalla fuera un array de C o PASCAL unidimensional desde 0 a 64.000 donde cada 320 bytes estamos situados en una nueva línea de pantalla (el byte 320 es el primer pixel de la segunda línea). Así, durante el retrazado la tarjeta únicamente tiene que dedicarse a leer bytes (todos ellos consecutivos) y representarlos en pantalla.
Tambien podríamos comparar la VideoRam con una gran pantalla de una sóla linea de ancho (de 320x200=64.000 pixels de ancho), y cuando la tarjeta traslada esos colores al monitor, cada 320 bytes salta a una nueva línea. Así obtenemos en pantalla una imagen de 320x200 pixels.
Por otra parte, hay que hacer notar que los 256 colores de que disponemos pueden ser adaptados a nuestras necesidades. La paleta por defecto contiene unos 32 tonos de azul, 32 de rojos, grises, etc... pero si estamos dibujando (por ejemplo) una selva, probablemente necesitaremos más tonos de verdes de los 32 que hay por defecto. La manera de cambiar la paleta (es decir, que cada número 0-255 corresponda a una tonalidad o color) la abordaremos en un próximo artículo, de manera que podríamos (en este ejemplo en concreto) hacer que los primeros 128 colores sean tonos de verdes y los restantes 128 tonos de azules (para el cielo de nuestra selva). Esto quiere decir que el color 0 no tiene porqué ser el negro (como en la paleta por defecto), sino que podemos adaptar cada color (desde el 0 al 255) a la tonalidad (mezcla de rojo, verde y azul) que deseemos. En el programa ejemplo5.exe de los ejemplos que acompañan al artículo podemos observar la paleta por defecto, para hacernos a la idea de los colores de que disponemos.
unsigned char *vgaseg = (unsigned char *) MK_FP(0xA000, 0); memset(vgaseg, 0, 64000);o también podemos aprovechar las órdenes de cadena del lenguaje ensamblador como stosb, stosw o stosd, precedidas del comando rep:
asm { mov ax, 0xA000 mov es, ax xor di, di mov al, 0 mov cx, 64000 rep stosb }En el listado 1 podemos ver la función ClearScreen() que se encarga de borrar la pantalla con el color especificado como parámetro usando instrucciones 8086 (stosb).
LISTADO 1: Función ClearScreen() de borrado de pantalla. /*---------------------------------------------------- ClearScreen(); Borrado de la pantalla (segm. 0A000h). Pseudocódigo: ES:DI = A000:0000 AL = Color Repetir CX veces ES:[DI++] = AL ----------------------------------------------------*/ void ClearScreen ( char color ) { asm { mov ax, 0xA000 mov es, ax xor di, di mov cx, 64000 mov al, [color] rep stosb } }La instrucción assembler stosb mueve el valor de AL a la posición de memoria apuntada por ES:DI e incrementa DI en uno tras la operación. El prefijo rep le indica al procesador que repita el proceso CX veces. De esta manera, el micro ejecutaría el siguiente pseudocódigo para el anterior bloque de ensamblador:
ES:DI = A000:0000h CX = 64.000 Repetir hasta que CX sea 0 { ES:[DI] = AL DI = DI + 1 CX = CX - 1 }Por supuesto, cambiando los valores iniciales podemos borrar tan sólo la mitad de la pantalla (CX=32.000), borrar las 10 primeras líneas (CX=320*10), borrar la parte inferior de la pantalla (CX=32.000, DI = 32.000), etc...
Stosw es el equivalente en 16 bits de stosb que almacena cada vez 2 bytes (AX), incrementado DI en 2, resultando por tanto más rápido al tener que hacer la mitad de repeticiones (CX = bytes totales/2 ). Como actualmente el equipo mínimo es un 386/486, es más recomendable usar stosw e incluso stosd si programamos en 32 bits.
Pulse aquí para bajarse los ejemplos y listados del artículo (19 Kb).
Figura 1: "Proceso de retrazado de la
pantalla.."
Figura 2: "Organización de la
VideoRAM en modo 13h."
Santiago Romero