Con l’implementazione hardware del tracciamento delle linee è stato completato (per lo più) l’argomento Amiga “packed” dal punto vista tecnico, ma alcuni articoli erano ancora necessari, a mio avviso, per chiarire dei casi d’uso comuni per quanto riguarda l’elaborazione della grafica sia da parte del sistema operativo (e delle relative applicazioni che fanno uso dell’interfaccia grafica / GUI) sia, in particolare, nei giochi (che, comunque, condividono diversi scenari con s.o. e applicazioni con GUI), mostrando i rispettivi vantaggi e svantaggi di grafica planare e packed (ove ce ne siano. Scenari in cui siano sostanzialmente identici non hanno motivo d’esser citati).
Le operazioni più comuni (lettura/scrittura di pixel, copia/spostamento di regioni rettangolari, tracciamento linee, tracciamento di rettangoli pieni, etc.) sono mediamente più efficienti con la grafica packed (come abbiamo visto anche nell’articolo teorico) grazie al fatto che i pixel non hanno i loro dati sparsi su più bitplane e che le dimensioni orizzontali delle aree rettangolari non debbano necessariamente essere multipli di 16 (perché il bus dati è a 16 bit) nonché allineate in memoria a 16-bit. Questo ormai dovrebbe essere noto dopo tutte le analisi e discussioni in merito, ma è anche la chiave e la base per comprendere le differenze fra grafica planare e packed (lo si capirà ancora meglio quando parlerò dei giochi).
Costi di programmazione del Blitter
A ciò si aggiunge anche il fatto che, quando si usa il Blitter (sostanzialmente obbligatorio farlo, con macchine dotate di processore 68000. Tranne per primitive grafiche che operano su singoli pixel / bit), le suddette operazioni vadano ripetute per la profondità di colore della grafica (cioè il numero di bitplane da processare).
Il che ha un certo costo, in quanto si devono impostare diversi registri di questo coprocessore e, come sappiamo, il processore è abbastanza lento negli accessi in memoria (anche per sistemi con bus dati a 32 bit per la cosiddetta chip RAM, come ECS per Amiga 3000 e AGA per Amiga 1200/4000, poiché sono obbligati ad aspettare uno slot di memoria / Color Clock come il 68000).
Il costo maggiore si ha per l’elaborazione del primo bitplane, perché ci sono diversi registri da impostare. Per i bitplane successivi il prezzo da pagare si riduce, in quanto alcuni registri rimangono con gli stessi valori, per cui soltanto quelli che cambiano (sicuramente tutti i puntatori a bitplane utilizzati, ed eventualmente il registro per specificare gli “operatori logici” = minterm in gergo amighista), più ovviamente il registro per far partire l’operazione (BLTSIZE
. O i due nuovi registri che sono disponibili a partire dall’ECS, BLTSIZV
e BLTSIZH
, nel caso di operazioni di blitting che interessino aree maggiori di 1024×1024 pixel).
Il problema è risolto con l’adozione della cosiddetta grafica interleaved (di cui abbiamo già parlato nell’articolo teorico), perché i bitplane sono raggruppati una riga alla volta, per cui è sufficiente programmare il Blitter una sola volta, esattamente come si farebbe con la grafica packed (che richiede sempre e soltanto una programmazione di questo coprocessore, qualunque sia l’operazione da eseguire), ma funziona soltanto se il Blitter dovesse eseguire esattamente le stesse operazioni su tutti i bitplane (cosa che non sempre si verifica. Si capirà meglio quando parlerò dei giochi).
Questo è il motivo per cui il s.o. dell’Amiga ha adottato la grafica interleaved a partire dalla versione 2.04 del sistema operativo (se non ricordo male), che però è stata rilasciata soltanto nel 1990. Dunque per 5 anni, dal 1985 al 1990, ha sempre usato grafica planare non-interleaved, con tutti i costi annessi.
Il che significa che qualunque operazione sulla grafica, sia essa del s.o. sia delle applicazioni, è sempre stata suddivisa in n
operazioni (una per ogni bitplane da processare), con lo svantaggio che, in genere, è necessario riprogrammare il Blitter da zero per ogni singolo bitplane, poiché lo stato di questo coprocessore cambia (in quanto serve ciecamente tutte le richieste che gli arrivano: dal s.o. alle applicazioni) e non è possibile sapere a priori se i registri siano cambiati e quali siano stati cambiati, in modo da evitare di aggiornare quelli che sono rimasti uguali.
L’unico modo per non riprogrammare tutti i registri per i bitplane successivi al primo consiste nel prendere totale possesso del Blitter, programmarlo direttamente per tutte le operazioni necessarie, e infine rilasciarlo nuovamente al s.o.. Cosa decisamente sconsigliata, perché se succedesse qualche problema con l’applicazione e il Blitter non venisse rilasciato, si sarebbe costretti a riavviare il sistema. Infatti il Blitter è una risorsa condivisa da tutti e il s.o. ne ha fatto uso persino per leggere e scrivere i dati da/sui dischi; per questo è fondamentale lasciare al s.o. la sua completa gestione, richiedendone l’utilizzo in maniera totalmente trasparente (e sicura) con apposite API.
Artefatti visivi
Un altro problema che capita con la grafica planare è che, se un’operazione non risulta sincronizzata col pennello elettrico (o magari è troppo grande e il tempo di vertical blank non è sufficiente per il suo completamento), si verificano degli artefatti grafici sotto forma di “sfarfallamento” dell’area grafica interessata. Ciò capita perché, quando lo schermo viene visualizzato, ci sarà parte di quell’area che avrà ancora alcuni bitplane non aggiornati con la nuova grafica, e quindi il controllore video avrà preso una parte di grafica dai bitplane nuovi e un’altra da quelli vecchi, unendoli in matrimonio e sfornando roba senso senso (da cui tali artefatti).
Questo fastidio è quasi del tutto assente con la grafica packed (si presenterebbe esclusivamente con profondità di colore che non fossero potenze del due e, in ogni caso, coinvolgerebbe al più un solo pixel per riga), che però potrebbe visualizzare grafica vecchia mischiata a quella nuova (situazione decisamente più tollerabile).
Prima rogna (per la grafica packed): renderizzare i font
Infine venivamo al punto più importante (per lo meno per me) in cui la grafica packed risulti un po’ più problematica rispetto a quella planare: usare i font per renderizzare testi. Finché si tratta di disegnare font a colori (visti per la prima volta proprio su Amiga: i Kara Fonts!) non c’è alcun problema; anzi, e per quanto detto finora, risulta molto più efficiente.
Ma i classici font monocromatici presentano problemi quando devono essere renderizzati su schermi con più di due colori, perché il colore da utilizzare è arbitrario e, quindi, è necessario “espandere” ogni punto dei singoli caratteri del font (che sono semplicemente bit impostati a 1
) “trasformandolo” nel colore desiderato.
Operazione, questa, che è possibile effettuare molto velocemente (anche più di quella planare) utilizzando lo stratagemma introdotto con l’articolo che ha trattato il tracciamento delle linee e il riempimento delle aree, il quale comporta l’utilizzo di un buffer addizionale costituito da una sola riga i cui pixel abbiano tutti lo stesso colore che verrà utilizzato come una sorta di texture per mappare (tramite la famigerata primitiva cookie-cut) i singoli pixel del font sullo schermo (framebuffer) di destinazione. Il costo non è esorbitante, perché in ultima analisi dipende dalla larghezza dei caratteri dello specifico font, ma deve tenere conto di tutti i colori visualizzabili con la specifica profondità di colore. Comunque vale esclusivamente per profondità di colore che non siano potenze del due, perché in questi casi, invece, non serve alcun buffer e l’operazione è di molto più veloce rispetto alla grafica planare (grazie al minor utilizzo di banda di memoria).
Per fare un esempio pratico, un font con caratteri larghi 16 pixel da utilizzare con uno schermo a 8 colori richiederà 8 buffer per memorizzare ciascuno degli 8 possibili colori. Ogni buffer occuperà 16 (larghezza del font) x 3 (dimensione dei pixel = profondità di colore dello schermo) = 48 bit = 6 byte. Quindi serviranno un totale di 8 x 6 = 48 byte per coprire tutti i casi (uno qualunque degli 8 colori disponibili). All’epoca erano maggiormente in uso font di 8 pixel di larghezza, quindi sarebbero serviti soltanto 8 x 3 = 24 byte, ma in realtà ne servirebbero 32 perché ogni buffer dev’essere un multiplo di 16 bit (dunque 4 byte per colore. 8 x 4 = 32).
Per curiosità, il Workbench dell’Amiga utilizzava uno schermo in alta risoluzione (640×200 o 640×256, a seconda che il sistema fosse NTSC o PAL) a soli 4 colori, per cui non serviva nessuno buffer. Discorso diverso se avesse usato 8 colori, ovviamente: in questo caso sarebbe servito.
Un’altra soluzione sarebbe potuta essere quella di utilizzare la CPU per renderizzare i testi, perché si presta molto bene a manipolare grafica packed. Sarebbe stata un po’ più lenta della soluzione di cui sopra, ma comunque abbordabile. Questo perché i font erano piccoli e utilizzare il Blitter per tracciare figure contenute in rettangoli da 8×8 pixel, ad esempio (era la dimensione standard della maggior parte dei font dell’epoca), richiedeva diversi cicli di clock della CPU soltanto per impostare tutti i registri del coprocessore, e ciò per ogni bitplane che sarebbe dovuto essere elaborato (come già spiegato).
Oltre al fatto, che in questo caso pesa non poco, di dover fare i conti con la dimensione del bus dati e il relativo spreco di banda di memoria, che per la grafica planare può essere molto elevato. Si capisce immediatamente prendendo il comunissimo font 8×8 e uno schermo a 8 colori = profondità di colore di 3 bit (con potenze del due, come già detto, la grafica packed è molto più veloce perché non incappa in problemi di disallineamento e spreco di banda). Prelevare i dati del font sprecherà sicuramente metà della banda a disposizione a prescindere dal tipo di grafica (planare o packed, il consumo di banda è esattamente lo stesso), poiché per leggere gli 8 bit di ogni riga di cui è costituto il font si dovranno leggerne sempre 16 (causa bus dati) e scartare gli altri 8.
Lo stesso, identico, spreco ci sarà quando si dovranno leggere e poi scrivere i bitplane (uno alla volta) dello schermo (framebuffer) su cui disegnare il carattere del font, ma ciò vale soltanto nel caso migliore, cioè quando tutti gli 8 pixel = bit della grafica del font finiranno nello schermo in una sola word (16 bit). Immaginiamo, ad esempio, di dover disegnare il carattere alla posizione di coordinate x pari a 0, 1, 2, …, fino ad arrivare a 8: in questi 9 casi si dovrà leggere e scrivere soltanto una word dello schermo, perché gli 8 pixel del font da disegnare saranno interamente contenuti in tale word.
Ma basta disegnare il font alla posizione 9, 10, …, fino a 15, e ci troviamo nel caso peggiore, perché una parte degli 8 pixel del font finirà in una word dello schermo e la parte rimanente andrà, invece, in quella successiva. Quindi saranno necessarie 2 letture e 2 scritture di word per poter completare l’operazione, e ciò per un solo bitplane. Uno spreco considerevole, insomma, che si moltiplica per il numero di bitplane.
Mentre sappiamo che con la grafica packed non ci sono bitplane, ma gli indici dei colori si trovano tutti di seguito, racchiusi in sequenze di bit senza interruzioni che non siano quelle provocate dal disallineamento provocato da profondità di colore che non siano potenze del due (3, in questo caso, visto che parliamo di 8 colori).
Infatti se vogliamo disegnare gli 8 pixel della prima riga del font sullo schermo, sappiamo che in questo caso dovremo leggere e scrivere dal framebuffer esattamente 8 x 3 = 24 bit e l’operazione sarà conclusa. Nel caso migliore, quindi, lo spreco sarà soltanto di 8 bit = un byte sprecato nella lettura dei dati dello schermo e un altro sprecato nella scrittura (perché si devono leggere in totale 32 bit e scriverne altrettanti per completare l’operazione).
Il caso peggiore, è invece, rappresentato dalla lettura e scrittura di 3 word (16 bit). Quindi in totale ci saranno 3 word lette e 3 word scritte, il che coincide col caso migliore della grafica planare (dove c’è una word letta e una scritta, ma ciò va ripetuto per i 3 bitplane). Per cui si capisce perché, nonostante l’handicap rappresentato dal dover utilizzare il piccolo buffer addizionale di cui ho parlato prima (sostanzialmente è questa la rogna, per questa primitiva grafica), complessivamente la grafica packed fa un lavoro decisamente migliore col rendering dei font, anche tenendo conto del fatto che il Blitter si programma una sola volta anziché una per ogni bitplane.
Inoltre, e chiudo quest’argomento, la situazione sarebbe poi migliorata con sistemi dotati di memoria Fast, perché con questi la CPU lavora più velocemente (specialmente con CPU più moderne e/o che operino a frequenze più elevati) e in maniera indipendente dal chipset. Infatti è anche il motivo per cui sono nate delle applicazioni come FBlit ed FText, che consentono di spostare parte della grafica in memoria Fast, usando ovviamente il processore per le relative elaborazioni, peraltro liberando preziosa memoria Chip.
La piaga: riempimento di aree
Infine, non possiamo dimenticare la primitiva di riempimento di aree (rettangolari), che richiede necessariamente l’uso di un buffer addizionale (un singolo bitplane) da utilizzare come soluzione temporanea in cui disegnare e riempire le aree interessate, per poi utilizzare la primitiva di cookie-cut per la grafica packed per impostare correttamente il colore da utilizzare per il riempimento (cosa che richiede un ulteriore buffer costituito, però, da una singola linea col colore in questione. Questo sempre e soltanto per profondità di colore che non siano potenze del due: per potenze del due non serve niente, poiché si può impostare direttamente il colore nel registro BLTBDAT
del Blitter).
Questa è oggettivamente l’unica primitiva particolarmente difficile per la grafica packed, nel caso in cui non si utilizzasse un’implementazione hardware. Fortunatamente e per com’è stata concepita nonché utilizzata, è una funzionalità di raro utilizzo, per cui la soluzione ibrida proposta è, a mio avviso, accettabile.
Possiamo quindi dire che, complessivamente, la grafica packed risulti più efficiente di quella lineare per gli utilizzi che ne fa un s.o. e relative applicazioni dotate di interfaccia grafica, a eccezione di quest’ultimo raro caso. Il prossimo articolo tratterà alcuni casi d’uso comuni per quanto riguarda la realizzazione e il funzionamento di giochi 2D.