Se verán a continuación una serie de procedimientos con los que se completa la librería personal que cada lector debe poseer para el desarrollo de aplicaciones gráficas, demos y juegos. Hay muchas funciones gráficas que se podrían explicar (líneas, dibujo de polígonos, escalado de imágenes, etc.), pero al diseñar este artículo hemos de ceñirnos al modo X, pues la programación gráfica de este tipo de efectos ya está contemplada en otras secciones de la revista.
Por lo tanto, en el presente texto se comentarán únicamente aquellas primitivas (líneas, buffers, sprites, etc.) cuya comprensión implique que el lector sea capaz de desarrollar sus propias rutinas para otros efectos. Esto quiere decir que dibujar líneas horizontales o verticales es mucho más que lo que se obtiene en pantalla, pues significa entender la manera de organizar algunos algoritmos dentro de los modos unchained.
Queda claro que las rutinas que se desarrollarán actúan directamente sobre la pantalla de la VGA de una manera didáctica. Para aplicaciones que requieran elevadas velocidades todos los cálculos deberán realizarse en una pantalla virtual de, por ejemplo, 320x200 y volcarse a la Video Ram. Para ello desarrollaremos funciones de vuelque de buffers y segmentos de memoria, de manera que al dibujar en estos buffers (lineales) se podrán usar las funciones normales de dibujo en modo estándar 13h. De esta manera, se puede usar cualquier función de dibujo (elipses, círculos, gráficos 3D, etc.,) que ya se posean dentro de nuestros programas en Modo X.

Al tener todos los puntos de la línea en el mismo plano, sólo hemos de calcular el plano en que cae el primer punto y escribir los diversos pixels de la línea hasta llegar a la coordenada Y2. Con este tipo de líneas nos evitamos cambiar de plano para cada punto a dibujar, hecho en el que radica su velocidad.
Veamos el algoritmo que describe estas acciones:
Dibujo de una línea vertical entre (X,Y1) y (X,Y2):
Calcular plano para (X,Y1).
Seleccionar plano de la VGA.
Calcular el offset del punto (X,Y1).
Hacer registros ES:DI = Offset calculado.
Poner color en registro AL.
Mientras no lleguemos a Y2:
Poner punto en ES:DI.
Incrementar DI en 80 (sig. punto).
Fin Mientras
En el listado 1 puede verse el código C y Ensamblador resultante de este
algoritmo. Este es bastante rápido y compacto, aunque debe hacerse notar que
en otras Resoluciones Virtuales no habría que incrementar en 80 el valor del
registro DI, sino que habría que tener en cuenta la anchura de la pantalla,
según el modo de pantalla actual (2x2 pantallas=160 bytes y 4x1=320 bytes
horizontales), como se explicó en el artículo dedicado a las resoluciones
virtuales que adoptaba la VGA en modo X. El hecho de que incrementemos DI en
79 y no en 80 es debido a que stosb, que coloca el valor que contenga AL en la
posición de memoria especificada por ES:DI, ya incrementa DI en una unidad
tras escribir el byte, por lo que debemos saltarnos en este caso 79 bytes más
para posicionarnos en el siguiente punto de la línea vertical. Todas las
funciones que veremos a continuación están desarrolladas para el modo 13X en
sistema 1x4 pantallas, pero son fácilmente adaptables a cualquier otro modo
que vayamos a utilizar en nuestros programas.
Listado 1: Dibujo de líneas verticales
void DrawVLine( int x, char y1, char y2, char color )
{
char alto;
unsigned int offs;
alto = y2-y1; /* y1 debe ser < y2 */
OutPortb( SEQU_ADDR , 0x02 ); /* seleccionar plano */
OutPortb( SEQU_ADDR+1, 0x01 << ( x & 3 ) );
offs = (y1<<6) + (y1<<4) + (x>>2);
/* offset */
asm {
mov ax, 0xa000
mov es, ax
mov di, [offs] /* ES:DI = (X,Y1) */
mov al, [color] /* AL = Color */
mov cl, [alto] /* CL = Altura */
}
VLineLoop:
asm {
stosb /* ES:DI = AL (y DI++) */
add di, 79 /* siguiente punto */
dec cl /* comprobamos si es */
jnz VLineLoop /* el último punto */
}
}
Si intentamos desarrollar una rutina simple para crear una línea horizontal directamente en pantalla (como en la figura 1), nos encontraremos con que cada punto de la línea cae en un plano distinto de la VGA. Este problema puede resolverse de dos formas: la primera de ellas sería calcular el plano con que se corresponde cada punto, cambiarlo mediante los registros de la VGA, y dibujar el punto en memoria. Esto significa que para una línea de 50 pixels de ancho, realizamos 50 cambios de plano, con la consecuente pérdida de velocidad.
El otro método, más laborioso, consiste en activar el plano 0 y dibujar los puntos de la línea que caen en el plano 0, haciendo lo mismo después con el plano 1, 2 y 3. Tantos son los factores a tener en cuenta que podemos deducir que en cuestiones de velocidad, la rutina de dibujo de líneas verticales es mucho más rápida que la de líneas horizontales.

Si estuviéramos bajo el modo estándar 13h, la solución se basaría en un bucle que volcara todos los bytes desde Segmento hasta 0A000h, como describe el siguiente pseudocódigo (80x86):
Mover 320 bytes desde DS:SI a ES:DI:
DS:SI = Segmento:0000
ES:DI = 0A000:0000
CX = 64000 (320x200)
REP MOVSB
Tratándose de un modo unchained, nos encontramos de nuevo con el problema
del cambio de plano. Cada punto del buffer tendrá una localización en pantalla
con su correspondiente plano, presentándose así dos posibilidades para
volcarlo. Por una parte podemos tratar el segmento como una serie de líneas
horizontales y mediante un bucle volcarlas a pantalla. La otra opción es
considerarlo como una serie de líneas verticales, con lo que descubrimos la
utilidad del análisis anterior sobre dibujo de líneas que tan obsoleto
parecía. Esta opción está desarrollada dentro del listado 2, y está en gran
parte en C para facilitar su comprensión, aunque por supuesto no será así en
la rutina incluida en la librería.
Listado 2: Volcado de segmentos por líneas verticales.
void FlipSegmentC( unsigned int Segm )
{
unsigned int offs;
int f,f2;
asm push ds
asm mov ax, 0a000h
asm mov es, ax /* ES:DI = VGA */
asm mov ds, [Segm] /* DS:SI = Segm */
asm xor si, si
for( f=0; f<320; f++ ) /*320 líneas verticales */
{
offs = f>>2 ;
asm mov di, [offs] /* DI = (x/4) */
asm mov si, [f] /* SI = x */
OutPortb( SEQU_ADDR , 0x02 );
OutPortb( SEQU_ADDR+1, 0x01 << (f & 3) );
/* Poner plano */
for( f2=0; f2<200; f2++ ) /* volcar 200 pixels */
{
asm movsb
asm add di, 79
asm add si, 319
}
}
asm pop ds
}
Para conseguir tan sólo 4 cambios de plano, bastaría con activar el plano 0 y volcar en pantalla todos los puntos del buffer que recaigan sobre el plano 0, repitiendo el proceso para los planos 1, 2 y 3.
Esto lo podemos hacer mediante un simple bucle, ya que sabemos que si el punto 0 cae en el plano 0, el 1 sobre el 1, etc..., entonces el punto 4 corresponderá de nuevo al plano 0. Veámoslo en pseudocódigo para aclarar dudas:
DS:SI = Segmento
ES:DI = 0
Activar Plano 0
Repetir 16.000 veces (64000/4)
AL = DS:SI ->Leemos byte del segmento virtual
ES:DI = AL ->Lo escribimos
DI = DI+1 ->x = x+4
SI = SI+4 ->siguiente punto
Repetir para Planos 1, 2 y 3
Como puede apreciarse en el algoritmo, activamos el plano 0, leemos un byte
del buffer virtual (AL = DS:SI), lo escribimos en pantalla (ES:DI = AL) y
pasamos al siguiente punto que corresponda al plano 0 (DI+=1 y SI+=4).
Repitiendo esto para 320x200/4 = 16000 bytes volcamos a pantalla, en el plano
0, todos los puntos del buffer que se deberían corresponder con este plano.
Haciendo esto para el resto de los planos se obtiene en pantalla el buffer que
se pretendía volcar, y todo esto, aunque parezca más complicado que la
anterior rutina, reduce el acceso a puertos de 320 a 4 llamadas. Todo un lujo
de vuelque por software, que podemos ver traducido a código C y ASM en el
listado 3.
Listado 3: Volcado de segmentos por planos.
void FlipSegmentASM( unsigned int Segm )
{
asm {
push ds
mov ds, [Segm]
xor si, si /* DS:SI = Segm */
mov ax, 0a000h
mov es, ax
xor di, di /* ES:DI = VGA */
xor bx, bx /* BX = 0 (Plano) */
}
CuatroPlanos:
asm {
mov dx, SEQU_ADDR
mov al, 2 /* selección de plano */
out dx, al /* OutPortb( SEQU_ADDR, 2 ); */
xor ax, ax
inc ax /* AX = 1 */
mov cl, bl /* CL = plano */
shl ax, cl /* AX = 1 << plano */
inc dx
out dx, al /* OutPortb( SEQU_ADDR, AL ); */
xor di, di /* offset inicial 1er punto */
mov si, bx
mov cx, 16000 /* repetir 16000 veces */
}
BuclePlano:
asm {
mov al, ds:[si] /* AL = DS:SI */
mov es:[di], al /* ES:DI = AL */
inc di /* Ste. punto de pantalla */
add si, 4 /* Ste. punto del buffer */
dec cx
jnz BuclePlano /* Repetir 16000 veces */
inc bl
cmp bl, 4 /* Repetir 4 veces (planos) */
jb CuatroPlanos
pop ds
}
}
Hay que decir también que aunque este volcado entraña gran dificultad en
cuanto a la comprensión del algoritmo para los programadores de alto nivel
(C/Pascal/etc..), cuando se entiende permite ya desarrollar funciones de
dibujo de sprites por líneas verticales, bloques y otros tipos de gráficos,
como puede verse en el listado 4.
Listado 4: Dibujo de Sprites por líneas verticales.
void DrawSpriteX( char *SprArray, int x, int y, char ancho, char alto )
{
unsigned int offs;
int f,f2;
asm mov ax, 0a000h
asm mov es, ax
for( f=0; f<ancho; f++ )
{
offs = (y<<6) + (y<<4) + ((x+f)>>2 );
asm mov di, [offs]
OutPortb( SEQU_ADDR , 0x02 );
OutPortb( SEQU_ADDR+1, 0x01 << ( (x+f) & 3 ) );
for( f2=0; f2<alto; f2++ )
{
_AL = SprArray[(f2*ancho)+f];
asm stosb
asm add di, 79
}
}
}
Este apartado se basa en su mayoría en la manipulación de los registros ya que estos nos permiten una cantidad inagotable de posibilidades. Los registros aparentemente resultan inútiles, pero de ellos se pueden extraer multitud de efectos interesantes, sólo hay que utilizar la imaginación y experimentar con ellos. En un principio se pueden manipular realizando pruebas con todos los controladores, pero hay que tener cuidado con los registros de Timing (registros CRTC 0-7) ya que con una utilización continuada de valores inapropiados puede dañar el monitor, (el monitor empieza a pitar) llegando hasta su destrucción por completo si este no se logra apagar a tiempo. El resto de registros no son peligrosos, lo más que pueden llegar a hacer es bloquear el monitor.
Para comenzar con las aplicaciones de los registros, este apartado tratará en primer lugar el registro 3C2h llamado Miscellaneous OutPut Register. A simple vista no parece un registro al cual se le puede sacar partido; tiene apariencia de poca utilidad que tan sólo el hardware interno puede utilizar, pero eso no es cierto, tiene más utilidades de las que uno podría imaginar.
En primer lugar este registro es capaz de activar o desactivar la polaridad mediante los bits 6-7. El bit 6 es capaz de poner negativa o positiva la polaridad horizontal del retrazo, y el bit 7 hace lo mismo pero con la polaridad vertical del retrazo. El bit 5 nos selecciona la página para el direccionamiento par/impar. En este caso si el bit 5 está a 1 indica que en todos los planos se ocupan las direcciones impares, de lo contrario si está a 0 nos ocuparan todas las direcciones pares. Los bits 2-3 se encargan de la selección del reloj de video que determina la resolución horizontal (a través de la frecuencia de pixels). Si activamos el bit 1 nos dará acceso a la RAM de la VGA por parte de la CPU, y si se desactiva no será posible ver nada en pantalla: ya no existe acceso ninguno.
Es aconsejable ver los ejemplos que aparecen en el apartado correspondiente al Modo X del CD de la revista.También puede verse como ejemplo el listado 5 que indica la manera de programar, a partir del modo 13h, una resolución vertical de 480 líneas.
Listado 5: Modo de 480 líneas.
void Set480Lines ( void )
{
InitModo13H(); /* modo 320x200x256 */
asm {
mov dx, 0x3CC /* registro de lectura del */
/* Miscellaneous O. R. */
in al, dx
mov dx, 0x3C2 /* registro de escritura */
/* del Misc. O. R. */
and al, 00111101b
mov ah, 11000010b /* bits 6-7 activados */
/* bit 1 regula el acceso a */
/* la VRAM (RAM de la VGA). */
or al, ah
out dx, al
}
}
Esta ha sido la aplicación al registro Miscellaneous OutPut Register, cuya
utilidad parecía escasa.Como anteriormente se ha dicho, la mayoría de los registros són útiles para programar efectos gráficos. A continación se explicará el funcionamiento de otro registro un poco más conocido por las aplicaciones que ha tenido en este curso de Modo X. El registro 09h del CRTC Controller (Max Scan Line Register) tiene utilidad a la hora de programar los efectos gráficos debido al efecto que produce. Este registro es capaz de duplicar las líneas en un modo gráfico. En caso de estar cargado el modo 13h de 320x200, este registro puede duplicar cada línea con tal de tener 320x400 líneas, cada una duplicada. Debido a su duplicación solo se podrán ver en pantalla las 100 primeras líneas del gráfico original. Por lo tanto la resolución resultante no es más que de 320x100 pixels en pantalla.
Esto quiere decir que este registro se basa en la repetición de cada línea, pero no solo la duplica, sino que tiene la posibilidad de repetir una misma línea un máximo de 15 veces, dependiendo de su programación. El resultado es que, si por ejemplo, hemos de crear un plasma de 160x100 que dibujaremos en 2x2 pixels, seleccionando una duplicación de 2 líneas tan sólo habremos de dibujar los pixels como 2x1 para que sea la VGA la que doble las líneas horizontales.
El funcionamiento de este registro es muy sencillo, tan solo hay que leer de este puerto (Max Scan Line Register) un byte para poder activar el bit 7 y activar el modo Double Scan (modo de duplicar líneas). Una vez activado el bit 7 se tiene que indicar el número de veces que se repetirá una sola línea con el fin de lograr una nueva resolución. Se puede repetir una línea como máximo 15 veces debido a que son los bits 0-3 los que se encargan de indicar dicha función.
Si se quiere programar la repetición de líneas, el bit 7 siempre debe estar activado (puesto a 1). Si el bit 7 no está puesto a 1, la VGA toma su forma extendida de 400 líneas ( su forma real en este modo gráfico ya que la VGA no reconoce ningún modo de menos de 350 líneas. Los modos de 200 líneas son generados por 400 líneas físicas). Si el bit 7 se pone a 1 se podrá programar el número de repeticiones mediante los bits 0-3.
Los ejemplos (ENTER400.C, ENTER600.C, ET13H400.C, ET13X400.C) del CD de la revista muestran la programación de este registro. Un ejemplo práctico se puede ver en el listado 6 que programa la duplicación de líneas en el modo de 320x200 (13h).
Listado 6: Modo de duplicación de líneas en 13h.
void main()
{
SetModo13H();
asm {
mov dx, 0x3D4 /* CRTC Controller */
mov al, 0x09 /* Registro 09h */
/* MaxScanLine Register */
out dx, al
inc dx
in al, dx
and al, 01110000b
add al, 129 /* 10000001b - activo bits 7 y 0*/
out dx, al /* Bit 0 indica repetir una vez */
} /* Bit 7 activa el modo repetición */
}
Respecto a programas o utilidades sobre el modo X y los registros nos permitimos recomendaros el programa TWEAK de Robert Schmidt, una gran utilidad de lectura y modificación de registros de la VGA, así como todos los textos que circulan por Internet y BBSs acerca de este tema. Sobre estos textos, se han incluido algunos de ellos en el subdirectorio DOCS del software que acompaña al artículo. Estos ficheros son programas o listados que pueden ayudar a comprender diferentes aspectos de la programación en 13h para posteriormente ser adaptados a modo X según las necesidades de cada programador.
Las dudas sobre el tema pueden ser enviadas tanto a la revista como a Compiler Software. Ante todo, gracias por haber seguido atentamente este curso de programación avanzada de la sección indocumentada de la VGA.
Pulse aquí para bajarse los ejemplos y listados del
artículo (74 Kb).
Figura 1: "Dibujo de líneas en modo X."
Figura 2: "Volcado de segmentos a pantalla."
Santiago Romero