Abbiamo visto nel precedente articolo come l’audio dell’Amiga fosse stato ignorato dagli ingegneri di casa Commodore, salvo improvvisare esperimenti insensati con componenti completamente alieni all’ecosistema per cercare di sopperire alle mancanze che, col passare del tempo, si sono fatte non solo vistose, ma anche molto pesanti e non più in grado di fronteggiare adeguatamente la concorrenza.
Questa stupenda macchina ha rappresentato un vero punto di svolta nel panorama della computazione domestica, perché ha portato così tante innovazioni, sostanzialmente in ogni area, da essere diventato il punto di riferimento col quale confrontarsi.
Non sono poche, infatti, la caratteristiche tecniche dell’hardware in dotazione, che hanno permesso di spaziare in lungo e largo su ogni dove, accontentando ogni genere di necessità nonché i desiderata degli sviluppatori (limitante a quanto fattibile con la tecnologia dell’epoca).
Ovviamente con l’avanzare dei progressi tecnologici ci si sarebbe aspettato degli aggiornamenti che, purtroppo, non sono arrivati oppure non sono stati adeguati o, peggio ancora, si sono rivelati del tutto inutili.
La moltiplicazione degli sprite
Uno di questi avrebbe dovuto interessare gli sprite, che sono stati molto usati in ambito videoludico (e non solo: anche il puntatore del mouse ne usava uno. Ma, in generale, erano utili anche per sovraimporre della grafica), che ha rappresentato il maggior mercato per questa piattaforma principalmente domestica.
D’altra parte Commodore aveva imbroccato la giusta via con l’altro “pezzo di cuore” che ci ha regalato anch’esso tante gioie: il Commodore 64. L’Amiga, da questo punto di vista, ne è stato un degno erede, offrendoci anch’esso 8 sprite, con un po’ di miglioramenti (che non sto qui a sottolineare. Almeno per il momento).
L’enorme utilità degli sprite è dovuta al fatto che si tratta di oggetti grafici che non necessitano di essere elaborati (né dalla CPU né dal Blitter), ma vengono visualizzati direttamente alle coordinate video a loro assegnante (sto volutamente semplificando il discorso).
Quindi non c’è bisogno di disegnarli, ed eventualmente ripristinare lo schermo una volta che siano stati visualizzati dalla circuiteria video: comodissimi! Talmente comodi che console come il Neo Geo non hanno nient’altro che sprite per visualizzare tutta la grafica a schermo (quindi niente tile né bitmap).
Questo è il motivo per cui, a dispetto delle limitazioni (che non sto qui a riportare per non allungare troppo il discorso), i programmatori hanno cercato di sfruttarli il più possibile, facendo i salti mortali (leggi: ricorrendo al loro multiplexing) con quei pochi a disposizione.
Infatti, e sfortunatamente, hanno seguito lo stesso destino dei canali audio dell’Amiga: il numero è rimasto esattamente lo stesso (sorvoliamo al momento sulle “innovazioni” introdotte col chipset AGA) fino alla fine.
Ma per aumentarne il numero si sarebbe potuta percorre una via simile a quella già vista nell’articolo dedicato all’audio. Infatti basti dare nuovamente un’occhiata al diagramma degli slot DMA a loro dedicati:
per rendersi conto che gli slot pari che li precedono sono tutti liberi.
Dunque, e come riportato nell’altro articolo, se gli sprite usano gli slot $15, $17 (primo sprite), $19, $1B (secondo), …, $31, $33 (ottavo), si sarebbero potuti impiegare $14, $16 (nono sprite), $18, $1A (decimo), …, $30, $32 (sedicesimo) per aggiungere altri otto sprite!
Anche l’implementazione sarebbe potuta essere simile, con la soluzione del registro di selezione per il banco di 8 sprite su cui lavorare (eventualmente condiviso con quello della selezione del banco audio, considerato che allo scopo spazio ce n’è in abbondanza), oppure estendere i registri del chipset in modo da avere tutti i registri direttamente indirizzabili.
Per completare l’opera sarebbe servito duplicare anche i registi che regolano i meccanismi di collisione fra sprite e/o sfondo, CLXCON e CLXDAT, ma francamente si tratta di una funzionalità poco utilizzata, per cui l’avrei lasciata in piedi soltanto per i primi 8 sprite (dunque niente registri addizionali di questo tipo per i nuovi sprite).
Il resto (priorità degli sprite e tavolozza dei colori da utilizzare) avrebbe funzionato in maniera identica facendo comportare i nuovi sprite esattamente come gli equivalenti primi otto (quindi lo sprite 8 funziona come l’1, il 9 come il 2, e così via).
E’ da sottolineare ancora una volta che questa soluzione non richiede cambiamenti radicali al sistema e si limita a sfruttare diversamente parte della banda di memoria che altrimenti verrebbe impiegata dal Copper, dal Blitter, o dalla CPU.
Quindi si rimane entro i limiti del funzionamento del chipset, e potete benissimo immaginare quante cose carine si sarebbero potute realizzare con tutti questi sprite a disposizione, specialmente se combinati (per coppia) a 16 colori: 8 sprite (larghi 16 pixel) utilizzabili (6 con lo scroll orizzontale abilitato) direi che inizino a essere allettanti…
Grafica specchiata (flip) orizzontalmente e verticalmente per gli sprite
Rimanendo sempre in tema di sprite, un grosso limite di quelli che sono stati implementati nell’Amiga è rappresentato dall’impossibilità di poter “specchiare” (mirroring. Chiamato flip in gergo) la grafica orizzontalmente e/o verticalmente. Per fare un esempio (di flip orizzontale):
Si tratta di una funzionalità estremamente importante, specialmente a quei tempi, poiché consentiva di dimezzare o anche ridurre a 1/4 lo spazio di memoria occupato dalla grafica (permettendo, quindi, di avere grafica molto più ricca e variegata nello stesso spazio), ed è il motivo per cui diverse console avevano almeno il flip orizzontale in hardware (anche perché è il più facile da implementare).
Ciò che suona strano è il fatto che non sia stato implementato nel chipset dell’Amiga, nonostante Jay Miner (il progettista principale) provenisse dal mondo Atari, dove aveva già aiutato a progettare i chip di alcuni famosi sistemi e alcuni di essi mettevano a disposizione questa caratteristica, per l’appunto.
Domanda che ho personalmente posto nel 2015 a Ron Nicholson (altro importantissimo membro del gruppo dei progettisti, nonché deputato al controllo dell’eventualmente sforamento del budget dei transistor impiegati nei tre chip custom) all’evento Amiga 30 che si è tenuto a Neuss, in Germania, il quale mi ha confermato che effettivamente è una cosa a cui non aveva pensato e che sarebbe stato utile implementare.
Come già anticipato, il flip orizzontale è una funzionalità molto semplice da implementare e per la quale sarebbe stato sufficiente utilizzare il bit #5 o il #6 dei registri SPRxCTL di ogni sprite, in modo da poterlo abilitare programmaticamente quando necessario.
Discorso diverso per il flip verticale, a causa di come sono stati, purtroppo, implementati gli sprite nell’Amiga, i quali sono, in realtà, una display list che contiene direttamente anche la grafica da visualizzare per ogni elemento grafico della lista da visualizzare a schermo.
Senza andare troppo nei dettagli allungando e complicando di più l’articolo, la sua implementazione avrebbe richiesto diverse modifiche alla circuiteria video in modo da riuscire ogni volta a riposizionare correttamente le locazioni di memoria da leggere (che sono di due tipi: la grafica vera e propria e le informazioni della display list sul dove e come visualizzare il prossimo sprite).
Questi problemi non ci sarebbero stati se gli sprite fossero stati implementati come pura grafica da visualizzare (quindi impostando manualmente i loro dati su come e dove visualizzarli). Cosa fattibile, anch’essa, ma aggiungendo una nuova modalità (“solo dati”) per la loro gestione (selezionabile come alternativa a quella normale).
Anche il Blitter col flip!
Fortunatamente il Blitter manipola direttamente la grafica (e solo quella: niente display list!), per cui questo problema non si pone.
Anzi, non serve nemmeno pensarci, perché in realtà quello verticale si può già emulare utilizzando alcune accortezze: facendo puntare la grafica alla prima locazione di memoria dell’ultima riga dell’immagine e selezionando come “modulo” (il valore che si somma alla fine dell’elaborazione di ogni riga per poter passare a quella successiva) due volte il numero di byte occupati dalla riga, ma con valore negativo.
Questo perché, quando il Blitter ha finito di processare una riga, normalmente si ritroverebbe all’inizio della riga successiva (sto semplificando il discorso per non appesantirlo con altri dettagli) con cui ripartire con l’elaborazione.
Sottraendogli una volta la dimensione di una riga si ritroverebbe nuovamente all’inizio della riga appena elaborata. Sottraendogli di nuovo la stessa quantità si posizionerebbe all’inizio di quella precedente a questa. E così via, andando a scorrere tutte le righe dall’ultima fino alla prima.
Riguardo il flip orizzontale, invece, servono alcune piccole modifiche. Ovviamente serve la circuiteria che si occupi di “scambiare di posto” i bit dal primo all’ultimo e viceversa, ma è semplicissima da implementare (e da applicare soltanto ai dati letti dai canali A e B, se abilitati).
Per il resto serve un bit per poterlo abilitare, e il registro BPLCON1 fa al caso nostro, perché ci sono diversi bit inutilizzati che possono essere impiegati allo scopo. Non servono altri bit o registri, perché il Blitter contiene già tutto ciò che serve, e necessita di un solo cambiamento alla sua logica di funzionamento, mentre per i programmatori c’è un solo altro cambiamento da fare per sfruttare questa caratteristica.
Nello specifico, quando si ha intenzione di utilizzare il flip orizzontale, i programmatori devono far puntare i canali A e B (che in genere sono utilizzati rispettivamente per la maschera e i dati da tracciare sullo schermo) all’ultima word (2 byte) della prima riga da elaborare.
Questo perché l’operazione di flip comporta il tracciamento partendo dall’ultima parte della grafica (che sarà specchiata, per l’appunto) che deve andare a finire all’inizio dello schermo, e così via procedendo nelle opposte direzioni (verso l’inizio della riga da tracciare per l’immagine, e verso la fine della riga per lo schermo).
Per far questo il Blitter dovrà, quindi, invertire il suo normale funzionamento, il quale prevede di spostarsi in avanti di una word alla volta, finché non si raggiunge la fine della riga. Dovrà, invece, spostarsi indietro di una word quando il flip sarà attivo. Ciò, ovviamente, soltanto per i canali A e B: tutto il resto funziona esattamente allo stesso modo.
Inoltre, i dati dei canali A e B vengono normalmente shiftati a destra di una certa quantità dopo che sono stati letti dalla memoria, per cui dovranno, invece, essere shiftati a sinistra col flip abilitato.
Si tratta di modifiche semplicissime, come si può vedere, che però consentono di dimezzare il consumo di memoria per gli oggetti grafici tracciati tramite il Blitter (chiamati BOB in gergo amighista) e che in assenza richiedono, purtroppo, di essere duplicati in memoria (la versione normale e quella “specchiata” orizzontalmente) in casi molto comuni e frequenti.
Copper in modalità “burst“
Un’altra modifica che avrebbe aiuto molto gli sviluppatori e, di riflesso, a creare giochi migliori sarebbe stata quella di consentire al Copper di poter impostare il valore di più registri (posti in sequenza) con una sola istruzione.
Il Copper è un processore estremamente semplice e dotato soltanto di due istruzioni: MOVE
(per copiare un dato a 16 in un registro), WAIT
per aspettare una posizione a video raggiunta dal pennello elettronico e/o il completamento di un’operazione del Blitter (sto semplificando anche qui), e SKIP
che funziona come la WAIT
, ma che salta l’istruzione successiva se la condizione non è, invece, soddisfatta.
E’ grazie al Copper che è stato possibile realizzare variegati effetti speciali a video (e non solo: io l’ho usato per programmare i canali audio, riproducendo la colonna sonora dei giochi a cui ho lavorato), tramite appositi programmi (chiamati Copper list in gergo amighista) eseguiti da questo coprocessore:
L’istruzione più usata è, manco a dirlo, la MOVE, perché consente di impostare i valori dei registri dei tre chip custom, ragione per la quale spesso sono necessarie più istruzioni di questo tipo allo scopo, con annesso consumo di memoria e, soprattutto, slot del DMA a disposizione per poter leggere queste istruzioni.
Ridurne il numero comporta, pertanto, parecchi vantaggi (si possono quasi raddoppiare il numero di colori che si possono cambiare in una riga video, tanto per fare un esempio significativo) e una semplice modifica al funzionamento della MOVE consente di raggiungere agevolmente l’obiettivo.
La prima word (2 byte) letta dal Copper è quella più importante perché consente di identificare il tipo di istruzione da eseguire (semplifico anche qui). Nello specifico, il bit #0 definisce una MOVE se è a zero, oppure una WAIT o SKIP diversamente.
Tutti i bit rimanenti della MOVE specificano in quale registro scrivere il valore, ma attualmente soltanto i primi 8 (#8..#1) sono utilizzati a tal fine, mentre gli altri (bit #15..#9) non lo sono e devono essere forzati a zero.
Poiché i registri dei chip custom partono dall’indirizzo esadecimale $DFF000
e potrebbero essere estesi teoricamente fino a $DFFFFF
(a partire da $E00000
lo spazio d’indirizzamento viene utilizzato per altro) per un totale di 4kB di area di memoria indirizzabile, ha senso riservare i primi 12 bit (#11..#0) per poter specificare il registro di partenza, mentre approfittiamo degli ultimi 4 bit (#15..#12) da sfruttare per specificare quanti registri impostare e, di conseguenza, quanti valori a 16 bit seguiranno il primo.
Per essere ancora più precisi, questi quattro bit indicheranno il numero di registri/valori, meno uno. Quindi 0 indicherà un registro da impostare, 1 due registri, e così via fino a 15 che consentirà di impostare 16 registri.
In questo modo la modifica è quasi totalmente retrocompatibile con l’attuale funzionamento del Copper, in quanto il primo caso della nuova istruzione MOVE coincide esattamente con quanto avviene già adesso.
Il quasi non è messo a caso, poiché è necessario effettuare anche una modifica al modo in cui si comporta l’istruzione SKIP. Infatti col nuovo funzionamento deve leggere sempre la word successiva (la prima della prossima istruzione), estrarne il numero di registri da impostare e tenerne conto per calcolare l’indirizzo dell’istruzione a essa seguente nel caso dovesse trattarsi di un’istruzione MOVE.
Anche qui sembrerebbe non esserci problemi di compatibilità, se non per il fatto che normalmente l’istruzione SKIP richiede due slot per la sua esecuzione e, quindi, 4 cicli di clock, mentre con questa modifica ne sarebbero necessari tre (deve sempre leggere la prima word dell’istruzione seguente).
La soluzione in ogni caso è estremamente semplice: è sufficiente abilitare il nuovo funzionamento del Copper utilizzando un bit nel registro COPCON (il quale è deputato alle impostazioni del Copper), in quale al momento utilizza un solo bit (quindi ce ne sono ben 15 a disposizione), e il gioco è fatto.
DMA per la tavolozza dei colori
Considerazioni simili si possono fare per un’altra innovazione tanto semplice quanto utile per alcuni scenari comuni, cioè quella di caricare valori nei registri dei tre chip custom senza dover passare necessariamente dal Copper.
Potrebbe sembrare una duplicazione, alla luce della suddetta modifica proposta per il Copper, ma ha i suoi perché se facciamo alcune considerazioni, dopo averne illustrato il funzionamento. Il quale consiste nell’impostare il puntatore alla memoria Chip (l’unica accessibile dai chip custom) dal quale leggere le word (16 bit) da scrivere, il registro di partenza, e il numero di registri da impostare (che farebbe partire l’operazione).
Sono richiesti, quindi, 4 registri a 16-bit nel banco dei registri di tali chip, ma ce ne sono già diversi non utilizzati che possono essere sfruttati. In più servirebbe un bit per indicare il fatto che tale canale DMA sarebbe libero (in modo da procedere con altre operazioni di copia); il registro DMACONR ne possiede un paio inutilizzati di cui se ne potrebbe impiegare uno.
Lo scenario principale è quello di poter caricare l’intera tavolozza di colori in un colpo solo, senza duplicare risorse (e banda) e richiedere ogni volta la generazione di apposite istruzioni nelle Copper list. Infatti la tavolozza si trova già in memoria, e viene letta per creare le apposite istruzioni nella Copper list, che a loro volta dovranno poi essere lette dalla memoria per poter effettivamente modificare i registri.
Inoltre la precedente modifica al Copper si limita a un massimo di 16 registri modificabili (il che richiederebbe più istruzioni di questo tipo in caso sia necessario impostarne di più), mentre in questo caso non ci sarebbe alcun limite. Basti immaginare quante istruzioni del Copper richiederebbe una tavolozza di 256 colori a 24 bit (ben 512 word) e, di conseguenza, quanto lunghe diventerebbero le Copper list anche sfruttando la nuova istruzione MOVE.
Considerazioni analoghe si possono fare nel caso si vogliano impostare tutti i puntatori ai bitplane da visualizzare, ad esempio, oppure tutti quelli degli sprite, o ancora buona parte dei registri del Blitter (nel caso siano predefinite o abbiano parti ripetitive) prima di farlo partire.
Conclusioni
Con quest’ultima si chiude la panoramica sulle innovazioni che sarebbero state possibili per migliorare il chipset dell’Amiga in ambito grafico (per lo più), la maggior parte delle quali sono di facile implementazione e richiedono poche risorse.
Piccoli investimenti che avrebbero avuto un notevole ritorno in termini di qualità nettamente superiori in ambito ludico (ma anche in altri settori), se i tecnici della casa madre avessero avuto maggior cognizione di come funzionasse la piattaforma ma, soprattutto, di come venisse usata e, di conseguenza, come farla evolvere opportunamente (soddisfacendo le esigenze degli sviluppatori).
L’unica più complicata e che ne richiede di più è il raddoppio degli sprite, ma parliamo comunque di roba fattibilissima già alla fine degli anni ’80, grazie ai progressi dei processi produttivi che hanno consentito di impacchettare molti più transistor nello stesso spazio.
Il prossimo articolo parlerà di innovazioni che sarebbero state possibili agendo su altri fattori.