Les transitions d'écran dans Legend of Zelda utilisent les fonctionnalités non documentées de NES

Pour l'effet du défilement vertical dans la première partie de "The Legend of Zelda", des manipulations graphiques "matérielles" NES sont utilisées, probablement pas fournies par les développeurs de la console.


Je n'ai pas accès à la documentation officielle de l'unité de traitement d'image (PPU - puce graphique) de la console NES, donc mes déclarations sur le "comportement indéfini" sont plus susceptibles d'être des suppositions. J'ai pris les spécifications du matériel graphique du wiki NesDev . PPU est contrôlé en écrivant dans des registres avec mappage de mémoire. Si vous utilisez ces registres de la manière qui (semble-t-il) a été conçue par les concepteurs, il serait impossible d'obtenir cet effet:


Lorsque vous faites défiler l'écran verticalement, tout l'écran doit défiler en même temps. Le GIF précédent montre un exemple de défilement vertical partiel. Une partie de l'écran reste immobile (éléments d'interface) et l'autre partie (zone de jeu) défile verticalement. Le défilement vertical partiel est impossible à implémenter avec le travail "standard" avec PPU.

En revanche, le défilement horizontal partiel est entièrement défini et possible.


L'écriture dans un registre PPU distinct au moment où l'image est tracée peut conduire à des artefacts graphiques. La légende de Zelda provoque intentionnellement un artefact qui se manifeste par un défilement vertical partiel. Dans cet article, je vais parler un peu du matériel graphique NES et expliquer comment fonctionne l'astuce de défilement vertical.

Types de graphiques


La console NES possède deux types de graphiques:

  • Les sprites sont des tuiles qui peuvent être placées à des endroits arbitraires sur l'écran et déplacées indépendamment les unes des autres.
  • Arrière-plan - une grille de tuiles qui peut être défilée en douceur comme une seule image.

Pour démontrer la différence entre les deux, je vais montrer une scène composée de sprites et de fond:


Et voici la même scène dans laquelle seuls les sprites sont visibles:


Et voici une scène dans laquelle seul l'arrière-plan est visible:


Défilement


Le processeur d'image (NES Picture Processor) prend en charge le défilement des images d'arrière-plan. Dans la mémoire graphique, le graphique d'arrière-plan est stocké sous la forme d'une grille bidimensionnelle de tuiles couvrant une zone deux fois la largeur et la hauteur de l'écran.

Une «fenêtre» est affichée à l'écran dans cette grille de la taille d'un écran, et la position de cette fenêtre peut être contrôlée avec précision. En déplaçant progressivement la fenêtre visible le long de la grille, un effet de défilement fluide est créé.

Le signal vidéo de sortie NES a une taille de 256x240 pixels. La grille de tuiles à l'intérieur de la mémoire est représentée comme une zone de 512x480 pixels et est divisée en quatre rectangles de taille d'écran appelés «tables de noms». Les jeux peuvent configurer l'unité de traitement d'image (PPU) en indiquant la position de la fenêtre visible en sélectionnant les coordonnées des pixels dans la grille des tables de noms.

Lorsque vous sélectionnez les coordonnées (0, 0), la totalité du tableau de noms supérieur gauche s'affiche à l'écran:


Passant à (125, 181), nous verrons un peu de chaque table de noms:


La fenêtre visible se réduit à l'arrière de la grille de tuiles en mémoire. Passant à (342, 290), nous plaçons le coin supérieur gauche de l'écran visible à l'intérieur du tableau des noms en bas à droite, et grâce au pliage, des parties de chacun des tableaux des noms seront visibles:


Pas assez de mémoire!


Chaque table de noms a une taille de 1 Ko, mais NES alloue seulement 2 Ko de sa mémoire vidéo à ces tables, de sorte que seules deux tables de noms peuvent tenir en mémoire à la fois.

Comment peut-il avoir quatre tables de noms?

Mise en miroir des tables de noms


La mémoire vidéo est connectée au PPU de telle manière que lorsque le PPU restitue une tuile de l'une des quatre tables de noms apparentes, en fait l'une des deux vraies tables est sélectionnée, et la lecture vient de là. En substance, cela signifie que les quatre tables de noms visibles sont en fait constituées de deux paires de tables identiques.

Cette image montre un instantané du contenu des quatre tableaux. Le haut à gauche et le haut à droite sont les mêmes que les deux inférieurs.


Pourquoi alors ne pas garder deux tables de noms?

Heureusement, la liaison exacte entre les tables apparente et réelle peut être configurée au moment de l'exécution. Si le jeu souhaite effectuer un défilement horizontal, il ajuste l'équipement graphique de sorte que les tableaux supérieur gauche et supérieur droit soient différents, et ils peuvent être défilés sans duplication notable. Dans cette configuration, les tableaux supérieur gauche et inférieur gauche feront référence au même tableau de nom réel; de même pour les deux tableaux de droite. Cette configuration est appelée mise en miroir verticale.


Il existe également une autre configuration possible - «Horizontal Mirroring», que les jeux utilisent pour le défilement vertical.


En règle générale, les jeux ne défilent pas en diagonale, car cela crée des artefacts autour des bords de l'écran en raison de la mise en miroir des tables de noms.

Cartouches


La cartouche de chaque jeu possède un matériel qui vous permet de configurer la mise en miroir des tables.


Certains jeux n'ont pas du tout besoin de basculer la mise en miroir, de sorte que la mise en miroir horizontale ou verticale est codée en dur dans leurs cartouches. D'autres jeux basculent dynamiquement entre ces deux modes, donc la mise en miroir dans leurs cartouches est configurée par programme. La légende de Zelda appartient à la deuxième catégorie. Enfin, les cartouches de certains jeux vraiment complexes ont une mémoire vidéo supplémentaire, c'est-à-dire qu'elles n'ont pas du tout besoin de mise en miroir: elles peuvent simultanément faire défiler verticalement et horizontalement sans artefacts de duplication visibles.

Exemple réel



Un exemple de défilement vertical qui s'affiche à l'écran.


Cela montre un enregistrement des tables de noms avec mise en miroir horizontale. La fenêtre actuellement visible est mise en surbrillance.

N'oubliez pas que le défilement le plus vertical n'est pas inhabituel - ce qui est inhabituel est le défilement vertical avec écran partagé .

Écran partagé


Chaque image du signal vidéo généré par NES est rendue de haut en bas, une rangée de pixels à la fois. Dans chaque ligne, les pixels sont dessinés un à la fois, de gauche à droite. À mi-chemin lors du rendu de l'image, le jeu peut reconfigurer le PPU, ce qui affecte l'affichage des pixels qui n'ont pas encore été rendus. L'un des changements les plus courants au milieu du cadre est la mise à jour de la position de défilement horizontal.



Lors du défilement horizontal entre les pièces, The Legend of Zelda commence toujours à partir de la position de défilement (0, 0) et affiche les éléments d'interface en haut de l'écran. Après avoir tracé la dernière ligne de pixels d'interface sur l'écran, le défilement horizontal change d'une valeur qui augmente avec chaque image, de sorte que la caméra se déplace en douceur.

L'animation de l'affichage des tables de noms montre comment le jeu passe de la mise en miroir horizontale à la mise en miroir verticale avant le défilement, puis à nouveau à l'horizontale une fois la transition terminée. De plus, pendant que le défilement se poursuit, les tables de noms en haut à gauche (et en bas à gauche) sont mises à jour et une copie de la pièce dans laquelle le joueur entre est enregistrée. Une fois le défilement terminé, le jeu cesse de diviser l'écran et est à nouveau entièrement rendu à partir du tableau supérieur gauche.

Mesure de rendu


Pour diviser l'écran dans la position souhaitée, le jeu doit en quelque sorte savoir quelle partie de l'image actuelle a été dessinée. Les chaînes de pixels sont rendues à une fréquence connue, de sorte que le nombre de chaînes de pixels rendues peut être déterminé en comptant le nombre de cycles de processeur qui se sont écoulés depuis le début de la trame.

Il existe une autre technique plus précise appelée Sprite Zero Hit.

NES peut rendre jusqu'à 64 images-objets à la fois. Le premier sprite dans la mémoire vidéo est appelé Sprite Zero (zéro sprite). Dans chaque image, dès qu'un pixel opaque d'un sprite zéro est superposé à un pixel d'arrière-plan opaque, l'événement Sprite Zero Hit se produit. Il définit un bit dans l'un des registres PPU avec un mappage de mémoire, qui peut être vérifié par le processeur.

Pour utiliser Sprite Zero Hit pour diviser l'écran, les jeux placent le sprite zéro en position verticale près de la bordure divisée, et pendant le rendu, ils vérifient constamment si l'événement Sprite Zero Hit s'est produit. Si c'est le cas, le jeu passe du défilement horizontal pour implémenter la séparation.

La transition horizontale entre les pièces avec et sans fond est illustrée ci-dessous.




Le cercle brun qui apparaît au début de la transition et disparaît à sa fin est un sprite zéro. Nous allons examiner de plus près l'interface avec et sans arrière-plan:



Un sprite zéro est un sprite de bombe blanchi qui correspond parfaitement au sprite de bombe ordinaire de l'interface de jeu. Le sprite zéro est configuré pour apparaître sous l'arrière-plan, mais comme les pixels noirs de l'interface sont considérés comme transparents, la bombe zéro sprite serait visible si elle n'avait pas été stratégiquement cachée derrière la bombe de l'interface.

Notez que Sprite Zero Hit apparaît quelques lignes de pixels avant la ligne inférieure de l'interface. Il se produit au pixel supérieur du fusible de la bombe, à 16 pixels du bas de l'interface. Lorsque Sprite Zero Hit se produit, le jeu commence à compter les cycles du processeur et, après avoir terminé le nombre de cycles requis, définit le défilement horizontal.

Suppression de faisceau


La plupart du temps, la console PPU dessine des pixels à l'écran. Il y a un court temps d'arrêt entre les images pendant lequel le rendu n'est pas effectué. Ce phénomène est appelé blanking (Vertical Blank ou vblank). Certains types de changements de configuration PPU ne peuvent être effectués que pendant vblank.

Registre défilant


Les jeux changent la position du défilement en écrivant dans le registre PPU appelé PPUSCROLL , qui correspond à l'adresse mémoire 0x2005 . La première opération d'écriture dans PPUSCROLL définit le composant X de la position de défilement et la deuxième opération définit le composant Y. De même, un enregistrement alternatif est effectué plus loin.

Ce qui suit montre toutes les opérations d'écriture non nulles dans PPUSCROLL pendant cette lecture (au ralenti) 16 images de l'écran avec l'intrigue du jeu. La composante de position de défilement Y est incrémentée toutes les deux images. Toutes les opérations d'écriture dans PPUSCROLL dans cet exemple sont effectuées pendant vblank, ce qui entraîne le défilement de l'arrière-plan entier.




Scrolling Screen Split


Les opérations d' PPUSCROLL dans PPUSCROLL pendant vblank prennent effet au début de la trame dessinée immédiatement après vblank. Si la position de défilement change pendant le rendu du cadre (c'est-à-dire pas pendant vblank), alors ce changement prend effet lorsque le dessin atteint la rangée de pixels suivante. Le défilement horizontal partiel est implémenté en écrivant dans PPUSCROLL pendant que le PPU dessine la dernière ligne de pixels avant le défilement.




Lors de la mise à jour de la position de défilement au milieu du cadre, seule la position X de la position de défilement est appliquée. C'est-à-dire que le composant de position de défilement Y est ignoré. Ainsi, si le jeu souhaite diviser l'écran et modifie la position de défilement d'une partie du cadre, il ne peut que défiler horizontalement.

Et pourtant:



Croyez-le ou non, la valeur du registre PPUSCROLL pas changé pendant cette transition.

Vous pouvez voir un artefact graphique d'un pixel de haut sous l'interface. Il s'agit d'un bug de mon émulateur provoqué par le manque de synchronisation des cycles d'horloge du processeur avec le rendu pixel par pixel.

Intervention dans d'autres registres


Le second registre, appelé PPUADDR , mappé à l'adresse mémoire 0x2006 , est utilisé pour définir l'adresse mémoire vidéo actuelle. Lorsqu'un jeu, par exemple, souhaite modifier l'une des tuiles de la table de noms, il écrit d'abord l'adresse de mémoire vidéo de la PPUADDR dans PPUADDR , puis écrit la nouvelle valeur de la PPUDATA dans PPUDATA - il s'agit du troisième registre mappé à l'adresse 0x2007 .

L'écriture dans PPUADDR pas pendant vblank (c'est-à-dire lors du rendu d'une image) peut provoquer des artefacts graphiques. Cela est dû au fait que la chaîne PPU, qui est affectée par l'écriture dans PPUADDR , est également directement contrôlée par le périphérique PPU dans le processus d'obtention de tuiles de la mémoire vidéo pour les dessiner. Étant donné que le processus de rendu à l'écran est effectué de haut en bas et de gauche à droite dans la ligne, le PPU attribue essentiellement à PPUADDR valeur de l'adresse de la PPUADDR actuelle. Lorsque le rendu se déplace d'une mosaïque à une autre, PPUADDR est incrémenté de la valeur actuelle.

Ainsi, l'écriture dans PPUADDR au milieu de la trame peut changer les tuiles reçues par la PPU de la mémoire pendant la durée de la trame actuelle.

PPUADDR opérations d'écriture dans PPUADDR pendant le saut vertical. Étant donné que la table de noms est également mise à jour pendant la transition, la sortie de toutes les opérations d'écriture vers PPUADDR sera trop étendue. Avec une transition horizontale, le défilement est défini pendant le rendu d'une ligne de pixels 63, par conséquent, nous ne considérerons les opérations d'écriture dans PPUADDR que pendant cette ligne.




Le motif est clairement visible. Toutes les deux trames, l'adresse enregistrée dans la ligne de pixels 63 est réduite de 32 (0x20). Mais comment cela conduit-il à une mise à jour de la position de défilement réelle?

Registre de défilement réel


À l'intérieur du PPU, il y a un registre de 15 bits non mappé au CPU. Il est utilisé à la fois comme adresse actuelle pour accéder à la mémoire vidéo et comme configuration de défilement en arrière-plan.

Lorsque vous travaillez avec cette valeur comme adresse, le bit 14 est ignoré et les bits 0-13 sont traités comme une adresse dans la mémoire vidéo.

Lorsque vous travaillez avec cette valeur comme configuration de défilement, ses différentes parties ont des significations différentes:


La sélection d'une table de noms est une valeur comprise entre 0 et 3 qui détermine la table de noms actuelle à partir de laquelle le dessin est créé.

Le défilement grossier en X et le défilement grossier en Y déterminent les coordonnées de la tuile à l'intérieur du tableau de noms sélectionné. Il s'agit de la tuile actuelle à dessiner.

Le défilement exact le long de Y contient une valeur de 0 à 7, qui détermine le décalage vertical actuel de la ligne de pixels à l'intérieur de la mosaïque actuelle. Les tuiles sont des carrés avec un côté de 8 pixels.

Le défilement exact sur X est absent de ce registre. Il existe un registre séparé contenant uniquement le décalage horizontal du pixel actuel, mais il n'est pas important d'expliquer comment le défilement vertical est effectué dans The Legend of Zelda.

Qu'advient-il de ce registre lorsqu'un jeu écrit dans PPUADDR ? Voici les trois premières opérations d'écriture de la démo ci-dessus.


En divisant les entrées de l'adresse en composants de défilement, vous pouvez clairement comprendre ce qui se passe ici. Toutes les deux images, la valeur du défilement brut en Y diminue, ce qui entraîne un défilement vertical d'une tuile ou de 8 pixels.

Tout au long de chaque trame, le décalage de défilement initial est de 0,0, après quoi l'enregistrement sur la ligne de pixels 63 est effectué à l'adresse. Cela signifie que les 63 premières lignes de pixels sont dessinées à partir du haut de la table de noms sélectionnée contenant l'arrière-plan de l'interface. Cependant, la 64e ligne de pixels est rendue davantage avec un défilement vertical appliqué à partir de cette adresse. Étant donné que le défilement vertical diminue toutes les deux images, il donne la sensation de défilement vertical d'une partie de l'écran.

Faites défiler vers le bas pour faire défiler vers le haut


La légende de Zelda ne peut pas complètement cacher cette astuce aux joueurs. Il crée un artefact visible sur les transitions verticales de l'écran, qui sont visibles si vous regardez de près. Lorsque vous vous déplacez entre les pièces, la première image de l'animation de défilement défile vers le bas. Voici l'animation au ralenti.



Dans le tableau des noms, vous pouvez voir ce qui se passe réellement. Bien qu'il puisse sembler aux joueurs que la zone visible défilera vers le haut en douceur, la transition de défilement commence en déplaçant la zone visible du tableau de noms supérieur gauche vers le tableau inférieur gauche, qui contient une copie de l'arrière-plan de la pièce. Cela est nécessaire car l'interface en haut de l'écran fait également partie de la table des noms, et si la zone visible défilait de sa position d'origine, elle passerait par l'interface.

Le défilement vertical est implémenté en écrivant dans le registre PPUADDR au milieu de la trame. La toute première valeur à écrire est 0x2800 . Deux images plus tard, 0x23A0 enregistré, puis la valeur commence à diminuer de 32 toutes les deux images.


L'écriture de la valeur 0x2800 dans le registre 0x2800 PPUADDR table de PPUADDR sur 2, ce qui rend la table de noms en bas à gauche. Étant donné que les deux valeurs de défilement sont 0, il commencera à partir de la tuile supérieure gauche de cette table de noms. Cependant, le défilement exact dans Y est 2, il y a donc un décalage vertical de deux pixels par rapport au haut du tableau de noms en bas à gauche. C'est pourquoi dans la toute première image de la transition, on voit une barre noire de 2 pixels de haut en bas de l'écran. La valeur de défilement initiale de l'animation de transition est décalée de 2 pixels vers le bas pour rendre la transition transparente.

Deux trames plus tard, la PPUADDR écrite dans 0x23A0 . Cela nous ramène à la table des noms en haut à gauche, et nous effectuons le rendu à partir de la 29e rangée de tuiles, c'est-à-dire en bas. Le défilement exact en Y contient toujours 2.

Pourquoi est-il nécessaire de définir Exact Scrolling in Y sur 2? Pourquoi le jeu n'écrit-il pas simplement 0x0800 et 0x03A0 pour ne pas souffrir d'un décalage de deux pixels?

Quatre tables de noms occupent la zone de 4 Ko dans l'espace d'adressage PPU, de 0x2000 à 0x2FFF . Chaque tuile de la table occupe un octet de mémoire vidéo (en fait, ce ne sont que des index dans une autre table), et l'ordre des tuiles et des tables de noms dans la mémoire vidéo est tel que Sélection d'une table de noms , Défilement grossier par Y et Défilement grossier par X constituent le décalage de la tuile à l'intérieur zones de mémoire avec tables de noms. Autrement dit, en prenant les 12 bits inférieurs du registre PPU interne et en les ajoutant à 0x2000 , vous pouvez trouver l'adresse de la 0x2000 dans la mémoire vidéo. Et ce n'est pas un hasard! C'est exactement la façon dont le registre doit être traité: à la fois comme registre d'adresse et comme registre de défilement.

Mais il y a un défaut.

Lors du traitement en tant que registre d'adresse, les bits 12 et 13 sont considérés comme faisant partie de l'adresse. Pendant le rendu, le PPU écrase constamment le registre avec l'adresse de la tuile rendue actuelle. Étant donné que les tuiles sont situées dans les tables de noms et que les tables sont situées dans la zone de mémoire de 0x2000 à 0x2FFF , PPU attribue des valeurs de cet intervalle au registre.

Lorsque le jeu écrit dans PPUADDR au milieu de la trame, s'il n'écrit pas l'adresse de la tuile dans la table des noms, alors le PPU essaiera de lire ailleurs dans la mémoire vidéo. Tous les octets qu'il comptera seront perçus comme des tuiles, ce qui est susceptible de conduire à des résultats indésirables. Par conséquent, toutes les valeurs enregistrées au milieu de la trame dans PPUADDR doivent être comprises entre 0x2000 et 0x2FFF . En prenant chaque nombre dans cet intervalle et en tenant compte de ses composantes de défilement, la valeur de défilement exacte en Y doit toujours être égale à 2.

Cette limitation signifie que nous ne pouvons pas modifier le défilement exact dans la direction Y au milieu du cadre, c'est-à-dire que lorsque vous utilisez cette astuce pour implémenter le défilement vertical de la séparation d'écran, nous sommes limités au défilement de 8 pixels à la fois et avons toujours un décalage vertical de deux pixels par rapport à la bordure de la tuile. La légende de Zelda se déplace de 4 pixels par image lors du défilement horizontal, mais de 8 pixels par image lors du défilement vertical, et nous savons maintenant pourquoi.

L'artefact est également visible lors du défilement entre les pièces vers le bas, mais dans ce cas, il se produit à la fin de l'animation.



Lecture complémentaire


  • Le wiki NesDev est une ressource inestimable pour en savoir plus sur le matériel NES. En particulier, le sujet de cet article sont des pages sur le défilement PPU
    et registres PPU .
  • Mon émulateur NES encore très inachevé est disponible ici .

Remarques


Jusqu'à ce que je découvre le registre interne de PPU, mon émulateur montrait l'effet d'effacement lors des transitions verticales de l'écran The Legend of Zelda.



L’image-objet de Link s'est déplacée vers le bas de l’écran, comme il se doit, mais l’arrière-plan n’a pas défilé. L'effacement a été provoqué par le fait que le jeu a progressivement mis à jour la table des noms afin qu'elle contienne les graphiques de la nouvelle salle, mais n'a pas mis à jour le défilement pour garder les mises à jour hors écran.

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


All Articles