Viernes, 19 de enero de 2024
Todo comenzó en la LX RU de Barcelona del 1 de julio de 2023... allí estuve hablando con Juanmi Ortuño, editor de la revista MSX Area con motivo de la publicación por sorpresa del número 10. Me dijo que no descartaba que en un futuro pudiera salir un nuevo número, pero con tranquilidad. Entonces le dije que podría hacer algún artículo comentando... quizás... cómo funcionan los scrolls del QBIQS, pero me dijo que mejor lo contase por otro lado ya que no garantizaba cuando iba a salir un nuevo número, si es que salía.
Por eso, cuando The Nestruo me preguntó si quería dar una charla en la pasada RUN QuesadaSX de noviembre de 2023, le dije que sí y que ya tenía una idea: contar cómo están programados los scrolls del QBIQS. Esa charla la di el sábado 4 de noviembre y se grabó... pero por problemas técnicos el audio de un par de charlas (incluída la mía) se grabó mal y apenas se entiende. Por ese motivo no se ha publicado el vídeo de la charla y, para que no se pierda el contenido, aquí vengo con unos cuantos posts en los que voy a explicar (quizá un poco más en detalle de como lo hice en la charla) cómo funcionan los scrolls del QBIQS.
Y digo bien: scrolls, en plural. Porque el juego tiene tres rutinas de scroll, con tres técnicas totalmente diferentes que se usan en distintos momentos del juego. Así pues, la idea es hacer tres posts contando en cada uno de ellos una de las rutinas de scroll, que son las siguientes:
Antes de entrar en materia, vamos a recordar brevemente cómo es la estructura de Screen 2 (y de Screen 4, que es el mismo modo salvo por los sprites). Screen 2 es un modo de 32x24 tiles que se divide en tres zonas de pantalla de 32x8 tiles cada una. En cada una de ellas tenemos tres tablas importantes:
En cada zona podemos definir 256 tiles utilizando 8 bytes de la tabla de patrones y otros 8 bytes de la de colores. Cada línea horizontal de 8 pixels del tile se define como sigue:
La idea original del scroll es de Jon Cortázar, que la pensó para el Rapid Burst, un juego de naves de scroll vertical que tenía en proyecto. Para quienes ya conozcan a Jon, habrán deducido que el scroll no utiliza ninguna característica de MSX2 o superior, por lo cual funciona también en cualquier otro sistema que tenga el mismo VDP que el MSX1, como Colecovision o NABU. Aún sin usar el registro de scroll de los MSX2, es capaz de mostrar un scroll real al pixel.
Por si alguien no hubiera visto el scroll de los créditos del juego, aquí os dejo un vídeo del mismo (aunque sin sonido):
Si nos fijamos, podemos extraer tres características reseñables del scroll, una buena y dos malas, que serían las siguientes:
Bueno, he de confesar que el scroll tiene una restricción adicional que quizá haya pasado desapercibida: es un scroll de tiles extendido. Aunque la técnica permitiría hacer scroll al pixel de cualquier contenido, por motivos de memoria el scroll se compone mediante una tabla de 256 tiles diferentes. Es decir, que los datos del scroll consisten en una matriz de filas que tienen 14 tiles de ancho, más, lógicamente, las tablas de patrones y colores que definen los tiles a visualizar.
¡Vamos al meollo! La clave del scroll consiste en utilizar dos tablas de nombres diferentes en VRAM a las que denominaremos tabla 0 y tabla 1 y que nos caben sin problemas en los 16Kb de VRAM que ocupa la pantalla en Screen 2. De esta manera podemos tener un doble buffer que nos será muy útil. El contenido de estas tablas (en hexadecimal) sería el mismo para las tres zonas de la pantalla:
¿Qué significan esos puntos suspensivos en las tablas? Básicamente que en las columnas de la 0 a la 8 y de la 23 a la 31 se puede poner cualquier tile que no esté utilizado en las 14 columnas centrales, por ejemplo el valor FF. Si definimos ese tile como vacío, ya tenemos los bordes laterales del scroll. La chicha realmente está en las 14 columnas centrales, que utilizan 112 tiles (14x8) cada una, pero (y aquí está el truco) dispuestos en vertical. Esto hace que los bytes de las tablas de patrones (y también los de la tabla de colores) de cada zona para una columna de tiles estén situados de forma consecutiva en VRAM.
¿Y cómo se construyen las tablas de patrones y colores? Pues estas tablas se construyen (y modifican) en RAM y se vuelcan a VRAM. Si tenemos un total de 112 tiles por tres zonas y por 16 bytes por tile (8 de patrones más 8 de colores), hacen un total de 5376 bytes a volcar para actualizar la pantalla al completo. Es decir, que no da tiempo a volcar toda esa cantidad en un único frame, tocar la música, modificar las tablas, etc. Este es el motivo por el cual el scroll va lento y avanza un pixel cada 8 frames.
En los siete primeros frames el proceso es idéntico: volcar y modificar dos columnas cada vez. Pero, ¿eso no haría que se viera cómo avanzan las columnas en distintos frames? Pues no. Gracias al doble buffer, lo que hacemos es que si estamos visualizando la tabla de nombres 0, entonces las tablas de patrones y colores se vuelcan a los tiles del 70 al DF, que son los que están en la tabla de nombres 1. Por el contrario, si estamos visualizando la tabla 1, volcaremos a los tiles del 00 al 6F, que son los que están en la tabla de nombres 0. Es decir, todos los volcados a VRAM realizados en estos siete frames no son visibles en ningún momento. Es en el octavo frame cuando, al cambiar el puntero a la tabla de nombres, visualizaremos el avance del scroll en un pixel en toda la pantalla de forma simultánea.
Esto explica por qué el scroll no ocupa todo el ancho de la pantalla, ya que necesitamos ese doble buffer para evitar que se vea cómo se actualizan las columnas en frames diferentes. Además, como necesitamos, al menos, un tile libre para utilizarlo como fondo para los laterales, no podemos ampliar el scroll a 16 columnas... Bueno, sí. Se podría hacer si utilizamos sprites para enmascarar las columnas de la 0 a la 7 y de la 24 a la 31. Para ello haría falta utilizar sprites de 16x16 ampliados, lo que nos permitiría tapar esas columnas de tiles con 6 filas de 4 sprites (con lo que no nos afecta la regla del 5º sprite). ¿Por qué no lo hice? Pues porque el scroll de la nave roja (el mismo que se ve en el vídeo de ejemplo) necesita algún sprite para mostrar correctamente la imagen de la nave debido al attribute clash (eso de los 2 colores por cada línea horizontal de un tile).
Pero aún así, el proceso implica volcar 768 bytes de RAM a VRAM y modificar esos 768 bytes en RAM para que podamos volcarlos 8 frames después y durante el retrazado vertical no da tiempo a volcar esa cantidad de bytes. Como ya nos tenemos que salir del retrazado hay que introducir una "pausa" entre out y out... pero nada nos dice que esa pausa tenga que ser uno o varios NOP, ya que podemos perfectamente utilizar código que haga algo más, que es precisamente lo que hace el juego:
¡Y ya está! No hay más trucos. A grandes rasgos es así como funciona el scroll de los créditos del QBIQS. En el siguiente post contaré cómo funciona el scroll de las piezas, que tiene algo más de chicha que este.
Por eso, cuando The Nestruo me preguntó si quería dar una charla en la pasada RUN QuesadaSX de noviembre de 2023, le dije que sí y que ya tenía una idea: contar cómo están programados los scrolls del QBIQS. Esa charla la di el sábado 4 de noviembre y se grabó... pero por problemas técnicos el audio de un par de charlas (incluída la mía) se grabó mal y apenas se entiende. Por ese motivo no se ha publicado el vídeo de la charla y, para que no se pierda el contenido, aquí vengo con unos cuantos posts en los que voy a explicar (quizá un poco más en detalle de como lo hice en la charla) cómo funcionan los scrolls del QBIQS.
Y digo bien: scrolls, en plural. Porque el juego tiene tres rutinas de scroll, con tres técnicas totalmente diferentes que se usan en distintos momentos del juego. Así pues, la idea es hacer tres posts contando en cada uno de ellos una de las rutinas de scroll, que son las siguientes:
- Scroll de los créditos.
- Scroll de las piezas.
- Scroll de los laterales.
Estructura de Screen 2
Antes de entrar en materia, vamos a recordar brevemente cómo es la estructura de Screen 2 (y de Screen 4, que es el mismo modo salvo por los sprites). Screen 2 es un modo de 32x24 tiles que se divide en tres zonas de pantalla de 32x8 tiles cada una. En cada una de ellas tenemos tres tablas importantes:
- Tabla de nombres: 256 bytes que permiten indicar qué tile (de 0 a 255) se va a visualizar en cada una de las 256 posiciones disponibles (32x8=256).
- Tabla de patrones: 2048 bytes que permiten redefinir el aspecto de los 256 tiles (8 bytes por tile) de la zona.
- Tabla de colores: 2048 bytes que permiten redefinir el color de los 256 tiles (8 bytes por tile) de la zona.
En cada zona podemos definir 256 tiles utilizando 8 bytes de la tabla de patrones y otros 8 bytes de la de colores. Cada línea horizontal de 8 pixels del tile se define como sigue:
- El byte de la tabla de patrones define los pixels del fondo (aquellos que están a 0 en el byte) y los pixels del frente (los que están a 1 en el byte)
- El byte de la tabla de colores define el color de los pixels de fondo (de 0 a 15) y el de los pixels de frente (de 0 a 15)
El scroll de los créditos
La idea original del scroll es de Jon Cortázar, que la pensó para el Rapid Burst, un juego de naves de scroll vertical que tenía en proyecto. Para quienes ya conozcan a Jon, habrán deducido que el scroll no utiliza ninguna característica de MSX2 o superior, por lo cual funciona también en cualquier otro sistema que tenga el mismo VDP que el MSX1, como Colecovision o NABU. Aún sin usar el registro de scroll de los MSX2, es capaz de mostrar un scroll real al pixel.
Por si alguien no hubiera visto el scroll de los créditos del juego, aquí os dejo un vídeo del mismo (aunque sin sonido):
Si nos fijamos, podemos extraer tres características reseñables del scroll, una buena y dos malas, que serían las siguientes:
- Es un scroll al pixel y permite mostrar variedad de contenido.
- Es estrecho, ocupa únicamente 14 tiles de ancho.
- Es lento, avanza un pixel cada 8 vblanks.
Bueno, he de confesar que el scroll tiene una restricción adicional que quizá haya pasado desapercibida: es un scroll de tiles extendido. Aunque la técnica permitiría hacer scroll al pixel de cualquier contenido, por motivos de memoria el scroll se compone mediante una tabla de 256 tiles diferentes. Es decir, que los datos del scroll consisten en una matriz de filas que tienen 14 tiles de ancho, más, lógicamente, las tablas de patrones y colores que definen los tiles a visualizar.
¡Vamos al meollo! La clave del scroll consiste en utilizar dos tablas de nombres diferentes en VRAM a las que denominaremos tabla 0 y tabla 1 y que nos caben sin problemas en los 16Kb de VRAM que ocupa la pantalla en Screen 2. De esta manera podemos tener un doble buffer que nos será muy útil. El contenido de estas tablas (en hexadecimal) sería el mismo para las tres zonas de la pantalla:
| Tabla 0 | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0-8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23-31 |
| ... | 00 | 08 | 10 | 18 | 20 | 28 | 30 | 38 | 40 | 48 | 50 | 58 | 60 | 68 | ... |
| ... | 01 | 09 | 11 | 19 | 21 | 29 | 31 | 39 | 41 | 49 | 51 | 59 | 61 | 69 | ... |
| ... | 02 | 0A | 12 | 1A | 22 | 2A | 32 | 3A | 42 | 4A | 52 | 5A | 62 | 6A | ... |
| ... | 03 | 0B | 13 | 1B | 23 | 2B | 33 | 3B | 43 | 4B | 53 | 5B | 63 | 6B | ... |
| ... | 04 | 0C | 14 | 1C | 24 | 2C | 34 | 3C | 44 | 4C | 54 | 5C | 64 | 6C | ... |
| ... | 05 | 0D | 15 | 1D | 25 | 2D | 35 | 3D | 45 | 4D | 55 | 5D | 65 | 6D | ... |
| ... | 06 | 0E | 16 | 1E | 26 | 2E | 36 | 3E | 46 | 4E | 56 | 5E | 66 | 6E | ... |
| ... | 07 | 0F | 17 | 1F | 27 | 2F | 37 | 3F | 47 | 4F | 57 | 5F | 67 | 6F | ... |
| Tabla 1 | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0-8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23-31 |
| ... | 70 | 78 | 80 | 88 | 90 | 98 | A0 | A8 | B0 | B8 | C0 | C8 | D0 | D8 | ... |
| ... | 71 | 79 | 11 | 89 | 91 | 99 | A1 | A9 | B1 | B9 | C1 | C9 | D1 | D9 | ... |
| ... | 72 | 7A | 82 | 8A | 92 | 9A | A2 | AA | B2 | BA | C2 | CA | D2 | DA | ... |
| ... | 73 | 7B | 83 | 8B | 93 | 9B | A3 | AB | B3 | BB | C3 | CB | D3 | DB | ... |
| ... | 04 | 7C | 84 | 8C | 94 | 9C | A4 | AC | B4 | BC | C4 | CC | D4 | DC | ... |
| ... | 75 | 7D | 85 | 8D | 95 | 9D | A5 | AD | B5 | BD | C5 | CD | D5 | DD | ... |
| ... | 76 | 7E | 86 | 8E | 96 | 9E | A6 | AE | B6 | BE | C6 | CE | D6 | DE | ... |
| ... | 77 | 7F | 87 | 8F | 97 | 9F | A7 | AF | B7 | BF | C7 | CF | D7 | DF | ... |
¿Y cómo se construyen las tablas de patrones y colores? Pues estas tablas se construyen (y modifican) en RAM y se vuelcan a VRAM. Si tenemos un total de 112 tiles por tres zonas y por 16 bytes por tile (8 de patrones más 8 de colores), hacen un total de 5376 bytes a volcar para actualizar la pantalla al completo. Es decir, que no da tiempo a volcar toda esa cantidad en un único frame, tocar la música, modificar las tablas, etc. Este es el motivo por el cual el scroll va lento y avanza un pixel cada 8 frames.
En los siete primeros frames el proceso es idéntico: volcar y modificar dos columnas cada vez. Pero, ¿eso no haría que se viera cómo avanzan las columnas en distintos frames? Pues no. Gracias al doble buffer, lo que hacemos es que si estamos visualizando la tabla de nombres 0, entonces las tablas de patrones y colores se vuelcan a los tiles del 70 al DF, que son los que están en la tabla de nombres 1. Por el contrario, si estamos visualizando la tabla 1, volcaremos a los tiles del 00 al 6F, que son los que están en la tabla de nombres 0. Es decir, todos los volcados a VRAM realizados en estos siete frames no son visibles en ningún momento. Es en el octavo frame cuando, al cambiar el puntero a la tabla de nombres, visualizaremos el avance del scroll en un pixel en toda la pantalla de forma simultánea.
Esto explica por qué el scroll no ocupa todo el ancho de la pantalla, ya que necesitamos ese doble buffer para evitar que se vea cómo se actualizan las columnas en frames diferentes. Además, como necesitamos, al menos, un tile libre para utilizarlo como fondo para los laterales, no podemos ampliar el scroll a 16 columnas... Bueno, sí. Se podría hacer si utilizamos sprites para enmascarar las columnas de la 0 a la 7 y de la 24 a la 31. Para ello haría falta utilizar sprites de 16x16 ampliados, lo que nos permitiría tapar esas columnas de tiles con 6 filas de 4 sprites (con lo que no nos afecta la regla del 5º sprite). ¿Por qué no lo hice? Pues porque el scroll de la nave roja (el mismo que se ve en el vídeo de ejemplo) necesita algún sprite para mostrar correctamente la imagen de la nave debido al attribute clash (eso de los 2 colores por cada línea horizontal de un tile).
Pero aún así, el proceso implica volcar 768 bytes de RAM a VRAM y modificar esos 768 bytes en RAM para que podamos volcarlos 8 frames después y durante el retrazado vertical no da tiempo a volcar esa cantidad de bytes. Como ya nos tenemos que salir del retrazado hay que introducir una "pausa" entre out y out... pero nada nos dice que esa pausa tenga que ser uno o varios NOP, ya que podemos perfectamente utilizar código que haga algo más, que es precisamente lo que hace el juego:
- Apuntamos con HL al byte a transferir y con DE al byte anterior.
- Transferimos el byte a VRAM asegurándonos de que HL no se modifica.
- Ejecutamos la instrucción LDI, con lo que hemos desplazado el byte transferido a una posición anterior (o sea, hacia arriba) y encima incrementamos tanto HL como DE.
- Cuando terminamos de volcar una zona de una columna (64 bytes), actualizamos los punteros, tanto a RAM como a VRAM, al inicio de la siguiente zona de esa misma columna o al inicio de la primera zona de la siguiente columna.
- Cada vez que se completa el volcado, en el octavo frame se añaden a la zona de trabajo del scroll los siguientes bytes (de patrones y colores) que pasarán a ser visibles en el siguiente volcado.
- Cuando se avanzan 8 pixels, se actualiza el puntero de la matriz con los datos de scroll apuntando a la siguiente fila de 14 tiles.
¡Y ya está! No hay más trucos. A grandes rasgos es así como funciona el scroll de los créditos del QBIQS. En el siguiente post contaré cómo funciona el scroll de las piezas, que tiene algo más de chicha que este.