martedì 26 aprile 2022

FREEDOM FIGHTER SMOOTH SCROLLING (ITA)





Finalmente, dopo aver rinviato troppo a lungo, mi accingo a rivelare come funziona lo smooth scrolling di Freedom Fighter.

Poiché devo presumere che l'articolo verrà letto anche da persone che non conoscono bene il sistema MSX (e in questo caso specifico MSX1) dovrò ovviamente accennare anche a come è organizzata la memoria video (VRAM) di questo sistema (almeno nel modo video Graphic2 che è quello usato in Freedom Fighter).


Comiciamo con la nametable. La nametable è l'area dello schermo, quella in cui in tutti i sistemi (almeno quelli che conosco) sono “contenuti” i caratteri che appaiono sullo schermo. Lo schermo dell'MSX ha una risoluzione di 256x192 pixels. Per cui è formato da 24 righe, ognuna delle quali contiene 32 caratteri 8x8 pixel. In totale abbiamo quindi 768 caratteri, per cui la nametable è lunga 768 bytes.
La particolarità dell'MSX, rispetto ad altri computer, riguarda proprio l'uso di questi caratteri. In computer come il Commodore64 abbiamo un character set di 256 caratteri, liberamente posizionabili in qualsiasi punto dello schermo. Nel sistema MSX, invece, nel modo Graphic2 lo schermo viene diviso in 3 zone da 8 righe ciascuna. Ognuna di queste zone ha un proprio charater set di 256 caratteri (32x8=256) per cui lo schermo può essere riempito con 768 caratteri tutti diversi uno dall'altro (questo è particolarmente utile per le schermate in grafica bitmap).
Questo significa che per avere gli stessi caratteri in tutte e 3 le zone dello schermo dovremo ridefinirli 3 volte.
Gran parte della memoria VRAM è utilizzata proprio per memorizzare le forme dei caratteri e i loro attributi colore.
Ogni “blocco” di caratteri occupa 256x8=2048 Bytes. Quindi le definizioni di tutti e tre i blocchi di caratteri usano 6144 Bytes dei 16384 disponibili nella VRAM dell'MSX1. La tabella degli attributi colori ha esattamente la stessa lunghezza, perchè per ogni linea di 8 pixel di ogni carattere possono essere specificati 2 colori (primo piano e sfondo): poiché l'MSX1 ha solo 16 colori, con un singolo byte possiamo inserire entrambi (uno del nibble alto e uno nel nibble basso). Quindi anche le definizioni dei colori occupano 6144 Bytes. Insomma, 12 KB se ne sono già andati per le definizioni dei nostri caratteri (la comunità MSX li identifica come “tiles”).
Fortunatamente, una volta definite forme e colori ogni carattere avrà un codice univoco, per cui richiamando il carattere 0 (ad esempio) esso comparirà a schermo completo dei suoi colori.
Quindi per quanto concerne i caratteri, essi occupano (compresa la nametable) circa 13KB. I rimanenti 3KB contengono le definizioni dei 32 sprites hardware e i loro rispettivi attributi (ma non ne parlerò perché non è rilevante per la tecnica dello scrolling di cui sto per parlare).


Come molti di voi sapranno, l'MSX1 non possiede nessun tipo di ausilio hardware allo scrolling. Per cui, tutto quello che scorre sullo schermo è frutto di tecniche software: dagli scrolling a blocchi a quelli che in diversi modi hanno cercato di portare fluidità nello shift degli schermi delle macchine dotate di tms9918 (nelle sue varie incarnazioni) e compatibili (quindi non solo MSX ma anche Colecovision, Sega SG-1000 e TI99/4A, per dirne alcuni).


Prima di descrivere la mia tecnica personale vediamo quale siano quelle più comunemente utilizzata per simulare uno scrolling verticale su MSX. Diamo ovviamente per scontato che tutti e tre i blocchi da 256 caratteri siano definiti allo stesso modo, perchè i caratteri che sono nel primo blocco attraverseranno tutto lo schermo e passeranno quindi al secondo e terzo blocco...
La prima tecnica è quella più semplice. Si tratta ovviamente dello scrolling a blocchi (caratteri), quindi con lo spostamento di 8 pixel per volta. Come detto, lo schermo MSX è formato da 24 righe da 32 caratteri ciascuna:



Per spostare verso il basso il contenuto di tutto lo schermo bisogna spostare tutti i caratteri dalla prima riga alla penultima verso il basso e poi disegnare la prima riga:



Per velocizzare questa operazione è buona norma avere una copia della nametable in un buffer apposito in RAM dove compiere velocemente tutte le operazioni per poi copiarne il contenuto in VRAM in una volta sola. Eseguendo questa operazione molto velocemente lo scrolling può addirittura sembrare fluido (cito ad esempio Road Fighter della Konami).


La seconda tecnica, comunemente usata per avere uno scrolling verticale sufficientemente fluido su MSX è quella dello scrolling a passi di 2 pixel. In pratica, dobbiamo tenere memorizzati in VRAM i vari fotogrammi che compongono lo spostamento di un carattere verso un altro. In questo modo, ad ogni “passo” dello scrolling sostituiamo tutti i caratteri presenti nell'area di gioco con il loro fotogramma successivo (per eseguire lo scrolling completo di un carattere servono 4 fotogrammi). Dopo aver “sostituito” i caratteri 4 volte si ritorna al carattere di partenza e allo stesso tempo si effettua uno scrolling di 8 pixel.
Vediamo un esempio con la grafica di Freedom Fighter.


Prendiamo in esame i due caratteri evidenziati. Eseguendo uno scrolling verticale verso il basso, il carattere dell'acqua deve lentamente sostituire quello della riva. Per cui il carattere della riva verrà “spinto” verso il basso 2 pixel alla volta. A sua volta anche il carattere subito sotto (erba) subirà la stessa sorte ad opera del carattere “riva” e anche il carattere del mare verrà soppiantato da un altro carattere del mare.

Ma forse è più chiaro con un esempio visivo:


Come vedete, il carattere in alto (acqua) ad ogni frame viene sostituito con un altro in cui l'immagine è spostata verso il basso di 2 pixel e le sue prime due righe contengono gli stessi dati delle due righe più basse del carattere che si trova subito sopra (in questo caso altra acqua)
Allo stesso modo il carattere in basso (sponda) ad ogni frame è sostituito con la sua immagine spostata di 2 pixel verso il basso e le prime due linee contengono i dati delle ultime due del carattere più in alto.
Dopo 4 frames viene effettuato il classico scrolling a blocchi, per cui il carattere della sponda è completamente sostituito da quello dell'acqua e quello dell'acqua (in questo caso) da un altro carattere dell'acqua.
Facciamo un po' di calcoli: servono 4 frames per ogni carattere che deve scrollare, ovviamente correlati agli altri caratteri che gli possono essere posti sopra (lo scrolling avviane infatti tra 2 caratteri per cui se ne deve tenere conto).
Per cui 256/4=64 caratteri totali con cui costruire la grafica. Sempre che non ci servano numeri e lettere... E di solito ci servono! Quindi togliamo le cifre da 0 a 9. 64-10=54 caratteri. Poi togliamo le lettere 54-26=28 caratteri con cui costruire la grafica. Ci si può arrangiare e tenere solo i caratteri che compongono la parola SCORE, ad esempio e magari anche HI così possiamo avere anche HISCORE quindi 54-7=47 caratteri.
Da questi caratteri dovremo organizzare le “coppie” dei caratteri da scrollare fra loro... ne consegue che la grafica non potrà essere molto varia, ma per uno scrolling fluido qualche sacrificio lo si può anche fare.
Immaginate ora di voler realizzare uno scrolling fluido “al pixel”. 8 frames per ogni carattere? Lasciamo perdere, dai!


Oppure no. 

Ecco come ho affrontato la sfida per il mio Freedom Fighter.

Il primo passo è stato ovviamente decidere le coppie di caratteri con i quali avrei costruito la mappa (abbiamo detto che lo scrolling avviene sempre tra due caratteri, no?). Cominciamo col dire che ho scelto di tenere disponibili tutte le lettere dell'alfabeto (oltre ai numeri e alla punteggiatura). Dopodiché ho selezionato 30 coppie di caratteri, ottenute mescolando 14 caratteri unici. Pochi per avere un risultato sufficiente. Per cui ad un certo punto del livello il tileset (gli MSXisti usano il termine tile per riferirsi alle “mattonelle” che compongono la grafica) viene cambiato con un altro di 30 coppie di caratteri, alcuni dei quali sono in comune, per facilitare la transizione da un tileset all'altro.
Questi sono i due tileset del primo livello (3 righe di “coppie” per ogni tileset):



Non è un caso che vi mostri i caratteri in coppie. E' proprio da queste coppie che ha origine lo scrolling fluido di Freedom Fighter. Partendo da queste coppie, infatti, ridefinisco costantemente i caratteri in modo da “simulare” uno scrolling. E lo faccio in questo modo.

Il tile di partenza per lo scrolling è, tra i due, il carattere che sta sotto (quello che verrà “compenetrato” dal carattere sovrastante). Il lavoro che viene eseguito, per ogni scorrimento di un pixel (e per ogni coppia di caratteri) è questo:


Queste coppie di tiles sono memorizzate nella ROM della cartuccia (ma nella primissima versione della routine di scrolling erano memorizzate in RAM) una di seguito all'altra, prima le definizioni dei caratteri e di seguito gli attributi colore. Quindi da ogni coppia di tiles “estraggo” (puntando alla giusta locazione di memoria con un offset) gli 8 bytes della definizione del carattere e li scrivo in VRAM, in tutti e tre i blocchi caratteri (è infatti necessario che i caratteri siano gli stessi in tutti e tre i blocchi). Subito dopo ripeto l'operazione per gli attributi colore: estraggo gli 8 bytes corrispondenti dei 30 caratteri e li scrivo in VRAM in tutti e tre i blocchi.



Facendo un rapido calcolo, per spostare di un pixel lo schermo scrivo in VRAM 8x30x3x2=1440 bytes. Questo comporta qualche problema: il nostro MSX, infatti, non riesce a scrivere tutti quei dati in VRAM durante un solo frame. La primissima versione di questa routine, infatti, seppur fluida presentava diversi sfarfallamenti e lampeggi. Questo perché la ridefinizione dei caratteri non era ancora completata durante il passaggio del pennello elettronico, per cui in alcuni punti dello schermo era possibile vedere il “lavoro” di ridefinizione.

Per ovviare a questo inconveniente si è reso necessario implementare un double buffer: in pratica si trattava di ridefinire caratteri NON visualizzati sullo schermo per poi scambiarli una volta terminato il processo. La mia soluzione è stata utilizzare i caratteri dal 128 al 157. Quindi i caratteri “primari” sono 0-29 e i “secondari” 128-157.
La mappa del livello viene scompattata in RAM (occupando poco più di 9 K) prima dell'inizio del livello e i caratteri che la formano sono tutti appartenenti al range 0-29.
Ogni volta che il processo di ridefinizione dei caratteri viene completato, un flag viene aggiornato per farci sapere quali caratteri devono essere ridefiniti e quali mostrati. Per cui se dobbiamo mostrare i caratteri secondari ogni valore della porzione di mappa visualizzata sullo schermo viene letto e per ognuno viene effettuato un “xor 128” (ma avrebbe potuto anche essere una semplice somma) per puntare al carattere secondario corrispondente, prima di inviarlo allo schermo.
Una volta effettuato lo scrolling di 7 pixel gli offset di caratteri e colori vengono resettati e viene effettuato uno scrolling di un carattere. Anche in questo caso, avendo tutta la mappa in RAM, è sufficiente spostare l'offset dell'area da copiare nella nametable di 32 bytes indietro (la mappa è memorizzata così come si vede a schermo).



Quando è necessario cambiare tileset, per aggiungere varietà, si cambiano semplicemente gli offset iniziali puntando alla zona della ROM in cui è memorizzato il tileset secondario.
Spero che l'articolo non vi abbia annoiato a morte, ma vi sia invece di stimolo a creare una vostra tecnica personale o, perché no, a migliorare questa!
A breve (01/06/2022) dichiarerò Freedom Fighter come freeware e ne pubblicherò il codice su Github. Siate buoni quando lo esaminerete, ho imparato a programmare in assembly creandolo!

7 commenti:

  1. Congratulazioni, Giuseppe!
    Non sarei mai in grado di fare quanto hai fatto tu per due motivi: la mia piattaforma di riferimento era il C64 (ci capivo comunque troppo poco, anche se qualche piccolo effetto di scrolling di caratteri lo riuscivo a fare; restavo in ogni caso sempre infinitamente lontano da quello che vedevo fare dai nordeuropei!) e non conosco MSX; tuttavia ho capito diciamo al 90% quello che mettevi in pratica. Ho letto con interesse il tuo post e ti manifesto tutta la mia stima! 💪💪💪

    RispondiElimina
    Risposte
    1. Ti ringrazio immensamente per il commento. Fa piacere sapere di aver scritto in modo abbastanza chiaro da far capire cosa si è fatto, soprattutto quando sai benissimo di aver usato delle tecniche cervellotiche :)

      Elimina
  2. Ciao Giuseppe, se non ho capito male, copi i tiles nei 3 set. E qui viene il mio dubbio: Non basta impostare le schermo in screen2, quindi 256 tiles in totale e lavorare solo su un unico set?

    RispondiElimina
  3. scusa intendevoi questo settaggio per avere un unico set di 256 char per tutto lo schermo:
    10 SCREEN 1
    20 VDP(0)=VDP(0) OR 2
    30 VDP(1)=VDP(1) AND &HE7
    40 VDP(3)=&h9F:VDP(4)=0

    RispondiElimina
  4. Purtroppo la soluzione che proponi non è attuabile per diversi motivi. Il primo motivo è che non è compatibile con tutti gli MSX. Infatti funziona solo sui vdp Texas Instruments. Su quelli Toshiba non funziona affatto.
    E se anche funzionasse c'è un altro grossissimo problema. Questa "modalità " non ufficiale purtroppo ha un bug: oltre a replicare la definizione dei tile in tutte e tre le zone (infatti non riduce il numero dei tileset, semplicemente effettua un mirroring) effettua anche un mirroring dei primi 8 sprites a video su tutte e tre le zone. Per cui ci si dovrebbe limitare ad usare solo gli sprite da 8 a 31 (p e 7 sono fuori gioco). Questa "conseguenza" si può usare nelle demo.competition perché apparentemente permette di mettere su schermo 24 sprites (8x3) + 24 sprites non moltiplicati (quindi 48 oggetti su schermo invece che 32). Il problema è che NON è una cosa su cui fare affidamento, perché se il vdp è torchiato a dovere e inizia a scaldare il mirroring scompare.
    E, ripeto, non è compatibile con tutti i modelli msx perché non replicabile su vdp toshiba.
    A questo proposito è possibile utilizzare il mirroring NON per la definizione dei pattern ma per i colori. Il mirroring dei colori NON causa mirroring degli sprites. Ma resta sempre l'incompatibilità con i vdp Toshiba. Puoi vedere ad esempio che Uridium per MSX1 ha nella schermata principale proprio la scelta per vdp Toshiba perché usa il mirroring delle tabelle colori per migliorare le performance. Io non ho voluto farlo perché volevonche il gioco avesse le stesse prestazioni su tutti gli msx

    RispondiElimina
  5. Sono alle prese con un esperimento di scroll fluido.
    In pratica ho questa configurazione:

    Pattern e Color da 0 a 127 (nei 3 banchi)
    Color e Pattern da 128 a 255 (nei 3 banchi), quindi con Pattern e Color scambiati rispetto al set precedente

    Valorizzo il set da 0 a 127 e visualizzo il set da 128 a 255
    Scambio Pattern e Color tramite registri
    Valorizzo il set da 128 a 255 e visualizzo il set da 0 a 127
    e così via

    Poichè valorizzo un set ma nè visualizzo un altro, secondo te devo sempre attendere il VBLANK?
    oppure è sufficiante farlo solo quando vado a visualizzare?

    Grazie ancora per il supporto

    RispondiElimina
    Risposte
    1. Puoi tranquillamente farlo fuori dal vblank. La routine di scrolling di Freedom Fighter lavora fuori dal vblank proprio perché l'area di lavoro non è visualizzata. È a tutti gli effetti una tecnica di double buffering

      Elimina