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:
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:
Además, es importante recordar que estas tablas no están en posiciones fijas de la VRAM, sino que existen unos punteros en el VDP que pueden modificar (con ciertas restricciones) la dirección de inicio de cada una de estas tablas.

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:
Si sumamos todo, tenemos que la tabla de nombres ocupa 768 bytes y las tablas de patrones y colores ocupan 6144 bytes cada una. En total, y teniendo en cuenta también las tablas con la información sobre los sprites, en 16Kb de VRAM podemos almacenar toda la información de la pantalla en este modo.

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:
A cambio de conseguir un scroll al pixel que nos permite mostrar un contenido variado, tenemos un par de restricciones (scroll estrecho y lento) que son consecuencias lógicas de la técnica utilizada.

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-891011121314151617181920212223-31
...0008101820283038404850586068...
...0109111921293139414951596169...
...020A121A222A323A424A525A626A...
...030B131B232B333B434B535B636B...
...040C141C242C343C444C545C646C...
...050D151D252D353D454D555D656D...
...060E161E262E363E464E565E666E...
...070F171F272F373F474F575F676F...
Tabla 1
0-891011121314151617181920212223-31
...707880889098A0A8B0B8C0C8D0D8...
...717911899199A1A9B1B9C1C9D1D9...
...727A828A929AA2AAB2BAC2CAD2DA...
...737B838B939BA3ABB3BBC3CBD3DB...
...047C848C949CA4ACB4BCC4CCD4DC...
...757D858D959DA5ADB5BDC5CDD5DD...
...767E868E969EA6AEB6BEC6CED6DE...
...777F878F979FA7AFB7BFC7CFD7DF...
¿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:
De esta manera se van volcando y desplazando los bytes de las tablas de patrones y colores (ya que el proceso anterior hay que realizarlo para ambas tablas) directamente, aprovechando las pausas necesarias entre out y out para realizar el desplazamiento de los bytes. Con este método, nos da tiempo de sobra a volcar los bytes de patrones y colores de dos columnas por frame y ejecutar el replayer de música para que suene al mismo tiempo que se ve el scroll de los créditos.

¡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.