PROGRAMACIÓN DE MODOS X

Artículo 5: EL SCROLL EN MODO X

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


En el anterior artículo tratamos el tema de la memoria en cuanto a su organización. Después de comprender como se programan estas dimensiones de pantalla, podemos adentrarnos al mundo de los scrolls de pantalla, un tema menos teórico a diferencia con los anteriores artículos, y que podremos poner en práctica para multitud de cosas.

Con el anterior artículo se terminó de explicar la manera con la que se puede programar al anchura y altura de las pantallas virtuales en Modo X, entendiendo como pantallas virtuales el espacio de memoria direccionable utilizada para guardar los contenidos gráficos. También se trató muy por encima la utilidad que tenia el CRTC en cuanto al scroll y el algoritmo necesario para que este se pudiera realizar. En este artículo se profundiza en el tema de los scrolles a nivel más profesional.

El registro Set Start Adress del CRTC, estudiado en anteriores artículos, es el encargado de realizar todo aquello que se va a tratar en el presente texto, dedicado integramente a los scrolles. Además se requieren otros registros para adaptar el scroll a lo que el programador desea realizar.

El scroll es un tema que inquieta a la mayoría de los programadores, principalmente a los iniciados en la programación gráfica, debido a su complejidad. Por este motivo, y ya adentrándonos en el Modo X, se va explicar como hacer un scroll en todas direcciones de manera eficaz, sin parpadeos ni diferencias de velocidad y de una forma suave, ideal para realizar demostraciones gráficas o juegos, sistemas que usan muchos juegos de la actualidad. Cuando se vea en el modo 320x200 un scroll tan suave como la velocidad de retrazado, estais delante de un juego realizado en Modo X con estas técnicas.


EL SCROLL VERTICAL

Este tipo de scroll es el más fácil de realizar y posiblemente el más utilizado por los programadores en sus demostraciones. Un motivo por el que este scroll es más cómodo de utilizar es por la disposición de las pantallas graficas.

Una vez inicializado el modo 13X o X, se presenta automáticamente una organización determinada de las pantallas, que vienen a ser siempre 4, situadas por defecto verticalmente, una encima de otra. Esta es la organización planar que toma por defecto el modo X debido a las rutinas que utilizamos.

Otros motivos por los que el scroll es más facil de realizar así ya se irán conociendo más adelante. Pasemos ahora al scroll vertical en sí:

Una vez volcado los gráficos de la manera adecuada en cada una de las páginas que se tienen en Modo X, es necesario utilizar el registro CRTC y los índices 0Ch y 0Dh para poder desplazar el "marco virtual" de la VGA (lo que vemos) a lo largo de la memoria gráfica (todas las imágenes de que disponemos). Como ya se sabe, estos últimos registros son los encargados de indicar la posición de comienzo a partir de la cual el haz de electrones del monitor tiene que dibujar en la pantalla el mapa de bits (imagen) que le hayamos indicado mediante el uso de estos registros (figura 1).

Figura 1. Organiz. para el scroll vertical.

Incrementando continuamente el valor de este registro para avanzar una línea completa se podrá observar un scroll, siempre y cuando antes de llamar a este registro se halla esperado un retrazo vertical, encargado de ralentizar la operación para obtener un scroll tan suave.

Ya que la intención es desplazar la imagen verticalmente, para desplazarla será necesario incrementar el valor 80 unidades cada vez sobre el valor total, que se mandará al registro 0Ch y 0Dh del CRTC. Como ya sabemos, 320 pixels en la pantalla son 80 en Modo X (320 pixels / 4 planos = 80 pixels por plano), por lo tanto cada vez que se desplacen 80 pixels, la coordenada Y se moverá en la pantalla, es decir se habrá scrolleado el gráfico en el monitor. La manera de utilizar este registro es la siguiente:


 outportb (CRTC_ADDR , 0x0C);
 outportb (CRTC_ADDR+1 , valor >> 8);    /* byte alto */
 outportb (CRTC_ADDR , 0x0D);
 outportb (CRTC_ADDR+1 , valor & 0xFF);        /* byte bajo */

El motivo por el que hay que utilizar dos indices es el siguiente: el valor que mandamos al registro de la VGA es un WORD, con lo que la única manera de introducirlo, es utilizando dos índices que contengan cada uno un byte, ya que los puertos del PC acceden únicamente a bytes, no a words.

En el listado 1 se puede ver la manera en la que se puede programar este tipo de scroll. Como ya se ha podido observar es muy sencillo, solo hay que incrementar la variable en 80 unidades para que se desplace una linea hacia abajo. Esta variable se escribirá en el registro que determina el comienzo de la dirección en memoria cada vez que se incremente, con lo que obtendremos en pantalla un aparente desplazamiento de la imagen, mientras que lo que desplazamos en realidad es, a modo de ejemplo, el monitor.


   Listado 1:  Programación de un scroll vertical

#include <dos.h>
#include "modox.h"

unsigned int x;

void main()
{

 Set_320x200X();             /* Programamos Modo 13X */
 SelectDimX (40);          /* Organización pantalla 1x4 */
 SetModoActual ( 0 );          /* Modo actual 1x4 */
 SetPage(0);            /* Posición página inicial 0 */

 LoadImages ();                /* Cargamos imagen */

 x = 0; 
 while (x < 600)   /* scrollea 3 pantallas */
 {  
    VRetrace ();     /* Espera retrazo vertical */
                     /* Bloque que escribe en el CRTC para */
                     /* modificar el Set Start Address */
  asm {
        mov ax, CRTC_ADDR
        mov al, 0Ch
        out dx, al
        mov ax, [x]
        shr ax, 8           /* byte alto del WORD */
        inc dx
        out dx, al
        dec dx
        mov al, 0Dh
        out dx, al
        inc dx
        mov ax, [x]
        and ax, 0FFh        /* byte bajo del WORD */
        out dx, al
    }    
      x = x + 80;         /* Incremento en 80 que */
                     /* desplazará un linea hacia abajo */

  }

  asm mov ax , 0003h	
  asm int 10h
}


EL SCROLL HORIZONTAL

Ahora se empieza a complicar el tema con la realización de este tipo de scroll, debido a que existen varias maneras de hacerlo. Antes de empezar con el scroll se tiene que programar la dimensión que va a tener la memoria.

Inicialmente las pantallas están organizadas en sentido vertical, y ahora se tienen que poner en horizontal. Como se explicó en los anteriores artículos, el tamaño se programa con el CRTC y el índice 13h (Offset Register). Una vez se tengan 4 pantallas seguidas podemos utilizar los índices 0Ch y 0Dh del CRTC para el scroll. Incrementando en 1 la variable que será escrita en estos últimos registros, se puede observar el desplazamiento horizontal.

Un punto que hay que tener en cuenta siempre que se realice este tipo de scroll, es que cuando se incrementa la variable en 1 se desplaza la pantalla en 4 pixels. Esto es debido a los 4 planos del Modo X. El hecho de que se desplace en 4 pixels no indica que el scroll no vaya a ser suave, sino que la velocidad es cuatro veces mayor (y más suave) que si se realizara pixel a pixel. El listado 2 indica la manera de realizar el scroll saltando cada incremento 4 pixels.


 Listado 2: Programación de un scroll horizontal

#include <dos.h>
#include "modox.h"

unsigned int x;

void main()
{

  Set_320x200X();             /* Programamos Modo 13X */
  SelectDimX (160);         /* Organización pantalla 4x1 */
  SetModoActual ( 2 );          /* Modo actual 4x1 */
  SetPage(0);               /* Posición página inicial 0 */

  LoadImages ();                /* Cargamos imagen */
  x = 0; 
  
  while (x < 240)         /* scrollea 3 pantallas */        
  {
    VRetrace();         /* Espera retrazo vertical */
        
   asm {
        mov ax, CRTC_ADDR
        mov al, 0Ch
        out dx, al
        mov ax, [x]
        shr ax, 8         /* byte alto del WORD */
        inc dx
        out dx, al
        dec dx
        mov al, 0Dh
        out dx, al
        inc dx
        mov ax, [x]
        and ax, 0FFh      /* byte bajo del WORD */
        out dx, al
    }    
   x++;         /* Incremento en 1*/
 }

 asm mov ax , 0003h	
 asm int 10h
}

Si de lo contrario, se desea realizar el scroll incrementando y desplazando la pantalla en 1 pixel, se tendrá que recurrir al HPP (Horizontal Pixel Panning Register), encargado de realizar este trabajo. Este registro se encuentra en el índice 13h del ATTRIBUTE CONTROLLER (3C0h), explicado en los artículos que trataban los registros de la VGA, pero este es un tema mucho más complejo de explicar y que requiere mucha experimentación por parte del programador, siendo necesario primero entender todos los sistemas simples de scroll, con los que podemos hacer efectos como los que hay en los ejemplos del CD.


EL SCROLL MULTIDIRECCIONAL

Una vez se domine la manera de realizar estos modelos de scroll, se puede efectuar una combinación muy util para juegos, introducciones o demostraciones gráficas.

Utilizando el índice 0Ch y 0Dh del CRTC, se pueden combinar los scrolls como muestra el listado 3. En los anteriores tipos de scroll se programaba el registro Set Start Address una linea más en dirección vertical o bién en dirección horizontal. Para hacer un scroll con trayectoria en diagonal, se debe que calcular el offset correspondiente a los valores X e Y (el listado 3 hace esto) y posteriormente mandarlo al puerto e índice correspondiente.


  Listado 3: Programación de un scroll multidireccional

#include <dos.h>
#include "modox.h"

unsigned int x , y;
char inc_x , inc_y;

void main()
{

 Set_320x200X();             /* Programamos Modo 13X */
 SelectDimX (160);         /* Organización pantalla 4x1 */
 SetModoActual ( 2 );          /* Modo actual 4x1 */
 SetPage(0);               /* Posición página inicial 0 */

  LoadImages ();                /* Cargamos imagen */

  x = 1;
  y = 1;
  inc_x = 1;
  inc_Y = 1;

  while (1)             /* condicion infinita */
  {
    VRetrace ();     /* Espera retrazo vertical */
    asm {
        mov ax, CRTC_ADDR
        mov al, 0Ch
        out dx, al
        mov ax, [x]
        shr ax, 8           /* byte alto del WORD */
        inc dx
        out dx, al
        dec dx
        mov al, 0Dh
        out dx, al
        inc dx
        mov ax, [x]
        and ax, 0FFh        /* byte bajo del WORD */
        out dx, al
    }
    x = x + inc_x;       /* Incremento en 1 */
    y = y + inc_y;
    if (x <= 0 || x >= 80) inc_x = inc_x * -1;
    if (y <= 0 || y >= 200) inc_y = inc_y * -1;

    asm mov ah, 1      /* comprobación de tecla */
    asm int 16h
    jnz Exit

 }

 Exit:;
 asm mov ax , 0003h	
 asm int 10h

}

Se pueden crear buenos efectos utilizando el scroll en cuatro direcciones, calculando siempre primero el offset donde empezar a dibujar nuestra ventana virtual, y teniendo dibujado dentro de la video memoria algún gráfico contínuo de 640x400 pixels (2x2 pantallas), para realizar un scroll con las cuatro pantallas encadenadas, como en EJEMPLO3.EXE, donde visualizamos un gráfico de este tamaño dentro de la video memoria.


EL SCROLL CON SPLIT-SCREEN (pantalla partida)

Este tipo de scroll, como su nombre indica, determina la pantalla de forma partida, en la cual existe una zona movible y otra estática. La naturaleza de este efecto radica en el desplazamiento de una zona de la pantalla mientras que otra zona se queda fija superpuesta a la zona anterior (siempre aparece encima), todo esto por hardware, sin necesidad alguna de rutinas de dibujo por nuestra parte. Tenemos así la posibilidad de crear marcadores, ventanas de ayuda e información , etc. en medio de la pantalla de nuestro programa, juego o demo, como en el caso de INTROX, donde dejamos fijas 70 líneas con información en la parte inferior de la pantalla, pudiendo observar en las 130 líneas superiores un desplazamiento de pantalla emulando un paisaje desértico.

El secreto está en el registro Line Compare o Split-Screen. Este registro es el encargado de determinar la línea en la cual se divide la pantalla. En la figura 2 se puede observar la estructura de la pantalla en esta modalidad.

Figura 2. SplitScreen

El funcionamiento de este registro es muy sencillo: a simple vista se puede ver como funciona, viendo que a partir de una determinada línea se para de volcar la memoria, para volver de nuevo al principio de la video memoria (offset 0). Pero lo que interesa es como funciona el registro internamente, y conociéndolo poder utilizarlo de manera que se puedan crear efectos de calidad profesional que podemos ver en demos de grupos técnicamente excepcionales.

Internamente la VGA trabaja de forma que, durante la representación de una imagen en pantalla, esta va contando permanentemente la linea actual. En los modos de 200 lineas, como pueden ser el 13h o X, el valor se mueve (a causa de la duplicación de lineas que más adelante se explicará) entre 0 y 400. Este número se encuentra en un registro al cual no se puede acceder a través de puertos, pero mediante otro registro (Line Compare que se encuentra en el CRTC, índice 18h) se puede utilizar para una comparación de línea. La VGA cuenta internamente la línea actual que está dibujando de video memoria a pantalla, mientras este registro compara el valor que contiene, con el valor que la VGA cuenta. De este modo cuando el valor es alcanzado, el contador de direcciones se carga con un 0, lo que indica que la representación comienza en esta línea con los datos que se encuentrán en el Offset 0 de la memoria de video. En pocas palabras, el Line Compare Register actúa como un límite en el que la VGA pasa a redibujar a partir del offset 0, de manera que vemos un número determinado de líneas de la página actual, y cuando la VGA llega al límite, comienza a redibujar desde el offset 0, pudiendo así ver media pantalla de la página 1 y media de la 0 al mismo tiempo que se realiza un scroll.

Dado que el número de línea no se puede guardar en 8 bits (ya que la VGA no reconoce ningún modo con menos de 350 líneas y los modos de 200 líneas son generados por 400 líneas físicas; a esto se debe la duplicación de lineas de la que antes se hablaba), el registro Line Compare suele estar dividido en tres registros (que contienen más bits para poder representar valores 0-350), teniendo en las tarjetas Super-VGA hasta cuatro. En el listado 4 se puede observar, para el manejo del Line Compare, la utilización de extensiones de los bits del Line Compare, como el OverFlow Register.


 Listado 4:  Función que permite programar una Split-Screen

void SplitScreen ( unsigned char );

void SplitScreen ( unsigned char row )
{
asm {   
    mov  bl, row      /* Screen-Splitting en línea row */
    xor  bh, bh
    shl  bx, 1        /* duplicamos lineas */
    mov  cx, bx

    mov  dx, CRTC_ADDR
    mov  al, 07h        /* Reg. 7 (Overflow Low) */
    out  dx, al
    inc  dx
    in   al, dx
    and  al, 11101111b  /* Cargar bit 4 con bit 8  */
    shr  cx, 4
    and  cl, 16
    or   al, cl
    out  dx, al

    dec  dx                
    mov  al, 09h        /* Reg. 9 (Maximum Row Address) */
    out  dx, al
    inc  dx
    in   al, dx
    and  al, 10111111b  /* Cargar bit 6 con bit 9 */
    shr  bl, 3
    and  bl, 64
    or   al, bl
    out  dx, al

    dec  dx
    mov  al , 18h        /* Reg. 18h (Line Compare/Split Screen) */
    mov  ah , row        /* activar los restantes 8 bits */
    shl  ah , 1
    out  dx , ax
    }
}

El listado 4 se encarga de utilizar el Line Compare accediendo a dos registros más. Esto se hace de una manera muy sencilla:

El primero de los registros es el Overflow Register (CRTC, índice 7). El bit 4 de este mismo registro será el utilizado en este caso, que se encarga concretamente de cargar en su lugar el bit 8 del Line Compare. El bit 4 del registro Overflow es el único entre los demás que no está protegido por el bit de protección del registro 11h. El bit 9 del registro de comparación de linea se encuentra en el segundo registro que se tiene que utilizar, que es el Maximum Row Address (CRTC,registro 9), concretamente en el bit 6. Para programar por tanto el Line Compare se tendrán que cargar los bits mediante desplazamientos y enmascaramiendos entre estos registros. Este trabajo es realizado por la rutina que se puede observar en el listado 4.

Lo primero que nos indica el procedimiento de este listado es la duplicación del valor en el modo de 200 líneas. Después se selecciona el Overflow Register para cargar en el bit 4 el bit 8 del valor de la línea mediante desplazamiento y enmascaramiento del mismo. Para esto hay que desplazar 4 bits hacia la derecha en el valor de la línea (SHR valor,4) con tal de dejar el bit 8 en la posición del bit 4.

A continuación se realiza con este último valor obtenido,un AND con 16 para saber si dicho bit(bit 8 del valor, ahora bit 4) está activado o no. Una vez hechos los cálculos se activa o no el bit 4 del Overflow Register dependiendo de si lo está o no al hacer el AND con 16.

Estos mismos cálculos se realizan ahora con el bit 9 del la línea y el bit 6 del registro 9h. Finalmente se colocan los restantes 8 bits del número de línea en el registro 18h (Line Compare o Split Screen).


 Listado 5:  Programación de un scroll con Split-Screen

#include <dos.h>
#include "modox.h"
#include "efectx.h"

unsigned int x , y;
char inc_x , inc_y;

void main()
{

 Set_320x200X();             /* Programamos Modo 13X */
 SelectDimX (160);         /* Organización pantalla 4x1 */
 SetModoActual ( 2 );          /* Modo actual 4x1 */
 SetPage(0);               /* Posición página inicial 0 */

 LoadImages ();                /* Cargamos imagen */

 x = 1; 
 y = 1;
 inc_x = 1;
 inc_Y = 1;

 SplitScreen ( 150 );        /* a partir de la línea 150 */
                             /* se encuentra la zona fija */

 while (1)
 {
   VRetrace ();     /* Espera retrazo vertical */
   
   /* Bloque que escribe en el CRTC para */
   /* modificar el Set Start Adress */
   asm {
      mov ax, CRTC_ADDR
     mov al, 0Ch
     out dx, al
     mov ax, [x]
     shr ax, 8        /* byte alto del WORD */
     inc dx
     out dx, al
     dec dx
     mov al, 0Dh
     out dx, al
     inc dx
     mov ax, [x]
     and ax, 0FFh     /* byte bajo del WORD */
     out dx, al
     }
     x = x + inc_x;       /* Incremento en 1 */
     y = y + inc_y;
     if (x <= 0 || x >= 80) inc_x = inc_x * -1;
     if (y <= 0 || y >= 200) inc_y = inc_y * -1;

     asm mov ah , 1    /* comprobación de tecla */
     asm int 16h
     jnz Exit

 }

 Exit:;
 asm mov ax , 0003h	
 asm int 10h
}


EN LA PRÓXIMA ENTREGA.

En la próxima (y última) entrega de esta serie de artículos veremos rutinas importantes de volcado de buffers y sprites y visualización de PCXs y rutinas de visualización de GIFs en modo X. Todo un lujo dedicado a las personas que nos han escrito y apoyado con sus comentarios sobre este curso de Modo X. Hasta el mes que viene.

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

Figura 1: "Estructura del Scroll vertical."
Figura 2: "Estructura del Split Screen."

Santiago Romero


Volver a la tabla de contenidos.