Categoria: Programmazione
Esiste una quantità impressionante di effetti grafici che è possibile realizzare sul C64. Molti giochi sfruttano immagini hires, smooth scrolling e flash del bordo per apparire più accattivanti.
Tuttavia, alcuni giochi non si limitano a questi semplici effetti; a volte le loro intro sono davvero spettacolari, alternano due o più immagini hires attraverso effetti "a scomparsa", disegnano fasci di luce che scorrono sullo schermo, allargano e stringono sprite... insomma, fanno cose che sarebbero impensabili senza l'esistenza di un particolare registro, detto registro di raster.
Prima di parlare di questo registro (anzi, di questi due registri, come vedremo tra poco) occorre rivedere un po' di teoria.
Il raster sul video
Il raster (o pennello elettronico, in inglese anche detto rasterbeam) altro non è un fascio di elettroni che aggiorna lo schermo del C64 50 volte al secondo nel sistema video PAL (usato in Europa) e 60 volte al secondo nel sistema video NTSC (usato negli Stati Uniti).
Ora, il raster non aggiorna il video istantaneamente, ma lo ricostruisce linea per linea, partendo dall'angolo dello schermo in alto a sinistra e spostandosi man mano di un pixel verso destra. Una volta aggiornato l'ultimo pixel della linea 0 (la prima linea di schermo, che non è visibile all'utente) riparte dall'estremità sinistra della linea 1, aggiorna tutti i pixel di questa seconda linea e riparte dall'estremità sinistra della linea 2... e via dicendo, fino a che non ha completato l'aggiornamento dell'ultima linea di schermo. A questo punto riparte dall'angolo dello schermo in alto a sinistra, e il processo ricomincia. Ciascuna delle linee processate dal raster viene appunto detta linea di raster.
Siccome i sistemi video PAL e NTSC sono diversi, i video di un C64 PAL e NTSC hanno dimensioni e strutture leggermente differenti: in particolare, le osservazioni che faremo in seguito valgono per il sistema video PAL.
Le linee di raster sono 312, convenzionalmente indicate con valori compresi tra 0 e 311; dunque, la prima linea di raster è la linea 0, e la 312a linea di raster è la linea 311. Di queste 312 linee, solo 284 sono visibili all'utente, e sono tutte quelle comprese tra la 16 e la 299. Delle linee di raster visibili, 84 sono interamente ricoperte dal colore usato per definire il bordo, mentre le altre 200 (precisamente tutte quelle comprese tra la 51 e la 250) costituiscono parte del bordo e la matrice video su cui è possibile definire sprite e caratteri. Ciascuna linea di raster si compone di 504 pixel, dunque l'intero video (visibile e non visibile) si compone di 312x504 = 157248 pixel.
L'immagine sottostante chiarisce quanto esposto finora:
Figura: In rosso la linea di raster 0, in verde lo schermo visibile (linee di raster 16-299), in arancione la linea di raster 311.
Poco fa abbiamo detto che il chip video del C64 (il celeberrimo VIC-II) impiega 1/50 di secondo per aggiornare un intero quadro video, perciò il tempo impiegato per aggiornare una singola linea di raster vale: 0.02 / 312 = 0.000064103 secondi ~ 64.103 microsecondi.
Il raster in memoria
Nel chip video del C64 esiste una locazione che tiene continuamente traccia della linea di schermo su cui si trova il rasterbeam: la n. 53266 ($D012 esadecimale). Tuttavia, le linee di raster di una schermata PAL sono più di 256; per tenere traccia delle linee 256-311 c'è bisogno di un bit supplementare, il bit 7 della locazione n.53265 ($D011 esadecimale) che, quando è settato, indica che il rasterbeam si trova su una delle linee video comprese tra la 256 e la 311. Questi due registri vengono aggiornati ad ogni passaggio del raster da una linea video alla successiva, e resettati dopo che il raster ha completato l'aggiornamento dell'intero quadro video.
Per farci una prima idea di come poter sfruttare questa preziosa risorsa del buon VIC, consideriamo questo programmino:
start lda #$9e
loop cmp $d012
bne loop
dec $d020
loop2 lda $d012
bne loop2
lda $d011
bmi loop2
inc $d020
jmp start
Il ciclo loop attende che il rasterbeam giunga sulla linea video 158 ($9e esadecimale), dopodiché decrementa il colore del bordo. A questo punto entra in gioco loop2, che aspetta che il rasterbeam raggiunga la linea 0 (la prima linea video) per ripristinare il colore del bordo originario; il processo si ripete all'infinito. Nota che, in loop2, è necessario anche un controllo sulla locazione $d011 per risolvere l'ambiguità tra la riga 0 e la riga 256, entrambe indicate con uno 0 dalla locazione $d012. Il risultato di tutto questo è facilmente immaginabile: un bordo bicolore.
Fermi tutti: il raster è arrivato!
L'esempio precedente rappresenta il primissimo approccio alla programmazione del raster. Ma come si può notare, è anche estremamente inefficiente: il programma testa continuamente la linea di schermo su cui si trova il raster, e per tutto il tempo in cui non esegue il codice di interrupt standard (timing del cursore, aggiornamento del buffer dei caratteri, aggiornamento dell'orologio) la CPU viene usata unicamente per questo. Ciò non permette al microprocessore di compiere altre operazioni utili (ad esempio, il lampeggio del cursore e l'output del keybuffer stesso).
Un approccio più interessante alla programmazione del raster consiste nel far generare al chip video una interruzione del microprocessore solo quando il rasterbeam giunge alla linea desiderata. A questo scopo, il VIC-II mette a disposizione il bit 0 del registro 53274 ($D01A esadecimale) che, quando settato, abilita il rasterbeam come possibile sorgente di interrupt. Per definire la linea video in cui dovrà essere generato l'interrupt basta scrivere nei registri $D011 (bit 7) e $D012 subito prima di attivare la sorgente di interrupt, e il gioco è fatto. In questo modo, la CPU può eseguire le altre operazioni per tutto il tempo in cui il rasterbeam non genera l'interrupt. Una volta che il raster ha generato l'interruzione e il codice di interrupt è stato eseguito, è necessario resettare il bit 0 del registro 53273 ($D019 esadecimale), altrimenti la macchina figurerà sempre in stato di interrupt e non permetterà l'esecuzione delle successive richieste d'interruzione.
Proviamo a modificare il programma di prima adottando questo nuovo approccio:
sei
lda #<irq
sta $0314
lda #>irq
sta $0315
lda #$9e
sta $d012
lda $d011
and #$7f
sta $d011
lda #$01
sta $d01a ; setta il rasterbeam come possibile sorgente di interrupt
cli
rts
irq lda $d019
and #$01
beq normal ; distingue tra IRQ del CIA1 e del VIC
dec $d020
alt lda #$00
sta $d012
lda alt-3
eor #$20
sta alt-3 ; alterna il colore del bordo
lda alt+1
eor #$9e
sta alt+1 ; alterna la linea di generazione dell'IRQ
inc $d019 ; permette le interruzioni successive
jmp $ea81
normal jmp $ea31
L'effetto visivo è lo stesso di prima, ma stavolta la CPU permette l'interazione con l'utente, perché una volta eseguita la routine di interrupt può tornare ad eseguire il ciclo principale per la lettura e l'output dei caratteri. Nota che la routine di IRQ determina quale sorgente ha causato l'interruzione; se si tratta del rasterbeam viene cambiato il colore del bordo, altrimenti (essendoci due sole sorgenti di interrupt attive) significa che l'interrupt è stato generato dal timer A del CIA1, e il C64 compie il consueto refresh del cursore, del keybuffer e dell'orologio interno.
Timing + sincronizzazione = terra promessa
Proviamo a creare una routine che disegni le prime 16 linee video ciascuna di un colore diverso dall'altro. Sembra facile: si genera un'interruzione del raster per ciascuna delle linee video comprese tra la 16 e la 31, e si incrementa il colore del bordo. Questo codice è perfettamente adatto al nostro scopo:
sei
lda #$0f
sta $02 ; inizializza il contatore di riga
lda #<irq
sta $0314
lda #>irq
sta $0315
lda #$10
sta $d012
lda $d011
and #$7f
sta $d011
lda #$01
sta $d01a ; setta il rasterbeam come possibile sorgente di interrupt
lda #$7f
sta $dc0d
cli
rts
irq inc $d020 ; cambia il colore del bordo
inc $d012 ; setta la prossima linea come la successiva linea di interrupt
dec $02 ; decrementa il contatore di riga
bne exit ; ultima riga?
lda #$0f
sta $02
lda #$fe
sta $d020
lda #$10
sta $d012 ; si', ricarica il contatore di riga, ripristina il bordo e le interruzioni
exit lsr $d019 ; permette le interruzioni successive
jmp $ea81 ; esce dall'interruzione
Sfortunatamente non succede affatto quello che volevamo (provare per credere). Perché?
Primo: quando il chip video genera un'interruzione (cosa che avviene esattamente quando il raster si trova all'inizio della linea video indicata nel registro $D012), può essere che la CPU non abbia ancora terminato di eseguire una certa istruzione. In questa situazione possono succedere due cose:
- Alla CPU mancano almeno due cicli per terminare l'esecuzione dell'istruzione corrente: in questo caso eseguirà una sequenza di interrupt della durata di 7 cicli subito dopo aver terminato l'esecuzione di questa istruzione.
- Alla CPU manca un solo ciclo per terminare l'esecuzione dell'istruzione corrente: in questo caso dovrà eseguire completamente anche la successiva istruzione, prima di eseguire la sequenza di interrupt di 7 cicli.
Secondo: dopo aver eseguito la sequenza di interrupt, il microprocessore deve verificare che tipo di interruzione si è verificata, passando attraverso questa routine del Kernal:
FF48 pha
FF49 txa
FF4A pha
FF4B tya
FF4C pha ; salva i registri sulla pila
FF4D tsx
FF4E lda $0104,x
FF51 and #$10 ; stabilisce il tipo di interruzione leggendo il flag B di SR
FF53 beq $ff58
FF55 jmp ($0316) ; interruzione di tipo BRK
FF58 jmp ($0314) ; interruzione di tipo IRQ
Nel caso in cui la sorgente dell'interruzione sia un'istruzione BRK, questa routine costa al 6510 ben 28 cicli di clock, mentre nel caso di una normale richiesta di interruzione (è il nostro caso) addirittura 29 cicli di clock. Dopo aver terminato di eseguire questa routine, inizia l'esecuzione del gestore di interrupt (nel nostro caso, la routine puntata dal vettore $0314-$0315). Ricordando che la durata di un'istruzione macchina va dai 2 ai 7 cicli di clock e facendo qualche semplice calcolo, si ottiene che il gestore di interrupt viene eseguito 2+7+29 = 38 cicli di clock dopo la generazione dell'interruzione nel caso migliore, e 1+7+7+29 = 44 cicli di clock dopo la generazione dell'interruzione nel caso peggiore.
Ebbene, qualche riga sopra abbiamo detto che il raster impiega con buona approssimazione 64 microsecondi per scansionare una linea video completa. Dato che nel C64 un periodo di clock è pari a circa 1.015 microsecondi, la durata della scansione di una rasterline è di 63 cicli di clock. Questo significa che quando il nostro codice di IRQ viene eseguito per la prima volta, il rasterbeam si trova ben oltre la metà della linea video 16.
A questo fatto va aggiunto che ciascuna istruzione del gestore di interrupt costa un numero variabile di cicli macchina. Facciamo un rapido conto:
CICLI DI CLOCK TRASCORSI
DALL'IRQ
------------------------
inc $d020 ; (+6) 44-50
inc $d012 ; (+6) 50-56
dec $02 ; (+5) 55-61
bne exit ; (+3)* 58-64 - il raster potrebbe gia' essere sulla linea video 17
...
lsr $d019 ; (+6) 64-70 - il raster e' gia' sulla linea video 17
...
Ne deduciamo che, una volta che la CPU sblocca la locazione $D019 il raster è già sulla linea 17, ed è già passato il momento buono per la generazione dell'interruzione; di conseguenza la prossima IRQ verrà generata quando il raster giungerà sulla linea 17 del quadro video successivo.
Il timing ci ha dimostrato che per ottenere effetti video precisi (e in generale, quasi tutti gli effetti grafici più elaborati) un solo interrupt è inadeguato: ci serve un'altra strategia. In altre parole, dobbiamo fare in modo che la CPU cambi il colore del bordo nel momento esatto in cui il raster passa da una linea di schermo a quella seguente. Già, ma come se non sappiamo con certezza dove si trova il raster nel momento in cui viene eseguito il codice di IRQ?
Possiamo eliminare (o perlomeno, arginare di molto) questa incertezza di 6 cicli con la tecnica del doppio interrupt. Questa tecnica di sincronizzazione consiste nel creare un primo gestore di interrupt (GI), che ne creerà a sua volta un secondo. Quest'ultimo gestore verrà eseguito mentre la CPU sta eseguendo un'istruzione della durata di 2 soli cicli di clock. Così facendo, nel caso ottimo il secondo GI verrà eseguito 2+7+29 = 38 cicli di clock dopo la generazione della seconda IRQ, mentre nel caso pessimo verrà eseguito 1+2+7+29 = 39 cicli di clock dopo la generazione della seconda IRQ, riducendo a un solo ciclo di clock l'incertezza sulla posizione del rasterbeam.
Questo procedimento richiede qualche calcolo, perciò prima illustrerò il codice macchina adatto allo scopo e poi darò tutte le spiegazioni del caso:
; Esempio di sincronizzazione con doppio interrupt
* = $c000
scan = $e5cd
lda #$01
sta $dc0d ; disattiva il timer A (keyscan manuale)
lda #<irq1
sta $0314
lda #>irq1
sta $0315 ; setta il primo gestore di interrupt
lda #$0d
sta $d012 ; la prima IRQ viene generata alla linea 13
lda #$7f
and $d011
sta $d011
lda #$01
sta $d01a
rts
; CICLI DI CLOCK
; TRASCORSI DALL'IRQ
; --------------------
irq1 lda #<irq2 ; 40-46
sta $0314 ; 44-50
lda #>irq2 ; 46-52
sta $0315 ; 50-56
lda #$0f ; 52-58
sta $d012 ; 56-62
inc $d019 ; 62-68 - il raster potrebbe gia' essere sulla linea video 14
; (per questo e' settato l'IRQ alla linea 15)
cli ; linea 14 cicli 1-7 (riattiva la modalita' IRQ)
inc $0313 ; linea 14 cicli 7-13
dec $0313 ; linea 14 cicli 13-19
inc $0313 ; linea 14 cicli 19-25
dec $0313 ; linea 14 cicli 25-31
inc $0313 ; linea 14 cicli 31-37
dec $0313 ; linea 14 cicli 37-43
inc $0313 ; linea 14 cicli 43-49
dec $0313 ; linea 14 cicli 49-55
inc $0313 ; linea 14 cicli 55-61
nop ; linea 14 cicli 57-63
nop ; - possibile IRQ
nop ; - possibile IRQ
nop ; - possibile IRQ
nop ; - possibile IRQ
nop ; nop di margine
jmp $ea31 ; permette al C64 le operazioni di routine
jmp scan
irq2 ldy #$10 ; linea 15 cicli 40-41
inc $0313 ; linea 15 cicli 46-47
dec $0313 ; linea 15 cicli 52-53
lda $0313 ; linea 15 cicli 56-57
fill inc $d020 ; linea 16 cicli 62-63 +6 - aggiorna il bordo ogni 63 cicli di clock
dey ; linea 16 cicli 1-2 +2 (la durata esatta di una linea di raster)
beq ret ; linea 16 cicli 3-4* +2*
inc $0313 ; linea 16 cicli 9-10 +6
dec $0313 ; linea 16 cicli 15-16 +6
inc $0313 ; linea 16 cicli 21-22 +6
dec $0313 ; linea 16 cicli 27-28 +6
inc $0313 ; linea 16 cicli 33-34 +6
dec $0313 ; linea 16 cicli 39-40 +6
inc $0313 ; linea 16 cicli 45-46 +6
dec $0313 ; linea 16 cicli 51-52 +6
nop ; linea 16 cicli 53-54 +2
jmp fill ; linea 16 cicli 56-57 +3
ret lda #<irq1
sta $0314
lda #>irq1
sta $0315 ; ripristina il primo GI
lda #$0d
sta $d012
inc $d019 ; riabilita le interruzioni del rasterbeam
jmp $ea81 ; torna dall'IRQ
; NOTE
; * 3 cicli quando avviene il salto (irrilevante)
Et voilà: le linee video dalla 16 alla 31, tutte di un colore diverso e senza sbavature (i demo-coder la chiamano routine di stable raster). Ma veniamo ai dettagli interessanti.
La prima IRQ viene generata non appena il raster passa sulla linea 13 (siamo nel ciclo 0), dopodiché il microprocessore compie i passaggi descritti sopra, e finalmente arriva al gestore di interrupt (irq1).
Nel momento in cui la CPU inizia ad eseguire la prima istruzione (lda <#irq2), accusa già un ritardo di 37-43 cicli nei confronti del rasterbeam (che ha già superato la metà di questa linea video), e setta la prossima IRQ in modo che avvenga alla linea 15 (settarlo alla linea 14 sarebbe rischioso, perché come si vede dal timing, il raster potrebbe già averla varcata).
A questo punto si ridà alla macchina la possibilità di accettare le interruzioni (cli) e si fa in modo che, nel momento in cui il raster varca la linea 15 (che corrisponde all'istante della seconda IRQ), il microprocessore stia eseguendo un'istruzione che richieda due soli cicli di clock (nop) in modo che, quando viene eseguito il gestore irq2, ci sia al più un ciclo di incertezza sulla posizione del raster in questa linea.
Una volta ottenuto questo, con una tabella degli opcode alla mano, si crea il ritardo necessario affinché la CPU finisca di incrementare il bordo nel momento stesso in cui il rasterbeam lo ridisegna, e si ripete il processo ogni 63 cicli di clock (cioè esattamente ogni linea video). Una volta colorate le prime 16 linee visibili (a questo scopo si è usato il registro Y come contatore), il secondo gestore di IRQ restituisce il controllo al primo, che permette il refresh dell'orologio, del keybuffer e del cursore (jmp $ea31 e jmp scan).
6510 vs VIC-II: la guerra infinita
La CPU del C64 non è sempre in attività. All'interno dello schermo visibile, vi sono delle linee video in corrispondenza delle quali la CPU deve sospendere l'esecuzione delle istruzioni, per consentire al chip video di leggere i colori e i codici che servono a definire i caratteri sullo schermo. Queste empasses del microprocessore si verificano normalmente ogni 8 linee video, ciascuna delle quali viene comunemente chiamata bad-line.
Durante una bad-line, a un certo punto il processore viene arrestato da un particolare segnale inviatogli dal VIC stesso, che gli impedisce di usare il bus per la lettura dei dati e degli indirizzi, consentendogli solo (eventualmente) operazioni di scrittura. Il chip video attende 3 cicli dopo la generazione del segnale per dare tempo alla CPU di ultimare le operazioni di scrittura, dopodiché si prende 40 cicli di clock nei quali legge i colori e i codici carattere associati a ciascuna colonna della matrice video; la CPU torna ad eseguire istruzioni al termine di questi 40 cicli di clock.
All'accensione della macchina le bad-lines sono le linee numero 51, 59, 67, 75, 83, 91, 99, 107, 115, 123, 131, 139, 147, 155, 163, 171, 179, 187, 195, 203, 211, 219, 227, 235, 243. All'interno delle bad-lines i cicli di clock "rubati" alla CPU sono tutti quelli compresi tra il 15esimo e il 54esimo.
Come si può intuire, realizzare effetti grafici nella matrice video è più difficile che sui bordi, perché bisogna tener conto della presenza di una bad-line ogni 8 linee di raster. In generale, le bad-lines sono fastidiose perché sfasano la sincronizzazione tra la CPU e il rasterbeam e tra il C64 e le periferiche. Ad esempio, se volessimo traslare le rasterbars di prima nel mezzo dello schermo, a causa delle bad-lines dovremmo rivederne completamente il timing... o forse no. Esistono infatti due modi per sbarazzarsi delle bad-lines:
- resettare il bit 4 della locazione $D011
- ad ogni linea video sfasare il registro di scrolling verticale
Il primo metodo è molto semplice, ma è piuttosto grossolano, perché di fatto cancella la matrice video, sostituendola con una schermata in tinta unita col bordo. In questo modo si possono creare tutti gli effetti grafici che si vogliono in qualunque parte dello schermo, ma non rimane più spazio per bitmap e caratteri. Infatti, non dovendo più scansionarli il chip video lascia a disposizione della CPU tutti i cicli di clock destinatigli.
Il secondo metodo richiede una piccola spiegazione tecnica. Il VIC-II implementa al suo interno un contatore a 3 bit che viene incrementato ad ogni passaggio del rasterbeam da una linea video alla successiva. Quando il valore contenuto nel contatore è pari al valore contenuto nel registro di scrolling verticale (i bit 2-1-0 del registro $D011) e la linea su cui si trova il raster è compresa tra 48 e 247, significa che il raster si trova su una bad-line, e il VIC-II si prenderà i famosi 40 cicli di clock per la scansione dei caratteri.
Quindi, se vogliamo liberarci delle bad-lines, non dovremo fare altro che modificare i 3 bit meno significativi di $D011 per ciascuna delle 16 linee video, in modo che in quelle 16 linee il contatore di linea del VIC-II e lo scroll register non corrispondano mai. A questo scopo un banale INC/DEC $D011 andrà benissimo:
; Esempio di sincronizzazione con doppio interrupt
; modificato per le bad-lines
* = $c000
scan = $e5cd
lda #$01
sta $dc0d ; disattiva il timer A (keyscan manuale)
lda #<irq1
sta $0314
lda #>irq1
sta $0315 ; setta il primo gestore di interrupt
lda #$98
sta $d012 ; la prima IRQ viene generata alla linea 152
lda #$7f
and $d011
sta $d011
lda #$01
sta $d01a
rts
; CICLI DI CLOCK
; TRASCORSI DALL'IRQ
; --------------------
irq1 lda #<irq2 ; 40-46
sta $0314 ; 44-50
lda #>irq2 ; 46-52
sta $0315 ; 50-56
lda #$9a ; 52-58
sta $d012 ; 56-62
inc $d019 ; 62-68 - il raster potrebbe già essere sulla linea video 153
; (per questo è settato l'IRQ alla linea 154)
cli ; linea 153 cicli 1-7 (riattiva la modalità IRQ)
inc $0313 ; linea 153 cicli 7-13
dec $0313 ; linea 153 cicli 13-19
inc $0313 ; linea 153 cicli 19-25
dec $0313 ; linea 153 cicli 25-31
inc $0313 ; linea 153 cicli 31-37
dec $0313 ; linea 153 cicli 37-43
inc $0313 ; linea 153 cicli 43-49
dec $0313 ; linea 153 cicli 49-55
inc $0313 ; linea 153 cicli 55-61
nop ; linea 153 cicli 57-63
nop ; - possibile IRQ
nop ; - possibile IRQ
nop ; - possibile IRQ
nop ; - possibile IRQ
nop ; nop di margine
jmp $ea31 ; permette al C64 le operazioni di routine
jmp scan
irq2 ldy #$10 ; linea 154 cicli 40-41
inc $0313 ; linea 154 cicli 46-47
dec $0313 ; linea 154 cicli 52-53
lda $0313 ; linea 154 cicli 56-57
fill inc $d020 ; linea 154 cicli 62-63 +6 - aggiorna il bordo ogni 63 cicli di clock
dey ; linea 155 cicli 1-2 +2 (la durata esatta di una linea di raster)
beq ret ; linea 155 cicli 3-4* +2*
inc $d011 ; linea 155 cicli 9-10 +6 - alterna inc/dec e RC <> SCROLY
lda #$20 ; linea 155 cicli 11-12 +2
eor fill+6 ; linea 155 cicli 15-16 +4
sta fill+6 ; linea 155 cicli 19-10 +4 - alterna inc e dec
inc $0313 ; linea 155 cicli 25-26 +6
dec $0313 ; linea 155 cicli 31-32 +6
inc $0313 ; linea 155 cicli 37-38 +6
dec $0313 ; linea 155 cicli 43-44 +6
inc $0313 ; linea 155 cicli 49-50 +6
lda $0313 ; linea 155 cicli 53-54 +4
jmp fill ; linea 155 cicli 56-57 +3
ret lda #$1b
sta $d011 ; ripristina il registro di scroll verticale fuori dalle rasterbars
lda #<irq1
sta $0314
lda #>irq1
sta $0315 ; ripristina il primo GI
lda #$98
sta $d012
inc $d019 ; riabilita le interruzioni del rasterbeam
jmp $ea81 ; torna dall'IRQ
; NOTE
; * 3 cicli quando avviene il salto (irrilevante)
Niente da dire sul codice, se non per il fatto che il timing di irq2 è stato modificato per far posto a inc/dec $d011, e che quando la macchina esce da irq2 viene ripristinato il valore originale del registro di scroll ($1b = 00011011), in modo da permettere la corretta scansione dei caratteri fuori dalle linee interessate dalle rasterbars.
L'unico inconveniente a cui si va incontro eliminando le bad-lines è un indesiderabile fondo nero nel mezzo dello schermo, dovuto proprio all'alterazione del registro di scrolling; perciò, se si intendono realizzare effetti grafici di stable raster anche sulla matrice video, è necessario modificare il timing ad-hoc per le bad-lines.
La scansione degli sprite
La tecnica di sincronizzazione del doppio interrupt è molto usata, ma non è completamente precisa. Come detto prima, rimane ancora un ciclo di clock di oscillazione. Il problema non si risolve facilmente, perché nel set di istruzioni 65xx nessuna dura un solo ciclo di clock. Benché esista un modo per sincronizzare perfettamente raster e microprocessore anche con il doppio interrupt, definendo uno sprite si può ottenere molto più facilmente l'effetto desiderato.
Oltre a prelevare le informazioni sui caratteri, il chip video scansiona ad ogni rasterline la locazione $D015, che indica se e quanti sprite sono attivi in quel momento. Ad esempio, poniamo il caso che durante la scansione di una certa linea video la locazione $D015 contenga il valore $01. Questo significa che è attivo sullo schermo lo sprite 0; a questo punto viene scansionata anche la locazione $D001 (la coordinata y dello sprite 0); se il valore in essa contenuto coincide con la linea attualmente scansionata dal raster ($D012), al 55esimo ciclo di clock il VIC-II invierà alla CPU il segnale di arresto. 3 cicli dopo, il VIC-II si prenderà 2 cicli di clock, necessari per leggere i dati relativi allo sprite. Questo processo si ripeterà identico nelle 20 linee video successive (come si sa, uno sprite viene visualizzato su 21 linee video).
Affinché le scansioni dei caratteri e degli sprite non interferiscano tra loro (uno sprite definito sulla matrice video attraversa 3 bad-lines), il chip video utilizza i cicli di clock 58-63 per la scansione dei primi 3 sprite e i cicli di clock 1-10 per la scansione degli altri 5. È da notare questo apparente non-senso: perché scansionare gli sprite 0-2 dopo gli sprite 3-7? Il fatto è che la scansione dei caratteri avviene proprio nel bel mezzo della rasterline, e la visualizzazione di uno sprite avviene una linea dopo la sua vera coordinata y (provare per credere).
Tutto questo sproloquio (necessario) per dire che, facendo in modo che il microprocessore arrivi ad eseguire il GI con un'oscillazione massima di 3 cicli (i 3 cicli che intercorrono tra il segnale d'arresto e la scansione dello sprite), dopo la scansione dello sprite sapremo esattamente a che ciclo inizierà l'esecuzione dell'istruzione successiva.
Proviamo a modificare il solito programma delle rasterbars, ottenendo stavolta una sincronizzazione assolutamente precisa. Al solito, prima il codice e poi i commenti:
; ESEMPIO DI SINCRONIZZAZIONE (ESATTA) CON UNO SPRITE
* = $c000
scan = $e5cd
lda #$01
sta $dc0d ; disattiva il timer A (keyscan manuale)
jsr defsprt ; definisce lo sprite 0
lda #<irq1
sta $0314
lda #>irq1
sta $0315 ; setta il primo gestore di interrupt
lda #$14
sta $d012 ; la prima IRQ viene generata alla linea 15
lda #$7f
and $d011
sta $d011
lda #$01
sta $d01a
loop jmp scan ; loop costante (massima oscillazione del raster = 3 cicli)
jmp loop
defsprt lda #$01
ldx #$00
ldy #$00 ; ultima linea di scansione dello sprite
stx $d000 ; = linea di generazione dell'IRQ
sty $d001
sty $d010
sta $d015
rts
; CICLI DI CLOCK
; TRASCORSI DALL'IRQ
; --------------------
irq1 ldx #$ff ; 40-43
lda $0313 ; 44-47
dec $0313 ; 50-53
lda $0313 ; 54-57 - il microprocessore si arresta dopo questa istruzione
fill stx $d020 ; 63 - quando la CPU inizia ad eseguire questa istruzione
; siamo al ciclo 60
; - siamo esattamente all'inizio della linea 21
dec $0313 ; +6
inc $0313 ; +6
dec $0313 ; +6
inc $0313 ; +6
dec $0313 ; +6
inc $0313 ; +6
dec $0313 ; +6
inc $0313 ; +6
nop ; +2
nop ; +2
inx ; +2
cpx #$0f ; +2
bne fill ; +3
inc $d019
jmp $ea31 ; - permette al C64 le operazioni di routine
Stavolta la linea generatrice dell'IRQ è la numero 20; quando la CPU comincia ad eseguire la prima istruzione (ldx #$ff) il rasterbeam si trova già oltre metà linea, ma l'incertezza sulla sua posizione è di soli 3 cicli. Il microprocessore si arresta dopo aver terminato di eseguire la quarta istruzione (lda $0313) perché il chip video gli ha già inviato il segnale d'arresto. I cicli 58 e 59 sono utilizzati dal VIC-II per leggere i dati relativi all'ultima linea dello sprite, e nei cicli 60-63 viene eseguita la stx $d020, perciò il rasterbeam cambia il colore del bordo esattamente nel punto d'inizio della linea video 21.
In sostanza, quindi, usare gli sprite evita di sprecare memoria inutilmente nello scrivere due gestori di interrupt, elimina il ciclo di clock di oscillazione e risulta concettualmente molto più semplice.
Poiché i caratteri vengono scansionati seguendo una meccanica analoga a quella degli sprite, è possibile sincronizzare CPU e rasterbeam anche attraverso le bad-lines.
Concludo qui la mia dissertazione sul raster. Spero che leggendo questo articolo qualcuno si sia incuriosito e abbia deciso di approfondire le conoscenze di questa straordinaria risorsa del Commodore 64. In attesa di novità, non mi resta che congedarmi. Alla prossima.