Come posso creare una demo grafica senza frame buffer?

Aug 18 2020

Sto programmando un microcontrollore - un minuscolo computer a 8 bit che ha circa 100 Mhz - 300 Mhz di velocità di clock della CPU e 64K di RAM dell'applicazione (in realtà ROM), ma solo una piccola quantità di RAM (6K) - non abbastanza vicino per un frame buffer grafico.

Quindi, voglio visualizzare una demo grafica animata su questa macchina. Sembra avere abbastanza potenza di calcolo per calcolare qualche tipo di grafica animata.

Per visualizzare la grafica animata, in qualche modo avrò bisogno di un programma per computer che ogni volta che viene richiamato restituisca una singola riga di pixel di un fotogramma dell'immagine, che viene quindi visualizzata. Il programma della scheda video continua a farlo, chiamando riga dopo riga fino a quando non ha visualizzato un intero fotogramma, quindi ricomincia dall'inizio per il fotogramma successivo.

Giusto per essere chiari, non sto chiedendo del programma che controlla il display. Sto chiedendo del programma demo. Ho bisogno di un programma demo che produca la sua grafica una riga alla volta, che la mia scheda video lancerà sullo schermo.

Sto cercando di immaginare che tipo di programma di grafica potrebbe funzionare in questo modo? Presumibilmente la visualizzazione di una sorta di formula matematica.

Qualcuno può suggerire tecniche per la programmazione grafica che funzionano in questo modo, in cui può essere chiamata una funzione, che calcola e restituisce una linea del frame, solo quando richiesto? Esistono programmi esistenti che funzionano in questo modo che potrei esaminare?

Spero che la domanda abbia un senso. Eventuali suggerimenti nella giusta direzione o suggerimenti sarebbero apprezzati.

Risposte

4 NicolBolas Aug 18 2020 at 22:55

Questo è un caso in cui sarebbe ragionevole guardare al funzionamento di hardware più retrò . L'hardware più vecchio aveva forti limitazioni sia nella memoria che nella potenza di elaborazione. Il tuo chip da 100+ Mhz è molto più veloce della maggior parte dei chip consumer degli anni '80 e precedenti. Quindi, mentre quelle CPU necessitavano di chip grafici dedicati per essere in grado di eseguire il rendering, la tua CPU molto più veloce probabilmente può gestire adeguatamente l'attività.

Suggerirei di iniziare con qualcosa come l'architettura tilemap-and-sprite del NES. Queste architetture sono progettate per essere sia efficienti in termini di memoria (ottenendo molto dallo spazio di archiviazione limitato) sia computazionalmente efficienti in un output riga per riga, poiché hanno generato dati pixel e li hanno inviati direttamente al dispositivo di visualizzazione alla velocità prevista di un CRT.

L'idea generale è questa. Una mappa delle tessere è composta da due parti: una serie di tessere e un array 2D di indici nella mappa delle tessere che rappresenta l'immagine da creare. Le tessere sono in genere 8x8 e sul NES erano 2 bit per pixel e le tessere utilizzavano tavolozze. O più specificamente, l'indice nella mappa delle tessere include non solo l'indice di una tessera ma anche l'indice della tavolozza da utilizzare con quella tessera. Quindi una tessera non è intrinsecamente associata a una tavolozza; l'associazione viene effettuata al punto di utilizzo (tecnicamente, sul NES, la mappa delle tessere e la mappa delle tavolozze erano separate, poiché ogni blocco 2x2 di tessere doveva utilizzare la stessa tavolozza).

Per consentire lo scorrimento, la mappa delle tessere è più grande dello schermo visibile e c'è un offset che rappresenta il punto in cui si trova l'angolo superiore sinistro dello schermo visibile all'interno della mappa delle tessere. Lascia che questo offset sia in posizione Xoffe Yoff.

Puoi vedere come questo renda semplice l'elaborazione riga per riga. Per generare una riga orizzontale per la posizione orizzontale Ypos(nello spazio dello schermo), è necessario ottenere il pixel iniziale all'interno della tilemap. Ciò richiede la trasformazione della posizione XY (0, Ypos)dallo spazio dello schermo allo spazio della tilemap. Quindi aggiungi ad esso il vettore (Xoff, Yoff), impugnando il vettore `` Xoff, Yoff + Ypos) `.

Si noti che quando si esegue una mappatura dallo spazio dello schermo allo spazio della tilemap, lo spazio della tilemap dovrebbe avvolgere attorno agli assi X e Y. Quindi ogni volta che si calcola un nuovo pixel nello spazio tilemap, è necessario avvolgerlo attorno alla dimensione dello spazio tilemap.

Ora, dobbiamo suddividere questo pixel di tilemap in due componenti 2D: l'indice di tile all'interno della tilemap che ci fornisce questo pixel e il pixel all'interno di quel riquadro che dobbiamo recuperare per questo pixel. L'indice delle tessere è solo il numero intero di pixel dello spazio della mappa delle tessere diviso per la dimensione delle tessere. La coordinata del pixel è il pixel dello spazio della tilemap, modificato dalla dimensione del riquadro. Data una dimensione della piastrella 8x8, stai facendo questo:

ivec2 tilemap_pixel = ...; //Compute the tilemap starting pixel as above.
ivec2 tilemap_tile = ivec2(tilemap_pixel.x & ~0x7, tilemap_pixel.y & ~0x7); //Mask off the lower 3 bits.
ivec2 pixel_in_tile = ivec2(tilemap_pixel.x & 0x7, tilemap_pixel.y & 0x7); //Mask off all but the lower 3 bits.

Quindi tilemap_tile, ora abbiamo l'indice della piastrella su cui stiamo lavorando. E pixel_in_tileci dà il pixel su cui stiamo lavorando. Quindi possiamo recuperare quel pixel, il cui valore può essere mappato in una tavolozza per produrre il colore finale per quel pixel. Oppure possiamo usarlo direttamente.

Ottenere il pixel successivo è piuttosto banale. Incrementiamo semplicemente pixel_in_tile.xdi 1 modulo la dimensione della piastrella. Se l'incremento ha superato la dimensione della piastrella, incrementiamo anche tilemap_tile.xdi 1 modulo la dimensione della piastrella. E vai avanti finché non hai riempito la riga di pixel.

Esistono molte opportunità per l'ottimizzazione delle prestazioni di un tale algoritmo.

Le tessere rappresentano probabilmente i dati più grandi. Un set di 128 tessere di 8x8 tessere, anche a 2 bpp, è 2K. Ma il fatto è che questi possono essere memorizzati nella ROM , poiché probabilmente non stai cambiando le tessere stesse. La dimensione della mappa delle tessere (supponendo che debba essere nella RAM) dipende dalla risoluzione di output desiderata e dalla dimensione delle tessere. Una mappa delle tessere per tessere 8x8 che possono coprire uno schermo 320x240 è di 1.200 byte. Non esattamente minuscolo. Avrai bisogno di occupare più memoria se vuoi spazio per uno scorrimento fluido (e quindi una mappa di tessere più grande).

Detto questo, è anche importante notare che la "dimensione dell'output" non deve essere la dimensione effettiva del dispositivo di visualizzazione . Ad esempio, se intendi disegnare su un dispositivo di visualizzazione 1080p, puoi comunque eseguire il rendering con una risoluzione interna che è, ad esempio, 8 volte più piccola, 240x135. È facile modificare questo algoritmo per generare essenzialmente lo stesso valore di pixel 8 volte di seguito, oltre a riutilizzare la stessa riga 8 volte per riga 1080p. In effetti, non sarebbe troppo difficile che questo algoritmo consenta lo scorrimento dei subpixel (scorrimento nello spazio 1080p, non nello spazio 135p) e persino l'aggiunta di un filtro tra i valori dei pixel. Una tilemap "dimensione di output" 240x135 richiederebbe solo 510 byte per la regione visibile, quindi si avrebbe più spazio per un'area di scorrimento più ampia.