La grafica 2D del Nintendo DS


I motori grafici

Il Nintendo DS è dotato di due distinti schermi LCD, entrambi di risoluzione 256x192 pixel e capaci di mostrare immagini fino a 262 mila colori (18 bpp, bit per pixel) e di due distinti motori grafici, ognuno dei quali collegato ad un solo schermo alla volta. Solamente uno dei due motori ha capacità grafiche 3D (quello che verrà sempre chiamato MAIN engine), mentre entrambi hanno quasi esattamente le stesse capacità riguardo la grafica 2D. E' inoltre possibile scegliere in che modo associare i due motori grafici ai due schermi LCD: ad esempio è possibile far sì che il MAIN engine generi immagini in grafica 3D/2D sullo schermo superiore mentre l'altro motore, il SUB engine, produce grafica 2D sullo schermo inferiore, il touch screen, o viceversa.

Le funzioni definite da libnds (in system.h) che operano su questa associazione tra i motori e gli schermi sono:

void lcdMainOnTop(void)

che lega l'output del motore principale allo schermo superiore e l'output del motore secondario allo schermo inferiore, e:

void lcdMainOnBottom(void)

che, viceversa, lega l'output del motore principale allo schermo inferiore e l'output del motore secondario allo schermo superiore. Infine vi è la pressoché inutile

void lcdSwap(void)

che scambia l'associazione corrente degli schermi.

Le modalità grafiche 2D

Ognuno dei motori grafici del Nintendo DS ha possibilità di lavorare con gli sfondi in un certo numero di modalità distinte, categorizzate in due gruppi: i modi testo e i modi bitmap. Il MAIN engine ha infine anche un modo diretto.

Nel modo diretto, chiamato Frame Buffer, è possibile impostare direttamente i valori RGB (red, green, blue - rosso, verde e blu) di ogni pixel dello schermo. In questo modo l'immagine potrà mostrare contemporaneamente fino a 32768 colori diversi (15 bpp) ma richiederà ben 96 KB di memoria video, considerando la dimensione dello schermo (256x192 pixel) e il fatto che ogni pixel richiede due byte di memoria video. In questa modalità, di fatto, si scavalca il motore grafico e quindi non sarà possibile utilizzare nessuna delle caratteristiche di questo, come ad esempio, ma non esclusivamente, gli sprite.

Nei modi bitmap è possibile impostare per ogni pixel dello schermo un colore scelto in una tavolozza (la palette) di 256 colori, palette che viene definita in un'area di memoria a parte appositamente dedicata, nella quale per ognuno di questi viene definita la terna RGB. In questo modo ogni immagine a pieno schermo può mostrare contemporaneamente al massimo 256 colori diversi ma richiede solamente 48 KB di memoria video poiché ogni pixel richiede esclusivamente un byte. E' però anche possibile utilizzare i modi bitmap a 32768 colori e quindi analoghi in un certo senso al modo diretto, pur essendo comunque gestiti dal motore grafico che, in questo caso, non viene scavalcato.

I modi testo, detti anche tiled ovvero a tessere sono di gran lunga sia i più complessi dal punto di vista del funzionamento sia i più utilizzati perché sfruttano in modo molto efficiente le funzionalità dell'hardware grafico 2D. In questa modalità si definisce nella memoria video un archivio di immagini da 8x8 pixel, a 16 o a 256 colori, e poi queste tessere verranno composte sullo schermo usando una mappa, una matrice che descrive quali tessere (come se fossero caratteri, da cui appunto modo testo) posizionare una a fianco dell'altra, una sopra l'altra, a formare un'immagine sola.

Sia nei modi testo che nei modi bitmap ogni schermo in realtà non avrà un solo sfondo ma ben 4 sfondi sovrapposti (i background) con la possibilità di definire quali pixel dovranno e quali non dovranno essere disegnati, permettendo così di poter vedere gli sfondi sottostanti attraverso quelli sovrastanti. Alcuni background poi, a seconda di quale modalità si sta utilizzando, potranno essere traslati in orizzontale e in verticale (scrolling) oppure addirittura ruotati o anche ingranditi o rimpiccioliti (rotation and scaling), tutto in modo molto semplice.

Infine ogni motore grafico, in aggiunta ai suddetti modi per gli sfondi, può gestire gli sprite. Ogni motore ne ha a disposizione fino a 128, di dimensioni a partire dal più piccolo definibile, 8x8 pixel, fino ai più grandi di 64x64 pixel. Ma parleremo della gestione degli sprite più approfonditamente in seguito.

Per attivare il modo grafico desiderato libnds ci mette a disposizione le due seguenti funzioni, definite anche queste in video.h:

void videoSetMode(u32 mode)

void videoSetModeSub(u32 mode)

che, rispettivamente, attivano la modalità desiderata sul motore grafico principale o sul secondario e inoltre specificano quali altre caratteristiche vorremo utilizzare: ad esempio potremmo richiedere che solo due dei quattro background siano attivi, che siano attivi gli sprite e spento il motore grafico 3D, e che si utilizzino le palette estese sia per i background che per gli sprite. A puro titolo di esempio.

La memoria video

Prima di addentrarci nei dettagli dell'uso della grafica, è necessario chiarire il funzionamento della memoria video del Nintendo DS. La consolle è equipaggiata con un totale di 656 KB di memoria video dedicata, suddivisa in 9 banchi di diversa dimensione e funzionalità. In comune tutti questi banchi hanno poche cose: ad esempio tutti hanno accesso a 16 bit in lettura e scrittura, cosa da tenere bene a mente perché per poter cambiare il valore di un byte o di un nibble in questa memoria bisogna prima leggere la halfword (16 bit) che lo contiene, modificare la parte desiderata ed infine riscrivere l'intera halfword. I tentativi di scrivere direttamente dei byte in queste memorie vengono ignorati, quindi bisogna prestare attenzione oppure sarà come non aver fatto nessuna scrittura. Questi 9 banchi, per convenzione, sono indicati con le lettere dalla A alla I e di questi i primi 4 (A, B, C, D) sono anche detti banchi principali e hanno una dimensione di 128 KB ognuno, mentre i successivi banchi hanno varie dimensioni tra i 16 KB e i 64 KB.

Ciò che apparirà complicato, e in effetti un po' lo è, è che alcuni banchi possono essere utilizzati con un motore grafico e non con l'altro, o possono essere dedicati ad alcune funzioni mentre altri banchi no, ed alcune funzioni possono essere svolte da un banco o da un altro mentre in certi casi per certe funzioni esiste uno ed un solo banco che è possibile utilizzare. A volte ciò potrebbe comportare lo spreco di un certo ammontare di memoria, oppure, al contrario, il dover rinunciare a certe funzionalità o limitarle. O anche potrebbe rendersi necessario ripianificare le associazioni tra i banchi e le funzioni a questi assegnate... tutto questo, fondamentalmente, perché per un banco non è possibile compiere due funzioni nello stesso momento.

Scendiamo in dettaglio: oltre ai 4 banchi principali, che come abbiamo detto sono anche i 4 più grandi avendo una taglia di 128 KB ognuno, abbiamo ancora il banco E, da 64 KB, il banco F ed il banco G, entrambi da 16 KB, poi H da 32 KB ed infine l'ultimo banco, I, da 16 KB.

Le diverse funzioni che è possibile assegnare ai banchi sono:

- LCD (nessuna funzione): il banco è mappato ad un indirizzo fisso, diverso per ognuno, in modo da comporre un unico continuo blocco di memoria, se dovesse servire. Tutti i banchi sono in grado di lavorare in questa modalità.
- Texture memory: il banco viene utilizzato come memoria per le texture che verranno usate dal motore 3D, evidentemente solo del MAIN engine dato che il SUB engine non è dotato di capacità 3D come già detto. Solo i 4 banchi principali (A, B, C, D) sono in grado di svolgere questa funzione.
- Palette texture memory: il banco viene utilizzato come memoria per le palette (tavolozze colori) delle texture, per quelle texture che ne hanno bisogno. Solo i banchi E, F e G sono in grado di svolgere questa funzione.
- Background memory del MAIN engine: il banco viene utilizzato come memoria per i background (indipendentemente da quanti se ne useranno) del motore principale. Possibile con tutti i banchi tranne gli ultimi due, H e I.
- Background memory del SUB engine: il banco viene utilizzato come memoria per i background del motore secondario, analogamente a quello che capita con il MAIN engine. Questo modo è possibile solo sui banchi C, H e I.
- Sprite memory del MAIN engine: il banco viene utilizzato come memoria per gli sprite del motore principale. Solo sui banchi A, B, E, F e G.
- Sprite memory del SUB engine: il banco viene utilizzato come memoria per gli sprite del motore secondario. Solo i banchi D e I.
- Memoria per le palette estese dei background / degli sprite del MAIN / SUB engine (tutte le 4 modalità sono distinte): parleremo delle palette estese più avanti, per ora accontentatevi di sapere che sul Nintendo DS è possibile dichiarare fino a 16 palette (da 16 o da 256 colori) per ogni background e fino ad altre 16 palette per gli sprite. Per ognuno dei due motori, ovviamente.

Le funzioni definite da libnds nel file video.h per impostare le modalità di funzionamento dei banchi sono le seguenti:

void vramSetBankA(VRAM_A_TYPE a)

void vramSetBankB(VRAM_B_TYPE b)

void vramSetBankC(VRAM_C_TYPE c)

void vramSetBankD(VRAM_D_TYPE d)

void vramSetBankE(VRAM_E_TYPE e)

void vramSetBankF(VRAM_F_TYPE f)

void vramSetBankG(VRAM_G_TYPE g)

void vramSetBankH(VRAM_H_TYPE h)

void vramSetBankI(VRAM_I_TYPE i)

Come parametro ognuna accetta un membro di un'enumerazione definita appositamente per ogni banco, stanti appunto le diverse funzionalità di ogni banco. Oltre alle funzioni sopracitate si definiscono anche le seguenti funzioni che possono risultare comode:

u32 vramSetMainBanks(VRAM_A_TYPE a, VRAM_B_TYPE b, VRAM_C_TYPE c, VRAM_D_TYPE d)

Imposta le proprietà per i 4 banchi principali (A, B, C, D) e restituisce il valore delle modalità precedentemente attive nei suddetti banchi, modalità che possono poi essere ripristinate rapidamente con l'apposita funzione:

void vramRestoreMainBanks(u32 vramTemp)

La tabella seguente riassume le modalità possibili per ogni banco, utilizzando le enumerazioni di libnds che possono essere utilizzate con le sopracitate funzioni per attivare le modalità richieste.

Memoria (dimensione) Modalità utilizzabili (*)
VRAM_BANK_A (128 KB) VRAM_A_LCD
VRAM_A_TEXTURE
VRAM_A_MAIN_BG
VRAM_A_MAIN_SPRITE
VRAM_BANK_B (128 KB) VRAM_B_LCD
VRAM_B_TEXTURE
VRAM_B_MAIN_BG
VRAM_B_MAIN_SPRITE
VRAM_BANK_C (128 KB) VRAM_C_LCD
VRAM_C_TEXTURE
VRAM_C_MAIN_BG
VRAM_C_SUB_BG
VRAM_BANK_D (128 KB) VRAM_D_LCD
VRAM_D_TEXTURE
VRAM_D_MAIN_BG
VRAM_D_SUB_SPRITE

VRAM_BANK_E (64 KB) VRAM_E_LCD
VRAM_E_MAIN_BG
VRAM_E_MAIN_SPRITE
VRAM_E_TEX_PALETTE
VRAM_E_BG_EXT_PALETTE
VRAM_BANK_F (16 KB) VRAM_F_LCD
VRAM_F_MAIN_BG
VRAM_F_MAIN_SPRITE
VRAM_F_TEX_PALETTE
VRAM_F_BG_EXT_PALETTE
VRAM_F_SPRITE_EXT_PALETTE
VRAM_BANK_G (16 KB) VRAM_G_LCD
VRAM_G_MAIN_BG
VRAM_G_MAIN_SPRITE
VRAM_G_TEX_PALETTE
VRAM_G_BG_EXT_PALETTE
VRAM_G_SPRITE_EXT_PALETTE
VRAM_BANK_H (32 KB) VRAM_H_LCD
VRAM_H_SUB_BG
VRAM_H_SUB_BG_EXT_PALETTE
VRAM_BANK_I (16 KB) VRAM_I_LCD
VRAM_I_SUB_BG
(1)
VRAM_I_SUB_SPRITE
VRAM_I_SUB_SPRITE_EXT_PALETTE

(*) L'elenco non è completo. Esistono altri valori, nelle enumerazioni definite da libnds, che però non sono relativi ad altre modalità grafiche ma semplicemente all'associazione degli indirizzi di memoria per i vari banchi. Non tratteremo l'argomento, per ora.

Per l'elenco completo delle enumerazioni definite da libnds, tutte le possibili modalità d'utilizzo dei banchi di memoria video vedi la Tabella 1:Tutte le modalità della memoria video.

(1) In realtà questa specifica enumerazione non è stata mai definita in libnds, comunque il banco I può essere utilizzato per i background del SUB engine, quindi mi è parso corretto riportarlo.

Uso del modo Frame Buffer

Come abbiamo anticipato, uno dei possibili modi grafici è il Frame Buffer, ovvero la possibilità, limitata però al solo MAIN engine, di controllare direttamente l'intensità di rosso, verde e blu di ogni pixel sullo schermo. Esiste sul Nintendo DS la possibilità di attivare uno di 4 Frame Buffer, numerati da 0 a 3, ed ognuno di questi usa come memoria associata uno dei banchi di memoria video principali, quindi il modo MODE_FB0 utilizza il banco A, il MODE_FB1 si serve del banco B, il MODE_FB2 il banco C ed infine al MODE_FB3 è abbinato il banco D. Questo consente di implementare semplici tecniche di animazione: utilizzando due Frame Buffer è possibile, ad esempio, disegnare un fotogramma senza mostrarlo finché non è completo e poi, una volta mostrato quello completo, cancellare e ridisegnare un nuovo fotogramma su quello che era stato mostrato prima. Questa tecnica si chiama Double Buffering e se lo scambio viene fatto con un intervallo regolare tra un fotogramma e l'altro può funzionare molto bene, ed è quello che si faceva ad esempio su un PC quando le schede grafiche non avevano capacità di accelerazione 2D/3D: di nascosto si cancellava e si ridisegnava tutto da capo, pixel per pixel, ogni volta.

Nell'esempio che segue utilizzeremo un Frame Buffer, il numero 0, configurando il banco di memoria video A nel modo corretto, il modo LCD, per creare un piccolo programmino che disegni un pixel bianco dove l'utente preme con il pennino.

#include <nds.h>

int main(void) {

  // impostiamo la memoria video
  vramSetBankA (VRAM_A_LCD);
  
  // impostiamo il modo framebuffer 0 sul MAIN engine
  videoSetMode (MODE_FB0);
  
  // associamo il MAIN engine allo schermo inferiore, il touchscreen
  lcdMainOnBottom ();
  
  while(1) {
  
    // leggiamo lo stato dei tasti e del touch screen
    scanKeys();
    
    // stiamo premendo sul touch screen?
    if (keysHeld() & KEY_TOUCH) {

      // leggi la posizione del pennino
      touchPosition touch; touchRead(&touch);
      
      // metti un pixel bianco dove è stato premuto
      VRAM_A [touch.py * SCREEN_WIDTH + touch.px] = RGB5(31,31,31);
    }
      
    // aspettiamo il prossimo refresh
    swiWaitForVBlank();
  }
}

I modi bitmap

A differenza dell'appena citato modo Frame Buffer, che funziona utilizzando direttamente l'intero schermo, i modi tiled e bitmap lavorano sui background e dato che, come abbiamo già detto, è possibile avere fino a 4 background contemporaneamente su ogni schermo, va da sé che potremmo anche avere contemporaneamente dei background tiled e dei background bitmap. Quindi, prima di iniziare a parlare dei background bitmap in specifico, chiariamo come si attivano i famosi 4 background.

Esistono 6 distinte modalità, tutte funzionanti in egual modo su entrambi i motori grafici, che si differenziano semplicemente in base a quali saranno le funzioni possibili dei 4 background. Perché ogni background può essere semplicemente di tipo testo, oppure rotazionale o infine di tipo esteso (il nome completo sarebbe rotazionale esteso, per brevità lo chiameremo sempre semplicemente esteso), ed è solo quest'ultimo tipo, l'esteso appunto, che consente la modalità bitmap. La tabella seguente dettaglia il modo in cui lavoreranno i 4 background nelle 6 modalità possibili, con i nomi di queste modalità così come definiti da libnds.

Modo grafico Background 0 (*) Background 1 Background 2 Background 3
MODE_0_2D Testo Testo Testo Testo
MODE_1_2D Testo Testo Testo Rotazionale
MODE_2_2D Testo Testo Rotazionale Rotazionale
MODE_3_2D Testo Testo Testo Esteso
MODE_4_2D Testo Testo Rotazionale Esteso
MODE_5_2D Testo Testo Esteso Esteso

(*) Attivando il motore 3D il background 0 viene "ceduto" alla grafica 3D ed utilizzato in esclusiva da questo. Solo sul MAIN engine, ovviamente, come abbiamo visto.

In queste modalità appena descritte in realtà non siamo neanche davvero obbligati ad usare tutti e 4 i background ed infatti, se non specifichiamo altrimenti, nessuno dei background verrà attivato. Quindi quando dichiareremo il modo in cui opereremo dovremo ricordarci di attivare i background che desideriamo utilizzare, analogamente a come faremo quando vorremo anche utilizzare gli sprite, ad esempio, o le palette estese.

Dobbiamo ancora dire che, come si accennava all'inizio, è possibile avere modi bitmap a 256 e 32768 colori. Per selezionare il nostro preferito non dovremo cambiare modalità grafica, semplicemente cambiare modalità al background, e per questo libnds ha definito delle costanti (in background.h) per accedere facilmente ai registri di controllo dei background e per settare le modalità desiderate. I registri principali per controllare i 4 background sul MAIN engine sono, rispettivamente, REG_BG0CNT, REG_BG1CNT, REG_BG2CNT e REG_BG3CNT mentre sul SUB engine sono REG_BG0CNT_SUB, REG_BG1CNT_SUB, REG_BG2CNT_SUB e REG_BG3CNT_SUB. I modi bitmap sui background estesi permettono l'utilizzo di bitmap di 4 diverse dimensioni (128x128 pixel, 256x256 pixel, 512x256 pixel -l'unica a non essere quadrata- ed infine 512x512 pixel) per ognuna delle due profondità di colore possibili generando quindi 8 possibilità che vengono definite in libnds, in background.h, come seguono:

256 colori (8 bpp):

BG_BMP8_128x128
BG_BMP8_256x256
BG_BMP8_512x256
BG_BMP8_512x512
(richiede 256KB di memoria video)

32768 colori (15 bpp):

BG_BMP16_128x128
BG_BMP16_256x256
BG_BMP16_512x256
(richiede 256KB di memoria video)
BG_BMP16_512x512 (richiede 512KB di memoria video)

Per completezza è opportuno precisare che in realtà il MAIN engine ha ancora una modalità grafica nella quale i 4 background distinti spariscono e rimane un unico background in modo bitmap (chiamato Mode 6 o anche large bitmap mode) che consente bitmap di dimensioni 1024x512 pixel oppure 512x1024 pixel a 256 colori (8 bpp). Il modo in questione è definito in libnds come MODE_6_2D e le bitmap, controllabili attraverso il registro del background 2, REG_BG2CNT, sono definite come segue:

BG_BMP8_1024x512 (richiede 512KB di memoria video)
BG_BMP8_512x1024 (richiede 512KB di memoria video)

Nell'esempio che segue utilizzeremo il background 3 (esteso) del modo 4 (il modo 3 o il modo 5 non avrebbero fatto differenza comunque) del SUB engine, per disegnare un pixel bianco dove l'utente preme con il pennino, in modo analogo all'esempio precedente. Da notare però che in questo esempio utilizzeremo il motore grafico secondario e il banco di memoria C impostato in modalità background (e non il banco A poiché non può lavorare come background per il SUB engine, come abbiamo già detto). Riguardo la matrice di rotazione/dimensionamento alla quale faremo riferimento: ne parleremo più avanti, quando tratteremo l'argomento dei background rotazionali.

#include <nds.h>

int main(void) {

  // impostiamo la memoria video (banco C)
  vramSetBankC (VRAM_C_SUB_BG);
  
  // impostiamo il modo 4 sul SUB engine
  // e attiviamo il background 3
  videoSetModeSub (MODE_4_2D | DISPLAY_BG3_ACTIVE);
  
  // impostiamo il BG 3 al modo 256x256 a 16 bpp
  // e indichiamo dove inizia in memoria la bitmap
  REG_BG3CNT_SUB = BG_BMP16_256x256 | BG_BMP_BASE(0);
  
  // 'azzera' la matrice di rotazione/dimensionamento per il background 3
  REG_BG3PA_SUB = 1 << 8;
  REG_BG3PB_SUB = 0;
  REG_BG3PC_SUB = 0;
  REG_BG3PD_SUB = 1 << 8;
  
  // associamo il MAIN engine allo schermo superiore
  // quindi il SUB engine sarà associato al touchscreen
  lcdMainOnTop ();
  
  while(1) {
  
    // leggiamo lo stato dei tasti e del touch screen
    scanKeys();
    
    // stiamo premendo sul touch screen?
    if (keysHeld() & KEY_TOUCH) {
     
      // leggi la posizione del pennino
      touchPosition touch; touchRead(&touch);
      
      // metti un pixel bianco dove è stato premuto
      BG_GFX_SUB [touch.py * SCREEN_WIDTH + touch.px] = RGB5(31,31,31) | BIT (15);
    }

    // aspettiamo il prossimo refresh
    swiWaitForVBlank();
  }
}

Sono almeno tre le differenze che saltano subito all'occhio in questo esempio, se paragonato all'esempio precedente, quello del Frame Buffer. Il primo è che nell'impostare la modalità bitmap per il background 3 abbiamo anche dovuto dirgli dove, nella memoria video assegnata ai background, inizia la bitmap che vogliamo visualizzare. Questo perché, ovviamente, si può usare lo stesso banco di memoria video non per un solo background ma per tutti, capienza permettendo, quindi va specificata per ogni background una informazione che dice dove inizia la bitmap, in passi da 16 KB.

La seconda differenza è che per accendere un pixel questa volta non scriviamo all'indirizzo del banco di memoria C ma ad un indirizzo apposito (BG_GFX_SUB, l'analogo per il MAIN engine è BG_GFX) al quale è associato l'indirizzo della memoria dei background del SUB engine. Questo ha il vantaggio che se dovessimo un domani decidere di usare un altro banco di memoria invece del banco C non dovremmo comunque modificare questo riferimento nel codice. Infine c'è anche da dire che quando al banco di memoria abbiamo detto di lavorare in una modalità diversa dal modo LCD, questo banco non sarà più raggiungibile all'indirizzo dove sarebbe stato raggiungibile altrimenti. Questo vale ovviamente per tutti i banchi di memoria video e più avanti vedremo addirittura che in alcune modalità i banchi di memoria video non saranno mappati a nessun indirizzo e quindi non sarà neanche possibile scriverci dentro.

Ma non anticipiamo ora questo, e invece facciamo notare l'ultima delle tre differenze: questa volta oltre che dare al pixel da disegnare il colore bianco (su 15 bit), abbiamo anche dovuto accendere il bit più significativo della halfword. Questo bit è il canale alfa (trasparenza) e ha due valori ai quali sono legati due semplici significati: se è 1 allora il pixel va disegnato, se è 0 invece il pixel non va disegnato e quindi sarà possibile vedere i background sottostanti attraverso questo background. Una possibilità che il Frame Buffer non ha.

Un'ultima considerazione, valida sia per i background di tipo bitmap, dei quali abbiamo appena scritto, che per i background di tipo testo, che ci apprestiamo a descrivere: in tutte le modalità che usano le palette (quindi sia i modi a 16 che i modi a 256 colori) i pixel definiti di colore 0 (zero) non saranno disegnati se sono attivi altri background dietro a quello sul quale stiamo disegnando. Questo perché altrimenti non sarebbe mai possibile vedere gli altri background attraverso questo, il che renderebbe infine gli altri background praticamente inutili. Torneremo comunque su questo argomento alla fine di questo documento.

I modi tiled

I modi 'a tessere' sono di gran lunga i modi più utilizzati per il grande risparmio di memoria e l'alta efficienza che portano ai programmi che li utilizzano. Il risparmio di memoria proviene dal riutilizzo continuo delle stesse tessere di 8x8 pixel, che quindi andranno ad occupare sempre lo stesso ammontare di memoria video indipendentemente da quante volte questa tessera verrà poi visualizzata sullo schermo. E' possibile utilizzare fino a 1024 tessere diverse per background se questo è nella modalità testo o esteso, e ogni tessera può anche essere visualizzata ribaltata orizzontalmente (mirror), ovvero con tutti i pixel di ogni riga disegnati in ordine inverso da destra verso sinistra, oppure ribaltata verticalmente (flip), ovvero con tutti i pixel di ogni colonna disegnati sottosopra, o anche avere entrambe le caratteristiche contemporaneamente. Nella modalità rotazionale invece non sono possibili né mirrorflip e il limite massimo di tessere utilizzabili è 256.

Per utilizzare questi modi si dovranno quindi distinguere, nella memoria video riservata ai background, un'area dove depositare le tessere e un'area dove depositare le mappe che descrivono come le tessere andranno posizionate per comporre il background. Le mappe dei background in modalità testo possono avere dimensioni di 32x32 tessere (generando quindi un background di 256x256 pixel) o di 64x32 o 32x64 tessere, sebbene in ogni caso siano in realtà due mappe da 32x32 tessere affiancate o sovrapposte l'una all'altra, oppure ancora possiamo avere una mappa di 64x64 tessere, in realtà utilizzando 4 mappe adiacenti da 32x32 tessere ognuna. In libnds queste modalità per i tiled background di tipo testo sono definite come segue:

BG_32x32
BG_64x32
BG_32x64
BG_64x64

Per i background di tipo esteso invece è possibile utilizzare mappe a partire dalla dimensione di 16x16 tessere (512 byte di dimensione in memoria video, genera un background di 128x128 pixel) fino a 128x128 tessere (32 KB occupati dalla mappa, genera un background di 1024x1024 pixel) ma stavolta rappresentate in memoria come un'unica mappa, e quindi ne sarà immediato l'utilizzo. In libnds queste modalità per i tiled background di tipo esteso si possono utilizzare attraverso le seguenti definizioni:

BG_RS_16x16
BG_RS_32x32
BG_RS_64x64
BG_RS_128x128

che sono le stesse che useremo per i tiled background di tipo rotazionale, mentre per scegliere la profondità di colore per le tessere dei background testo sono definite le seguenti costanti:

BG_COLOR_16
BG_COLOR_256

che selezionano rispettivamente, come si intuisce, le tessere a 16 colori (4 bpp, quindi ogni halfword contiene 4 pixel) e quelle a 256 colori (8 bbp, 2 pixel per ogni halfword). Queste costanti non sono invece da usare per i background di tipo esteso: questi possono esclusivamente utilizzare tessere a 256 colori. Ma quali sono questi colori tra i quali possiamo scegliere i nostri 16 o 256? In qualunque modo grafico a 16 e 256 colori (quindi anche nel modo bitmap a 8 bpp) il colore di ogni pixel è in realtà l'indice di un elemento nella tavolozza (palette) e ognuno dei due motori grafici del Nintendo DS ha una palette apposta per i background e una apposta, distinta dalla precedente, per gli sprite, oltre alle palette estese di cui parleremo più avanti. (Tutte queste palette sono memorizzate in un'area di memoria appositamente dedicata e non facente parte dei 9 banchi di memoria video). Ogni elemento della palette è una halfword che definisce il colore su 15 bit in modo del tutto analogo a quanto fatto nella modalità video diretta del Frame Buffer. Le quattro palette standard sono definite da libnds come vettori ognuno di 256 elementi con i seguenti nomi:

BG_PALETTE
SPRITE_PALETTE
BG_PALETTE_SUB
SPRITE_PALETTE_SUB

Quindi per utilizzare un modo grafico testo dovremo obbligatoriamente svolgere almeno le seguenti operazioni:

- Impostare un banco di memoria video per i background.
- Impostare un motore grafico in un modo tra il modo 0 e il modo 5, e attivare almeno un background.
- Caricare nella memoria video le tessere di 8x8 pixel, prestando attenzione alla profondità del colore (le tessere a 16 colori occupano 32 byte ognuna mentre le tessere a 256 colori occupano 64 byte ognuna)
- Caricare nella palette dei background del motore grafico in uso tutti i colori che verranno utilizzati dalle tessere
- Caricare nella memoria video almeno una mappa per le tessere, che usi le tessere definite
- Impostare il background prescelto nella modalità corretta, in accordo alle dimensioni della mappa e al numero di colori

Nell'esempio utilizzeremo il MAIN engine per visualizzare un quadratino circa al centro del touchscreen, utilizzando il modo grafico 0 e il background 1 in modo testo a 16 colori, con una mappa 32x32 tessere. Proveremo a far muovere il quadratino seguendo il pennino trascinato sullo schermo.

#include <nds.h>

int main(void) {

  // impostiamo la memoria video (banco A)
  vramSetBankA (VRAM_A_MAIN_BG);
  
  // impostiamo il modo 0 sul MAIN engine e attiviamo il BG 1
  videoSetMode (MODE_0_2D | DISPLAY_BG1_ACTIVE);
  
  // creiamo una tessera vuota (32 byte)
  u32 TesseraVuota[8] = 
  {
    0x00000000,
    0x00000000,
    0x00000000,
    0x00000000,
    0x00000000,
    0x00000000,
    0x00000000,
    0x00000000
  };
  
  // creiamo una tessera con un quadratino (32 byte)
  u32 TesseraQuadratino[8] = 
  {
    0x11111111,
    0x10000001,
    0x10000001,
    0x10000001,
    0x10000001,
    0x10000001,
    0x10000001,
    0x11111111
  };
  
  // copiamo le tessera in memoria video (32 byte ognuna)
  swiCopy(TesseraVuota, BG_TILE_RAM(1), 32);
  swiCopy(TesseraQuadratino, (u8*)BG_TILE_RAM(1) + 32, 32);
  
  // impostiamo la palette ai colori 0=nero, 1=blu intenso
  BG_PALETTE [0] = RGB5(0,0,0);
  BG_PALETTE [1] = RGB5(0,0,31);
  
  // riempiamo la mappa usando la tessera vuota (è la tessera #0)
  int i;
  for (i=0;i<32*32;i++)
    ((u16*)BG_MAP_RAM(0)) [i] = 0;
    
  // mettiamo la tessera con il quadratino (la #1) circa in centro
  // allo schermo (15,11)
  ((u16*)BG_MAP_RAM(0)) [11*32+15] = 1;
    
  // impostiamo il BG 1 al modo tiled 32x32 a 4 bpp
  // e indichiamo dove iniziano nella memoria video la mappa e le tessere
  REG_BG1CNT = BG_32x32 | BG_COLOR_16 | BG_MAP_BASE(0) | BG_TILE_BASE(1);
  
  // associamo il MAIN engine allo schermo inferiore, il touchscreen
  lcdMainOnBottom ();
  
  int down_X=0, down_Y=0;
  while(1) {
  
    // leggiamo lo stato dei tasti e del touch screen
    scanKeys();
    
    // abbiamo appena toccato il touch screen?
    if (keysDown() & KEY_TOUCH) {
     
      // leggi la posizione del pennino
      touchPosition touch; touchRead(&touch);
      
      // memorizziamola per vedere dove va a finire
      down_X=touch.px;
      down_Y=touch.py;
    }
  
    // oppure siamo ancora appoggiati sul touch screen?
    else if (keysHeld() & KEY_TOUCH) {
     
      // leggi la posizione del pennino
      touchPosition touch; touchRead(&touch);
      
      // sposta il BG 1 seguendo il trascinamento
      REG_BG1HOFS = down_X - touch.px;
      REG_BG1VOFS = down_Y - touch.py;
    }
    
    // altrimenti riallinea al centro il quadratino
    else {
     
      // sposta il BG 1 "a zero"
      REG_BG1HOFS = 0;
      REG_BG1VOFS = 0;
    }
    
    // aspettiamo il prossimo refresh
    swiWaitForVBlank();
  }
}

Ora chiariamo alcuni dettagli. Il primo è relativo alla funzione swiCopy(). Questa funzione utilizza delle procedure che effettuano la copia di dati da una locazione all'altra tra le varie memorie della consolle; non è una procedura veloce, ma è molto comoda perché gestisce autonomamente la scrittura a 16 bit che è richiesta dalla memoria video, nonostante nella funzione si debba specificare il numero di byte che devono essere copiati. Non importa, ma ricordatevi che in realtà nel nostro caso la funzione non farà la copia di 32 byte ma di 16 halfword. Ovviamente a patto che non vogliate copiare un numero dispari di byte, nel qual caso l'operazione fallirebbe.

Un altro dettaglio è come viene utilizzata la memoria video; come si intuisce facilmente nel nostro banco di memoria andremo a memorizzare sia le tessere quanto le mappe che useremo per creare i nostri background, perciò dovremo prestare attenzione a non sovrascrivere le mappe con le tessere o viceversa. Dato che ogni mappa occupa 32x32 per 2 byte = 2 KB allora ad ogni incremento di BG_MAP_RAM e di BG_MAP_BASE ci sposteremo avanti di 2 KB, ma è possibile utilizzare al massimo fino solo a 32 mappe, quindi dovremo obbligatoriamente limitarci ai primi 64KB. Analogamente ad ogni incremento di BG_TILE_RAM e di BG_TILE_BASE ci sposteremo di 16 KB quindi saltando il primo blocco, come abbiamo fatto nell'esempio, abbiamo lasciato liberi i primi 16 KB per essere sicuri di non sovrascrivere la mappa che si trova all'inizio della memoria.

REG_BG1HOFS e REG_BG1VOFS invece sono i due registri write-only (cioè è solamente possibile scrivere in questi registri ma non leggerne il valore) che permettono di traslare (scroll) orizzontalmente e verticalmente il background, in questo caso il background 1. Funzionano solo nei background di tipo testo mentre non funzionano né con i rotazionali né con gli estesi, che hanno altri registri per svolgere queste e altre ancor più complesse operazioni. Forse avrete anche notato che il quadratino che scompare da destra riappare a sinistra e viceversa: è l'effetto del wrapping del background, e non è disattivabile nella modalità testo. In verticale invece il quadratino che scompare in basso non appare immediatamente dall'alto e viceversa: questo perché il background è più alto dello schermo (256 pixel di altezza del background contro i 192 pixel di altezza dello schermo) e quindi il wrapping non è immediato.

L'ultimo dettaglio è relativo al fatto che si possano usare fino a 1024 tessere al massimo nonostante ogni elemento della mappa sia in realtà a 16 bit mentre per rappresentare i numeri tra 0 e 1023 sono sufficienti 10 bit: tutto questo avviene perché i 6 bit più significativi di ogni elemento nella mappa sono riservati a funzioni speciali come le palette estese, i primi 4 bit, ed i rimanenti due bit sono proprio quelli che, se settati, attivano rispettivamente il mirror (10° bit) e il flip (11° bit) della tessera. In libnds (in background.h) sono definite le due comode costanti:

TILE_FLIP_H
TILE_FLIP_V

Quindi per visualizzare una tessera tess in modo che appaia ribaltata sia orizzontalmente che verticalmente dovremo calcolare il valore da inserire nella mappa con la formula:

(tess & 0x03FF) | TILE_FLIP_H | TILE_FLIP_V

Come già abbiamo scritto in precedenza, solo i background di tipo testo ed esteso utilizzano questo sistema su 16 bit, con 1024 possibili tessere diverse. I background di tipo rotazionale invece utilizzano solo mappe con elementi a 8 bit e quindi possono avere al massimo 256 tessere distinte e sono prive di capacità di mirror o flip o delle palette estese.

I background rotazionali

Per ultimi trattiamo i background rotazionali, tutto sommato i meno interessanti poiché una sorta di 'versione economica' dei background estesi, dato che tutto quello che si può fare con un background rotazionale si può fare ugualmente con un background esteso. Questi possono risultare utili solo nel caso in cui si voglia risparmiare memoria video, essendo le mappe dei background rotazionali più piccole, al costo però di una minor flessibilità. Come già detto con background di questo tipo saremo limitati a 256 tessere diverse al massimo, non avremo possibilità di ribaltamento orizzontale o verticale della tessera e non potremo neanche utilizzare le palette estese. In più i background rotazionali come gli estesi supportano solamente tessere a 256 colori, mentre i background di tipo testo supportano anche tessere a 16 colori.

Come in tutti i modi tiled, anche in questo dovremo definire delle mappe nelle quali descriveremo la posizione di ogni tessera. Come nel modo esteso e a differenza del modo testo in questo modo le mappe partono da una dimensione minore, 16x16 tessere (256 byte di dimensione in memoria video, genera un background di 128x128 pixel), e salgono fino a 128x128 tessere (16 KB occupati dalla mappa in memoria video, genera un background di 1024x1024 pixel) ma senza essere rappresentate come mappe di dimensione base affiancate, e quindi ne sarà immediato l'utilizzo.

Le costanti definite da libnds per i background rotazionali sono le seguenti:

BG_RS_16x16
BG_RS_32x32
BG_RS_64x64
BG_RS_128x128

Esiste poi ancora un bit che è estremamente importante: un bit che attiva il wrapping del background. Quando il wrapping è disattivato, se il background è più piccolo dello schermo vedremo solo una parte dello schermo occupata dal background, altrimenti l'intero background sarà ripetuto sopra e sotto e a destra e sinistra tante volte quante necessarie a riempire l'intero schermo. Può essere utile per ottenere vari effetti grafici di interessante impatto. Il wrapping è attivabile o disattivabile attraverso le costanti:

BG_WRAP_ON
BG_WRAP_OFF

come al solito definite nel file background.h. Attenzione però, questi flag sono utilizzabili solo con i background rotazionali o estesi, i background di tipo testo, invece, hanno il wrap attivo di default e non è disattivabile.

Nell'esempio che segue utilizzeremo il SUB engine per visualizzare un background rotazionale al centro del quale disegneremo due quadratini blu e due crocette rosse. Trascinando il pennino sullo schermo verso l'alto o verso il basso forzeremo il ridimensionamento (scale) del background, mentre trascinando il pennino a destra e sinistra forzeremo la rotazione (rotation) del background in senso orario o antiorario. Infine tenendo premuto uno dei due tasti sulla spalla del Nintendo DS (i tasti L e R) attiveremo il wrapping per osservare l'effetto che fa.

#include <nds.h>

// questa funzione genera un "falso" coseno, usando un'onda triangolare
// restituisce +1 .. -1 in virgola fissa 24.8
int fake_cos (int angle) {
  int ret=0;
  angle %= 360;
  if (angle<0)
    angle += 360;
  switch (angle) {
    case 0 ... 180:ret = 256-(512*angle/180);
                    break;
    case 181 ... 359:ret = ((angle-180)*512/180) - 256;
                      break;
  }
  return (ret);
}

// sin(x) = cos(90-x)
int fake_sin (int angle) {
  return (fake_cos(90 - angle));
}

int main(void) {

  // impostiamo la memoria video (banco C)
  vramSetBankC (VRAM_C_SUB_BG);
  
  // impostiamo il modo 1 sul SUB engine e attiviamo il BG 3
  videoSetModeSub (MODE_1_2D | DISPLAY_BG3_ACTIVE);
  
  // creiamo una tessera quasi vuota (64 bytes)
  u8 TesseraQuasiVuota[64] = 
  {
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,2,1,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0
  };
  
  // creiamo una tessera con un quadratino
  u8 TesseraQuadratino[64] =
  {
    1,1,1,1,1,1,1,1,
    1,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,1,
    1,0,0,0,0,0,0,1,
    1,1,1,1,1,1,1,1
  };
  
  // creiamo una tessera con una crocetta
  u8 TesseraCrocetta[64] = 
  {
    0,0,0,2,2,0,0,0,
    0,0,0,2,2,0,0,0,
    0,0,0,2,2,0,0,0,
    2,2,2,2,2,2,2,2,
    2,2,2,2,2,2,2,2,
    0,0,0,2,2,0,0,0,
    0,0,0,2,2,0,0,0,
    0,0,0,2,2,0,0,0
  };
 
  // copiamo le tessere in memoria video (64 byte ognuna)
  swiCopy(TesseraQuasiVuota, BG_TILE_RAM_SUB(1), 64);
  swiCopy(TesseraQuadratino, (u8*)BG_TILE_RAM_SUB(1) + 64, 64);
  swiCopy(TesseraCrocetta, (u8*)BG_TILE_RAM_SUB(1) + 128, 64);
  
  // impostiamo la palette ai colori 0=nero, 1=blu, 2=rosso
  BG_PALETTE_SUB [0] = RGB5(0,0,0);
  BG_PALETTE_SUB [1] = RGB5(0,0,31);
  BG_PALETTE_SUB [2] = RGB5(31,0,0);
  
  // riempiamo la mappa usando la tessera quasi vuota (è la tessera #0)
  // attenzione, ogni halfword definisce DUE tessere
  int i;
  for (i=0;i<8*16;i++)
    ((u16*)BG_MAP_RAM_SUB(0)) [i] = 0x0000;
    
  // mettiamo una tessera con il quadratino (la #1) e una con la crocetta (#2)
  // circa in centro allo schermo (6,7) e (7,7)
  ((u16*)BG_MAP_RAM_SUB(0)) [(7*16+6)/2] = 0x0102;
  
  // mettiamo altre DUE tessere, una con la crocetta (la #2) e una con il
  // quadratino alla riga sotto le precedenti
  ((u16*)BG_MAP_RAM_SUB(0)) [(8*16+6)/2] = 0x0201;
  
  // impostiamo il BG 1 al modo rotazionale a 16x16 tessere
  // e indichiamo dove iniziano nella memoria video la mappa e le tessere
  REG_BG3CNT_SUB = BG_RS_16x16 | BG_MAP_BASE(0) | BG_TILE_BASE(1);
  
  // associamo il MAIN engine allo schermo inferiore
  // il SUB engine quindi sarà associato allo schermo superiore
  lcdMainOnBottom ();
  
  // posiziono il background in centro allo schermo
  REG_BG3X_SUB = - (256-128)/2 << 8;
  REG_BG3Y_SUB = - (192-128)/2 << 8;
  
  int down_X=0, down_Y=0;
  while(1) {
  
    // leggiamo lo stato dei tasti e del touch screen
    scanKeys();
    
    // stiamo premendo uno dei tasti sulla spalla del DS?
    if (keysHeld() & (KEY_L|KEY_R))
      
      // attiva il wrap del background
      REG_BG3CNT_SUB |= BG_WRAP_ON;
    else
     
      // DISattiva il wrap del background
      REG_BG3CNT_SUB &= ~BG_WRAP_ON;

    // abbiamo appena toccato il touch screen?
    if (keysDown() & KEY_TOUCH) {
     
      // leggi la posizione del pennino
      touchPosition touch; touchRead(&touch);
      
      // memorizziamola per vedere dove va a finire
      down_X=touch.px;
      down_Y=touch.py;
    }
  
    // oppure siamo ancora appoggiati sul touch screen?
    else if (keysHeld() & KEY_TOUCH) {
     
      // leggi la posizione del pennino
      touchPosition touch; touchRead(&touch);
      
      int angle = (touch.px - down_X) * 5;
      int scale = (down_Y - touch.py) * 5 + (1 << 8);
      
      REG_BG3PA_SUB = (fake_cos (angle) * scale) >> 8;
      REG_BG3PB_SUB = (-fake_sin (angle) * scale) >> 8;
      REG_BG3PC_SUB = (fake_sin (angle) * scale) >> 8;
      REG_BG3PD_SUB = (fake_cos (angle) * scale) >> 8;
    }
    
    // altrimenti riporta tutto alla situazione iniziale
    else {
      
      // 'azzera' la matrice di rotazione/dimensionamento per il background 3
      REG_BG3PA_SUB = 1 << 8;
      REG_BG3PB_SUB = 0;
      REG_BG3PC_SUB = 0;
      REG_BG3PD_SUB = 1 << 8;
    }
    
    // aspettiamo il prossimo refresh
    swiWaitForVBlank();
  }
}

E' necessario infine ancora chiarire come si controlla la rotazione e il ridimensionamento dei background rotazionali. I background rotazionali, come gli estesi, hanno ognuno 4 registri che sono demandati a matrice di rotazione/dimensionamento. Ognuno di questi valori è un numero dotato di segno ed espresso in virgola fissa con parte frazionaria su 8 bit. Se non avete confidenza con i numeri in virgola fissa allora potete provare ad immaginare che il numero sia un intero dotato di segno che esprime quante volte si deve accumulare una fissata frazione per trovare il valore risultante. La frazione è 1/2(bit della parte frazionaria), quindi nel nostro caso avendo 8 bit nella parte frazionaria risulterà 1/28 = 1/256. Allora scrivendo dentro ogni registro dovremo esprimere i valori in 256esimi di intero, ed è per questo che per impostare il valore 1 dovremo scrivere proprio 256 (esattamente quel 1 << 8 che si vede nel codice).

Secondo quanto riportato nelle specifiche tecniche, per ottenere i valori da inserire nei quattro registri per ottenere un background ruotato di x gradi e ridimensionato in scala m occorre impostare i valori:
PA = m cos (x) ; PB = - m sen (x) ; PC = m sen (x) ; PD = m cos (x) . PA e PD sono impostati allo stesso valore (e PB e PC esattamente opposti) solo perché stiamo utilizzando la stessa scala sia per il ridimensionamento verticale che quello orizzontale, altrimenti se volessimo distinguere le due scale, mx e my, per esempio per avere delle distorsioni, avremo:
PA = mx cos (x) ; PB = - mx sen (x) ; PC = my sen (x) ; PD = my cos (x) .

Se invece preferite evitare la trigonometria, potete vedere la matrice nello stesso modo in cui la vede il motore grafico: partendo da ogni pixel dello schermo (e non dal background) dovremo calcolare quale pixel del background va disegnato. Per fare questo calcoleremo il prossimo pixel da disegnare attraverso la suddetta matrice interpretando i 4 registri come segue:
PA = incremento orizzontale (x) sul background per ogni pixel in orizzontale (x) sullo schermo
PB = incremento orizzontale (x) sul background per ogni pixel in verticale (y) sullo schermo
PC = incremento verticale (y) sul background per ogni pixel in orizzontale (x) sullo schermo
PD = incremento verticale (y) sul background per ogni pixel in verticale (y) sullo schermo
e quindi a questo punto se avete capito tutto vi dovrebbe anche risultare chiaro perché la matrice di identità (cioè quella che visualizzerà il background sullo schermo senza né rotazione né ridimensionamento) sia PA=1; PB=0; PC=0; PD=1.

Oltre a questi 4 registri per ogni background rotazionale (o esteso) vi sono ancora i 2 registri di scostamento (offset, o volendo scroll) che permettono di spostare l'origine del background dove desiderato, e hanno dei nomi definiti in libnds. Nel nostro esempio, dove usiamo il background 3 del SUB engine, i registri sono REG_BG3X_SUB per lo scostamento orizzontale e REG_BG3Y_SUB per lo scostamento verticale. Per gli altri background avremo nomi simili (cambia la cifra dentro il nome) mentre dovremo usare quelli senza la desinenza _SUB quando vogliamo riferirci ai background del MAIN engine.

Priorità dei background

Si è detto che nelle modalità tiled e bitmap è possibile attivare fino a tutti e 4 i background contemporaneamente, e che attraverso l'uso del colore 0 per i modi tiled o attraverso l'uso dell'alfa bit per i modi bitmap, è possibile definire quali pixel dovranno essere davvero disegnati, e quindi andranno a coprire ciò che è disegnato dietro, e quali pixel invece non saranno invece mostrati sul background, permettendo quindi di vedere ciò che è visualizzato su un background inferiore. Di norma i background si presentano sovrapposti nell'ordine standard, ovvero il background 0 sovrasta il background 1 che sta davanti al 2, e il background 3 è quindi il fondale più distante possibile. Questa impostazione, fortunatamente, non è fissa, altrimenti non ci sarebbe modo di avere un background di tipo bitmap (ad esempio il background 2 nel modo 5) davanti a tutti gli altri, a meno di non tenere spenti i due background superiori. Invece, attraverso l'uso della priorità, si può ridefinire (anche dinamicamente, volendo) l'ordine dei background assegnando ad ognuno un valore tra 0 e 3: più è piccolo questo valore più il background acquista priorità e quindi viene mostrato avanti. A parità di priorità ovviamente rimane valido il principio precedente, ovvero il background con numero minore verrà disegnato davanti mentre tutti gli altri dietro, in ordine. E' definita quindi in libnds (in background.h) la seguente macro:

BG_PRIORITY(n)

che può essere usata appunto a questo scopo. Ad esempio per dare al background 2 la massima priorità ed essere certi che i background 0 e 1 vengano disegnati sicuramente dietro al 2, si potrebbe usare il seguente codice:

REG_BG2CNT |= BG_PRIORITY(0);
REG_BG0CNT |= BG_PRIORITY(1);
REG_BG1CNT |= BG_PRIORITY(1);


Sverx, 22 Maggio 2009. Ultima modifica 8 Marzo 2013.


Domande? Dubbi? Suggerimenti? Vuoi scambiare due parole con me? Ho preparato un forum apposta!

Torna all'indice per accedere alle altre sezioni.

riferimenti:

http://nocash.emubase.de/gbatek.htm#dsvideo

http://dev-scene.com/NDS/Tutorials_Day_2#2D

http://dev-scene.com/NDS/NDS_Tutorials_VramTable

http://nocash.emubase.de/gbatek.htm#dsmemorycontrolvram

http://dev-scene.com/NDS/Tutorials_Day_3#Frame_buffer...finally

http://dev-scene.com/NDS/Tutorials_Day_3#Bitmap_Graphics_Modes

http://nocash.emubase.de/gbatek.htm#dsvideobgmodescontrol

http://www.dev-scene.com/NDS/Tutorials_Day_4#Map_Entries

http://www.dev-scene.com/NDS/Tutorials_Day_4#Background_Memory_Layout_and_VRAM_Management

http://nocash.emubase.de/gbatek.htm#lcdvrambgscreendataformatbgmap

http://nocash.emubase.de/gbatek.htm#lcdiobgcontrol

http://www.coranac.com/tonc/text/affine.htm