Lo schermo del C64 e il raster

0scur0 22-03-2021.
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:

schermo del c64

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.

Commenti
Commenta gioco Ci sono 3 commenti per questo articolo. Registrati se vuoi lasciarne uno.
Finalmente un articolo che riassume tutte le basi dell'argomento e che rappresenta un buon punto di partenza per affrontare questa vasta tematica. Per i più volenterosi, segnalo anche questo thread sul forum, la cui lettura certamente risulta molto meno scorrevole, ma da cui si può estrapolare qualche informazione in più: LINK Buon raster a tutti!
# - postato da eregil - 23 March 2021 [01:31]
Salve, sappiamo che per una IRQ Routine vettorizzata in RAM tramite $0314-$0315 il jitter (ossia l'incertezza di ritardo) è 6 in quanto riveniente dalla differenza tra 38 cicli (caso migliore) e 43 cicli (caso peggiore), invece per una IRQ Routine vettorizzata in ROM tramite $fffe-$ffff, qual è il ritardo sia nel caso peggiore che in quello migliore e qual è, dunque, il jitter? Grazie a tutti in anticipo per l'eventuale risposta.
# - postato da Attila - 09 May 2022 [07:04]
Ciao, Se dai un'occhiata al disassemblato delle ROM noterai, che la routine puntata a $ff48 è proprio quella vettorizzata in $fffe-$ffff, quindi il jitter all'ingresso di questa routine si calcola sottraendo al numero di cicli calcolati nell'articolo il numero di cicli richiesti per la routine stessa, ovvero 2+7=9 nel caso ottimo e 1+7+7=15 nel caso pessimo.
# - postato da 0scur0 - 09 May 2022 [18:30]
Commodore 64
Pannello Utente
289 Visitatori, 0 Utenti

Ciao, ospite!
(Login | Registrati)
Merchandising
Ultimo Commento
Clicca per leggere tutti i commenti
Clowns
Uno dei miei primissimi giochi, con tanto di paddles per controllare meglio il tutto! Grafica e sonoro semplicissimi ma tanto divertimento!
leggi »
Re Tucano
Articolo
Intervista a Luciano Merighi (Merifon)
Ottimo articolo… solo una nota all’Autore dell’articolo… tra i programmatori bolognesi “pionieri” c’ero anche io: Andrea Paselli! Con Luca Zarri e Marco Corazza ho realizzato Chuck Rock, Over The Net, Mystere, Halley Adventure… ...
Andrea Paselli
Superlink