Concludiamo questa breve serie sul Nintendo DS analizzando quella parte del Nitro Processor che si occupa di gestire la grafica poligonale: il 3D Graphics Engine.
Il 3D Graphics Engine ha un’architettura abbastanza semplice. Si compone sostanzialmente di due macro blocchi: il Geometry Engine (GE), che si occupa di processare i poligoni, ed il Rendering Engine (RE), che disegna i pixel a partire dalle informazioni processate dal Geometry Engine.
Non sono presenti unità programmabili, ma tutte le funzioni sono organizzate secondo una struttura, più o meno classica, a pipeline fissa.
Si parte da un buffer FIFO (First In First Out), che accoglie i comandi di rendering e li somministra agli stadi successivi, nello stesso ordine in cui arrivano.
Il Geometry Engine si occupa di 3 processi:
- Trasformazione dei vertici da un sistema di coordinate ad un altro
- Calcolo delle luci per ogni vertice
- Trasformazione delle coordinate delle texture
Il processo 1) consiste in una serie di moltiplicazioni tra vettori e matrici, dove i primi sono i vertici dei poligoni ed le seconde sono i coefficienti di un sistema di equazioni, dette funzioni di proiezione o trasformazioni affini. Sono i calcoli matematici che stanno alla base delle regole della prospettiva.
Il processo 2) non è molto diverso dal precedente, e in sostanza si considerano i vettori che rappresentano la direzione delle fonti luminose in relazione ai singoli vertici. Attraverso opportune funzioni si determinano l’intensità ed il colore della luce su un dato vertice. Per effettuare questi calcoli occorrono sia le coordinate dei vertici, sia le cosiddette normali. Le normali, o perpendicolari, sono semplici vettori perpendicolari alla superficie dell’oggetto. Giocando opportunamente con le normali si può modulare la luce in modo tale da sfumare gli spigoli degli oggetti poligonali, creando così delle ombreggiature uniformi, ma questo lo vedremo più in là, durante la fase di rendering.
Il processo 3) consiste anch’esso in una serie di trasformazioni affini, e serve per determinare quali pixel delle texture vanno mappati sulle coordinate dei vertici. Basta calcolare soltanto i pixel dei vertici, mentre quelli interni al poligono sono ricavati per interpolazione durante la fase successiva, dentro il Rendering Engine.
Si capisce a colpo d’occhio che questi processi sono molto simili tra loro, perchè in tutti i casi bisogna sostanzialmente eseguire somme e moltiplicazioni in gran quantità. Il Geometry Engine, per questo motivo, può essere considerato come un coprocessore matematico estremamente specializzato. Sa fare solo due operazioni (somma e prodotto), ma le esegue molto velocemente e su intere matrici in un solo colpo.
Per raggiungere questa efficienza mantenendo bassa la complessità dell’hardware, i progettisti del Nitro Processor hanno deciso di lavorare soltanto su numeri interi, perchè trattare i numeri in floating point sarebbe eccessivamente dispendioso.
Com’è possibile effettuare calcoli sulle coordinate usando soltanto numeri interi? Intuitivamente vien da pensare che con tutte queste moltiplicazioni tra matrici, qualche numero con la virgola ogni tanto ci scappa, no? Apriamo una breve parentesi per approfondire un po’ la questione.
La soluzione si chiama Fixed-Point Arithmetic, e consiste nel trattare i numeri interi come se fossero numeri con la virgola, tenendo invariata la posizione della virgola che viene stabilita a priori. Questo si contrappone alla Floating Point Arithmetic, dove il calcolo della posizione della virgola (l’esponente) comporta moltissimi calcoli aggiuntivi rispetto al calcolo del numero vero e proprio (la mantissa).
Nel nostro caso le coordinate, sia quelle dei vertici (x,y,z) che quelle delle texture (u,v), sono espresse usando numeri interi a 16 bit così ripartiti:
- 1 bit per il segno
- 3 bit per la parte intera (che può andare quindi da -7 a +7)
- 12 bit per la parte decimale (da 0 fino a 4095 / 4096, che corrisponde a 0.999755859375)
Il massimo range rappresentabile varia quindi da -7.999755859375 a +7.999755859375, che possiamo tranquillamente approssimare a (-8.0, +8.0) estremi esclusi.
Nei calcoli interni al GE le coordinate nello spazio globale usano 16 bit per la parte intera, quindi si ha a disposizione uno spazio molto più ampio dove muoversi.
La matematica in Fixed Point ha il pregio di essere semplice da calcolare (richiede semplici ALU intere, al posto di complicate e costose FPU) e presenta una distribuzione perfettamente uniforme in tutto il range. Ciò significa che in genere i calcoli saranno più precisi, perchè soffre un po’ meno dei difetti di arrotondamento che affliggono i calcoli in virgola mobile. Lo svantaggio sta nel range molto limitato, inaccettabile per calcoli che richiedono la compresenza di numeri distanti tra loro di molti ordini di grandezza.
Nel nostro caso potremmo tranquillamente fare l’associazione 1 a 1 tra le unità del GE e i metri, e quindi possiamo tranquillamente accettare di avere un range di +/- 8.0 metri per descrivere un oggetto, e +/- 65.5 Km per descrivere il mondo all’interno del quale questo oggetto si muove. In base alle esigenze ovviamente si possono fare diverse associazioni, adottando un’opportuna scala, ma queste cose vanno concordate tra i programmatori ed i grafici che devono produrre i modelli 3D e le scene.
Chiusa la parentesi, proseguiamo con il Rendering Engine.
Una volta calcolate le coordinate che i poligoni avranno sullo schermo, si passa alla fase di Rastering, compiuta dal RE, che consiste nel calcolare il colore finale di ogni pixel dello schermo.
Nell’effettuare questo calcolo vengono prese in considerazione non solo il colore dei pixel della texture, ma anche il colore del vertice e quello della luce, ottenuto nel precedente passaggio da parte del Geometry Engine. Nell’equazione (o meglio nella catena di equazioni) entrano anche altri fattori quali il livello di trasparenza del poligono (o della texture), eventuali ombre proiettate da altri poligoni (stencil shadows) ed effetti vari (edge marking, antialiasing, toon shading, ecc…).
Il risultato del RE non viene ancora mostrato a schermo, ma subisce un ulteriore passaggio.
In base alla configurazione dei registri del Nitro Processor (ad opera del programmatore), l’output del RE può essere inviato al BG0 del 2D Graphics Engine A (che a sua volta effettuerà un ulteriore processo, fondendo il BG0 con il resto della grafica 2D), oppure può essere per così dire catturato in un banco di VRAM (una sorta di screenshot) per consentire eventuali effetti grafici di postprocessing (motion blur ad esempio) oppure per attuare alcune tecniche avanzate.
Una di queste tecniche, e forse la più utile e sfruttata ampiamente, consiste nell’effettuare uno screenshot ad ogni frame, poi scambiare il ruolo dello schermo superiore e quello inferiore, visualizzare lo screenshot precedente tramite il 2D Graphics Engine B (quello sprovviso di unità 3D), ed infine riscambiare gli schermi e procedere così ad libitum.
In questo modo si calcolano le scene 3D per entrambi gli schermi e tramite uno switch ad ogni fotogramma si possono apprezzare animazioni 3D su entrambi gli schermi ad un massimo di 30 fps (60 fps totali, alternando i frame pari sullo schermo superiore e i frame dispari su quello inferiore).
I dettagli di tutti i calcoli ed i passaggi effettuati dal GE e dal RE non sono mai entrati nel pubblico dominio, e quindi non li tratterò per evidenti motivi. Tuttavia il discorso merita di essere concluso con una breve rassegna dei numeri e delle caratteristiche tecniche di questa piccola GPU:
Formati di Texture
- da 8×8 fino a 1024×1024, con dimensioni potenze di 2 (quindi 8, 16, 32, 64, 128, 256, 512 e 1024)
- Index color con palette da 2,4 e 8 bit
- True color a 15 bit + 1 di alpha
- 4×4 Texel Compressed Texture (formato proprietario compresso)
- Translucent texture A5I3: 5 bit per l’alpha (32 livelli, da completamente opaco a completamente trasparente), 3 bit per il colore (da una palette di 8 colori)
- Translucent texture A3I5: 3 bit per l’alpha (8 livelli), 5 bit per il colore (palette da 32 colori)
Caratteristiche geometriche:
- Max 2048 triangoli (6144 vertici) per frame @ 60 fps (circa 120.000 triangoli al secondo)
- Supporta Triangoli, Quadrilateri, strip di triangoli e strip di quadrilateri
- 4 luci in hardware
- Transform & Lighting
Effetti:
- Gouraud shading
- Antialiasing
- Edge marking
- Toon shading
- Fog
- Stencil shadows
Per adesso è tutto. Nel prossimo articolo tratterò alcune tematiche relative allo sviluppo di videogames su altri tipi di piattaforme mobile, ovviamente parlo degli smartphone, e vedremo insieme quali sono i vantaggi e gli svantaggi di avere un sistema dotato di OS multitasking.
Vedremo inoltre come uno smartphone con una CPU da 500 MHz possa prendere sonore mazzate, nella grafica 2D, da un Nintendo DS con soli 66 MHz di clock.
130KM e rotti di lato mappa, oggettini di 8M di lato con 2048 triangoli per coprire tutto fanno sì che il massimo ottenibile con una simile estensione sia questo:
http://freespace.virgin.net/james.handlon/battlezone/bzshot11.gif
Non avranno un po’ esagerato con la mappa ?
Ehehe carinissimo :-)
Beh, quelle che ho citato sono le massime estensioni senza contare la scala, puoi concentrare tutto il range in uno spazio molto più piccolo (ad esempio una stanza) incrementando così la densità di informazioni.
Queste comunque sono cose secondarie… nessuno farebbe mai un gioco con una vera mappa di 130 kilometri su un DS! Viene difficile farlo su una PS3, figurati su questo povero cosino underpowered!
@ Antonio
“Vedremo inoltre come uno smartphone con una CPU da 500 MHz possa prendere sonore mazzate, nella grafica 2D, da un Nintendo DS con soli 66 MHz di clock”
Questa non me la voglio perdere!!! :D
Probabilmente sarà qualcosa del tipo:
“Sì, lo smartphone taldeitali tiene la super uber fi.. cpu mentre il DS va avanti col tam tam, però la super uber ..ga cpu per essere super uber f..a ha bisogno di super api, le quali sono talmente incasinate che un programmatore preferisce impazzire con i puntatori. Morale della favola: con le super api il programmatore va in vespa, mentre facendosi il mazzo su DS non solo vola ma ci mette pure meno tempo”
Vedremo se c’ho azzeccato anche se questa più che la storia di uno smartphone sempra quella delle ultime due playstation :P
No, il discorso è completamente diverso :-)
Un programmatore professionista, secondo te, si lascia spaventare dalle API? Gli argomenti saranno di tipo tecnico, e riguarderanno i calcoli necessari per generare una schermata tipica in 2D usando solo solo il software rendering in CPU (quello che fa uno smartphone di fascia media), contro l’hardware rendering di un dispositivo con acceleratore grafico (prenderò come esempio il Nintendo DS, ma il discorso vale anche per smartphone più evoluti con GPU integrata).
Poi farò anche un discorso relativo all’interazione “maligna” dell’OS multitasking, quindi tutti argomenti indipendenti dalle API e dall’interfaccia esposta al programmatore.
“Un programmatore professionista, secondo te, si lascia spaventare dalle API?”
Non dovrebbe, ma su ps2 per moltissimo tempo è stata una tragedia a causa delle api scadenti offerte da sony e sta pur certo che dall’altra parte non c’erano ragazzini delle medie a cercare di farle andare.
“Gli argomenti saranno di tipo tecnico, e riguarderanno i calcoli necessari per generare una schermata tipica in 2D usando solo solo il software rendering in CPU”
Ah bhè allora è chiaro: nella cpu mancano tutte quelle cosettine che si rigirano le matrici ad occhi chiusi, fargli fare lo stesso lavoro di una gpu, quella ne esce pazza. Non basterebbe una cpu 10 volte più veloce a recuperare terreno.
“Poi farò anche un discorso relativo all’interazione “maligna” dell’OS multitasking”
Ricorda che starai pur sempre parlando di uno smartphone quindi di un telefono: parte delle risorse della macchina dovranno sempre tenere d’occhio che non arrivi una chiamata, una mail o un sms.
Tra tutti i devkit che ho avuto modo di usare, gli unici decenti sono quelli Microsoft. Gli altri fanno tutti piangere, chi più chi meno.
Esattametne :-)
Le trasformazioni affini sono sicuramente tra le cose più pesanti, ma anche il semplice blit di un’immagine vedrai che viene gestito in modo molto diverso e poco efficiente ai fini della programmazione di un gioco. Molto importante anche la gestione dell’input, che in un sistema multitasking attraversa diversi strati software e alcuni context switch tra kernel e user mode.
L’assenza dell’OS in questi casi azzera quasi completamente le latenze (o comunque rimangono inferiori alla durata del singolo frame, quindi all’occhio umano appare come istantanea).
Esattamente, anche questo è un aspetto importante da considerare. L’OS deve continuamente interrompere il programma in foreground per dare un minimo timeslice anche agli altri processi. Un OS microkernel in particolare avrà un numero molto elevato di context switch e quindi un bell’overhead (Symbian ad esempio), perchè il suo stesso kernel è spezzato in vari processi con address space differenti e una semplice syscall può comportare anche una decina di context switch, procedura molto costosa.
Complimenti all’autore! :)
mi associo ai complimenti!
ciao,scusami la domanda forse molto stupida,ma mi chiadevo se esistesse un programma utile a editare questo tipo di grafica,xk sto traducendo un gioco del nintendo ds e ne avrei un bisogno matto
@autore
Sicuro che con questi articoli sul DS non stati violando qualche decina di NDA verso Nintendo o le aziende per cui hai lavorato?
Io infatti uso un emulatore per NDS su Samsung Galaxy Star a solo 1Ghz, il doppio di quello sopracitato a 500Mhz, eppure anche se i giochi (Rom) 2D mi funzionano alla grande quelli 3D vanno abbastanza a rilento. Secondo me il motivo per cui un gioco per NDS a 66Mhz va lento su uno smartphone a 500-1Ghz e che il Nintendo Ds Esegue i giochi via Hardrive mentre lo Smartphone gli emula via Software non disponendo dei processori interni del NDS e quindi con molte più esigenze.
Io credo che con uno Smartphone a 1Ghz gestisse i giochi per console via Hardrive si riuscirebbe senza problemi a eguagliare la potenza Ps Vita!!!! XD