Présentation
Avec ce projet, je voulais répondre à une question: est-il possible d'écrire une API Java pour Playstation 2 et de créer une démo graphique dessus. Je ne veux pas révéler les spoilers, mais la réponse est oui.
Il y a quelques années, j'ai lancé un projet
Java Grinder qui reçoit des fichiers Java .class compilés et fonctionne en fait comme un désassembleur. Mais au lieu de se désassembler en code assembleur Java, il se désassemble en code source assembleur pour de vrais processeurs. Si le fichier de classe a besoin d'autres fichiers de classe, ils sont également lus et traités. Tous les appels de méthode API sont écrits dans la sortie, soit en tant que code assembleur intégré, soit en tant qu'appels à des fonctions pré-écrites qui effectuent leur tâche prévue.
Étant donné que
Java Grinder a été écrit en C ++, orienté objet, abstrait, polymorphe et beaucoup plus de mots RH préférés de haut niveau, pour l'étendre, il était principalement nécessaire de créer la classe Playstation2, d'étendre la nouvelle classe R5900, d'étendre la classe Generator principale.
En conséquence, le projet s'est avéré être plus important que ce à quoi je m'attendais. Le système lui-même est assez simple, mais j'ai encore beaucoup à apprendre et trouver des informations de qualité n'est pas si simple. En fait, pour la première fois dans ce projet, j'ai repris une vraie programmation 3D. Dans un autre article, j'ai déjà parlé de ce que j'ai appris sur ma page de
programmation Playstation 2 .
Vous trouverez ci-dessous une vidéo et une explication détaillée du processus de développement.
Vidéo
J'ai enregistré une démo sur une PS2 slim en connectant des câbles audio et vidéo à un graveur de DVD. J'étais un peu inquiet que la PS2 ait une sorte de protection Macrovision qui ruinerait le signal vidéo, mais elle était éteinte ou l'enregistreur de DVD l'ignorait. La vidéo commence par une démonstration de la vraie Playstation 2, qui exécute une démo. La console est connectée à un convertisseur signal composite vers VGA, connecté à un écran LCD, prouvant que la démo fonctionne sur une vraie machine. Ensuite, j'ai ajouté un collage avec la vraie vidéo enregistrée directement à partir de la PS2 dans l'enregistreur de DVD.
YouTube:
https://youtu.be/AjM069oKUGsProjets similaires sur mikekohn.net
Démo
En me souvenant de la démo
Sega Genesis Java , je regrette un peu de ne pas l'avoir rendue plus intéressante. Ensuite, il m'a été plus intéressant de démontrer les capacités de l'API Java. Quand j'ai commencé ce projet, j'ai décidé de faire quelque chose de plus sérieux. Malheureusement, j'ai de nouveau été tellement épuisé lors de l'étude du système et de la création de l'API que je n'avais pas assez de force pour une grande démo.
- Logo 3 milliards d'appareils: Il s'agit du logo Java basse résolution de 3 milliards d'appareils exécuté par Joe Davisson pour sa démo Commodore 64 Java .
- Logos: je les ai dessinés avec un marqueur et je les ai scannés (à l'exception du logo Java).
- Stars: J'ai en fait copié le code de la démo Java de Sega Genesis et l'ai modifié pour qu'il fonctionne avec l'API Playstation 2. Le texte ici est également écrit avec un marqueur et numérisé.
- Fractales de Mandelbrot: elles sont démontrées à l'aide de l'unité vectorielle 0, qui calcule les fractales, et l'unité vectorielle 1 effectue des calculs 3D. MIPS contrôle ce que font les deux appareils vectoriels.
- Cubes: J'ai dessiné ces cubes dans Wings3d et écrit du code C pour convertir les fichiers STL en tableaux que Java Grinder pourrait utiliser. J'ai ajouté des couleurs manuellement.
- Ring of squares: juste une tentative pour dessiner beaucoup d'objets en mouvement sur l'écran. Il valait probablement la peine d'ajouter plus d'objets avant que le système ne commence à ralentir.
La musique
Pour la démo, j'ai composé et enregistré trois chansons, mais en conséquence, je n'en ai utilisé que deux. La première composition est en fait une mélodie que j'ai écrite pour un autre projet publié sur mon site (
projet accepteur de pièces ) il y a environ un an ... au départ il n'y en avait qu'une partie. Une fois le projet terminé, j'ai pensé qu'il serait intéressant de mettre un solo de guitare dessus, et après l'enregistrement, j'ai imaginé que cette musique joue pendant que les étoiles volent dans la démo. Je n'ai réussi à l'utiliser qu'après quelques mois. La guitare de la composition est
Fender Strat I festonnée .
J'ai enregistré la deuxième composition juste un jour avant la publication de l'article. Le solo de guitare sonne un peu ... ivre parce qu'il se joue sur une guitare que j'ai transformée en
fretless . Je ne suis pas très bon pour le jouer, et les notes aiguës s'estompent très rapidement, mais les diapositives sonnent plutôt bien. La partie rythmique a été jouée sur mon
kit Yngwie Wanna- Be (Squier Strat festonné pas cher, overdrive DOD YJM308 et Mini-Marshall alimenté par des batteries de 9 volts).
J'ai programmé des percussions pour les deux compositions en utilisant le programme
Drums ++ écrit de longue date. Il reçoit des fichiers texte d'entrée enregistrés dans ma propre langue et les transforme en fichiers .mid, que j'ai importés dans Apple Garage Band, après quoi vous pouvez enregistrer des pistes de base et de guitare. Les fichiers source
fretless.dpp et
shoebox.dpp se trouvent dans le dossier des ressources de mon référentiel de démonstration.
La musique est lue par la Playstation 2 SPU2, et grâce au R5900, elle peut faire d'autres travaux. En raison du manque de bonne documentation, j'ai presque terminé la démo sans aucune musique. Plus d'informations ci-dessous.
Voici deux pistes au format MP3:
Développement
Le projet est développé depuis longtemps. J'ai commencé à ajouter des instructions R5900 Emotion Engine à l'assembleur MIPS dans
naken_asm , puis je suis allé chercher des instructions à virgule flottante et des instructions d'unité macro / micro-vecteur. Faisant un grand répit pour travailler sur d'autres projets, j'ai étudié tous les autres aspects nécessaires à cette démo et j'ai ajouté leur support à
Java Grinder . Si quelqu'un s'intéresse aux détails de bas niveau, j'ai créé une page sur laquelle j'ai essayé de documenter toutes les informations collectées: la
programmation Playstation 2 .
J'ai principalement programmé en utilisant
l'émulateur PCXS2 . C'est assez pratique, car je peux y examiner des registres et autres à l'écran. Mais ce n'est certainement pas aussi flexible et simple que MAME lors du développement de
Sega Genesis . Par exemple, dans MAME, il est plus facile d'examiner la mémoire, la RAM et les registres vidéo / audio pour s'assurer que
Java Grinder fonctionne correctement.
En travaillant avec le code pour Sega, j'ai fait une erreur - je ne l'ai pas testé sur la machine jusqu'à ce que la démo soit écrite. Il y avait au moins trois bizarreries dans le code Sega que l'émulateur ignorait, mais ils n'aimaient pas la vraie machine. Cette fois, après avoir écrit les différentes parties du code, je les ai testées sur une vraie machine, de sorte qu'après la démo, cela fonctionne à la fois sur de l'équipement réel et sur l'émulateur. Je suis de nouveau tombé sur des choses qui fonctionnaient dans l'émulateur, mais qui n'ont pas démarré sur une vraie PS2. J'ai également constaté qu'il fonctionnait sur une vraie Playstation 2, mais qu'il fonctionnait incorrectement dans l'émulateur.
Fonctionnalités de l'API
- L'unité vectorielle 0 propose des méthodes de chargement / exécution de code et de chargement / déchargement de données.
- L'unité vectorielle 1 effectue des rotations et des projections 3D.
- Textures utilisant un format 16 ou 24 bits (la transparence est indiquée en noir).
- Les textures au format 16 bits peuvent être encodées en RLE.
- Code pour dessiner des points, des lignes, des triangles, avec et sans textures.
- Brouillard et ombrage par Guro.
- Méthodes d'accès à un générateur de nombres aléatoires.
- Utilisation de deux contextes (remplacement de pages)
- Insérez de grandes données binaires dans le code assembleur compilé.
- Jouer de la musique.
API
La partie principale de l'API est définie dans la classe
Playstation2 . Au départ, j'allais lui donner une grande liberté - la possibilité de définir des modes vidéo et autres, mais j'ai pensé qu'il valait mieux cacher toutes ces difficultés. Essentiellement, il configure simplement un affichage 640x448 entrelacé. Comme avec d'autres projets
Java Grinder , j'ai essentiellement ajouté des méthodes / fonctionnalités selon les besoins.
Il y a un autre ensemble de classes auquel j'ai donné le nom ennuyeux
Draw3D . En substance, ils définissent tous les types de primitives que Graphics Synthesizer peut rendre avec la prise en charge des textures 16, 24 et 32 bits. J'ai pensé à ajouter des textures 8 bits, mais j'ai décidé de ne pas le faire encore. Draw3D fournit des rotations 3D, des projections, des transferts de matériel DMA, des textures, etc. Vous vous demanderez probablement pourquoi je ne l'ai pas créé sur la base d'OpenGL, mais je n'avais jamais travaillé avec OpenGL auparavant. Il était une fois j'étais engagé dans une simple programmation ps2dev, mais il n'y avait rien de sérieux là-bas et je me souviens à peine de ce projet, donc je le répète - nous pouvons supposer que c'est la première fois que je fais quelque chose de sérieux en 3D.
Il existe des exemples d'utilisation de toutes ces choses, non seulement dans la démo, mais aussi dans le dossier d'
exemples .
Presque toutes les difficultés sont cachées dans l'API. Le développeur n'a pas à se soucier de vider le cache, de l'informatique 3D, etc. Cependant, le retour sur investissement a été une diminution de la polyvalence. Par conséquent, si, par exemple, la texture a été modifiée par le processeur, mais que seuls les 64 premiers pixels ont changé, vous devez effacer une seule ligne de cache de 64 octets, mais Java Grinder efface toute l'image. Il marque les objets, ils ne sont donc effacés que si nécessaire, mais efface tout le fragment de mémoire. Avec une forte probabilité lors du changement de 64 octets, l'image entière change également.
Unité vectorielle 0 (VU0)
L'utilisateur de Java Grinder est libre d'utiliser VU0. J'ai utilisé la partie de démonstration intitulée «Deux unités vectorielles, un MIPS» pour rendre les fractales de Mandelbrot. Le code peut être mieux optimisé, par exemple, la plupart des instructions à virgule flottante d'unité vectorielle ont un temps d'exécution de 1 et une latence de 4. Autant que je sache, cela signifie que si le registre est la cible de l'instruction, il peut être exécuté en 1 cycle, mais afin de pour que le résultat soit disponible, 4 cycles sont nécessaires. Si ce registre est utilisé, l'unité vectorielle restera inactive jusqu'à ce qu'elle soit prête. Par conséquent, l'idée est que vous devez organiser les instructions afin de pouvoir exécuter chaque instruction en 1 cycle sans interruption. Lorsque j'ai créé les
fractales Mandelbrot sur la Playstation 3 l'année dernière, j'ai optimisé ce code tout en calculant 8 pixels (2 registres SIMD), tout en remarquant une forte augmentation de vitesse. Dans le cas actuel, j'ai essayé de rendre le code plus facile à lire, donc je n'ai pas pris la peine de l'optimiser.
VU0 ne contient que 4 Ko de mémoire de données et vous n'y écrirez pas l'intégralité de l'image fractale. MIPS envoie donc les coordonnées de seulement 1/8 de l'image à la fois.
L'étrangeté que j'ai rencontrée en travaillant avec VU0: J'ai d'abord exécuté le code VU0 à l'aide d'instructions et utilisé VIF0_STAT pour vérifier la fin de leur exécution. Il semble que VIF0_STAT ne fonctionne pas si vous ne démarrez pas VU0 avec le package VIF. Ce problème est résolu dans l'émulateur, mais l'erreur persiste dans le matériel réel. En conséquence, j'ai découvert que vcallms et l'utilisation de cfc2 dans le registre 29 fonctionnent dans les deux cas.
Il me semble que les jeux d'instructions d'unités vectorielles manquent des instructions de comparaison parallèle trouvées dans les
jeux d' instructions
Intel X86_64, Playstation 3 et même MIPS R5900 Emotion Engine. Les fractales de Mandelbrot doivent calculer la formule de manière itérative jusqu'à ce que le résultat soit supérieur, donc avec un ensemble d'instructions différent, je voudrais simplement effectuer une comparaison parallèle qui créerait un masque pour tous les binaires 1 ou 0. Le masque peut être utilisé pour arrêter l'incrémentation des compteurs de couleurs. Pour Playstation 2, j'ai dû dériver une formule très maladroite, qui est assez proche de la formule fractale. J'ai documenté cela dans le code source de
mandelbrot_vu0.asm en lignes avec Python commenté.
Je trouve cool dans les appareils à unités vectorielles de la console Playstation 2 que c'est génial que je n'ai vu aucun autre ensemble d'instructions SIMD dans lequel toutes les instructions FPU peuvent avoir l'attribut .xyzw indiquant l'instruction parmi les quatre nombres à virgule flottante qu'il possède affecte. Autrement dit, si j'avais besoin que le résultat de l'instruction n'affecte que la composante x du vecteur, j'ajouterais simplement .x à la fin de l'instruction. Une autre chose intéressante est qu'il s'agit d'un ensemble d'instructions VLIW, c'est-à-dire qu'à chaque cycle, deux instructions sont exécutées simultanément. En fait, le module ressemble plus à un DSP qu'à un processeur polyvalent.
Unité vectorielle 1 (VU1)
VU1 est réservé par Java Grinder pour effectuer des rotations, mouvements et projections 3D. Les objets Draw3D sont transmis à VU1 à l'aide de la méthode draw (), qui utilise l'assembleur d'unités vectorielles pour convertir les points et les transférer vers le synthétiseur graphique. Le code assembleur dans VU1 peut être beaucoup mieux optimisé pour la vitesse, mais il convient à mes besoins, j'ai donc décidé de laisser le code facile à lire (non optimisé).
Pour étudier les formules de projections et de virages, j'ai utilisé Wikipedia:
projections et
virages .
Le code de transformation 3D se trouve également dans le référentiel
naken_asm sous la forme d'un simple fichier .asm:
rotation_vu1.asm .
MIPS R5900
Je n'ai pas vraiment aimé le jeu d'instructions MIPS jusqu'à ce que je commence à travailler sur ce projet. En fait, c'est assez facile de travailler avec ce CPU. La version Emotion Engine de ce CPU a des fonctionnalités très pratiques. Plus particulièrement, les registres ont une longueur de 128 bits, mais en fait ils sont simplement utilisés pour le chargement / stockage et SIMD. Autrement dit, ce sont des registres 128 bits, ALU 64 bits et des pointeurs 32 bits.
Il était également possible d’introduire des optimisations dans le code principal de MIPS, mais je ne l’ai pas fait pour maintenir la lisibilité du code ou par manque de temps. Par exemple, le CPU MIPS est inactif pendant un cycle si le registre d'instructions cible a été utilisé immédiatement après sa configuration. Ce comportement pourrait être amélioré.
Piratages Java
Java Grinder lui-même a aussi ses propres ... bizarreries, mais quelque chose manque tout simplement, principalement parce que je visais initialement le MSP430 et pour la plupart, c'était une expérience. L'un des éléments manquants était l'incapacité d'allouer de la mémoire aux objets. J'ai ajouté cette fonctionnalité sur Playstation 2 pour instancier plusieurs objets, principalement en utilisant l'API Draw3D. Je n'ai pas écrit d'allocateurs de mémoire ou de récupérateurs de place, donc tous les nouveaux appels sont effectués sur la pile. Je pensais implémenter quelque chose comme un allocateur de mémoire dynamique, mais j'ai finalement décidé de ne pas le compliquer. J'ai également ajouté pour Playstation 2 une prise en charge étendue des nombres à virgule flottante (float) (en partie, cette prise en charge était toujours dans le code Epiphany / Parallella). Certaines autres choses, telles que les types long et double, ne sont toujours pas prises en charge.
La chose la plus ennuyeuse que j'ai faite a probablement été liée à la terrible restriction des fichiers de classe Java. La méthode Java ne peut pas dépasser 64 Ko si je me souviens bien. C'est peut-être normal, mais le problème se pose lorsqu'il existe un tableau statique dans le fichier de classe et qu'il ne se décharge pas dans le fichier de classe en tant que données binaires. Il est placé dans le fichier de classe en tant qu'instruction d'assembleur Java dans un initialiseur statique pour créer un tableau. J'ai essayé d'enregistrer des images dans des fichiers de classe en tant que tableaux d'octets statiques [], mais certains d'entre eux ne convenaient pas, j'ai donc ajouté une méthode au fichier de classe Memory
Java Grinder :
byte[] Memory.preloadByteArray(String filename);
Il ne télécharge pas ce fichier au moment de l'exécution, mais le télécharge au moment de la construction à l'aide de la directive .binfile
naken_asm . Les images sont copiées dans le binaire de sortie lors de l'assemblage.
Avec tout cela à l'esprit, j'espère vraiment que
James Gosling ne tombera jamais sur mon projet.
Les images
L'API Draw3D peut utiliser des textures 16, 24 et 32 bits. Les pixels de texture peuvent être définis pixel par pixel ou en chargeant à l'aide de tableaux d'octets []. J'ai également ajouté la possibilité de compresser les images RLE au format {longueur, lo16, hi16}, où lo16 et hi16 sont la couleur 16 bits au petit format endian, qui est copiée dans la texture "longueur" fois.
Les outils
Lorsque je travaillais sur Sega pour créer des outils pour créer des images, de la musique, etc., j'ai utilisé la langue de Google Go, juste pour apprendre une nouvelle langue. Cette fois, j'ai essayé Rust. Le premier outil convertit les binaires en code source Java et le second convertit BMP au format binaire, qui peut être chargé dans des textures, y compris au format RLE. En conséquence, je les ai écrits en Python, au cas où quelqu'un voudrait me rejoindre pour créer une démo.
Son
Après avoir compris comment fonctionnent les périphériques graphiques et vectoriels, la dernière étape a été le son. Je pensais que ce serait la partie la plus facile, surtout après avoir étudié le PDF avec la description Sony SPU2. Comme j'avais tort. Cette partie du système est très mal documentée.
La première chose que j'ai découverte est que le SPU2 (unité de traitement du son) est connecté à l'IOP (processeur d'E / S, alias processeur Playstation 1). Le processeur Playstation 2 est connecté à ce IOP via quelque chose appelé SIF. Le PDF de Sony ne mentionne que SIF DMA, mais ne dit rien sur son utilisation.
En conséquence, j'ai refusé d'utiliser SIF, mais j'ai décidé d'ajouter un éditeur de liens à
naken_asm afin de pouvoir utiliser kernel.a à partir du SDK PS2DEV. Linker a gagné, mais a échoué.
À ce stade, j'ai déjà décidé que je ne pouvais pas faire fonctionner le son, et je voulais juste terminer la démo sans lui. Mais cela m'a tourmenté, j'ai donc décidé de jeter un œil au code source de divers émulateurs Playstation 2 pour comprendre comment fonctionne SIF. Enfin, j'ai compris comment accéder directement à la mémoire à partir du code MIPS R3000 dans IOP et l'exécuter (il y a un exemple dans le dossier d'exemples du référentiel naken_asm). J'ai réussi à faire fonctionner le son dans l'émulateur.
À la fin, j'ai découvert que la mémoire IOP (y compris SPU2) était située dans l'espace du moteur d'émotion, donc j'ai mis beaucoup d'efforts (la documentation est extrêmement petite et dans aucun des émulateurs, elle est implémentée complètement correctement, mais cela n'a pas d'importance pour eux de fonctionner ), J'ai appris à travailler avec le son.
Comparaison de l'émulateur et du fer
J'ai trouvé quelques différences entre l'exécution sur une vraie machine et dans un émulateur.
- Si le package GIF définit le registre PRIM à la fois les valeurs IIP (méthode d'ombrage) et que les bits FIX sont tous à 1, alors l'émulateur prend en compte le bit IIP et effectue l'ombrage Gouro, tandis que l'équipement réel effectue l'ombrage plat.
- Si le paquet GIF est transmis via PATH3 (EE directement à GS) et que l'indicateur EOP (fin de paquet) n'est pas défini, alors si VU1 essaie d'envoyer le paquet GIF via PATH 1 (VU1 à GS), cela entraînera un blocage dans le matériel réel, mais fonctionnera dans l'émulateur.
- Ignorer la suppression du cache du processeur avant le transfert DMA n'est pas nécessaire, mais sur une vraie machine, cela conduit à un comportement étrange.
- Lorsque vous placez SPU2 dans l'espace EE, l'émulateur peut simplement enregistrer des données audio dans FIFO SPU2. Sur une vraie Playstation 2, après avoir enregistré 32 mots, il faut écrire dans le registre pour donner une commande d'effacement du FIFO. De plus, sur du matériel réel, lors du réglage de l'adresse de transmission / de début du SPU2, le mode de transmission doit être défini sur 0. Les émulateurs ne se soucient pas si le mode a une valeur de 0.
- L'écriture d'IOP d'EE dans les registres de mémoire alloués plante sur une vraie machine, même si elle est en mode noyau. L'émulateur permet à ces opérations de fonctionner quel que soit le mode CPU actuel.
- L'utilisation de canaux SIF DMA fonctionne dans l'émulateur, mais je n'ai toujours pas réussi à les faire fonctionner sur de l'équipement réel. J'obtenais une erreur d'accès à la mémoire pour les registres SIF DMA même en mode noyau.
- L'émulateur est trop lent pour exécuter une démo lors du calcul des fractales à l'aide de VU0, donc le son n'est pas synchronisé.
Pour résumer
Je voulais écrire un programme pour Playstation 2 presque dès le moment même de son achat. En fait, j'avais déjà un kit Linux pour PS2 depuis longtemps (je pense que c'est pourquoi j'ai acheté Playstation 2), et j'ai même essayé de travailler avec la bibliothèque PS2DEV en C, mais c'est une expérience complètement différente par rapport à la programmation en langage assembleur directement pour le fer.
Je dois remercier
Lukash d' avoir enregistré l'ancien code source de l'assembleur et les documents PS2. Je ne sais pas si je pourrais même commencer sans la démo Duke 3 Star, qui m'a aidé à initialiser l'équipement. Je suis également reconnaissant aux développeurs de l'émulateur
PCSX2 , qui a considérablement accéléré le processus de débogage. De plus, je ne pouvais pas comprendre le son si je n'avais pas regardé le code source de l'émulateur et ne comprenais pas ce qui n'allait pas.
Et merci à
Sony pour ce beau petit ordinateur. Si quelqu'un de Sony lit cet article, voici un conseil: pourquoi ne pas le réduire à la taille d'un Rapsberry Pi et le vendre en tant que carte pour des projets de loisirs? :).
Créer une démo
git clone https://github.com/mikeakohn/playstation2_demo.git
git clone https://github.com/mikeakohn/java_grinder.git
git clone https://github.com/mikeakohn/naken_asm.git
cd naken_asm
./configure
make
cd ..
cd java_grinder
make java
make
cd ..
cd playstation2_demo
make