HOME            INDEX           LINK              CONTACT

 

RETRO-COMPUTING (PROGRAMMATION POUR AUTISTE)

 

Programmation de la Playstation

(En construction et susceptible de contenir (beaucoup) d’erreur…)

 

ATTENTION, ce qui suit sont mes notes concernant la programmation de la PLAYSTATION en très bas-niveau : sans aucun SDK et autres librairies (seul les fonctions de la ROM seront utilisées).

Si vous cherchez seulement à programmer la PSX avec du C/C++ et le SDK, dirigez-vous à cette adresse :

http://psxdev.net/index.html

Vous trouverez tous ce qu’il vous faut : SDK comprenant les bibliothèques, compilateur C/C++/MIPS3000, documentations et tutoriels J

 

Si vous souhaitez attaquer la Playstation en bas-niveau alors vous pouvez continuer à lire cette page et comme toujours vous méfiez de ce que je raconte et plus encore pour cette console. Certains passages sont des copier/coller de mes références, d’autre sont des remarques diverses sans lien entre eux.

Néanmoins je pense que cette section donne tous les « trucs » de base et vous permettra de gagner pas mal de temps (en tout cas je l’espère).

 

Si vous chercher un compilateur MIPS ou autres outils que vous avez la flemme de les développez, vous trouverez cette adresse : http://hitmen.c02.at/html/psx_tools.html se trouve un assembleur SPASM avec une syntaxe type MOS/M68000 et un compilateur MIPSGNU.

Le SDK sur psxdev contient également un assembleur (aspsx/asmpsx).

Vous pouvez éventuellement utiliser mon assembleur écrit en JAVA: ZawaMIPSX. Il n’est pas encore terminé, ni très évolué et la syntaxe est un petit peu spécifique concernant les macros (ou plutôt leur absence) et directives. Il ne gère seulement que le segment .Text et produit directement un exécutable psx.

 

Néanmoins, dans ces articles ainsi que les programmes d’exemple on utilisera ZawaMIPSX.

 

Comme avec l’AMIGA, je pars toujours du principe que l’on n’a rien (ni SDK/compilateur/linker) et que l’on a envie éventuellement de programmer les outils plus aptes à répondre à nos besoins.

C’est juste un exercice de style pour montrer que nous n’avons pas besoin de SDK pour développer mais seulement de connaissances.

D’ailleurs je ne comprends absolument pas comment peut-on retirer une réelle satisfaction en programmant cette architecture complexe avec le SDK et les bibliothèques.

 

Ne seront pas aborder dans cette partie :

- Le MDCP pour le décodage de bloc type JPEG

- Le lecteur CDROM que nous contrôlerons par les fonctions du bios pour la lecture de fichiers.

- La programmation directe des contrôleurs PAD que nous contrôlerons par les fonctions du bios.

- Les ports de carte mémoire que nous contrôlerons par les fonctions du bios.

- Le décodeur du lecteur CD/ROM pour le son et la reverb unit du SPU.

- Les ports externes (PIO et SIO).

 

Contrairement à l’Amiga, je documente maintenant systématiquement ce que je fais avec la PSX. Donc des articles plus spécifiques sur le GTE, le SPU et le CD/ROM sont en cours également.

 

Vous trouverez ici les listings complets d’une série progressive de programmes en rapport avec cet article et compilables avec ZawaMIPSX ainsi que quelques fichiers media utilisés dans les exemples.

 

References :

-          Hitmen PSX

-          Sony Developer CD

-          No$PSX specifications

-          Service Manual Playstation 75xx

 

 

 

1-    Introduction

 

Console (et particulierement les consoles de 5/6eme generation) oblige, la Playstation possède une architecture très exotique.

 

La PSX est bâtit autour d’un circuit à intégration multiple fabriqué par SONY et LSI. Ce circuit (IC103 CXD8606 que nous appellerons R3000A) intègre un processeur MIPS R3000 (plus proche du R3051) 32 bits de type RISC cadencé à 33MHZ munit de 4Ko de cache d’instructions. Le processeur est customisé pour les besoins de la PSX avec une SRAM de 1Ko qui remplace le D-cache et l’amputation du COP1 pour le calcul des flottants. Le coprocesseur système (COP0) est quasi identique au standard en revanche, le coprocesseur de calcul vectoriel (COP2 ou GTE) est lui spécifique. La puce contient également un décodeur de bloc DCT type JPEG (MDCP) et, semble-t-il, un autre composant hybride contenant les compteurs internes (3 compteurs) et faisant l’interface avec les interruptions externes (provenant du GPU, SPU, CD/ROM etc.).

 

Le system travaille dans une RAM de 2MB.

 

La PSX possède également deux autres circuits spécialisés :

Le GPU (IC203 CXD8561) est le coprocesseur graphique, il est très puissant et possède un paquet de routine de dessin « Hardware ». La mémoire vidéo (1Mo de VRAM + 1 cache texture de 2KO+ tampon de commande de 64 octets ??) y est indépendante et inaccessible directement par le processeur.

Le SPU (Intégrée en un seul chip IC732 depuis 75xx) est le chip audio 16 bits avec de belles possibilités: 24 canaux programmables avec enveloppe ADSR, synthèse ADPCM, générateur de bruit, synthèse FM et une infinité d’autre choses mais il n’est également pas sans défaut. Le SPU possède un tampon de 512 Ko. 

 

Le lecteur CD/ROM double vitesse possède également un encodeur PCM->ADPCM en relation étroite avec le SPU.

Deux port manettes et deux port carte mémoire.

Un port Série.

La PSX intègre une ROM de 512KO dans lequel se trouve son OS.

 

Voici à peu près le bloc fonctionnel de la PlayStation

 

                                      

 

 

 

2-    Quel(s) émulateur(s) utiliser pour la programmation ?

 

L’utilisation d’un émulateur est quasi indispensable (sans link-cable) et surtout au début pour l’apprentissage.

 

Contrairement aux émulateurs pour les retro-machines comme le C64, les Amstrad CPCs et WinUAE pour l’Amiga, les émulateurs de la PSx restent encore très loin d’une émulation correcte. En effet de par sa complexité et son manque de documentation bas-niveau, la PSX est une machine redoutable à émuler : presque 20 ans d’existence et aucun émulateur vraiment convainquant. 

La plupart sont plus des émulateurs de jeux PSX que de véritable émulateur Playstation.

 

Actuellement le meilleur émulateur pour moi est XEBRA. En tout cas pour tester des programmes que vous écrirez en bas-niveau, c’est celui qui se rapprochera le plus du vrai fonctionnement du GPU de la PSX (hormis le cache de commande). Il respecte aussi assez bien le timing d’une vrai PSX et tous les jeux que j’ai testés dessus ont fonctionné de façon impressionnante. Il n’est pas parfait en termes d’émulation du SPU ou quelque problème de rendu à l’écran par exemple et peut-être d’autres composants que je connais moins. Il n’empêche je conseille vivement l’utilisation de cet émulateur pour tester nos programmes. Malheureusement, XEBRA ne contient pas de debugger.

 

Les émulateurs populaires à base de plugins (Psxe, Psxseven etc) dépendent surtout de la qualité du plug-in et en termes de GPU, ils sont tous inferieurs à XEBRA. Mais ils permettent le chargement direct d’exécutable ce qui nous évite la génération de l’image du CD. Bien qu’ils émulent assez bien la plupart des jeux, comme pour PSx1.13 ils pardonnent beaucoup trop les erreurs de programmations.

 

PSx1.13 est une autre alternative avec son debugger qui peut breaker lors d’un changement d’état de mémoire mais il pardonne beaucoup trop les erreurs de programmation et ne semble absolument pas émuler ni timing ni le cache du GPU et le son SPU est un peu bizarre. Dommage car en termes de GUI c’est mon préféré.

 

C’est pourquoi je vous conseille l’utilisation de NoCash-PSX.  Il contient un bon debugger capable de breaker sur une addresse et sur des changement de mémoire avec en plus la possibilité de modifier le code en direct. Il est capable d’executer directement un executable sans passer par generation de l’image CD.

De plus l’auteur propose même une documentation de la PSX la plus complète qui soit actuellement trouvable sur le net. La description de certains registres, dans cet article, sont des copie/colle de celle-ci. Même si il est loin d’etre parfait en terme d’émulation (GPU cache commande, read delay et autres), l’auteur semble travailler encore dessus donc on peut espérer des améliorations très prochainement. Et pour le développement c’est certainement l’émulateur idéal.

http://problemkaputt.de/psx.htm

 

Mais encore une fois ne vous fiez surtout pas à un emulateur : dans tous les cas il faudra tester nos programmes sur une vrai PSX pour être certain de leur fonctionnement et de nombreuses surprises (souvent extrement frustrante) peuvent vous attendre.

 

Nous somme en 2013 à l’heure ou j’ecris ce paragraphe et il est fort possible que la situation ait quelque peu changee…

 

 

3-    MIPS R3000

 

Avant de commencer il faut, bien entendu, se familiariser avec l’assembleur du MIPS R3000.

 

Voici un lien pour tout savoir sur le R3000 ainsi que son jeu d’instructions:

http://cgi.cse.unsw.edu.au/~cs3231/doc/R3000.pdf

Et un excellent lien en français :

http://cgi.cse.unsw.edu.au/~cs3231/doc/R3000.pdf

 

Je  me contenterais de faire un petit résumé rapide et suffisant pour comprendre tous les listings qui suivront et en insistant sur les spécificités du R3000A de la PSX.

 

Processeur RISC de 32 bits architecture MIPS I. 32 Registres de 32 bits (r0 jusqu’à r31). En standard, il est équipé de deux coprocesseurs arithmétiques le Cop1 et Cop2.

La PlayStation utilise une version modifiée de ce processeur avec un cache d’instructions de 4Ko et un pseudo cache de données (ScrachPad) de 1Ko. Cadencé à 33MHZ et amputé du cop1 et munis d’un cop2 spécifique : le GTE. Le Bus de données est bien sur de 32 bits. Nous verrons plus tard les mécanismes du cache I ainsi que des tampons R/W.

 

 

L’OS de la PSX et la plupart des assembleurs utilisent les syntaxes suivantes pour les registres :

Register number

Name

Usage

R0

$ZR

Constant Zero

R1

$AT

Reserved for the assembler

R2-R3

$V0-$V1

Values for results and expression evaluation

R4-R7

$A0-$A3

Arguments

R8-R15

$T0-$T7

Temporaries (not preserved across call)

R16-R23

$S0-$S7

Saved (preserved across call)

R24-R25

$T8-$T9

More temporaries (not preserved across call)

R26-R27

$K0-$K1

Reserved for OS Kernel

R28

$GP

Global Pointer

R29

$SP

Stack Pointer

R30

$FP

Frame Pointer

R31

$RA

Return address (set by function call)

 

REMARQUES DIVERSES :

-          Un mot ou WORD désigne 32 bits ! Même sur les x86 actuelle, on continue d’appeler un mot 16 bits et un double-mot (DWORD) 32 bits héritage 16 bits des x86.

-          Et donc un demi-mot 16 bits (HALF).

-          La programmation du R3000 est clairement orienté registre.

-          Un seul mode d’adressage disponible : Off(Reg) où Reg est l’adresse de base représenté par un registre et Off est l’offset signé sur 16 bits.

-          Les adresses doivent toujours être multiples de 4 pour être adressées exceptée pour les instructions lb/h/w et sb/h/w qui acceptent des adresses non alignées avec une pénalité de non alignement.  

-          Pipeline de 5 niveaux (P-E-A-M-W) mais les « Interlocks » (comme les multiplication ou divisions) peuvent bloquer le pipeline.

-          Le processeur est Little-Endian pour la Playstation même si théoriquement il peut être configuré en Big-Endian.

-          Le Registre 0 n’est accessible qu’en lecture avec toujours une valeur de 0.

-          Le registre R31 est le registre de l’adresse de retour d’un appel par JAL.

-          Le Registre 28 GP est utilisé par les compilateurs pour pointer au milieu d’une zone statique de mémoire (section BSS/DATA).

-          Le Registre 30 FP est utilisé pour stocke les variables de sous-routines par les compilateurs (modèle STACK-FRAME).

-          Pas de gestion de pile explicite, même les appelles avec retour se font sur les registre (en général le R31). Le registre 29 SP est utilisé comme pile par les compilateurs et l’OS de la PSX.

-          Une unité de calcul de multiplication et division entier indépendant. 

-          Apres un branchement il y a un délai d’un cycle et l’instruction suivante est toujours exécutée: insérer une instruction utile et si vous n’avez aucune instruction indépendante insérer un NOP.

-          Apres une lecture de mémoire il y a un délai d’un cycle à cause de la nature du pipeline, insérer une instruction (utile) avant d’interpréter le résultat !

-          RISC oblige, le code en assembleur devient très vite indigeste. C’est pourquoi il est d’usage d’utiliser des pseudo-instructions (LI, LA, SUBI, NOP,MOV) étendues pour englober plusieurs instructions de base ou bien traduire l’instruction avec un memo plus compréhensible. Mais je n’utiliserai pas les Pseudo-Instructions (hormis NOP) dans cette section car je veux vous faire sentir le RISC du R3000.

 

Nous allons juste passez en revue les bases pour comprendre les listings de cette première partie.

 

La syntaxe des  listings qui suivront est spécifique à mon assembleur. J’utilise la syntaxe officiel du MIPS : hexadécimale avec ‘0x’ commentaire avec ‘;’ label se terminant toujours avec ‘:’ En revanche, j’incorpore un system de souslabel qui commence par le caractere ‘.’. La syntaxe des macros, commandes d’insertion et include sont différentes. Les directives .reorder ne sont pas incluses. Les registre peuvent être indifféremment représenter par R8,$8,$T8,T8.

 

Copie de valeurs immédiates dans un registre

L’instruction LUI charge dans les demi-mots de poids fort du registre la valeur immédiate sur 16 bits en argument. Le demi-mot de poids faible est réinitialisé à 0.

            LUI R8,0xAAAA               #Charge dans le Registre R8=0xAAAA 0000

 

Les instructions ADDI, ADDIU, ORI, ANDI et XORI utilisent toujours comme argument RT, RS, IM.

RT est le registre de destination, RS est le registre source et IM est la valeur immédiate sur 16 bits. L’opération s’effectue toujours ainsi : RT=RS op IM

ADDI travaille en 16 bits signés, ADDIU travaille en non signés.

En combinant l’instruction LUI et ces dernières nous pouvons écrire dans un registre n’importe quelle valeur.

            ORI R8,R0,0x0000           ; R8=0x0000 00000

                LUI R8,0xCCAA               ; R8=0xCCAA 0000

                ORI R8,R8,0xDDEE         ; R8=0xCCAA DDEE

LUI R8,0x0001                  ; R8=0x00001 0000

ADDIU R8,R8,0x0400      ; R8=0x00001 0400

ADDIU R8,R0,0x0010      ; R8=0x0000 0010

ADDI R8,R8,0xFFFF       ; R8=0x0000 000F Decremente R8

ADDI R8,R0,0xFFFF       ; R8=0xFFFF FFFF

 

 

Copie et opérations de registre à registre :

Les instructions AND, OR, XOR, ADD, ADDU, SUB permettent, en outre les opérations traditionnelles, de copier les registre entre eux.

La syntaxe est OP RD,RS,RT ou l’instruction est exécuté de la façon suivante : RD=RS OP RT         

ADD R8,R9,R10                 ; R8=R9+R10

SUB R8,R9,R10                 ; R8=R9-R10

ADDU R8,R9,R0                ; R8=R9

ADDU R8,R0,R9                 ; R8=R9

AND R8,R9,R10                 ; R8=R9&R10

OR R8,R9,R0                      ; R8=R9

 

Incrémentation et décrémentation :

On utilise ADDI dans les deux cas la valeur immédiate est interprété en tant que 16 bits signée et donc :

            Addi R8,R8,0x0001          ; R8++

                Addi R8,R8,0xFFFF         ; R8—

                Addi R8,R8,4                      ;R8+=4

                etc

                                  

Pseudo-Instruction NOP:

L’instruction NOP qui ne fait rien et consomme 1 cycle est en fait l’instruction SLL R0,R0,0 code par 0x0000 0000.

 

Adressage Mémoire 

Il n’y a qu’un seul mode d’adressage de type Off(RS)RS est l’adresse de base représenté par un registre et Off et l’offset signé sur 16 bits

Pour lire ou écrire dans une case mémoire on utilise les instructions :

-          LW LH LB respectivement lecture en mémoire d’un mot, d’un demi-mot et d’un octet avec extension de signe

-          LHU LBU respectivement lecture en mémoire d’un demi-mot et d’un octet non signé.

-          SW SH SB respectivement écriture en mémoire d’un mot, d’un demi-mot et d’un octet.

La syntaxe est OP RT,IM(RS) ou RT est le registre de destination et RS le registre d’adresse mémoire. IM l’offset sur 16 bits signées dans la mémoire.

L’alignement de l’adresse est important en fonction du type lu/écrit. L’adresse pour LW/SW doit-être aligne sur 4 octets, LH/LHU/SH sur 2 octets. Sinon le processeur part en exception.

ATTENTION, après une lecture de mémoire il y a un délai d’un cycle. Si nous devons nous servir du registre RT juste après, nous devons insérer un NOP avant.

 

Lecture en Mémoire :

            lw rd,Offset(rs)   ; charge dans rd la valeur pointée par rs+offset

                nop                        ; délai de lecture

 

ATTENTION ! Apres une lecture en mémoire, il y a 1 délai d’une instruction et rd ne peut être interpréter que durant le prochain cycle. La plupart des émulateurs ne prennent pas en compte ce délai (hormis Xebra)

 

Ecriture en Mémoire :

            Sw rd,Offset(rs)  ; écriture de rd dans la mémoire pointée par rs+offset

 

Exemples :

lui R4,0x1001    

ori R4,R4,0x0200              ; R4 pointe vers 0x1001 0200

lw R8,0(R4)                         ; Copie dans R8 la valeur qui se trouve à l’adresse 0x1001 0200

nop                                        ; Délai de lecture ici superflus

lw R9,4(R4)                         ; Copie dans R9 la valeur qui se trouve à l’adresse 0x1001 0204

addi R8,R8,1                       ; Increment R8

addi R9,R9,-1                     ; Décrémente R9

sw R8,0(R4)                         ; Ecriture de la valeur de R8 à l’adresse 0x1001 0200

                sw R9,4(R4)                         ; Ecriture de la valeur de R9 à l’adresse 0x1001 0204

 

 

Registre Global GP

L’offset de l’adressage mémoire permet d’atteindre -32KO a +32KO. Pour cette bonne raison, il est d’usage d’initialiser le registre R28 (GP) à l’adresse du milieu de notre code. De cette façon, une variable de notre code est seulement définit par son offset et on peut alors simplifier les accès en mémoire tout en économisant 2 instructions et 2 cycles:

sw R8,OFFSETGP VAR(GP)

Au lieu de

lui R9,VAR+1

ori R9,R9,VAR

sw R8,0(R9)

 

L’idéal est évidement de posséder un segment DATA où seront contenue nos variables et initialiser le GP par DATA + 32768. 

Les compilateurs utilisent de la même façon du registre R30 (FP) pour implémenter le modèle de la « stack-frame » et l’accession aux variables locales par offset lors dans une fonction.

 

 

Opération de décalage de bits 

Les instructions décalage logique SLL et SLR ont pour syntaxes: OP RD,RT,SH

Ou nous avons RD = RT OP SH

 

            ORI R8,R0,0x0180                           ; Charge R8 avec la valeur 0x0180

                SLL R8,R8,1                                      ; R8=R8<<1=0x0300

                SLL R9,R8,2                                      ; R9=R8<<2=0x0C00

                SLL R10,R9,16                                  ; R10=R9<<16=0x0C00 0000

            etc

Attention, le jeu d’instruction du R3000 ne contient pas de rotation de bits.

 

           

Pile

Pas de pile explicite nous devons procéder manuellement: Par convention, le registre R29 est utilisé comme pile par les compilateurs.

;PUSH

ADDI R29,R29,0xFFFC                  ; R29=R29-4       

SW Rn,0(R29)                                    ; PUSH Rn

               

;POP

 LW Rn,0(R29)                                  ; POP RN

ADDI R29,R29,0x0004                    ; R29=R29+4

 

PILE PSX: ATTENTION!!!

Certaines fonctions du BIOS sont boguer et détruise les premiers éléments de la pile.

Pour éviter de mauvaise surprise je fais comme ça :

;PUSH

ADDI R29,R29,-32                            ; R29=R29-32    

SW Rn,4(R29)                                    ; PUSH Rn

;Etc…

               

;POP

 LW Rn,4(R29)                                  ; POP RN

;etc…

ADDI R29,R29,32                             ; R29=R29+32

 

De plus si vous voulez quitter correctement un programme il est impérative de sauver les registre non pas dans la pile mais dans un espace statique de votre programme.

Beaucoup de fonctions du BIOS détruisent non seulement le contenu, mais le pointeur de pile également !!!

 

Branchement conditionnel

 

ATTENTION, L’INSTRUCTION SUIVIT D’UN BRANCHEMENT EST TOUJOURS EXECUTE !

INSERER DONC UNE INSTRUCTION UTILE OU BIEN UN NOP.

 

Les instruction BEQ et BNE utilisent la syntaxe : BEQ RS,RT,OFFSET et effectuent le branchement si RS=RT et RS !=RT respectivement.

                Ori R8,R0,4                        ; R8=4

.Boucle :

                ……………..

Addi R8,R8,-1                     ; R8--

                Bne R8,R0,.Boucle            ; Si R8 !=0 branche

                Nop                                       ; Délai de branchement

.Suite :

                ………………….

 

Les autres instructions BGEZ, BGTZ, BLEZ, BLTZ utilisent la syntaxe BGEZ RS,OFFSET ou RS est toujours comparé à la valeur 0.

BGEZ branche si RS est supérieur ou égale à 0

BGTZ branche si RS est strictement supérieur à 0

BLEZ branche si RS est inférieur ou égale à 0

BLTZ  branche si RS est strictement inférieur à 0

            Ori R8,R0,4                        ; R8=4

.Boucle :

                ……………..                      

                Addi R8,R8,-1                     ; R8--

                Bgtz R8,.Boucle                 ; Si R8 >0 branche

                Nop                                       ; Délai de branchement

.Suite :

                ………………….

 

L’offset est toujours implicitement multiplié par 4, les branchements sont donc limité à 256 KO (-128KO; +127 KO) dans le code.

           

Branchement inconditionnel

L’instruction J adresse effectue un saut direct. L’adresse est codée sur 26 bits multipliée par 4. On peut donc atteindre jusqu’à 256 Mo. Pour la Playstation c’est beaucoup plus qu’il n’en faut.

 

Appel de routine

En général, une routine s’appelle via un JAL adresse.  L’instruction JAL adresse sauve l’adresse d’appelle dans le registre R31 et effectue le branchement (toujours avec un délai d’une instruction).

La routine doit donc contenir JR R31 pour revenir à l’adresse d’appel.

Il est également possible d’utiliser l’instruction JAL Reg,Adresse pour préciser le registre dans lequel nous voulons sauvez l’adresse d’appel. La routine devra donc contenir l’instruction JR Reg approprié.

 

            ;Appel de ma routine

JAL MaRoutine

NOP

………………………

               

MaRoutine:        

JR R31                 ; R29=R29-4

NOP      

 

Comme nous le voyons, l’architecture ne nous impose rien de particulier c’est aux développeurs de l’organiser.

Mais bien sur des conventions existent et ce sont celles qui sont données dans le tableau avant qui sont appliquées pour la Playstation plus celle que je donnerai.

Exemple :

MaRoutine:

ADDI R29,R29,0xFFFC                  ; R29=R29-4       

SW R31,0(R29)                                  ; PUSH Rn

                …………………………………

 LW R31,0(R29)                                ; POP RN

JR R31                                                 ; RET

ADDI R29,R29,0x0004                    ; R29=R29+4

 

 

           

Multiplication et division

L’unité de calcul est indépendante.

Les instructions MUL DIV se servent de registres spéciaux LO et HO

            multu R16,R17                                                  ;R16*R17

                mflo R18                                                             ;=R18

Ces instructions possèdent plusieurs cycles d’exécution et il important d’insérer plus instruction indépendante avant d’utiliser MFLO ou MFHO pour ne pas bloquer le pipeline.           

 

Instruction système :

            SYSCALL déclenche une interruption pour la PSX l’exception permet de passer en mode superviseur.

 

Coprocesseur :

Le R3000A contient 2 coprocesseurs le COP0(System) et le COP2 (GTE).

Chaque coprocesseur contient en théories ses registres spécifiques 32 registres de control et 32 registres data.

Pour la Playstation, le COP0 ne contient qu’un certain nombre de registres de contrôle (Le 12,13 et 14) accessible par les instructions MFC0 et MTC0.

Le COP1 n’est pas présent. Et le COP2 est le processeur de calcul vectoriel le GTE que nous verrons plus tard.

 

 

Pseudo-Instruction 

Du code assembleur RISC devient très vite indigeste donc il est d’usage d’utiliser des pseudo-instructions qui peuvent englober plusieurs instructions ou bien rendre une instruction plus compréhensible :

Par exemple :

LI R8,0x18001F00 remplace LUI R8,0x1800 , ORI R8,R8,0x1F00.

MOV R8,R7 remplace OR R8,R7,R0

SUBI R8,R8,1 remplace ADDI R8,R8,-1

Mais les listings de cette section n’utiliseront que rarement ces pseudo-instructions.

 

 

Quelques conventions appliquées :

Les listings qui suivront distinguent 2 types de fonction :

-          Les fonctions minimales qui n’affectent seulement les registres R2 et R3. Les fonctions de bases InitGPU, WaitGPUReady, InitSPU etc n’affectent que ces registres.

-          Les autres fonctions qui affectent les autres registres temporaires également.

-          Le registre R27 ($K0) pointera toujours sur la base des registres hardware c’est-à-dire l’adresse 0x1F80 0000

-          On ne se sert que de R31 ($RA) comme registre de link et donc seule l’instruction JAL sera utilisée comme appel de routine.

-          Si une routine se sert des registre R16-R23 ($S0-$S7), elle se doit de ne pas les affecter et donc de les sauvés en Pile pour les restaurée avant un retour.

-          Une Routine peut utiliser le registre FP est emprunter à la Pile un espace pour les variables locales (Stack-Frame model).

                                                                                                                                                                                

Remarque :     Pour plus de clarté, les optimisations même bateau sont évités au début, mais plus on avancera plus le code présenté sera optimisé. 

 

 

4-    Organisation Mémoire

 

 

Même si les spécifications MIPS du R3000 prévoient la possibilité de virtualiser la mémoire, celle-ci n’est pas implémenter dans le R3000A de la PSX.

Sur la Playstation la mémoire est mappée de cette façon par segment:

La zone 0x00000000-0x001FFFFF correspond à la mémoire réelle accessible seulement par les processus en mode Superviseur.

La zone 0x80000000-0x801FFFFF correspond à la même zone physique mais accessible par les processus en mode utilisateur.

La zone I/O (registres des chips spécialisées) est dans la zone : 0x 1F801000 – 0x1F801FFF.

 

D-Cache :

Une zone mérite une attention particulière, c’est la zone 0x1f800000~0x1f8003ff. Celle-ci pointe sur le D-Cache de 1KO (SRAM interne du processeur) où le processeur lit et écrits des mots en 1 cycle contre 5/6 cycles pour la RAM normal. Cette zone n’est évidemment pas accessible par la DMA.

 

Memory Map

00000000 - 001FFFFF (2mb)                RAM segment for System

1F000000 - 1F7FFFFF (up to 8mb)          adaptor rom shadow (action replay)

1F800000 – 1F8003FF (1Kb)                D-Cache ou ScratchPAD.

1F801000 - 1F801xxx (8kb)                hardware i/o map: gpu, pads, memcard, pio port, sio port

80000000 - 801FFFFF (2mb)                RAM segment for user
80000000 - 8000FFFF (64kb)        
System Area

            80010000 – 801FFFFF (2Mb-64Kb)     User Area (Code, Heap et stack)

bfc00000 - bfc7ffff (512kb)                            System ROM (BIOS)

 

Pour l’instant nous devons simplement prendre en compte que nos programmes utiliseront l’espace 0x8001 0000-0x81FF FFFF.

L’OS de la PX réservé les 64 premiers Ko (0x8000 0000 0x8000 FFFF). Le reste de la mémoire est à notre disposition.

Si vous voulez insérer vos programmes dans un CD démo officielle, charger vos exécutables à l’adresse 0x80018000.

 

 

5-    Cache, Timing et Optimisation

Si vous êtes presser sauter cette partie, vous y reviendrez tranquillement plus tard.

 

Les éléments à prendre en compte sont :

-          le pipeline.

-          Le cache I

-          Le pseudo D cache : scratch PAD

-          Les tampons FIFO : 4 octets pour la lecture et 4 tampons de 4 octets pour l’écriture.

-          Les instructions Interlock

 

Le R3000 est doté (selon les spécifications) d’un pipeline de 5 niveaux (IF/RD/ALU/MEM/WB).

IF :                  Instruction Fetch

RD :                Read argument from register and decode instruction

ALU :             Perform operation (Arithmetics/Shift/Boolean/Adress Calculation)

MEM :            Access Memory (Data cache) if sw instruction.

WB :               Write back ALU Memory and register

 

L’une des forces de l’implémentation MIPS du R3000 est le mécanisme appelé ‘Bypass’ qui fait que la modification d’un registre par l’ALU ou bien un acces mémoire dans MEM est prise en compte même si le résultat de l’opération n’est pas physiquement écrit dans le registre. Le Bypass est implémentée dans le RD qui est rafraichit par le bypass dans ALU et dans MEM.

L’autre élément à prendre en compte et qu’en réalité RD possède également une ALU pour les branchements. Voilà pourquoi seulement une instruction est toujours exécutée après une instruction de branchement et non pas deux.

 

Le délai de lecture s’explique par la nature du pipeline et de l’emplacement des bypass. Regardez :

 

LW $8,0($4)

ADD $8,$8,1

 

Cycle/Pipelines

IF

RD

ALU

MEM

WB

Commentaire

1

LW R8,0(R4)

 

 

 

 

 

2

ADD R8,R8,1

LW R8,0(R4)

 

 

 

 

3

-

ADD R8,R8,1

LW R8,0(R4)

 

 

R8 toujours pas disponible

4

-

-

ADD R8,R8,1

LW R8,0(R4)

 

R8 disponible mais trop tard car ADD est dans L’ALU


                                                                                 

LW R8,0(R4)

NOP

ADD R8,R8,1

 

Cycle/Pipelines

IF

RD

ALU

MEM

WB

Commentaires

1

LW R8,0(R4)

 

 

 

 

 

2

NOP

LW R8,0(R4)

 

 

 

 

3

ADD R8,R8,1

NOP

LW R8,0(R4)

 

 

R8 Disponible

4

 

ADD R8,R8,1

NOP

LW R8,0(R4)

 

ADD incrémente R8 correctement

5

 

 

ADD R8,R8,1

NOP

LW R8,0(R4)

 

 

 

Délai de lecture et branchement :

 

Rappelez-vous que le branchement est effectué dans RD.

Supposons que 0(R4) soit égale à 0 et  R2 à 1

 

LBU R2,0(R4)

BEQ R2,R0,@L

ADDI R2,R2,1

@L:

OR R3,R2,R0

 

CYCLE/PIP

IF

RD

ALU

MEM

WB

Commentaires

1

LBU R2,0(R4)

 

 

 

 

 

R2=1

2

BEQ R2,R0,@L

 

LBU R2,0(R4)

 

 

 

 

R2=1

3

ADDI R2,R2,1

 

BEQ R2,R0,@L

 

LBU R2,0(R4)

 

 

 

R2=1

Non branch

4

OR R3,R2,R0

ADDI R2,R2,1

 

BEQ R2,R0,@L

 

LBU R2,0(R4)

 

 

R2=0

5

 

OR R3,R2,R0

ADDI R2,R2,1

 

BEQ R2,R0,@L

 

LBU R2,0(R4)

 

R2+=1 = 1

6

 

 

OR R3,R2,R0

ADDI R2,R2,1

BEQ R2,R0,@L

 

R3=R2=1


 

LBU R2,0(R4)

NOP

BEQ R2,R0,@L

ADDI R2,R2,1

ADDI R2,R2,2

@L:

OR R3,R2,R0

 

CYCLE/PIP

IF

RD

ALU

MEM

WB

Commentaires

1

LBU R2,0(R4)

 

 

 

 

 

R2=1

2

NOP

 

LBU R2,0(R4)

 

 

 

 

R2=1

3

BEQ R2,R0,@L

 

NOP

 

LBU R2,0(R4)

 

 

 

R2=0

 

4

ADDI R2,R2,1

 

BEQ R2,R0,@L

 

NOP

 

LBU R2,0(R4)

 

 

R2=0

Branch

5

ADDI R2,R2,2

OR R3,R2,R0

ADDI R2,R2,1

 

BEQ R2,R0,@L

 

 

 

R2=0

 

6

 

OR R3,R2,R0

ADDI R2,R2,1

 

 

 

R3=R2=1

7

 

 

OR R3,R2,R0

 

 

 


 

           

Mais comme toujours nous devons nous méfier des mangeurs de cycles suivants :

-          Data Cache non prêt (pour la PSX cette situation n’existe pas car le Cache D est fixée dans une adresse constante)

-          Cache I non prêt

-          Bus mémoire occupé (Lecture/écriture/Dma)

-          Instructions mflo et mfho

-          Lecture mémoire qui prendra toujours 5 cycles si le buffer n’est pas prêt + pénalité si le buffer Write n’est pas vide.

-          Ecriture en mémoire si les buffers non prêts.

-          Ecriture ou lecture sur des adresses non alignées (exemple Lh/lb sh/sb).

 

La lecture et l’écriture en mémoire sont évidemment les points ou l’on rencontrera les pertes de cycles.

Le R3000a de la PSX implémente un tampon d’écriture FIFO de 4 mots et un tampon de lecture d’un mot Buffer R.

Le bus mémoire charge tout d’abord ce tampon lors d’une lecture ce qui prend 5 cycles.

L’écriture est dotée de 4 tampons de 4 octets Buffer W. Le chargement effective dans la mémoire prend alors 4 cycles mets si on s’arrange pour aligner les écritures par 4 mots on va minimise les effets de ces pertes de cycles car l’écriture dans le tampon ne prend que 1 cycles et le chargement aligné de 4 word ne prendra que 10 cycles.

Le Buffer W aura toujours la priorité sur le Buffer R ! Une lecture provoque automatiquement le vidage du Buffer W et donc la « consommation » effective des écritures.

 

Un point obscur reste l’arbitrage du bus mémoire. Par exemple lorsque les canaux DMA vont le réquisitionner, je n’ai pour l’instant aucune idée comment cela se passe, la documentation de Sony n’en parle pas.  D’après le « Service Manual », l’arbitre se situe dans le R3000A et doit certainement jouer sur les différentes vitesses des composants (Le CPU est cadencée à 33,86MHZ, le GTE à 67 MHZ et le GPU à 53MHZ le SPU à 4MHZ) afin d’éviter les inter-blocages du bus.

 

Un deuxième point délicat reste le fonctionnement du Cache I. Le R3000A utilise un algorithme de « Direct Mapping » pour la gestion du cache I.

Le cache I contient 256 lignes de 16 octets (soit 4 instructions). Chaque ligne contient un champ (un Tag) d’information. Le Tag indique une instruction dans la ligne a déjà été lue et indique les 20 bits supérieur de l’adresse source de la RAM.

Lorsque une instruction est pré-chargé, les bits 5-12 de l’adresse sont utilisé pour spécifier une ligne unique celle-ci étant adressée en interne par 8 bits. Le CPU va vérifier le Tag de la ligne pour vérifier si l’instruction s’y trouve. Si l’instruction se trouve dans le cache le pré-chargement (IF) ne prend qu’un cycle. Sinon le CPU va lire la ligne correspondante de la RAM la pénalité sera fonction de l’alignement de l’instruction dans la ligne. Si l’instruction est la première sur la ligne il n’y a que 4 cycles de pénalité, si c’est la deuxième 5 cycles si c’est la troisième 6 cycles et si c’est la dernière 7 cycles. Il y aura donc de 4 à 7 cycles de pénalité pour charger l’instruction dans le Buffer R. Enfin 1 cycle supplémentaire est nécessaire pour charger la ligne.

 

Voilà pourquoi a priori nous devons alignée notre code pour les séquences dans une boucle par exemple afin de minimiser les ratés du cache (CACHE-MISS).

 

Comme nous le voyons, l’ordre du code et son alignement des blocs de codes pour le cache, l’utilisation intensifs des registres et du scratch-PAD sont les clefs de voutes d’un code optimisé pour notre Playstation.

 

6-    PSX EXE et Premiers programmes (enfin !)

 

Entrons dans le vif du sujet. Comment faire booter un programme sur notre PSX. C’est très simple, notre CD doit contenir un exécutable et un fichier texte appelé SYSTEM.CNF indiquant le nom de l’exécutable que l’OS doit lancer après un boot.

Examinons tout d’abord le format de l’exécutable.

 

Executable PSX Structure :

typedef struct _EXE_HEADER_ {

            unsigned byte id[8];

            unsigned long text;                            // 8

            unsigned data;                                   //12

            unsigned long pc0;                             //16

            unsigned long gp0;                            //20

            unsigned long t_addr;                        //24

            unsigned long t_size;                         //28

            unsigned long d_addr;                       //32

            unsigned long d_size;                        //36

            unsigned long b_addr;                       //40

            unsigned long b_size;                        //44

            unsigned long s_addr;                       //48

            unsigned long s_size;                         //52

            unsigned long sp,fp,gp,ret,base;        //56,60,64,68,72

} EXE_HEADER;

ATTENTION !!! LITTLE EDIAN !!!

 

Nous n’utiliserons pas de segment DATA et autre BSS. Seul le segment TEXT sera utilisé. La taille sera fonction de notre fichier BIN (c.à.d. notre code machine) à « Linker ».

Si vous voulez que votre code puisse se lancer du « Laucher Demo » officiel. Fait démarrer votre code en 0x80018000 voir l’annexe.

Dans nos exemples, nous chargerons à l’adresse 0x8001 0000 mais les autres programmes démarreront à 0x80018000.

 

Exemple:

0x0000            50 53 2D 58 20 45 58 45 00 00 00 00 00 00 00 00

0x0010            yy yy yy yy gg gg gg gg zz zz zz zz xx xx xx xx                

0x0020            00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x0030            pp pp pp pp 00 00 00 00 00 00 00 00 00 00 00 00

0x0040            00 00 00 00 00 00 00 00 00 00 00 00 53 6F 6E 79

0x0050            20 43 6F 6D 70 75 74 65 72 20 45 6E 74 65 72 74

0x0060            61 69 6E 6D 65 6E 74 20 49 6E 63 2E 20 66 6F 72

0x0080            20 45 75 72 6F 70 65 20 61 72 65 61 00 00 00 00

............            00

0x07F0           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x0800            Début du code/données.

 

*          zz zz zz zz       Adresse du segment .text      exemple

*          xx xx xx xx     Taille du segment .text           exemple 00 48 04 00 taille = 0x0004 4800

*          yy yy yy yy     Register PC                            exemple 78 BE 02 80 correspond à l’adresse 80 02 BE 78 Le code démarre à cette adresse.

*          pp pp pp pp     Adresse de la Pile                  exemple F0 FF 1F 80 La pile commence donc en 80 1F FF F0.

*          gg gg gg gg     Registre GP                            exemple 00 00 80 80 La valeur sera du GP sera donc de 80 80 00 00

 

L’adresse 0x4C contient la chaine « Sony Computer Entertainment » etc. Cette chaine n’est pas indispensable. Ma Playstation exécute les exécutables prive de cette chaine sans problème.

La partie « objet (à partir de l’adresse 0x800 notre code/donnees) est chargé en zz zz zz zz.  Le PC indique ou se situe le Start de notre code.

Pour l’instant, le plus important pour nous est de faire démarrer notre code pour commencer à tester notre PSX

 

Et le plus simple est de faire comme ceci :

PC0= 0x80010000

SEG=0x80010000

Sur notre assembleur/Linker, on fera toujours :

org 0x8001 0000              

                Start:

 

Taille= toujours multiple de 2048 sinon la PS refusera de le charger. 

Pour l’instant cette partie ignore le registre GP que j’initialise à 0. Mais lors de la deuxième partie on s’en servira (ainsi que des pseudo-instructions) pour alléger notre code.

 

 

PSX_001_1.asm:

Programme de 4 octets qui ne fait rien et tourne en boucle infinie.

 

            Org 0x80010000                               ; Notre code objet sera toujours chargé à cette adresse.

                Start :

                               Beq $9,$9,Start                 ;Boucle infinie=0x1129 FFFF

                               nop

                       

Résultat en détail de l’exécutable :

ADRESSE                DATA                                                                                   COMMENTAIRE

0x0000                       50 53 2D 58 20 45 58 45 00 00 00 00 00 00 00 00              CHAINE “PS-X EXE”

0x0010                       00 00 01 80 00 00 00 00 00 00 01 80 00 20 00 00               0x10: PC (Où démarre notre Code) 0x18 : Segment TEXT 0x1C : Taille du Segment TEXT

0x0020                       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x0030                       F0 FF 1F 80 00 00 00 00 00 00 00 00 00 00 00 00              0x30: Initialisation de la PILE

……….                      00

0x0800                       FF FF 29 11 00 00 00 00 00 00 00 00 00 00 00 00              Notre code 0x1129FFFF => BEQ R9,R9,-1 ;boucle infinie

……….                      00

0x0FF0                       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

 

Rappelez-vous que la PSX travaille en Little-Endian !

Sur notre PSX, il n’y a aucun moyen de savoir si notre objet a bien été chargé et exécuter. L’écran de présentation restant fixe.

Donc on va plutôt tenter d’initialiser l’affichage et vérifier que quelque chose se produise.

 

Deuxieme Test: RESET GPU

On va réinitialiser le GPU ce qui devrait provoquer l’effacement de l’écran de présentation.

Nous verrons un peu plus tard le GPU. Pour l’instant nous devons savoir que pour faire faire des taches au GPU, nous devons lui envoyer des commandes sur 2 ports :

-          Control Register 0x1f80 1814 dans lequel sont envoyées des commandes de configuration.

-          Data Register 0x1f80 1810 dans lequel sont envoyées des commandes de dessins.

 

L’adresse de base des registres I/O est 0x1F80 0000 et on va s’intéresser au registre de contrôle du GPU 0x1814 (mappé en 0x1F80 1814) pour réinitialiser notre GPU.

Control Register 0x1f80 1814           WRITE                 B31-B16 COMMAND          B15-B0               PARAMETERS

Exemple de Commande:

* RESET GPU:     CMD=0x00           PARAM=0x00

Donc pour envoyer la commande RESET GPU, on écrit simplement un mot (4 octets) avec comme valeur 0 à l’adresse 0x1F80 1814.

 

PSX_001_2.asm:

               Org 0x80010000

                    START :

                                         LUI R27, 0x1f80                             ; Le register k1 (R27) pointe sur la base I/O (Registre hardware)  

                                         SW R0,0x1814(R27)                       ; Envoi de la commande 0x0000 0000 dans GPU1 0x1F80 1814

                                         BEQ R0,R0,0xFFFF                       ; boucle infinie

                                         NOP

 

Résultat en détail de l’exécutable :

ADRESSE                DATA                                                                                  COMMENTAIRE

0x0000                       50 53 2D 58 20 45 58 45 00 00 00 00 00 00 00 00              CHAINE “PS-X EXE”

0x0010                       00 00 01 80 00 00 00 00 00 00 01 80 00 20 00 00               0x10: PC (Où démarre notre Code) 0x18 : Segment TEXT 0x1C : Taille du Segment TEXT

0x0020                       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x0030                       F0 FF 1F 80 00 00 00 00 00 00 00 00 00 00 00 00              0x30: Initialisation de la PILE

……….                      00

0x0800                       80 1F 1B 3C 88 31 01 80 FF FF 29 11 00 00 00 00            Le code          

……….                      00

0x0FF0                       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

 

Si l’écran vire au noir après l’écran de licence c’est que notre code a été bien exécuté !

 

Troisième Test: Draw red triangle

On peut encore essayer une variante plus intéressante pour être absolument certain que nous faisons bien exécuter notre code sur la PSX. Nous allons afficher un triangle rouge sans réinitialiser le GPU.

Par défaut, la PSX démarre avec un GPU configuré en 640x480. Nous verrons dans la section suivante les commandes du GPU, pour le moment essayons la commande suivante :

 

*Dessiner un triangle:          

MOT 1= 0x20 BB GG RR

MOT 2=  Y0 X0

MOT 3 = Y1 X1

MOT 4 = Y2 X2                  

Dessine un triangle de couleur RGB et de sommet S0(X0,Y0),S1(X1,Y1),S2(Y2,X2).

Les paramètres BB GG et RR représente les composant bleu, vert et rouge code chacun sur 8 bits

Nous envoyons cette commande dans 0x1F80 1810 le registre de données (GPU0) du GPU.

 

PSX_001_3.asm:

                    Org 0x80010000

                    Start:         

lui R27,0x1F80                                         ;R27 pointe vers I/O BASE

                    ;Dessine un triangle rouge avec les coordonnées suivante (20,400) (320,20) (620,400)

lui R8,0x2000                                            ;Commande GPU DRAW TRIANGLE 0x20 BB GG RR où RGB désigne les couleurs RVB chacune codée sur 8 bits.                                                    

ori R8,R8,0x00FF                                    ;RED TRIANGLE

sw R8,0x1810(R27)                                  ;SEND CMD TO GPU 0x1F80 1810

lui R8,400                                                   ;Y0=400

ori R8,R8,20                                              ;X0=20

sw R8,0x1810(R27)                                  ;SEND PARAM TO GPU 0x1F80 1810

lui R8,20                                                     ;Y1=20

ori R8,R8,320                                            ;X1=320

sw R8,0x1810(R27)                                  ;SEND PARAM TO GPU 0x1F80 1810

lui R8,400                                                   ;Y2=400

ori R8,R8,620                                            ;X2=620

sw R8,0x1810(R27)                                  ;SEND PARAM TO GPU 0x1F80 1810

                    ;END DRAW TRIANGLE

beq r0,r0,0xFFFF                                     ;boucle infinie

nop

 

 Résultat en détail de l’exécutable :

ADRESSE                DATA                                                                                  COMMENTAIRE

0x0000                       50 53 2D 58 20 45 58 45 00 00 00 00 00 00 00 00              CHAINE “PS-X EXE”

0x0010                       00 00 01 80 00 00 00 00 00 00 01 80 00 08 00 00               0x10: PC (Où démarre notre Code) ; 0x14 : GP ; 0x18 : Segment TEXT ; 0x08: Taille du Segment TEXT ;

0x0020                       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x0030                       F0 FF 1F 80 00 00 00 00 00 00 00 00 00 00 00 00              0x30: Initialisation de la PILE

……….                      00

0x0800                       80 1F 1B 3C 00 20 08 3C FF 00 08 35 10 18 68 AF           ; Notre code

0x0810                       90 01 08 3C 14 00 08 35 10 18 68 AF 14 00 08 3C            ; Notre code

0x0820                       40 01 08 35 10 18 68 AF 90 01 08 3C 6C 02 08 35            ; Notre code

0x0830                       10 18 68 AF FF FF 00 10 00 00 00 00 00 00 00 00             ; Notre code

……..                         00

0x0FF0                       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

 

 

Si nous exécutons ce code et que nous voyons apparaitre un triangle rouge (Transparent car affiche une ligne sure deux et scintillant) c’est que nous avons réussi à faire faire exécuter du code à notre Playstation ! J

 

PSX_001_3.exe sous emulateur                                                                        PSX_001_3.exe sous Playstation SCPH-7502

 

Remarque importante:

Attention n’essayer pas d’envoyer d’autres commandes pour l’instant. Le programme fonctionnera si vous n’envoyez qu’une seule commande.

Sur ma PSX les artefacts sont systématiques car pas de synchronisation avec le retour de balayage. Donc si vous envoyer d’autre commande il se peut que vous n’obtiendrai pas l’effet escompté à cause de la limite du cache de commande du GPU et de l’absence de synchronisation verticale.

 

7-    CD/ROM ET BOOT

 

Une fois notre exécutable généré, comment faire pour produire notre CD bootable pour notre PSX. Avant cela il s’agit de contourner le système de protection. Et oui la Playstation est protégée de lecture de tout CD/R. Fort heureusement cette protection est relativement facile à contourner.

 

Le code de protection est composé de 4 bytes ASCI codés sur le WOBBLE du CD/ROM :

« SCEI »                JAPON

« SCEE »               EUROPE

« SCEA »              AMERIQUE

Le code pays de la Playstation est localisé en ROM.

 

Cette protection fait donc deux pierres d’un coup.

Elle empêche la Lecture de CD/ROM dont le code pays est diffèrent de la PSX.

Le lecteur de la Playstation lit le WOBBLE du CD et en fonction de sa localité elle vérifie la conformité en fonction de la chaine.

Les conséquences est qu’il est impossible de

-          Faire démarrer un jeu japonais sur une console européenne ou américaine et vice versa.

-          Un CD-R contiendra évidemment un WOBBLE non conforme : La PSX refusera de booter avec ce CD.

 

Etant donné qu’il nous est impossible (du moins pour le commun des mortels) de modifier le Wobble d’un CD-R il nous faut utiliser d’autres alternatives :

 

-          Une première solution c’est celle que j’utilise est d’installer un ModChip sur sa PSX : celui-ci court-circuite les lectures du WOBBLE et envoie vers le CPU la chaine conforme.

Il existe beaucoup de variante d’installation et qui dépendent également de la version de notre PSX.

J’espère faire un petit tuto la dessus avec un Attiny13 de chez Amtel (la plupart des modchip sont des PIC à 8 broches mais les AVR ont incontestablement l’avantage d’une reprogrammation pratiquement sans limite (10000 réécriture assurée ça donne de la marge) et un simple programmateur USB-asp pour microcontrôleur AVR se trouve pour moins de 5euros).

 

-          Une deuxième solution est d’acheter une cartouche Action Replay (ou se le faire) qui se branche sur le port parallèle de la PSX et qui court-circuite la ROM et qui permet de programmer la PSX plus facilement en chargeant des programmes via carte-mémoire ou bien en court-circuitant la protection.

 

-          Une solution de fortune est la technique de changement de CD : en gros on boot la PSX avec un CD officiel, une fois l’écran de présentation affiché des que le CD ralentit, on change immédiatement le CD officiel par notre CD-R. Une fois fait, le CD se met à tourner vite et ralenti immédiatement remettre le CD-officiel et c’est là que cela devient dure : tout de suite après que l’écran disparaissent, on remet son CD-R. Le programme sur notre CD-R devrait démarrer normalement.

 

-          Enfin une bonne solution est d’utiliser un CD-Demo officiel que l’on dupliquera partiellement et on remplacera les exécutables présents par nos programmes (voir plus loin). C’est la solution pour ceux qui possède une PSX non « modee » et qui ne veulent prendre le risque d’endommager leur console et qui ne veulent pas passez par la technique du swap très contraignante.

 

Toujours est-il, l’utilisation d’un émulateur indispensable pour l’apprentissage mais attention car ceux-ci sont encore très loin du fonctionnement de la vrai PSX : si globalement ils émulent correctement la plupart des jeux ils émuleront en revanche très mal le vrai fonctionnement de la PSX et de surcroît toutes les bêtises que nous pouvons faire en programmant directement dessus  !!!

 

Une fois passez cette protection, il y a parait-il d’autre protections mineurs mais qui, semble-t-il, dépendant de la ROM:

- Licence qui consiste en 37632 octets (les 16 premiers secteurs) (en mode 2, un secteur fait 2352 octets) se présente ainsi qui contient la licence d’utilisation. (Il est facile d’extraire cette licence à partir de n’importe quelle CD.ROM playstation).

- Le Volume doit s’appeler PLAYSTATION sur le secteur de boot (0x9320 Secteur 16) et le nom en 0x9340.

J’avoue ne pas comprendre comment ces protections agissent. Toutes les démos et autres programmes ont très bien fonctionné sans.

Je n’ai pas le courage de vérifier sur quelles ROMs ça marche et sur lesquelles non : j’utilise la licence et basta.

 

Que doit contenir notre CD/ROM pour que la PSX puisse booter ?

Tout simplement notre exécutable (toujours aligné sur 2048 octets) et un fichier texte de format Unix/AMIGA/LINUX (ligne séparé seulement par 0x0A) nommé « SYSTEM.CNF » qui est un fichier text dans lequel nous devons indiquer à l’OS-PSX l’exécutable à booter plus quelques infos supplémentaires :

BOOT = cdrom:\NOTREBOOT.EXE;1

TCB = 4

EVENT = 10

STACK = 801FFF00

 

Je donne le fichier SYSTEM.CNF par défaut. TCB indique le nombre max de thread.

Ici notre exécutable se nomme évidemment « NOTREBOOT.EXE »

Attention ! Veuillez que le nom de l’executable ne depasse pas 10 caracteres (Extention incluse) et utiliser que des caracteres majuscule.

Deuxième chose importante : Lorsque vous gravez vos CD, choisissez obligatoirement le MODE 2 avec pour 2352 octet la taille d’un secteur. La PSX ne gère que ce format.

 

Pour créer le CD/ROM, perso je fais comme ça :

-          J’utilise le pack MODE2Create que vous trouverez ici

-          Le programme BurnAtONCE

 

Avec MODE2CREATE je créer le BIN du CD ou j’insère le fichier SYSTEM.CNF et l’exécutable + éventuellement d’autre données. (Pour que le bin soit au moins égale à 768 ko)

Exemple de commande :

mode2cdmaker  -f "SYSTEM.CNF" -f "MYPSX.EXE" -f "FICHIER.DAT" -o MYPSX

Ensuite je remplace les premiers 16 secteurs du BIN par la licence avec une utilitaire qui s’écrit en 10 minutes.

Une fois le BIN convertit créer je crée le fichier CLUE correspondant en ajoutant éventuellement des pistes musicales et ensuite j’utilise BURNEATONE pour Graver le CD.

Le plus important et que le BIN soit suffisamment volumineux (au moins 768 ko) c’est pourquoi il faut insérer des fichiers superflus dans le bin.

 

Lancement des programmes avec une PSX sans modchip sans disc swap avec un CD démo officiel :

Comme cite plus haut une bonne méthode pour démarrer nos programmes est de disposer d’un CD démo officiel. On effectue une copie complète du BIN du CD. Ensuite avec un programme spécialisé dans l’extraction et la génération de fichier bin de CD on remplace les exécutable par les nôtres. Ensuite on boot avec la démo officiel, une fois que le menu s’affiche, on retire le CD officiel et on le remplace par le nôtre. Et lors du lancement d’une démo, nos exécutables devraient se lancer.

 

Tout d’abord nous devons comprendre comment le CD-ROM en mode 2/From1 est décomposées en secteurs et ces derniers se présente de cette façon :

 

Le Champs Sync contient tout bêtement 12 octets : 00 FF FF FF FF FF FF FF FF FF FF FF

3 octets pour l’adresse du secteur et 1 bit pour le mode. Par exemple le secteur 16 d’un CD playstation contiendra :

00 00 02 00 (pour l’adresse du secteur) et 02 (Pour le mode).

Ensuite viennent les données de 2336 mais sachant qu’un secteur logique de données sera limite a 2048 octets on a en faites :

Mode2/Form1 (CD-XA)

  000h 0Ch  Sync

  00Ch 4    Header (Minute,Second,Sector,Mode=02h)

  010h 4    Sub-Header (File, Channel, Submode AND DFh, Codinginfo)

  014h 4    Copy of Sub-Header

  018h 800h Data (2048 bytes)

  818h 4    EDC (checksum accross [010h..817h])

  81Ch 114h ECC (error correction codes)

C’est à dire que les données proprement dites vont de 0x018 à 0x818.

Mais si on remplace un secteur par d’autres données on devra obligatoirement générer l’EDC et l’ECC. Comme c’est assez complexe et que le sujet de cet article est la programmation de la playstation et non comment un CD-R est formaté en mode2/form1 vous pouvez sauter cette partie : vous prenez l’utilitaire et basta : le plus important pour le lecteur est de pouvoir exécuter du code-machine écrit par lui-même sur une Playstation sans mode-chip.

 

La fiabilite d’un CD-ROM etant ce qu’elle est, il est imperatif d’utiliser, pour chaque secteur, un code correcteur. Un codage Red-Salomon est utilisee et va s’appliquer deux fois consecutivement.

Comme c’est assez technique et que le sujet de cet article est la programmation de la playstation je ne detaillerais pas mais je prevois un article specialement sur le sujet.

 

Tout d’abord il s’agit de lire le Root directory.

Dans le secteur 16, à l’offset 158 (sur 2 octets) se trouve le numéro du secteur du Root.

Dans le root secteur chaque entrée est identifié par :

OFFS

0             short len

2-6          int sector Little Endian

6-9          int sector Big Endian

10-13      int file size Little Endian                     ;A modifier ??

14-17      int File size Big Endian                        ; Amodifier ??

18-24      Date information                                  ;Do not care

25           byte typefile (flags)                              2:dir;0 - > FILE

26           File Unit size                                        0

27           Interleave Gap Size                              0

28-31      Volume Sequence Number                 

32           byte namelen

33           name file (rest of len).

 

Donc il suffit simplement d’identifier un fichier exe suffisamment grand pour le remplacer par le nôtre.

PS : Pour l’instant ca ne marche pas malgre les bon EDC/ECM test et modification du size ?

 

Vous trouverez ici un petit utilitaire écrit en JAVA et qui contient une classe JAVA ZawaCDPatch qui nous permettra entre autre de remplacer un fichier exécutable par le nôtre (sans changer le nom du fichier). Les codes sources étant dispo vous pourrez par la suite le compléter si cela vous chante.

 

 

 

8-    Introduction au GPU

 

La programmation directe du GPU qui semble facile au premier abord peut s’avérer assez délicate si l’on ne fait pas attention à quelques détails importants.

Les plus importants sont :

-          le cache de commande de (64 octets)

-          le retour vertical.

 

Pour éviter les problèmes de cache il vaut mieux utiliser une linked list de dessin et la DMA. Cependant pour l’apprentissage nous utiliserons au début l’envoie de commande direct nous verrons les « linked list » plus tard. Si nous ne surveillons pas le cache et que nous utilisons l’envoi de commande direct en I/O, il nous faudra systématiquement utiliser la fonction WaitGPUReady (que nous verrons tout à l’heure). Le problème avec le retour vertical nous limite dans le nombre de dessin que peut effectuer le GPU durant un inter-frame.

Typiquement en dessin direct par I/O, nous somme limité à un effacement de l’écran + 2 triangles texturés ou bien 6 triangles non texturée.

 

J’insiste sur ces points car aucun émulateur ne simule le vrai comportement du GPU lorsque le cache est plein ou lorsque nous dessinons durant un frame. (Même Xebra).

De plus le GPU semble déclencher des exceptions (Je n’ai pas encore identifié les conditions ni comment contrôler ce genre d’évènement).

Le GPU peut accéder à la mémoire via la DMA ou nous pouvons envoyer un liste d’instruction préparée avant nous verrons cela lors de la section sur la DMA.

De plus il semble y avoir plusieurs versions du GPU les différences semblent être minimes mais j’avoue qu’il m’est impossible de les étudier hormis avec la GPULIB.

Le GPU de ma Playstation me renvoi 0x0000 0002 lorsque j’utilise la commande GetInfo (0x1000 0007).

 

Le GPU possède une VRAM de 1024*512 16 bits. Le processeur ne peut écrire directement dessus.

Les images peuvent être organise en :

               - 16 bits (1 bit de transparence + BGR sur 5 bits chacun).

               - 8 bits (1 octet par pixel ou la valeur de l’octet est un index dans une table de couleur de 256 entrées dans la VRAM)

               - 4 bits (4 pixels par demi-mots ou la valeur de 4 bits est un index dans une table de couleur 16 entrées dans la VRAM)       

 

Les modes d’affichage pour la résolution verticale sont de 240 ou 480. Pour cette dernière valeur, l’entrelacement est activé.

Nous n’aborderons pas ce mode dans cet article car l’impossibilité d’implémenter un double buffer plus les problèmes de dessin qu’elle engendre (celui-ci affiche une ligne paire et impaire alternativement : il faut imperativement passer par DMA) rend ce mode assez delicat (même si la PSX démarre dans ce mode).

 

Pour simplifier, on se bornera ici à configurer le GPU en 320x240.

 

Nous avons vu dans le deuxième et troisième programme de test la façon dont on envoie des commandes vers le GPU pour lui faire réaliser certaines choses.

Les commandes sont de deux types :

Envoyer dans le registre CTRLPORT 0x1814 (que nous appellerons GPU1) ou envoyer dans le registre 0x1810 DATAPORT (que nous appellerons GPU0).

Les commandes du GPU1 sont dédiées à la configuration du GPU, gestion de la DMA, le mode d’affichage et la zone de la VRAM à afficher.

Les commandes du GPU0 sont dédiées au dessin, aux transferts MEM<->VRAM et autre configurations de la zone de dessins etc.

 

La lecture du GPU1 nous permet également d’accéder aux états de celui-ci.

Control Register 0x1f80 1814        READ        (GPU1)

  0-3   Texture page X Base   (N*64)                                           ;GP0(E1h).0-3

  4     Texture page Y Base   (N*256) (ie. 0 or 256)                           ;GP0(E1h).4

  5-6   Semi Transparency     (0=B/2+F/2, 1=B+F, 2=B-F, 3=B+F/4)               ;GP0(E1h).5-6

  7-8   Texture page colors   (0=4bit, 1=8bit, 2=15bit, 3=Reserved)            ;GP0(E1h).7-8

  9     Dither 24bit to 15bit (0=Off/strip LSBs, 1=Dither Enabled)             ;GP0(E1h).9

  10    Drawing to display area (0=Prohibited, 1=Allowed)                      ;GP0(E1h).10

  11    Set Mask-bit when drawing pixels (0=No, 1=Yes/Mask)                    ;GP0(E6h).0

  12    Draw Pixels           (0=Always, 1=Not to Masked areas)                ;GP0(E6h).1

  13    "reserved"            (seems to be always set?)

  14    "Reverseflag"         (0=Normal, 1=Distorted)                          ;GP1(08h).7

  15    Texture Disable       (0=Normal, 1=Disable Textures)                   ;GP0(E1h).11

  16    Horizontal Resolution 2     (0=256/320/512/640, 1=368)                 ;GP1(08h).6

  17-18 Horizontal Resolution 1     (0=256, 1=320, 2=512, 3=640)               ;GP1(08h).0-1

  19    Vertical Resolution         (0=240, 1=480, when Bit22=1)               ;GP1(08h).2

  20    Video Mode                  (0=NTSC/60Hz, 1=PAL/50Hz)                  ;GP1(08h).3

  21    Display Area Color Depth    (0=15bit, 1=24bit)                         ;GP1(08h).4

  22    Vertical Interlace          (0=Off, 1=On)                              ;GP1(08h).5

  23    Display Enable              (0=Enabled, 1=Disabled)                    ;GP1(03h).0

  24    Interrupt Request (IRQ1)    (0=Off, 1=IRQ)                             ;GP0(1Fh)/GP1(02h)

  25    DMA / Data Request, meaning depends on GP1(04h) DMA Direction:

          When GP1(04h)=0 ---> Always zero (0)

          When GP1(04h)=1 ---> FIFO State  (0=Full, 1=Not Full)

          When GP1(04h)=2 ---> Same as GPUSTAT.28

          When GP1(04h)=3 ---> Same as GPUSTAT.27

  26    Ready to receive Cmd Word   (0=No, 1=Ready)                                    ;GP0(...) ;via GP0

  27    Ready to send VRAM to CPU   (0=No, 1=Ready)                                    ;GP0(C0h) ;via GPUREAD

  28    Ready to receive DMA Block  (0=No, 1=Ready)                                    ;GP0(...) ;via GP0

  29-30 DMA Direction (0=Off, 1=?, 2=CPUtoGP0, 3=GPUREADtoCPU)                 ;GP1(04h).0-1

  31    Drawing even/odd lines in interlace mode (0=Even or Vblank, 1=Odd)

 

Control Register 0x1f80 1814        WRITE      (GPU1)

Les commandes envoyées vers le GPU1 sont en générales pour la configuration de notre GPU, des modes, de la zone à afficher etc.

 

CMD 0x00 RESET GPU

Paramètres : 0x0000: Initialisation du GPU. En fonction de la PSX, rétablit les paramètres initiaux. « Enable Display » est FALSE ce qui explique pourquoi l’écran devient tout noire. 

 

CMD 0x01 RESET COMMAND BUFFER

Parametres:0x0000  Initialisation du cache de commande FIFO.

 

CMD 0x02 RESET IRQ

Paramètres: 0x0000 ?????    

 

CMD 0x03 DISPLAY ENABLE/DISABLE

Paramètres :           0x0000 ENABLE 0x0001 DISABLE

 

CMD 0x04 DMA SETUP

Paramètres :           0x0000 DMA DISABLE     0x0002 CPU TO GPU          0x0003 GPU TO CPU

 

CMD 0x05 DISPLAY AREA

Paramètres     bit  $00-$09  X (0-1023)               bit  $0A-$12  Y (0-512)

Définition des coordonnées de départ de la zone à afficher            

 

CMD 0x06/0x7 HORIZONTAL/VERTICAL DISPLAY

PAL:       gpu_ctrl(6, 0xC62262); // Horizontal screen range            gpu_ctrl(7, 0x04B42D); // Vertical screen range

NTSC:    gpu_ctrl(6, 0xC4E24E); // Horizontal screen range           gpu_ctrl(7, 0x040010); // Vertical screen range

 

CMD 0x08  SETDISPLAY MODE

Paramètres:            0x08 0 0 RF W1 INT CB VM H W0W0

RF:         Reverse Flag (a essayer)

W1:         Voir Width

INT:        Interruption Enable/Disable

CB:         Color bits 15/24

VM:        Video Mode NTSC/PAL

H:           Resolution H                         240/480

W0:         Resolution W tel que:

         W0(b1-b0)        W1(b6)   
         00               0   256 pixels
         01               0   320
         10               0   512
         11               0   640
         00               1   384

 

              

Data Register 0x1f80 1810                 WRITE           (GPU0)

 

On peut classer les commandes du GPU0 en 3 groupes, les commandes de configuration, les commandes de transfert et les commandes de dessin de primitives.

En fonction des bits B31-B30-B29 on a :

B31         B30         B29

0             0             0                             N/A Ce sont théoriquement des commandes à envoyer au GPU1 que nous avons vues.

0             0             1                             Dessin Polygone (3/4 sommets)

0             1             0                             Dessin Line

0             1             1                             Dessin Sprite

1             0             0                             Transfer

1             1             1                             Commande de configuration

 

Commande de configuration :

Dans le port GPU0, les commandes de configuration servent à définir les parties où nous pouvons dessiner, nous verrons plus tard ces commandes lors de l’implémentation du double-buffer.

 

DRAW AREA START (0xE3):

Word 0:  0xE3 00 PP PP PP                Paramètres PP :      X=b0-b9                Y=b10-b19                                       

DRAW AREA END (0xE4):

Word 0:  0xE3 00 PP PP PP                Paramètres PP :      X=b0-b9                Y=b10-b19

DRAW AREA OFFSET (0xE5):

Word 0:  0xE3 00 PP PP PP                Paramètres PP :      X=b0-b9                Y=b10-b19

DRAW MASK SETTING (0xE6):

Word 0:  0xE3 00  00 00 0P                Paramètres P          b0 : 0 n’applique pas de masque          1 : Applique un masque dans la VRAM au moment du dessin: b15=1

                                                                                            b1 : 0 ignore les masques                      1 : Prend en compte les masques au moment du dessin

 

              

Tout d’abord avant d’envoyer de dessiner quoi que ce soit il nous faut correctement initialiser notre GPU et préparer quelques fonctions pour surveiller l’état du GPU.

              

Fonction InitGPUSimple :

Cette fonction réinitialise le GPU passe en mode 320*256 en 16 bits en mode PAL. Place la zone d’affichage en 0,0 dans la VRAM.

InitStdGPU:

                    ;R27 POINT TO I/O BASE

                    lui R27,0x1F80                                                    ; POINT TO I/O BASE

                    lui R8,0x0000                                                       ; CMD=RESET GPU

                    sw R8,0x1814(R27)                                             ; SEND CMD

                    ;HORIZONTAL

                    lui R8,0x06C6

                    ori R8,R8,0x2262

                    sw R8,0x1814(R27)

                    ;VERTICAL

                    lui R9,0x0704

                    ori R9,R9,0xB42D

                    sw R9,0x1814(R27)

                    ;DISPLAY AREA => WHERE TO DISPLAY (TOP/LEFT)

                    lui R8,0x0500                                   ; CMD=0x05

                    ori R8,R8,0x0000                            ; 0,0

                    sw R8,0x1814(R27)                         ; SEND TO GPU1

                    ;SET DRAW MODE => TEXTUE PAGE AND DRAW INTO DISPLAY ENABLE

                    lui R9, 0xe100

                    ori R9,R9,0x0400                            ; draw mode: B18=1 Draw to display area permitted

                    sw R9,0x1810(R27)                         ; SEND TO GPU0

                    ;SET DRAW AREA START=> FROM WHERE WE CAN DRAW

                    lui R8, 0xe300

                    ori R8,R8,0x0000                            ; 0,0

                    sw R8,0x1810(R27)                         ; SEND TO GPU0

                    ;SET DRAW AREA END BOTTOM/RIGHT => TO WHERE WE CAN DRAW

                    lui R9, 0xe407                                  ; 511

                    ori R9,R9,0xFFFF                          ; 1023

                    sw R9,0x1810(R27)

                    ;SET DRAW AREA OFFSET => WHERE DRAW

                    lui R8, 0xe500                                  ; DRAW OFFSET

                    ori R8,R8,0x0000                            ; OFFSET 0

                    sw R8,0x1810(R27)

                    ;MASK SETTINGS NONE

                    Lui R2,0xE600                                ; Mask setting : Désactive les masques.

                    Sw r2,0x1810(R27)                         ; SEND TO GPU1

                    ;MODE PAL 320*256

                    lui R9,0x0800                                   ;R12-> CMD 8 SET MODE

                    ori R9,R9,0x0009                            ;B3=1->PAL B4=0 =>16 BIT B0=1->W=320

                    sw R9,0x1814(R27)                         ;SEND TO GPU1

                    ;DMA DISABLE

                    lui R8,0x0400                                   ;DMA DISABLE

                    sw R8,0x1814(R27)                         ;SEND TO GPU1

                    ;ENABLE DISPLAY

                    lui R9,0x0300                                   ;CMD=0x03

                    ori R9,R9,0x0000                            ;B0=0 => ENABLE DISPLAY

                    sw R9,0x1814(R27)                         ;SEND TO GPU1

                    ;RETURN

                    jr R31                                                 ;And return

               nop

 

Fonction WaitGPUIdle :

Cette fonction attend que le GPU soit prêt à recevoir une liste de commande par DMA. Le GPU est dit IDLE si le bit 28 est allumé. Cette fonction sera utilisée plus tard pour l’envoi de liste chainée de commande par DMA.

WaitGPUIdle:

.GPUNOTIDLE:

                    lw R2,0x1814(R27)    

                    lui R3,0x1000                                   ;MASK TEST B27 GPU BUSY

                    and R2,R2,R3

                    bne R2,R3,.GPUNOTIDLE           ;NO 0x0400 0000 so WAIT

                    nop

;RETURN

                    jr R31                                                 ;And return

                    nop

 

Fonction WaitGPUReady :

Cette fonction attend que le GPU soit prêt à recevoir des commandes. Le GPU est prêt si le bit 26 est allumé. Si une commande en cours n’est pas complétée ou si le cache de commande est plein, Le bit 26 sera éteint. Cette fonction est très importante surtout lorsqu’au début en tant que débutant on utilise souvent l’envoi de commande direct par I/O.

WaitGPUReady:

                    .GPUNOTREADY:

                    lw R2,0x1814(R27)

                    lui R3,0x0400                                   ;MASK TEST B26 GPU NOT READY

                    and R2,R2,R3

                    bne R2,R3,.GPUNOTREADY      ;NO 0x0400 0000 so WAIT

                    nop

                    jr R31                                                 ;And return

                    nop

 

 

Commande de Dessin :

En fonction de la commande les paramètres varieront. Je ne vais pas faire une liste exhaustive (elle se trouve dans les liens au début).

On va juste voir quelques exemples de dessins de primitives n’utilisant pas les textures.

On utilise les coordonnées de l’écran :

X : coordonnées horizontal de gauche à droite

Y : coordonnées vertical de haut en bas.

 

 

Ligne Monochrome (0x40):

Word 0:  0x40 BB GG RR                  BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence).

Word 1:  YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet 0 SIGNEES

Word 2: YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet 1 SIGNEES

 

Triangle simple (0x20):

Word 0:  0x20 BB GG RR                  BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence).

Word 1:  YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet 0 SIGNEES

Word 2: YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet 1 SIGNEES

Word 3: YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet 2 SIGNEES

 

Triangle Gradue (Ombrage Garound) (0x30):

Word 0:  0x30 BB GG RR                  BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence) du sommet 0.

Word 1:  YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet 0 SIGNEES

Word 2:  N/A BB GG RR                   BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence) du sommet 1.

Word 3: YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet 1 SIGNEES

Word 4:  N/A BB GG RR                   BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence) du sommet 2.

Word 5: YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet 2 SIGNEES

 

Rectangle simple (0x60):

Word 0:  0x60 BB GG RR                  BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence).

Word 1:  YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du sommet inferieur gauche SIGNEES

Word 2: HH HH WW WW                Largeur (512 max) et longueur (1024 max) du rectangle

 

Remplissage (0x02):

Cette commande est quasi identique au rectangle simple, la différence et que celle-ci ignore les masks et les 4 premiers bits de la position horizontale et donc de surcroît est plus rapide.

Word 0:  0x02 BB GG RR                  BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence).

Word 1:  YY YY XX X0                    Coordonnées vertical (Y) et horizontal (X) du sommet inferieur gauche. 4 premiers bits inferieurs sont ignorés.

Word 2: HH HH WW WW                Largeur (512 max) et longueur (1024 max) du rectangle.

 

Pixel (0x68):

Word 0:  0x68 BB GG RR                  BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence).

Word 1:  YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du pixel SIGNEES

 

 

 

Fonction GPUClearDisplayVRAM:

Cette fonction efface (remplit en noire) la VRAM en 0,0 sur 320,256.

;CLEAR SCREEN 320*256 at 0,0

;ASSUME THAT R27=0x1F80

GPUClearDispayVRAM:

                    lui R2,0x0200                                   ; 0x02 FILL DRAW BUFFER B=0

                    ori R2,R2,0x0000                            ; GR=0

                    lui R3,0x0100                                   ; HEIGHT 256

                    ori R3,R3,0x0140                            ; WIDTH  320

                    sw R2,0x1810(R27)                         ; SEND CMD TO GPU0 FILL DRAW BUFFER WITH BLACK COLOR

                    sw R0,0x1810(R27)                         ; SEND PARAM TO GPU0 TOP/LEFT=0

                    sw R3,0x1810(R27)                         ; SEND PARAM TO GPU0 WIDTH*HEIGHT=320*256

                    jr R31

                    nop

 

Fonction R3000Delay :

Cette fonction est une autre petite bricole pour nos programmes de début. Elle attend un certain temps en fonction du registre R4 passé en paramètre.

;MISC FUNCTIONS

;$A0 => HOW MANY Tick to wait

;33MHZ 33.000.000 instruction per seconde

;loop is 4 cycles

;So  $A0=0x0080 0000 to wait above 1 second

M3000Delay:

                    addi $A0,$A0,0xFFFF                   ;Decrement $A0                               1 cycle

                    bgtz $A0,M3000Delay                   ;Repeat if $A0>0                             2 cycles

                    nop                                                      ;branch delay                                   1 cycle

                    jr $RA                                                ;Return                                               2 cycles

                    nop                                                      ;Branch delay                                  1 cycle

 

Les 5 fonctions que nous venons de voir ne seront pas répétées dans les listings afin d’économiser de la place.

 

PSX_02.asm:

Très, très simple programme utilisant les fonctions vues précédemment : initialise le GPU à 320x256 en 16 bits en PAL.

Le programme utilise les commandes 0x02 (Remplissage ecran), 0x30 (Triangle ombré) et 0x60 (Rectangle simple) vues précédemment.

 

Start:

                    jal InitStdGPU               ;GPU en MODE PAL 320*256 écran visible en 0,0 de la VRAM

                    nop

                   

Mainloop:

                    jal WaitGPUReady

                    nop

                   

                    ;EFFACE L’ECRAN PRINCIPAL

                    Jal GPUClearDisplayVRAM

                    nop

                   

                    ;DESSINE UN TRIANGLE GRADUEE AVEC DES SOMMET ROUGE BLEU ET VERT

                    ;VERT 0

                    lui R8,0x3000                 ;CMD 0x30                                                            

                    ori R8,R8,0x00FF          ;RED VERT 0

                    sw R8,0x1810(R27)       ;SEND CMD TO GPU 0x1F80 1810

                    lui R9,200                       ;Y0=200

                    ori R9,R9,20                   ;X0=20

                    sw R9,0x1810(R27)       ;SEND CMD TO GPU 0x1F80 1810

                    ;VERT 1

                    lui R8,0x00FF                 ;BLUE

                    ori R8,R8,0x0000           ;BLUE

                    sw R8,0x1810(R27)       ;SEND CMD TO GPU 0x1F80 1810

                    lui R9,20                         ;Y1=20

                    ori R9,R9,160                 ;X1=160

                    sw R9,0x1810(R27)       ;SEND CMD TO GPU 0x1F80 1810

                    ;VERT 2

                    lui R8,0x0000                 ;GREEN

                    ori R8,R8,0xFF00          ;GREEN

                    sw R8,0x1810(R27)       ;SEND CMD TO GPU 0x1F80 1810

                    lui R9,200                       ;Y2=200

                    ori R9,R9,300                 ;X2=300

                    sw R9,0x1810(R27)       ;SEND CMD TO GPU 0x1F80 1810

                   

                    ;Court delai

                    jal M3000Delay

                    lui $A0,0x0020

                   

                    jal WaitGPUReady

                    nop

 

                    ;DESSINE UN RECTANGE PLEIN ROUGE 40,20 20x20

                    lui R8,0x6000                                      ;0x60 FILL DRAW BUFFER B=0

                    ori R8,R8,0x00FF                               ;RED

                    lui R9,10                                              ;Y=60

                    ori R9,R9,10                                       ;X=240

                    lui R10,20                                            ;H=20

                    ori R10,R10,20                                   ;W=20

                    sw R8,0x1810(R27)                            ;SEND CMD TO GPU

                    sw R9,0x1810(R27)

                    sw R10,0x1810(R27)

                   

                    ;DESSINE UN RECTANGE PLEIN VERT 40,20 20x20

                    lui R8,0x6000                                      ;0x60 FILL DRAW BUFFER B=0

                    ori R8,R8,0xFF00                               ;VERT

                    lui R9,10                                              ;Y=20

                    ori R9,R9,290                                     ;X=280

                    lui R10,20                                            ;H=20

                    ori R10,R10,20                                   ;W=20

                    sw R8,0x1810(R27)

                    sw R9,0x1810(R27)

                    sw R10,0x1810(R27)

                   

                    ;DESSINE UN RECTANGE PLEIN BLEU 40,20 20x20    

                    lui R8,0x60FF                                     ;0x60 Rectangle

                    ori R8,R8,0x0000                               ;BLUE

                    lui R9,210                                            ;Y=200

                    ori R9,R9,10                                       ;X=80

                    lui R10,20                                            ;H=20

                    ori R10,R10,20                                   ;W=20

                    sw R8,0x1810(R27)

                    sw R9,0x1810(R27)

                    sw R10,0x1810(R27)

                   

                    ;DESSINE UN RECTANGE PLEIN BLANC 40,20 20x20

                    lui R8,0x60FF                                     ;0x60 Rectangle

                    ori R8,R8,0xFFFF                              ;WITHE

                    lui R9,210                                            ;Y=200

                    ori R9,R9,290                                     ;X=280

                    lui R10,20                                            ;H=20

                    ori R10,R10,20                                   ;W=20

                    sw R8,0x1810(R27)

                    sw R9,0x1810(R27)

                    sw R10,0x1810(R27)

                   

                    ;Court delai

                    jal M3000Delay

                    lui $A0,0x0020

                   

                    ;En Boucle

                    j Mainloop

                    nop

 

PSX_02.asm Des petits carrés clignotant autour d’un triangle en ombrage Gouraud

 

ATTENTION, le programme ci-dessous fonctionne sur une Vrai console. Mais comme je l’ai déjà dit, il faut prendre garde lorsque l’on charge trop de commandes.

Utiliser GPUWaitReady pour être sûr que le cache contienne au moins une entrée libre. Toutefois cela peut s’avérer insuffisant car le GPU n’a pas toujours le temps d’exécuter toutes les commandes avant la fin d’un retour vertical.

 

L’utilisation de la DMA, aborder plus tard, résout le problème de cache pour ce qui est des commandes de dessin et le double-buffering résoudra les problèmes pour les animations.

 

Conseils pour éviter les frustrations :

- Cache de commandes (64 octets).

- Une fois la commande exécutée, le cache libère une place dans la file FIFO.

- Lorsque le cache est plein, le bit GPUReady s’éteint.

- Lorsque l’on vide le cache, certaines opérations en début de queue seront tous simplement annulées donc attention. Pour éviter cela, il faut donc utiliser la fonction GPUWaitIdle avant de vider le cache.

- Lorsque nous envoyons le premier mot d’une commande, le bit GPUReady s’éteint et ne s’allumera que lorsque la commande sera complétée par tous ses paramètres et que, bien sûr, le cache contienne encore de la place pour l’envoi d’une nouvelle commande.

- Lors du mode de 480 ligne, l’entrelacement est actif il faut donc dessiner deux fois une fois les lignes paires et l’autre les lignes impaire en surveillant le retour horizontale (Compteur 1).

- XEBRA semble bien mieux émuler le GPU que tous les autres émulateurs. En tous cas tous les programmes graphiques fonctionnant sous cet émulateur on toujours fonctionner sur ma PSX et presque tous ceux qui ne fonctionnaient pas sous XEBRA ne fonctionnaient également pas sur la PSX tout en fonctionnant parfaitement sur tous les autres Emulateurs.

Je n’ai pas essayé tous les jeux avec XEBRA mais pour moi c’est actuellement le meilleur émulateur.  Dommage qu’il ne possède pas de debugger et c’est pour ce manque que j’encourage l’utilisation en complément de NoCache-PSX ou eventuellement PSx1.13 pour la programmation.

 

 

9-    ROM et Routine Système.

 

La Playstation contient en ROM un OS (Appelle PSX-OS) qui contient pas mal de routine basique d’entrée/sortie d’initialisation des ports comme les manettes, le CD/ROM et de gestion des évènements et interruptions. L’auteur de PS-NOCACHE critique fortement cet OS lui reprochant ces bugs, sa gestion des events et surtout sa lenteur (exécutions de certaines routines en section non-cache, gestion des interruptions).

 

Il y a 2 types de services :

-          Les services BIOS appelable avec l’instruction j 0xa0 j0xb0 j 0xc0

-          Les services System (pour la Gestion des interruptions) appelable avec l’instruction SYSCALL

 

Les SYSCALL Enter_Critical_Section (1) et Exit_Critical_Section (2) inhibe et rétablit respectivement les interruptions.

Une des lib PSX Apres avoir booté, désinstalle le driver du Lecteur CD entre un Enter et Exit critical section. Puis réinstalle le driver. Pourquoi ?

 

Les services BIOS sont appelés via des Jump J. Pour pouvoir appeler ces fonctions (j 0xA0   j 0xB0   j 0xC0) correctement il faut insérer les appelles dans une fonction avec une instruction JAL de façon à ce que le service retourne correctement grâce au  registre R31.

Assez bizarrement, le numéro de fonction est passe en registre R9 ($T1)

Les paramètres sont passe en en $A0-$A3 (R4-R7) et le reste dans la pile.

Les valeurs de retour sont en $V0 ($V1 si 64 bits) (R2/R3).

 

Exemple :

               Jal ResetEntryInt

                    Nop

                    …………………

ResetEntryInt :

                    addi R9,R0,0x0018

                    j 0xb0

                    nop

                    jr R31

                    Nop            

 

 

Importance de ResetEntryInt :

Un bon conseil, commencez toujours par cette fonction avant de commencer votre programme. Cela vous évitera de mauvaise surprise.

Le bios se sert d’une liste de tache à effectuer lors d’une interruption, on réinitialise cette liste avec justement la fonction ResetEntryInt.

 

 

Les exemples qui vont suivre nous montrerons comment se servir des services du BIOS.

 

Service I/O :

Avant de se servir des services I/O pour pouvoir charger et lire un fichier en CD/ROM, il nous faut correctement initialiser certaine fonction dont la réinstallation du driver du CD/ROM et ensuite le réinstaller.

Lorsque votre programme boot depuis le CD/ROM un appel à ResetEntryInt (0xB0 0x18) suffit.

En revanche si vous démarrez votre programme depuis un CD de démo officielle et que vous avez l’intention d’utiliser les services du BIOS pour la lecture CD, il faudra utiliser la sequence Remove96 et CDInit pour initialiser les drivers CD/ROM

 

Sequence d’initialisation:

; Réinstallation des services du CD/ROM

;TOUJOURS APPLER DANS  NOTRE CODE BOOT

Start:

                   

                    ;RESET CALLBACK

                    Jal ResetEntryInt                            ; Réinitialise la table des vecteurs d’interruptions standard.

                    nop

 

                    ;RESET HANDLE AND CONFIGURE CD/ROM POUR SON UTILISATION SI VOTRE PROGRAMME NE DEMMARRE PAS DEPUIS UN BOOT

                    jal EnterCriticSec

                    nop

                    sh R0,0x1074(R27)                         ; Disable all interruption

                    sh R0,0x1070(R27)                         ; IF interruption should occur disable it

                    jal Remove96                                   ; DESINSTALLATION DES SERVICES I/O DU CD/ROM

                    nop

                    jal ExitCriticSec

                    nop

                    jal Init96                                            ; REINSTALLATION DU DRIVER POUR LE CD/ROM

                    nop

                   

                    ;$K1 pointe sur I/O hardware

                    Lui R27,0x1F80

 

Main:

                    ;SUITE DE NOTRE CODE

                    …………………………………….

 

;LES FONCTIONS CI-DESSOUS NE SERONT PAS REPETER DANS LES EXEMPLE SUIVANTS !

ResetEntryInt:

                    addi R9,R0,0x0018

                    j 0xb0

                    nop

                    jr $RA

                    nop            

EnterCriticSec:

                    ADDI R4,R0,0x0001  ; Service 1 => ENTER_CRITICAL_SECTION

                    SYSCALL                     ; EnterCriticalSection

                    jr $RA

                    nop

ExitCriticSec:

                    ADDI R4,R0,0x0002  ; Service 2 => EXIT_CRITICAL_SECTION

                    SYSCALL                     ; ExitCriticalSection

                    jr $RA

                    nop

Remove96:

                    addi R9,R0,0x0072

                    j 0xa0                                                 ; DESINSTALLATION DU SERVICE ISO 96 CD/ROM

                    nop

                    jr $ra

                    nop

Init96:

                    addi R9,R0,0x0071

                    j 0xa0                                                 ; INSTALLATION DU SERVICE ISO 96 DU CD/ROM

                    nop

                    jr $RA

                    nop            

 

Description des fonctions I/O standard :

Le numéro de fonction est passé en registre R9

Les paramètres sont passés en en A0-A3 (R4-R7) et le reste dans la pile.

Les valeurs de retour sont en V0 (V1 si 64 bits) (R2/R3).

 

ATTRIBUTS

#define O_RDONLY            1

#define O_WRONLY            2

#define O_APPEND            8

#define O_RDWR                (O_RDONLY | O_WRONLY)

#define  O_CREAT              512

#define O_TRUNC               1024

 

OUVERTURE FICHIER

Open                      j 0xb0 R9=0x32    unsigned long open (char *devname, unsigned long flag)  ($A0,$A1)

Devname: nom du fichier

Flag: voir flags.

La fonction retourne le fd du fichier ou -1 si l’ouverture échoue.

 

FERMETURE FICHIER

Close                      j 0xb0 R9=0x36    long close (unsigned long fd)  ($A0)

fd: descriptor du fichier

La fonction retourne le fd du fichier ou -1 si la fermeture échoue.

 

LECTURE FICHIER

Read                      j 0xb0 R9=0x34    long read (unsigned long fd, void *buf, long n) ($A0,$A1,$A2)

fd File descriptor                                    
buf Read buffer address                          
n Number of bytes to be read

La fonction retourne le nombre de octets lues.         

 

DEPLACEMENT POINTEUR FICHIER

Lseek                     j 0xb0 R9=0x33    long lseek (unsigned fd,long offset,long flag)  ($A0,$A1,$A2)

 

Flags:

#define SEEK_SET 0

#define SEEK_CUR               1

#define SEEK_END               2

 

 

EXEMPLE CHARGEMENT ET LECTURE D’UN FICHIER :

 

;Exemple de chargement de fichier

                    ;OPEN FILE

                    LUI             A0,>FILENAME                             ;ADRESSE HAUTE DU FICHIER

                    ORI            A0,A0,FILENAME                         ;ADRESSE BASSE DU FICHIER

                    ADDI          A1,A1,0x0001                                  ; A1=1 => READ ONLY

                    ;BIOS CALL : OPEN FILE

                    JAL            OPENFILE

                    NOP

                    ;TEST RETURN VALUE V0 R2

                   

HANDLEFILE :

                    DW             ?                                                           ; SAUVE LE HANDLE RETOURNE PAR LA FONCTION

FILENAME :

                    BYT            "cdrom:\IMAGE.BMP;1"           ; CD/ROM

FILEMEMCARD

                    BYT           "bu01:IMAGE.BMP"                   ; Memory cards

OPENFILE:

               J                  0x0B                                                   ; FONCTION BIOS

                    ORI            R9,R0,0x0032                                  ; Nr 0x32 => OPEN FILE

 

 

ATTENTION !!! cd-rom en petits caractères sinon ça foire !!!

 

Lorsque on lit un fichier situe sur un CD/ROM avec le service Read il est important que le buffer soit un multiple de 2048. Sinon ça foire !

Pareil pour l’offset qui doit être multiple de 2048.

 

Exploration FILE I/O

 

struct DIRENTRY {

char name[20]; Filename

long attr; Attributes (dependent on file system)

long size; File size (in bytes)

struct DIRENTRY *next; Pointer to next file entry (for user)

char system[8]; Reserved by system

};

 

Exemple:

;GET FIRST FILE OF DIR

                    lui $A0,> FileScan

                    ori $A0,$A0, FileScan                                        ;A0=>File

                    lui $A1,>DirEntry

                    ori $A1,$A1,DirEntry                                        ;A1=>DirEntry

                    jal FirstFile

                    nop

                    ori $S0,R0,0x0028

.Loop:

                    ;NEXT FILE

                    lui $A0,>DirEntry

                    ori $A0,$A0,DirEntry                                        ;A1=>DirEntry

                    jal NextFile

                    nop

                    beq R2,R0,.EndLoop

                    nop

                    ;PRINT FileNAME

                    lui $A0,>DirEntry

                    ori $A0,$A0,DirEntry                                        ;A0=>DirEntry

                    or $A1,$S0,R0

                    sll $A1,$A1,16

                    ori $A1,$A1,0x0008

                    jal Print4B

                    nop

                    addiu $S0,$S0,0x0008

                    j .Loop

                    nop

.EndLoop:

                    j .End

                    nop

.End:

                    beq R0,R0,0xFFFF                         ;

                    nop

FileScan:

                    asci "cdrom:\?*",0

MemScan:

                    Asci “bu00:?*”,0

DirEntry:

                    word 0,0,0,0

                    word 0,0,0,0

                    word 0,0,0,0

                    word 0,0,0,0

 

Attention !!! Les fonctions du BIOS sont passablement boguer et peuvent détruire le contenu de la pile !!!

Pour éviter toute mauvais surprise utilisée une zone statique pour la sauvegarde des registres ou pour la pile si vous utilisez les fonctions du BIOS.

 

Gestion de la mémoire :

 

Stack (la pile) :

La pile (registre SP ou R29) est configure par l’OS lors du boot de notre programme en fonction des paramètres dans notre exécutable. Nous pouvons bien sûr reconfigurer l’emplacement mais franchement je ne vois à aucun moment l’utilité. Placer la pile en 0x801FFFF0 et ne changez rien.

 

HEAP (le tas) :

Avant d’utiliser le TAS, nous devons l’initialiser avec la fonction InitHeap.

Ensuite les fonctions malloc et free sont sans surprises.

A(39h) - InitHeap(addr, size)

A(33h) - malloc(size)

A(34h) - free(buf)

 

Evidement vous pouvez ignorer le Tas est gérer vous-même l’espace mémoire. Parait-il que les fonctions du Tas sont egalement bugger et de plus Les fonctions du BIOS sont lentes donc c’est souvent une bonne solution.

 

 

10-                      Contrôleur PAD

 

Pour simplifier, on va utiliser les routines du BIOS pour la lecture du PAD. Ici il y a juste un petit point particulier et qu’avant il est nécessaire de s’occuper d’abord d’initialiser le gestionnaire de carte mémoire avant d’initialiser le PAD lorsque on démarre à partir d’un CD/ROM

Il semble que le BIOS soit bogué (encore) à ce niveau. Si on essaye d’initialiser le PAD sans avoir initialiser le gestionnaire de MEMORYCARD, le système plante.

 

Donc avant d’initialiser le PAD il faut faire comme cela :

 

Initialisation standard :

 

;INIT INT VECTOR TABLE

                    Jal ResetEntry                                 ;Réinitialise la file  d’interruptions

                    nop

;INIT CARD

                    addi $A0,R0,0x0001

                    jal InitCard

                    nop

                    jal StartCard

                    nop

                    jal _Bu_Init

                    nop

                    jal StopCard

                    nop

………………………………………..

;MEMORY CARD FUNCTIONS

InitCard:

                    addi R9,R0,0x004A

                    j 0xb0

                    nop

                    jr $RA

                    nop            

StartCard:

                    addi R9,R0,0x004B

                    j 0xb0

                    nop

                    jr $RA

                    nop            

StopCard:

                    addi R9,R0,0x004C

                    j 0xb0

                    nop

                    jr $RA

                    nop            

BuInit:

                    addi R9,R0,0x0070

                    j 0xa0

                    nop

                    jr $RA

                    nop            

 

Mais pour que ces routines system fonctionnent, il faut impérativement d’abord appelé la fonction ResetEntry 

POURQUOI? ->Car la fonction ResetEntry initialise la file d’interruptions : La fonction d’entrée d’interruption parcourt une liste de fonction à appeler lorsqu’une interruption se produit.

Les fonctions bios de lecture du PAD ajoutent une entrée dans la queue des fonctions lorsqu’un VBlank apparait.

Donc c’est durant un retour vertical que les données du PAD sont lues.

 

Lecture du PAD :

 

Les fonctions du BIOS installe dans l’interruption du retour vertical, une routine qui lit l’état du PAD et écrit l’état à l’adresse spécifié en paramètre. Cela nous permet également de vérifier approximativement l’état d’un retour vertical.

 

Méthode 1 :

L’auteur de PS-NOCACHE déconseille fortement l’utilisation la fonction 0x15. Mais comme c’est la première que j’ai utilisé et qui a fonctionné (ouaaaa !!! le pad réponds !!) … J

Mais je pense que cette routine est la source de beaucoup de probleme que j’ai rencontre… ???

 

On utilise la routine du BIOS PAD_INIT 0x0015 j 0xB0 à ne pas confondre avec InitPAD 0x0012 j 0xB0

Le paramètre en $A0 concerne le type de PAD et le pad (1 ou 2) et on utilise toujours soit 0x2000 0001 ou 0x2000 0002.

Le paramètre en $A1 indique a quelle adresse doit être stocké le résultat de la lecture du PAD. Réserver dans le buffer 20 word en tout : je ne suis pas certain de l’utilité mais j’ai eu le droit à de beau plantage lorsque je ne réservais que 4 word. (Je n’ai pas depuis vérifié combien exactement on doit réserver).

;PAD_INIT AND VERTICAL SYNC

;THIS ROUTINE WORKS ON REAL PSX 

PAD_INIT:   

                     lui $A0,0x2000

                    ori $A0,$A0,0x0001

                    lui $A1,>PadBufI

                    ori $A1,$A1,PadBufI

                    addi R9,R0,0x0015

                    j 0xb0

                    nop

                    jr $RA

                    nop

PadBufI:

                    word 0,0,0,0

PadData :

                    word 0,0,0,0

                    word 0,0,0,0

                    word 0,0,0,0

                    word 0,0,0,0

 

Ensuite pour lire le PAD, on va continuellement lire le buffer, si celui-ci est vide (égale à 0) on attend (nous ne sommes pas encore dans un retour vertical). Lorsque le buffer est plein nous sommes dans un retour vertical et la routine transfert l’état du PAD à l’adresse qui suit 4 word du PADBuffer.

;THIS ROUTINE WORKS ON REAL PSX

ReadPadStatus:

                    lui $T1,>PadBufI

                    ori $T1,$T1,PadBufI

.WaitVSync:

                    lw $T0,0($T1)

                    nop

                    beq $T0,R0,.WaitVSync

                    nop

                    lui $T2,0xFFFF

                    ori $T2,$T2,0xFFFF

                    xor $T0,$T0,$T2

                    sw $T0,16($T1)           ;COPY TO PADDATAI

                    jr $RA

                    nop

 

Ensuite on peut interpréter l’état des boutons très simplement :

Mask des buttons du Pad :

;Lecture de l’état du PAD

lui R8,>PadDataI

ori R8,R8,PadDataI   ;R8 pointe sur PadDataI

lw R16,0(R8)                ;Lecture de PadDataI dans le Registre R16

nop

;MASK BUTTONS PAD

ori $T0,R0,0x8000     ;DROITE

ori $T0,R0,0x4000     ;BAS

ori $T0,R0,0x2000     ;GAUCHE

ori $T0,R0,0x1000     ;HAUT

ori $T0,R0,0x0080     ;MASK BUTTON CARRE

ori $T0,R0,0x0040     ;MASK BUTTON CROIX

ori $T0,R0,0x0020     ;MASK BUTTON ROND

ori $T0,R0,0x0010     ;MASK BUTTON TRIANGLE

ori $T0,R0,0x0100     ;MASK BUTTON SELECT

ori $T0,R0,0x0800     ;MASK BUTTON START

ori $T0,R0,0x0004     ;MASK BUTTON L1

ori $T0,R0,0x0001     ;MASK BUTTON L2

ori $T0,R0,0x0008     ;MASK BUTTON R1

ori $T0,R0,0x0002     ;MASK BUTTON R2

 

 

Le PADBuffer indiquant un retour vertical, la WaitVSyncOnly attend d’être dans un début de retour vertical pour rendre la main.

;THE PAD IS READ ON VBLANK

;SO WHEN THE PAD IS READ, PADBUF should be 0xFFFF FFFFF we are in VBLANK

;INSTEAD PADBUF is 0.

WaitVSyncOnly:

                    lui $T1,>PadBufI

                    ori $T1,$T1,PadBufI

                    ;INIT

                    sw R0,0($T1)                ;WRITE 0 AND WAIT AS VBLANK SET THIS FIELD

.@1:

                    lw $T0,0($T1)              ;READ FIELD

                    nop

                    beq $T0,R0,.@1           ;AND WAIT FOR START OF VBLANK

                    nop

                    jr $RA

                    nop

 

Animation:

Grace à la fonction WaitVSyncOnly, on est en mesure d’effectuer de petite (très petite) animation en simple buffer.

Exemple :

               jal WaitGPUReady

                    nop

                    jal WaitVSyncOnly

                    nop

                    ;CLEAR SCREEN

                    jal GPUClearVRAM

                    nop

                    ;DRAW

                    jal DrawTransTriTx

                    nop

 

Methode 2 :

 

                    Je n’ai toujours pas essaye la deuxieme method avec la function InitPAD 0x0012 j 0xB0

 

 

PROGRAMME DEPUIS UN CD-DEMO OFFICIEL

 

Etant donné que les fonctions du BIOS sont capables de détruire le contenu et le pointeur de pile, il faut dès le début de notre programme sauver toutes les registres vitaux dans un espace statique dans notre programme.

Ci-dessous un programme qui peut être démarré d’un CD-Démo et fermer proprement après un appui sur select. Le programme ne fait rien à part afficher un triangle bleu centrée.

ORG 0x8001 8000

Start:

                    ;SAVE REGISTER ON STATIC SPACE (NOT ON STACK BECAUSE BUGS OF PSX-OS)

                    lui R2,>SaveRegister

                    ori R2,R2,SaveRegister

                    sw R31,0(R2)                                    ;SAVE RA

                    sw R29,4(R2)                                    ;SAVE SP

                    sw $K0,8(R2)                                    ;SAVE K0

                    sw $K1,12(R2)                                  ;SAVE K1

                    sw $S0,16(R2)                                   ;SAVE S0

                    sw $S1,20(R2)                                   ;SAVE S1

                    sw $S2,24(R2)                                   ;SAVE S2

                    sw $S3,28(R2)                                   ;SAVE S3

                    sw $S4,32(R2)                                   ;SAVE S4

                    sw $S5,36(R2)                                   ;SAVE S5

                    sw $S6,40(R2)                                   ;SAVE S6

                    sw $S7,44(R2)                                   ;SAVE S7

                    ;GPU INIT 320*240 PAL

                    lui R27,0x1F80                                ;R27 pointe vers I/O BASE

                    jal InitStdGPU

                    nop

                    ;SEQUENCE STANDARD : RESETENTRY

                    jal ResetEntryInt

                    nop

                    ;PREPARE function before using PAD_INIT

                    addi $A0,R0,0x0001

                    jal InitCard

                    nop

                    jal StartCard

                    nop

                    jal BuInit

                    nop

                    jal StopCard

                    nop

                    ;PAD

                    jal PAD_INIT

                    nop

;******** NOTRE PROGRMME DEMMARRE VRAIMENT ICI

                    ;QUICK TEST DRAW A TRIANGLE

                    lui R27,0x1F80                                ;R27 pointe vers I/O BASE

                    ;Dessine un triangle BLEU Centree

                    lui R8,0x20FF                                  ;Commande GPU DRAW TRIANGLE 0x20 BB GG RR où RGB désigne les couleurs RVB chacune codée sur 8 bits.                                                

                    ori R8,R8,0x0000                            ;0000

                    sw R8,0x1810(R27)                         ;SEND CMD TO GPU 0x1F80 1810

                    lui R8,200                                          ;Y0=200

                    ori R8,R8,20                                     ;X0=20

                    sw R8,0x1810(R27)                         ;SEND PARAM TO GPU 0x1F80 1810

                    lui R8,20                                            ;Y1=20

                    ori R8,R8,160                                   ;X1=160

                    sw R8,0x1810(R27)                         ;SEND PARAM TO GPU 0x1F80 1810

                    lui R8,200                                          ;Y2=200

                    ori R8,R8,300                                   ;X2=300

                    sw R8,0x1810(R27)                         ;SEND PARAM TO GPU 0x1F80 1810

MainLoop:

                    ;READ PAD DATA

                    jal WaitVSync

                    nop

.ReadPad:

                    lui $S1,>PadDataI

                    ori $S1,$S1,PadDataI

                    lw $S0,0($S1)                                    ;PAD DATA ON $S0

                    nop

.Select:

                    andi $T0,$S0,0x0100                     ;Button Select

                    beq $T0,R0,MainLoop                  ;NON ON BOUCLE

                    nop

;******** FIN DE NOTRE PROGRAMME

.EndProg:

                    ;STOP PAD

                    jal StopPad

                    nop

                    ;RESET GPU

                    lui R3,0x1F80                                  ; R3->IO

                    sw R0,0x1814(R3)                           ; RESET GPU HARD

                    ;RESTAURE REGISTER FROM STATIC SPACE

                    lui R2,>SaveRegister

                    ori R2,R2,SaveRegister

                    lw R31,0(R2)                                     ;SAVE RA

                    lw R29,4(R2)                                     ;SAVE SP

                    lw $K0,8(R2)                                    ;SAVE K0

                    lw $K1,12(R2)                                  ;SAVE K1

                    lw $S0,16(R2)                                   ;SAVE S0

                    lw $S1,20(R2)                                   ;SAVE S1

                    lw $S2,24(R2)                                   ;SAVE S2

                    lw $S3,28(R2)                                   ;SAVE S3

                    lw $S4,32(R2)                                   ;SAVE S4

                    lw $S5,36(R2)                                   ;SAVE S5

                    lw $S6,40(R2)                                   ;SAVE S6

                    lw $S7,44(R2)                                   ;SAVE S7

                    ;RETURN 0 AND QUITTE PROGRAMM

                    or R2,R0,R0                                      ;RETURN 0

                    jr R31                                                 ;QUITTE PROGRAMME

                    or R3,R0,R0                

include "PSXSYSTEM.asm"                           ;POUR LES APPELLES DES FONCTION BIOS

                    nop

include "PSXGPU.asm"                                    ;ROUTINES GPU DEFINIT AU DEBUT

                    nop

SaveRegister:

                    word 0,0,0,0                                      ;R31,R29,$K0,$K1

                    word 0,0,0,0,0,0,0,0,0                     ;S0..S7

                    word 0,0                                             ;8 octets supplémentaires au cas ou…

              

Tous les programmes de démonstrations de ce tutoriel utiliseront dorénavant le corps de ce listing.

 

 

11-                      Transfert d’Image sans DMA

 

Pour transférer une image de la RAM vers la VRAM, on utilise la commande :

0xA0      Y|X         H|W        PIXEL(S)

Pixel contient 1 ou 2 pixels en fonction du mode.

 

Tous les tests de transferts ont marché même une image de 1024*512 sur tout la VRAM. L’auteur de NoCash-PSX affirme que le transfert sur tout la VRAM n’est pas possible : peut-être sur une autre version du GPU. Si vous avez un problème avec, vérifier d’abord si vous ne faites pas d’erreurs de programmation (si par exemple vous envoyer trop de données vous aurez droit a des effets imprévisibles).

Il semble que la fonction 0xA0 ne se sert pas du cache de commande même si la plupart des librairies que j’ai pu voir initialise le cache avant le transfert. J’ai l’intuition que cela est absolument inutile mais je n’ai pas vérifié donc initialisez le cache avant un transfert et basta.

 

La GPULib pour les transferts désactive les interruptions vérifie que le cache n’est pas plein le vide éventuellement vérifie la taille du transfert, divise par 64 octets (16 words taille max d’un bloc).

Transfert le premier bloc non égale à 16 par I/O et le reste par DMA.  (Lorsque le PAD est initialisé ou non, les transferts I/O fonctionnent dans tous les cas).

 

 

Exemple Transfert VRAM I/O:

;$A0 =>  PTR MEM SOURCE

;$A1 =>  DESTINATION Y|X DANS LA VRAM

;$A2 =>  IMAGE PARAMS H|W

Mem2VramIO:

;PUSH R31

                    addi R29,R29,0xFFFC                   ; R29=R29-4                

                    sw R31,0(R29)                                  ; PUSH R31

;R27 POINT TO I/O BASE

                    lui R27,0x1F80

                    ;CALC NEED BLOCK

                    ;READ W/H

                    Or R8,$A2,R0                                  ;W

                    Or R9,$A2,R0                                  ;R9=H<<16|W

                    Srl R9,R9,16                                     ;R9=H

                    multu R8,R9                                     ;W*H

                    mflo R10                                           ;W*H=R10

;CALC COUNT OF 1 words 2 pixels

                    srl R10,R10,1                                   ;R10/2=>W*H/2

;WAIT FOR GPU READY

                    jal WaitGPUReady

                    nop

;SETUP DMA OFF POUR LE GPU

                    lui R9,0x0400                                   ;DMA CMD

                    ori R9,R9,0x0000                            ;DMA OFF

                    sw R9,0x1814(R27)                         ;SEND CMD DMA OFF TO GPU1

;FLUSH CACHE

                    lui R8,0x0100

                    sw R8,0x1810(R27)                         ;FLUSH CACHE GPU0

                    lui R10,0xE600                                ;DISABLE MASK STUFF

                    sw R10,0x1810(R27)                      ;CMD DISABLE MASK STUFF

                    lui R9,0xA000

                    sw R9,0x1810(R27)                         ;SEND IMG CMD 0xA0

                    sw $A1,0x1810(R27)                      ;SEND CMD DST Y|X

                    sw $A2,0x1810(R27)                      ;SEND CMD H|W

;START COPY LOOP

.Loop:

                    lw R8,0($A0)                                     ;READ 2 PIXEL

                    addi R10,R10,0xFFFF                   ;R10 CNT--

                    sw R8,0x1810(R27)                         ;SEND 2 PIXEL

                    bne R10,R0,.Loop                           ;loop if R10!=0

                    addi $A0,$A0,0x0004                    ;NEXT 4

;END COPY LOOP

;POP R31

                    lw R31,0(R29)                                  ; POP RN

                    nop                                                      ; LOAD DELAY

;RET

                    jr R31                                                 ; RETOUR

                    addi R29,R29,0x0004                    ; RESTAURE STACK                   

 

 

 

12-                      Polygone Texturé et Sprites

 

Maintenant que nous savons transférer des images en VRAM nous pouvons essayer les sprites et les polygones texturés.

 

POLYGONE TEXTURES :

La grande force de la PSX est de pouvoir afficher des polygones ombrés et texturés. Ce qui permet de faire un grand nombre de choses sans grand effort (même si certains trouverons cela moins intéressant la PSX contient quelques défis et problèmes à résoudre comme notamment l’absence de Z-Buffer, le clipping en Z etc.). Pour l’instant nous allons seulement voir comment afficher en triangle texture en ombrage plat (FLAT SHADING).

 

La VRAM est organisé en Texture page, 32 zones de 64x256 où le no de page est donnée par la formule TY/16+TX/64.

La page de texture est sélectionnée dans les paramètres de commandes de dessin pour les polygones (Triangles, rectangles-poly).

 

Une texture page possède 3 modes d’affichage couleur :

0                           4 bits       16 couleurs

1                           8 bits       256 couleurs

2                           15bits      RGB                      un pixel = octet (5 bit pour chaque RGB)

Pour les Sprites, le mode est sélectionnable par la commande 0xE1 (Set Texture Page) que nous verrons plus tard.

Sinon c’est dans la commande polygone que nous définissons le mode couleur.

 

Tout d’abord il convient de mettre la texture dans la VRAM. Pour simplifier nous allons utiliser le mode 15 bits.

Une texture ne peut avoir des dimensions supérieures à 256x256.

 

Une fois la texture dans la VRAM,  on peut configurer la fenêtre de Texture par la fonction 0xe2 : c’est là que nous définissons une fenêtre dans la page de texture.

 

GP0(0xE2) - Texture Window setting

  0-4    Texture window Mask X   (in 8 pixel steps)

  5-9    Texture window Mask Y   (in 8 pixel steps)

  10-14  Texture window Offset X (in 8 pixel steps)

  15-19  Texture window Offset Y (in 8 pixel steps)

  20-23  Not used (zero)

  24-31  Command  (E2h)

 

;TEXTURE WINDOW SETTING OUR IMAGE IS ON PAGE X=0 Y=0 W=64 H=64

;$13-$0F                $0E-$0A                               $9-$5                                                                   $4-$0     

;TWY                     TWX                                     TWH                                                                    TWW                     ;MULTIPLE OF 8 !

TWY TWX de façon à ce que WX=TWX*8 et WY=TWY*8

Et TWW et TWH configure la taille de façon à ce que WW=256-(TWW*8) et WH=256-(TWH*8)          

               lui R27,0x1F80

                    lui $T1,0xe200                                                     ; Le CLUT ID=0

                    ori $T1,$T1,0x0000                                           ; 0,0 256,256

                    ori $T1,$T1,0x0318                                           ; 0,0 64 ,64

                    sw $T1,0x1810(R27)                                           ; SEND CMD

 

ATTENTION La page de texture ne change rien aux conséquences sur les coordonnées de textures : u v vont toujours se limite entre 0 et 255 dans la Texture Page.

Si nous limitons notre fenêtre à 64*64 et que les coordonne de texture d’un polygone à 4 cotes avec u et v possédant des valeurs de 0 et 255.

La texture 64*64 sera répétée et non affichée entièrement !

La fenêtre de texture sert donc principalement à la répétition de texture. Par exemple un texture de 32*32 avec la fenêtre configuré sur cette texture on peut alors répéter 8 fois la texture sur un polygone en utilisant u et v de 0 ; 255.

 

Par default la fenêtre est définit sur tout la texture. Donc pour l’affichage de notre polygone nous ne le ferons pas.

 

Pour afficher notre triangle texturé, nous utilisons la commande 0x24 qui va se présenter ainsi :

24   BB   GG RR

YY YY  XX XX                  ;COORDONNES VERTEX 0

ID CLUT               V    U                                    ;IDCLUT=0          Coordonne de Texture v u vertex 0

YY YY  XX XX                  ;COORDONNES VERTEX 1

TP                          V    U                                    ;Texture page         Coordonne de Texture v u vertex 1

YY YY  XX XX                  ;COORDONNES VERTEX 2

N/A                        V   U                                     ;N/A=0   v u vertex 2

 

Les coordonne de texture vont de 0 à 255.

En mode 15 bits on s’en tape du ID CLUT

Le paramètre clef est TP (Texture Page) qui se présente sur 16 bits ainsi :

B8-B7 : Mode : 10 ->15 bits, 00-> 4bit, 01->8bit

B4 : YP page tel que Y=YP*256

B3-B0 : XP page tel que X=XP*64

 

Supposons que nous chargions une Texture de 256*256 dans la VRAM en X=512 Y=0 de 15 bits.

TP aura donc la valeur de 0x108        

 

Exemple Triangle texturé:

; Affiche un triangle texture dont celle ci de 256x256 se trouve en 512,0 dans la VRAM

TriText1:

                    ;R27 POINT TO I/O BASE

                    lui R27,0x1F80

                    ;SET TEXTURE WINDOW 0,0 256*256

                    lui $T1,0xe200                                                     ;CLUT ID=0 DON’T CARE

                    ori $T1,$T1,0x0000                                           ;PARAMS 0

                    sw $T1,0x1810(R27)                                           ;SEND CMD

                    ;DRAW A TEXTURED TRIANGLE (20,200) (140,20) (300,200)

                    lui $T0,0x2480                                ;TEXTURED 3 point plygone                                             

                    ori $T0,$T0,0x8080                       ;GRAY 0.5

                    sw $T0,0x1810(R27)

                    ;FOR EACH VERTEX

                    ;VERTEX 0

                    lui $T1,200                                       ;Y0=200

                    ori $T1,$T1,20                                ;X0=20

                    sw $T1,0x1810(R27)                      ;SEND COORDS Y|X

                    lui $T2,0x0000                                ;CLUT ID =0 MODE 15 bit DON’T CARE

                    ori $T2,$T2,0xFF00                      ;V=255 U=0

                    sw $T2,0x1810(R27)                      ;SEND CLUTID AND COORDTEXT

                    ;VERTEX 1

                    lui $T1,20                                          ;Y0=20

                    ori $T1,$T1,140                              ;X0=140

                    sw $T1,0x1810(R27)                      ;SEND COORDS Y|X

                    lui $T2,0x0108                                ;TEXTURE PAGE : 15 bit direct(b8-b7)=2 - X(b3-b0)=8=>8*64=512 - Y=0(b4=0) (1 0000 1000)

                    ori $T2,$T2,0x0080                       ;V=0 U=128

                    sw $T2,0x1810(R27)                      ;SEND TP AND COORDS U|V

                    ;VERTEX 2

                    lui $T1,200                                       ;Y0=200

                    ori $T1,$T1,300                              ;X0=300

                    sw $T1,0x1810(R27)                      ;SEND COORDS Y|X

                    lui $T2,0x0000                                ;DON'T CARE

                    ori $T2,$T2,0xFFFF                      ;V=255 U=255

                    sw $T2,0x1810(R27)                      ; SEND COORDS U|V

                    jr $RA

                    nop         

 

SPRITE :

Pour l’affichage des Sprites nous allons nous intéresser aux tables de couleurs les CLUTs et au Texture Pages.

A noter qu’il ne s’agit pas là de vrai Sprites câblés traditionnels mais de rectangle texturés avec la couleur noire toujours transparente.

Toujours est-il que la Playstation possede de telle capacitée d’affichage qu’elle rivalise sans probleme avec n’importe quelle borne d’arcade contemporaine pour l’affichage 2D.

 

La VRAM est organisé en Texture page, 32 zones de 64x256 où le no de page est donnée par la formule TY/16+TX/64.

Une texture (image) de Sprite  ne peut avoir des dimensions supérieures à 256x256.

La texture page est configuree par la commande du GPU0 0xE1

GP0(E1h) - Draw Mode setting (aka "Texpage")

  0-3   Texture page X Base   (N*64) (ie. in 64-halfword steps)    ;GPUSTAT.0-3

  4     Texture page Y Base   (N*256) (ie. 0 or 256)               ;GPUSTAT.4

  5-6   Semi Transparency     (0=B/2+F/2, 1=B+F, 2=B-F, 3=B+F/4)   ;GPUSTAT.5-6

  7-8   Texture page colors   (0=4bit, 1=8bit, 2=15bit, 3=Reserved);GPUSTAT.7-8

  9     Dither 24bit to 15bit (0=Off/strip LSBs, 1=Dither Enabled) ;GPUSTAT.9

  10    Drawing to display area (0=Prohibited, 1=Allowed)          ;GPUSTAT.10

  11    Texture Disable (0=Normal, 1=Disable if GP1(09h).Bit0=1)   ;GPUSTAT.15

  12    Unknown (usually zero) (but BIOS does set this bit on power-up...?)

  13    Unknown (usually zero) (but BIOS does set it equal to GPUSTAT.13...?)

  14-23 Not used (should be 0)

 

;SET TEXTURE PAGE CMD 0xE1 OR DRAW MODE SETTING

                    ;B10 Draw to display B09:DITTER               B8-B7 TP                      B6-B5:ABR                   B4=Y          B3-B0=X

                    ;1 ENABLE                                      0 (DITHER OFF)        10 (16 bits)                    00(Don't care)              0                    1000(8->8*64=512)

                    ;0x0508

SetTexurePage :                                                                                                                    

               lui R8,0xE100              ;CMD E1 SET TEXTURE PAGE

                    ori R8,R8,0x0508       ;SEE ABOVE

                    sw R8,0x1810(R27)    ;SEND CMD GPU0   

 

Examinons la commande 0x64. Elle est composée de 4 mots de cette façon :

0x64       BB          GG         RR

YY         YY         XX         XX

CLUT     CLUT     VV         UU

HH         HH         WW        WW

 

Comme pour le triangle les 24 bits suivant définissent la couleur en BGR.

Ensuite les position du coin supérieur gauche par Y(16 bits) et X(16 bits)

Ensuite vient la position du CLUT 10 bits pour Y et 5 bits pour X tel que X/16.

Et enfin la taille du Sprite vient sur le mot suivant H dans le nibble supérieur et W dans le nibble inferieur.

Avec cette commande nous pouvons réaliser des Sprites 3D.

Pour l’instant, je laisse au lecteur le soin de réaliser seul un programme utilisant des Sprites 3D avec la commande 0x64.

 

Examinons la commande 0x74. Elle affiche un sprite de 8x8 Elle est composée de 3 mots de cette façon :

0x74       BB          GG         RR

YY         YY         XX         XX

CLUT     CLUT     VV         UU

 

Comme pour le triangle les 24 bits suivant définissent la couleur en BGR.

Ensuite les position du coin supérieur gauche par Y(16 bits) et X(16 bits)

Ensuite vient la position du CLUT 10 bits pour Y et 5 bits pour X tel que X/16.

Et les coordonnes de texture dans la texture page.

 

Premier exemple : Fonts 16 bits en 8x8.

La fonction ci-dessous se sert d’une image de 128*128 16 bits ou chaque caractère se trouve logiquement dans une table de 16*16 entrées.

Un caractère est affiché par un sprite 8x8 (commande 0x74).

Elle suppose que la texture page soit déjà configurée et se trouve en 512,0 dans la VRAM (Page 8).

Les coordonnées de texture du Sprite sélectionnent le caractère.

Afin de limiter la longueur du listing, cette routine gère les coordonnées d’écran de manières simplificatrices : X et Y sont limités à 511 (and 0x1FF). Chaque caractère incrémente X par 8. Si X atteint 0 alors Y est augmenté de 8.

; Affichage d'une chaine de caractères ASCII 8*8

; Utilisation des Spirtes 8x8

; SetTexurePage suppose que l’image des fonts se trouve en 512,0 dans la VRAM

; L’image des fonts est une table de 16*16 donc de dimension 128*128

; Configuration de la Texture Page en 512*0

; PARAMETRES :

;                   $A0=>PtrText la chaine doit se terminer par 0

;                   $A1=>Position a l'ecran Y|X

Print16b:

; SAVE RETURN ADRESS

                    addi R29,R29,0xFFFC                                       ; R29=R29-4                

                    sw R31,0(R29)                                                      ; PUSH R31

; START

                    jal SetTexturePage

                    nop

.printChar:

                    ;R10->Char

                    lb R10,0x0000($A0)                                            ;R10 get first char

                    nop

                    beq R10,R0,.End                                                  ;0 means END STRING

                    ;IMAGE represents TABLE 16x16 of 8x8 sprite chars.

                    ;LINE=char/16->R11

                    or R11,R0,R10                                                     ;R11=R10=Char

                    srl R11,R11,4                                                        ;R11/16=>LINE NUMBER

                    ;COLUMN=CHAR-(LINE*16)

                    sll R13,R11,4                                                        ;R13=Line*16

                    sub R14,R10,R13                                                 ;R14=R10-R13=CHAR-(LINE*16)

                    ;AT THIS POINT R11->LINE NUMBER AND R14=COLUMN

                    ori R12,R0,0x00FF                                             ;MAX 255

                    sll R11,R11,3                                                        ;R11*8 LINE NUMBER->V

                    sll R14,R14,3                                                        ;R14*8 COL NUMBER->U

                    and R14,R14,R12                                                ;U MASK

                    and R11,R11,R12                                                ;V MASK

                    sll R11,R11,8                                                        ;V UPPER NIBBLE

                    or R11,R11,R14                                                   ;R11 IS THE UV

                    andi R11,R11,0xFFFF                                       ;CLUT TO 0

; SHOW ONE SPRITE 8x8 TEST CMD 0x74

                    jal GPUWaitReady                                             ;Since we could send lot of sprite the simpler things is

                    nop                                                                          ; to wait systematically if GPU is ready to receive command.

                    lui R8,0x74FF

                    ori R8,R8,0xFFFF                                               ;SPRITE WHITE

                    sw R8,0x1810(R27)                                             ;SEND GPU0 CMD

                    sw $A1,0x1810(R27)                                           ;SEND GPU0 POSITION Y|X

                    sw R11,0x1810(R27)                                           ;SEND GPU0 CLUT|V|U

; ADJUST X|Y

                    ;R8=X R9=Y

                    ori R8,$A1,R0                                                      ;R8=Y<<16|X

                    andi R8,R8,0x01FF                                             ;R8=X

                    ori R9,$A1,R0                                                      ;R9=Y<<16|X

                    srl R9,R9,16                                                          ;R9=Y

                    ;NEXT X

                    addi R8,R8,0x0008                                             ;NEXT X

                    andi R8,R8,0x01FF                                             ;MAX X =512

                    bne R8,R0,.SetYX                                                ;NO SO LET Y

                    nop

                    ;NEXT Y

                    addi R9,R9,0x0008

                    andi R9,R9,0x1FF                                               ;MAX=512

; SET $A1 Y|X

.SetYX:

                    sll R9,R9,16                                                           ;Y ON UPPER NIBBLE

                    or $A1,R9,R8                                                        ;SET $A1=Y<<16|X

                    j .printChar                                                           ;PRINT NEXT CHAR

                    addiu $A0,$A0,0x0001                                      ;NEXT CHAR

.End:

; END POP AND RET

                    lw R31,0(R29)                                                       ; POP RN

                    nop

                    jr R31                                                                     ; RETOUR

                    addi R29,R29,0x0004

                

 

Deuxième exemple : Fonts 4 bits en 8x8. Utilisation des tables couleurs CLUT.

En mode 4 bits. Un demi-mot représente 4 pixels en ordre inverse (les 4 premiers bits représentent le pixel le plus à droite les derniers 4 bits le plus à gauche) où la couleur code sur 4 bits est l’index d’une table de couleur de 16 bits (5 bits pour BGR le premier bit pour la transparence) de 16 entrées qui se trouve en VRAM.

On sélectionne la table de couleur par le paramètre CLUT sur 16 bits. Ou les 10 premiers bits représentent Y et les 6 autres représentent X/16.

 

Pour passez en mode 4 bits, notre fonction SetTexturePage devient :

;SET TEXTURE PAGE CMD 0xE1 OR DRAW MODE SETTING 512*0

                    ;B10 Draw to display                     B09:DITTER               B8-B7 TP                      B6-B5:ABR                  B4=Y          B3-B0=X

                    ;1 ENABLE                                      0 (DITHER OFF)        00 (4 bits)                       00(Don't care)              0                    1000(8->8*64=512)

                    ;0x0408                                                                                                               

               lui R8,0xE100              ;CMD E1 SET TEXTURE PAGE

                    ori R8,R8,0x0408       ;SEE ABOVE

                    sw R8,0x1810(R27)    ;SEND CMD GPU0   

 

;SET TEXTURE PAGE CMD 0xE1 OR DRAW MODE SETTING EN 512*256

                    ;B10 Draw to display                     B09:DITTER               B8-B7 TP                      B6-B5:ABR                  B4=Y          B3-B0=X

                    ;1 ENABLE                                      0 (DITHER OFF)        00 (4 bits)                       00(Don't care)              1(256)                    1000(8->8*64=512)

                    ;0x0408                                                                                                               

               lui R8,0xE100              ;CMD E1 SET TEXTURE PAGE

                    ori R8,R8,0x0418       ;SEE ABOVE

                    sw R8,0x1810(R27)    ;SEND CMD GPU0   

 

 

Et l’image Fonts ne fait plus que 32*128.

Cela ne modifie en rien la routine comme si en 4 bits la page se met également en 4 bits et visualise 64*256 de la VRAM.

Nous devons seulement indiquer le bon CLUT à notre SPRITE.

 

               or R11,R11,R14                               ; R11 IS THE UV

                    andi R11,R11,0xFFFF                   ; CLUT TO 0

                    lui R13,0x7FE0                               ; CLUT ID->Y=511 X=32=(512/16)

                    or R11,R11,R13                               ; R11=CLUT|Y|X

;SHOW ONE SPRITE 8x8 TEST CMD 0x74

                    jal WaitGPUReady                         ;VERY IMPORTANT !!!

                    nop                                                                                                                   ;

                    lui R8,0x74FF

                    ori R8,R8,0xFFFF                          ; SPRITE WHITE

                    sw R8,0x1810(R27)                         ; SEND GPU0 CMD

                    sw $A1,0x1810(R27)                      ; SEND GPU0 POSITION Y|X

                    sw R11,0x1810(R27)                      ; SEND GPU0 CLUT|V|U

 

 

Conversion hexa->Text :

Pour commencer à examiner les valeurs des registres de notre PSX il nous faut une petite routine de conversion en hexadécimale :

;Convert Val on $A0 to Hexadecimal Chaine pointed by $A1        

MyConvertVal:

; SAVE RETURN ADRESS ON STACK

                    addi R29,R29,0xFFFC                   ; R29=R29-4                

                    sw R31,0(R29)                                  ; PUSH R31

; START

                    lui R8,>.HexVal

                    ori R8,R8,.HexVal                          ;R8=>HEXAV ALUE

                    ori R12,R0,4                                     ;R12 is CNT 4 Bytes

; EACH BYTE

.Loop:

                    or R10,$A0,R0                                 ;R10 is the value

                    sll $A0,$A0,8                                    ;Upper bytes don't care

                    sra R10,R10,24                                ;GET B31-b24

                    or R9,R10,R0                                                       

                    andi R9,R9,0x00FF                        ;R9 b-31-b24

;R9 contains byte

;B7-B4

                    or R11,R9,R0

                    srl R11,R11,4

                    andi R11,R11,0x000F                    ;B31-B28

                    addu R11,R11,R8                            ;GET HEXA adress

                    lb R11,0(R11)                                   ;R11 HEX

                    nop

                    sb R11,0($A1)                                   ;PRINT

                    nop

                    addiu $A1,$A1,0x0001                  ;NEXT STRING

;END B31-B28

;B3-B0

                    or R11,R9,R0

                    andi R11,R11,0x000F                    ;B31-B28

                    addu R11,R11,R8                            ;GET HEXA adress

                    lb R11,0(R11)                                   ;R11 HEX

                    nop

                    sb R11,0($A1)                                   ;PRINT

;END B3-B0

                    addi R12,R12,0xFFFF                   ;DEC CNT

                    bne R12,R0,.Loop

                    addiu $A1,$A1,0x0001                  ;NEXT STRING

                    ;END WRITE 0

                    sb R0,0($A1)                                     ;0 end string

;END

                    lw R31,0(R29)                                  ; POP $RA

                    nop

                    jr R31                                                 ; Return

                    addi R29,R29,0x0004                    ; Restaure SP

.HexVal:

                    asci             "0123456789ABCDEF"

 

 

Ensuite une routine inverse à partir d’une chaine de caractères pointé par R4 et supposée représenter une valeur en hexa, on convertit en valeur dans le registre R2.

Avec tous ces routines nous pouvons commencer a plus efficacement analyser la PSX, ses registres I/O durant des programmes de test par exemple.

 

 

Quad Polygone :

 

Les quads sont gérer par le gpu de façon assez étrange.

 

              

               V1                                    V3

 

 

 

 


            V0                             V2

Le GPU génère deux triangle v0,v1,v2 et v1,v2,v3 pour rendre le quad.

 

Examinons les 2 commandes :

 

Commande 0x28  Quad simple

28   BB   GG RR

YY YY  XX XX                  ;COORDONNES VERTEX 0

YY YY  XX XX                  ;COORDONNES VERTEX 1

YY YY  XX XX                  ;COORDONNES VERTEX 2

YY YY  XX XX                  ;COORDONNES VERTEX 3

 

 

; Draw 4 point polygon

; $A0 -> ptr mesh (X,Y,Z,0)

DrawTrRect:

                    ;RECTANGLE SIMPLE

                    ;GET X1

                    lw R10,0($A0)                                  ;V0

                    lw R11,8($A0)                                  ;V1

                    lw R12,16($A0)                                ;V2

                    lw R13,24($A0)                                ;V3

                    ; 4 points polygone mono

                    lui R8,0x2880                                   ; RECT 4 point polygone                                                      

                    ori R8,R8,0x8080                            ; GRAY

                    sw R8,0x1810(R27)

                    ;VERTEX 0

                    sw R10,0x1810(R27)  ; SEND COORDS Y|X

                    ;VERTEX 1

                    sw R11,0x1810(R27)  ; SEND COORDS Y|X

                    ;VERTEX 2                  (3)

                    sw R12,0x1810(R27)  ; SEND COORDS Y|X

                    ;VERTEX 3

                    sw R13,0x1810(R27)  ; SEND COORDS Y|X

.End:

                    jr R31                                                                    

                    nop

 

Commande 0x2C Quad texturé.

2C   BB GG RR

YY YY  XX XX                  ;COORDONNES VERTEX 0

ID CLUT               V    U     ;IDCLUT=0          Coordonne de Texture v u vertex 0

YY YY  XX XX                  ;COORDONNES VERTEX 1

TP                          V    U     ;Texture page         Coordonne de Texture v u vertex 1

YY YY  XX XX                  ;COORDONNES VERTEX 2

N/A                        V   U      ;N/A=0   v u vertex 2

YY YY  XX XX                  ;COORDONNES VERTEX 3

N/A                        V   U      ;N/A=0   v u vertex 3

 

; Draw 4 point polygon texture

; Assume 256x256 texture located on 768,0 in VRAM

; $A0 -> ptr mesh (X,Y,Z,0)

DrawTrRect:

                    ;RECTANGLE SIMPLE

                    ;GET X1

                    lw R10,0($A0)                                  ;V0

                    lw R11,8($A0)                                  ;V1

                    lw R12,16($A0)                                ;V2

                    lw R13,24($A0)                                ;V3

                    ; 4 points polygone Mono Texture

                    lui R8,0x2C80                                  ; RECT 4 point polygone                                                      

                    ori R8,R8,0x8080                            ; GRAY

                    sw R8,0x1810(R27)

                    ;VERTEX 0

                    sw R10,0x1810(R27)  ; SEND COORDS Y|X

                    ori R9,R0,0xFF00       ; CLUT ID=0 TU= 0 TV=0xFF

                    sw R9,0x1810(R27)    ; SEND

                    ;VERTEX 1

                    sw R11,0x1810(R27)  ; SEND COORDS Y|X

                    lui R9,0x010C              ; TEXTURE PAGE X=12 (768) Y=0

                                                             ; TU= 0x00 TV=0x00

                    sw R9,0x1810(R27)    ; SEND

                    ;VERTEX 2                  (3)

                    sw R12,0x1810(R27)  ; SEND COORDS Y|X

                    ori R9,R0,0xFFFF      ; TU= 0xFF TV=0xFF

                    sw R9,0x1810(R27)    ; SEND

                    ;VERTEX 3

                    sw R13,0x1810(R27)  ; SEND COORDS Y|X

                    ori R9,R0,0x00FF       ; TU= 0xFF TV=0x00

                    sw R9,0x1810(R27)    ; SEND

.End:

                    jr R31                                                                    

                    nop

 

 

13-                      DOUBLE BUFFER

 

Comme on en sait suffisamment pour commencer à envoyer plusieurs centaines de polygones, il est temps d’implémenter le double-buffer.

 

Vous avez sans doute remarquez lors de la partie consacre à une configuration du GPU que celle-ci possède toutes les fonctions nécessaire pour implémenter facilement un double buffer.

GPU1:

CMD 0x05 DISPLAY AREA

Paramètres     bit  $00-$09  X (0-1023)               bit  $0A-$12  Y (0-512)

Définition des coordonnées de départ de la zone à afficher            

GPU0:

DRAW AREA OFFSET (0xE5):

Word 0:  0xE3 00 PP PP PP                Paramètres PP :      X=b0-b9                Y=b10-b19

 

Grace à elle, on peut changer de surface d’affichage sans changer les routines de dessins.

En effet supposons que l’on veuille utiliser 2 tampons de 320*240 en (0,0) (A) et (0,256) (B). On démarre par exemple comme écran d’affichage (0,0) et écran de dessin (0,256) en configurant avec le Display Area. Il nous suffit dans ce cas et en fonction de changer les paramètres DrawArea Offset.

Avant de permuter il faut juste contrôler le statut IDLE du GPU et le retour verticale et voilà ! Implémenter un double-tampon n’a jamais était aussi facile J

 

Fenêtrage ou Clipping:

GP0(E3h) - Set Drawing Area top left (X1,Y1)
GP0(E4h) - Set Drawing Area bottom right (X2,Y2)

  0-9    X-coordinate (0..1023)

  10-19  Y-coordinate (0..1023) (really 10bit, not 9bit) (should be 512 max)

  20-23  Not used (zero)

  24-31  Command  (Exh)

 

Grace aux fonctions « Drawing area »0xE3 et 0xE4. Le programmeur peut s’affranchir des problèmes de clipping en X et Y permettant également une implémentation facile de multifenêtres.

 

Config1 :

X start=0 ;Xend=319 (13F)                 Y Start=0;Y End=255 (0xFF)            

0xE300 0000                                        TOP,LEFT=0,0

0xE403 FD3F                                      BOTTOM RIGHT=255,319

Config2

X start=0 ;Xend=319 (13F)                 Y Start=256(0x100);Y End=511 (0x1FF)         

0xE304 0000                                        TOP,LEFT=256,0

0xE407 FD3F                                      BOTTOM,RIGHT=511,319

 

En revanche il revient au programmeur de clipper les sommets dont la profondeur se situe derrière le plan de projection pour la 3D. Il s’agira surtout d’exclure ou non un polygone et éventuellement d’interpoler les coordonnées de texture.

De plus lorsque un polygone est clipper, suivant certaine situation, il consomme des cycles GPU donc il vaut mieux dans certaine situation clipper soit même.

 

;DOUBLE BUFFER

;ALWAYS CALL INIT SURFACE BEFORE USING

;REQUIERD GPUINIT

InitSurface:

                    lui R2,>CurSurface

                    ori R2,R2,CurSurface

                    sw R0,0(R2)                                       ;Init State 0

                    ;DISPLAY AREA 0,0 320*256

                    ;DISPLAY AREA => WHERE TO DISPLAY (TOP/LEFT)

                    lui R3,0x0500                                   ; CMD=0x05 0,0

                    sw R3,0x1814(R27)                         ; SEND TO GPU1

                    ;SET DRAW AREA OFFSET => WHERE DRAW

                    lui R3, 0xe508                                  ; DRAW OFFSET Y 256

                    sw R3,0x1810(R27)

                    ;CLIPING AREA Start 0,256

                    lui R2,0xE304

                    sw R2,0x1810(R27)

                    ;CLIPING AREA END 319,511

                    lui R3,0xE407

                    ori R3,R3,0xFD3F

                    sw R3,0x1810(R27)

                    jr R31                                                

                    nop

;FLIP FRONT<->BACK BUFFER

FlipSurface:

                    lui R3,>CurSurface

                    ori R3,R3,CurSurface

                    LW R2,0(R3)

                    nop

                    Beq R2,R0,.@1                                 ;Si State=0 state come to 1

                    Nop

                    ;STATE 1->0 Front=(0,0) Back=(0,256)

                    ;DISPLAY AREA => WHERE TO DISPLAY (TOP/LEFT)

                    lui R3,0x0500                                   ; CMD=0x05 0,0

                    sw R3,0x1814(R27)                         ; SEND TO GPU1 

                    ;SET DRAW AREA OFFSET => WHERE DRAW

                    lui R2, 0xe508                                  ; DRAW OFFSET 256

                    sw R2,0x1810(R27)

                    ;CLIPING AREA Start 0,256

                    lui R2,0xE304

                    sw R2,0x1810(R27)

                    ;CLIPING AREA END 319,511

                    lui R3,0xE407

                    ori R3,R3,0xFD3F

                    sw R3,0x1810(R27)

                    J .End

                    Nop

.@1:

                    ;STATE 0->1 Front=(0,256) Back=(0,0)

                    ;DISPLAY AREA => WHERE TO DISPLAY (TOP/LEFT)

                    lui R2,0x0504                                   ; CMD=0x05 offset 0,256

                    sw R2,0x1814(R27)                         ; SEND TO GPU1 

                    ;SET DRAW AREA OFFSET => WHERE DRAW

                    lui R3, 0xe500                                  ; DRAW OFFSET Y0

                    sw R3,0x1810(R27)

                    ;CLIPING AREA Start 0,0

                    lui R2,0xE300

                    sw R2,0x1810(R27)

                    ;CLIPING AREA END 319,255

                    lui R3,0xE403

                    ori R3,R3,0xFD3F                          ;B9-B0 x=0x3FF   0xF FCxx =>

                    sw R3,0x1810(R27)

.End:

                    ;lui R2,0x0300                                 ;DISPLAY ENABLE

                    ;sw R2,0x1814(R27)                       ;SEND TO GPU

                    lui R3,>CurSurface

                    ori R3,R3,CurSurface

                    LW R2,0(R3)

                    nop

                    Xori R2,R2,1                                    ; Inverse l’etat

                    jr R31                                                 ; RETOUR

                    sw R2,0(R3)                                       ;Mémorise l’etat

 

;CLEAR BACK BUFFER

ClearBackBuffer:

                    lui R3,>CurSurface

                    ori R3,R3,CurSurface

                    lw R2,0(R3)

                    nop

                    bne R2,R0,.@1                                 ; STATE 0

                    lui R3,0x0000                                   ; TOP

                    lui R3,0x0100                                   ; TOP=256

.@1:

                    lui R2,0x0200                                   ; 0x02 FILL DRAW BUFFER B=0

                    ori R2,R2,0x0000                            ; GR=0

                    lui R4,0x0100                                   ; HEIGHT 256

                    ori R4,R4,0x0140                            ; WIDTH 320

                    sw R2,0x1810(R27)                         ; SEND CMD TO GPU0

                    sw R3,0x1810(R27)                         ; SEND PARAM TO GPU0

                    sw R4,0x1810(R27)                         ; SEND PARAM TO GPU0

                    jr R31                                                 ; RETOUR

                    nop

CurSurface:

                    word           0                                      ;0 ->Back=(0,256) 1->Back=(0,0)

 

Exemple d’utilisation:

               Jal InitSurface

                    nop

LoopMain:

                    jal WaitGPUReady

                    nop

                    jal ClearBackBuffer 

                    nop

                    ; OTHER DRAWS

                    ; WITH I/O call WaitGPUReady

                    ;……………………………..

                    jal WaitGPUIdle

                    nop

                    jal WaitGPUReady

                    nop

                    jal WaitVSyncOnly   

                    nop

                    jal FlipSurface

                    nop

                    j LoopMain

                    nop

 

 

 

14-                      DMA

 

 

La DMA permet au chip spécialisé comme le GPU ou le SPU de s’affranchir du CPU est d’aller lire ou écrire directement de la RAM vers la VRAM (GPU) ou la SPU-RAM(SPU).

L’utilisation de la DMA est indispensable pour le transfert de grosse quantité de données à grande vitesse dans la VRAM ou la SRAM.

 

Les registres DMA sont mappés entre 0x1f80_1080 and 0x1f80_10f4. Il y a 6 canaux DMA.

Base Address

Channel Number

Device

0x1f80_1080

DMA channel 0

MDECin

0x1f80_1090

DMA channel 1

MDECout

0x1f80_10a0

DMA channel 2

GPU (lists + image data)

0x1f80_10b0

DMA channel 3

CD-ROM

0x1f80_10c0

DMA channel 4

SPU

0x1f80_10d0

DMA channel 5

PIO

0x1f80_10e0

DMA channel 6

GPU OTC (reverse clear the Ordering Table)

 

Chaque base est structurée de la même façon:

DMA Memory Address Register (D_MADR) 0x1f80_10n0        MADR Pointer to the virtual address the DMA will start reading from/writing to.

31

0

MADR

 

 

DMA Block Control Register (D_BCR) 0x1f80_10n4

B31-B16 BA: NOMBRE DE BLOC A COPIER OU LIRE

B15-B0   BS: TAILLE D’UN BLOC

ATTENTION, Le GPU et le SPU sont limités à un bloc de 16 words.

 

DMA Channel Control Register (D_CHCR) 0x1f80_10n8

0

TR

0

LI

CO

0

DR

7

1

13

1

1

8

1

TR 0 No DMA transfer busy. 1 Start DMA transfer/DMA transfer busy. 

LI 1 Transfer linked list. (GPU only).

CO 1 Transfer continuous stream of data.

DR 1 Direction from CPU to RAM Componements 0 Direction from memory componements to CPU.

 

DMA Interrupt Control Register (DICR) 0x1f80_10f4

Utilisation inconnue, il faudra que je vérifie moi-même sur une vrai PSX. Je suppose que c’est pour déclencher une exception lorsque le transfert DMA est terminé ?

La structure doit être proche du registre DPCR.

 

Mais avant d’utiliser la DMA nous devons indiquer au registre de contrôle général quel canal DMA nous souhaitons utiliser :

DMA Primary Control Register (DPCR) 0x1f80_10f0

Ou chaque canal est contrôlé par 4 bits :

B3-B0                    Channel 0              0x0000 000F

B4-B7                    Channel 1              0x0000 00F0        

B11-B8                  Channel 2              0x0000 0F00

B15-B12                Channel 3              0x0000 F000        

B19-B16                Channel 4              0x000F 0000

ETC

Etat initial de DPCR=0x0000 9099    1001(CH3)            0000(CH2)             1001(CH1)            1001(CH0) 

A priori, pour mettre en marche un canal nous devons écrire dans ces registres on écrit la valeur de 1 sur le 4eme bit. Les 3 autres bits indiquent le niveau de propriétés.

Mais les exemples pour le SPU écrivent B3=1 B2=1 B1=0 B0=1.

Par exemple on peut mettre en marche le canal 2 (GPU) comme ceci :

              

               LUI             R27,0x1F80                  ; R27 pointe vers les registres I/O

                    ORI            R8,R0,0x0800              ; R8=0x0800 (CHANNEL 2)

            SW              R8,0x10F0(R27)          ; DMA Canal 2 actif (GPU)

            LUI             R8,0x0008                    ; R8=0x0008 0000 (CHANNEL 4)

            SW              R8,0x10F0(R27)          ; DMA Canal 4 actif (SPU)

            NOP                                                    ; Write Delay

 

 

Exemple transfert de données son dans le SPU-BUFFER

 

Pour transférer un échantillon dans le tampon du SPU on procède comme suit :

 

-          On écrit dans le B5B4 du SPUCNT(0x1DAA) la valeur de 2 pour dire au SPU que l’on va effectuer un transfert RAM->SRAM

-          On lit dans le SPUSTAT(0x1DAE) si les les B5-B4 sont allumé-éteint respectivement sinon on attend

-          On écrit 4 dans le registre SPUTRANSCTRL (0x1DAC)  pour indiquer un transfert normal (bit 2 allumé).

-          On indique dans le registre SPUADR (0x1DA6) l’adresse de destination du tampon SPU en écrivant la valeur de l’ADRESSE divisée par 8.

-          On allume le canal DMA 4 du SPU si ce n’est pas fait DPCR(0x10F0)

-          On indique au canal DMA l’adresse source de la RAM dans le registre MADR du canal 4 (0x10C0)

-          On indique dans le DMA CTRL BLOCK (0x10C4) la taille du bloque (16 words) et le nombre de blocs

-          On démarre le transfert par le Registre DMA CHCR du canal 4 (0x10C8) avec la valeur 0x0100 0201 (Start DMA ; TR=1 ; CO=1 ; DR=1)

-          Eventuellement, on attend la fin du transfert en surveillant l’état du Bit TR du CHCR du canal 4 (0x10C8) TR=1 Busy TR=0 fini.

 

 

;LOAD SOUND WITH DMA

; $A0 *buffer               $A1=SIZE $A2=SPU-BUFFER ADRESS

LoadSoundDMA:

                    ;PUSH R31

                    addi R29,R29,0xFFFC                                       ; R29=R29-4                

                    sw R31,0(R29)                                                      ; PUSH R31

                    ;I/O BASE

                    lui R27,0x1F80                                                    ; R27 => I/O BASE

                    ;SET SPU ADRESS

                    or R8,R0,R6                                                          ; R8=R6 SPU ADRESS BUFFER

                    srl R8,R8,3                                                            ; DIVIDE BY 8

                    sh R8,0x1DA6(R27)                                            ; SET ADRESS SOUND

                    nop

;SPU_CONTROL SET TO DMA *SPU_REG0 = (*SPU_REG0 & 0xFFCF) | 0x0020;

                    lh R8,0x1DAA(R27)                        ; GET SPU_CONTROL REGISTER

                    nop                                                      ; Load delay

                    andi R8,R8,0xFFCF                       ; Clear B4-B5

                    ori R8,R8,0x0020                            ; And Set to B5=1 B4=0

                    sh R8,0x1DAA(R27)                       ; SET DMA WRITE

                    nop

;Wait as DMA Write mode is effective

;SET SOURCE ADRESS *SPUDMA_MEMADDR = src_addr;       //0x1F8010C0

                    sw $A0,0x10C0(R27)                      ; $A0=SOUND PTR

;SET SIZE BA (Block amount) BS(Block Size=16 WORD) SPUDMA_BCR 0x1F8010C4

                    ori R8,$A1,R0                                                      ; R8=size

                    srl R8,R8,4                                                            ; Nb Block=SIZE/16

                    sll  R8,R8,16                                                          ; NB BLOCK ON UPPER HALF

                    ori R9,R0,0x0010                                                ; one block=64 bytes or 16 words

                    or R9,R9,R8                                                          ; R9=R8|R9

                    sw R9,0x10C4(R27)                        ; SET SIZE TO TRANSFERT

;START TRANSFERT *SPUDMA_CHCR = 0x01000201;                                                       //0x1F8010C8

                    lui R8,0x0100

                    ori R8,R8,0x0201                                                ; R8= 0x01000201

                    sw R8,0x10C8(R27)                        ; START DMA WRITE

;while(*SPUDMA_CHCR & 0x01000000) ;

.WaitDMA1:

                    lw R9,0x10C8(R27)                       

                    lui R10,0x0100                                                     ; TEST B24

                    and R9,R9,R10                                                    

                    bne R9,R0,.WaitDMA1                                      ; IF B24=1 DMA IS ON SO WAIT

                    nop            

                    ;POP R31

                    lw R31,0(R29)                                                       ; POP R31

                    nop                                                                          ; LOAD DELAY

                    jr R31                                                                     ; RETOUR

                    addi R29,R29,0x0004                                         ; USE DELAY TO COMPLETE POP

 

Est-ce que celui la marche ?? A Tester lors de test avec les textes aussi. Il semblerait qu’il faille impérativement lire le registre 0x1DAA avant de transférer pour vider les tampons W et rendre effective l’écriture.

La lecture seul de DMA write request du registre 0x1DAE ne suffit pas. A re-tester par occasion.

Voir LOADSOUNDDMA4 qui fonctionne correctement.

 

Remarque diverses sur la DMA :

-          Apparemment un seul canal peut effectuer un transfert dans le même temps. La priorité des canaux peuvent être configurées.

-          Si par exemple un canal est en train de transférer, et que vous lancer un autre canal dont la priorité est plus haute, le premier transfert en cours sera tout bonnement interrompu.

-          Un transfert d’image en VRAM via DMA ne fonctionne pas toujours je ne sais pas précisément encore pourquoi.

 

 

15-                      Affichage par Linked List

 

Lorsque nous avons plusieurs milliers de polygones à dessiner, il vaut mieux utiliser la DMA que bloquer le processeur par I/O. De plus l’execution par DMA est beaucoup plus rapide que par I/O.

Une linked list sera une liste chainee de commande à envoyer au GPU.

L’utilisation de la DMA et d’une liste de commandes nous assure une vitesse optimale et une grande flexibilité pour l’affichage.

 

Un nœud d’une linked list est composé de cette façon :

Adresse                  TYPE                                    Description

0                             WORD   nn xx xx xx                           nn est le nombre de word que comprend la commande, xx xx xx est le pointeur sur le prochain noeud

For( i=0 i<nn ;i++) {

4*i+4                     WORD   cc cc cc cc                             commande GPU + parametres

}

 

Par exemple Supposons que l’on veuille envoyer une commande qui efface l’écran et ensuite une autre commande qui dessine un triangle rouge et que notre liste se trouve à l’adresse 0x80020000

 

ADDRESSE         COMMANDE      MEMOIRE           COMMENTAIRES

0x80020000           03 02 00 10            10 00 02 03            ; 3 word commandes ; next node= 0x2010

0x80020004           02 00 00 00            00 00 00 02            ; CMD 0x2 BLACK

0x80020008           00 00 00 00            00 00 00 00            ;TOP=LEFT=0

0x8002000C          01 00 01 40            40 01 00 01            ;H=256;W=320

0x80020010           04 FF FF FF          FF FF FF 04          ;word 4 commandes; fin de liste

0x80020014           20 00 00 FF           FF 00 00 20           ;TRIANGLE ROUGE

0x80020018           00 D0 00 20           20 00 D0 00           ;32;208

0x8002001C          00 D0 00 A0          00 20 D0 00           ;160,32

0x80020020           00 00 01 08                                          ;256,320

Etc.

 

LittleEndian : Attention dans la mémoire on aura la représentation inverse !

 

LINKEDDOTLIST :

                               ;ADD DOT ENTRY

                                         ;2 WORD COMMAND AND POINT TO NEXT

                                         ;$S4 point to current node of linked list

                    .Loop:

                                         ………………..

                                         ;R8 and R9 contains command and parametres,

                                         lui R12,0x00FF

                                         ori R12,R12,0xFFFF ;R12 MASK HEADER xxFFFFFF

                                         addi R10,$S4,12                              ;R10=NEXT NODE HEADER=>WORD HEADER+2 WORD CMD

                                         and R10,R10,R12                            ;R10=00XXXXXX where XX is the next node adress

                                         ori R11,R0,2                                     ;2 cmd

                                         sll R11,R11,24                                  ;cmd nn in R11 ->nnXX XXXX

                                         or R10,R10,R11                               ;R10=HEADER LINKED LIST

                                         sw R10,0($S4)                                   ;WRITE HEADER

                                         sw R8,4($S4)                                     ;CMD 1

                                         sw R9,8($S4)                                     ;CMD 2

                                         ;sw R8,0x1810(R27)

                                         ;sw R9,0x1810(R27)

                                         ;NEXT LIST

                                         addi $S4,$S4,12                               ;NEXT NODE

                                         …………………..

               .EndLoop :

                                         addi $S4,$S4,0xFFF4                     ;-12

                                         lw R10,0($S4)                                   ; Read the last note header

                                         Lui R12,0x00FF                              ;R12->MASK end list

                                         Ori R12,R12,0xFFFF                     ;R12=0x00FFFFFF

                                         or R10,R10,R12                               ; mark header as end is with OR 0xnnFFFFFF

                                         sw R10,0($S4)                                   ; WRITE THE NODE

                              

Supposons $S4 = 0x8004 0000 c’est a dire l’adresse de la linked List. Et trois commande dot : Bleu 0,0 Vert 1,0 et Rouge en 1,1

Pixel (0x68):

Word 0:  0x68 BB GG RR                  BB GG RR désignent les couleurs bleu, vert et rouge respectivement codées sur 15 bits (le bit supérieur pour la transparence).

Word 1:  YY YY XX XX                   Coordonnées vertical (Y) et horizontal (X) du pixel SIGNEES

On aura:

ADDRESSE                        COMMANDE                      MEMOIRE                          COMMENTAIRE

0x8004 0000                         02 04 00 0C                          0C 00 04 02                          2 Commande list suivant en 0x800400C + 12 (header + 2 word cmd)

0x8004 0004                         68 FF 00 00                          00 00 FF 68                           PIXEL BLEU

0x8004 0008                         00 00 00 00                           00 00 00 00                           POSTION 0,0

0x8004 000C                        02 04 00 18                           18 04 00 02                           2 Commande list suivant en 0x8004018 + 12 (header + 2 word cmd)

0x8004 0010                         68 00 FF 00                          00 FF 00 68                           PIXEL VERT

0x8004 0014                         00 00 00 01                           01 00 00 00                           POSITION 1,0

0x8004 0018                         02 FF FF FF                         FF FF FF 02                          2 Commande DERNIERE COMMANDE

0x8004 001C                        68 00 00 FF                          FF 00 00 68                           PIXEL ROUGE

0x8004 0020                         00 01 00 01                           01 00 01 00                           POSITION 1,1

 

Pour envoyer notre linked list. On procède ainsi.

-          On utilise la fonction WaitGPUIdle en attendant que le bit 27 soit allumé indiquant que le GPU est prêt à recevoir une linkedlist.

 

-          On allume le canal 2 (GPU) de la DMA si ce n’a pas été fait en écrivant dans le DPCR(0x1F80 10F0)

ORI                 R8,R0,0x0800              ; R8=0x0800

SW                  R8,0x10F0(R27)          ; DMA Canal 2 actif (GPU)

 

-          Indique au GPU que l’on va effectuer un transfert de la RAM vers la VRAM avec la commande GPU1 0x04 avec le paramètre 0x0002

LUI                 R8,0x0400                    ; CMD GPU1 SET DMA

ORI                 R8,R8,0x0002              ; SET ENABLE DMA FOR RAM TO VRAM

SW                  R8,0x1814(R27)          ; SEND TO GPU1

 

-          Indique l’adresse de notre linked list dans le registre MADR du canal 2 (0x1F80 10A0)

LUI                 R8,>LinkedList          

ORI                 R8,R8,LinkedList       ; R8->LinkedList

SW                  R8,0x10A0(R27)         ; SET ADRESS

 

-          Mettre le registre BCR du canal 2 (0x1F80 10A4) à 0 étant donné qu’il s’agit d’un transfert d’une linked list

SW                  R800x10A4(R27)        ; LINKED LIST SO NO SIZE INDICATION

 

-          Démarrer le transfert en allumant les bits 24 (Start transfert), 10(Linked List) et 0 (direction RAM->VRAM) dans le registre CHCR du canal 2 (0x1F80 10A8)

LUI                 R8,0x0100                    ; b24=1

ORI                 R8,R8,0x0401              ; b10=1 ;b0=1

SW                  R8,0x10A8(R27)         ; START DMA TRANSFERT

 

Le programme suivant est un exemple complet d’utilisation d’une linkedlist en utilisant que la commande Dot.

 

ORG 0x8001 8000

Start:

                    ;R27 POINT TO I/O BASE

                    lui R27,0x1F80

                    jal InitStdGPU

                    nop

                    ;Important reinitialise la table d'interruption

                    jal ResetEntryInt

                    nop

                    ;PREPARE function PAD_INIT

                    addi $A0,R0,0x0001

                    jal InitCard

                    nop

                    jal StartCard

                    nop

                    jal BuInit

                    nop

                    jal StopCard

                    nop

                    jal PAD_INIT

                    nop

                    ;R27 POINT TO I/O BASE AGAIN

                    lui R27,0x1F80

                    ;Double buffer

                    jal WaitGPUReady

                    nop

                    jal GPUClearVRAM

                    nop

                    jal InitSurface

                    nop

Main:

.MainLoop:

                    jal WaitVSync

                    nop

                    lui $S1,>PadDataI

                    ori $S1,$S1,PadDataI

                    lw $S0,0($S1)

                    ori $T6,R0,0x0100                         ;BUTTON SELECT

                    beq $T6,$S0,.select                         ;END PROGRAM

                    nop

                    j .LoopMainEnd

                    nop

.Select:

                    ;QUIT

                    j .End

                    nop

.LoopMainEnd:

.Draw:

                    jal WaitGPUReady

                    nop

                    jal ClearBackBuffer                      ;CLEAR BACKBUFFER

                    nop

                    jal DrawDotSINDMA

                    nop

                    lui $A0,>LinkedList

                    ori $A0,$A0,LinkedList

                    jal SendLinkedList

                    nop

.EndDraw:

                    jal WaitGPUIdle

                    nop

                    jal WaitGPUReady

                    nop

                    jal WaitVSyncOnly   

                    nop

                    jal FlipSurface

                    nop

                    j .MainLoop

                    nop

.End:

                    jr R31

                    nop

include "MIPS\\PSXGPU.asm"

                    nop            

include "MIPS\\PSXSYSTEM.asm"

                    nop

;DMA SINUS POINT

DrawDotSINDMA:

                    ;PUSH

                    addi R29,R29,0xFFD0                   ; R29=R29-48              

                    sw R31,8(R29)                                  ; PUSH R31

                    sw $S0,12(R29)

                    sw $S1,16(R29)

                    sw $S2,20(R29)

                    sw $S3,24(R29)

                    sw $S4,28(R29)

                    sw $S5,32(R29)

                    sw $S6,36(R29)

                    ;UPDATED TIME

                    lui $S0,>.Time

                    ori $S0,$S0,.Time

                    lw $S6,0($S0)

                    nop

                    addi $S6,$S6,17                               ;TIME + 17

                    andi $S6,$S6,0xFFFF

                    sw $S6,0($S0)                                   ;NEW TIME

                    ;LINKED LIST POINTER

                    lui $S4,>LinkedList

                    ori $S4,$S4,LinkedList

                    lui $S5,>.SINTAB

                    ori $S5,$S5,.SINTAB

                    ;DRAW FOR EACH

                    ori $S2,R0,160                                 ;LINES

.@Y:

                    ori $S3,R0,240                                 ;SAMPLES

                    .@X:

                                         ;GET COLOR IN FUNCTION OF TIME AND Y

                                         or R9,$S3,R0                                    ;R9=curX

                                         add R9,R9,$S2                                 ;+curY

                                         add R9,R9,$S6                                 ;R9=curX+TIME

                                         andi R9,R9,0x00FF                        ;R9=&0xFF

                                         sll R9,R9,1                                         ;ALIGN half

                                         addu R9,R9,$S5                               ;R9=idx+SINTAB

                                         lhu $S1,0(R9)                                    ;$S1=color

                                         ;LINKED LIST NOD

                                         lui R8,0x6800

                                         or R11,R0,$S1                                  ;COLOR TIME

                                         andi R11,R11,0x00FF                    ;ONLY 8 BIT COMP IN R11

                                         sll R12,R11,8                                    ;R12=0xNN00

                                         sll R13,R11,16                                  ;R13=0xNN0000

                                         or R11,R11,R12                               ;R11=0x00NNNN

                                         or R13,R13,R11                               ;R13=0x00NN NNNN

                                         or R8,R8,R13                                   ;R8=0x68NN NNNN

                                         addi R9,$S2,40                                 ;R9=Y+8

                                         sll R9,R9,16                                      ;R9=Y<<16

                                         or R10,$S3,R0                                  ;R10=X

                                         addi R10,$S3,40                              ;X+64

                                         andi R10,R10,0xFFFF

                                         or R9,R9,R10                                   ;Y|X

                                         ;ADD DOT ENTRY

                                         ;2 WORD COMMAND AND POINT TO NEXT

                                         lui R12,0x00FF

                                         ori R12,R12,0xFFFF                      ;R12 MASK HEADER xxFFFFFF

                                         addi R10,$S4,12                              ;R10=NEXT NODE HEADER=>WORD HEADER+2 WORD CMD

                                         and R10,R10,R12                            ;R10=00XXXXXX where XX is the next node adress

                                         ori R11,R0,2                                     ;2 cmd

                                         sll R11,R11,24                                  ;cmd nn in R11 ->nnXX XXXX

                                         or R10,R10,R11                               ;R10=HEADER LINKED LIST

                                         sw R10,0($S4)                                   ;WRITE HEADER

                                         sw R8,4($S4)                                     ;CMD 1

                                         sw R9,8($S4)                                     ;CMD 2

                                         ;NEXT LIST

                                         addi $S4,$S4,12                               ;NEXT NODE

                                         ;LOOP X

                                         bne $S3,R0,.@X                               ;FOR X>0 LOOP @X

                                         addi $S3,$S3,0xFFFF                    ;X--

                    ;LOOP Y

                    bne $S2,R0,.@Y                                                   ;FOR Y>0 LOOP @Y

                    addi $S2,$S2,0xFFFF                                         ;Y--

                    ;ADD END ENTRY ; FOR THE PREVIOUS POINTER LIST OR 0xnnFFFFFF

                    addi $S4,$S4,0xFFF4                                         ;-12

                    or R10,R10,R12                                                   ;R10 should contain header and R12 the mask so mark as end is with 0xnnFFFFFF

                    sw R10,0($S4)

                    ;POP RET

                    lw R31,8(R29)                                 

                    lw $S0,12(R29)                                

                    lw $S1,16(R29)                                

                    lw $S2,20(R29)                                

                    lw $S3,24(R29)                                

                    lw $S4,28(R29)                                

                    lw $S5,32(R29)                                 ;

                    lw $S6,36(R29)

                    jr R31                                                                    

                    addi R29,R29,0x0030                   

.Time:

                    word 0

.SINTAB:

                    half 128,131,134,137,140,143,146,149,152,155,158,161,164,167,170,173

                    half 176,179,182,185,187,190,193,195,198,201,203,206,208,210,213,215

                    half 217,219,222,224,226,228,230,231,233,235,236,238,240,241,242,244

                    half 245,246,247,248,249,250,251,251,252,253,253,254,254,254,254,254

                    half 255,254,254,254,254,254,253,253,252,251,251,250,249,248,247,246

                    half 245,244,242,241,240,238,236,235,233,231,230,228,226,224,222,219

                    half 217,215,213,210,208,206,203,201,198,195,193,190,187,185,182,179

                    half 176,173,170,167,164,161,158,155,152,149,146,143,140,137,134,131

                    half 128,125,122,119,116,113,110,107,104,101,98,95,92,89,86,83

                    half 80,77,74,71,69,66,63,61,58,55,53,50,48,46,43,41

                    half 39,37,34,32,30,28,26,25,23,21,20,18,16,15,14,12

                    half 11,10,9,8,7,6,5,5,4,3,3,2,2,2,2,2

                    half 1,2,2,2,2,2,3,3,4,5,5,6,7,8,9,10

                    half 11,12,14,15,16,18,20,21,23,25,26,28,30,32,34,37

                    half 39,41,43,46,48,50,53,55,58,61,63,66,69,71,74,77

                    half 80,83,86,89,92,95,98,101,104,107,110,113,116,119,122,125

;Fonction sendlinkedlist

;$A0 List TO SEND   

SendLinkedList:

.GPUNOTIDLE:

                    lw R2,0x1814(R27)                        

                    lui R3,0x1000                                   ;MASK TEST B27 GPU BUSY

                    and R2,R2,R3

                    bne R2,R3,.GPUNOTIDLE           ;NO 0x0400 0000 so WAIT

                    nop

                    ;START DMA CHANNEL 2

                    ori R2,R0,0x0800                            ; R8=0x0800 CHANNEL 2

                    sw R2,0x10F0(R27)                        ; DMA Canal 2 actif (GPU)

                    ;SETUP THE GPU DMA

                    lui R3,0x0400                                   ; CMD GPU1 SET DMA

                    ori R3,R3,0x0002                            ; SET ENABLE DMA FOR RAM TO VRAM

                    sw R3,0x1814(R27)                         ; SEND TO GPU1

                    ;SETUP THE MADR

                    sw $A0,0x10A0(R27)                      ;MDAR_2

                    ;SETUP THE SIZE

                    sw R0,0x10A4(R27)                        ;BCR_2

                    ;START THE DMA TRANSFERT

                    lui R2,0x0100

                    ori R2,R2,0x0401

                    sw R2,0x10A8(R27)                        ;START TRANSFERT

                    nop

;WAIT DMA FINISH

.WaitDMA2:

                    lw R3,0x10A8(R27)                        ;DMA CHCR_2                               

                    lui R2,0x0100                                   ;TEST B24

                    and R3,R3,R2                                                      

                    beq R3,R2,.WaitDMA2                  ;IF B24=1 DMA IS BUSY SO WAIT

                    nop

                    jr R31

                    nop

LinkedList:

                    word 0

 

 

LinkedList01.asm

 

Les linked list sont également presque indispensable pour la 3D dans la mesure où l’on doit traiter un certain nombre de données: on transféra directement les données dans une commande que l’on insèrera dans une linked list. Par exemple on considère une liste de triangle, on traite chaque triangle en transformant ces sommets, ensuite on applique le clipping des interpolations de textures et ensuite on envoie dans la liste la commande(s) et on passe au triangle suivant.

La Playstation ne possédant pas de Z-buffer, les linked list peuvent résoudre partiellement le problème : Soit en  respectant l’ordre de profondeur durant l’insertion dans la liste (insertion minmax) ou bien une série de liste en fonction de la profondeur : si par exemple on crée 64 pointeurs de tête dont chacune contient une liste de polygone en fonction de leur valeur moyenne de profondeur on obtient ainsi un pseudo-zbuffer de 6bits, la DMA va ensuite parcourir la liste en utilisant l’algo du dessinateur (du plus éloigné au plus près) et résoudra le problème par recouvrement.

C’est la façon la plus simple de palier le problème de profondeur et celle utilisée par la LIB officielle et la DMA possède également une fonction pour initialiser une table OT qui contient une table de pointeurs de tête initialisé

 

 

16-                      Son : SPU

 

Le SPU est un chip sonore de 16 bits avec une mémoire de 512KO et 24 voix programmable. Son bus de données est sur 16 bits. D’après le service manual, il est cadencé à 4,19MHZ donc ATTENTION : Le R3000 est beaucoup plus rapide que le SPU donc lorsque nous écrivons sur les ports qui auront une influence directe sur les prochaines instructions (lecture, transfert), il est conseillé d’attendre un petit peu.

La mémoire devra contenir des échantillons ADPCM en gros 512 KO d’ADPCM PSX équivalent à 2Mo de PCM. Pour ma part cette ADPCM est le principal reproche que je fais à ce chip car le résultat est un peu bizarre. (Ce n’ai pas du tout handicapant de façon général mais des fois on a envie de générer autre chose que du son avec un chip son lol ).

 

Le SPU sera vu en détail lors d’un chapitre spécifique. Pour l’instant le but du jeu est uniquement qu’une des voix (sur les 24 disponibles) de la PSX nous lisent un échantillon ADPCM. Nous avons déjà vu dans la section DMA comment transférer des échantillons ADCPM en SPU-Ram. Pour l’instant on s’en tape : l’adresse 0x1010 devrait contenir des parasites que nous lirons

Avant de voir le SPU il faut d’abord savoir comment celui-ci interprète les données de sa mémoire tampon de 512 KO.

 

Attention le SPU est un autre composant que les émulateurs émulent justement très mal surtout le timing d’écriture/lecture dans le registre.

 

Analysons les registres du SPU. Ils sont tous de 16 bits.

 

1F801DAAh - SPU Control Register (SPUCNT)

  15    SPU Enable              (0=Off, 1=On)       (Don't care for CD Audio)

  14    Mute SPU                (0=Mute, 1=Unmute)  (Don't care for CD Audio)

  13-10 Noise Frequency Shift   (0..0Fh = Low .. High Frequency)

  9-8   Noise Frequency Step    (0..03h = Step "4,5,6,7")

  7     Reverb Master Enable    (0=Disabled, 1=Enabled)

  6     IRQ9 Enable (0=Disabled/Acknowledge, 1=Enabled; only when Bit15=1)

  5-4   Sound RAM Transfer Mode (0=Stop, 1=ManualWrite, 2=DMAwrite, 3=DMAread)

  3     External Audio Reverb   (0=Off, 1=On)

  2     CD Audio Reverb         (0=Off, 1=On) (for CD-DA and XA-ADPCM)

  1     External Audio Enable   (0=Off, 1=On)

  0     CD Audio Enable         (0=Off, 1=On) (for CD-DA and XA-ADPCM)

Changes to bit0-5 aren't applied immediately; after writing to SPUCNT, it'd be usually recommended to wait until the LSBs of SPUSTAT are updated accordingly. Before setting a new Transfer Mode, it'd be recommended first to set the "Stop" mode (and, again, wait until Stop is applied in SPUSTAT).

1F801DAEh - SPU Status Register (SPUSTAT)

  15-12 Unknown? seems to be usually zero

  11    Writing to First/Second half of Capture Buffers (0=First, 1=Second)

  10    Data Transfer Busy Flag          (0=Ready, 1=Busy)

  9     Data Transfer DMA Read Request   (0=No, 1=Yes)

  8     Data Transfer DMA Write Request  (0=No, 1=Yes)

  7     Data Transfer DMA Read/Write Request ;seems to be same as SPUCNT.Bit5

  6     IRQ9 Flag                        (0=No, 1=Interrupt Request)

  5-0   Current SPU Mode   (same as SPUCNT.Bit5-0, but, applied a bit delayed)

 

Avant de pouvoir jouer quoique ce soit, nous devons d’abord initialiser correctement le SPU :

Initialisation du SPU :

;INIT SPU

InitSPU:

                    ;PUSH R31

                    addi R29,R29,0xFFFC                   ; R29=R29-4                

                    sw R31,0(R29)                                  ; PUSH R31

                    ;R27 -> I/O BASE

                    lui R27,0x1F80                                ;R27 POINT TO I/O BASE

                    ; DMA CHANEL 4 ON                   ;DPCR |=0x80000

                    lui R8,0x0008                                   ;SET 1011 in 4 bytes of channel 4 in order to start it.

                    lw R9,0x10F0(R27)                         ;READ DPCR

                    nop

                    or R9,R9,R8                                      ;DMA CHANNEL 4 ON

                    sw R9,0x10F0(R27)                        ;SET DMA CHANEL 4 ON

                    nop

                    ;OTHER SPU

                    ori R8,R0,0x3FFFF                        ;VOLUME MAX

                    sh R8,0x1D80(R27)                         ;SPU_MVOL_L          MAIN VOLUME LEFT

                    sh R8,0x1D82(R27)                         ;SPU_MVOL_R          MAIN VOLUME RIGHT

                    sh R0,0x1DAA(R27)                       ;SPU_CONTROL       WRITE 0

                    ;WAIT SPU READY

                    jal SsWait

                    nop

                    ;SPU_STATUS=4

                    ori R9,R0,0x0004      

                    sh R9,0x1DAC(R27)

                    ;WAIT SPU READY

.WaitSPU:

                    ori R9,R0,0x07FF                           ; MASK FOR SPU STATUS 2 B0-B10 must be off 0111 1111 1111

                    lh R8,0x1DAE(R27)                        ; READ SPU STATUS 2

                    nop                                                      ; Load Delay

                    and R8,R8,R9                                   ; R8=(R8&R9) and R9=0x7FF all B0-B10 must be off

                    bne R8,R0,.WaitSPU  ; IF R8!=0 THEN BRANCH

                    nop                                                      ; Delay Branching

 

                    ;OTHER INIT SPU

                    sh R0,0x1D84(R27)                         ;SPU_REVERB_L

                    sh R0,0x1D86(R27)                         ;SPU_REVERB_R

                    ori R8,R0,0xFFFF                          ;MASK 0xFFFF KEY OFF1/2

                    sh R8,0x1D8C(R27)                        ;SPU_KEY_OFF1

                    sh R8,0x1D8E(R27)                        ;SPU_KEY_OFF2

                    sh R0,0x1D90(R27)                         ;SPU_KEY_FM_MODE1

                    sh R0,0x1D92(R27)                         ;SPU_KEY_FM_MODE2

                    sh R0,0x1D94(R27)                         ;SPU_KEY_NOISE_MODE1

                    sh R0,0x1D96(R27)                         ;SPU_KEY_NOISE_MODE2

                    sh R0,0x1D98(R27)                         ;SPU_KEY_REVERB_MODE1

                    sh R0,0x1D9A(R27)                        ;SPU_KEY_REVERB_MODE2

                    sh R0,0x1DB0(R27)                        ;SPU_CD_MVOL_L MUTE MASTER VOLUME CD

                    sh R0,0x1DB2(R27)                        ;SPU_CD_MVOL_R MUTE MASTER VOLUME CD

                    sh R0,0x1DB4(R27)                        ;SPU_EXT_VOL_L

                    sh R0,0x1DB6(R27)                        ;SPU_EXT_VOL_R

                    ;INIT ALL 24 VOICE

                    and R9,R9,R0                                   ;R9=0

                    ori R9,R9,23                                     ;24 voice to init (23-0)

                    lui R10,0x1F80                                ;R10 ->I/O BASE 0x1F80

                    ori R10,R10,0x1C00                      ;R10 -> SPU_VOICE_0_BASE

.LoopVoice:

                    sh R0,0x0000(R10)                         ;VOLUME LEFT

                    sh R0,0x0002(R10)                         ;VOLUME RIGHT

                    sh R0,0x0004(R10)                         ;PITCH

                    sh R0,0x0008(R10)                         ;ENV ADS

                    sh R0,0x000A(R10)                         ;ENV R

                    addi R10,R10,0x0010                    ;NEXT VOICE

                    addi R9,R9,0xFFFF                        ;Voice count--

                    bne R9,R0,.LoopVoice                   ;Next Voice

                    nop

                    jal SsWait

                    nop

;SPU_CONTROL = 0xC000;

                    and R8,R0,R8                                                      

                    ori R8,R0,0xC000                           ;1100 0000 0000 0000 : SPU ON

                    sh R8,0x1DAA(R27)                       ;SPU_CONTROL SET

;REVERB ADRESS

                    ori R8,R0,0x1FFF                           ;Aucune place pour le tampon de la reverb

                    sh R8,0x1DA2(R27)                        ;SPU_REVERB_WORK_ADDR

;POP R31

                    lw R31,0(R29)                                  ; POP RN

                    nop                                                      ; LOAD DELAY

                    jr R31                                                 ; RETOUR

                    addi R29,R29,0x0004                    ; USE DELAY TO COMPLETE POP

                   

 

Une fois le SPU initialisé nous devons configurer une des voix pour que celle-ci puisse jouer un échantillon en mémoire.

InitVoice0 and Play :

……………………..

;PREPARE VOICE 0

                    ;SET ADRESS FOR VOICE 0

                    ori R8,R0,0x1010                                                ;SPU BUFFER ADRESS=0x1010

                    srl R8,R8,3                                                            ;DIVIDE BY 8

                    sh R8,0x1C06(R27)                                             ;SET ADRESS SOUND

                    ;OTHER PARAMS FOR VOICE 0

                    ori R8,R0,0x3FFFF                                            ;VOLUME MED

                    sh R8,0x1C00(R27)                                             ;VOLUME LEFT

                    sh R8,0x1C02(R27)                                             ;VOLUME RIGHT

                    ori R8,R0,0x0400                                                ;11KHZ

                    sh R8,0x1C04(R27)                                             ;SET PITCH sample 11KHZ

;PLAY VOICE 0

                    ori R8,R0,0x0001                                                ;VOICE 0 START

                    sh R8,0x1D88(R27)                                             ;START VOICE 0

 

La RAM du SPU est de 512 KO d’où les premier 4 KO sont réservé (voir le chapitre SPU).

Les 16 bytes suivants contiennent un échantillon ADPCM chargé par la ROM lors de l’écran de présentation.

Donc on démarrera nos échantillons à l’adresse 0x1010. Remarquez que les registres (pour chaque voix) d’adresse de l’échantillon est de 16 bits. L’adresse effective est donc un multiplie de 8 pour l’accès au 512KO. 

 

Problème lors des transferts :

               Malheureusement je rencontre toujours des problèmes lors de Transfer d’échantillons par DMA qui ne marche pas à tous les coups. On dirait qu’il faut attendre une condition spécifique pour qu’elle s’effectue. LAQUELLE ?

Il me faut impérativement écrire un debbuger PSX avec lequel on pourra également transférer du code par port séries depuis mon PC.

 

 

17-                      Temporisation et gestion des interruptions

 

Le R3000 possède en standard 6 lignes d’interruptions matériels, 2 lignes d’interruptions logicielles et une ligne Reset.

Un signal RESET le processeur saute à l’adresse 0xBFC0 0000

Les autres interruptions (ainsi que les exceptions) provoquent un branchement à l’adresse 0x0000 0080.

Les lignes d’interruption hardware sont connectées au chip R3000A (mais passe non directement dans le CPU mais dans un composant à part dans le chip) avec les autres chips spécialisés (GPU, SPU CD/ROM etc.).

Ci-dessous, les ports I/0 pour la gestion des interruptions.

1F801070h I_STAT - Interrupt status register (R=Status, W=Acknowledge)
1F801074h I_MASK - Interrupt mask register (R/W)
Status: Read I_STAT (0=No IRQ, 1=IRQ)
Acknowledge: Write I_STAT (0=Clear Bit, 1=No change)
Mask: Read/Write I_MASK (0=Disabled, 1=Enabled)

  0     IRQ0 VBLANK (PAL=50Hz, NTSC=60Hz)

  1     IRQ1 GPU   Can be requested via GP0(1Fh) command (rarely used)

  2     IRQ2 CDROM

  3     IRQ3 DMA

  4     IRQ4 TMR0  Timer 0 aka Root Counter 0 (Sysclk or Dotclk)

  5     IRQ5 TMR1  Timer 1 aka Root Counter 1 (Sysclk or H-blank)

  6     IRQ6 TMR2  Timer 2 aka Root Counter 2 (Sysclk or Sysclk/8)

  7     IRQ7 Controller and Memory Card - Byte Received Interrupt

  8     IRQ8 SIO

  9     IRQ9 SPU

  10    IRQ10 Controller - Lightpen Interrupt (reportedly also PIO...?)

  11-15 Not used (always zero)

  16-31 Garbage

 

Attribut:

Intreg_pending      equ          0x1f801070           ; Interrupt occur

Intreg_mask           equ          0x1f801074           ; Interrupt enable

I_Vblank                equ                           1           ; Vblank is bit 0

 

En construction ….

 

Dans le CPU, le coprocesseur system COP0 contient des registres  

 

TOT (Table of Tables) adresse 0x0100 avec 32 entrées:

L’entrée 0 contient la table des interruptions.

 

Pour ajouter un structure dans la PILE d’interruption, on utilise la fonction du BIOS SysEnqIntRP(priority,struc) j 0x0C (0x02)

Struct HandleList {

               WORD *pNext;                   //Le prochain handler

               WORD *pFunc1;                 //Fonction 1

               WORD *pFunc2;                 //Fonction 2

               WORD zero;                         //Je ne sais plus

};

 

COUNTERS

La Playstation possède 3 compteurs montés dans le chip du R3000A pour les besoins de synchronisation et de temporisation.

Chaque compteur possède 3 registres : un registre de mode (0x11n4), un registre qui contient la valeur actuelle du compteur (0x11n0),  et un registre qui contient la valeur cible du compteur (la condition pour déclencher une interruption) (0x11n8)).

Bien que les registres de compteur (0x11n0 et 0x11n8) soit de 32 bits le compte se fait sur les 16 bits inferieurs.

Il semble que chaque compteur peut-être synchronise soit sur la fréquence du CPU soit en fonction du compteur 1/8 de la fréquence du CPU (compteur 2) soit le retour horizontal (compteur 1) soit la fréquence d’un pixel (compteur 0).  

Le registre 0x11n4 est structure de cette façon (NOCache Reference):

1F801104h+N*10h - Timer 0..2 Counter Mode (R/W)

  0     Synchronization Enable (0=Free Run, 1=Synchronize via Bit1-2)

  1-2   Synchronization Mode   (0-3, see lists below)

         Synchronization Modes for Counter 0:

           0 = Pause counter during Hblank(s)

           1 = Reset counter to 0000h at Hblank(s)

           2 = Reset counter to 0000h at Hblank(s) and pause outside of Hblank

           3 = Pause until Hblank occurs once, then switch to Free Run

         Synchronization Modes for Counter 1:

           Same as above, but using Vblank instead of Hblank

         Synchronization Modes for Counter 2:

           0 or 3 = Stop counter at current value (forever, no h/v-blank start)

           1 or 2 = Free Run (same as when Synchronization Disabled)

  3     Reset counter to 0000h  (0=After Counter=FFFFh, 1=After Counter=Target)

  4     IRQ when Counter=Target (0=Disable, 1=Enable)

  5     IRQ when Counter=FFFFh  (0=Disable, 1=Enable)

  6     IRQ Once/Repeat Mode    (0=One-shot, 1=Repeatedly)

  7     IRQ Pulse/Toggle Mode   (0=Short Bit10=0 Pulse, 1=Toggle Bit10 on/off)

  8-9   Clock Source (0-3, see list below)

         Counter 0:  0 or 2 = System Clock,  1 or 3 = Dotclock

         Counter 1:  0 or 2 = System Clock,  1 or 3 = Hblank

         Counter 2:  0 or 1 = System Clock,  2 or 3 = System Clock/8

  10    Interrupt Request       (0=Yes, 1=No) (Set after Writing)    (W=1) (R)

  11    Reached Target Value    (0=No, 1=Yes) (Reset after Reading)        (R)

  12    Reached FFFFh Value     (0=No, 1=Yes) (Reset after Reading)        (R)

  13-15 Unknown (seems to be always zero)

  16-31 Garbage (next opcode)

 

Exemple:

; Lecture du Counter 2

lh $S0,0x1120(R27)

 

 

18-                      Introduction au GTE

 

Nous allons maintenant examiner le coprocesseur2 du R3000A qui est appelé le GTE pour « Geometry Transformation Engine ».

Il s’agira juste d’une petite introduction, un chapitre spécifique consacré à la 3D lui sera consacré.

 

Le GTE est le coprocesseur 2 du R3000A. Il est spécialisé dans le calcul vectoriel mais, comme nous allons le voir, de façon très ciblé. 

Il est composé de 64 registres de 32 bits : les Data Register GDn et les Control Register GCn. De façon générale, les registres GC contiennent les paramètres et GD le résultat des calculs

On lance les opérations que doit effectuer GTE via l’instruction COP2 gteInstr où gteInstr désigne une instruction que peut effectuer le GTE nous en verrons seulement quelques-unes.

 

GTE Command Encoding (COP2 imm25 opcodes)

  31-25  Must be 0100101b for "COP2 imm25" instructions

  20-24  Fake GTE Command Number (00h..1Fh) (ignored by hardware)

  19     sf - Shift Fraction in IR registers (0=No fraction, 1=12bit fraction)

  17-18  MVMVA Multiply Matrix    (0=Rotation. 1=Light, 2=Color, 3=Reserved)

  15-16  MVMVA Multiply Vector    (0=V0, 1=V1, 2=V2, 3=IR/long)

  13-14  MVMVA Translation Vector (0=TR, 1=BK, 2=FC/Bugged, 3=None)

  11-12  Always zero                        (ignored by hardware)

  10     lm - Saturate IR1,IR2,IR3 result (0=To -8000h..+7FFFh, 1=To 0..+7FFFh)

  6-9    Always zero                        (ignored by hardware)

  0-5    Real GTE Command Number (00h..3Fh) (used by hardware)

 

Les autres instructions du R3000 pour interagir avec le GTE sont :

LWC2  gd, imm(base)              Lecture de la memoire dans gd.stores value at imm(base) in gte data register gd.

SWC2  gd, imm(base)              Ecriture de la mémoire avec gd. stores gte data register at imm(base).

MTC2  rt, gd                            Ecriture de gd avec une valeur d’un register du MIPS3000. stores register rt in GTE data register gd.

MFC2  rt, gd                            Lecture de gd qui sera charger dans un register du MIPS3000. stores GTE data register gd in register rt.

CTC2  rt, gc                             Ecriture d’un gc avec un register MIPS3000. Stores register rt in GTE control register gc.

CFC2  rt, gc                             Lecture d’un gc dans un registre MIPS3000. stores GTE control register in register rt.

 

Donc pour charger ou lire des valeurs de registres du GTE nous pouvons nous servir des fonctions:

CTC2 CFC2 pour les registres gc

MTC2 MFC2 ou LWC2 SWC2 pour les registres gd.

Attention, les instructions MTC2,MFC2,CTC2 et CFC2 ont des delaies (au moins 1 cycles) pour assurer j’insere 2 autres instruction avant de démarrer une nouvelle opération du Cop2 ou d’interpréter une lecture.

La LibGTE le fait quasi-systématiquement en insérant deux instruction (souvent des NOP).

Par contre, il semble que les instructions COP2 soient « interlocker » mais que certaines contiennent quand meme 2 delaies !!

En tout cas ca peux etre la cause de probleme : c’est pourquoi tant que je ne connais pas le fond du probleme j’inserer toujoujrs 2 nop apres un ctc,mtcmcfc et 2 nop apres un COP2.

Attention, les instruction LWC2 et SWC2 ne sont jamais utilise par la bibliotheque officiel, et je soupconne un bug (voir RTCUBL04.asm lors d’un resize) …  affaire a suivre.

 

 

Accéder au GTE :

Avant d’utiliser le GTE nous devons le mettre en service en allumant le bit30 du registre 12 (registre SR) du COP0.

 

               MFC0        R8,SR                             ; Recupere SR dans R8

                    LUI             R9,0x4000                    ; Bit B30 = 1

                    OR              R8,R8,R9                       ; Allume le bit 30

                    MTC0        R8,SR                             ; Mis en service du GTE

 

Les 64 registres du GTE (32 gd et 32 gc) représentent divers éléments en fonction de l’opération effectuée.

Attention !!! Les formats varient en fonction des éléments, de plus les réels n’est pas des Flottants mais une virgule fixe qui va dépendre du type de l’élément !

Un registre contient en général plusieurs éléments sur 16 ou 8 bits.

 

Implicitement le GTE est composé de plusieurs structures eux-mêmes composées d’éléments spécifiques. Les structures sont par exemple, une matrice 3x3, un vecteur de translation, des vecteurs génériques, des vecteurs de résultats etc.

Par exemple les 9 éléments de la matrice3x3 sont en virgule fixe avec 1 bit pour le signe, 3 bits pour la partie entière et 12 bits pour la partie fractionnelle (pas de 1/4096). En complément a deux pour les signées.

Exemple :

               1.0          =>           0x1000

               .5            =>           0x0800

               .25          =>           0x0400

               -1.0         =>           0xF000

               Etc.

 

Un élément (X,Y,Z) d’un vecteur générique (V0,V1,V2) est un entier signé sur 16 bits.

Un éléments (X,Y,Z) du vecteur de translation est un réelle de 32 bits signe 15 bits pour la partie entière et 16 bits pour la fractionnée (pas de 1/65536).

 

Voici une liste des registres du GTE (non exhaustive) :

Registres GTE

No.                         Name                                    Description

 gc0                        R12R11                                Rotation matrix éléments 11, 12

 gc1                        R21R13                                Rotation matrix éléments 13, 21

 gc2                        R23R22                                Rotation matrix éléments 22, 23

 gc3                        R32R31                                Rotation matrix éléments 31, 32

 gc4                        R33                                       Rotation matrix element 33

 gc5                        TRX                                      Translation vector X

 gc6                        TRY                                      Translation vector Y

 gc7                        TRZ                                      Translation vector Z

 gd0                       VX0VY0                              Vector 0 X and Y.

 gd1                       VZ0                                      Vector 0 Z.

 gd2                       VX1VY1                              Vector 1 X and Y.

 gd3                       VZ1                                      Vector 1 Z.

 gd4                       VX2VY2                              Vector 2 X and Y.

 gd5                       VZ2                                      Vector 2 Z.

 gd9                       IR1                                        Resultats 16 bits

 gd10                     IR2                                        Resultats 16 bits

 gd11                     IR3                                        Resultats 16 bits

 gd12                     SXY0                                    Resultat Vecteur 0 (FIFO) transformé X et Y (16 bits signées chacun)

 gd13                     SXY1                                    Resultat Vecteur 1 (FIFO) transformé X et Y (16 bits signées chacun)

 gd14                     SXY2                                    Resultat Vecteur 2 (FIFO) transformé X et Y (16 bits signées chacun)

 gd15                     SXYP                                   Resultat Vecteur P transformé X et Y (16 bits signées chacun)

 gd16                     SZ0                                       Resultat Vecteur 0 transformé Z (16 bits poids forts non signées)

 gd24                     MAC0                                   Resultats 32 bits

 gd25                     MAC1                                   Resultats 32 bits

 gd26                     MAC2                                   Resultats 32 bits

 gd27                     MAC2                                   Resultats 32 bits

 

Exemple d’instructions :

Mnémo                  Asm R3000                           description

rtv0                        cop2 0x0486012                   v0 * rotmatrix

rtv1                        cop2 0x048E012                   v1 * rotmatrix

rtv2                        cop2 0x0496012                   v2 * rotmatrix

rtv0tr                      cop2 0x0480012                   v0 * rotmatrix + tr vector

rtv1tr                      cop2 0x0488012                   v1 * rotmatrix + tr vector

rtv2tr                      cop2 0x0490012                   v2 * rotmatrix + tr vector

 

 

Nous allons commencer par les fonctions de rotation et Translation du GTE en utilisant 4 structures sous-jacente :

-          Un vecteur générique le V0.

-          La matrice3x3 de rotation,

-          le vecteur de translations

-          l’accumulateur (32bits MAC 16 bits IR). 

-          Les résultats seront stocke dans MAC et IR (MAC sur 32 bits et IR sur 16 bits).

 

 

Nous utiliserons, dans cette série de tutoriaux consacrée, la convention matrice suivante :

Convention Matrice :

               XT          YT          ZT

X            |R11        R21         R31|                                      XT          =R11.X+R12.Y+R13.Z

Y            |R12        R22         R32|                                      YT          =R21.X+R21.Y+R23.Z

Z             |R13        R23         R33|                                      ZT           =R31.X+R32.Y+R33.Z

              

Registre Gd31 FLAG

Le registre de données 31 est le registre de drapeaux pour contrôler les erreurs de calcul. Le bit 31 de ce registre s’allume lorsque la dernière opération a provoqué une erreur.

 

Premier programme :

On va utiliser le GTE de façon minimaliste pour écrire un programme qui fait pivoter un triangle texturée sur lui-même. Ce programme permettra de nous familiariser avec les instructions R3000 pour gérer le GTE et quelques structures élémentaires du GTE comme la matrice 3x3 et les vecteurs.

 

D’abord voici le corps du programme qui travaillera en simple buffering avec synchronisation avec le retour vertical grâce à la fonction WaitVSyncOnly vu auparavant. (Si vous envoyer plus de triangles, utilisez le double-buffering).

 

GTETut01.asm :

Start:

                    ;GPU INIT

                    jal InitStdGPU

                    nop

                    ;CLEAR MAIN SCREEN

                    jal WaitGPUReady

                    nop

                    jal GPUClearVRAM

                    nop

                    ;STANDART INIT

                    jal ResetEntryInt

                    nop

                    ;PREPARE function PAD_INIT

                    addi $A0,R0,0x0001

                    jal InitCard

                    nop

                    jal StartCard

                    nop

                    jal BuInit

                    nop

                    jal StopCard

                    nop

                    ;PAD

                    jal PAD_INIT

                    nop

                    ;LOAD A IMAGE 256*256 ON 768,0

                    lui $A0,>Image

                    ori $A0,$A0,Image                         ;A0=>Image

                    ;IF IMAGE CONTAIN HEADER SKIP THEM

                    ;Addiu $A0,$A0,32                         ;for example Image has 32 bytes header.

                    lui $A1,0x0000                                ;0  -> on TOP of VRAM

                    ori $A1,$A1,768                              ;768 RIGHT on VRAM

                    lui $A2,256                                       ;Height of image=256

                    ori $A2,$A2,256                              ;Width =256

                    jal Mem2VramIO

                    nop

;******* MAIN LOOP **********************     

MainLoop:

                    ;WAIT LITTLE

                    lui $A0,0x0010

                    jal M3000Delay

                    nop

                    ;TRANSFORM MESH TO POLYTRANS

                    jal DemoGTE

                    nop

                    ;NEXT ANGLE

                    lui $A0,>CurRad

                    ori $A0,$A0,CurRad

                    lh R8,0($A0)

                    nop

                    addi R8,R8,1

                    sw R8,0($A0)

;WAIT GPU READY

                    jal WaitGPUReady

                    nop

;WAIT VSYNC

                    jal WaitVSyncOnly

                    nop

;CLEAR SCREEN

                    jal GPUClearVRAM

                    nop

;DRAW POLYTRANS

                    jal DrawTransTriTx

                    nop

;GOTO LOOP

                    j MainLoop

                    nop

;FONCTIONS GTEDEMO LOADMRZ16 et DRAWTRANSTRITX

;………………………………………………………… voir plus bas

;VARIABLES

CurRad:

                    word 0,0

Mesh:

                    half -85,85,10,0                                ;Mesh V0

                    half 0,-85,10,0                                  ;Mesh V1

                    half 85,85,10,0                                 ;Mesh V2

                    half 0,0,0,0

 

POLYTRANS:

                    word 0,0,0,0

                    word 0,0,0,0

 

Image:

                    Insert « ImageFileName »

 

On utilisera une texture de 256x256 chargée dans la VRAM en 768,0. La texture page sera donc 0x010C (Y=0 ; X=768/16=12)

La variable CurRad contiendra l’index courant de l’angle de rotation que le programme incrémente à chaque frame, la variable Mesh pointe sur l’espace contenant 3 sommets de notre triangle centre en 0. Comme nous n’aurons que 16 angles possibles on attendra environ 32ms entre chaque frame. 

La variable POLYTRANS contiendra le résultat c’est-à-dire les sommets transformés par le GTE que nous utiliserons pour dessinez notre triangle.

 

Voyons à présent les autres fonctions celles qui nous intéressent.

 

Nous devons charger la matrice de rotation suivante :

Rotation Z d’un polygone texture.

               XT          YT          ZT

X            |COS       SIN         0.0 |                                       XT          =COS.X-SIN.Y

Y            |-SIN       COS       0.0 |                                       YT          =SIN.X+COS.Y

Z             |0.0          0.0          1.0 |                                       ZT           =Z

 

Le GTE ne possède pas de fonction sinus et cosinus c’est à nous de les générer. Dans notre exemple on utilisera une table minimale de fonction sinus à 16 entrées.

La fonction LoadMrz16 charge la matrice RZ en fonction du paramètre ID passe en $A0 tel que Rad=(PI/8)*ID.

;LOAD MRZ $A0=Id angle

LoadMrz16:

                    ;PUSH

                    addi R29,R29,0xFFF0                   ; R29=R29-16              

                    sw R31,8(R29)                                  ; PUSH R31

                    sw $S0,12(R29)

                    ;Common Value GTE

                    ori R8,R0,0x1000                            ;GTE 1.0 value

                    ctc2 R0,Gc3                                      ;GC3=M32|M31=0.0

                    ctc2 R8,Gc4                                      ;GC4=M33=1.0

                    ;$A1 point to TAB SIN

                    lui $A1,>.SinTab

                    ori $A1,$A1,.SinTab

                    ;LOAD SIN

                    and $S0,R0,R0                                 ;$S0=0

                    add $S0,$S0,$A0                             ;$S0 ADD ID

                    andi $S0,$S0,0x000F                     ;KEEP INDEX

                    sll $S0,$S0,1                                     ;ID*2

                    add $S0,$S0,$A1                             ;$S0->SinTab+id

                    lhu R11,0($S0)                                 ;R11=SIN(ID)

                    nop

                    or R9,R11,R0                                   ;R9=SIN(ID)

                    beq R9,R0,.@1                                 ;IF r9=0 SKIP minus

                    nop

                    ;R9=-SIN(ID)

                    xori R9,R9,0xFFFF                        ;INVERSE COMPLEMENT

                    addi R9,R9,1                                     ;COMPLEMENT A DEUX R9=-SIN(ID)

.@1:

                    sll R11,R11,16                                  ;R11=SIN|0

                    ctc2 R11,Gc1                                    ;GC1=M21|M13=SIN|0

                    sll R9,R9,16                                      ;R9=-SIN|0

                    ;LOAD COS

                    addi R8,$A0,4                                  ;R8=ID+4

                    andi R8,R8,0x000F

                    sll R8,R8,1

                    add R8,R8,$A1

                    lhu R8,0(R8)                                     ;R8=cos(ID)

                    nop

                    or R9,R9,R8                                      ;R9=-SIN|COS

                    ctc2 R8,Gc2                                      ;GC2=M23|M22=0|COS

                    ctc2 R9,Gc0                                      ;GC0=M12|M11=-SIN|COS

                    ;POP RET

                    lw R31,8(R29)                                  ; POP RN

                    lw $S0,12(R29)                                 ; POP S0

                    jr R31                                                 ; RETOUR

                    addi R29,R29,0x0010                    ; SP+=16

.SinTab:    ;16 entre sin(i)=SinTab[i&0xF]; cos(i)=SinTab[(i+4)&0xF];

                    half 0,1568,2896,3784,4096,3784,2896,1568

                    half 0,63969,62640,61752,61440,61752,62640,63969

 

Encore une fois cette fonction charge les valeurs que devra contenir les éléments de la matrice 3x3 représenté par gc0 à gc4. Chaque élément de la matrice est en virgule fixe avec 1 bit pour le signe, 3 bits pour la partie entière et 12 bits pour la partie fractionnelle (pas de 1/4096) et en complément à deux.

 

Gardons cette fonction elle nous servira par la suite nous l’étendrons (si vous ne l’avez pas déjà fait) à 256 valeur possible d’angle.

 

DEMOGTE est notre fonction principale qui charge la matrice avec la fonction LoadMRZ et transforme les sommets de Mesh, un triangle de (-85,-85),(0,-85),(85,85) dans PolyTrans.

Nous utilisons seulement la fonction rtv0tr du GTE (0x0480012) qui se charge de multiplie le vecteur v0 par la matrice MR et additionne par le Vecteur TR avec des valeurs de 160 120 pour à peu près replacer le triangle au centre de l’écran (320*240). Il nous faut donc répéter l’opération pour les 3 sommets de notre polygone.

Le résultat est stocké dans les registres Gd25-Gd27 du GTE que nous transférons ensuite dans notre espace PolyTrans.

;ROTATE POLY AROUND Z AXIS

DEMOGTE:

                    ;PUSH

                    addi R29,R29,0xFFF0                   ; R29=R29-16              

                    sw R31,8(R29)                                  ; PUSH R31

                    sw $S0,12(R29)

                    ;START

                    jal EnableGTE

                    nop

                    ;*** LOAD MROTZ element

                    lui $S0,>CurRad

                    ori $S0,$S0,CurRad

                    lh $A0,0($S0)

                    jal LoadMrz16

                    nop

                    ;*** TX=160 TY=120

                    ori R8,R0,160

                    ctc2 R8,Gc5                                      ;TrX=160

                    ori R9,R0,120

                    ctc2 R9,Gc6                                      ;TrY=120

                    ;*** LOAD VECTOR V0

                    lui $S0,>.Mesh

                    ori $S0,$S0,.Mesh                           ;S0=>.Mesh

                    lui $S2,>PolyTrans

                    ori $S2,$S2,PolyTrans                  ;S2->PolyTrans

                    ori $S1,R0,3

.LoopTrans:

                    lwc2 R0,0($S0)                                 ;VX,VY

                    lwc2 R1,4($S0)                                 ;VZ

                    nop                                                      ;DELAY

                    nop                                                      ;DELAY

                    ;*** USE RTV0TR cop2 $0486012

                    cop2 0x0480012                              ;RTV0TR

                    nop                                                      ;DELAY

                    nop                                                      ;DELAY

                    ;*** STORE RESULT MAC1,MAC2,MAC3

                    mfc2 R8,R25                                    ;MAC1 X                                                              

                    mfc2 R9,R26                                    ;MAC2 Y

                    mfc2 R10,R27                                  ;MAC3 Z

                    sh R8,0($S2)                                      ; Resulta Vn dans polytrans X

                    sh R9,2($S2)                                      ; Resultat Vn dans polytrans Y

                    sh R10,4($S2)                                   ; Resultat Vn dans polytrans Z

                    ;** NEXT VERT

                    addi $S1,$S1,0xFFFF                    ;$S1--         CNT--

                    addi $S2,$S2,8                                 ;NEXT  VERT POLYTRANS                                                                                       

                    bne R0,$S1,.LoopTrans

                    addi $S0,$S0,8                                 ;NEXT VERT MESH

                    ;POP RET

                    lw R31,8(R29)                                  ; POP RN

                    lw $S0,12(R29)                                 ; POP S0

                    jr R31                                                 ; RETOUR

                    addi R29,R29,0x0010                    ; SP+=16

 

Enfin, la fonction DrawTransTriTx ne pose pas de problème: nous envoyons simplement au GPU la commande de dessin de triangle texture avec les sommets de PolyTrans.

Remarquez que nous n’utilisons pas la composante Z lors du dessin. Seul Y et X sont pris en compte.

Une autre subtilité est que lors de la lecture du sommet, le little endian inverse l’ordre X<<16|Y en Y<<16|X lorsque le vecteur est charge dans un registre !

Exemple avec rad = 0 donc pour V0 X= X – 0 et Y = Y et après translation X =-85+160 = 75 (0x004B) et Y = 85 + 160 = 245 (0x00F5).

 

                    ;*** STORE RESULT MAC1,MAC2,MAC3

                    mfc2 R8,R25                                    ; MAC1 X 0x004b                                              

                    mfc2 R9,R26                                    ; MAC2 Y 0x00f5

                    …………………………………………………………..

               sh R8,0($S2)                                      ; Resultat Vn.X dans polytrans X => 0x004b -> 0x4b00

                    sh R9,2($S2)                                      ; Resultat Vn.Y dans polytrans Y => 0x00F5 -> 0xF500

                    ………………………………………

                    ;GET VERTEX

                    lw R10,0($A0)                                  ; V0 0($A0) = 0x4b00 f500 apres lw r10 = 0x00f5 004b !

 

C’est pourquoi on doit considérer lors de lecture et écriture l’ordre X<16|Y et non Y<<16|X comme c’est le cas lorsque nous envoyons les coordonnées de sommet par registre en dure.

Oui c’est assez lourd-dingue : c’est la « magie »  (pour être polie) du little-endian mais les habitués du x86 ne devraient pas être très déroutés.

 

DrawTransTriTx:

                    ;DRAW TRANSFORMED TRIANGLE BY GTE

                    lui $A0,>POLYTRANS

                    ori $A0,$A0,POLYTRANS           ;$A0->POLYTRANS

                    ;GET VERTEX

                    lw R10,0($A0)                                  ;V0

                    lw R11,8($A0)                                  ;V1

                    lw R12,16($A0)                                ;V2

                    ;TRIANGLE TEXTURE

                    lui R8,0x2480                                   ;TEXTURED triangle                                                           

                    ori R8,R8,0x8080                            ;GRAY TRIANGLE

                    sw R8,0x1810(R27)

                    ;VERTEX 0

                    sw R10,0x1810(R27)                      ;SEND COORDS Y|X

                    ori R8,R0,0xFF00                           ;V=255 U=0

                    sw R8,0x1810(R27)                         ;SEND CLUTID AND COORDTEXT

                    ;VERTEX 1

                    sw R11,0x1810(R27)                      ;SEND COORDS Y|X

                    lui R8,0x010C                                  ;TEXTURE PAGE: 15 bit direct X=12*64=>768 Y=0

                    ori R8,R8,0x0080                            ;V=0 U=128

                    sw R8,0x1810(R27)                         ;SEND CLUTID AND COORDTEXT

                    ;VERTEX 2

                    sw R12,0x1810(R27)                      ;SEND COORDS Y|X

                    lui R8,0x0000                                   ;DON'T CARE

                    ori R8,R8,0xFFFF                          ;V=255 U=255

                    sw R8,0x1810(R27)                         ;SEND CLUTID AND COORDTEXT

                    jr R31                                                 ; RETOUR

                    nop

 

 

GteTut1.asm en action

 

J’encourage le lecteur à écrire son propre programme et à étendre la fonction de rotation pour une meilleure fluidité. Ou bien s’amuser a pivoté le triangle sur un autre axe que Z pour donner un effet 3D.

 

Deuxième programme :

Nous allons maintenant essayer d’écrire un programme minimal 3D (sans problème de clipping ou de profondeur) en introduisant la fonction RTPS et 3 autres structures du GTE.

On va juste mettre en rotation un petit maillage (un petit cube texture 6 faces de 4 sommets chacune) en fonction du PAD.

On va simplifier au maximum donc on va effectuer les opérations en coordonne local de l’objet, ensuite on se replace dans les coordonne de l’observateur en reculant de quelque unité. Et on appliquera une simple projection avec la fonction du GTE RTVP. On calculera la normal de chaque face pour l’envoi l’affichage ou non.

La rotation se fera seulement en Y. Le pad contrôlera seulement la position du cube en X et Y.

Nous sommes obligés ici d’utiliser le double buffer car la PSX ne peut dessiner plus de 2 triangles texturés avec effacement de l’écran dans un frame par I/O.

 

Le corps de notre programme se présentera ainsi :

 

ORG 0x8008 0000

Start:

                    ; SAVE R31

                    lui R8,>GlobalReturn

                    ori R8,R8,GlobalReturn

                    sw R31,0(R8)

                    ;R27 POINT TO I/O BASE

                    lui R27,0x1F80

                    ;GPU INIT 320*240 PAL

                    jal InitStdGPU

                    nop

; INITIALISATION: INSTALL PAD IRQ ON VBLANK WITH BIOS FUNCTIONS

; SEQUENCE A FAIRE SINON LE PAD NE FONCTIONNE PAS (Les cmds CD ne sont pas obligaotire REMOVE96 et Init96).

                    jal ResetEntryInt

                    nop

                    ;PREPARE function before using PAD_INIT

                    addi $A0,R0,0x0001

                    jal InitCard

                    nop

                    jal StartCard

                    nop

                    jal BuInit

                    nop

                    jal StopCard

                    nop

                    ;PAD

                    jal PAD_INIT

                    nop

                    ; AGAIN R27 POINT TO I/O BASE

                    lui R27,0x1F80

                    ; INIT DOUBLE BUFFER

                    jal InitSurface

                    nop

                    ; LOAD TEXTURE

                    lui $A0,>Texture1

                    ori $A0,$A0,Texture1                                       

                    ori $A1,R0,768

                    jal ZPS2VramIO

                    nop

                    ;INIT GTE

                    jal EnableGTE

                    nop

; MAIN PROGRAMM

Main:        

;READ PAD DATA

                    jal WaitVSync

                    nop

                    lui $T0,>CurRad

                    ori $T0,$T0,CurRad

.ReadPad:

                    ;MODE 1 READ PAD

                    lui $S1,>PadDataI

                    ori $S1,$S1,PadDataI

                    lw $S0,0($S1)               ;PAD DATA ON $S0

                    nop

.Right:

                    andi $T0,$S0,0x8000                     ;RIGHT ?

                    beq $T0,R0,.Left

                    nop

                    ;RIGHT

                    lw $T1,0($T0)

                    nop

                    addi $T1,$T1,-1

                    sw $T1,0($T0)

                    j .down

.Left:

                    andi $T0,$S0,0x2000                     ;LEFT ?

                    beq $T0,R0,.Down

                    nop

                    lw $T1,0($T0)

                    nop

                    addi $T1,$T1,1

                    sw $T1,0($T0)

.Down:

                    andi $T0,$S0,0x4000                     ;DOWN ?

                    beq $T0,R0,.Up

                    nop

.Up:

                    andi $T0,$S0,0x1000                     ;Up?

                    beq $T0,R0,.Buttons

                    nop

.Buttons:

.Select:

                    andi $T0,$S0,0x0100                     ;Button Select ?

                    beq $T0,R0,.Process

                    nop

                    j .EndProg                                         ; Termine le programme

                    nop

; MAIN PROCESSING

.Process:

                    ;PROCESSING HERE

                    jal MainGTE

                    nop

;DRAWING

.InitDraw:

                    jal WaitGPUIdle

                    nop

                    jal WaitGPUReady

                    nop

.Draw:

                    jal ClearBackBuffer

                    nop

                    ;DRAWING HERE

                    jal DrawCube

                    nop

.FlushDraw:

                    jal WaitGPUIdle

                    nop

                    jal WaitGPUReady

                    nop

                    jal WaitVSyncOnly   

                    nop

                    jal FlipSurface

                    nop

.EndMain:

                    beq R0,R0,Main         

                    nop

.EndProg:

                    ; Restore R31

                    lui R8,>GlobalReturn

                    ori R8,R8,GlobalReturn

                    lw R31,0(R8)

                    nop

                    jr R31                                                                    

                    nop

GlobalReturn:

                    word 0

; Notre Maillage :

FaceColor :

                    word 0xA0A0A0

                    word 0xA0A0A0

word 0x808080

word 0x808080

word 0x606060

word 0x606060

MeshCube:

                    ; FRONT FACE

                    half -32,32,-32,0          ;V0

                    half -32,-32,-32,0         ;V1

                    half 32,32,-32,0           ;V2

                    half 32,-32,-32,0          ;V3

                    ; BACK FACE

                    half 32,32,32,0             ;V6

                    half 32,-32,32,0           ;V7

                    half -32,32,32,0           ;V4

                    half -32,-32,32,0          ;V5

                    ; FACE GAUCHE

                    half -32,32,32,0           ;V4

                    half -32,-32,32,0          ;V5

                    half -32,32,-32,0          ;V0

                    half -32,-32,-32,0         ;V1

                    ; FACE DROITE

                    half 32,32,-32,0           ;V2

                    half 32,-32,-32,0          ;V3

                    half 32,32,32,0             ;V6

                    half 32,-32,32,0           ;V7

                    ; FACE HAUT

                    half -32,-32,-32,0         ;V1

                    half -32,-32,32,0          ;V5

                    half 32,-32,-32,0          ;V3

                    half 32,-32,32,0           ;V7

                    ; FACE BASSE

                    half -32,32,32,0           ;V4

                    half -32,32,-32,0          ;V0

                    half 32,32,32,0             ;V6

                    half 32,32,-32,0           ;V2

                   

;ANGLES DE ROTATION Y

CurRad:

                    word 0

 

Voyons la fonction de transformation en perspective  RTPS :

RTPS     cop2 $0180001      Perspective transformation

Dans notre exemple on ne s’intéressera uniquement qu’à la projection proprement dites.

Les seuls paramètres d’entrée qui nous intéresserons seront :

-          La matrice de rotation              MR         Gc0-Gc4

-          Le vecteur de translation         TR          Gc5,Gc6,Gc7

-          La distance de projection         H            Gc26

-          Les offsets d’écran                  OFFX,OFFY Gc24,Gc25

Les paramètres de sortie qui nous intéressent seront :

-          SPXP, SPYP            Gd15

Les rotations et translation ne change pas par rapport à RTV0TR mais une transformation de projection est appliquée.

SPXP = OFFX + (H/SZ)*X

SPYP = OFFY + (H/SZ)*Y

 

Le GTE contient également une petite pile FIFO du résultat des SP ;

SP0 = SP1 ; SP1 = SP2 ; SP2 = SPP

Pour l’instant on va ignorer plusieurs problèmes à résoudre que l’on rencontrera plus tard lorsqu’il s’agira d’écrire un petit moteur 3D. En revanche il est indispensable de clipper les faces en fonction de la valeur de leur vecteur normal. Pour cela on se servira d’une autre fonction du GTE NCLIP 0x1400006 qui effectue un produit vectoriel : (SV1-SV0)^(SV2-SV0).

 

NCLIP                  COP2 0x1400006                Produit vectoriel (Cross Product)

IN:          SXSY0 (GD12), SXSY1 (GD13), SXSY2 (GD14)

Calcul:    (SV1-SV0)^(SV2-SV0)

OUT :     MAC0 (GD24)     

                   

 

Tout d’abord nous avons besoin d’une fonction pour charger la matrice de rotation sur l’axe Y. Notre table de sinus sera cette fois ci de 256 entrées.

; LOAD MRY $A0=Id angle with 256 entries sinus table

                    ; ***********************************

                    ;                                       XT                                                       YT                                                       ZT

                    ; X               |                   COS                                                    0.0                                                        -SIN

                    ; Y               |                   0.0                                                       1.0                                                        0.0

                    ; Z               |                   SIN                                                      0.0                                                        COS

                    ; ***********************************

                    ; GC0=M12|M11=0.0|COS

                    ; GC1=M21|M13=0.0|SIN

                    ; GC2=M23|M22=0.0|1.0

                    ; GC3=M32|M31=0.0|-SIN

                    ; GC4=N/A|M33=0.0|COS

LoadMry256:

                    ;Common Value GTE

                    ori R8,R0,0x1000                            ;GTE 1.0 value

                    ctc2 R8,Gc2                                      ;GC2=M23|M32=0.0|1.0

                    ;$A1 point to TAB SIN

                    lui $A1,>SinTab256

                    ori $A1,$A1,SinTab256

                    ;LOAD SIN

                    and R2,R0,R0                                   ;R2=0

                    add R2,R2,$A0                                 ;R2 ADD ID

                    andi R2,R2,0x00FF                        ;KEEP INDEX

                    sll R2,R2,1                                         ;ID*2

                    add R2,R2,$A1                                 ;R2->SinTab+id

                    lhu R11,0(R2)                                   ;R11=SIN(ID)

                    nop

                    or R9,R11,R0                                   ;R9=SIN(ID)

                    xori R9,R9,0xFFFF                        ;INVERSE COMPLEMENT

                    addi R9,R9,1                                     ;COMPLEMENT A DEUX R9=-SIN(ID)

                    ;SUITE

                    ctc2 R11,Gc1                                    ;GC1=M21|M13=0.0|SIN

                    ctc2 R9,Gc3                                      ;GC3=M32|M31=0.0|-SIN

                    ;LOAD COS

                    addi R8,$A0,64                                ;R8=ID+32

                    andi R8,R8,0x00FF

                    sll R8,R8,1

                    add R8,R8,$A1

                    lhu R8,0(R8)                                     ;R8=cos(ID)

                    nop

                    or R9,R9,R8                                      ;R9=-SIN|COS

                    ctc2 R8,Gc0                                      ;GC0=M12|M11=0.0|COS

                    ctc2 R8,Gc4                                      ;GC4=N/A|M33=0.0|COS

                    ;RET

                    jr R31                                                

                    nop

                   

;TABLE DE SINUES 256 Entree:512 octets.

SinTab256:

                    half 0,100,200,301,401,501,601,700,799,897,995,1092,1189,1284,1379,1474

                    half 1567,1659,1751,1841,1930,2018,2105,2191,2275,2358,2439,2519,2598,2675,2750,2824

                    half 2896,2966,3034,3101,3166,3229,3289,3348,3405,3460,3513,3563,3612,3658,3702,3744

                    half 3784,3821,3856,3889,3919,3947,3973,3996,4017,4035,4051,4065,4076,4084,4091,4094

                    half 4096,4094,4091,4084,4076,4065,4051,4035,4017,3996,3973,3947,3919,3889,3856,3821

                    half 3784,3744,3702,3658,3612,3563,3513,3460,3405,3348,3289,3229,3166,3101,3034,2966

                    half 2896,2824,2750,2675,2598,2519,2439,2358,2275,2191,2105,2018,1930,1841,1751,1659

                    half 1567,1474,1379,1284,1189,1092,995,897,799,700,601,501,401,301,200,100

                    half 0,65436,65336,65235,65135,65035,64935,64836,64737,64639,64541,64444,64347,64252,64157,64062

                    half 63969,63877,63785,63695,63606,63518,63431,63345,63261,63178,63097,63017,62938,62861,62786,62712

                    half 62640,62570,62502,62435,62370,62307,62247,62188,62131,62076,62023,61973,61924,61878,61834,61792

                    half 61752,61715,61680,61647,61617,61589,61563,61540,61519,61501,61485,61471,61460,61452,61445,61442

                    half 61440,61442,61445,61452,61460,61471,61485,61501,61519,61540,61563,61589,61617,61647,61680,61715

                    half 61752,61792,61834,61878,61924,61973,62023,62076,62131,62188,62247,62307,62370,62435,62502,62570

                    half 62640,62712,62786,62861,62938,63017,63097,63178,63261,63345,63431,63518,63606,63695,63785,63877

                    half 63969,64062,64157,64252,64347,64444,64541,64639,64737,64836,64935,65035,65135,65235,65336,65436

              

 

Ensuite voyons les structures qui contiendront les données transformées. Nous aurons 2 structures:

 

QuadClip:

                    word 0,0

                    word 0,0

                    word 0,0

QuadTrans:

                    ;FACE FRONT

                    Word 0,0,0,0

                    ;FACE BACK

                    Word 0,0,0,0

                    ;FACE LEFT

                    Word 0,0,0,0

                    ;FACE RIGHT

                    Word 0,0,0,0

                    ;FACE TOP

                    Word 0,0,0,0

                    ;FACE BOTTOM

                    Word 0,0,0,0

 

QuadClip contiendra pour chaque face le résultat de NCLIP et QuadTrans les sommets transformée en X,Y.

 

Voyons donc maintenant notre fonction MainGTE qui est le cœur du programme et qui va se charger à chaque frame de remplir QuadClip et QuadTrans.        

 

;GTE CUBE TRANSFORM

GTE_CUBETRANS:

                    ;PUSH

                    addi R29,R29,0xFFE0                   ; R29=R29-32              

                    sw R31,8(R29)                                  ; PUSH R31

                    sw $S0,12(R29)

                    sw $S1,16(R29)

                    sw $S2,20(R29)

;INIT PTR

                    lui $S0,>MeshCube

                    ori $S0,$S0,MeshCube                   ;S0=>.MeshCube

                    lui $S1,>QuadTrans

                    ori $S1,$S1,QuadTrans                 ;S1->QuadTrans

                    lui $S2,>QuadClip

                    ori $S2,$S2,QuadClip                    ;S1->QuadClip

;INIT RTPS

                    ;PARA PROJ

                    ;H=128.0

                    ori R2,R0,0x0100                            ;H=128

                    ctc2 R2,R26

                    ;OFFSET SCREEN

                    lui R2,160

                    ctc2 R2,R24                                      ;OFFX=160

                    lui R3,120

                    ctc2 R3,R25                                      ;OFFY=120

                    ; OBJECT POSITION

                    ctc2 R0,Gc5                                      ;TRX

                    ori R2,R0,64

                    ctc2 R2,Gc6                                      ;TRY

                    ori R3,R0,256

                    ctc2 R3,Gc7                                      ;TRZ

                    ; LOAD ROTATION MATRIX

                    lui $A0,>CurRad

                    ori $A0,$A0,CurRad 

                    lw $A0,0($A0)

                    jal LoadMry256

                    nop

;FOR EACH FACE

                    ori R8,R0,6                                       ;6 faces

.loopface:

                    ;FOR EACH VERT

                    ori R9,R0,4                                       ;4 Vert

.loopVert:

                    lwc2 R0,0($S0)                                 ;VX,VY dans gd0

                    lwc2 R1,4($S0)                                 ;VZ dans gd1

                    nop

                    nop

                    ;COP RTPS

                    ;Xp=OFFX+(H/SZ)*X ; Yp=OFFY + (H/SZ)*Y

                    cop2 0x0180001

                    nop

                    nop

                    ;STORE THE RESULT ON CUBETRANS

                    swc2 r15,0($S1)                               ; store on CubeTrans

                    addiu R9,R9,-1                                 ; vert--

                    addiu $S1,$S1,4                               ; Next vert_trans ptr

                    bne R9,R0,.loopVert

                    addiu $S0,$S0,8                               ; Next vert_mesh ptr

                    ;NCLIP TO CHECK IF THE FACE IS VISIBLE

                    cop2 0x1400006                              ; DOT PRODUCT TO MAC0

                    addiu R8,R8,-1                                 ; face--

                    nop

                    mfc2 R3,R24                                    ; MAC0 dans R3

                    nop                                                      ; VERY IMPORTANT

                    nop                                                      ; BEFORE STORE THE RESULT

                    sw R3,0($S2)                                     ; STORE CLIP

                    bne R8,R0,.loopface

                    addiu $S2,$S2,4                               ; Next ptr ClipFace

                    ;POP RET

                    lw R31,8(R29)                                 

                    lw $S0,12(R29)                                

                    lw $S1,16(R29)

                    lw $S2,20(R29)

                    jr R31                                                 ; RETOUR

                    addi R29,R29,0x0020                    ; SP+=32

 

La fonction commence par initialisée les paramètres du GTE H= 128 ; OFFX = 160 ; OFFY = 120 ; TRX = 0 ; TRY = 64 ; TRZ =256.

Le paramètres H représente la distance du plan de projection plus il est petit plus la déformation de profondeur est importante. En revanche, plus H est grand et plus la projection tend à être cavalière.

Lorsque Z est inférieur à H le GTE n’effectue plus le calcul correctement mais nous verrons cela dans un prochain chapitre.

Les paramètres TRX,TRY et TRZ descend l’objet et recule notre cube pour une meilleur visualisation mais je vous encourage à modifier le programme pour gérer la position de l’objet avec le PAD.

Ensuite nous appelons, après avoir récupère l’angle de rotation courant, LoadMry256 pour charger la matrice de rotation.

Ensuite pour chacune des 6 faces, nous transformons chaque sommet et nous stockons le résultat dans QuadTrans, une fois les sommets de la face transformés, nous utilisons NCLIP et stockons le résultat dans QuadClip.

Remarquez qu’ici notre little-endian ne nous dérange pas car nous stockons le registre GD15 (XPYP) directement dans les sommets de QuadTrans avec l’instruction swc2 r15, offset(R).

 

 

La fonction DrawCubeProc se charge simplement de vérifier le résultat de NCLIP chaque face et si la valeur est négative utilise DrawGenQuad pour dessiner la face.

 

DrawCubeProc:

                    ;PUSH

                    addi R29,R29,0xFFE0                                      

                    sw R31,8(R29)                                 

                    sw $S0,12(R29)

                    sw $S1,16(R29)

                    sw $S2,20(R29)

                    sw $S3,24(R29)

                    ;INIT PTR

                    lui $S0,>QuadClip

                    ori $S0,$S0,QuadClip                    ; S0=> QuadClip

                    lui $S1,>QuadTrans

                    ori $S1,$S1,QuadTrans                 ; S1->QuadTrans

                    ori $S2,R0,6                                      ; S2 = cnt faces : 6 faces

                    lui $S3,>FaceColor

                    ori $S3,$S3,FaceColor                  ; S3->Face color

.loopface:

                    ;CHECK CLIP

                    lw R8,0($S0)

                    nop

                    bgtz R8,.endface                              ; if positive skip

                    addiu $S0,$S0,4                               ; next clip face

                    ; WAIT GPU CMD

                    jal WaitGPUReady

                    nop

                    ;SET PARAMETERS

                    lw $A1,0($S3)                                   ; Get Color face

                    jal DrawGenQuad

                    or $A0,$S1,R0                                  ; QUAD TRANS          

.endface:

                    Addiu $S3,$S3,4                              ; Next Color Face

                    addiu $S2,$S2,-1                              ; face--

                    bne $S2,R0,.loopface                                         

                    addiu $S1,$S1,16                             ; NextQuadtrans

                    ;POP RET

                    lw R31,8(R29)                                 

                    lw $S0,12(R29)                                

                    lw $S1,16(R29)

                    lw $S2,20(R29)

                    lw $S3,24(R29)

                    jr R31                                                

                    addi R29,R29,0x0020                   

 

Notre fonction DrawRecGen utilisera des quads texturés pour dessiner les faces. Nous passerons en paramètre le pointeur de QuadTrans, la couleur de la face. La texture page et les coordonnées de texture sont en dure.

;Draw 4 point polygon

;$A0->POLYTRANS

;$A1->COLOR

DrawGenQuad:

                    ;RECTANGLE TEXTURE

                    lw R10,0($A0)                                  ;V0

                    lw R11,4($A0)                                  ;V1

                    lw R12,8($A0)                                  ;V2

                    lw R13,12($A0)                                ;V3

                    ;QUAD MONO TEXTURED

                    lui R8,0x2C00                                  ; RECT 4 point polygone                                                      

                    or R8,R8,$A1                                   ;COLOR

                    sw R8,0x1810(R27)

                    ;VERTEX 0

                    sw R10,0x1810(R27)                      ;SEND COORDS Y|X

                    ori R9,R0,0xFF00                           ;V=255 U=0

                    sw R9,0x1810(R27)                         ;SEND CLUTID AND COORDTEXT

                    ;VERTEX 1

                    sw R11,0x1810(R27)                      ;SEND COORDS Y|X

                    lui R9,0x010C                                  ;TEXTURE PAGE: 15 bit direct X=12*64=>768 Y=0

                    ;ori R9,R9,0x0000                          ; V=0 U=0

                    sw R9,0x1810(R27)                         ;SEND CLUTID AND COORDTEXT

                    ;VERTEX 2                  (3)

                    sw R12,0x1810(R27)                      ;SEND COORDS Y|X

                    ori R9,R0,0xFFFF                          ;Don't care clut id V=255 U=255

                    sw R9,0x1810(R27)                         ;SEND CLUTID AND COORDTEXT

                    ;VERTEX 3

                    sw R13,0x1810(R27)                      ;SEND COORDS Y|X

                    ori R9,R0,0x00FF                           ;V=0 U=255

                    sw R9,0x1810(R27)                         ;SEND CLUTID AND COORDTEXT

.End:

                    jr R31                            

                    nop         

 

              

GteTut2.asm en action

 

Les programmes d’exemple contiennent GteTut3 qui rajoute la rotation sur l’axe X et les positions Y et Z de l’objet et avec également la possibilité de changer H.

Hormis l’utilisation de RTV0TR et RTPS le programme est fort similaire. L’ajout du control par boutons est trivial.

GTE_CUBETRANS devient :

 

;GTE CUBE TRANSFORM

GTE_CUBETRANS:

                    ;PUSH

                    addi R29,R29,0xFFE0                   ; R29=R29-32              

                    sw R31,8(R29)                                  ; PUSH R31

                    sw $S0,12(R29)

                    sw $S1,16(R29)

                    sw $S2,20(R29)

                    sw $S3,24(R29)

                    sw $S4,28(R29)

;INIT PTR

                    lui $S0,>MeshCube

                    ori $S0,$S0,MeshCube                   ;S0=>.Mesh

                    lui $S1,>QuadTrans

                    ori $S1,$S1,QuadTrans                 ;S1->PolyTrans

                    lui $S2,>QuadClip

                    ori $S2,$S2,QuadClip                    ;S1->PolyTrans

;INIT RTPS

                    ;OFFSET SCREEN

                    lui R2,160

                    ctc2 R2,R24                                      ;OFFX=160

                    lui R3,120

                    ctc2 R3,R25                                      ;OFFY=120

;FOR EACH FACE

                    ori $S3,R0,6                                      ;6 faces

.loopface:

                    ;FOR EACH VERT

                    ori $S4,R0,4                                      ;4 Vert

.loopVert:

                    ; LOAD ROTATION MATRIX Y

                    lui $A0,>CurRadCube

                    ori $A0,$A0,CurRadCube            ;S1->PolyTrans

                    lw $A0,0($A0)                                 

                    jal LoadMry256

                    nop

                    ctc2 R0,Gc5                                      ;TRX = 0

                    ctc2 R0,Gc6                                      ;TRY = 0

                    ctc2 R0,Gc7                                      ;TRZ = 0

                    lwc2 R0,0($S0)                                 ;VX,VY dans gd0

                    lwc2 R1,4($S0)                                 ;VZ dans gd1

                    nop

                    nop

                    ;*** USE RTV0TR cop2 $0486012

                    cop2 0x0480012                              ;RTV0TR

                    nop

                    nop

                    ; LOAD ROTATION MATRIX X

                    lui $A0,>CurRadCube

                    ori $A0,$A0,CurRadCube            ;S1->PolyTrans

                    lw $A0,4($A0)

                    jal LoadMrx256

                    nop

                    mfc2 R9,R25                                    ;MAC1 X                                                              

                    mfc2 R8,R26                                    ;MAC2 Y

                    mfc2 R10,R27                                  ;MAC3 Z

                    lui R11,0xFFFF

                    sll R8,R8,16                 

                    and R8,R8,R11

                    andi R9,R9,0xFFFF

                    or R8,R8,R9

                    mtc2 R8,Gc0

                    mtc2 R10,Gc1

                    ; OBJECT POSITION

                    lui $A0,>PosView

                    ori $A0,$A0,PosView                     ;S1->PolyTrans

                    lw R8,0($A0)                                     ;CurX

                    lw R9,4($A0)                                     ;CurY

                    lw R10,8($A0)                                  ;CurZ

                    lw R11,12($A0)                                ;CurH

                    ctc2 R8,Gc5                                      ;TRX

                    ctc2 R9,Gc6                                      ;TRY

                    ctc2 R10,Gc7                                    ;TRZ

                    ctc2 R11,R26                                    ;H

                    nop                                                     

                    nop

                    ;COP RTPS

                    ;Xp=OFFX+(H/SZ)*X ; Yp=OFFY + (H/SZ)

                    cop2 0x0180001

                    nop

                    nop

                    ;STORE THE RESULT ON CUBETRANS

                    swc2 r15,0($S1)                               ; store on CubeTrans

                    addiu $S4,$S4,-1                              ; vert--

                    addiu $S1,$S1,4                               ; Next vert_trans ptr

                    bne $S4,R0,.loopVert

                    addiu $S0,$S0,8                               ; Next vert_mesh ptr

                    ;NCLIP TO CHECK IF THE FACE IS VISIBLE

                    cop2 0x1400006                              ; DOT PRODUCT TO MAC0

                    addiu $S3,$S3,-1                              ; face--

                    nop

                    mfc2 R3,R24                                    ; MAC0 dans R3

                    nop                                                      ; VERY IMPORTANT

                    nop                                                      ; BEFORE STORE THE RESULT

                    sw R3,0($S2)                                     ; STORE CLIP

                    bne $S3,R0,.loopface

                    addiu $S2,$S2,4                               ; Next ptr ClipFace

                    ;POP RET

                    lw R31,8(R29)                                 

                    lw $S0,12(R29)                                

                    lw $S1,16(R29)

                    lw $S2,20(R29)

                    jr R31                                                

                    addi R29,R29,0x0020                    ; SP+=32

;OBJECT POSITION

PosView:

                    word 0,8,160,256                             ;x,y,z,h

;OBJECT ROTATION Y,X

CurRadCube:

                    word 0,0

 

 

              

 

 

19-                      Conclusion

 

Voilà. Il reste encore beaucoup à apprendre, mais on en sait suffisamment pour faire des démos, des jeux en exploitant les merveilleuses possibilités de la Playstation et bien sûr continuer à percer les nombreux secrets que cache encore cette console. Les possibilités de cette machine étant assez extraordinaires il y a vraiment de quoi faire.