Pantallas Virtuales

 
Hardware
Modo 13h
Retrazado Vertical
Primitivas
La Paleta
Pantallas Virtuales
Sprites
Tablas Prefedifinidas
Efectos
Texto
Formatos gráficos
Periféricos
Optimizaciones
Herramientas
Cursos
Links
Foro
Correo Electrónico


¿Qué son y para qué sirven?

Alguna vez habrán visto alguna demo, o por lo menos un juego comercial y se habrán dado cuenta que se hacen muchas animaciones y efectos, y no se ve ni el más mínimo parpadeo en la pantalla.

Si por ejemplo nosotros realizamos una animación simple, como puede ser el movimiento de un cuadrado por la pantalla, notaremos que el movimiento no es suave, sino que la figura parpadea constantemente y a veces puede pasar que no se vea alguna parte de la figura. En la sección ejemplos, que se encuentra al final de esta pagina, podrán darse cuenta de estos efectos que suceden, y hacen que la realización de una animación no sea profesional.

Todo este parpadeo es producido por los constantes cambios que se hace en la pantalla en el momento en que se está dibujando.

En la sección retrazado vertical, vimos que el usar esta técnica sincronizaba un poco nuestro programa con la pantalla, pero supongamos que tenemos que dibujar un pantalla con un fondo un poco complejo y encima de este fondo tenemos que dibujar varias figuras que se moverán en la pantalla. El tiempo de retrazado vertical no seria suficiente para dibujar toda la pantalla, así que la técnica de retrazado vertical en este caso no nos serviría.

Piensen por ejemplo que no sería nada excelente que el usuario viera como dibujamos el fondo y luego las figuras una por una y después borramos la pantalla para volver a dibujar el fondo. Esto sería horrible, lo ideal sería que todo se dibujara tan rápidamente que diera la impresión que el fondo nunca se borra y que las figuras realmente están en movimiento.

Pero la CPU y en especial la memoria de video, no son tan rápidos como para lograr esto, por lo que tenemos que hacer un pequeño truco.

Que pasaría si en lugar de dibujar el fondo y las figuras en la pantalla, las dibujamos en otro lugar y luego simplemente copiamos todo a la pantalla real, una vez que ha sido todo dibujado. Y mientras el usuario admira la escena, nosotros estamos dibujando el siguiente cuadro en nuestra “pantalla virtual”, pero el usuario no verá nada hasta que copiemos toda las escena a la pantalla real. Copiar una pantalla de un lugar a otro, en el modo13h significa mover 64.000 bytes de una zona de la memoria a otra. 

Entonces una pantalla virtual es simplemente una zona de memoria, en el caso del modo 13h de 64.000 bytes, que funciona como la memoria real de la VGA. Nosotros podemos dibujar puntos, líneas, círculos o lo que sea en nuestra pantalla virtual, la única diferencia es que nada de lo que dibujemos en la pantalla virtual es visible. Nosotros podemos dibujar una escena que tarde 1 segundo en dibujarse y luego simplemente copiamos los 64 Kb de la pantalla virtual a la memoria de la VGA, lo cual tarda solo un instante.

Para el usuario que esta viendo el programa, la pantalla se dibujó en ese instante, aunque como ya sabemos esto no fue así. Además mientras el usuario esta viendo la pantalla, nosotros podemos estar dibujando en otras pantalla virtuales sin modificar lo que realmente se esta viendo.
 

Volver


¿Cómo se hace una pantalla virtual?

La pantalla virtual, como ya sabemos, debe contener 64.000 bytes. Para necesitamos reservar una zona de memoria con 64 Kb. Alguien podría pensar que para esto simplemente podríamos usar un arreglo declarado de la siguiente forma:

unsigned char pvirtual[64000];

Pero lamentablemente, el lenguaje C y muchos otros lenguajes de alto nivel, utilizan 64 Kb para las variables del programa, por lo que si hacemos lo anterior, nos quedaríamos sin memoria para otras variables. Así que tenemos solo una posibilidad, y esta es usar punteros para acceder al heap (montón). Esto en C lo haríamos así, primero definimos un puntero que apunte a datos de tipo byte:

unsigned char *pvirtual;

Luego deberíamos reservar la memoria necesaria, utilizando las funciones que nos proporciona el lenguaje C, podríamos ocupar la funcion malloc o la callos, la diferencia de esta última con la función malloc, es que llena con ceros el bloque reservado, lo cual es bueno en nuestro caso, ya que nuestra pantalla virtual quedaría automáticamente limpia, es decir toda llena con el color 0, que por defecto es el negro. Entonces podríamos reservar memoria de dos formas:

pvirtual=(unsigned char *)malloc(64000);

o

pvirtual=(unsigned char *)calloc(64000, sizeof(unsigned char));

De esta forma tendríamos un puntero, apuntando al primer elemento del bloque de 64 Kb. Pero hay un problema, debemos verificar si realmente se nos reservó la memoria requerida para esto podemos ver si la variable pvirtual es igual a NULL, si es así significa que no hay memoria suficiente, esta memoria se refiere a la memoria convencional que posee libre nuestro PC. Recuerden que la memoria convencional es de 640 Kb solamente. Para verificar si se reservó o no memoria, podemos hacer lo siguiente:
 

if(pvirtual==NULL)
{
    printf(“\n¡No hay memoria suficiente!\n”);
    exit(1);
}

Pero antes que termine nuestro programa, debemos liberar la memoria utilizada por la pantalla virtual, y esto lo haremos simplemente utilizando la funcion free:

free(pvirtual);

Ya sabemos crearnos una pantalla virtual, pero seria conveniente tener una funcion que nos hiciera todo este trabajo, así que crearemos un nuevo tipo de dato para la pantalla virtual, esto lo haremos así:

typedef unsigned char *t_pvirtual;

Así, si necesitamos una pantalla virtual simplemente la declararemos de esta forma:

t_pvirtual pv;

La funcion que nos cree la pantalla virtual, es decir la que nos reserve la memoria y nos indique además si pudo lograrlo, se podría hacer así:
 

unsigned char *SetPVirtual(unsigned char **pv)
{
    *pv=(unsigned char *)calloc(64000, sizeof(unsigned char));

     return (*pv);
}

Ojo con esta funcion, recibe como parámetro un puntero a un puntero, esto es así para poder ocupar el puntero fuera de la funcion y modificarlo. Después de reservar la memoria, la funciona devuelve el mismo puntero, esto nos servirá para verificar si se pudo reservar la memoria. En caso que no se logra la reserva, la funcion devolverá NULL, ya que *pv tendrá ese valor. Un ejemplo simple de la utilización de esta función podría ser el siguiente:
 

// Declaramos una variable de tipo pantalla virtual
t_pvirtual pv;

//Llamamos a la función con el parámetro requerido

if(!SetPVirtual(&pv))
{
    printf(“\nNo hay memoria suficiente\n”);
    exit(1);
}

Ojo con el ejemplo, le pasamos la dirección del puntero &pv, ya que la función nos pide un puntero a un puntero. Si la funcion devuelve NULL, significará que no se pudo reservar  memoria y por lo tanto ese NULL equivale a que la funcion devolvió un cero, luego la negamos con ! y por lo tanto la condición del if tendrá el valor 1, lo que significa que es verdadero (recuenden que en C, 0=falso y 1=verdadero) y por lo tanto se ejecutará el código correspondiente al if.

La funcion inversa, es decir la que elimina el bloque de memoria reservada es el siguiente:
 

void FreePVirtual(unsigned char **pv)
{
    free(*pv);
    *pv=NULL;
}

Esta función es muy simple, lo que se hace es liberar el bloque de memoria, reservado anteriormente con malloc o calloc, y luego se hace que el puntero no apunte a nada, es decir se le asigna el valor NULL. 


Volver


¿Cómo dibujar en la pantalla virtual?

Si ya tenemos reservada la memoria para un puntero llamado por ejemplo pv, dibujar en pv es análogo a como lo hicimos con el puntero DIR_VGA, la única diferencia es que ahora lo que hagamos o mejor dicho dibujemos, no se verá en la pantalla. Por ejemplo podríamos hacer lo siguiente con la variable pv:
 

for(x=0; x<320; x++)
    for(y=0; y<200; y++)
        pv[y*320+x]=random(256);

En este caso lo que se hace es simplemente llenar los 64.000 bytes de nuestra pantalla virtual con pixeles al azar.

Ahora que ya sabemos trabajar muy bien con una pantalla virtual tendremos que modificar todas las rutinas que hemos hecho hasta el momento. Para esto simplemente se le agregará un parámetro a cada función, donde este parámetro será un puntero que nos indicará donde dibujar. Voy hacer solo el ejemplo para la funcion PutPixel, las demás modificaciones las podrás ver al final de esta pagina en el archivo vgalib.h
 

void PutPixel(int x, int y, unsigned char color, unsigned char *where)
{
    where[(y<<8)+(y<<6)+x]=color;
}

Si se dan cuenta es super simple, ahora cuando hagan un programa solo deben especificar donde quieren dibujar. Si quisieran dibujar en la pantalla real, simplemente le pasan como ultimo parámetro el puntero DIR_VGA. 


Volver


¿Cómo copiamos una pantalla virtual a la VGA?

Esto también es simple. Para esto podemos usar una función que se encuentra en mem.h y es la función memmove. Lo que hace esta funcion es mover un bloque de memoria de un lugar a otro. Sus parámetros son: un puntero destino, otro fuente, y el numero de bytes que se quieren mover. El número de bytes máximo que se pueden mover es de 64.000 bytes, lo que justamente corresponde a lo que nosotros necesitamos. Podemos crear una funcion que reciba dos punteros uno destino y otro fuente:
 

void FlipTo(unsigned char *source, unsigned char *where)
{
    memmove(where, source, 64000);
}

Por ejemplo si quisiéramos mover el contenido de la pantalla virtual pv al memoria de la VGA, haríamos lo siguiente:

FlipTo(pv, DIR_VGA);

Como esta función será muy utilizada podemos crear otras similar, pero solo con un parámetro:
 

void Flip(unsigned char *source)
{
    memmove(DIR_VGA, source, 64000);
}

Como ven, ya sabemos todo lo necesario para trabajar con pantallas virtuales. Pero las funciones Flip y FlipTo (que técnicamente son iguales) pueden resultar un poco lentas. La solución a esto es crear estas funciones en ensamblador, pero todo lo que se refiere a optimizaciones lo encontraras en la sección correspondiente. La función de volcado es una de las que debería estar mejor optimizada para lograr una velocidad aceptable en nuestros programas. 


Volver


Ejemplo

Librería gráfica: vgalib.h

Muestra un cuadro en movimiento usando tres técnicas diferentes:

1) Se borra y se vuelve a dibujar el cuadro.
2) Se inserta el procedimiento WaitRetrace() antes de mover el cuadro.
3) Se borra y se dibuja el bloque en una pantalla virtual, luego llama al procedimiento WaitRetrace() y luego se copia la pantalla virtual a la VGA.

Cuando ejecuten el programa verán la diferencia, y ahí se darán cuenta de la gran utilidad de las pantallas virtuales. De aquí en adelante las pantallas virtuales se usarán mucho, especialmente en lo que se refiere a animaciones.

Ver ejemplo
 
 

Bajar todo Bajar todo (26 Kb)

 
Volver

Anterior
Página principal
Siguiente
1
Hosted by www.Geocities.ws

1