INTRODUCCION A LA PROGRAMACION EN X WINDOW

Artículo 11: Direct Graphics Access (DGA).

Autor: (c) Santiago Romero.
Revista: Programación Actual (Prensa Técnica) nº 29, Agosto-1999



Mediante la extensión DGA de X Window es posible tener acceso directo al FrameBuffer de la tarjeta de vídeo, a la videomemoria misma, pudiendo utilizar nuestro propio código gráfico o incluso librerías de otros sistemas con funciones preparadas para acceder a la videomemoria, obteniendo así el máximo rendimiento y velocidad.


El uso de la extensión Direct Graphics Access (DGA) proporciona una velocidad incluso superior a la obtenida con Mit Shared Memory (XShm), dando acceso directo al programa cliente a la VRAM de la tarjeta (FrameBuffer). Su principal desventaja es que al disponer de acceso a la videomemoria misma nuestro programa deberá de tener un set de funciones gráficas ya programadas (no pudiendo usar las de la API de Xlib), debiendo estar preparadas para trabajar en cualquier modo de vídeo y profundidad de color existentes. Esto implica la necesidad de crear una librería de rutinas gráficas de sprites, fuentes, bitmaps, y otras primitivas, o bien reutilizar alguna de las ya existentes para éste u otros Sistemas Operativos.

En MSDOS existen multitud de librerías gráficas creadas para acceder directamente en modo 13h (320x200) a la videomemoria, así como librerías para Vesa 2.0 que utilizan el Linear Frame Buffer para dibujar directamente sobre la memoria de la tarjeta. Como DGA tiene el mismo significado (escritura directa sobre el buffer de vídeo de la tarjeta) es posible adaptar facilmente estas librerías para su uso bajo Xwindow y DGA.

De la misma manera, muchos programadores de Windows-9x que utilizan DirectX basan sus librerías en el uso de la función ::lock() de las Surface, la cual devuelve un puntero a la surface para poder trabajar sobre ella directamente (que es, de hecho, lo que se hace en MSDOS y en DGA), de modo que dichas librerías pueden adaptarse también para ser usadas en X.

Un ejemplo de programa que usa esta técnica gráfica es, de nuevo, el emulador de máquinas recreativas X Mame, quien hace uso de ésta técnica así como de la MitSharedMemory extensión para acelerar la versión X-Window de su potente emulador.


LISTADO 1: SECCION DE INICIALIZACION DE XMAME

/*
 *	XFree86 VidMode and DGA support by Jens Vaasjo <jvaasjo@iname.com>
 */
#ifdef USE_DGA
#define __XF86_DGA_C

/* (inicialmente habían algunas declaraciones de funciones y estructuras) */
/* lo primero que nos interesa es la función de inicialización : */

int xf86_dga_init(void)
{
   int major,minor,event_base,error_base,flags;
	
   xf86_dga_available = 0;
   xf86ctx.screen     = DefaultScreen(display);

    if(geteuid())
     fprintf(stderr,"DGA requires root rights\n");
  
   else if(!XF86DGAQueryVersion(display,&major,&minor))
     fprintf(stderr,"XF86DGAQueryVersion failed\n");

   else if(!XF86DGAQueryExtension(display,&event_base,&error_base))
     fprintf(stderr,"XF86DGAQueryExtension failed\n");

   else if(!XF86DGAQueryDirectVideo(display,xf86ctx.screen,&flags))
     fprintf(stderr,"XF86DGAQueryDirectVideo failed\n");

   else if(!(flags & XF86DGADirectPresent))
     fprintf(stderr,"XF86DGADirectVideo support is not present\n");

    else if(!XF86DGAGetVideo(display,xf86ctx.screen,
                   &xf86ctx.base_addr,&xf86ctx.width,
                   &xf86ctx.bank_size,&xf86ctx.ram_size))
       fprintf(stderr,"XF86DGAGetVideo failed\n");
   else
      xf86_dga_available = 1;
		
   if (!xf86_dga_available)
      fprintf(stderr,"Use of DGA-modes is disabled\n");

   if(setuid(getuid()))
   {
        perror("setuid");
       XCloseDisplay(display);
       return OSD_NOT_OK;
   }
   return OSD_OK;
}

/* a continuación podemos ver la función para establecer el colormap */
static int xf86_setup_palette_pseudocolor(void)
{
   XColor color;
   int i;

   xf86ctx.cmap = XCreateColormap(display,window,xf86ctx.visual,AllocAll);
   for(i=0;i<xf86ctx.visual->map_entries;i++)
   {
       color.pixel = i;
     color.red   = 0;
     color.green = 0;
     color.blue  = 0;
     color.flags = DoRed | DoGreen | DoBlue;
     XStoreColor(display,xf86ctx.cmap,&color);
   }

   if(!XF86DGAInstallColormap(display,xf86ctx.screen,xf86ctx.cmap))
   {
      fprintf(stderr_file,"XF86DGAInstallColormap failed\n");
      return 1;
   }
   return 0;
}

/* por ultimo, la funcion de cierre del display DGA */
void xf86_dga_close_display(void)
{
   if(xf86ctx.cmap)
   {
      XFreeColormap(display,xf86ctx.cmap);
      xf86ctx.cmap = 0;
   }
   if(xf86ctx.pixels)
   {
      free(xf86ctx.pixels);
      xf86ctx.pixels = NULL;
   }
   if(xf86ctx.grabbed_mouse)
   {
      XUngrabPointer(display,CurrentTime);
      xf86ctx.grabbed_mouse = FALSE;
   }
   if(xf86ctx.grabbed_keybd)
   {
      XUngrabKeyboard(display,CurrentTime);
      xf86ctx.grabbed_keybd = FALSE;
   }
   XF86DGADirectVideo(display,xf86ctx.screen, 0);
           xf86_dga_vidmode_restoremode(display);
}

#endif /*def USE_DGA*/

 FIN LISTADO


FUNCIONES DGA

En primer lugar vamos a ver las declaraciones de todas las funciones de DGA disponibles, así como la acción que realizan y el significado de sus parámetros de llamada, para posteriormente ver los pasos necesarios para utilizarlas en nuestro programa.

Las funciones aquí comentadas tienen sus prototipos en X11/extensions/xf86dga.h, por lo que la siguiente línea será necesaria para los programas que las usen:


#include <X11/extensions/xf86dga.h>
 
También es necesario linkar la librería de funciones DGA (libXxf86dga.a) durante el proceso de compilación de nuestro programa:

gcc -o programa programa.c -lX11 -L/usr/X11R6/lib -lXext -lXxf86dga

Por otra parte, al igual que ocurría con XShm, antes de poder utilizar cualquier función de DGA es necesario consultar al servidor X si dispone de la extensión en el sistema, así como la versión de la misma (1.0 o superior). Asímismo, si la extensión está instalada existirá en el sistema un programa de test de la misma (simplemente rellena la pantalla de un color) situado en /usr/X11R6/bin. Dicho programa se llama dga y debemos probarlo como root o activándole el bit SetUID (luego veremos el porqué de esto).


FUNCIONES DE INICIALIZACION Y VERSIONES

Bool XF86DGAQueryExtension( Display *display, int *event_base_return, int *error_base_return);
Nos devuelve el número de error y evento asignado a la extensión DGA. Ademas devuelve cero si no existe DGA en nuestro sistema, con lo que nos sirve para comprobar si dicha extensión está disponible en el servidor X:



 Display *display;
 int event_base, error_base;
 
 if( !XF86DGAQueryExtension(display, &event_base, &error_base) ) 
 {
    printf("\a\nNo se encontró la DGA extension.\n");
    return(-1);
  }

XF86DGAQueryVersion( Display *display, int *majorVersion, int *minorVersion);
Esta función devuelve la versión de la extensión XFree86-DGA en el formato majorVersion.minorVersion, para los enteros que se le pasen por referencia:


  Display *display;
  int major, minor;
 
  XF86DGAQueryVersion(display,&major,&minor);
  printf("Version de DGA: %d.%d\n", major, minor );

En muchas de las funciones de DGA hace falta pasarle variables por referencia (como en el caso de major y minor) para el retorno de resultados por la misma. Otro ejemplo de ello es la siguiente función, muy importante para saber si nuestro hardware soporta DirectVideo:

XF86DGAQueryDirectVideo(Display *display, int screen, int *flags);
Devuelve las capacidades de DirectVideo del dispositivo gráfico en la variable flags, que se debe interpretar bit a bit (como máscaras) usando para ello constantes predefinidas en las cabeceras de DGA:


  if( flag & XF86DGADirectPresent ) -> Soporte de DirectVideo está disponible.

Si flag valiese 0, nuestro hardware no podria utilizar DirectVideo para acceder a la VRAM.


FUNCIONES DE ACCESO AL FRAMEBUFFER

XF86DGAGetVideo(Display *display, int screen, char **addr, int *width, int *bankSize, int *memSize);
Esta función devuelve un puntero al principio de la videomemoria (*addr), dandonos pues acceso directo a la misma. Además nos devuelve la anchura de línea (*width), el tamaño de la memoria (*memSize) en KiloBytes y el tamaño de un banco de memoria (*bankSize). Lo que realiza esta función es mapear el video-framebuffer de la tarjeta de vídeo con el puntero que nos es devuelto, para lo cual se utiliza normalmente el recurso de sistema /dev/mem. Esto es una operación privilegiada que sólo podrá realizar root a menos que hagamos lo siguiente para nuestro ejecutable en la linea de comandos:


 chown root.root fichero_ejecutable
 chmod a+s fichero_ejecutable

Con esto activamos el bit SetUID del ejecutable (al menos hasta que la función sea llamada) para que quien lo ejecute herede los permisos del propietario (en este caso root) para la ejecución del programa, y, por tanto, disponga también de acceso a /dev/mem.

XF86DGADirectVideo(Display *display, int screen, int flags);
Activa o desactiva DirectVideo (es decir, indica si el servidor da o no da el control del framebuffer al programa cliente). El parámetro flags es una combinación de bits (obtenida con el operador || ) de las siguientes constantes:

     XF86DGADirectGraphics     Activar DirectVideo.
     XF86DGADirectMouse        Activar una opción que permite que
                               el movimiento del puntero del ratón
                               sea interpretado y remitido a nosotros
                               como movimiento relativo a la anterior
                               posición, en lugar de absoluto respecto
                               a la pantalla.
     XF86DGADirectKeyb         Activar la opción de información directa
                               de eventos del teclado.


FUNCIONES DE BANCOS Y PALETA

XF86DGASetVidPage(Display *display, int screen, int page);
Permite especificar la página del framebuffer (banco) en la que trabajar, y sólo es necesario su uso cuando el hardware al que se está accediendo trabaja con bancos en lugar de con direccionamiento lineal (bankSize<memSize). Quienes hayan programado en MSDOS con VESA 1.2 o 2.0 probablemente ya sabrán que ciertas tarjetas dividen la videomemoria en bancos con los que trabajar linealmente y que hay que usar determinadas peticiones de selección de banco para pasar a otras secciones de la pantalla de trabajo. Para más información véase el Curso de Programación Gráfica publicado en esta misma revista (nºs 1 al 14), seccion SVGA Vesa 1.2.

XF86DGAGetViewPortSize(Display *display, int screen, int *width, int *height);
Devuelve la anchura (*width) y altura (*height) de la ventana de visión (viewport). Esta ventana es una porción del framebuffer total cuando éste es mayor que la pantalla, de modo que el viewport actúa literalmente como una ventana hacia el total del framebuffer.

XF86DGASetViewPort(Display *display, int screen, int x, int y);
Cambia las coordenadas de la esquina superior izquierda de la ventana de visión a (x,y). Esto nos permite realizar scrolls y tareas similares cuando el framebuffer es mayor que nuestro área de visión.

Bool XF86DGAViewPortChanged(Display *display, int screen, int n);
Chequea si se ha finalizado la ejecución de una anterior llamada a SetViewPort() por parte del hardware, lo cual implica que al menos un retrazo vertical de pantalla haya ocurrido desde el anterior cambio de (x,y) en el ViewPort. Gracias a esto es posible implementar page-flipping para animaciones sin parpadeos, nieve, distorsión, etc. Con 2 páginas ya es posible realizar page-flipping (el más simple de ellos). Este número de páginas se especifica en la variable n. Podremos empezar a escribir en la siguiente página cuando esta función nos devuelta TRUE. Para triple-buffering (n=3) o multi-buffering (n>3), la función chequea si la operación SetViewPort (n-2) anterior ha sido realizada con éxito.

XF86DGAInstallColormap(Display *display, int screen, Colormap cmap);
Permite especificar el colormap del display *display al especificado por la variable cmap (i.e., instalar un colormap para la aplicación). Esta función debe ser llamada siempre después de que DirectVideo haya sido activado, con el fin de no obtener un error del tipo XF86DGAScreenNotActive. Gracias a esta función nuestras aplicaciones podrán disponer de la paleta (colormap) más apropiada para su ejecución.

int XF86DGAForkApp(int screen);
Esta función permite hacer un fork() de la aplicación (ver man fork en Linux para saber cómo se realiza y controla el proceso de copia de la aplicación). Devuelve 0, o en caso de error el mismo código de error que la función fork(). En la figura 1 podemos ver dicha página man traducida al castellano.


USANDO LA EXTENSIÓN DGA

A continuación veremos los pasos lógicos a seguir antes de utilizar la extensión DGA en nuestros programas. Para ello lo primero que necesitamos es la inclusión del fichero de cabecera que prototipa dichas funciones (ademas de acordarnos en la compilación de linkar con la libreria libXxf86dga.a):


#include  <X11/extensions/xf86dga.h>

Como ya se ha comentado, lo siguiente que debemos hacer es chequear si la extensión DGA está disponible en el servidor X mediante las funciones XF86DGAQueryExtension() y XF86DGAQueryVersion():


 Display *display;
 int major, minor, event_base, error_base;
 
 if( !XF86DGAQueryExtension(display, &event_base, &error_base) ) 
 {
    printf("\a\nNo se encontró la DGA extension.\n");
    return(-1);
 }

 XF86DGAQueryVersion(display,&major,&minor);
 printf("Version de DGA: %d.%d\n", major, minor );

La versión inicial de este servidor y compatible con todos los prototipos de funciones y código visto aquí es la v1.0. Puede hacerse necesario comprobar que la versión major.minor sea >= 1.0 por si el servidor dispusiera de alguna beta o pre-release (<1.0) que no tuviera implementadas correctamente las funciones aquí presentadas.

A continuación obtenemos los datos del video-framebuffer:


Display *display;
char *address;
int width, height, bytes_per_line, bank_size, vram;
 
/* Primero obtenemos el tamaño del viewport o de los bancos */
XF86DGAGetViewPortSize(display, DefaultScreen(display), 
                                           &width, &height);  

/* posteriormente obtenemos la direccion del framebuffer en si mismo */
XF86DGAGetVideo(display,DefaultScreen(display), &address, 
                                           &bytes_per_line, &bank_size, &vram);
 
/* activamos DirectVideo */
XF86DGADirectVideo(display,DefaultScreen(display), XF86DGADirectGraphics );

Otra posibilidad hubiese sido hacer uso de las otras 2 capacidades de DGA (DirectMouse para el reporte relativo del movimiento del puntero del ratón y DirectKeyb para reporte directo de eventos del teclado):


/* activamos DirectVideo, DirectMouse y DirectKeyb */
XF86DGADirectVideo(display, DefaultScreen(display),
                   XF86DGADirectGraphics | XF86DGADirectMouse | XF86DGADirectKeyb );

Mediante las variables obtenidas (width, height) podemos adaptar nuestras rutinas gráficas al tamaño de la pantalla, área o banco, así como saber la dirección exacta del framebuffer (address) para poder escribir directamente sobre él. El permiso de acceso a dicha dirección lo obtendremos tras la llamada a XF86DGADirectVideo().

Lo siguiente que debemos hacer es averiguar si la tarjeta de vídeo que estamos manejando requiere el uso de bancos o si por el contrario tenemos libre acceso con direccionamiento lineal a la videomemoria de la misma:


Display *display;
Bool banking;
 
if( bank_size < (vram*1024)) 
{
    XF86DGASetVidPage(display, DefaultScreen(display), 0);
    banking = True;
 }
 else
    banking = False;

Con esto disponemos de una variable (banking) que estará a TRUE si la tarjeta necesita direccionamiento por bancos o a FALSE si disponemos de un framebuffer lineal. Gracias a esto nuestro codigo de dibujo puede ser similar al siguiente pseudocódigo:


/*-- pseudocódigo de funcion de dibujo de un pixel para usar con DGA --*/
void PutPixel( int x, int y, long color, Bool banking, char *address )
{
  if( banking == True )
  {
    banco =calcular_banco_donde_reside_el_pixel();
    offset = calcular_offset_del_pixel_en_dicho_banco();
    XF86DGASetVidPage( display, screen, banco );
    address[offset] = color;
  }

  else
  {
    calcular_direccion_absoluta_del_pixel();
    address[direccion] = color;
  }
}

Esto quiere decir que debemos disponer de funciones preparadas para poder trabajar con bancos si es necesario. El trabajo con bancos no es excesivamente lento con las tarjetas de hoy en dia (aunque ya la mayoría soportan Linear Frame Buffer no es seguro que la tarjeta del cliente disponga de esta característica). Trabajar con bancos no es complicado y se puede hacer tal y como se explicó en su momento en el Curso de Programación Gráfica de esta revista (nº 7).

Nuestro último paso antes de comenzar a trabajar en el programa con DGA es poner el viewport a (0,0):


XF86DGASetViewPort(display, DefaultScreen(display), 0, 0);

En este momento, para trabajar en DGA existen 2 posibilidades:

a)- Modo de direccionamiento lineal:
Si la variable banking vale 0, esto indica que no es necesario acceder al framebuffer gráfico por medio de bancos y que por tanto tenemos acceso a la videomemoria como si fuera un único array de Ancho*Alto elementos (pixels). De este modo, para acceder a un determinado pixel de pantalla, bastaría con escribir en la posición correcta de la memoria un dato con el formato adecuado:


 address[(bytes_per_line*y)+x] = color;
Donde color sería un char, short o long dependiendo de la profundidad de color (bits por pixel) de la pantalla, y dentro del cual estarían las 3 componentes de color R, G, B expresadas adecuadamente para ese determinado modo de pantalla (o un índice de color de 0 a 255 para los modos de paleta). Este modo de direccionamiento hace las rutinas de dibujado en pantalla muy rápidas y sencillas, de modo que simplemente habremos de preocuparnos de trabajar con los gráficos correctamente en 8, 16, 24 y 32 bpp. En resumen, si disponemos de acceso lineal podemos basar todas nuestras rutinas gráficas en cálculos del tipo:


offset=(y*AnchoPantalla)+x;
vram[offset] = color_del_pixel;

b)- Modo de direccionamiento por bancos:
Si la variable banking contiene el valor 1 quiere decir que el hardware que está siendo utilizado no soporta acceso directo a toda la videomemoria, sino que sólo puede acceder a una pequeña parte de la misma cada vez. Probablemente (y tomando como ejemplo los procesadores ix86), nosotros sólo tendremos acceso a los 64K (65.536 bytes) de la ventana de videomemoria, que es nuestra ventana a la VideoRAM, de manera que en el modo 640x480x256 (por ejemplo) escribiendo bytes en este segmento tan sólo escribimos en la parte superior de la pantalla, tal y como puede apreciarse en la figura 2. Bastaría con desplazar esa ventana al bloque (banco) inmediatamente inferior (hacia abajo), para que podamos escribir en otra porción de la pantalla SVGA, tal y como se muestra en la misma figura. Este es el concepto de banco (Bank), que podemos cambiar con la función DGA XF86DGASetVidPage(). El posible valor del parámetro bank es un número entero (0-n) 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 así como de las dimensiones de la ventana o viewport.


Bancos en SVGA

En realidad el viewport 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 mismo.


CERRANDO EL DGA

Antes de finalizar el programa, o cuando ya no necesitemos más del uso de la extensión, deberemos desactivar el soporte de DirectVideo en el servidor X:


  XF86DGADirectVideo(display, DefaultScreen(display), 0);

Tras esto ya se puede salir del programa con normalidad o continuar trabajando con otras técnicas.


EN RESUMEN

Hemos visto hasta ahora las 2 extensiones más importantes de X Window en cuanto a gráficos de alta velocidad de refiere (MShm y DGA), así como las técnicas básicas de Xlib para desarrollar el esqueleto de nuestro programa. Nuestro próximo paso, en la próxima entrega, será aprender a utilizar determinadas funciones de la librería Imlib para cargar ficheros gráficos JPG, GIF, JPEG, TIF, XPM o similares en nuestros programas, automáticamente convertidos a la profundidad de color del display activo, y trazados ya en el pixmap que deseemos, lo cual dotará a nuestro programa de infinitas posibilidades de representación gráfica, cargando desde fichero el entorno o imagenes del mismo.

Santiago Romero


Volver a la tabla de contenidos.