Las transiciones de pantalla en Legend of Zelda usan las funciones no documentadas de NES

Para el efecto de desplazamiento vertical en la primera parte de "The Legend of Zelda", se utilizan manipulaciones gráficas de "hardware" de NES, muy probablemente no proporcionadas por los desarrolladores de la consola.


No tengo acceso a la documentación oficial de la Unidad de procesamiento de imágenes (PPU - chip gráfico) de la consola NES, por lo que mis declaraciones sobre "comportamiento indefinido" tienen más probabilidades de ser suposiciones. Tomé la especificación del hardware de gráficos de NesDev Wiki . PPU se controla escribiendo en registros con mapeo de memoria. Si usa estos registros de la forma en que fue (parece) concebida por los diseñadores, entonces sería imposible lograr este efecto:


Al desplazar la pantalla verticalmente, toda la pantalla debe desplazarse a la vez. El GIF anterior muestra un ejemplo de desplazamiento vertical parcial. Parte de la pantalla permanece estacionaria (elementos de interfaz), y la otra parte (área de juego) se desplaza verticalmente. El desplazamiento vertical parcial es imposible de implementar con el trabajo "estándar" con PPU.

Por el contrario, el desplazamiento horizontal parcial está completamente definido y es posible.


Escribir en un registro PPU separado en el momento en que se dibuja el marco puede conducir a artefactos gráficos. The Legend of Zelda intencionalmente causa un artefacto que se manifiesta como desplazamiento vertical parcial. En esta publicación, hablaré un poco sobre el hardware de gráficos NES y explicaré cómo funciona el truco de desplazamiento vertical.

Tipos de graficos


La consola NES tiene dos tipos de gráficos:

  • Los sprites son mosaicos que se pueden colocar en lugares arbitrarios en la pantalla y mover independientemente uno del otro.
  • Fondo: una cuadrícula de mosaicos que se puede desplazar suavemente como una sola imagen.

Para demostrar la diferencia entre los dos, mostraré una escena compuesta de sprites y fondo:


Y aquí está la misma escena en la que solo los sprites son visibles:


Y aquí hay una escena en la que solo se ve el fondo:


Desplazamiento


El procesador de imágenes (NES Picture Processor) admite el desplazamiento de imágenes de fondo. En la memoria de gráficos, el gráfico de fondo se almacena como una cuadrícula bidimensional de mosaicos que cubren un área dos veces el ancho y alto de la pantalla.

Se muestra una "ventana" en la pantalla en esta cuadrícula del tamaño de una pantalla, y la posición de esta ventana se puede controlar con precisión. Al mover gradualmente la ventana visible a lo largo de la cuadrícula, se crea un efecto de desplazamiento suave.

La señal de salida de video NES tiene un tamaño de 256x240 píxeles. La cuadrícula de mosaicos dentro de la memoria se representa como un área de 512x480 píxeles y se divide en cuatro rectángulos del tamaño de una pantalla llamados "tablas de nombres". Los juegos pueden configurar la Unidad de procesamiento de imágenes (PPU) indicando la posición de la ventana visible seleccionando la coordenada de píxeles en la cuadrícula de las tablas de nombres.

Cuando selecciona la coordenada (0, 0), se mostrará en la pantalla toda la tabla de nombres superior izquierda:


Pasando a (125, 181), veremos un poco de cada tabla de nombres:


La ventana visible se minimiza en la parte posterior de la cuadrícula de mosaico en la memoria. Pasando a (342, 290), colocamos la esquina superior izquierda de la pantalla visible dentro de la tabla de nombres inferior derecha, y gracias al plegado, se verán partes de cada una de las tablas de nombres:


¡No hay suficiente memoria!


Cada tabla de nombres tiene un tamaño de 1 KB, pero NES asigna solo 2 KB de su memoria de video a estas tablas, por lo que solo dos tablas de nombres pueden caber en la memoria a la vez.

¿Cómo puede tener cuatro tablas de nombres?

Duplicar tablas de nombres


La memoria de video está conectada a la PPU de tal manera que cuando la PPU representa un mosaico de una de las cuatro tablas de nombres aparentes, de hecho, se selecciona una de las dos tablas reales, y la lectura proviene de allí. En esencia, esto significa que las cuatro tablas de nombres visibles en realidad están formadas por dos pares de tablas idénticas.

Esta imagen muestra una instantánea del contenido de las cuatro tablas. La parte superior izquierda y la superior derecha son las mismas que las dos inferiores.


¿Por qué entonces simplemente no mantener dos tablas de nombres?

Afortunadamente, el enlace exacto entre las tablas aparente y real se puede configurar en tiempo de ejecución. Si el juego quiere realizar un desplazamiento horizontal, entonces ajusta el equipo gráfico para que las tablas superior izquierda y superior derecha sean diferentes, y puedan desplazarse sin duplicación notable. En esta configuración, las tablas superior izquierda e inferior izquierda se referirán a la misma tabla de nombre real; de manera similar para las dos tablas correctas. Esta configuración se llama Vertical Mirroring.


También hay otra configuración posible: "Espejo horizontal", que los juegos usan para el desplazamiento vertical.


Por lo general, los juegos no se desplazan en diagonal, ya que crea artefactos alrededor de los bordes de la pantalla debido a la duplicación de las tablas de nombres.

Cartuchos


El cartucho de cada juego tiene hardware que te permite configurar la duplicación de la mesa.


Algunos juegos no necesitan cambiar la duplicación, por lo que la duplicación horizontal o vertical está codificada en sus cartuchos. Otros juegos cambian dinámicamente entre estos dos modos, por lo que la duplicación en sus cartuchos se configura mediante programación. The Legend of Zelda pertenece a la segunda categoría. Finalmente, los cartuchos de algunos juegos verdaderamente complejos tienen memoria de video adicional, es decir, no necesitan reflejo en absoluto: pueden desplazarse simultáneamente vertical y horizontalmente sin artefactos de duplicación visibles.

Ejemplo real



Un ejemplo de desplazamiento vertical que se muestra en la pantalla.


Esto muestra un registro de tablas de nombres con reflejo horizontal. La ventana actualmente visible está resaltada.

Recuerde que el desplazamiento más vertical no es inusual; lo inusual es el desplazamiento vertical con pantalla dividida .

Pantalla dividida


Cada cuadro de la señal de video generada por NES se representa de arriba a abajo, una fila de píxeles a la vez. En cada fila, los píxeles se dibujan uno a la vez, de izquierda a derecha. A mitad de camino al renderizar el marco, el juego puede reconfigurar el PPU, lo que afecta la visualización de píxeles que aún no se han renderizado. Uno de los cambios más comunes en el medio del marco es actualizar la posición de desplazamiento horizontal.



Al desplazarse horizontalmente entre habitaciones, The Legend of Zelda siempre comienza desde la posición de desplazamiento (0, 0) y muestra los elementos de la interfaz en la parte superior de la pantalla. Después de dibujar la última línea de píxeles de la interfaz en la pantalla, el desplazamiento horizontal cambia en un valor que aumenta con cada cuadro, por lo que la cámara se mueve suavemente.

La animación de la visualización de las tablas de nombres muestra cómo el juego cambia de la duplicación horizontal a la vertical antes de desplazarse, y luego nuevamente a la horizontal después de que se completa la transición. Además, mientras continúa el desplazamiento, las tablas de nombres superior izquierda (e inferior izquierda) se actualizan, y se graba en ellas una copia de la sala en la que ingresa el jugador. Una vez que se completa el desplazamiento, el juego deja de dividir la pantalla y nuevamente se representa por completo desde la tabla superior izquierda.

Medición de renderizado


Para dividir la pantalla en la posición deseada, el juego necesita descubrir de alguna manera qué parte del cuadro actual se dibujó. Las cadenas de píxeles se procesan a una frecuencia conocida, por lo que el número de cadenas de píxeles procesadas se puede determinar contando el número de ciclos de procesador que han pasado desde el comienzo del marco.

Hay otra técnica más precisa llamada Sprite Zero Hit.

NES puede renderizar hasta 64 sprites a la vez. El primer sprite en la memoria de video se llama Sprite Zero (cero sprite). En cada cuadro, tan pronto como un píxel opaco de un sprite cero se superpone a un píxel de fondo opaco, ocurre el evento Sprite Zero Hit. Establece un bit en uno de los registros PPU con mapeo de memoria, que puede ser verificado por el procesador.

Para usar Sprite Zero Hit para dividir la pantalla, los juegos colocan el sprite cero en una posición vertical cerca del borde dividido, y durante el renderizado comprueban constantemente para ver si ha ocurrido el evento Sprite Zero Hit. Si es así, el juego cambia del desplazamiento horizontal para implementar la separación.

La transición horizontal entre habitaciones con y sin fondo se muestra a continuación.




El círculo marrón que aparece al comienzo de la transición y desaparece al final es un sprite cero. Echaremos un vistazo más de cerca a la interfaz con y sin antecedentes:



Un sprite cero es un sprite de bomba blanqueado que combina perfectamente con el sprite de bomba normal de la interfaz del juego. El sprite cero está configurado para aparecer debajo del fondo, pero dado que los píxeles negros de la interfaz se consideran transparentes, la bomba de sprite cero sería visible si no se hubiera ocultado estratégicamente detrás de la bomba desde la interfaz.

Tenga en cuenta que Sprite Zero Hit se produce unas pocas líneas de píxeles antes de la línea inferior de la interfaz. Se produce en el píxel superior del fusible de la bomba, que está a 16 píxeles desde la parte inferior de la interfaz. Cuando ocurre Sprite Zero Hit, el juego comienza a contar los ciclos del procesador, y después de completar el número requerido de ciclos establece el desplazamiento horizontal.

Haz en blanco


La mayoría de las veces, la consola PPU dibuja píxeles en la pantalla. Hay un breve tiempo de inactividad entre fotogramas durante el cual no se realiza el renderizado. Este fenómeno se llama supresión (vertical en blanco, o vblank). Algunos tipos de cambios de configuración de PPU solo se pueden realizar durante vblank.

Registro de desplazamiento


Los juegos cambian la posición de desplazamiento escribiendo en el registro PPU llamado PPUSCROLL , que se asigna a la dirección de memoria 0x2005 . La primera operación de escritura en PPUSCROLL define el componente X de la posición de desplazamiento, y la segunda operación establece el componente Y. Del mismo modo, la grabación alternativa se realiza más.

A continuación se muestran todas las operaciones de escritura distintas de cero en PPUSCROLL durante esta reproducción (en cámara lenta) 16 cuadros de la pantalla con la trama del juego. El componente de posición de desplazamiento Y se incrementa cada dos cuadros. Todas las operaciones de escritura en PPUSCROLL en este ejemplo se realizan durante vblank, lo que hace que todo el fondo se desplace junto con él.




Desplazamiento de pantalla dividida


Las operaciones de escritura en PPUSCROLL durante vblank surten efecto al comienzo del marco dibujado inmediatamente después de vblank. Si la posición de desplazamiento cambia durante la representación del marco (es decir, no durante vblank), este cambio surte efecto cuando el dibujo alcanza la siguiente fila de píxeles. El desplazamiento horizontal parcial se implementa escribiendo en PPUSCROLL mientras el PPU dibuja la última fila de píxeles antes del desplazamiento.




Al actualizar la posición de desplazamiento en el medio del cuadro, solo se aplica la posición X de la posición de desplazamiento. Es decir, el componente de posición de desplazamiento Y se descarta. Por lo tanto, si el juego quiere dividir la pantalla y cambia la posición de desplazamiento de parte del cuadro, solo puede desplazarse horizontalmente.

Y sin embargo:



Lo creas o no, el valor del registro PPUSCROLL no PPUSCROLL cambiado durante esta transición.

Puede ver un artefacto gráfico de un píxel de altura debajo de la interfaz. Este es un error de mi emulador causado por la falta de sincronización de los ciclos de reloj del procesador con la representación píxel por píxel.

Intervención en otros registros.


El segundo registro, llamado PPUADDR , asignado a la dirección de memoria 0x2006 , se usa para configurar la dirección de memoria de video actual. Cuando un juego, por ejemplo, quiere cambiar uno de los mosaicos en la tabla de nombres, primero escribe la dirección de memoria de video del PPUADDR en PPUADDR , y luego escribe el nuevo valor del PPUDATA en PPUDATA : este es el tercer registro asignado a la dirección 0x2007 .

Escribir en PPUADDR no durante vblank (es decir, cuando se procesa un marco) puede causar artefactos gráficos. Esto se debe a que la cadena PPU, que se ve afectada por la escritura en PPUADDR , también está directamente controlada por el dispositivo PPU en el proceso de obtención de mosaicos de la memoria de video para dibujarlos. Dado que el proceso de renderizado en la pantalla se realiza de arriba a abajo y de izquierda a derecha dentro de la línea, el PPU esencialmente asigna a PPUADDR valor de la dirección del PPUADDR actual que se PPUADDR dibujando. Cuando el renderizado se mueve de un mosaico a otro, PPUADDR se incrementa en el valor actual.

Por lo tanto, escribir en PPUADDR en el medio del marco puede cambiar los mosaicos recibidos por el PPU de la memoria durante la duración del marco actual.

Llevemos PPUADDR operaciones de escritura a PPUADDR durante el salto vertical. Dado que la tabla de nombres también se actualiza durante la transición, el resultado de todas las operaciones de escritura en PPUADDR será demasiado extenso. Con una transición horizontal, el desplazamiento se establece durante el procesamiento de una fila de píxeles 63, por lo tanto, consideraremos escribir operaciones en PPUADDR solo durante esta fila.




El patrón es claramente visible. Cada dos cuadros, la dirección registrada en la línea de píxeles 63 se reduce en 32 (0x20). Pero, ¿cómo conduce esto a una actualización en la posición de desplazamiento real?

Registro de desplazamiento real


Dentro de la PPU hay un registro de 15 bits no asignado a la CPU. Se utiliza tanto como la dirección actual para acceder a la memoria de video como como una configuración de desplazamiento en segundo plano.

Cuando se trabaja con este valor como una dirección, el bit 14 se ignora y los bits 0-13 se tratan como una dirección en la memoria de video.

Cuando se trabaja con este valor como una configuración de desplazamiento, sus diferentes partes tienen diferentes significados:


La selección de una tabla de nombres es un valor de 0 a 3 que determina la tabla de nombres actual a partir de la cual se realiza el dibujo.

El desplazamiento grueso en X y el desplazamiento grueso en Y determinan la coordenada del mosaico dentro de la tabla de nombres seleccionada. Este es el mosaico actual para dibujar.

El desplazamiento exacto a lo largo de Y contiene un valor de 0 a 7, que determina el desplazamiento vertical actual de la línea de píxeles dentro del mosaico actual. Los mosaicos son cuadrados con un lado de 8 píxeles.

El desplazamiento exacto en X está ausente en este registro. Hay un registro separado que contiene solo el desplazamiento horizontal del píxel actual, pero no es importante para explicar cómo se realiza el desplazamiento vertical en The Legend of Zelda.

¿Qué le sucede a este registro cuando un juego escribe en PPUADDR ? Aquí están las tres primeras operaciones de escritura de la demostración que se muestra arriba.


Al dividir las entradas en la dirección en componentes de desplazamiento, puede comprender claramente lo que está sucediendo aquí. Cada dos cuadros, el valor de Desplazamiento aproximado en Y disminuye, lo que lleva al desplazamiento vertical en un mosaico u 8 píxeles.

A lo largo de cada cuadro, el desplazamiento de desplazamiento inicial es 0.0, después de lo cual la grabación en la línea de píxeles 63 se realiza en la dirección. Esto significa que las primeras 63 líneas de píxeles se dibujan desde la parte superior de la tabla de nombres seleccionada que contiene el fondo de la interfaz. Sin embargo, la fila 64 de píxeles se renderiza aún más con el desplazamiento vertical aplicado desde esta dirección. Dado que el desplazamiento vertical disminuye cada dos cuadros, da la sensación de desplazamiento vertical de una parte de la pantalla.

Desplácese hacia abajo para desplazarse hacia arriba


The Legend of Zelda no puede ocultar este truco a los jugadores por completo. Crea un artefacto visible en las transiciones verticales de la pantalla, que son notables si se mira de cerca. Al moverse entre habitaciones, el primer cuadro de la animación de desplazamiento se desplazará hacia abajo. Aquí está la animación en cámara muy lenta.



En la tabla de nombres, puede ver lo que realmente está sucediendo. Aunque puede parecer a los jugadores que el área visible se desplazará hacia arriba sin problemas, la transición de desplazamiento comienza moviendo el área visible desde la tabla de nombres superior izquierda a la tabla inferior izquierda, que contiene una copia del fondo de la sala. Esto es necesario porque la interfaz en la parte superior de la pantalla también es parte de la tabla de nombres, y si el área visible se desplaza hacia arriba desde su posición original, pasaría por la interfaz.

El desplazamiento vertical se implementa escribiendo en el registro PPUADDR en el medio del marco. El primer valor que se escribirá es 0x2800 . Dos cuadros más tarde, 0x23A0 registra 0x23A0 , y luego el valor comienza a disminuir en 32 cada segundo cuadro.


Escribir el valor 0x2800 en el registro 0x2800 PPUADDR tabla de PPUADDR en 2, lo que representa la tabla de nombres inferior izquierda. Dado que ambos valores de desplazamiento son 0, comenzará desde el mosaico superior izquierdo de esta tabla de nombres. Sin embargo, el desplazamiento exacto en Y es 2, por lo que hay un desplazamiento vertical de dos píxeles desde la parte superior de la tabla de nombres inferior izquierda. Es por eso que en el primer fotograma de la transición, vemos una barra negra de 2 píxeles de altura en la parte inferior de la pantalla. El valor de desplazamiento inicial para la animación de transición se desplaza 2 píxeles hacia abajo para que la transición sea fluida.

Dos cuadros más tarde, el PPUADDR escribe en 0x23A0 . Esto nos lleva de vuelta a la tabla de nombres superior izquierda, y representamos desde la fila 29 de mosaicos, es decir, la parte inferior. El desplazamiento exacto en Y todavía contiene 2.

¿Por qué es necesario establecer Exact Scrolling en Y en 2? ¿Por qué el juego simplemente no escribe 0x0800 y 0x03A0 para no sufrir un desplazamiento de dos píxeles?

Cuatro tablas de nombres ocupan el área de 4 KB en el espacio de direcciones PPU, de 0x2000 a 0x2FFF . Cada mosaico en la tabla ocupa un byte de memoria de video (de hecho, son solo índices en otra tabla), y el orden de los mosaicos y las tablas de nombres en la memoria de video es tal que al seleccionar una tabla de nombres , el desplazamiento grueso por Y y el desplazamiento grueso por X conforman el desplazamiento del mosaico dentro áreas de memoria con tablas de nombres. Es decir, tomando los 12 bits inferiores del registro interno de PPU y agregándolos a 0x2000 , puede encontrar la dirección del 0x2000 en la memoria de video. ¡Y esto no es casualidad! Así es exactamente como debe manejarse el registro: tanto como un registro de dirección como como un registro de desplazamiento.

Pero hay un defecto.

Cuando se procesa como un registro de dirección, los bits 12 y 13 se consideran parte de la dirección. Durante la representación, la PPU sobrescribe constantemente el registro con la dirección del mosaico actual. Como los mosaicos se encuentran en las tablas de nombres y las tablas se encuentran en el área de memoria de 0x2000 a 0x2FFF , PPU asigna valores de este intervalo al registro.

Cuando el juego escribe en PPUADDR en el medio del cuadro, si no escribe la dirección del mosaico en la tabla de nombres, el PPU intentará leer desde otro lugar en la memoria de video. Cualquier byte que cuente se percibirá como mosaicos, lo que probablemente dará lugar a resultados no deseados. Por lo tanto, todos los valores registrados en el medio del cuadro en PPUADDR deben estar en el rango de 0x2000 a 0x2FFF . Tomando cada número en este intervalo y teniendo en cuenta sus componentes de desplazamiento, el valor de desplazamiento exacto en Y siempre debe ser igual a 2.

Esta limitación significa que no podemos cambiar el desplazamiento exacto en la dirección Y en el medio del marco, es decir, cuando usamos este truco para implementar el desplazamiento vertical de la separación de la pantalla, estamos limitados a desplazar 8 píxeles a la vez y siempre tenemos un desplazamiento vertical de dos píxeles desde el borde del mosaico. The Legend of Zelda mueve 4 píxeles por cuadro cuando se desplaza horizontalmente, pero 8 píxeles por cuadro cuando se desplaza verticalmente, y ahora sabemos por qué.

El artefacto también se nota al desplazarse entre las habitaciones hacia abajo, pero en este caso ocurre al final de la animación.



Lectura adicional



Notas


Hasta que descubrí el registro interno de PPU, mi emulador mostró el efecto de borrado durante las transiciones verticales de la pantalla de The Legend of Zelda.



El sprite de Link se movió hacia abajo de la pantalla, como debería ser, pero el fondo no se desplazó. El borrado fue causado por el hecho de que el juego actualizaba gradualmente la tabla de nombres para que contuviera los gráficos de la nueva sala, pero no actualizaba el desplazamiento para mantener las actualizaciones fuera de la pantalla.

Source: https://habr.com/ru/post/460383/


All Articles