Assembly Avanzato con MASM
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)