PROGRAMACIÓN DE MODOS X

Artículo 1: INTRODUCCIÓN AL MODO X

Autor: (c) Santiago Romero
Revista: Sólo Programadores (Tower Communications) número 22, Junio-1996


Actualmente, el modo 13h se ha convertido en la estrella de los modos gráficos de la VGA. Este modo basa su éxito en la simplicidad de su programacion de sus 256 colores simultáneos en pantalla. Imagínate conseguir con tu VGA resoluciones más altas que proporcionen a tus programas un aspecto profesional. Esto está ahora a tu alcance gracias al MODO X.


EL MODO 13h ESTANDAR

El modo 13h posee una resolución de 320 x 200 píxels, con 256 colores simultáneos de 262.144 posibles cambiando los componentes DAC de la tarjeta (cambiando la paleta). Estas son unas aceptables características en cuanto a colores y resolución horizontal, pero la resolución vertical de 200 píxels proporciona a estos un aspecto alargado, más alto que ancho, de manera que un círculo en MCGA 13h parece más bien una elipse achatada. La solución estaría en usar las tarjetas Super VGA, con resoluciones entre 640x480 y 1280x1024, a 256 y más colores, que proporcionan ratios (relacilto) de aspecto cuadrado. Posiblemente pienses que estas tarjetas son definitivas, pero tienen grandes desventajas por ahora:

- Las tarjetas Super VGA por lo general son algo lentas, y lo que es peor, hay diferencias de velocidad entre ellas. Las altas resoluciones hacen que las operaciones de pantalla en SVGA se ralenticen usando el estándar Vesa, y programar cada tarjeta por separado resulta una incomodidad debido al gran número de ellas (Trident, Tseng, etc...)
- Si alguna vez ha parpadeado alguno de tus scrolles o sprites, imagmover bloques de 100x100 píxels que en una pantalla de 1024x768 no son más que sprites "pequeños".
- Se acabó eso de dibujar en un segmento aparte y volcarlo a la memoria con rapidez. El sistema de planos de las SVGAs hace la programación más complicada.
- Las compresiones gráficas no consiguen evitar el abultado tamaño de las imagenes en el disco duro y eso es algo que pagarían los programadores noveles, ocupando los juegos un gran tamaño a menos que se usen complejas compresiones (GIF, JPG...).

A esas desventajas, se le une el impacto definitivo a favor del modo 13h: La programación del modo 13h hace que poner un punto sea una operación matemática para conseguir un offset o desplazamiento respecto al punto (0,0) de la pantalla. La programación de este modo gráfico debería ser ya familiar antes de iniciarse en Modo X, pero por si acaso veamos las bases:

Los gráficos que están en pantalla son guardados en memoria en forma de bytes y el ordenador cada vez que tiene que refrescar la pantalla se dedica a leer de la memoria y redibujar los pixels antes de que el fósforo se extinga de cada uno de los puntos que componen el monitor. A partir del segmento A000h offset 0 de la memoria se encuentra la imagen binaria que representa la pantalla VGA, que el haz de electrones transforma en colores a una velocidad de entre 60 y 70 hertzios, donde cada byte se corresponde con un píxel. Debido a que el rango de valores de un byte est255, disponemos de 256 colores cuyos tonos pueden ser modificados cambiando el DAC o lo que se conoce comunmente como cambiar la paleta. Es muy sencillo calcular el offset de un píxel con la famosa fórmula de:


  Offset = ( y * 320 ) + x;

o su variación ultrarrápida:


  Offset = ( y << 8 ) + ( y << 6 ) + x.

Con ella nos "saltamos" 320 bytes por cada línea y le sumamos la coordenada X para calcular el offset en memoria en el que colocar el byte al que corresponda el color de nuestro pixel, siempre en el rango 0-255.

Esto quiere decir que si se coloca el ncolor blanco) en la posición A000:0000 de la memoria, aparecerá un píxel de ese color en la posición (0,0) de la pantalla. Para rellenar la pantalla de color negro (0) habría que rellenar 64.000 bytes a 0 en 0A000h. Hay que fijarse en un detalle que condicionó el descubrimiento del modo X. Si se usa un byte por píxel, 320x200 pixels nos da un offset máximo de 64.000 (aprox. 64K). Pero resulta que TODAS las tarjetas VGA esten como mínimo 256 Kb de memoria o más. ¿Donde están esos 192 Kb que faltan?


LA ORGANIZACION PLANAR

Espero que conozcas algo sobre los planos, por que esta es la razón del despilfarro de memoria. Todo viene por mantener la compatibilidad con las anteriores tarjetas. Los creadores de las tarjetas EGA ( Enhaced Graphics Array ) usaron un método denominado planar, que toma su nombre del uso de planos. Intentaré dar una explicación breve. Ya sabéis que cada color está formado por tres componentes: Red , Green y Blue (R,G,B), que son los colores primarios cuya mezcla da el resto de colores existentes. El máximo color posible en una tabla de 16 colores es el 15 (rango 0-15). Ese número puede ser representado en binario justo por un nibble (4 bits, de tal manera que 15 = 1111b).

. En vez de usar un byte por cada color, como en el modo 13h, se les ocurrió poner cada uno de los bits que indican el color (p. ej. 1011b) en un "plano" de la tarjeta, que podríamos comparar a 4 segmentos separados, para lo que usarían cuatro planos. El primer plano contiene los rojos, el segundo los verdes, el tercero contiene los azules y el último la intensidad.

Como ejemplo, si en una tarjeta EGA ponemos aleatoriamente bits a uno en el primer plano, aparecerían en la pantalla puntos rojos, que son los que contiene el primer plano. Para poner un punto blanco habría que poner un 1 en todos los planos en el bit correspondiente, ya que 15 = 1111 = blanco, siempre que no hayamos cambiado la paleta. Con las combinaciones posibles (0000, 0001, 0010, .... ,1111) disponemos de los 16 colores que el DAC de la EGA dibujará en pantalla.

Así pues ya podemos diferenciar entre modos de video planares (como los de 16 colores y modo X) y lineales (como el 13h). Y resulta que al crear la VGA los fabricantes decidieron hacer un modo fácil de programar (13h), que resultó ser un modo lineal (todos los bytes de los pixels son consecutivos) , pero con parte planar, ya que la VGA guardaba automáticamente esos bytes del segmento A000 en sus planos como en la siguiente figura:

Figura 1: El modo 13h

Esto aclaraba lo de los 192K que faltaban, no han desaparecido sino que el sistema de planos nulo que usa no los utiliza. El programador no tiene que preocuparse de los planos en el modo 13h, ya que son gestionados por la propia tarjeta, ahorrando al programador su cálculo, pero haciéndonos pagar un alto precio por esto: perdemos 196.608 bytes (192K) que en modo X nos supondrán 3 páginas más de video para dibujar, lo que favorece cosas como la animación por page flipping: podemos dibujar un fotograma en una página, cambiar la visualización a ésta y mientras tanto se está dibujando el siguiente fotograma en otra página a la que se cambiará en cuanto creamos conveniente. Con sólo 2 de las páginas se puede usar esta y otras técnicas que consigue buenos efectos de animación.


PASANDO AL MODO X

A cierto programador llamado Michael Abrash lo de los 192K desaparecidos en la VGA no le dejaba dormir por las noches, así que comenzó a experimentar con la VGA. Si tienes un manual técnico de la VGA por ahí sabrás que la VGA tiene un serie de registros que controlan todo: el barrido , la sincronización de la pantalla, etc. Pues bien este señor se puso a modificar los registros (no aleatoriamente, por supuesto), y encontró que podía modificar algunos registros de manera que no hubieran bytes sin usar en cada plano, y poder acceder así a los 256K de la tarjeta, con la posibilidad de 4 páginas de video en las que dibujar y entre las que hacer scrolles mediante hardware. En particular hubo un registro que le llamó mucho la atención, y se puso a trabajar sobre él. ¿Imaginais lo que es experimentar con algo de lo que no existe documentación alguna y que cada error supone un posible reset? Así destripó Michael Abrash los modos indocumentados de la VGA.

Michael Abrash descubrió que había un bit en la VGA que cuando estaba a cero forzaba a la tarjeta a usar planos de bits como en 640x480 y la mayoría de resoluciones EGA, y que se ponía a 1 cuando los modos eran lineales (como el 13h). Ese bit es el llamado CHAIN-4 , porque encadena los cuatro planos. De manera que ya tenemos un paso adelante hacia el modo X: Inicializamos el modo 13h y cambiamos el bit en cuestión, cosa que se hace enviando un valor a un determinado puerto (ver ejemplos de inicialización). Con esto forzaríamos a la VGA a gestionar el modo 13h como un modo de 4 planos, "desencadenándolos" o liberándolos del modo lineal. De ahí el termino unchained. Pero eso no basta para entrar en el modo X. Hay que ajustar otros registros en la VGA como sincronización, refresco, etc... , que evitan parpadeos y que son necesarios para la correcta visualización de las imágenes y para que la propia tarjeta reconozca el nuevo modo de gestión de la memoria.

"Pero si no hay modo linear, sino planar, la programación del modo X con respecto a poner píxels, etc... se hace más complicada", pensará el lector. Acierta, pero el lector descubrirá que también hay formulas de offset parecidas a las que se usan en modo 13h, que automatizarán la tarea de colocación de pixels. Lo más importante por ahora es comprender el modo de organizacied para crear las primeras rutinas gráficas.

Así que para comenzar miremos el ejemplo 1 que sirve para ilustrar al inicialización de 320x200x256 unchained, que aunque todavía no es el llamado modo X, tiene sus mismas características y se suele usar tanto como el 320x240. En el directorio de los ejemplos hay un programa llamado RATIO.EXE que muestra la diferencia de resolución entre el modo 13h y el modo X. Las rutinas gr crearemos funcionaron 320x200X como con 320x240X. Si posees una SVGA, dado que son compatibles con la VGA, también podrás disfrutar del modo X. Hay que hacer notar que hay algunos monitores que no soportan algunas resoluciones X como 360x400 o 360x360, sobre todo los antiguos.


/*    Ejemplo de inicialización del modo 13X */

#include <dos.h>      /* outport */

#define SEQU_ADDR 0x3c4
#define CRTC_ADDR 0x3d4

void Set_320x200X( void );

void Set_320x200X()            
{
        /* 1: inicializar modo 13h */
  asm  mov ax , 13h
  asm  int 10h 	
        /* 2: ponemos el bit CHAIN-4 a 0 */
        /* 3: ajuste del modo de palabras */
        /* 4: ajuste del modo de dobles palabras */
        /* 5: seleccionar los cuatro planos para...*/
  outport( SEQU_ADDR , 0x0604 );       
  outport( CRTC_ADDR , 0xE317);        
  outport( CRTC_ADDR , 0x0014);        
  outport( SEQU_ADDR , 0x0F02);

  asm {    /* ...borrar la pantalla */
    mov ax, 0xA000 
    mov es, ax
    xor di, di
    xor ax, ax
    mov cx, 32000
    rep stosw
       }                                                  
}
Lo que hace el programa es inicializar primero el modo 13h para despureprogramar algunos registros de la VGA de manera que, despuar el bit CHAIN 4, la tarjeta inicie el nuevo modo gráfico 320x200X. La inicialización del modo 320x240x256, o MODO X, se reduce a una modificación del total vertical register, además de registros de polaridad y retrazo para que redibujen las nuevas 40 líneas extra de pantalla y para que las reconozca la tarjeta, pero eso lo veremos en la próxima entrega, dedicada principalmente a la programacimás útiles (para modos unchained) registros de la VGA.

Como lo que interesa de momento es la teoría, he usado ensamblador porque asumo que quién se quiere meter en modo X es porque ya conoce a fondo el modo 13h y algo de Assembler. Además de 320x200, puedes conseguir resoluciones tales como 320x240, 360x400, 400x600, 256x256 y otras similares.

Pero lo que más interesa ahora es comprender la lplanos de los modos X. Una vez esto sea entendido, verás como desaparece ese halo de misterio que rodea al modo X y como comienza a ocupar un lugar importante en tus futuras creaciones. En el ejemplo 2 se puede ver la rutina que inicializa el MODO X con 320x240 píxels de resolución.


/*    Ejemplo de inicialización del modo X */

#include <dos.h>   /* outport */

#define SEQU_ADDR 0x3c4
#define CRTC_ADDR 0x3d4

void Set_320x240X ( void );

void Set_320x240_X( )
{

  Set_320x200X( );       /* primero inicializamos 13X */

  outportb(0x3C2, 0xE3);  
  outport(0x3D4, 0x2C11);     /* permitir escritura */
  outport(0x3D4, 0x0D06);     /* Total vertical */
  outport(0x3D4, 0x3E07);     /* Overflow Register */
  outport(0x3D4, 0xEA10);     /* Vertical retrace start */
  outport(0x3D4, 0xAC11);     /* Vertical end y Write prot. */
  outport(0x3D4, 0xDF12);     /* Vertical display enable end */
  outport(0x3D4, 0xE715);     /* Start vertical blanking */
  outport(0x3D4, 0x0616);     /* End vertical blanking */

}

LA ORGANIZACIÓN DE LOS MODOS UNCHAINED

Ahora llega la parte más importante. Si se comprende la organización del modo 13X y la manera de poner un píxel en pantalla, podremos crear nuestra primera librería del modo 13X (320x200X256_X y 320x240x256_X). Después de esto, dibujar sprites, líneas y polígonos será sencillo, y se hará de manera muy parecida al modo 13h. Primero puedes mirar la organizacio 13X (y similares) en la siguiente figura:

Figura 2: El modo 13X
.

El sistema de planos es algo más complicado que el lineal del modo 13h. Como se puede ver en la figura, en el primer plano de la VGA se guardan los pixels 0, 4, 8, etc... ; en el segundo: 1, 5, 9, etc.. y así con los 4 planos. Entonces, para poner un píxel en pantalla, basta con seleccionar el plano correspondiente y poner en A000:offset el valor del color de nuestro píxel. Los planos están guardados en la memoria que acabamos de aprovechar (los 192K) y podemos usar el segmento de la VGA (0A000h) para comunicarle a la tarjeta los cambios que queremos hacer en estos planos. Esto se reduce a colocar en el segmento A000h bytes que indiquen los colores de los pixels. e se va pareciendo a lo que se hacía antes? Tomando en cuenta esto datos, en nuestro PutPixelX debemos:

	A. Seleccionar el plano correcto.
B. Calcular el offset desde A000.
C. Usar Stosb para poner el punto.

A: Seleccionar el plano correcto:

Para seleccionar el plano en el que escribir en una VGA, tan solo hay que mandarle el índice o funcizar (en este caso cambiar de plano) al puerto 0x3c4 y el número del plano al puerto 0x3c5. Esto se puede hacer de manera muy sencilla:


  outportb( 0x3c4 , 0x02 )     /* función seleccionar plano */
  outportb( 0x3c5 , 1 )        /* plano 1 */

O de manera "todo-en-uno", aprovechando que outport(port , valor); de DOS.H manda a port el byte alto del entero y a port + 1 el byte bajo, al igual que hace out de asm.


  outport( 0x3c4 , 0x0201 );   /* los 2 bytes en uno */

Pero, ¿cómo sabemos en que plano cae el píxel? Fácil: al fijarse en la figura 2, en cada plano los pixels aumentan de 4 en 4. Es tan sencillo como que los dos últimos bytes de la coordenada X nos lo indican, porque están entre 0 y 3. Ese número se podrn C usando plano = 1 << (X % 4) y en Pascal con plano = 1 SHL (X MOD 4), pero es mejor usar algo que se pueda transportar a Asm y que sea más optimizado que el operador %, pués este implica una división y el posterior cálculo del resto:

  Plano = 1 << ( X & 3 );
    /* EN C/C++, << = SHL    y    & = AND */
Y con esta formula que depende de la X ya se calcula el plano a activar.

También se pueden activar los 4 planos al mismo tiempo por lo que al colocar un valor en memoria aparecerían cuatro pixels consecutivos, algo bueno para dibujar líneas horizontales y rellenar polígonos. Los programadores más avanzados se habrán percatado de que el resultado de 1 << (X & 3) es un número superior a 1, ya que hay que activar algLos últimos 4 bits del byte que le enviamos a la VGA le indican que planos activar según los bits estén activados o no. Así, si le enviamos el byte 00000100b activará el tercer plano, ya que está activado el tercer bit.

Lo que hacemos en la fórmula es colocar un uno en el último bit (00000001b) y desplazarlo hacia la izquierda hasta que el uno se coloque sobre el plano que realmente queremos activar. Veamos un ejemplo: supongamos que tenemos un pixel que ha de dibujarse en la coordenada (2,0). El resultado de x&3 será 2 (el plano en el que estcolocamos el último bit a uno, 0001b, y lo desplazamos a la derecha el número obtenido, cuyo resultado sería 0100b. Este es el valor que debemos enviar al puerto 0x3c5 y que le indica a la VGA el plano que deseamos activar. No se puede usar sólo x&3 porque en el caso de que el resultado fuera cero no activaríamos nigsería mov al, 0) mientras que lo que nos interesa es activar el pixel 0.

Recordemos que SHL a, b desplaza los bits del operando a, y lo hace b veces hacia la izquierda:

  SHL 00111010b, 1   =   01110100b

La forma de activar los 4 planos es enviar como nlano el valor 15 (1111b) porque al tener activados los 4 bits la VGA asume que se va a escribir en los 4 planos, de manera que al colocar, por ejemplo, el byte 1 en A000:0000 nos aparecerán cuatro pixels consecutivos con el color azul. Hay que hacer notar que hasta que no seleccionemos otro plano la VGA mantendrá siempre activado el no seleccionado.

B: Calcular el offset desde A000:

En MCGA estándar había que saltar 320 bytes por cada línea más la coordenada X del punto para calcular el desplazamiento desde A000. En modo X el offset se calcula de similar manera:

Sabemos que la resolución horizontal de la pantalla es 320. Si hay cuatro planos, cada uno de estos planos guardará 320 / 4 = 80 pixels por linea. O sea, al dividir por 4 ya calculamos el offset de la X, y por fin: si saltamos 80 bytes por cada línea, el resultado es nuestro querido punto.

Sobre la multiplicación por 80, hay que hacer notar que en modo X disponemos de 4 páginas y podemos colocarlas como queramos: ya sea las 4 en sentido vertical, horizontal y mixto: teniendo 2 páginas de ancho por 2 de alto. El criterio de selección de un sistema u otro depende del tipo de programa o efecto a crear; es decir, si queremos realizar un scroll de 4 pantallas horizontales, lógicamente tendremos que inicializar el modo 4x1. En este caso, habría que saltarse 320 píxels por cada línea, ya que las 4 pantallas está colocadas una a continuación de la otra. Este tema será el desarrollado principalmente en el próximo nde se explicará con más detenimiento. Volviendo con nuestro modo X colocado en 1x4 (1 pantalla horizontal, 4 hacia abajo), tenemos que después de haber seleccionado el plano correcto, el cálculo se reduce a la fórmula usada en la siguiente figura:

Figura 3: Calculo del offset
.

Como ejemplo, imaginemos que colocamos en A000:0000 el color 15 (Blanco). Si antes de poner el valor en memoria seleccionamos el plano 1, aparecerá en la posición (0,0) un pixel blanco. Si el plano seleccionado era el 2, aparecerá en (1,0) y si seleccionásemos los 4 mediante el valor 1111b, aparecerían 4 pixels blancos (0,0), (1,0), (2,0) y (3,0). Fíjate que cada offset de la memoria sirve para poner 4 puntos, indicplano correcto. Por eso en vez de 320 píxels necesitamos 320/4 =80. De esta manera, 80*200 pixels da = 16.000 bytes. Como el segmento de la VGA es de 65.536 bytes, disponemos de 65.536 / 16.000 = 4'1 páginas gráficas donde dibujar y entre las que hacer scroll. Fién que en el caso del modo 320x240 en vez de tener 4,1 páginas disponemos de 4 páginas justas, debido a las 40 líneas extras.

C: Poner el punto el pantalla:

Desarrollemos por ahora nuestra primera rutina PutPixelX para hacer las primeras pruebas. Esta se encargará de recibir las coordenadas X,Y y el color , calculará el plano y el offset del pixel y finalmente lo colocará en pantalla, siguiendo los pasos que acabamos de ver.


void PutPixelX( int, int, char );

void PutPixelX( int x, int y, char color ) 
{
 unsigned int offs;

  outportb( 0x3c4 , 0x02 );    /* función seleccionar plano */
  outportb( 0x3c5 , 0x01 << ( x & 3 ) );
          /* esto ultimo calcula automáticamente el plano */		
          /* partir de los 2 últimos bits de la coord.x */

 offs = (80 * y) + (x / 4);

 asm { 
   mov ax , 0xa000
   mov es , ax
   mov di , offs
   mov al , color
   stosb             /* ponemos el punto */
     }
}
Multiplicar por 80 y dividir por 4 puede ser profundamente optimizado mediante instrucciones de desplazamiento SHL y SHR ( << y >> ) para acelerar la colocacipuntos. La división entre cuatro la podemos sustituir por x>>2 y la multiplicación por 80 es los mismo que multiplicar por 16 miplicación por 64. Aunque parezca algo más complicado podemos sustituir la línea


  offs = (80 * y) + (x / 4);

por su variación optimizada:


  offs = (y << 6) + ( y << 4 ) + ( x >> 2 );


EN LA PRÓXIMA ENTREGA:

En el próximo artículo optimizaremos las rutinas y profundizaremos en el modo X de manera que cada lector explote el modo unchained que más le interese a sus juegos o programas (320x200, 320x240, 256x256 ...). Ademproporcionaremos un listado de los puertos de los registros y ejemplos de usos de scrolles hardware, subiendo el listón hasta un nivel más profesional. El objetivo es adquirir suficientes conocimientos como para desarrollar programas en otros modos (256x256 o modo q, 360x400, etc.) experimentando con los registros.

Como proyecto, propongo al lector que realice una función GetPixel ( x , y ); que devuelva el color de un píxel, partiendo de que para poder leer de un plano hay que usar, en vez de los puertos anteriores, los siguientes: ( y que el offset se calcula de la misma manera ).


  outport( 0x3CE , 0x04 );       /* Función leer de plano */
  outport( 0x3CE+1, plano );     /* plano a leer ( 0 - 3 )*/

El plano se calcula con plano = x & 3 y no con 1 << (x & 3) porque el plano de lectura ya no es superior a 1, y el valor 0 representa el primer plano. Esto es debido a que no se pueden leer varios planos al mismo tiempo y por eso optaron por usar el más sencillo método de x&3, sin desplazamientos.

La función ya está implementada en la librería pero es aconsejable que se intente su creación si se quiere desarrollar librerías para otros modos unchained, para ir adquiriendo práctica, ya que esta serie de artículos cubrirá sólo los principales modos. La librería está creada como un fichero .H (MODOX.H) y para su utilización basta con que el fichero estsmo directorio que el fichero que se está compilando y que coloques al principio de tus programas la línea #include "modox.h".

El lector dispone además de una transformación a ASM y PASCAL en los ficheros include MODOX.INC y MODOX.PAS para utilizar el modo X en estos lenguajes de programación, así como ejemplos de su uso.

El mejor consejo que te podemos dar es que practiques y experimentes colocando valores en la memoria de la VGA y cambiando los planos. Para consultas sobre el tema puedes contactar con cualquier miembro de Compiler Software en las direcciones indicadas en los ejemplos del disco o en la propia revista. Hasta la proxima entrega, eXplota al 100% tu tarjeta gráfica y practica.

Pulse aquí para bajarse los ejemplos y listados del artículo (6 Kb).
Figura 1: "Organización del modo 13h estándar."
Figura 2: "Organización del modo X planar o unchained."
Figura 3: "Calcular el offset en función de las coordenadas del punto."

Santiago Romero


Volver a la tabla de contenidos.