Tutorial de ZX Basic + Fourspriter #22: Parpadeando

¡Houston, tenemos un problema! Si os habéis fijado, cuando hay más de dos sprites activos aparece un cierto parpadeo en la parte superior de la pantalla que queda super mal. Joder, vaya basuraca ¿no? Entonces te preguntarás ¿para qué carajo estoy usando fourspriter si luego se ve mal y parpadea?

Ay, amigo, cuánta razón. Pero no te preocupes. Primero vamos a explicar por qué ocurre el parpadeo. Y no, no es para camelarte y que te olvides de él. Es para culturizarte, querido lector.

Veamos. Para empezar hablemos de rasters. La imagen que ves en tu pantalla no se forma por arte de magia, no. Lo sentimos, pero el famoso cuento del Hada Video es mentira, y los electroduendes son los padres. En el Spectrum hay un chip llamado ULA que hace un montón de cosas. Una de las cosas que hace, y que, además, es tela de importante, consiste en crear la imagen que ves en la pantalla. Para ello va enviando señales de video que construye ella misma. Las señales de video consisten en una onda analógica que luego la TV interpreta y utiliza para modular un haz de electrones que impacta contra la pantalla, iluminando ciertos puntos. O al menos era así antes, con las pantallas de tubo. Lo importante de esto es que esa señal lineal ha de convertirse en un rectángulo. Y para ello, lo que se hace es empezar por la esquina superior izquierda, e ir rellenando hacia la derecha hasta que llegamos al borde, tras lo cual bajamos una linea y volvemos a empezar a rellenar de izquierda a derecha, y así hasta que hayamos completado toda la imagen. Esto no es así al 100% (antes de que me salte el típico friki premio novel en televisores rajando en los comentarios), pero bueno, a efectos prácticos nos vale.

Por tanto, la ULA lo que hace es lo siguiente: primero lanza una interrupción, y acto seguido empieza a generar el borde superior de la imagen. Cuando digo borde me refiero a lo que se pinta de un color cuando haces un BORDER en BASIC. Cuando termina el borde superior, empieza con la imagen que hay en la memoria gráfica del Spectrum: pinta un cacho de borde (el de la izquierda), lee la memoria de video (los 32 de los 6912 bytes que hay a partir de 16384 en la RAM del Spectrum) mientras dibuja los colores correspondientes, pinta otro cacho de borde (el de la derecha), y vuelta a empezar. Así hasta que se acaba la imagen de la memoria de video, tras lo cual pinta el borde de abajo. Cuando acaba, vuelve a empezar por el principio.

La operación halt en ensamblador lo que hace es esperarse a que haya una interrupción enmascarable. En un Spectrum esto solo ocurre cuando la ULA va a empezar a dibujar la pantalla. Por eso se suele usar para sincronizarse con la pantalla.

Fourspriter no usa ningún buffer. Esto significa que hace todo directamente sobre la pantalla. Lo que hace es restaurar el fondo del sprite en su ubicación anterior, almacenar el fondo del sprite en su actual ubicación, y pintar el sprite encima. Una y otra vez. La rutina de actualización a la que llamamos en cada frame del juego, fsp21updateSprites, llama al siguiente código en ensamblador (archivo fsp2.1.bas, linea 524 y siguientes):

        upd_sprites:
                    halt
                    
                    call    borra_sprites
                    call    init_sprites
                    call    pinta_sprites
                    call    upd_coord
                    
                    ret

¿Veis el halt del principio? Eso significa que la rutina empieza a ejecutarse justo cuando la ULA empieza a dibujar la imagen. De hecho, empieza a ejecutarse mientras la ULA dibuja el borde superior.

El parpadeo es debido a que, si incrementamos el número de sprites activos, la rutina tarda más en terminar su tarea, y la ULA ha terminado con el borde superior y ha llegado ya a la pantalla propiamente dicha antes de que hayamos terminado de actualizar los sprites. Si los sprites implicados que aún no han terminado de actualizarse se encuentran en la parte superior y se cruzan con la ULA actualizando la pantalla, pues veremos como se borran y se vuelven a imprimir: los vemos parpadear.

Básicamente, fourspriter no tiene tiempo de hacerlo todo mientras la ULA pinta el borde, y se le ven las bragas. ¡Mierda!

Afortunadamente, esto tiene una solución muy sencilla (aunque en realidad se me ocurrió hace poquísimo, tonto de mí, porque además luego me entero de que hay otra gente que lleva meses usando este “truco”). Si lo piensas, en total estamos haciendo tres halts en nuestro juego: uno en fourspriter para sincronizarse con la ULA (el que hemos visto más arriba), y dos más en el bucle del juego para perder tiempo y que no vaya todo tan rápido. La idea es la siguiente: eliminar uno de los halts, y sustituirlo por un bucle dentro de la rutina en ensamblador upd_sprites que simplemente pierda el tiempo, que no haga nada hasta que la ULA toque el borde inferior. Entonces empezaríamos a ejecutar las rutinas de actualización. ¿Os dais cuenta? de esa forma, tendremos mucho más tiempo para hacer cosas en la pantalla sin que se vean: el tiempo que la ULA esté dibujando el borde inferior, más el que esté dibujando el borde superior.

Huelga decir que esto se me ocurrió cagando, como todas las grandes ideas de la historia de la humanidad.

Se me ocurrió pero no sabía cuánto esperar. Tras un par de sugerencias que me hicieron en el foro de WOS me dispuse a medirlo de forma empírica. Simplemente haces halt, pones el borde de un color (azul, que es muy bonito), metes un bucle, y lo vuelves a poner a negro. Así, poco a poco, vas ajustando la duración del bucle hasta que el color del borde cambie a negro justo al llegar al borde inferior. Luego quitas los cambios de color del borde, y te quedas con la duración del bucle.

La rutina upd_sprites, con el bucle con la duración correcta, queda así. Así que no tardes en abrir fsp2.1.bas y cambiar ese trozo de código… O descárgate el paquetico de este capítulo pulsando aquí mismo.

        upd_sprites:
                    halt
                    
                    ld bc, 1900
        loop_waste: dec bc
                    ld a, b
                    cp 0
                    jp nz, loop_waste
                    ld a, c
                    cp 0
                    jp nz, loop_waste
                    
                    call    borra_sprites
                    call    init_sprites
                    call    pinta_sprites
                    call    upd_coord
                    
                    ret

Recuerda que tendremos que irnos al bucle principal en feoncio.bas y eliminar un halt de los dos que aparencen al principio, si no el juego irá más lento.

Y seguramente os preguntaréis por qué no hago yo el cambio y distribuyo fourspriter con el cambio ya hecho… Buena pregunta. Es muy sencillo: este cambio nos viene bien ahora a nosotros en este caso, pero es posible que alguien quiera ocupar el tiempo que nosotros simplemente estamos esperando a que la ULA llegue al borde de abajo haciendo otras cosas… ¿Quién sabe? En ese tiempo podríamos tocar un poco de música, por ejemplo.

Aún tenemos un par de modificaciones más que hacer a fourspriter, pero antes vamos a interactuar un poco con esos enemigos ¿no? ¡en el próximo capítulo!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: