CURSO DE PROGRAMACIÓN GRÁFICA

Artículo 7: MODOS SVGA

Autor: (c) Santiago Romero.
Revista: Programación Actual (Prensa Técnica) nº 7, Octubre-1997


Los modos SVGA dan más soltura a nuestros programas al dotarles de más resolución o colorido. En esta entrega se observará la manera de inicializar y utilizar los diferentes modos de vídeo SuperVGA con VESA 1.2.

Vamos ahora a utilizar la extensión VESA de la BIOS para trabajar en modos SuperVGA con resoluciones como 640x480, 800x600 ó 1024x768, todas ellas a 256 colores. Ahora nos será de utilidad todo lo aprendido: llamada a interrupciones de la BIOS, aprovechamiento de la linealidad de la memoria y creación de nuestras propias rutinas gráficas.

Primero haremos un poco de historia del qué, cómo y porqué de la aparición de la VESA.


SUPER VGA ESTANDAR

Tras la aparición de la VGA surge una nueva tarjeta mejorada (la SuperVGA o SVGA) con prestaciones hasta aquel entonces increibles (resoluciones como 1024x768, y paletas de hasta 16 millones de colores). En el mercado informático ninguna empresa puede copiar la circuiteria creada por otra empresa, de ahí que cada fabricante de hardware (los OEM, como son llamados) como pudiera ser Trident, Orchid, Ati, Cirrus Logic, etc, diseñó su propia tarjeta que con distinta aunque similar circuitería producía los mismos resultados, es decir, capaz de ofrecer esos modos de altas paletas y resoluciones. El resultado: un montón de tarjetas de carácterísticas similares pero de recursos distintos, de manera que un programa que aproveche todas las carácterísticas de una tarjeta SVGA, por ejemplo, Trident, no funcionaría en otro ordenador con una Cirrus, y viceversa. Ante tal cúmulo de tarjetas había 2 soluciones, o detectar cada tipo de tarjeta y utilizar en cada programa el total de drivers existentes proporcionados por cada fabricante de hardware, o crear un estándar que permitiera que todas ellas requirieran la mismas rutinas por parte del programador. Y ahí nació la VESA.

El comité VESA (Video Electronics Standards Association) creó una extensión VBE VESA que permitía esta compatibilidad. Los drivers VESA son código ejecutable que bien puede estar incluido en la BIOS de la tarjeta, o bien cargarse desde disco, quedándose residente (tal y como la famosa UNIVESA), de manera que la interrupción 10h (la interrupción de video en el ordenador, que conocimos en las primeras entregas) se amplía y pasa a ofrecernos nuevos servicios para inicializar modos SVGA, realizar cambios de bancos (que a continuación veremos), etc.

En un principio la versión más usada fue la VESA v1.2, aún disponible en muchos ordenadores como los 486, aunque los nuevos ordenadores Pentium ya comienzan a incluir eel driver VESA VBE v2.0, de manera que no haya que cargarlo desde disco.


FUNCIONES DE LA VESA v1.2

Como por ahora estamos trabajando en modo real (recordemos que esto es una iniciación a la programación gráfica), vamos a trabajar con la VESA 1.2 (por supuesto, la VESA 2.0 incorpora todas las funciones de la versión 1.2), de manera que una aplicación en SVGA con VESA 1.2 podrá ejecutarse en muchos 486, que todavía son un amplio mercado de usuarios. Para aquellos con conocimientos de modo protegido, pueden aprovecharse del Linear Frame Buffer (así se llama la nueva característica de memoria lineal) siguiendo el magnífico artículo que sobre VESA 2.0 está siendo publicado en la sección de DEMOSCENE.

Así que con nuestra extensión VESA en memoria, tenemos un nuevo set de funciones bajo nuestro control en la int 10h como las que se detallan en la tabla 1. Hay muchas otras funciones aparte de las descritas, pero vamos por ahora con las más importantes.

 TABLA 1: Algunas funciones de la int10h para SVGA:

-----------------------------------------------------------
INT 10h - SET SuperVGA VIDEO MODE 
        AX = 4F02h
        BX = modo a inicializar
Devuelve: AL = 4Fh función soportada.
          AH = estado:
            00h Correcto.
            01h Falló.

-----------------------------------------------------------
INT 10h - GET CURRENT VIDEO MODE
        AX = 4F03h
Devuelve: AL = 4Fh función soportada.
          AH = estado:
            00h Correcto.
                BX = Modo actual.
            01h Falló.

-----------------------------------------------------------
INT 10h - CPU VIDEO MEMORY CONTROL
        AX = 4F05h
        BH = subfunción:
            00h Cambiar banco de memoria.
                DX = Número de banco.
            01h Leer banco actual.
                Devuelve: Banco actual.
        BL = Ventana hardware a elegir:
            00h ventana A.
            01h ventana B.
Devuelve: AL = 4Fh función soportada.
          AH = estado:
            00h Correcto.
            01h Falló.

Supongamos que queremos desarrollar una función que inicialice modos de video SVGA para posteriormente trabajar con ellos. Si miramos la tabla anterior observamos la subfunción 02h: Set SuperVGA video mode, que nos va a permitir esto. En AH hay que introducir el valor 4fh (indicador de servicio del driver VESA), en AL la subfunción (02h) y en BX el modo a inicializar. Veamos una posible función SetSVGAVideoMode():


SetSVGAVideoMode( int modo )
{
asm {
      mov ax, 4f02h
      mov bx, [modo]
      int 21h
   }
}

El valor a introducir en BX puede verse en la tabla 2 y determina el modo SuperVGA que inicializar. Supongamos que deseamos inicializar el modo 800x600x256. Bastaría con ejecutar:


 SetSVGAVideoMode( 0x102 );

Por supuesto, antes de usar las funciones de la VESA deberemos asegurarnos de que esta está presente en el sistema, sus características (versión, etc.) y si el modo que queremos iniciar está soportado por ese driver VESA. Todo esto podemos hacerlo de igual manera por medio de distintos servicios de la int 10h, como el 00h (Get SVGA information) ó 01h (Get SVGA mode information), permitiendo leer en un buffer o estructura las características del driver VESA instalado y de los modos que soporta esa tarjeta, tal y como muestran los ejemplos del CD de la revista.

  TABLA 2: Algunos modos SVGA:

 
 MODO  -  RESOLUCIóN
 -------------------
 100h -  640x400x256
 101h -  640x480x256
 102h -  800x600x16
 103h -  800x600x256
 104h -  1024x768x16
 105h -  1024x768x256
 106h -  1280x1024x16
 107h -  1280x1024x256
 108h -  80x60 texto
 109h -  132x25 texto
 10Ah -  132x43 texto
 10Bh -  132x50 texto
 10Ch -  132x60 texto
 10Dh -  320x200x32K
 10Eh -  320x200x64K
 10Fh -  320x200x16M
 110h -  640x480x32K
 111h -  640x480x64K
 112h -  640x480x16M
 113h -  800x600x32K
 114h -  800x600x64K
 115h -  800x600x16M
 116h -  1024x768x32K
 117h -  1024x768x64K
 118h -  1024x768x16M
 119h -  1280x1024x32K
 11Ah -  1280x1024x64K
 11Bh -  1280x1024x16M 
 ---------------------


TRABAJANDO EN SVGA

Una vez inicializado el modo SVGA volvemos de nuevo a la cuestión básica: cómo se representa en estos modos la unidad básica a partir de la cual crear el resto de las formas: el pixel.

Con un modo SVGA inicializado tenemos 2 soluciones: podemos utilizar la función de la BIOS Write Graphics Pixel (Función 0Ch de la int 10h) para representar pixels individuales (algo tediosamente lento como ya comentamos), o intentar averiguar cómo trabaja la tarjeta en SVGA para tratar de acceder a la VideoRAM de igual manera que hacíamos en modo 13h.

Para empezar hay una buena noticia y otra mala. La buena es que en los modos SVGA de 256 colores (los que nos interesan por ahora), el direccionamiento es lineal igual que en 320x200, de manera que podremos aplicar nuestros conocimientos a estas rutinas gráficas. La mala noticia radica en que nosotros sólo tenemos acceso al segmento 0xA000, que es nuestra ventana a la VideoMemoria de la tarjeta, y recordemos que este segmento tiene sólo 65.536 bytes.

Habrá quien se pregunte: ¿y en qué me afecta esto? Muy sencillo: cuando trabajábamos en 320x200x256 colores, al tener 256 colores utilizábamos 1 byte por pixel (0-255), de manera que 320x200x1 = 64.000 bytes. Con tan sólo los primeros 64.000 bytes del segmento 0xA000 (como en la figura 1) podíamos trabajar en toda la pantalla, o lo que es lo mismo, los modos estándar de la VGA no requerían más de 64KB de VideoRAM. Ahora pongamos el caso de trabajar en 640x480x256. Como es un modo de 1 byte por pixel (256 colores), necesitamos 640x480x1 = 307.200 bytes. Esta es la razón de que las tarjetas SVGA dispongan de 512KB, 1024KB o actualmente, hasta 4MB de VideoRAM. Necesitan ese espacio para almacenar los bytes que representan los pixels que hay en pantalla.

VideoRam

Como ya sabemos, nosotros sólo tenemos acceso a los 64K (65.536 bytes) que hay en el segmento 0xA000, que es nuestra ventana a la VideoRAM, de manera que en el modo 640x480x256 escribiendo bytes en este segmento tan sólo escribimos en la parte superior de la pantalla, tal y como puede verse en la figura 2,

Bancos en SVGA

lo que quiere decir que tan sólo podemos trabajar con las 65.536/640 = 102 líneas iniciales de una pantalla en SVGA. Esto puede comprobarse iniciando el modo 640x480x256 (modo 101h) y usando la orden:


 pokeb( 0xA000, 65535, 15 );

Con esa orden aparecerá un punto blanco aproximadamente en la línea 102 de la pantalla. Si reducimos el offset (ampliarse no se puede, recordemos el límite de la segmentación) obtendremos puntos más cercanos a (0,0), hasta el offset 0 que corresponde a este punto.

Pero está claro que debe haber una solución para este problema: si sólo disponemos de una ventana por la que ver un trocito"de la VideoRAM, bastaría pues con mover la ventana (en el caso de 320x200 esto no era necesario como puede verse en la figura 1). Para solucionar nuestro problema bastaría con decirle al ordenador que la desplace hacia abajo, para que podamos escribir en otra porción de la pantalla SVGA, tal y como muestra la figura 2. Este es el concepto de banco (Bank). Mediante la subfunción 05h de la VESA podemos cambiar el banco desde el 0 (el que está por defecto) a, por ejemplo, el 1, de manera que podríamos dibujar (escribiendo en 0xA000) a las líneas entre la 102 y la 204. A título de ejemplo, es como si dispusiéramos de 5 posiciones distintas donde colocar nuestra ventana para trabajar en las 5 partes que componen la pantalla. Ante el problema de disponer sólo de 64KB a los que acceder, los fabricantes lo solucionaron por medio de bancos (posiciones de la "ventana virtual), conmutando entre los distintos bancos según las coordenadas en que queramos poner el pixel (proceso conocido como Bank Switching, conmutación de bancos). Veamos pues una rutina de selección de bancos.


void SVGASwitchBank( int banco )
{
asm {
	mov ax, 4f05h
	xor bx, bx
	mov dx, [banco]
	int 10h
    }
}

El posible valor del parámetro banco es un número entero que indica la "ventana" que queremos seleccionar dentro del total que hay para acceder a la VRAM. El número de ventanas que haya dependerá del modo gráfico en que estemos trabajando. En un modo de m*n pixels tendremos ((m*n)/65536) bancos, de manera que el último de ellos permite leer y escribir en las últimas líneas de la pantalla. En realidad el segmento 0A000h es una ventana móvil por la totalidad de la VRAM disponible en la tarjeta, de manera que al tratar de leer o escribir un pixel individual tendremos que tener en cuenta en qué banco cae (según sus coordenadas), cambiar a este banco y escribirlo en la posición correcta dentro del segmento 0A000h. Esto podemos verlo en el listado 3. El proceso en dicho listado es:

  - Calcular el offset absoluto del pixel con (y*AnchoP)+x.
  - Calcular el banco en que reside el pixel (x,y) mediante
    (abs_offs/65536) y cambiar a éste por medio del driver VESA.
  - Calcular, dentro de ese banco, el offset que le corresponde
    en que escribir dentro de 0xA000 y escribir el pixel en memoria.
 LISTADO 3: PutPixel en SVGA.

void SVGAPutPixel( int x, int y, char color )
{
 int vga_offs;
 char banco;
 long abs_offs;

 abs_offs = (y*AnchoP)+x;
 banco = abs_offs/65536;
 vga_offs = abs_offs-(banco*65536);

 SVGASwitchBank( banco );
 pokeb( 0xA000, vga_offs, color );

}

Otras operaciones, como por ejemplo el borrado de pantalla, derivan de las primitivas anteriores: bastaría con seleccionar todos los bancos, uno por uno, y rellenar con un valor (color deseado) el segmento 0xA000 para cada banco.

   Desde Banco=0 a MaxBanco
     SVGASwitchBank(Banco);
     Rellenar 0xA000 con <color>;
     Banco++;
   Fin Desde

Por si aún hay dudas acerca del sistema de bancos, los ejemplos del CD son suficientemente explicativos para resolverlas, estando creados todos ellos para mostrar el proceso de Bank Switching con claridad.

Así pues, ya podemos pues realizar programas en SVGA adaptando las librerías al uso de la nueva función PutPixel() tal y como se ha hecho en los ejemplos.


VESA v2.0

La VESA v2.0 tiene una característica fundamental para los programadores: el Linear Frame Buffer. Cuando el micro está en Protected Mode (modo protegido), se aprovechan plenamente los registros de 32 bits y desaparece la segmentación de la memoria del modo real. A partir de este momento, toda la memoria es lineal desde el primer byte de nuestra RAM hasta el último. Esto permite trabajar en SVGA como en modo 13h: sin segmentos y sin bancos, con buffers de memoria totalmente lineales, en los que podremos utilizar nuestras rutinas gráficas del tipo offset=(y*AnchoPantalla)+x, doblando la utilidad de lo aprendido durante el curso.


PERSPECTIVAS DEL CURSO

A partir de ahora se pretende además comentar datos sobre la programación de gráficos en otros sistemas operativos mucho más potentes que MSDOS, como Windows 95 o Linux/Unix. Para eso se introducirán comentarios en aquellas partes que sean específicas de DOS, recomendando cómo realizar el mismo proceso en otros sistemas, debido a que el curso en sí debe ser abstracto (tratar el proceso de los gráficos en general) con ejemplos sencillos, de ahí la elección del MSDOS para estos.

El sistema operativo Windows 95 proporciona sus propias funciones de acceso a video y MicroSoft ha desarrollado las librerías DirectX para acelerar este acceso. Con la API anteriormente comentada (DirectDraw), podemos aplicar fácilmente cualquier proceso gráfico 2D en pantalla trabajando como lo veníamos haciendo hasta ahora, y dejando a la librería los procesos de representación, volcado de pantallas virtuales, y selección de modos de video, gracias a funciones como IDirectDraw::SetDisplayMode(), por ejemplo.

Bajo Linux es recomendable usar alguna de las librerías gráficas de que dispone, como la SVGALIB, que viene incluida con el sistema y proporciona todas las funciones gráficas necesarias. El sistema operativo Linux es uno de los más completos para los programadores, pues incluye compiladores, librerías, utilidades y código fuente en el mismo CD de instalación, tales como el gcc o g++, las VGA o SVGALIB, etc. El entorno gráfico X-Windows de Linux también incluye utilidades de creación, manuales, debugger y gráficos utilizando llamadas del propio servidor X.


EN LA PROXIMA ENTREGA

El lector debe practicar ahora con el tratamiento de los sprites y bitmaps y aplicarle diferentes efectos de prueba, tales como el recorte o clipping (controlar si parte del sprite queda fuera de la pantalla e imprimir sólo la parte que queda dentro), la animación, etc. Debe además practicar desarrollando programas en 13h y en SVGA, utilizando y optimizando las rutinas de las librerías.

El próximo artículo abordaremos los formatos gráficos más sencillos como el RAW, el SCR y el PCX para conocer su estructura completa y sus métodos de compresión, y es que los bitmaps pueden ser comprimidos ocupando un menor espacio y descomprimidos posteriormente para su utilización. De los principios básicos del almacenamiento de imágenes/bitmaps con y sin compresión nos ocuparemos en el próximo número.

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

Figura 1: "VideoMemoria para modo 13h."
Figura 2: "VideoMemoria para modos SVGA."

Santiago Romero


Volver a la tabla de contenidos.