www.delorie.com/djgpp/doc/ug/graphics/vesa.html   search  
Guía: Modos Gráficos VESA

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.

Especificación VBE 2.0 - ftp://x2ftp.oulu.fi/pub/msdos/programming/specs/vbe20.zip
La Especificación VESA Oficial, versión 2.0.
Especificación VESA 1.2 - ftp://x2ftp.oulu.fi/pub/msdos/programming/specs/vesasp12.zip
Una versión anterior de la especificación VESA. Esta ha sido reemplazada por el documento anterior, pero puede ser una referencia útil si quiere asegurarse que su código funcionará en diferentes versiones del driver VESA.
VGADOC - ftp://x2ftp.oulu.fi/pub/msdos/programming/docs/vgadoc4b.zip
Para cualquier masoquista que quiera tratar de escribir código SVGA diréctamente a nivel de hardware en vez de usar VESA, este contiene información detallada a cerca de los conjuntos de chips más comunes.

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)