Doble/Triple buffering y Page flipping

2007/10/10

Hoy toca hablar de los métodos de buffering al pintar en pantalla en Allegro (Bueno, en cualquier programa, pero implementado en Allegro xD)

Cuando se hace un juego, siempre hay que ir pintando los gráficos en pantalla, y si se hace sin ningún tipo de buffer, se pinta a la vez que el monitor se refresca, provocando los molestos efectos que hacen que parece que la pantalla parpadea. Esto es, en resumidas cuentas, porque cuando estamos pintando el frame N, también hay restos del N-1, y el monitor pinta partes de cada uno.

Para evitar esto, se usa un buffer donde se pinta todo lo necesario del frame y después se vuelca a la memoria de video para que el monitor lo actualice. Hay varias técnicas.

Page Flipping:

Consiste en tener el doble de memoria (dos páginas de memoria) de lo que la pantalla usa. Es decir, si tenemos una resolución de 800×600 a 16bpp, se usa casi 1 Mbyte de memoria de video. Pues en este caso usaríamos casi 2, para que mientras se dibuja en una página, se muestra la otra.

Por ejemplo, inicializamos el sistema gráfico de la siguiente manera:

BITMAP *pantalla_buffer[2];
int pantalla_actual;
BITMAP *pantalla;

pantalla_buffer[0] = create_video_bitmap(SCREEN_W, SCREEN_H);
pantalla_buffer[1] = create_video_bitmap(SCREEN_W, SCREEN_H);
pantalla_actual = 0;
pantalla = pantalla_buffer[pantalla_actual];
Con esto estamos reservando dos páginas (pantalla_buffer) e indicando que la actual es la 0. Además tenemos la variable pantalla, que será a la que nos refiramos siempre por comodidad, para no tener que estar poniendo pantalla_buffer[pantalla_actual] siempre, y además la haremos accesible al resto de paquetes.

Y para actualizar la pantalla, se llamaría en cada proceso de renderizado (en cada frame) a:

show_video_bitmap(pantalla);
pantalla_actual = (pantalla_actual+1)%2;
pantalla = pantalla_buffer[pantalla_actual];

Con la primera linea lo que hacemos es pedir que se pinte el bitmap pantalla (la página donde hemos pintado). Con las dos siguientes lineas actualizamos la página actual, para que en el siguiente frame, al pintar sobre el bitmap pantalla, pintemos en la otra página a la que se está mostrando.

En algunos sistemas hay que especificar la memoria de video concreta que hay que reservar, así que, antes de nada habría que poner:


#ifdef ALLEGRO_VRAM_SINGLE_SURFACE
error_init = set_gfx_mode(GFX_AUTODETECT_WINDOWED, w, h, w*2, h);
#else
error_init = set_gfx_mode(GFX_AUTODETECT_WINDOWED, w, h, 0, 0);
#endif

Doble Buffer:

Es una técnica bastante parecida, pero en vez de tener dos páginas de memoria de video, tenemos un buffer de memoria principal, que volcaremos sobre la pantalla en cada frame.

Para inicializar el sistema gráfico lo haríamos con:


BITMAP *pantalla = create_bitmap(SCREEN_W, SCREEN_H);

En este caso la variable pantalla actuaría de buffer. Y para actualizar la pantalla copiaríamos el buffer en la memoria de video con:


blit(pantalla, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);

Triple Buffer:

Aunque por su nombre parezca que es como el doble buffer, la verdad es que su implementación se parece más a la del page flipping, pero con tres páginas.

Además, esta técnica no la soportan todos los sistemas, y por ello se debería de dar opcional a las otras técnicas, por si no funciona. Para comprobar si se puede o no, se usa:


gfx_capabilities & GFX_CAN_TRIPLE_BUFFER

Si el flag comprobado no está activado, puede intentar activarse de todas maneras con la funcion enable_triple_buffer()

Una vez se haya comprobado que se puede, se inicializa igual que el page_flipping, pero en vez de ser un array de 2 páginas, es de 3:


BITMAP *pantalla_buffer[3];
int pantalla_actual;
BITMAP *pantalla;

pantalla_buffer[0] = create_video_bitmap(SCREEN_W, SCREEN_H);
pantalla_buffer[1] = create_video_bitmap(SCREEN_W, SCREEN_H);
pantalla_buffer[2] = create_video_bitmap(SCREEN_W, SCREEN_H);
pantalla_actual = 0;
pantalla = pantalla_buffer[pantalla_actual];
Y se actualiza cada frame de la siguiente manera:


while(poll_scroll());
request_video_bitmap(pantalla);
pantalla_actual = (pantalla_actual+1)%3
pantalla = pantalla_buffer[pantalla_actual];

Lo que hacemos con la primera linea es comprobar el estado del hardware para ver si está listo o no. Con la segunda pide una página de la memoria de video, pero no espera para un repintado. Y con las dos últimas lineas, se actualiza la página actual, de igual manera que en el page flipping.

Ya que disponemos del código para cada tipo de renderizado, y no es muy complicado, lo suyo sería encapsularlo en funciones y hacer posible que el usuario seleccionase cual quiere, por si alguna le va mejor que otras en su sistema (por ejemplo, en mis pruebas, con page_flipping me va muy rápido en Linux, pero lento en Windows…)

Anuncios