www.delorie.com/djgpp/doc/ug/graphics/vesa.html | search |
Los modos VGA estándard están muy bien, pero pronto, es seguro, que usted querra una resolución más alta, o por ejemplo una con más de 256 colores. Esto significa cambiar a un modo SVGA, que significa Super-VGA, es decir, mejor de lo que era posible con las tarjetas VGA original. Hoy hay miles de diferentes e incompatibles tarjetas SVGA en el mercado, pero usted no necesita escribir código especial para cada una de ellas, porque usted puede usar la interfaz VESA estándard. Esta es una API de software que fué diseñada por la Asociación de Estándares de Electrónicos de Video, y usualmente es implementada como una utilidad TSR cargable o como parte de la ROM BIOS de su tarjeta de video. VESA le permite hacer cosas como cambiar los modos gráficos y desplegar imágenes sin necesidad de saber los detalles de cada conjunto de chips graficos: esto es obviamente algo bueno si quiere que su programa trabaja en diferentes máquinas!
Si usted aun no lo ha hecho, debería comenzar por obtener una copia de la especificación VESA(vea los enlaces al final de este fichero). Una gran cantidad de material de la documentación official se repertirá aquí, pero esto es un tutorial para iniciados más que una referencia técnica completa, por lo que usted probablemente quiere tener la especificación a mano como referencia.
Todas las funciones VESA son accesadas llamando a al interrupción 0x10 con un valor de la forma 0x4F?? en el registro AX, donde ?? representa la función específica que quiere ejecutar. Ellas regresan un valor de cero en AH si la función tiene éxito, o no-cero si se produjo un error.
El primer paso es asegurarse de que un driver VESA está presente, y recojer una copia de la estructura de información VESA. Ésta es definida como:
typedef struct VESA_INFO { unsigned char VESASignature[4] __attribute__ ((packed)); unsigned short VESAVersion __attribute__ ((packed)); unsigned long OEMStringPtr __attribute__ ((packed)); unsigned char Capabilities[4] __attribute__ ((packed)); unsigned long VideoModePtr __attribute__ ((packed)); unsigned short TotalMemory __attribute__ ((packed)); unsigned short OemSoftwareRev __attribute__ ((packed)); unsigned long OemVendorNamePtr __attribute__ ((packed)); unsigned long OemProductNamePtr __attribute__ ((packed)); unsigned long OemProductRevPtr __attribute__ ((packed)); unsigned char Reserved[222] __attribute__ ((packed)); unsigned char OemData[256] __attribute__ ((packed)); } VESA_INFO;
Los modificadores __attribute__ se necesitan para asegurarnos que gcc enpaque la estructura en la disposición VESA estándard en vez de añadir bytes entre algunos campos como lo haría normalmente.
Habiendo declarado la estructura, usted puede llamar a la función VESA 0x4F00 para llenarla con la información a cerca del driver actual. Como VESA fue diseñada como una API en modo real para ser usada por programas de 16 bits, estos datos deben ser transferidos usando un buffer en memoria convencional con las funciones dosmemput() y dosmemget(): vea el capítulo DPMI para los detalles de todo esto. La siguiente función copiará la información del driver VESA en una estructura global VESA_INFO. regresando cero si fue exitosa o -1 si algo ocurrió malo(si no hay un driver disponible).
#include <dpmi.h> #include <go32.h> #include <sys/farptr.h> VESA_INFO vesa_info; int get_vesa_info() { __dpmi_regs r; long dosbuf; int c; /* use the conventional memory transfer buffer */ dosbuf = __tb & 0xFFFFF; /* initialize the buffer to zero */ for (c=0; c<sizeof(VESA_INFO); c++) _farpokeb(_dos_ds, dosbuf+c, 0); dosmemput("VBE2", 4, dosbuf); /* call the VESA function */ r.x.ax = 0x4F00; r.x.di = dosbuf & 0xF; r.x.es = (dosbuf>>4) & 0xFFFF; __dpmi_int(0x10, &r); /* quit if there was an error */ if (r.h.ah) return -1; /* copy the resulting data into our structure */ dosmemget(dosbuf, sizeof(VESA_INFO), &vesa_info); /* check that we got the right magic marker value */ if (strncmp(vesa_info.VESASignature, "VESA", 4) != 0) return -1; /* it worked! */ return 0; }
Después de llamar la función get_vesa_info() usted puede querer examinar unos cuantos valores que dejó en la estructura vesa_info, notablemente los campos VESAVersion, Capabilities, y TotalMemory. Asumiendo que la llamada se ejecutó con éxito, el siguiente paso es saber qué modo quiere usar, y recoger otra estructura de información que es específica para ese modo. En teoría VESA soporta una variedad infinita de resoluciones posibles, pero obviamente la mayoría del hardware solo puede manejar unos cuantos modos específicos. Hasta el momento el más común el la resolución 640x800, pero la mayoría de las tarjetas tambien pueden manejar tamaños de 800x600 y 1024x768, y muchos pueden ir hasta 1280x1024 y 1600x1200 así como, ocasionalmente, soportando modos de baja resolución como 320x240 y 360x400, y extraños tamaños como 512x512. Hay unos cuantos modos estándard como 0x101 para el modo 640x480 de 256 colores, y usted verá muchos tutoriales y código que usan esos valores fijos, pero no es una buena idea depender de ellos porque la versión más reciente de la especificación VESA advierte que pueden cambiar en el futuro. Aunque esto no es un problema, porque existe un modo perfecto de revisar qué modos están disponibles al ejecutarse, que tambien tiene la ventaja de permitir que su programa soporte cualquier modo extraño que el driver pueda soportar en el futuro o en diferente hardware, incluso si usted no sabía a cerca de él cuando lo escribió
Información a cerca de un modo particular puede obtenerse en forma similar al bloque de información VESA principal, pero usando la función 0x4F01 con una estructura diferente:
typedef struct MODE_INFO { unsigned short ModeAttributes __attribute__ ((packed)); unsigned char WinAAttributes __attribute__ ((packed)); unsigned char WinBAttributes __attribute__ ((packed)); unsigned short WinGranularity __attribute__ ((packed)); unsigned short WinSize __attribute__ ((packed)); unsigned short WinASegment __attribute__ ((packed)); unsigned short WinBSegment __attribute__ ((packed)); unsigned long WinFuncPtr __attribute__ ((packed)); unsigned short BytesPerScanLine __attribute__ ((packed)); unsigned short XResolution __attribute__ ((packed)); unsigned short YResolution __attribute__ ((packed)); unsigned char XCharSize __attribute__ ((packed)); unsigned char YCharSize __attribute__ ((packed)); unsigned char NumberOfPlanes __attribute__ ((packed)); unsigned char BitsPerPixel __attribute__ ((packed)); unsigned char NumberOfBanks __attribute__ ((packed)); unsigned char MemoryModel __attribute__ ((packed)); unsigned char BankSize __attribute__ ((packed)); unsigned char NumberOfImagePages __attribute__ ((packed)); unsigned char Reserved_page __attribute__ ((packed)); unsigned char RedMaskSize __attribute__ ((packed)); unsigned char RedMaskPos __attribute__ ((packed)); unsigned char GreenMaskSize __attribute__ ((packed)); unsigned char GreenMaskPos __attribute__ ((packed)); unsigned char BlueMaskSize __attribute__ ((packed)); unsigned char BlueMaskPos __attribute__ ((packed)); unsigned char ReservedMaskSize __attribute__ ((packed)); unsigned char ReservedMaskPos __attribute__ ((packed)); unsigned char DirectColorModeInfo __attribute__ ((packed)); unsigned long PhysBasePtr __attribute__ ((packed)); unsigned long OffScreenMemOffset __attribute__ ((packed)); unsigned short OffScreenMemSize __attribute__ ((packed)); unsigned char Reserved[206] __attribute__ ((packed)); } MODE_INFO; MODE_INFO mode_info; int get_mode_info(int mode) { __dpmi_regs r; long dosbuf; int c; /* use the conventional memory transfer buffer */ dosbuf = __tb & 0xFFFFF; /* initialize the buffer to zero */ for (c=0; c<sizeof(MODE_INFO); c++) _farpokeb(_dos_ds, dosbuf+c, 0); /* call the VESA function */ r.x.ax = 0x4F01; r.x.di = dosbuf & 0xF; r.x.es = (dosbuf>>4) & 0xFFFF; r.x.cx = mode; __dpmi_int(0x10, &r); /* quit if there was an error */ if (r.h.ah) return -1; /* copy the resulting data into our structure */ dosmemget(dosbuf, sizeof(MODE_INFO), &mode_info); /* it worked! */ return 0; }
Obviamente esta función solo es útil si usted ya conoce el número del modo que debe pasar como el parámetro, pero esa información se puede obtener fácilmente del bloque de información VESA principal. Este contiene una lista de todos los modos posibles que están soportados por el driver, por lo que usted puede escribir una pequeña rutina que circulará a travez de todos esos modos, recogiendo información a acerca de cada uno en turno hasta que encuentra el que usted estába buscando. Por ejemplo:
int find_vesa_mode(int w, int h) { int mode_list[256]; int number_of_modes; long mode_ptr; int c; /* check that the VESA driver exists, and get information about it */ if (get_vesa_info() != 0) return 0; /* convert the mode list pointer from seg:offset to a linear address */ mode_ptr = ((vesa_info.VideoModePtr & 0xFFFF0000) >> 12) + (vesa_info.VideoModePtr & 0xFFFF); number_of_modes = 0; /* read the list of available modes */ while (_farpeekw(_dos_ds, mode_ptr) != 0xFFFF) { mode_list[number_of_modes] = _farpeekw(_dos_ds, mode_ptr); number_of_modes++; mode_ptr += 2; } /* scan through the list of modes looking for the one that we want */ for (c=0; c<number_of_modes; c++) { /* get information about this mode */ if (get_mode_info(mode_list[c]) != 0) continue; /* check the flags field to make sure this is a color graphics mode, * and that it is supported by the current hardware */ if ((mode_info.ModeAttributes & 0x19) != 0x19) continue; /* check that this mode is the right size */ if ((mode_info.XResolution != w) || (mode_info.YResolution != h)) continue; /* check that there is only one color plane */ if (mode_info.NumberOfPlanes != 1) continue; /* check that it is a packed-pixel mode (other values are used for * different memory layouts, eg. 6 for a truecolor resolution) */ if (mode_info.MemoryModel != 4) continue; /* check that this is an 8-bit (256 color) mode */ if (mode_info.BitsPerPixel != 8) continue; /* if it passed all those checks, this must be the mode we want! */ return mode_list[c]; } /* oh dear, there was no mode matching the one we wanted! */ return 0; }
Y finalmente, ¡usted está listo para seleccionar el modo gráfico VESA y comenzar a dibujar cosas en la pantalla!. Esto se hace llamando a la función 0x4F02 con el número del modo en el registro BX:
int set_vesa_mode(int w, int h) { __dpmi_regs r; int mode_number; /* find the number for this mode */ mode_number = find_vesa_mode(w, h); if (!mode_number) return -1; /* call the VESA mode set function */ r.x.ax = 0x4F02; r.x.bx = mode_number; __dpmi_int(0x10, &r); if (r.h.ah) return -1; /* it worked! */ return 0; }
La memoria de video SVGA está localizada en la dirección física 0xA0000, igual que en el modo 13h, pero hay un pequeño problema con esto: ¡simplemente no hay suficiente espacio para que quepa todo allí! El mapeado original DOS de la memoria solo incluye espacio para 64k de memoria de video entre 0xA0000 y 0xB0000, que está bien para una resolución de 320x200, pero no es siquiera suficientemente a lo que requiere una pantalla a 640x480( que toma hasta 300k de espacio en el framebuffer, y a resoluciones mayores requiere aun más). Los diseñadores de hardware solucionaron éste problema usando una arquitectura de memoria por bancos, donde la región de 64k de la memoria VGA se usa como una ventana deslizante sobre la memoria de video real dentro de su tarjeta. Para accesar una localización arbitraria en la pantalla SVGA primero debe llamar a la función VESA 0x4F05 para decirle que banco quiere usar, y entonces escribir en la localización de la memoria de ese banco. Usted puede escoger el banco con la siguiente función:
void set_vesa_bank(int bank_number) { __dpmi_regs r; r.x.ax = 0x4F05; r.x.bx = 0; r.x.dx = bank_number; __dpmi_int(0x10, &r); }
Usando esto, una simple función putpixel puede ser implementada como:
void putpixel_vesa_640x480(int x, int y, int color) { int address = y*640+x; int bank_size = mode_info.WinGranularity*1024; int bank_number = address/bank_size; int bank_offset = address%bank_size; set_vesa_bank(bank_number); _farpokeb(_dos_ds, 0xA0000+bank_offset, color); }
Nota: Muchos tutoriales VESA, y en realidad unos cuantos programas de producción asumen que los bancos de la memoria SVGA siempre serán de 64k de tamaño. Esto es cierto en cerca del 95% de las tarjetas, pero por allí hay algún hardware con bancos de tamaños de 4 o 16k, por lo que la aproximación correcta es leer el tamaño del banco desde el campo WinGranularity del la estructura de información del modo como se demostró anteriormente.
Nota 2: ¡La función de cambiar bancos es muy lenta! esta simplística rutina putpixel es demaciado ineficiente para ser útil en la vida real. Puede ser mejorada haciendo que la función set_vesa_bank() solo cambie los bancos si el nuevo valor es diferente al actual, y usted debería tratar de optimizar sus funciones de dibujo más complejas para usar la menor cantidad de cambios de bancos posibles.
Como el cambio de bancos es tan lento y torpe, usualmente es más útil hacer todo el dibujo en un arreglo framebuffer en la memoria regular, y copiarla al rededor de la ventana VESA en un solo paso una vez que la imágen está completa. Esto puede ser hecho usando la función de las siguientes líneas:
void copy_to_vesa_screen(char *memory_buffer, int screen_size) { int bank_size = mode_info.WinSize*1024; int bank_granularity = mode_info.WinGranularity*1024; int bank_number = 0; int todo = screen_size; int copy_size; while (todo > 0) { /* select the appropriate bank */ set_vesa_bank(bank_number); /* how much can we copy in one go? */ if (todo > bank_size) copy_size = bank_size; else copy_size = todo; /* copy a bank of data to the screen */ dosmemput(memory_buffer, copy_size, 0xA0000); /* move on to the next bank of data */ todo -= copy_size; memory_buffer += copy_size; bank_number += bank_size/bank_granularity; } }
La descripción del anterior mecanismo de cambio de bancos es en realidad una simplificación del asunto, porque VESA soporta dos bancos diferentes(descritos como "ventanas"(windows) en la especificación), que pueden ser configurados en una variedad de formas dependiendo del hardware. Usualmente solo hay un banco que es usando para lectura y escritura en la memoria de video, pero algunas tarjetas pueden tener dos ventanas usando diferentes rangos de direcciones(ej: uno desde 0xA0000 a 0xB0000 y otro desde 0xB0000 a 0xC0000 o dos trozos de 32k, desde 0xA0000 hasta 0xA8000 a 0xB0000), o podría tener una ventana para escribir en la pantalla y otra diferente para leer desde ella, donde cada una puede estar posicionada independientemente de la otra. Usted no necesita preocuparse de esto cuando dibuja en la pantalla, siempre y cuando se asegure de usar los valores WinSize y WinGranularity en vez de asumir que los bancos siempre tendrán un tamaño de 64k, pero es esencial revisar la configuración de las ventanas antes de tratar de leer los pixeles desde la pantalla. Si los últimos bits en el campo mode_info.WinAAttributes son 1, la primera ventana se puede leer y puede proseguir normalmente. Si no, se tiene una segunda ventana para las operaciones de lectura, lo que significa cambiar la función set_vesa_bank() para poner un 1 en el registro BX, y escribir a mode_info.WinBSegment*16 en vez de la dirección por defecto 0xA0000.
Referencias.
traducido por ADnoctum
webmaster donations bookstore | delorie software privacy |
Copyright © 1998 by DJ Delorie | Updated Jan 1998 |
Please take a moment to fill out this visitor survey
You can help support this site by visiting the advertisers that sponsor it! (only once each, though)