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
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 -lXxf86dgaPor 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).
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.
chown root.root fichero_ejecutable chmod a+s fichero_ejecutableCon 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.
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.
#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:
XF86DGADirectVideo(display, DefaultScreen(display), 0);Tras esto ya se puede salir del programa con normalidad o continuar trabajando con otras técnicas.
Santiago Romero