Assembly Avanzato con NASM

Capitolo 10: La memoria video in modalità grafica standard


Nel precedente capitolo abbiamo analizzato le caratteristiche generali delle modalità video testo standard; in questo capitolo, invece, parleremo dell'evoluzione del supporto grafico attraverso i vari adattatori video standard imposti nel corso degli anni dalla IBM.

10.1 Principio di funzionamento di un monitor per PC

Per comprendere meglio alcuni concetti esposti in questo capitolo, analizziamo sommariamente il principio di funzionamento delle due più diffuse famiglie di monitor per PC.

10.1.1 Monitor CRT

Il monitor CRT o Cathode Ray Tube (tubo a raggi catodici) è così chiamato in quanto al suo interno è presente un dispositivo denominato cannone elettronico il cui scopo è quello di "sparare" un raggio di elettroni (raggio catodico) verso la parte interna dello schermo che è interamente ricoperta da piccolissime particelle di fosforo; una importante caratteristica del fosforo è quella di emettere luce quando viene colpito da una qualche fonte di energia (come quella legata allo stesso raggio catodico).
Nel caso più semplice, rappresentato dai monitor monocromatici, vengono usate particelle di fosforo capaci di emettere luce di un unico colore (in genere, bianco, verde o ambra); in un caso del genere, per colpire i fosfori viene usato un singolo raggio catodico.
L'hardware della scheda video legge il contenuto "visibile" della VRAM e lo trasferisce sullo schermo secondo lo schema mostrato in Figura 10.1; in tale figura, l'andamento del raggio catodico è stato volutamente esagerato per motivi di chiarezza. Attraverso apposite placche di deflessione, il raggio elettronico può essere deviato, sia verticalmente, sia orizzontalmente; il riempimento (o ritraccia) dello schermo viene effettuato posizionando inizialmente il raggio in alto a destra.
Il raggio viene spostato in orizzontale sino all'estremità sinistra dello schermo in modo da tracciare la prima linea di pixel (linea di scansione); i pixel che vengono colpiti emettono luce, mentre gli altri restano spenti (colore nero). Lo stesso raggio viene poi riportato a destra in modo da procedere con la ritraccia della successiva linea di scansione.
Naturalmente, questo movimento a zig-zag del raggio catodico si ripete finché non viene tracciato l'intero schermo; a questo punto, il raggio viene riportato in alto a destra in modo da dare inizio alla ritraccia di una nuova schermata.

I fosfori colpiti dal raggio catodico emettono luce per un intervallo di tempo estremamente breve (una frazione di secondo); ciò rende necessaria la ritraccia dello schermo per numerose volte al secondo.
Il numero di ritracce al secondo dell'intero schermo prende il nome di vertical refresh (rinfresco verticale); per evitare fastidiosi fenomeni di sfarfallio, dovuti alla persistenza delle immagini nella retina dell'occhio umano, il vertical refresh deve avvenire diverse decine di volte al secondo (cioè, la frequenza del vertical refresh deve essere di diverse decine di Hz).
I monitor di qualità sono capaci di supportare vertical refresh abbondantemente superiori a 60 Hz anche con risoluzioni grafiche molto alte; maggiore è la frequenza del vertical refresh, minore sarà l'affaticamento degli occhi (in quanto l'immagine sullo schermo risulterà più stabile).
Ovviamente, il vertical refresh è necessario, non solo per sopperire alla "decadenza luminosa" dei fosfori, ma anche per il fatto che il contenuto dello schermo può variare molto rapidamente necessitando quindi di un continuo aggiornamento; questo aspetto diventa particolarmente importante, ad esempio, nel caso dei videogiochi.

Nei monitor a colori, ogni particella di fosforo monocromatico viene sostituita con una terna di particelle che comprende: un fosforo a luce rossa, un fosforo a luce verde e un fosforo a luce blu; il singolo raggio catodico viene sostituito con tre raggi, ciascuno dei quali colpisce una delle particelle della terna producendo così un classico colore RGB.
Al variare dell'intensità di ogni raggio varia anche l'intensità della luce emessa dal fosforo colpito; in questo modo, si possono ottenere teoricamente infiniti colori!

10.1.2 Monitor LCD

Il monitor LCD o Liquid Crystall Display (visore a cristalli liquidi) basa il suo funzionamento sulle proprietà chimico-fisiche di particolari composti; tali proprietà furono scoperte nel 1888 dal botanico austriaco Friedrich Reinitzer relativamente al benzoato di colesterile.
In particolare, Reinitzer si accorse che questa sostanza sembrava possedere due specifiche temperature di fusione; a 145 °C diventava un liquido opaco, per poi tornare trasparente a 179 °C.
In seguito, il professore di fisica tedesco Otto Lehmann studiò il fenomeno in modo più approfondito scoprendo che, effettivamente, tra le due temperature di fusione il benzoato di colesterile si comporta come un liquido che però mantiene la struttura cristallina tipica dei solidi; per evidenziare tale aspetto, nel 1889 lo stesso Lehmann coniò il termine cristallo liquido (CL).

La caratteristica fondamentale dei CL è che le loro molecole hanno una forma piuttosto allungata e, allo stato liquido, risultano molto vicine le une alle altre; tali molecole sono costrette quindi a disporsi (allinearsi) secondo una direzione preferenziale lungo un asse detto direttore.
Un'altra caratteristica importantissima dei CL è che le loro molecole sono soggette ai campi elettrici; infatti, applicando un campo elettrico ad un CL, le sue molecole si orientano tutte parallelamente al campo stesso.

Tutte queste caratteristiche dei CL sono state sfruttate, a partire dal 1968, per la realizzazione dei visori a cristalli liquidi (LCD); la Figura 10.2 illustra il principio di funzionamento di una cella LCD. In Figura 10.2a vediamo quello che succede nella cella in assenza di campo elettrico; in tal caso le molecole di CL tendono ad orientarsi secondo una direzione che possiamo supporre casuale. Per alterare questa situazione vengono utilizzati due strati di allineamento che, oltre a permettere il passaggio della luce, presentano pure una superficie interna aderente per le stesse molecole di CL.
La superficie interna dello strato di allineamento superiore è microscopicamente corrugata in direzione nord sud, mentre la superficie interna dello strato di allineamento inferiore è microscopicamente corrugata in direzione est ovest; di conseguenza, le molecole che stanno in alto si dispongono in direzione nord sud, quelle che stanno in basso si dispongono in direzione est ovest, mentre quelle che stanno in mezzo si dispongono secondo direzioni intermedie tra nord sud e est ovest.
Una sorgente di luce che arriva dall'alto (dall'interno dello schermo) incontra un filtro polarizzatore progettato per polarizzare la luce stessa secondo la direzione nord sud; la luce così polarizzata penetra nella cella e, a causa della disposizione elicoidale delle molecole, subisce una rotazione di 90° (polarizzazione in direzione est ovest).
Il secondo filtro polarizzatore in basso, può essere attraversato solo dalla luce polarizzata in direzione est ovest; questo è proprio il caso di Figura 10.2a, per cui l'utente che osserva davanti al monitor vedrà un puntino illuminato!

In Figura 10.2b vediamo quello che succede in presenza di un campo elettrico; in tal caso, come è stato già anticipato, le molecole di CL si dispongono parallelamente alle linee di forza del campo stesso.
La sorgente di luce che arriva dall'alto, polarizzata in direzione nord sud, questa volta non subisce alcuna rotazione e non può quindi attraversare il filtro polarizzatore inferiore; l'utente che osserva davanti al monitor vedrà un puntino nero!

Nei monitor LCD monocromatici si varia l'intensità della sorgente di luce bianca in modo da ottenere le varie tonalità di grigio; nei monitor LCD a colori si utilizzano dei filtri che scompongono la luce bianca nelle tre componenti primarie RGB.

Considerando il fatto che durante il normale funzionamento i pixel dello schermo sono quasi tutti accesi, si può facilmente intuire per quale motivo sia stato associato il caso di Figura 10.2a all'assenza di campo elettrico; infatti, in questo modo si ottiene un enorme risparmio di energia elettrica (e ciò spiega anche il bassissimo consumo di energia elettrica dei monitor LCD rispetto ai monitor CRT)!

Il concetto di vertical refresh rimane valido anche per i monitor LCD; infatti, anche se in questo caso non esiste il problema della "decadenza luminosa" dei fosfori, bisogna ricordare che il contenuto dello schermo può variare molto rapidamente (ad esempio, nei videogiochi) per cui è necessario un continuo aggiornamento.

10.2 Supporto grafico fornito dagli adattatori CGA

Il CGA è stato il primo adattatore video standard della IBM capace di fornire il supporto per tre modalità grafiche differenti; la Figura 10. 3 mostra tutti i dettagli. Queste tre modalità grafiche vengono definite interlacciate; tale nome è dovuto al fatto che l'hardware della scheda video, nel disegnare una schermata, traccia per prime tutte le scan line di indice pari e subito dopo quelle di indice dispari.
Lo scopo di questa tecnica è quello di ridurre il fenomeno dello sfarfallio causato dalla lentezza, da parte dell'hardware, nel ridisegnare lo schermo; in effetti, la modalità grafica CGA viene ricordata principalmente per tale serio problema.
Tutte le scan line di indice pari sono accessibili a partire dall'indirizzo logico B800h:0000h, mentre quelle di indice dispari sono accessibili a partire dall'indirizzo logico BA00h:0000h; come si può notare, ai tempi del CGA non si utilizzava ancora il frame buffer all'indirizzo A000h:0000h dedicato alle modalità grafiche.

Nelle modalità 04h e 05h a 4 colori, vengono utilizzati 2 bit per ogni pixel (con 2 bit possiamo rappresentare 22=4 colori); ogni byte del vettore della VRAM memorizza quindi le informazioni relative a 8/2=4 pixel.
Nella modalità 06h a 2 colori, viene utilizzato 1 bit per ogni pixel (con 1 bit possiamo rappresentare 21=2 colori); ogni byte del vettore della VRAM memorizza quindi le informazioni relative a 8/1=8 pixel.

La Figura 10.4 mostra un esempio relativo ai primi due byte della VRAM in modalità 320x200 a 4 colori; in questo caso supponiamo che i colori siano rappresentati come: 00b nero, 01b rosso, 10b verde, 11b blu. Come si può notare, la figura mostra l'angolo in alto a sinistra dello schermo e cioè, la parte iniziale della scan line di indice 0 accessibile a partire dall'indirizzo logico B800h:0000h; in base a quanto è stato spiegato in precedenza, le informazioni relative ai primi 8 pixel si trovano memorizzate nei primi 8/4=2 byte della VRAM.
Trattandosi di una risoluzione grafica 320x200, ogni scan line è formata da 320 pixel ed occupa quindi nella VRAM un blocco da 320/4=80 byte (0050h byte); le varie scan line di indice pari (0, 2, 4, 6, 8, ...) partiranno quindi dagli indirizzi logici B800h:0000h, B800h:0050h, B800h:00A0h, B800h:00F0h, ...
Per le scan line di indice dispari (1, 3, 5, 7, ...) il discorso è del tutto analogo; tali scan line partiranno quindi dagli indirizzi logici BA00h:0000h, BA00h:0050h, BA00h:00A0h, BA00h:00F0h, ...

In base a quanto è stato appena esposto, se volessimo riempire tutto lo schermo con il colore 10b per le 100 scan line di indice pari e con il colore 01b per le 100 scan line di indice dispari, il procedimento da seguire sarebbe molto semplice; osservando che un blocco da 100 scan line è formato da 320x100=32000 pixel, pari a 32000/4=8000 byte, possiamo scrivere: Ovviamente, per velocizzare le operazioni possiamo osservare che 8000 byte equivalgono a 2000 doubleword per cui, dopo aver caricato i colori dei pixel in EAX, possiamo porre CX=2000 e sostituire STOSB con STOSD.

Ben diverso è il discorso nel momento in cui vogliamo accedere in I/O ad un determinato pixel dello schermo; come risulta evidente dalla Figura 10.4, il procedimento da seguire è piuttosto contorto e ciò porta ad inevitabili ripercussioni sulle prestazioni dei programmi.

Prima di tutto dobbiamo individuare il BYTE della VRAM contenente le informazioni relative al pixel che ci interessa; a tale proposito, osserviamo subito che, se ogni pixel occupasse 1 byte nella VRAM, potremmo calcolare facilmente la posizione (byte_index) del BYTE in cui si trova il pixel di coordinate x, y (colonna, riga) con la formula:
byte_index = (y * 320) + x
In realtà sappiamo che ogni BYTE della VRAM contiene le informazioni relative a 4 pixel per cui, sia il valore 320, sia la coordinata x, devono essere divisi per 4; possiamo porre allora 320/4=80 e xb=x/4 (divisioni intere).
Analogamente, le 200 scan line sono divise in due gruppi, per cui la coordinata y deve essere divisa per 2; possiamo porre allora yb=y/2 (divisione intera).
A questo punto possiamo scrivere:
byte_index = (yb * 80) + xb = ((y / 2) * 80) + (x / 4)
La divisione intera x/4 è molto importante in quanto il suo resto è sempre un valore intero compreso tra 0 e 3 (ricordiamo che il resto è sempre inferiore al divisore); tale valore rappresenta la posizione, all'interno del BYTE di indice byte_index, della coppia di bit relativi al pixel a cui vogliamo accedere.
Nel caso, ad esempio, del pixel di coordinate x=6, y=2, si ottiene 6/4=1 con resto 2; quindi, le informazioni relative al nostro pixel si trovano nella terza coppia (indice 2) di bit del BYTE di indice byte_index della VRAM.
Una volta individuate queste informazioni, dobbiamo isolare la coppia di bit a cui vogliamo accedere in I/O; questa operazione, ovviamente, deve essere tale da non influenzare i pixel adiacenti.
Supponendo di voler accedere in scrittura ad un determinato pixel dello schermo, possiamo scrivere allora la seguente procedura in stile C: Dopo aver calcolato il valore ((y/2)*80)+(x/4), utilizziamo il resto di x/4 per determinare la posizione, all'interno del BYTE di indice byte_index, della coppia di bit che rappresenta il pixel che ci interessa; tale resto deve essere quindi moltiplicato per 2 in modo da ottenere la posizione effettiva dei due bit da modificare.
Osservando la Figura 10.4 possiamo notare che nei byte della VRAM le varie coppie di bit sono ordinate da destra verso sinistra, mentre sullo schermo i pixel corrispondenti risultano disposti da sinistra verso destra; per tenere conto di tale aspetto basta sottrarre il resto di x/4 da 3 (massimo valore possibile per il resto). Il risultato così ottenuto viene memorizzato in CL; lo stesso CL viene usato in combinazione con SHL per posizionare correttamente la coppia di bit del parametro colore memorizzato in AL.
Prima di procedere con la modifica del pixel dobbiamo stabilire se ci troviamo su una scan line pari o dispari; a tale proposito, basta controllare se il bit meno significativo della coordinata y vale 0 (scan line pari) o 1 (scan line dispari).
La modifica del pixel è preceduta dall'azzeramento dei due suoi bit; a tale proposito, utilizziamo l'istruzione ROL in combinazione con CL sul valore DL=11111100b. Lo stesso DL può essere così usato con l'istruzione AND per azzerare i soli due bit che ci interessano; a questo punto, l'istruzione OR scrive il colore (AL) del pixel da modificare.

Per velocizzare le operazioni possiamo subito osservare che la divisione x/4 consiste nel far scorrere di due posti verso destra i bit di x; come sappiamo, i due bit che traboccano da destra rappresentano il resto della stessa divisione x/4. Prima di applicare l'istruzione SHR a x dobbiamo quindi memorizzare il relativo resto; a tale proposito, basta copiare in una apposita locazione i due bit meno significativi di x.
Per quanto riguarda il prodotto yb*80 possiamo notare che 80=24+26 per cui possiamo scrivere simbolicamente:
yb * 80 = yb * (24 + 26) =  (yb * 24) + (yb * 26) = (SHL yb, 4) + (SHL yb, 6)
Tutte le considerazioni appena esposte si applicano in modo molto simile per la modalità video CGA 06h; a tale proposito, bisogna ricordare che questa volta viene utilizzato 1 solo bit per ogni pixel per cui ciascun byte della VRAM memorizza le informazioni relative a 8/1=8 pixel.

10.3 Supporto grafico fornito dagli adattatori EGA e VGA

Successivamente al CGA, la IBM ha introdotto gli adattatori EGA e VGA; la Figura 10.5 mostra tutti i dettagli. La modalità video principale introdotta dall'adattatore EGA è la 10h caratterizzata da una risoluzione di 640x350 pixel a 16 colori; analogamente, la modalità video principale introdotta dall'adattatore VGA è la 12h caratterizzata da una risoluzione di 640x480 pixel a 16 colori. Nel seguito ci occuperemo quindi di queste due modalità.

Gli adattatori EGA e VGA presentano, da un punto di vista tecnico, caratteristiche molto simili; in particolare, la principale analogia riguarda l'utilizzo dei cosiddetti bit planes (piani di bit) per la memorizzazione dei colori dei pixel.
Per capire il perché dell'utilizzo di tale tecnica osserviamo che, ad esempio, nel caso della modalità 10h abbiamo una risoluzione di 640x350=224000 pixel, ciascuno dei quali può assumere uno tra 16 possibili colori; per rappresentare ogni pixel sono quindi necessari 4 bit (24=16) per cui ogni byte della VRAM contiene le informazioni relative a 8/4=2 pixel.
Se venisse utilizzata allora la tecnica di Figura 10.4, ogni schermata 640x350 richiederebbe 224000/2=112000 byte di memoria; questo valore è nettamente maggiore della dimensione di un segmento di memoria e ciò costringerebbe l'utente a destreggiarsi tra diversi blocchi da 64 KiB per gestire una singola schermata.
Il discorso appena svolto vale, a maggior ragione, per la modalità 12h con risoluzione di 640x480=307200 pixel; per risolvere il problema è stata allora adottata la tecnica illustrata in Figura 10.6. In sostanza, una schermata risulta distribuita su 4 piani sovrapposti, indicizzati con i valori 0, 1, 2, 3; il colore del pixel di coordinate x, y sullo schermo viene ottenuto combinando i 4 bit che si trovano alle coordinate x, y di ciascun piano.
Supponendo di assegnare al colore verde il valore 0010b, possiamo osservare in Figura 10.6 che al pixel di coordinate 3, 0 sullo schermo corrisponde il bit (0) di coordinate 3, 0 sul Piano 0, il bit (1) di coordinate 3, 0 sul Piano 1, il bit (0) di coordinate 3, 0 sul Piano 2 e il bit (0) di coordinate 3, 0 sul Piano 3; unendo questi 4 bit si ottiene, appunto, 0010b.
Nel caso del modo VGA, ciascuno dei piani sovrapposti è costituito da 640x480=307200 bit ed occupa quindi 307200/8=38400 byte di memoria; in questo modo si evita che una singola schermata si trovi distribuita su due o più blocchi da 64 KiB, cosa che costringerebbe l'utente a dover saltare da un blocco all'altro!

I 4 piani di bit, essendo sovrapposti, risultano tutti accessibili a partire dallo stesso indirizzo logico; per gli adattatori EGA e VGA tale indirizzo è A0000h:0000h.
In Figura 10.6 vediamo (in alto a sinistra) la parte iniziale della VRAM con il primo elemento del vettore che è costituito da 4 byte sovrapposti per la memorizzazione di 8 pixel; si tratta chiaramente dei primi 8 pixel visualizzati nell'angolo in alto a sinistra dello schermo.
Il programmatore può selezionare il bit plane desiderato attraverso apposite porte hardware dell'adattatore; per illustrare il procedimento generale da seguire, analizziamo un esempio pratico riferito alla modalità 12h da 640x480 pixel.
Osserviamo subito che, in questo caso, ognuna delle 480 scan line è formata da 640 pixel e quindi, ogni bit plane risulterà a sua volta composto da 480 righe da 640 bit ciascuna, pari a 640/8=80 byte; per ottenere l'indice (byte_index) del byte della VRAM contenente il pixel di coordinate x, y dobbiamo allora calcolare:
byte_index = (y * 80) + (x / 8)
In analogia al caso dell'adattatore CGA, il resto della divisione intera x/8 (che è sempre un numero intero compreso tra 0 e 7) ci fornisce la posizione, all'interno del byte di indice byte_index, del pixel a cui vogliamo accedere; come al solito, tale posizione è intesa a partire dal bit più significativo a causa del fatto che, come si vede in Figura 10.6, i bit nella VRAM sono ordinati da destra verso sinistra, mentre i corrispondenti pixel sullo schermo sono ordinati da sinistra verso destra.
Una volta ottenute queste informazioni, possiamo procedere con l'accesso alla VRAM; a tale proposito, come è stato già anticipato, dobbiamo programmare apposite porte hardware dell'adattatore.
Prima di tutto dobbiamo accedere alla porta 03CEh denominata Graphics Address Register (o GAR); in tale porta dobbiamo scrivere l'indice 8 che ci permette di selezionare il Bit Mask Register (o BMR).
Dopo accediamo alla porta 03CFh denominata BMR Data Area per specificare in quale posizione (nel byte di indice byte_index) si trova il pixel a cui vogliamo accedere; ad esempio, se vogliamo accedere al bit in posizione 3, dobbiamo scrivere in tale porta il valore 00001000b.
In seguito accediamo alla porta 03C4h denominata Sequencer Address Register (o SAR); in tale porta dobbiamo scrivere l'indice 2 per selezionare il Map Mask Register (o MMR).
A questo punto, attraverso il MMR, la cui porta è 03C5h, selezioniamo i bit plane da abilitare; ad esempio, per abilitare tutti i 4 bit plane dobbiamo scrivere in tale porta il valore 00001111b (ciascuno dei primi 4 bit rappresenta uno dei bit plane a partire da quello di indice 0).
Terminata questa fase dobbiamo obbligatoriamente leggere il byte di indice byte_index della VRAM; questa lettura è molto importante in quanto obbliga l'adattatore a bloccare (to latch) il byte contenente il pixel a cui vogliamo accedere.
Il byte appena bloccato deve essere "pulito" scrivendo in esso il valore 00000000b; subito dopo possiamo finalmente scrivere, nella porta 03C5h, il nuovo colore a 4 bit.
L'ultimo passaggio consiste nello scrivere il valore 11111111b nel byte di indice byte_index; tale operazione prende il nome di write back e permette di sbloccare il byte che abbiamo appena modificato.

Supponendo di voler accedere in scrittura ad un determinato pixel dello schermo, possiamo scrivere allora la seguente procedura in stile C: In questo esempio utilizziamo vari accorgimenti per velocizzare i calcoli del tipo y*80, x/8 e così via; si può anche notare che, per calcolare il resto di x/8, carichiamo lo stesso x in CX e isoliamo i 3 bit meno significativi.

10.4 Supporto grafico fornito dagli adattatori MCGA

Dopo l'uscita dell'EGA e prima dell'avvento del VGA, la IBM ha introdotto un particolare modello di adattatore denominato MCGA; la Figura 10.7 mostra tutti i dettagli relativi alle modalità grafiche supportate da tale adattatore. La modalità video principale introdotta dall'adattatore MCGA è la 13h, caratterizzata da una risoluzione di 320x200 pixel a 256 colori; nel seguito ci occuperemo quindi di tale modalità.

La caratteristica fondamentale dell'adattatore MCGA è l'estrema semplicità del metodo adottato per la memorizzazione dei pixel nella VRAM; tale semplicità è legata al fatto che ciascun pixel può assumere uno tra 256 differenti colori e richiede quindi per la sua rappresentazione 8 bit (28=256), pari a 1 byte.
La conseguenza è che ogni byte della VRAM rappresenta 1 pixel sullo schermo; i 320x200=64000 pixel di una schermata MCGA vengono gestiti allora nella VRAM sotto forma di vettore lineare da 64000 byte.
Tale dimensione è inferiore ai 64 KiB di un segmento di memoria e comporta quindi una gestione estremamente semplice ed efficiente; proprio questo aspetto ha dato alla modalità 13h una enorme popolarità ai tempi del DOS tra i programmatori di videogiochi.

La Figura 10.8 illustra i concetti appena esposti; in tale figura vediamo i primi 3 byte della VRAM associati ai primi 3 pixel visualizzati nell'angolo in alto a sinistra dello schermo. Come possiamo notare, il primo pixel si trova all'indirizzo A000h:0000h della VRAM, il secondo pixel si trova all'indirizzo A000h:0001h della VRAM e così via; tenuto conto allora della risoluzione 320x200, per accedere al byte della VRAM contenente il pixel di coordinate x, y basta calcolare semplicemente:
byte_index = (y * 320) + x
Considerando poi il fatto che 320=26+28, possiamo anche scrivere:
byte_index = (y * (26 + 28)) + x = ((y * 26) + (y * 28)) + x = ((SHL y, 6) + (SHL y, 8)) + x
La procedura per la scrittura di un pixel si semplifica quindi in questo modo: La situazione è altrettanto semplice nel momento in cui vogliamo riempire una intera schermata con un determinato colore (ad esempio, 10001111b); in tal caso, dopo aver caricato 4 byte di colore in EAX e dopo aver posto CX=64000/4 (doubleword) e ES:DI=A000h:0000h, possiamo eseguire l'istruzione:
rep stosd

10.5 La tavolozza dei colori

In base a quanto è stato esposto in questo capitolo, si potrebbe supporre che i gruppi di bit della VRAM assegnati a ciascun pixel rappresentino in modo diretto il colore del pixel stesso; in molti casi (come vedremo nel capitolo successivo) ciò può essere vero ma, per certi adattatori video, tali bit codificano in realtà l'indice relativo all'elemento di un vettore che contiene l'effettivo colore del pixel.
Un caso emblematico è quello dell'adattatore VGA dove ogni gruppo di bit della VRAM contiene un indice relativo ad uno dei 256 elementi di un vettore di colori; ciascun elemento assume la struttura mostrata in Figura 10.9. I 3 byte sono ordinati da sinistra verso destra; di conseguenza, RED è il primo byte, GREEN è il secondo, BLUE è il terzo.
Come si può notare, solo i primi 6 bit di ogni byte vengono usati per codificare l'intensità delle componenti primarie Red, Green, Blue; con questo sistema è possibile rappresentare un totale di:
26 * 26 * 26 = 26 + 6 + 6 = 218 = 262144 colori
Questi 262144 colori rappresentano la cosiddetta palette (tavolozza dei colori); come abbiamo visto in precedenza, a seconda del modo video che si sta utilizzando, risultano disponibili tutti o parte dei 256 elementi del vettore dei colori.
Nel modo 12h sono disponibili solo i primi 16 elementi del vettore dei colori; nel modo 13h sono disponibili tutti i 256 elementi del vettore dei colori.

Tale vettore risulta accessibile attraverso le porte hardware 03C7h, 03C8h e 03C9h; la porta 03C7h (PEL Read Register) permette di specificare l'indice dell'elemento da leggere, la porta 03C8h (PEL Write Register) permette di specificare l'indice dell'elemento da scrivere, la porta 03C9h (PEL Data Register) permette di effettuare materialmente la lettura o la scrittura dei dati (la sigla PEL sta per Picture ELement).
Ogni volta che vengono letti o scritti i 3 byte di un PEL attraverso la porta 03C9h, si verifica automaticamente l'incremento di 3 (byte) del puntatore al vettore dei colori; ciò ci permette di leggere o scrivere l'intero vettore con un'unica istruzione Assembly.

10.6 Esempi pratici

Analizziamo ora alcuni esempi pratici riferiti al modo 13h (risoluzione 320x200 a 256 colori); come è stato spiegato in precedenza, questo modo video è estremamente semplice da programmare e si presta quindi in modo particolare per esperimenti di tipo grafico.

10.6.1 Rotazione dei colori nel modo 13h

Il primo esempio mostra come visualizzare tutti i 256 colori della modalità 13h e come scambiarli di posto in modo da ottenere un effetto "rotazione"; la Figura 10.10 mostra il listato del programma ROTCOLOR.ASM. Questo programma visualizza 256 rettangoli distribuiti su 8 righe e 32 colonne; ciascuno dei rettangoli viene poi riempito con uno dei 256 colori disponibili.
Successivamente, viene attivato un loop all'interno del quale i 256 colori dell'adattatore vengono ruotati di una posizione verso destra ad ogni iterazione; a tale proposito, si utilizza un vettore di 256 terne RGB che viene continuamente sottoposto ad una sorta di ROR.
In particolare, ad ogni iterazione il primo colore RGB della palette viene copiato in tmp_rgb e i restanti 255 in tmp_vrgb; dopo, tmp_rgb viene copiato nell'ultimo elemento di tmp_vrgb e così abbiamo ruotato di un posto verso destra tutti i 256 colori della palette.
Scrivendo ora tmp_vrgb nella porta 03C9h, i colori sullo schermo cambiano automaticamente, senza la necessità di ridisegnare i rettangoli; infatti, tutto il lavoro viene svolto dall'hardware video al successivo refresh dell'immagine sul monitor.

Nei veri ambienti DOS (ma anche nella DOS Box di Windows) il programma di Figura 10.10 può produrre qualche effetto indesiderato; in particolare, si può notare un effetto di alterazione dei colori.
Come è stato spiegato nella sezione Assembly Base, ciò è dovuto all'uso massiccio di istruzioni INS e OUTS in combinazione con il prefisso REP; questa situazione è problematica in quanto molte porte hardware richiedono che, tra una operazione di I/O e quella successiva, debba trascorrere un opportuno intervallo di tempo.
Per risolvere questo problema conviene posizionare le istruzioni INS e OUTS all'interno di un normale loop, evitando così l'uso di REP; eventualmente, nello stesso loop si possono inserire anche istruzioni destinate a creare dei ritardi di tempo.

Un altro problema del programma di Figura 10.10 può essere riscontrato sui vecchi monitor CRT collegati a PC poco potenti; in tal caso si nota un evidente fenomeno di sfarfallio dello schermo.
Per risolvere tale problema si può ricorrere alla porta 03DAh denominata Input Status Register; il bit 3 del byte letto da tale porta vale 1 solo quando è in atto un vertical retrace.
Di conseguenza, possiamo sincronizzarci facilmente con lo stesso vertical retrace attraverso il seguente codice che deve essere posizionato prima dell'istruzione OUTSB che aggiorna i colori appena ruotati: In sostanza, se il bit 3 vale 1 significa che è in atto un vertical retrace; in questo caso ci conviene aspettare che il vertical retrace abbia termine.
Se il bit 3 vale 0 significa che il vertical retrace è appena terminato e il pennello elettronico si sta riposizionando nell'angolo in alto a sinistra (rispetto all'utente); in tal caso ci conviene attendere l'inizio del successivo vertical retrace in modo da poter sincronizzare il nostro output con il ridisegno dello schermo.

10.6.2 Visualizzazione di stringhe in modalità grafica

Nel precedente capitolo è stato spiegato che, in modalità testo, sullo schermo possiamo visualizzare esclusivamente simboli appartenenti al set di codici ASCII; di conseguenza, eventuali informazioni numeriche da mostrare sul monitor devono essere prima convertite in forma di stringhe.
Nel caso poi della modalità grafica, siamo anche costretti a crearci da zero i simboli (font) necessari per visualizzare le stringhe; a tale proposito, il metodo più semplice consiste nello sfruttare appositi font di tipo bitmap memorizzati nella ROM BIOS della scheda video.
Analizziamo, in particolare, il seguente servizio della INT 10h riservato agli adattatori EGA, MCGA e VGA: Selezionando, ad esempio, BH=06h, otteniamo in ES:BP l'indirizzo di un vettore di bitmap 8x16 che possiamo utilizzare per visualizzare caratteri in modo grafico; la Figura 10.11 mostra un esempio pratico che stampa sullo schermo la lettera 'R' maiuscola (o uno qualunque dei 256 simboli a scelta del programmatore). Osserviamo che il codice ASCII della lettera 'R' è 82; quindi, la bitmap relativa alla lettera 'R' sarà quella di indice 82 nel vettore puntato da ES:BP.
Ogni bitmap 8x16 occupa 16 byte (infatti, ogni byte contiene una linea da 8 pixel) per cui, l'offset all'interno del vettore sarà: 82*16=1312; la bitmap che ci interessa si viene a trovare quindi all'indirizzo logico ES:BP+1312.

La visualizzazione della bitmap avviene con il solito loop interno innestato in un loop esterno; ad ogni iterazione del loop interno viene stampata una linea della bitmap (per l'esattezza, vengono stampati solo i pixel in corrispondenza dei bit che valgono 1).

Bibliografia

Cristopher, Feigenbaum, Saliga - MS-DOS MANUALE DI PROGRAMMAZIONE - Mc Graw Hill

VGABIOS.TXT - Elenco servizi Video BIOS della INT 10h
(disponibile nella sezione Documentazione tecnica di supporto al corso assembly dell’ Area Downloads di questo sito - vgabios.zip)

VGAREGS.TXT - Programmazione dei registri VGA
(disponibile nella sezione Documentazione tecnica di supporto al corso assembly dell’ Area Downloads di questo sito - vgaregs.zip)