|
|
|
¿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.
¿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:
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í:
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:
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:
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. ¿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:
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
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. ¿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:
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:
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. 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.
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.
|