CURSO DE PROGRAMACION GRÁFICA

Artículo 10: MODOS UNCHAINED/X

Autor: (c) Miguel Cubas Serer.
Revista: Programación Actual (Prensa Técnica) nº 10, Enero-1998


NOTA: Ver las figuras de este artículo en el curso de modos X.

Hasta hace poco el modo 13h era el modo gráfico más usado por los programadores, debido a su simplicidad y fácil manejo. Esta entrega vamos a analizar los registros de la VGA, y mediante su utilización, obtendremos nuevos y mejores modos gráficos que nos saquen de la rutina del ya conocido 320x200.

Hasta hace poco tiempo, las tarjetas de vídeo no tenían más de 256K de memoria, de la cual tenían que sacar el mayor rendimiento de ella obteniendo modos gráficos como el modo 13H de 320x200x256 o 640x400x16. A pesar de no tener gran cantidad de vídeo RAM para mejores resoluciones, el modo 13H ha sido utilizado por casi todos los programadores debido a su fácil manejo de memoria lineal. A medida que el hardware va desarrollándose, las tarjetas van adquirieron más memoria VRAM, con lo que la capacidad de obtener más resolución y colores por pixel aumenta notablemente. De todos modos este no es el caso que vamos a tratar en la presente entrega, ya que con un mínimo de memoria de vídeo se van a obtener nuevos y variados modos gráficos en los que poder trabajar a grán velocidad. Cuando, por ejemplo, iniciamos el modo de vídeo 13H, los registros de la VGA se programan automáticamente de forma que nos da la resolución y colores particulares de este modo. Pues bién, en esta entrega nos vamos a encargar de programar nosotros mismos los registros con el fin de conseguir diferentes resoluciones con más de una página de VRAM a nuestra disposición para volcar los gráficos que queramos. Ahora puede que lector se pregunte como es posible que manipulando los registros de la VGA podamos obtener mayores resoluciones, y aun más tener de reserva otras tantas páginas para nuestro uso adicional con tan sólo el mínimo de memoria de vídeo de la que disponemos. Esto es algo sencillo que se va a explicar a continuación.


MODOS CHAINED Y UNCHAINED

A cierto programador llamado Michael Abrash, le llamaba la curiosidad el hecho de haber 256K de memoria de vídeo y que tan sólo se pudieran utilizar 64K (resolución de 320x200), de donde quedaban 192K de memoria libre que no se utilizaba. Esto le hizo pensar que la memoria de la VGA estaba compuesta por 4 segmentos, de los cuales estos no se aprovechaban al 100%. Estos cuatro planos, en el modo 13H, estaban programados de forma que se utilizaban linealmente, y aprovechando una mínima parte de cada uno se obtenía la resolución de 320x200 con 256 colores. La figura 1 muestra la manera en la que se accede, de forma lineal, a la memoria VGA en el modo 13H.

Pués este programador, lo que hizo fue reprogramar el modo 13H cambiando los valores de los registros, y experimentando con ellos intentó dar uso a esa memoria inútil que no se utilizaba. Pero además, esa memoria sería útil para realizar efectos de scroll por hardware, split screen, etc..., con lo que además de tener más resolución tendríamos otras páginas de memoria para trabajar con ellas en estos efectos. Con grán trabajo y esfuerzo, descubrió que la VGA contaba con un bit que si estaba a 0 forzaba a la VGA a usar los 4 planos, como es el caso del modo 640x480x16 que hace uso de estos, y la mayoría de los modos EGA. Y el bit estaba a 1 cuando los modos eran lineales (como el caso del 13H). Este bit es el llamado CHAIN-4, porque encadena los cuatro planos. De ahí los términos chained y unchained que significa que el bit está a 1 o a 0 respectivamente. Por tanto cuando hablamos de modos chained nos referimos a modos lineales y cuando son unchained a los modos planares donde hacemos uso de los cuatro planos. Son los modos unchained a los que nos referimos cuando hablamos de MODOS X, y por tanto los que vamos a tratar en este artículo. Teniendo la idea básica de que es y como se inicializa el MODO X, tenemos grán parte del trabajo hecho. Ahora sabemos que inicializando el modo 13H y cambiando el bit en cuestión, que se hace enviando el valor al puerto correspondiente, forzaríamos a la VGA a gestionar el 13H como un modo de 4 planos. Pero con esto no hemos terminado de programar el MODO X, ahora es necesario modificar algunos registros que den estabilidad al modo gráfico que deseamos establecer, tales como registros de sincronización, refresco, etc... La figura 2 muestra como el modo X utiliza los 4 planos, aprovechando hasta el último rincón de la memoria disponible. Teniendo en cuenta que cada plano dispone de 64000 bytes y tenemos 4 planos, son 256000 bytes (256K) los que podemos utilizar para la programación de los modos gráficos.


LOS REGISTRO DE LA VGA

La VGA trabaja constantemente con unos registros propios que utiliza para la configuración de los diferentes modos gráficos. Estos registros son puertos con unas características específicas, a los cuales se les puede enviar un índice y un valor para obtener unos resultados determinados. Para la modificación de estos es necesario enviar el valor que se quiera a un índice del puerto. Pero no todos los registros se utilizan de igual manera. El anterior artículo trato el tema de los registros de la VGA, y allí se definieron todos los registros y la manera en que debían ser modificados. En esta entrega vamos a dar uso a las funciones que tienen cada uno de estos registros, de manera que tendremos que tener en cuenta lo explicado en el anterior artículo. Si no recordamos mal, anteriormente se explicó la manera de modificar cada tipo de registro, donde se hacía referencia a una variable llamada INDEX. Pués esta variable es una subfunción del registro en cuestión. La tabla 1 contiene todos los registros de la VGA y sus correspondientes puertos. Y las tablas 2 y 3 (links al final del articulo) contienen los índices de los puertos CRTC y VGA Sequencer. El resto de los puertos serán explicados más adelante ya que ahora no vamos a hacer referencia a ellos.


EL MODOS X: INICIALIZACIÓN

Tal y como se decía antes, la programación de los modos unchained que descubrió Michael Abrash trabajando con los registro indocumentados de la VGA, nos van a proporcionar opción de trabajar con nuevas resoluciones y con más memoria que nos dará acceso a más de una página en la que poder volcar nuestro gráficos. Al trabajar, a partir de ahora, con modos unchained, el acceso a los planos para volcar gráficos no va a ser como en el modo 13H, que lo hacíamos de forma lineal. En este caso va a ser necesario activar el plano que se vaya a utilizar en cada momento para la escritura de bytes en el. Pero esto es algo que se verá más adelante. A la hora de programar un MODO X, además de programar el CHAIN-4 es necesario cambiar algunos registros que nos permitan la correcta visualización de los gráficos en la pantalla y evitar parpadeos u otros efectos no deseados. Por lo tanto, los registros que nos muestra la tabla 1 son los utilizados para la inicialización de estos modos. Además de estos registros, existen otros, también de la VGA, que no van a sernos de utilidad, por lo que no vamos a mencionarlos por ahora. Lo primero que debe hacerse para la inicialización de un modo X es activar el modo 13H estandar, y a continuación establecer los nuevos valores para los registros que nos van a cambiar a un modo X. El listado 1 nos muestra la manera en que son programados los puertos para la inicialización del modo 13X (320x200X). Este modo nos va a permitir tener una resolución física de 320x200, como en 13H, pero una resolución lógica de 320x800. Es decir, que tendremos 4 pantallas de 320x200 ya que, como decíamos antes, hemos forzado a la VGA a utilizar los cuatro planos. Como puede verse en el listado de ejemplo, lo primero que se hace es inicializar el 13H para que se establezcan unos valores iniciales en los registros. A continuación modificamos el bit que nos va a permitir el acceso a los 4 planos. Y por último programamos los registros CRTC_ADDR (Cathode Ray Tube Controller) primero, y SEQU_ADDR (VGA Sequencer) después para terminar de ajustar el modo que deseamos.

LISTADO 1: Inicialización de 320x200X

#include <dos.h>         /* para outport() */

#define SEQU_ADDR 0x3c4
#define CRTC_ADDR 0x3d4

void Set_320x200X ( void );

void Set_320x200X ( void )
{
  asm  mov ax, 13h     /* inicializamos modo 13h */
  asm  int 10hM

  outport( SEQU_ADDR, 0x0604 );    /* bit CHAIN-4 a 0 */
  outport( CRTC_ADDR, 0xE317);    /* ajuste modo palabras */
  outport( CRTC_ADDR, 0x0014);     /* ajuste modo dobles palabras */

  outport( SEQU_ADDR , 0x0F02);
  /* seleccionar los cuatro planos para...*/

  asm {       /* ...borrar la pantalla */
       mov ax , 0xA000
       mov es , ax
       xor di , di
       xor ax , ax
       mov cx , 32000
       rep stosw
      }
}

LISTADO 2: Inicializacion de 320x240X

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

#define SEQU_ADDR 0x3c4
#define CRTC_ADDR 0x3d4

void Set_320x240X ( void );

void Set_320x240_X( void )
{

  Set_320x200X( );            /* 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 retrace end y Write */
  outport(0x3D4, 0xDF12);     /* Vertical disp/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 13h 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 organización del modo 13X ( y similares ) en la figura 2.

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. Esto se va pareciendo cada vez más al modo 13h lineal. Con una única diferencia, que hay que posicionarse en el plano correcto antes de calcular el offset. Estos serían los pasos a seguir:

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


SELECCIÓN DE PLANO

Para seleccionar el plano en el que escribir en una VGA, tan solo hay que mandarle el índice o función a realizar ( 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 de seleccionar plano */
 outportb( 0x3c5, 1 )        /* plano 1 */

O bién utilizando la función outport(), que envía un word o un entero (valor de 16 bits), se puede hacer todo de una.


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

Pero como lo que interesa es poner un pixel en pantalla, necesitamos saber donde (en que plano) va a caer dicho byte. Esto es así de fácil, si nos fijamos en la figura 2, en un mismo plano los pixels aumentan de 4 en 4 debido a que existen tres planos más que se encargan de escribir los 3 pixels restantes. Esto es muy sencillo, si tenemos un valor como coordenada X y queremos averiguar a que plano corresponde, deberemos dividir la X entre el número de planos que existan, es decir entre 4 planos. Con esto sabemos que si la operación da como resultado 0, deducimos que el pixel cae en el plano 0. Si de lo contrario, la operación da un resto, sabemos que quiere decir que ese resto nos indica el plano al que corresponde. Este cálculo se puede hacer en C usando el signo de módulo mediante: (X % 4), o bién (X & 3) que podría ser transformado a ASM como: AND AX , 3 donde AX tiene el valor de la X. Y si queremos hacerlo en PASCAL la sentencia sería así: (X MOD 4).

Una vez sabido el plano en el que cae el pixel, hay que indicarle al registro Write-Plane-Mask (0x3C4) cual es el plano mediante un número. Ese número se podría saber en 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 );

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 algún plano.

Los ú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 está, que no es el segundo, sino el tercero puesto que el 0 también se cuenta ). Ahora colocamos 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 ningún plano ya que siempre tenemos que indicar con un bit el plano a activar.

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 número de plano 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 último plano seleccionado.


CALCULO DEL OFFSET

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. Por tanto, una línea es representada en cada plano por 80 pixels. Y al dividir por 4 la X ya calculamos su offset , y si saltamos 80 bytes por cada línea, el resultado es el punto que buscábamos.

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 o mixto, es decir, teniendo 2 páginas de ancho por 2 de alto. Con esto quiere decirse que no siempre ha de multiplicarse la Y por 80 para posicionarse en la línea correcta, sino que dependiendo de la anchura lógica que se tenga la multiplicación será de 80, 160, 240 o 320. 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án colocadas una a continuación de la otra. Este tema será el desarrollado principalmente en el próximo número, donde 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 que se muestra a continuación:


 Offset = (80 * Y) + (X / 4);

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). Cada offset de la memoria sirve para poner 4 puntos, indicándole el plano correcto. Por eso en vez de 320 píxels necesitamos 320/ 4 = 80 para dibujar una línea horizontal completa. 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 poder dibujar, hacer scrolls y otras cosas que ya comentaremos en posteriores artículos. Y sin embargo, 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. En los modos X tenemos otras resoluciones como pueden ser 360x360, 320x400, etc ... donde el número de las paginas de reserva variará dependiendo del modo gráfico al que nos refiramos.


LA FUNCION PUTPIXEL

Desarrollemos por ahora nuestra primera rutina PutPixelX para hacer las algunas 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 offset;
 
 outportb( 0x3c4 , 0x02 );          /* Función seleccionar plano */
 outportb( 0x3c5 , 0x01 << (x & 3) );
  /* Esto calcula automáticamente el plano
    a partir de los 2 últimos bits de la coord.x */  
 offset = (80 * y) + (x / 4);       /* calculamos el offset */
 asm { 
   mov ax , 0xa000
   mov es , ax
   mov di , offset
   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 colocación de los puntos. La división entre cuatro la podemos sustituir por x >> 2 y la multiplicación por 80 es los mismo que multiplicar por 16 más la multiplicación por 64. Aunque parezca algo complicado podemos sustituir la línea


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

por su variación optimizada:


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


PROXIMA ENTREGA

Hasta aquí hemos llegado, por ahora, en el tema del MODO X. En esta entrega hemos tratado únicamente la inicialización a los modos unchained y la manera de calcular el offset en memoria de video para poder dibujar posteriormente pixels en pantalla. En la próxima entrega entraremos más en materia pudiendo aprender nuevas cosas sobre el modo x. Aprenderemos a hacer scrolls vía hardware, scrolls de pantalla partida, cambiar la resolución lógica de la VRAM, y muchas cosas más. Hasta el próximo número.

Pulse aquí para bajarse los ejemplos y listados del artículo (19 Kb).

NOTA: Ver las figuras de este artículo en el curso de modos X.

Santiago Romero


Volver a la tabla de contenidos.