PROGRAMACIÓN DE MODOS X

Artículo 4: RESOLUCIONES VIRTUALES

Autor: (c) Santiago Romero y Miguel Cubas.
Revista: Sólo Programadores (Tower Communications) número 25, Septiembre-1996


Finalizado el tema de los registros, nos dedicamos ahora a las resoluciones de pantallas que nos permitirán hacer scrolles multidireccionales y organizar la memoria de la VGA a nuestro gusto.

La tarjeta de video dispone de al menos 256K de memoria, como se pudo ver en anteriores artículos, con lo que hacen un total de 4 pantallas en 320x200 (y un poco más, ya que un segmento son 65535 bytes, y 320x200 son 64.000).

Lo que se viene a decir en este apartado es que esta memoria se puede manipular con el fin de obtener unas dimensiones determinadas de memoria de video para el MODO X y 13X, es decir, que si se tienen 4 pantallas se pueden organizar de varias maneras, un ejemplo sería programar el MODO 13X para unas dimensiones virtuales de 640x400 con lo que se obtendrían 2x2 pantallas. Puede verse en la figura 1 como están organizadas las pantallas verticalmente; esta es la posición inicial que presenta el Modo 13X al ser iniciado, y que luego puede ser modificado a gusto del programador de acuerdo con el programa que vaya a realizar. También se indica en las figuras 2 y 3 los restantes modos principales de los que se puede disponer en el Modo 13X.

Figura 1. Organizacion 13X.

Hay que tener en cuenta que la forma de calcular el offset para dibujar un punto en pantalla no es la misma en todas las dimensiones, con lo que se tendrá que programar una rutina diferente para cada modalidad de tamaño, debido a que no en un modo de 1x4 pantallas no hay el mismo número de bytes horizontales (80) que en uno de 2x2 (160), como se puede ver en las figuras.

Esto de programar una rutina para cada modo de pantalla es una difícil tarea debido a la gran variedad tamaños que existen, por lo tanto no queda más remedio que crear para cada juego, demo o utilidad gráfica una rutina que funcione en éste, o bién hacer una rutina que se le pueda pasar un parámetro indicando con que modalidad se está trabajando. Más adelante se proporcionará una función que calculará los offsets de diferente forma para cada dimensión de pantalla.

Nótese que la manera de programar la organización de las páginas no afecta a la resolución de pantalla, sino a la manera en que se organizan las páginas virtuales dentro de la memoria, organización que puede ser beneficiosa para determinado tipo de efectos.


VAMOS A LA PRÁCTICA

La manera de programar los registros para obtener estas dimensiones es muy sencilla, tan sólo hay que acceder a un registro y mandar el valor correspondiente para cada acceder a nuestras nuevas resoluciones virtuales. Con esto se accede de otra manera a la memoria, disponiendo de un nuevo ancho y alto.

El registro de la VGA que se utiliza para conseguir esto es el Offset Register, del Cathode Ray Tube Controller (CRTC) cuya definición podemos encontrar en las tablas de registros publicadas en el número anterior de Sólo Programadores, y que podemos ver en la tabla 1.

  TABLA 1:

Puerto: 3d4h, CRCT_ADDR
Index: 13h
Descripción:  3d4h index 13h (r/W): CRTC: Offset register
bit 0-7  Número de bytes por ScanLine (línea horizontal) partido por N,
         donde N es 2 para modos de sistemas de direccionamiento de bytes,
         4 para modos de palabras y 8 para modos de dobles palabras. En
         Modo 13X y X hay que dividir por 2, pues es un modo de bytes
Con la función RegisterOut() de la librería VGAREGS.H, su modificación se reduce a:


 RegisterOut( CRTC_ADDR, 0x13, valor );

No obstante, podemos hacerlo también sin la librería tal y como se explicó en el pasado artículo para reprogramar el CRTC:


 outportb( 0x3D4, 0x13 ); 
 outportb( 0x3D5, valor );

Valor es un byte que indica la anchura, de manera que la forma de leer la memoria cambiará automáticamente teniendo tantos pixels horizontales como hayamos indicado. El valor de la anchura que se introduzca en el puerto no es más que un número entero partido entre dos. Este número entero indica la anchura lógica (contando ya los planos) que tiene cada Scan Line (cada línea horizontal de la pantalla) dividido entre dos. De esta manera, el valor inicial que tiene este registro cuando inicializamos el modo X es 40, ya que disponemos de 80 bytes horizontales por cada línea, como se puede ver en la figura 1. Si quisiéramos colocar el modo X en un modo de, por ejemplo, 2x2 pantallas, tendríamos, como se puede ver en la figura 2, 160 bytes por cada línea (aunque la pantalla siga teniendo 80 bytes por página).
Figura 2. Modo de 2x.2 pantallas

Como hay que partirlo por dos, resultaría lo siguiente:


 RegisterOut( CRTC_ADDR, 0x13, (160/2) );

Pero, ¿por qué hay que dividir entre dos? Muy sencillo, es debido a la simple limitación de que los puertos tan sólo pueden manejar bytes, así que si quisiéramos enviar el valor 320 (4x1 pantallas; 80 bytes*4 pantallas = 320), nos encontraríamos con un número mayor de 255. El sistema que se decidió pasa por dividir por 2 la anchura lógica y enviarlo al puerto:


  valor = (anchura / 2);

Ya que en este artículo se va a trabajar en una resolución de 320x200, se utilizará la memoria de video disponible para dimensionar la pantalla en tres modos principales, que son: 1x4, 2x2 y 4x1 pantallas, aunque el lector se puede crear sus propias resoluciones de páginas probando distintos valores con el Offset Register. En el listado 1 se puede observar como han sido programados estos 3 modos principales.


/*
 SetDimX( char DIM ) 
                                       
 Selección del sistema de organización de memoria en forma de
 pantallas virtuales. Se le pasa el valor YA DIVIDIDO entre 2.
 El motivo de la división entre dos está explicado en el artículo.
 También pueden usarse los defines como parámetros:                          
 Valores de DIM:                                                             
        DIM1x4  -->  variable 'anchura' = 40  (28h)
        DIM2x2  -->  variable 'anchura' = 80  (50h)
        DIM4x1  -->  variable 'anchura' = 160 (A0h)  
*/

void SetDimX ( unsigned char anchura )
{

  if( anchura == DIM1x4 ) modoactual = 0;
  else if( anchura == DIM2x2 ) modoactual = 1;
  else modoactual = 2;

  asm mov dx, 0x3d4        /* CRTC_ADDR */
  asm mov al, 0x13         /* offset register */
  asm out dx, al           /* valor 13h al puerto 0x3d4 */

  asm inc dx                /* cambiamos el puerto al 0x3d5 */
  asm mov al,[anchura]     
  asm out dx, al           /* valor anchura al puerto 0x3d5 */
}


CALCULAR EL OFFSET

Como se ha dicho antes, la forma de calcular el offset para poner un punto en pantalla no es la misma para cada organización de memoria.

Si se desea poner un pixel en 1x4 pantallas, disponemos (ver figura 1) de 80 bytes por línea. En este caso, para calcular el offset del pixel en la posición X,Y usábamos la fórmula:

   Offs = (y*80) + (x/4)

Si inicializamos, por ejemplo, el modo de 2x2 pantallas, aunque sólo tengamos 80 píxels por pantalla, la página virtual que hemos organizado (ver figura 2) nos obliga a saltarnos 160 bytes por línea, con lo que la fórmula quedaría así:

  Offs = (y*160) + (x/4)

Esto supone tener una rutina para cada modo de pantalla, pero hay un cálculo que permite utilizar una sola rutina para los diferentes offsets a calcular. Este cálculo nos permitirá utilizar los tres modos mencionados hasta ahora, que son los más comunes, de manera que si se desean utilizar otras dimensiones, se tendrá que hacer una rutina para cada una de estas.

En el listado 2 se muestra la manera de calcular el offset en las diferentes organizaciones. Como se puede ver en este listado, estan definidos tres modos asignándoles a cada uno de ellos un número. Este número corresponderá a la variable modoactual, definida y utilizada en la rutina que calcula el offset en pantalla. A continuación hay definido un array de 4x3 que indica el offset de comienzo de cada página en estos modos. Aquí ponemos el offset de cada página para los 3 modos de organización X, para agilizar los cálculos.


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

  OutPortb( SEQU_ADDR, 0x02 );
                   /* función seleccionar plano */
  OutPortb( SEQU_ADDR+1, 0x01 << ( x & 3 ) );    

                    /* calculamos el offset */
  offs = (((y<<6) + (y<<4))<<  Array_Desp[modoactual] );
  offs += (x>>2);

asm {
        push es
        mov ax , 0xa000
        mov es , ax
        mov di , offs
        mov al , color
        stosb                                      
        pop es          /* ponemos el punto */
    }
}

Antes de utilizar estas rutinas hay que llamar a las funciones SetModennn(); y SetDimX() indicándoles en que modo se va a trabajar y la página que se utilizará para volcar los gráficos, para que actualicen los valores de las variables necesarias para nuestra nueva librería.

En este listado podemos ver el array que contiene tres valores que nos ayudarán a adaptar el offset calculado para la coordenada Y al modo que corresponda. Como puede verse, este array contiene los valores 0, 1 y 2. Lo que se hace es calcular 80*y + x, y el resultado lo multiplicamos por 1, 2 o 4 según el modo de pantalla que tengamos inicializado.

Es decir, si tenemos como coordenada Y el valor 30 y calculamos su offset para una página (80), tendremos que multiplicar por cuatro para obtener el offset de Y en el modo 4x1.

El motivo por el que se seleccionan estos valores en funciones es cuestión de organización y tiempo de acceso, para no tener que pasar estos parámetros a las funciones y sufrir su introducción y extracción de la pila, los definimos de forma global y los inicializamos una sola vez al comienzo del programa, ya que de lo contrario habría que asignar el valor cada vez que se llame a la función.

Una vez se hayan asignado los valores de modoactual y de pagina, se debe llamar a la función mandándoles las coordenadas X e Y y la función se encargará de devolver un valor que corresponderá al offset pedido. La manera de calcular el offset es muy sencilla, como se puede observar en el listado 2 lo primero que se calcula es el offset para una pantalla de 320x200 sin contar con la coordenada X. A este valor lo tenemos que multiplicar por un número tal que se adapte al modo correspondiente, es decir, si tenemos unas dimensiones de 4x1 tendremos que multiplicar este offset por cuatro ya que la anchura de nuestra area gráfica es cuatro veces más grande. Para evitar engorrosas multiplicaciones usamos la instrucción SHL (<<), de manera que, dependiendo del modo en el que se esté, se desplazará (y*80) tantas veces como sea necesario para multiplicar por 1, 2 o 4, dependiendo del valor de la variable modoactual. Un ejemplo de esto podría ser:

  Offs = (y*80) << Array_Des[ modo_video ]

Donde Array_Des sería un array que contuviera los valores 0, 1 y 2. De esta manera, si modo_video es 0 (1x4) la multiplicación resultaría (y*80) * 1. Si modo_video es 1, sería (y*80)*2, etc... De esta ingeniosa manera, por desplazamientos, conseguimos calcular el offset para cualquier modo de pantalla.

El siguiente paso es sumar a este resultado un número con tal de colocarse en el principio de la página seleccionada y en la coordenada Y que corresponda, calculada en el paso anterior. Finalmente al resultado de todo este proceso se le suma la coordenada X dividida entre cuatro, ya que trabajamos en MODO X, y hemos de calcular y seleccionar el plano en el que cae cada pixel.

Para finalizar devolvemos el resultado al lugar donde ha sido llamada esta función, que puede ser utilizado para una función que ponga un pixel o para cualquier otra.


LAS PÁGINAS VIRTUALES

Veamos ahora de donde salen estas páginas y de cuantas disponemos. En modo 13X, al gestionarse una pantalla completa con tan sólo 80 bytes, pués estos 80 bytes tienen acceso a 4 pixels, nos encontramos que la pantalla está formada por 80*200 = 16.000 bytes. Como el segmento de la video memoria tiene 65.536 bytes, resulta que disponemos de 65.536 / 16.000 = 4x1 páginas virtuales, que podemos colocar de cualquier manera ( 1x4, 2x2, 4x1 y, en definitiva, cualquier resolución que sume 4 pantallas, como 2.8x1.7).

Figura 3. Modo 4x1

En modo X, debido a las 40 líneas verticales extra disponemos de 3,6 páginas, cuya mejor colocación podría ser 2x3,6, 2x1,9 y 4x0,9 páginas.


UN EJEMPLO DE USO DEL CRTC:
SCROLL VERTICAL POR HARDWARE

Basándonos en las funciones que contiene el CRCT Controller explicadas en el número anterior pueden desarrollarse efectos profesionales que resultan más dificiles de realizar en el modo estándar 13h, tales como el scroll hardware vertical para modo X en sistema de 1x4 pantallas.

El scroll vertical en los modos unchained es un recurso muy utilizado en primeras demos de muchos programadores debido a su sencillez y suavidad.

Intentar hacer un scroll mediante buffers virtuales y vuelques a pantalla de estos buffers puede llegar a requerir una compleja sincronización si se quiere evitar la diferencia de velocidad que existe entre los distintos modelos de PC, pero puede resultar sencillo si nos apoyamos en los conocimientos sobre el CRTC Controller que hemos adquirido y tomamos como base de nuestro scroll el retrazo, que prácticamente nos asegura la misma velocidad en distintos modelos, pues éste ocurre siempre entre 60 y 70 veces por segundo, según el monitor y modo de video establecido.


CAMBIOS DE PÁGINA

Para comenzar, puede verse en la figura 1 la organización de la video-memoria (segmento A000h) cuando está inicializado el modo X en 320x200. Como puede verse, las cuatro páginas de que se dispone están colocadas verticalmente una sobre otra, de manera que los bytes que representan el gráfico de la página 0 están al principio del segmento, desde el offset 0 hasta el offset 16.000. A partir del offset 16.000 (80x200), comienzan los bytes que representan el gráfico de la página 1, y así sucesivamente encontramos las siguientes páginas en el offset 32.000 (página 2) y 48.000 (página 3).

El haz de electrones, al redibujar la pantalla, comienza a leer bytes a partir del offset correspondiente a la página actual y los representa en pantalla con sus distintas tonalidades según los valores del DAC de video (según la paleta) que estén establecidos en ese momento. Si hubiese alguna manera de cambiar este offset por defecto a 80x200, por ejemplo, entonces lo que el haz de electrones redibujaría durante el siguiente ciclo, como se puede ver en la figura 1, sería la página 1, entendiendo como página 0 la que aparece por defecto para las operaciones gráficas.

La manera de hacer esto, de cambiar la dirección que se toma como la esquina superior izquierda de nuestra "ventana virtual", dentro del marco de la video memoria, nos la brinda el CRTC Controller mediante el registro Start Address Register,índices 0ch y 0dh. Al registro de índice 0ch (Start Address High) se le proporciona el byte alto del nuevo offset y al que tiene como índice 0dh (Start Address Low) se le pasa el byte bajo. Hubo de ser dividido en dos registros porque es la mejor manera de pasar un valor de 16 bits (un offset) mediante puertos, que transmiten valores de 8 bits.

De esta manera, si queremos que aparezcan el pantalla lo que haya en la segunda página, de offset 16.000, se haría de la siguiente manera mediante lenguaje ensamblador:


  mov     [offset], 16000
  mov     dx, CRTC_ADDR
  mov     bx, [offset]
  mov     ax, bx
  out     dx, ax        ;Start Address low
  mov     ax, cx
  out     dx, ax        ;Start Address high

Gracias a las funciones que controla el CRTC podemos colocar la "ventana virtual" en cualquier lugar de la video memoria, no sólo de página en página, sino que se puede seleccionar como offset, por ejemplo, el valor 8.000 (80x100) y visualizar en pantalla (figura 4) las 100 últimas líneas de la página 0 y las 100 primeras de la página 1. Esta última opción es la que usaremos para realizar el scroll vertical.

Figura 4. Cambio del SAReg.


LA FUNCION SETADDRESS

En la librería VGAREGS.H disponemos desde el número anterior de la función SetAddress(), a la que se le pasa un valor de 16 bits que constituye el offset donde se desea colocar el "marco virtual".

Por supuesto, esta función está basada en el Start Address Register, como se puede ver en el listado 3.


void SetAddress( unsigned int offst )
{
 asm {
   mov dx, 3d4h             /* CRTC Controller */
   mov bx, [offst]          /* offset en memoria */
   mov al, 0ch              /* indice 0ch */
   mov ah, bh               /* byte alto */
   out dx, ax
   mov al, 0dh              /* indice 0dh */
   mov ah, bl               /* byte bajo */
   out dx, ax
     }
}

La función SetPage() de MODOX.H utiliza el Start Address Register (ver tabla 2) para colocar el marco virtual en cualquiera de las páginas, contando como página 0 la inicial, situadas en los offsets preparados en uno de los arrays globales de la librería.

  TABLA 2:

Puerto: 3d4h, CRTC_ADDR
Index:  0Ch y 0Dh.
Descripción:   3d4h index  Ch (r/W): CRTC: Start Address High Register:
bit 0-7  8 bits superiores de la dirección en memoria (offset lógico)
         que considerar como inicio al redibujar la pantalla.


                3d4h index  Dh (r/W): CRTC: Start Address Low Register
bit 0-7  8 bits inferiores de la dirección en memoria (offset lógico)
         que considerar como inicio al redibujar la pantalla.


EL ALGORITMO DE SCROLL

Imaginemos una imagen situada en la página 1 de pantalla en modo X. Esta imagen estaría situada, por lo tanto, a partir del offset 16.000 en la video-RAM. Este esquema puede verse también en la figura 4, donde se deduce que, si bajamos nuestra ventana virtual una línea hacia abajo cada vez, iremos perdiendo de vista la página 0 e irá apareciendo la página 1 desde abajo hacia arriba.

El efecto de scroll de una pantalla es similar al desarrolado por el siguiente algoritmo:

1. Dibujar en la página 1 el gráfico a scrollear.
2. Borrar la página 0.
3. Cambiar el Start Address Register para que apunte una línea más abajo que donde estaba antes.
4. Esperar un retrazo vertical para ralentizar el efecto.
5. Si aún no hemos alcanzado el principio de la página 1, saltar al paso 3.

Este algoritmo hace que durante su primera ejecución, veamos en pantalla las 200 líneas de la página 0 y 0 líneas de la página 1. Al avanzar una línea hacia abajo nuestro marco virtual, se visualizarán 199 líneas de la página 0 y 1 de la página 1.

Tras varias repeticiones del bucle nos encontramos con que vemos 100 líneas de la primera página y 100 de la segunda, hasta que finalmente, tras su última ejecución, vemos las 200 líneas de la página 1. El resultado de estas ejecuciones es el aparente desplazamiento de la imagen que hay en la página 1 sobre la pantalla.


LA BASE DEL SCROLL

La base del scroll consiste el cambio del Start Address para colocarlo cada vez una línea más abajo. La librería VGAREGS.H dispone ahora de nuevas funciones de control específicas para el Start Address. La primera de estas funciones SetAddress(), modifica el valor de Start Address dándole el del argumento que se le pasa. La segunda, SetPage(), se basa en la función anterior para facilitar la selección de página usando números de página en vez de offsets en memoria, considerando 0 como la página inicial y 3 como la última página en memoria.

Para realizar el scroll es necesario el uso de la función SetAddress() para avanzar nuestro "puntero" virtual en memoria en 80 unidades, que es justo la resolución horizontal que tienen los modos unchained que estamos estudiando (320 píxels / 4 planos = 80 píxels por plano).

Teniendo una imagen situada en la página 1 y teniendo la página 0 vacía, veamos el código C que realizaría un scroll vertical de una sola pantalla:


 for( f=0; f<200; f++ )
 {
  SetAddress( (f*80) );
  WaitVRetrace();
 }

El resultado de este código sería algo parecido a lo que puede verse en la siguiente figura:

Figura 5. Efecto Scroll

Resulta necesario el retrazo vertical ya que sin éste el efecto resultaría demasiado rápido para poder verse. Por supuesto, no hemos de limitarnos a scrollear sólo una pantalla. Existe la posibilidad de realizar scrolles a cuatro pantallas, simular "rebotes" de la pantalla de arriba a abajo ,etc....


PRÓXIMA ENTREGA

Nuestro próximo objetivo son scrolles horizontales, verticales y multidireccionales, tanto de las 4 pantallas del modo 13X como en un sistema que permitirá usar tantas pantallas como se necesites, usando tan sólo 2 páginas. Hasta entonces, en los ejemplos del CD encontrareis un documento de resumen de lo visto hasta ahora.

Figura 1: "Organización del modo X (1x4)."
Figura 2: "Organización del modo X (2x2)."
Figura 3: "Organización del modo X (4x1)."
Figura 4: "Modificación del Start Address Register."
Figura 5: "Resultado del efecto scroll."

Santiago Romero y Miguel Cubas


Volver a la tabla de contenidos.