El siguiente artículo busca continuar con el trabajo desarrollado el semestre anterior en donde se especificaron las caracteristicas básicas del adaptador de display, en particular del adaptador CGA, sus técnicas de generación de caractéres, el controlador del CRT,etc. En dicho trabajo se menciona el acceso a los distinos adaptadores de display mediante los servicios de atención a interrupciones de la BIOS, en particular la interrupción 10h, cosa que en este artículo se enfatizará y se darán las bases de la programación de gráficos mediante uso de lenguajes de alto nivel ,como ANSI C, y de assembler .
Modos alfanuméricos: En los modos alfanuméricos (modos de texto), la pantalla está dividida en una serie de celdillas donde sólo cabe un carácter (tanto un carácter alfabético como un número -de ahí el término alfanumérico-), proporcionándonos resoluciones generalmente reducidas, como por ejemplo 80x25, que quiere decir que tenemos la pantalla limitada a 80 caracteres por línea horizontal y con 25 líneas en el total visualizable. Este tipo de limitaciones es el que nos queremos saltar pasando a programar en los modos gráficos, en contraposición a los de texto. Cualquier forma gráfica en estos modos tiene que ser creada a base de combinaciones de símbolos ASCII.
Pixel: Cuando ejecutamos un programa en modo gráfico, mirando a la pantalla podemos darnos cuenta de que el dibujo ahi representado está formado por una serie de unidades mínimas (unos pequeños puntos cuadrados), cada una de ellas con un color, y con la acumulación de las cuales se forman las figuras gráficas que vemos representadas en el monitor.
La palabra pixel viene de la abreviación del término inglés PEL -picture element- (elemento del dibujo), y representa la unidad mínima capaz de ser representada por el monitor en esa resolución.
Resolución: Acabamos de nombrar la palabra resolución en la definición de pixel, y es que en la pantalla caben un número determinado de pixels (o puntos) a lo ancho y a lo alto de ella. Así, podemos ver que, en un determinado juego, en la pantalla caben 320 pixels a lo ancho y 200 a lo alto mientras en Windows pueden caben por ejemplo 640x480 pixels en la misma pantalla.
La resolución de un modo de vídeo, especificado en el formato (ej: 640x480) nos indica la cantidad de pixels que caben en la pantalla tanto a lo ancho (640 en este caso) como a lo alto (480), lo que quiere decir que en la pantalla hay un total de 640x480 pixels = 307.200 pixels (en nuestro ejemplo). Queda claro pués que a mayor resolución, como el tamaño del monitor es siempre el mismo, los puntos serán más pequeños y conseguiremos una mayor calidad gráfica, mientras que a resoluciones menores (ej: 320x200) es más fácil notar el tamaño de los puntos y la calidad representada será menor. Además mientras mayor resolución queramos mayor memoria de video necesitaremos, por ejemplo:
En modo 320x200 a 256 colores = 320x200 = 64000 direcciones , o sea
, 64 KB (justo el tamaño de un segmento!)
ya que 256 colores de codifican en 8 bits=1byte
Coordenadas de pantalla: cuando necesitemos localizar en pantalla una posición específica (por ejemplo: un punto), lo haremos a partir de sus coordenadas, que consisten en dos números que indican una posición respecto a las coordenadas <0,0> (parte superior izquierda de la pantalla).
Supongamos que en el modo 320x200 queremos localizar el centro de la pantalla. Este será, obviamente, el punto (160,100), puesto que ambos números nos indican la posición horizontal primero y luego vertical considerando la pantalla como un eje XY. El último punto de la pantalla tendría en este caso las coordenadas (319,199).
Modos gráficos: Cada modo gráfico se caracteriza por su resolución y su número de colores. Así, tenemos desde el modo gráfico 320x200x256 (256 colores) hasta, por ejemplo, el 1024x768x16M (con 16 Millones de colores). Nuestra tarjeta gráfica es capaz de inicializar todos estos distintos modos, y podremos elegir el más conveniente a nuestros propósitos para usarlo en nuestro programa. Queda claro entonces que, cuanto más nueva y potente sea nuestra tarjeta gráfica (que va instalada dentro del ordenador), de más modos gráficos dispone, de tal manera que sería imposible inicializar el modo 800x600x256 colores en una tarjeta gráfica que no sea SVGA (Super VGA). Cuando nos dispongamos a crear un programa, tendremos que decidir primero en que modo gráfico vamos a realizarlo, debido a que cada modo se programa de una manera y, como es obvio, a menor resolución y menor número de colores, el programa resultante será más rápido. Con respecto al término modo gráfico, que no sea un modo de texto no quiere decir que no podamos representar información textual, pués construiremos los carácteres a base de pixels, ya que cualquier carácter es en sí mismo un símbolo gráfico.
Figura 1: formato típico de lineas en modo texto.
Paleta: Llamaremos paleta (por ahora) al conjunto de colores que posee un modo gráfico (2, 16, 256, 32.000 ó 16 millones, por ejemplo) y ya entraremos más adelante en cómo modificar este juego de colores de que disponemos para adaptar los colores existentes a las tonalidades deseadas.
Dada la inmensidad de estándares gráficos existentes para los ordenadores compatibles, que sucedieron al primer adaptador que sólo soportaba texto (MDA), y que de hecho llenan varias estanterías en las librerías, sólo se tratará de una manera general el tema para dedicarnos a estudiar un en particular, el VGA. Se considerarán los estándares más comunes, con algunos ejemplos de programación de VGA en ensamblador. Las tarjetas gráficas tradicionales administran normalmente entre 16 Kb y 1 Mb de memoria de vídeo, en el segmento 0B800h las CGA/Hércules y en 0A000h las VGA. En los modos de vídeo que precisan más de 64 Kb se recurre a técnicas especiales, tales como planos de bits para los diferentes colores, o bien dividir la pantalla en pequeños fragmentos que se seleccionan en un puerto E/S. Las tarjetas EGA y posteriores vienen acompañadas de una extensión ROM que parchea la BIOS normal del sistema para añadir soporte al nuevo sistema de vídeo.
mov ah, 0 /* AH=0:
Init VideoMode */
mov al, 13h /* modo: 13h */
int 10h
/* llamada a int 10h */
Simplemente conociendo los parámetros que necesita cada interrupción y servicio podemos utilizar cualquiera de las funciones de bajo nivel de que nos provee la BIOS del PC. Esta interrupción en concreto, la interrupción 10h, es la llamada interrupción de video, y nos permite inicializar modos de video (servicio 0), poner pixels en pantalla (servicio 0Ch), leer el valor de un pixel (servicio 0Dh), cambiar la paleta disponible (desde AX=1000h a 101Bh) y otros servicios a disposición del programador. Aparte de la interrupción 10h existen más interrupciones con sus correspondientes servicios (33h=servicios del ratón; 16h=servicios de teclado, etc...), que nos harán el proceso de creación mucho más sencillo.
En la tabla 2 disponemos del listado de los modos de video que pueden ser inicializados en una VGA estándar mediante el servicio 0 de la interrupción 10h. Es importante volver del modo gráfico al anterior en el cual se ejecutó nuestro programa,TABLA 1: Algunos servicios de la int 10h. ------------------------------------------------------------------------- | PARÁMETROS | SERVICIO | FUNCIÓN QUE REALIZA | ------------------------------------------------------------------------- | AH = 00h | Set Video Mode | Inicializa el modo de video especi- | | AL = Modo | | ficado en AL, segun los valores de | | | | la tabla 2. | ------------------------------------------------------------------------- | AH = 0Ch | Write Pixel | Dibuja el pixel (CX,DX) en pantalla | | CX = Coord. X | | con el color Al. El parámetro página| | DX = Coord. Y | | debe ser 0 en 13h ya que sólo hay 1 | | AL = Color | | página de video y esa es la 0. | | BH = Página | | | ------------------------------------------------------------------------- | AH = 0Dh | Read Pixel | Devuelve en Al el color del pixel | | CX = Coord. X | | de la posición (CX,DX). | | DX = Coord. Y | | | | BH = Página | | | | Devuelve: | | | | AL = Color | | | ------------------------------------------------------------------------- | AH = 0Fh | Get Video Mode | Devuelve en AL el modo de video | | Devuelve: | | actual. | | AL = Modo | | | ------------------------------------------------------------------------- | AX = 1001h | Set Border Color| Cambia el color del borde de la pan-| | BH = Color | | talla al color BH (por defecto es 0,| | | | equivalente al negro). | -------------------------------------------------------------------------
Aún cuando disponemos de interrupciones estamos limitados a las funcionen que esten disponibles en la BIOS del PC.TABLA 2: Modos de vídeo de la BIOS. ---------------------------------------------------------------------- MODO TIPO RESOLUCIÓN COLORES VRAM SISTEMA ---------------------------------------------------------------------- 00h Texto 40x25 16 B800h CGA/MCGA/EGA/VGA 02h Texto 80x25 16 grises B800h CGA/MCGA/EGA/VGA 03h Texto 80x25 16 B800h CGA/MCGA/EGA/VGA 04h Gráfico 320x200 4 B800h CGA/MCGA/EGA/VGA 06h Gráfico 640x200 2 B800h CGA/MCGA/EGA/VGA 07h Texto 80x25 mono B000h MDA/Herc/EGA/VGA 0Dh Gráfico 320x200 16 A000h EGA/VGA 0Eh Gráfico 640x200 16 A000h EGA/VGA 10h Gráfico 640x350 16 A000h EGA/VGA (256Kb) 12h Gráfico 640x480 16 A000h VGA 13h Gráfico 320x200 256 A000h MCGA/VGA ------------------------------------------------------------------------
Dicho de otro modo, es como si el primer megabyte de memoria RAM estuviera compuesto por bloques de 64Kb cada uno. Cuando queremos escribir en una posición de memoria, le indicamos al ordenador en qué bloque está (segmento), y dentro de ese bloque cual es el byte que queremos modificar (offset o desplazamiento).
Esto es así porque en el 8086 los registros eran de 16 bits (sólo pueden adoptar valores entre 0 y 65.536), por lo que para acceder a la memoria idearon este método para, pudiendo utilizar tan sólo hasta el número 65.535, hacer referencia a posiciones de memoria más elevadas (cuando se creó el PC nadie se podía imaginar que algún día necesitaría más de 64Kb de RAM). Con este sistema de segmentación de memoria, escribir un byte en la posición 65.536 se reduce (de una manera intuitiva a modo de ejemplo) a escribirlo en el bloque 1, offset 0. Realmente en el PC las direcciones absolutas se construyen mediante:p>
Dir_Física = (Segmento*16)+Offset
Esto sólo ocurre en modo real, pues a partir de la aparición
de micros de 32 bits (386+), con registros de este tamaño puede
accederse a la memoria de manera lineal, en un nuevo modo del micro llamado
modo protegido, permitiendo la manipulación de hasta 4 gigabytes.
El objetivo de la anterior introducción a la segmentación
de memoria es el segmento 0A000h. Este segmento de memoria (64Kb, 65.536
bytes) es el segmento de VideoRAM, es decir, es donde la VGA guarda los
datos de las imágenes gráficas que dibuja en el monitor.
Si escribimos algún valor en este segmento, la próxima vez
que la tarjeta gráfica redibuje la pantalla (lo hace entre 50 y
70 veces por segundo) el valor que hemos escrito aparecerá en pantalla
en forma de punto. Pero veamos que es lo que hace la tarjeta gráfica
con esta VideoMemoria.
Figura 2: Barrido de la pantalla por el CRTController
Esto quiere decir que cada número del 0 al 255 se corresponde con un color. Por defecto, el 0 es el negro, el 1 el azul, y así hasta llegar al 255. Entre el 0 y el 255 disponemos de gamas de azules, verdes, amarillos, etc..., que componen la paleta por defecto de la VGA. Que este modo gráfico sea de un byte por pixel significa que al escribir un byte en este segmento de memoria, su equivalente en pantalla será un pixel, que aparecerá automáticamente en cuanto el haz de electrones pase por esa posición al refrescar la imagen.
En la figura 3 tenemos una representación de cómo está
organizada la VideoRAM en el modo 13h.
Figura 3: Organización lineal del modo 13h.
Como puede verse, al byte 0 le corresponde el pixel (0,0) (el primero
de la pantalla); al byte 1 le corresponde el pixel (1,0), al byte número
320 le correspondería el pixel (0,1), (primer pixel de la línea
1, porque hay 320 pixels de resolución horizontal) y así
hasta el byte 63.999 del segmento, que corresponde a la posición
(319,199). Depende del offset en que coloquemos el byte, el punto aparecerá
en distinta posición en el monitor (cada byte es un pixel individual
en la pantalla).
El segmento de la VideoRAM se comporta en este modo de video como si
fuera una larga línea de pixels de manera que al llegar al final
de una línea horizontal de pantalla, el siguiente byte de la VideoMemoria
es el que continúa en la siguiente línea de pantalla. De
ahí el término direccionamiento lineal: es como si la pantalla
fuera un array de C o PASCAL unidimensional desde 0 a 64.000 donde cada
320 bytes estamos situados en una nueva línea de pantalla (el byte
320 es el primer pixel de la segunda línea). Así, durante
el retrazado la tarjeta únicamente tiene que dedicarse a leer bytes
(todos ellos consecutivos) y representarlos en pantalla.
Tambien podríamos comparar la VideoRam con una gran pantalla
de una sóla linea de ancho (de 320x200=64.000 pixels de ancho),
y cuando la tarjeta traslada esos colores al monitor, cada 320 bytes salta
a una nueva línea. Así obtenemos en pantalla una imagen de
320x200 pixels.
Por otra parte, hay que hacer notar que los 256 colores de que disponemos
pueden ser adaptados a nuestras necesidades. La paleta por defecto contiene
unos 32 tonos de azul, 32 de rojos, grises, etc... pero si estamos dibujando
(por ejemplo) una selva, probablemente necesitaremos más tonos de
verdes de los 32 que hay por defecto. La manera de cambiar la paleta (es
decir, que cada número 0-255 corresponda a una tonalidad o color)
la abordaremos mas adelante, de manera que podríamos (en este ejemplo
en concreto) hacer que los primeros 128 colores sean tonos de verdes y
los restantes 128 tonos de azules (para el cielo de nuestra selva). Esto
quiere decir que el color 0 no tiene porqué ser el negro (como en
la paleta por defecto), sino que podemos adaptar cada color (desde el 0
al 255) a la tonalidad (mezcla de rojo, verde y azul) que deseemos.
.MODEL SMALL .CODE ;programa de prueba grafica ,por Ivan Brito programa: call set_video ;inicializa el modo de video (320x200,256 colores) ;********* seteamos el segmento de memoria de video ********************** mov ax,9fffh add ax,1h ;segmento de video direccionado en a000h (tama¤o=64kb) mov es,ax ;utilizamos el segmento extra del micro para direccionarlo mov al,03h ;seteamos el color ;********** escribimos en los pixeles en la memoria de video ******************************* mov di,0000h mov es:[di],al ;escribimos el pixel mov di,0001h mov es:[di],al ;escribimos el pixel mov di,0002h mov es:[di],al ;escribimos el pixel mov di,0003h mov es:[di],al ;escribimos el pixel mov di,140h mov es:[di],al ;escribimos el pixel mov di,280h mov es:[di],al ;escribimos el pixel mov di,3c0h mov es:[di],al ;escribimos el pixel ;******* leemos pixeles y desplegamos en pantalla lo que hay ******************************************** ; mov ah,02h ; mov dl,es:[di] ; int 21h ;******* llamamos a funcion 01 de la int 21h para simular un getch()**** mov ah,01h int 21h ;******* nos vamos del modo de video *********************************** call off_video ;******* preparamos la funcion 9h de la int 21h para desplegar strings *************** mov ax,@DATA mov ds,ax mov dx,offset Texto mov ah,9h int 21h ;******** esta funcion es para terminar "sin problemas " el programa ** mov ax,4c00h int 21h ;******* deficicion de procedimientos ********************************** set_video proc near mov ah,00h mov al,13h int 10h ret set_video endp off_video proc near mov ah,0 mov al,3 int 10h ret off_video endp .DATA Texto DB 'Programa OK!, Chester$' .STACK END programa
Aunque este bloque assembler tiene más líneas que
un bloque C utlizando los driver BGI y esto induzca a pensar que puede
ser más lento que el pokeb() o que una simple llamada al servicio
0Ch de la int 10h, lo cierto es que es, con diferencia, más rápido
que la primera acción y muchísimo más que la segunda.
El acceso directo con assembler a la VideoRAM es la manera más
directa posible de representar gráficos en pantalla, único
camino a seguir al desarrollar aplicaciones y juegos de tiempo crítico
(aquellos donde es necesaria mucha velocidad de representación)
al igual que hacen los videojuegos de las compañías profesionales,
aunque sí es preciso hacer notar que la VideoRAM es bastante más
lenta que la RAM normal, pero se puede solucionar con la s llamadas "pantallas
virtuales"
Si el pixel (0,199) está situado en el offset 63.380, el pixel (1,199) será el siguiente ( (199*320)+1 = 63.380 + 1= 63.381). De ello se deduce que el offset de cualquier pixel (x,y) será:
offset = (320*y) + x;
Por lo tanto, el pixel (160,100) estará situado en el segmento
0A000h, en el offset (320*100) + 160 = 32160. Si escribimos el valor 1
(azul en la paleta por defecto) en esta posición de memoria, en
el centro de la pantalla aparecerá un pequeño pixel de color
azul.
Así de sencillo es el cálculo del offset correspondiente
a cualquier pixel (x,y). Únicamente hemos de saltarnos 320 bytes
por cada línea desde el principio del segmento (pixel (0,0) = offset
0) hasta llegar al offset deseado.
Los primeros ordenadores personales tenían un sistema de
visualización monocromático, es decir, dos colores en pantalla
(fondo y primer plano), que limitaban la vistosidad de los programas. Con
la aparición de nuevas tarjetas como la CGA o la EGA se pudo disponer
primero de cuatro colores y después de hasta 16 colores diferentes
en la pantalla, con la que ya se podían hacer presentaciones más
logradas, pero la tarjeta gráfica que ofreció unas prestaciones
de calidad fue la tarjeta VGA que conseguía visualizar hasta 256
colores de forma simultánea, aunque en una resolución de
320 pixels de ancho por 200 de alto. También permitía resoluciones
altas, de 640 x 480 pixels, aunque a 16 colores.
Para representar los colores el PC se sirve de una paleta que utiliza de forma parecida a la de los pintores. En ella se realizan mezclas para conseguir unas nuevas tonalidades o nuevos colores que se puedan necesitar.
Cada color está constituido por una mezcla tres colores básicos : el rojo, el verde y el azul. A estos se les llama componentes porque la union de estas tres dan como resultado un color. Por lo tanto, una paleta estará constituida por tres componentes para cada color. Para realizar estas mezclas se utiliza un sistema llamado RGB (del inglés red-green-blue) que combina estos tres colores básicos. El porqué de la utilización de este sistema es porque el monitor genera el color centrando los tres haces de luz en un solo punto. Cada haz es de un color básico, es decir, un haz es rojo, otro verde y el otro azul, por lo que al unirlos se verá en la pantalla el color que corresponda a la mezcla de las tres intensidades de cada haz. Basta con acercarse a una televisión para comprobar que cada punto está compuesto por tres pequeños puntos, cada uno activado con una intensidad.
De esta forma, para conseguir el color blanco las tres componentes deberán estar al máximo, y para conseguir el color negro las tres componentes deben estar a cero. Siguiendo la misma tónica, el color rojo más puro la tarjeta lo genera con R=63, G=0 y B=0. Podemos generar así hasta 262.144 colores, como se verá en el apartado “El DAC de la Tarjeta gráfica”.
De esta manera, la paleta de colores tomando como referencia el modo 320x200 a 256 colores, es simplemente un array de 768 elementos (256 colores * 3 componentes cada uno = 768) conteniendo las componentes de estos 256 colores que la VGA guarda internamente para que, al redibujar la pantalla (puede consultarse el proceso de retrazado en la entrega anterior), cuando la tarjeta encuentra en la VideoRAM el numero 0, por ejemplo, va a esta tabla y activa los 3 haces para que generen esa tonalidad RGB.
La paleta gráfica por defecto tiene unas componentes determinadas de manera que siempre el color 0 es el negro, el 1 el azul, etc... Si miramos la paleta por defecto de la VGA, estos 768 elementos que contiene almacenados la tarjeta tendría un aspecto como:
Paleta_Defecto[768] =
{ 0,0,0, 0,0,17, ... etc ... };
El color 0 es negro porque sus componentes son (0,0,0). De la misma
manera, el color 1 es azul por defecto porque sus componentes son (0,0,17).
El color 15 es blanco porque sus componentes son (63,63,63).
Si queremos que el color 0 en vez de ser negro (0,0,0) sea un amarillo
pálido, podemos cambiar dentro de este array interno los valores
(0,0,0) por, por ejemplo, (23,44,0).
Paleta_Defecto[0] = 23;
Paleta_Defecto[1] = 44;
Paleta_Defecto[2] = 0;
De esta misma manera, podemos cambiar el color 1, el 15, el 255... en resumen: podemos cambiar cualquier color a la tonalidad que deseemos. De ahí la necesidad de cambiar la paleta: para cada dibujo necesitamos unos colores en concreto (más verdes y menos rojos, todo grises, etc...).
El DAC (digital-analog converter) se utiliza para convertir las señales digitales que genera el ordenador a señales analógicas aplicables al CRT. De esta forma, y si el monitor esta capacitado para ello, con sólo cambiar nuestra tarjeta de vídeo por otra con un DAC mejor se conseguirían más colores representables por la pantalla.
El DAC de una tarjeta VGA es de 6 bits con lo que se consiguen 64 combinaciones
diferentes para cada componente de color. Si recordamos, cada color tiene
tres componentes diferentes(RGB), por lo que se puede calcular el numero
de colores diferentes representable por una tarjeta VGA:
64 Rojos * 64 Verdes * 64 Azules = 64 ^ 3 = 262.144
Este número es la cantidad de colores diferentes capaz de generar
la VGA, pero no se pueden mostrar todos a la vez por la limitación
de la paleta. Dependiendo del modo de vídeo podremos disponer de
un número mayor o menor de colores; de esta forma de entre los 262.144
colores la paleta nos fuerza a coger un número determinado de ellos.
Por ejemplo, un modo de 256 colores indica que la paleta tendrá
256 colores de los posibles 262.144, y modificando las componentes de cualquiera
de estos colores podemos tener acceso a otras tonalidades de entre el total
generable.
Cuando el DAC retrazando la pantalla lee este valor, lee de la paleta (guardada por la tarjeta) las componentes del color 10 (r=Paleta[10*3], g=Paleta[10*3+1], b=Paleta[10*3+2]) y programa los 3 haces para que dibujen en pantalla esa tonalidad. Cambiando estas 3 componentes de la paleta, cuando el DAC lea otra vez este número, cogerá de este array interno las nuevas componentes y el color resultante en pantalla será el que nosotros deseábamos, sin necesidad de cambiar ningún dato del dibujo original. Por lo tanto la imagen no cambia, sólo cambia la tonalidad de todos los puntos de la pantalla que tengan dicho color.
Figura 3:Organización de la paleta
Después de iniciar cualquier modo gráfico de 256 (o menos)
colores, el ordenador nos ofrece una paleta por defecto, que contiene unos
colores para uso general. Esto puede ser útil en determinados casos
por ser una paleta variada, pero también puede ser que no interese.
Supongamos que se quiere hacer el dibujo de una selva. Para este dibujo
se necesita una gama de verdes amplia, mientras que probablemente otros
colores como rojos y azules no se utilicen, por lo que si se utiliza la
paleta por defecto probablemente al dibujo le falten verdes. En cambio,
si se cambia la paleta por otra donde el color predominante sea el verde,
el dibujo podrá utilizar unos tonos de verdes específicos
para las hojas, otros tonos para los matorrales, etc. con lo que se consigue
más realismo en la imagen.
Para cambiar un color sólamente hay que cambiar sus tres componentes.
Hay varias formas de hacerlo : utilizando las órdenes que el BGI
pone a nuestra disposición, por medio de interrupciones o por medio
de puertos. Para empezar veremos cómo se realiza en C.
Para poder utilizar la librería gráfica, como veremos más adelante, hace falta incluir en la cabecera del programa la librería <graphics.h> en la que se incluyen todas las funciones del BGI, pero nos centraremos en la función para cambiar la paleta : ‘setrgbpalette()’. Esta función cambia el color por su RGB, por lo que tendrá como parámetros el número de color a cambiar y las tres componentes del nuevo color :
- setrgbpalette (numcolor, rojo, verde, azul); donde ‘numcolor’ es el número de la paleta que deseamos cambiar, y ‘rojo’, ‘verde’ y ‘azul’ son las tres componentes del color que queremos obtener.
Si se inicia el modo gráfico con un bloque ensamblador, no se
podrán utilizar las funciones BGI porque requieren que se inicie
el modo con la función initgtraph(), que inicia unas variables como
el ancho o el alto de la pantalla para que el BGI pueda trabajar.
Tabla 3: Registros del DACLos puertos 0x3C7 y 0x3C8 actúan como punteros hacia la paleta, es decir, el valor enviado a estos puertos indica el color dentro de la paleta de 256 colores que debe ser leido o escrito mediante el acceso al puerto 0x3C9.
---------------------------------------------------------------------------
Registro: PEL Address Read Mode Register
Puerto: 03c7h
Función: Expresa dentro de la tabla interna de 256 colores (PEL) el
color al que apunta el puntero interno del DAC cuando leemos
la paleta del puerto 0c39h (PEL DATA REGISTER). Cada 3 lecturas
del puerto 3c9h este registro se incrementa en 1 (cada 3
componentes leidas se incrementa el puntero para que se refiera
al siguiente color).
----------------------------------------------------------------------------
Registro: PEL Address Write Mode Register
Puerto: 03c8h
Función: Apunta dentro de la tabla interna al color que se modificará
al realizar 3 escrituras en el PEL DATA REGISTER. Tras estas
3 escrituras en el puerto 03c9h el puntero se incrementa en 1
para hacer referencia al siguiente color dentro de la tabla PEL.
----------------------------------------------------------------------------
Registro: PEL Data Register
Puerto: 03c9h
Función: Mediante este puerto podemos modificar o leer la paleta interna
de la tarjeta gráfica. Al escribir un valor en este puerto el
DAC lo almacena dentro de la paleta actual en la dirección
apuntada por el puntero de escritura, e igual para la lectura.
Este puerto constituye para el programador la ventana a través
de la cual modificar la paleta de la tarjeta, conjuntamente con
los registros PEL Address Read y PEL Address Write, que nos
permiten seleccionar el color a leer o escribir. Tras esta
selección los 3 valores leidos/escritos en este puerto
constituirán las componentes RGB del color.
----------------------------------------------------------------------------
Al utilizar el puerto 0x3C9 automáticamente se incrementa el puntero (al realizar 3 lecturas o 3 escrituras), por lo que al volver a utilizar el puerto nos devolverá las componentes del siguiente color. Así, se pueden leer o escribir más de un color de forma consecutiva sin utilizar cada vez el puerto 0x3C7 ó el 0x3C8, sólamente llamando al puerto 0x3C9 para cada componente que se quiera leer o escribir.
Suponiendo que se quiera modificar las componentes de un color de la
paleta por otras diferentes, los pasos a seguir serian los siguientes:
1.- Enviar al puerto 0x3c8 el º de color a cambiar
(dirección para escritura).
2.- Enviar al puerto 0x3c9 la primera componente del
nuevo color (rojo)
3.- Enviar al puerto 0x3c9 la segunda componente
del nuevo color (verde)
4.- Enviar al puerto 0x3c9 la tercera componente del
nuevo color (azul)
De forma práctica, un listado en C sería:
outportb (0x3C8, num_color);
outportb (0x3C9, rojo);
outportb (0x3C9, verde);
outportb (0x3C9, azul);
También se puede hacer con un bloque en ensamblador dentro del
C para mayor rapidez, usando las instrucciones assembler ‘IN’ y ‘OUT’.
La instrucción ‘IN AL, DX’ lee del puerto DX el valor que contiene
y lo almacena en AL. De la misma manera, ‘OUT DX, AL’ escribe en el puerto
DX el valor contenido en el registro AL. Tan sólo hemos de cargar
los registros con los valores correctos para evitar las llamadas a funciones
C (como outportb() e inportb(), que además de ser más lentas
que el bloque assembler, requieren la inclusión del fichero DOS.H).
Como puede verse en la tabla 2, al realizar en el ejemplo anterior
las 3 lecturas, el puntero interno del DAC se incrementa automáticamente,
por lo que podemos leer las componentes del siguiente color sin especificarlo
en el puerto 3c7h (igual ocurre en la escritura).
Los registros de las tarjetas son el dispositivo E/S de más
bajo nivel de nuestros adaptadores graficos, y en este número vamos
a aprender a controlarlos para aprovechar nuestra tarjeta al 100% en nuestros
programas.
Los registros de las tarjetas gráficas representan acceder al
nivel más bajo (más bajo significa más directo a hardware)
en cuanto a programación gráfica se refiere. El objetivo
es proporcionar unos conocimientos generales en lo que son los registros
de las tarjetas gráficas, su uso y más tarde particularizar
en algunos de ellos para conseguir una solución hardware a un problema
en un programa gráfico.
Como es de suponer cada tarjeta SVGA tiene su set de registros y por cuestiones de compatibilidad no convenía programar estas tarjetas a nivel de circuitería, de manera que solucionaremos esto usando el set de registros de las tarjetas VGA, set estándar para todos los adaptadores de este tipo y con el que las SVGA son totalmente compatibles, funcionando los programas que desarrollemos en cualquier tarjeta gráfica VGA o superior (por compatibilidad descendente).
Figura 4: Registros internos de la VGA.
-Attribute Controller (puerto 3c0h): es el controlador de atributos, relacionado con todos los temas de colores, atributos, etc, accediéndose por el puerto 3c0h.
-VGA Sequencer (puerto 3c4h): es el secuenciador de la VGA y gestiona sus funciones más críticas e importantes (reseteo de la tarjeta, generador de carácteres, etc).
-Graphics Controller (puerto 3c3h): es el controlador gráfico de la VGA, dando acceso a la lectura de planos, reseteo de registros, etc.
- Cathode Ray Tube Controller (puerto 3d4h): maneja todos los datos referentes al tubo de rayos catódicos que refresca o redibuja la pantalla, en un sistema asociado al monitor.
-El SVGA Controller (puerto ???h) podemos considerar que estará
presente en las tarjetas SuperVGA, conteniendo los registros extendidos
para este tipo de tarjetas. Su puerto de acceso varía según
el fabricante de la tarjeta, de ahí los problemas de compatibilidad
en la programación de estas tarjetas.
Supongamos que queremos cambiar el color del borde de la pantalla (normalmente
negro) a azul. Miramos en cualquier manual de referencia que contenga los
registros de la VGA y vemos que existe un registro llamado <<Border
Color Register>> al que se accede mediante el Attribute Controller. En
el manual pondrá algo así como:
Attribute Controller (3c0h):
Border Color Register, index 11h, r/w.
Bits (0-5): VGA screen border color.
El significado de esto es que el Border Color Register es el registro
número 11h (index 11h) del ATTRC, que es de lectura y escritura
(r/w), es decir, que podemos leer su valor y modificarlo, y que si los
bits del 0 al 5 de este byte indican el color del borde (0=negro, 1=azul,
etc).
Simplificando conceptos: los registros VGA pueden ser de escritura,
de lectura, o de escritura y lectura (w, r, r/w), y cada registro de un
controlador tiene un número asociado (index port o índice
de puerto) que indica dentro del total de registros de un controlador a
cuál nos referimos. Ahora tan sólo habría que acceder
al registro nº 11h del ATTRC (puerto 3c0h), escribiendo allí
el color que queremos que tome el borde, en nuestro caso azul (1).
La función que tiene el index port al acceder a un registro se
puede comparar con el que tiene el registro AH (subfunción) al llamar
a una interrupción, porque al igual que la interrupción 10h
tiene diversas funciones, un controlador VGA puede controlar más
de un registro, y necesitamos indicarle cuál de ellos queremos leer
o modificar. Así pues, el índice de puerto le indica al puerto
al que se escribe cuál es el registro que queremos modificar de
todos los que ese puerto controla. Únicamente hemos de enviar los
parámetros tal y como los requiere cada controlador, y ese es el
principal problema de la programación a bajo nivel de la VGA.
Tabla 4 : Algunos registros VGA----------------------------------------------------------------------
Port-Index: 02h Port: 03c4h
Registro: Color plane write enable register
Descrip.: Controla la escritura en los diferentes planos de la VRAM.
Bit 7, 6 Reservados
Bit 3 Permitir escritura en plano 3.
Bit 2 Permitir escritura en plano 2.
Bit 1 Permitir escritura en plano 1.
Bit 0 Permitir escritura en plano 0.
----------------------------------------------------------------------
Port-Index: 04h Port: 03c4h
Registro: Memory mode register
Descrip.:
Bit 4-7 Reservados
Bit 3 Bit Chain 4. (b3=1 -> modos de 256 colores).
Bit 2 Direccionamiento Odd/even.
Bit 1 Activado si hay más de 64 de VRAM.
Bit 0 b0=1: modo alfanumérico. b0=0: modo gráfico.----------------------------------------------------------------------
Port-Index: 01h Port: 3d4h
Registro: Horizontal display enable register
Descrip.: Nº total de carácteres visualizados (horiz.) menos 1.
----------------------------------------------------------------------
Port-Index: 02h Port: 3d4h
Registro: Start Horizontal blanking register
Descrip.: Carácter en el que empieza el blanqueo.
----------------------------------------------------------------------
Port-Index: 04h Port: 3d4h, 3b4h
Registro: Start Horizontal retraze register
Descrip.: Carácter en que empieza el retrazo horizontal.
-----------------------------------------------------------------------
Port-Index: 0ah Port: 3d4h
Registro: Cursor Start Register
Descrip.: Indica línea inicial (0-16) donde empieza el cursor hardware.
Bit 7, 6 : reservados.
Bit 5 : Cursor off
Bit 4-0 : Cursor Start
-----------------------------------------------------------------------
Port-Index: 0bh Port: 3d4h
Registro: Cursor End Register
Descrip.: Indica línea final (0-16) donde empieza el cursor hardware.
Bit 7 : reservado.
Bit 6, 5 : Cursor skew.
Bit 4-0 : Cursor end.
-----------------------------------------------------------------------
Port-Index: 13h Port: 3d4h, 3b4h
Registro: Offset/Logical Screen Width register
Descrip.: Anchura lógica entre 2 scanlines sucesivos.
Port-Index: Port: 3dah
Registro: CTRC Status register
Descrip.: Indica el estado del retrazo vert. Y horiz.
Bit 3: Retrazo vertical:
0 = refrescando.
1 = volviendo.
Bit 0: Retrazo horizontal:
0 = refrescando.
1 = volviendo.
----------------------------------------------------------------------
Port-Index: 05h Port: 03ceh
Registro: Mode register
Descrip.: Especifica el modo de lectura y escritura de los datos.
Bit 7 Reservado (0).
Bit 6 (b6=1) modo de 256 colores.
Bit 5 Modo de rotación de registros.
Bit 4 Modo odd/even de direccionamiento.
Bit 3 Lectura de datos (b3=0 leer, b3=1 comparar).
Bit 2 Reservado (0).
Bit 1, 0 Modo de escritura de datos.
0 = escritura directa,
1 = transferencia VRAM a VRAM,
2 = Uso de color o patrón.
----------------------------------------------------------------------
Port-Index: 06h Port: 03ceh
Registro: Miscellaneous register
Descrip.: Define datos de distintos tipos (mapeado, modo, etc).
Bit 7-4 Reservados
Bit 3-2 Segmento de memoria (g_window) y su tamaño.
00 = A000h -> 128k
01 = A000h -> 64k
10 = B000h -> 32k
11 = B800h -> 32k
Bit 1 Odd/even enable (used in text modes)
Bit 0 Graphics mode enable
----------------------------------------------------------------------
Port-Index: 10h Port: 03c0h
Registro: Mode control register
Descrip.: Datos referentes a los modos de vídeo.
Bit 6 (VGA) Si b6=1, la anchura del pixel = 8 (256 colores).
Bit 5 (VGA) Si 0, se ignora la comparación de línea.
Bit 4 Reservado
Bit 3 Si b3=1, el bit 7 del byte de atributo significa
parpadeo. Si es 0, ese bit significa intensidad.
Bit 2 Si b2=1: caracteres de 9 bits.
Bit 1 Modo monocromo si es 1. Color si es 0.
Bit 0 b0=1->modo gráfico. B0=0->modo de texto.
----------------------------------------------------------------------
Port-Index: 11h Port: 03c0h
Registro: Screen border color register
Descrip.: Indica el color del overscan (borde).
----------------------------------------------------------------------
Port-Index: - Port: 03c7h
Registro: Lookup table read index register.
Descrip.: Indica el color a leer en el registro 03c9h (cambia el puntero
dentro de la paleta interna de la tarjeta de vídeo).
----------------------------------------------------------------------
Port-Index: - Port: 03c8h
Registro: Lookup table write index register.
Descrip.: Indica el color a escribir en el registro 03c9h (cambia el puntero
dentro de la paleta interna de la tarjeta de vídeo).
----------------------------------------------------------------------
Port-Index: - Port: 03c9h
Registro: Lookup table data register.
Descrip.: Apunta a la paleta interna (actual), dentro del color especificado
con los registros 03c8h o 03c7h, y en este puerto podemos modificar
esta tabla (cambio/lectura de la paleta).
dentro de la paleta interna de la tarjeta de vídeo).
----------------------------------------------------------------------
Para comprender la importancia de estos registros vamos a comentar
el Registro de estado del CRTC (STATUS_P, 3dah), cuya descripción
puede verse en la tabla 3. El significado de sus bits es sencillo de interpretar
y proporciona información sobre los retrazos verticales y horizontales.
Como ya sabemos, en el monitor hay un haz de electrones (tubo de rayos
catódicos) que bombardea el fósforo del monitor componiendo
pixel a pixel la imagen final entre 60 y 70 veces por segundo, tomando
de la VRAM los bytes que representan cada color. Al final de cada línea
horizontal se produce un retorno a la siguiente línea (Retrazo Horizontal),
y al finalizar el retrazado completo el haz vuelve a (0,0) (incluido el
borde) desde el final de la pantalla (como vimos en el nº 2). A este
tiempo de retorno se le llama, pues, Retrazo Vertical. En este período
de tiempo el haz está volviendo y no redibujando, de manera que
podemos aprovechar este tiempo muerto para escribir en VRAM sabiendo que
hasta el próximo refresco no aparecerán los cambios que estamos
realizando (deben ser cambios muy rápidos). De esta manera nos aseguramos
que no habrá parpadeo ni nieve debido a estas escrituras. Como se
puede ver en la descripción, el bit 3 de este registro indica:
Bit 3 = 0 -> Haz redibujando la pantalla.
Bit 3 = 1 -> Haz volviendo a (0,0).
La manera de esperar este tiempo muerto para redibujado de pantallas
consiste en esperar a que el haz se encuentre redibujando (=refrescando)
la pantalla (bit3=0), y luego esperar a que acabe de hacerlo (bit3=1),
volviendo. Si sólo se comprobara esta última condición
podríamos encontrarnos con el haz a punto de llegar a (0,0) con
el consiguiente parpadeo en las operaciones gráficas. Veamos como
sería el código de espera al retrazo vertical (WaitVRetrace()):
mov dx, 3dah
vert1:
in al,dx
test al,8
jnz vert1
vert2:
in al,dx
test al,8
jz vert2
El código está implementado en assembler porque tras su
ejecución tenemos un tiempo mínimo (el que tarde el haz en
volver a (0,0)) para realizar cambios en VRAM, de manera que la sincronización
ha de ser lo más rápida posible. Con funciones C/PASCAL (outportb(),port[])
habría saltos, accesos a pila, retornos, etc, malgastando un tiempo
demasiado importante.
Con este mismo registro también podemos sincronizarnos con el
retrazo horizontal mediante el bit 1 (como se hace en las barras de copper,
por ejemplo). Otra posible aplicación es generar (para cualquier
tipo de juego, animación o efecto) los fotogramas en una pantalla
virtual, esperar el fin de retrazado y volcar esta pantalla virtual sobre
la VRAM, repitiendo continuamente el proceso.
Todo lo anterior se puede realizar de manera trasparente (computinezca!)
si trabajamos con las librería apropiadas: hablamos del sistema
BGI -Borland Graphics Interface- (Interface gráfico que usan tanto
C como Pascal), que permite, mediante órdenes concretas del lenguaje
que estemos utilizando, acceder a las funciones de dibujo más corrientes
a partir de las cuales podemos empezar nuestro periplo por los modos gráficos.
Para empezar, hay que tener claro que los ficheros .BGI incluidos con
los compiladores de Borland (en el subdirectorio \BGI) son ficheros de
código ejecutable con funciones como putpixel(), circle(), line(),
etc... que el programador puede usar para crear cualquier forma gráfica.
Para poder usar estas funciones en nuestros programas, y tomando como
ejemplo el compilador Borland C (o Turbo C), para ello tan sólo
hemos de cumplir 3 requisitos:
1. Incluir el fichero de cabecera graphics.h
2. Incluir la librería graphics.lib en el momento de la
compilación.
Esto es muy sencillo de hacer desde dentro del entorno
C (abriendo
un fichero proyecto desde las opciones del compilador)
o desde
fuera, mediante:
bcc fichero.c graphics.lib
3. Que cuando ejecutemos nuestro programa, esté presente
en el directorio
actual ( u otro definido ) el fichero BGI correspondiente
al modo
gráfico que estemos inicializando (ej: CGA
= CGA.BGI, VGA = EGAVGA.BGI,
etc...).
Si queremos que el ordenador use el modo gráfico de mayor resolución de que disponga nuestro PC, bastará con seleccionar gdriver=DETECT y no dar ningún valor a gmode. La figura 5 muestra los distintos driver BGI.
Figura 5: Driver BGI para trabajar en modos graficos.
#include <stdio.h> #include <graphics.h> int main(void){ int gdriver,gmode,color; int x; /* Permito cambiar los driver en linea */ printf("ingrese el driver , el modo de video y el color\n"); printf("driver: "); scanf("%d",&gdriver); printf("modo: "); scanf("%d",&gmode); printf("color: "); scanf("%d",&color); initgraph(&gdriver,&gmode,"c:\\tc"); /* inicializo el modo gráfico */ printf("Modo de video: %d,%d",getmaxx()+1,getmaxy()+1); getch(); setcolor(color); line(0,0,getmaxx(),getmaxy()); /*sin comentarios*/ getch(); /*Ahora lo bonito*/ for(x=1;x<=getmaxy();x++){ circle((getmaxx()/2),(getmaxy()/2),x); } for (y=1;y<=17;y++){ setcolor(y); for(x=1;x<=y*10;x++){ circle((getmaxx()/2),(getmaxy()/2),x); } } getch(); closegraph(); /*ojo, se debe volver al modo texto */ return(0); }La función initgraph( driver, modo, directorio ) inicializa el modo gráfico usando el driver driver, situado en el path directorio, y entrando en el modo gráfico especificado en modo. En este caso, buscará en el directorio C:\TC cuando se ejecute nuestro programa. Si sabemos que el driver va a estar en el directorio actual cuando se ejecute nuestro programa, bastará con poner comillas vacías ("")
Tabla 5: Funciones típicas de la librería graphic.h
Son muchas y graves las desventajas de estos ficheros. Para empezar,
al incluir la librería graphics.lib, el tamaño del programa
ejecutable aumenta sobre los 18.000 bytes (en el caso de Borland C/C++
v3.1) debido a la inclusión de todas las funciones, aunque sólo
usemos algunas de ellas.
Además, el programa al ser ejecutado deberá tener disponible
en el directorio actual (u otro establecido mediante initgraph) el fichero
BGI correspondiente al modo que se inicialice, y aunque se puede linkar
junto al ejecutable usando la utilidad BGIOBJ.EXE, el sistema BGI sigue
pecando en el sentido que más puede quejarse un programa: su velocidad.
Si un programador desarrolla una librería para, por ejemplo,
320x200x256 colores, creará las funciones específicas para
ese modo, aprovechando las características que la tarjeta nos brinde
para él, y obteniendo una "librería especializada" que funcionará
exclusivamente en dicho modo. Por contra, los BGI tienen que funcionar
para distintos modos de video, y todo eso dentro de la misma función
detectando en cada momento la resolución que se está usando
y de una manera "estándar" para todos ellos, por lo que el código
ejecutable resultante sea demasiado lento para intentar hacer con él
un programa de carácter profesional.
En particular en el caso del proyecto osciloscopio dada la velocidad
del XT resultará ( en teoría) bastante lenta la ejecución
de nuestro programa considerando que el adaptador CGA utiliza sólo
1 bits por pixel, de manera que si programamos nuestras rutinas gráficas
en ensamblador resultarán , aunque costoso en términos de
horas hombre, bastante más rápidas.