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.
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).
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 }
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.
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 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.
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 }
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