Objectiu
Vols saber com veure les imatges al MSX? Necessites carregar un dibuix com a pantalla inicial del teu joc? O potser una pantalla d'algun nivell?
Tot això ho podràs fer seguint aquest post. Veurem com podem transformar una imatge generada amb una altra plataforma usant un programa de dibuix i poder-ho visualitza al MSX.
Aquest tutorial servirà per fer-ho a través del MSX-DOS i utilitzant els modes de pantalla del MSX del screen 5 ó 7 (així és com es criden en el basic, i són coneguts normalment, però en el MSX-Technical book els anomenen Graphic 4 i Graphic 6).
Com funciona el video al MSX?
La part que s'encarrega de generar les imatges al MSX és el VDP. A la primera generació s'utlitza el TMS9918 amb 16K de memòria, en el MSX2 es passa al Yamaha V9938 amb 128K, més colors i scroll vertical; i finalment el MSX2+ i Turbo-R que tenen una versió millorada que és el V9958 que afegeix scroll horitzontal. Després de les versions oficials, va aparèixer el V9990 (que diuen que és el que haurien d'haver anat al MSX Turbo-R) que té molts més colors, 512K de memòria RAM i scroll multicapa.
Segons el mode de la pantalla que estigui operant el VDP interpretarà el que té en la seva memòria (molts cops dita VRAM) com els colors de pixels o els colors de patrons.
En aquest capítol ens centrarem en la càrrega dels screen 5 ó 7 que tenen un ús de la VRAM a nivell de pixel. En aquests modes de pantalla, es poden mostrar 16 colors diferents a la vegada d'una paleta de 512 colors possibles. Les dades que es guarden a la memòria és l'índex de color de cadascun dels pixels.
A la imatge següent hi ha un esquema basat en un gràfic del MSX-Technical Handbook:
La part esquerra correspon a la memòria de video (VRAM) i la dreta la seva correspondència amb la imatge que es genera a la pantalla. Així tenim que el primer byte de la pantalla conté la informació dels pixels (0,0) i (1,0). Per indexar 16 colors, necessitem només 4 bits. La part més alta del byte conté el pixel de més a l'esquerra i la part baixa el de la dreta. Tal i com indiquen les fletxes. En total podem mostrar 256 * 212 pixels. Per pintar-los necessitem 256 * 212 / 2 = 27136 bytes (l'esquema apareix fins al 27135 ja que comença a comptar des de 0), ja que cada byte conté dos pixels.
Els índex de colors es defineixen usant el registre #16 i es guarda a la memòria a l'adreça 0x7680 fins a la 0x76AF per a l'screen 5 i entre la posició 0xFA80 i 0xFAAF per l'screen 7.
Afortunadament el Fusion-C té una comanda per definir la paleta que és
void SetPalette((Palette *) mypalette)
a on li passem un array amb tots els colors; o utilitzem la funció
void SetColorPalette(char ColorNumber, char Red, char Green, char Blue)
que ens permet modificar un sol color en lloc de tota la paleta.
Com indiquem quin fitxer del disquet s'ha de carregar?
A l'apartat anterior hem vist com funciona la part de la memòria de vídeo. Ara ens queda veure com llegim els bytes d'un fitxer que està al disquet. D'aquesta feina s'encarrega el MSX-DOS.
El MSX-DOS és una adaptació del CP/M per al MSX, necessita d'una ROM pròpia que aporta les funcions de BDOS i el MSX ha de tenir com a mínim 64Kb.
Per llegir fitxers, el MSX-DOS defineix el FCB (File Control Block) que és una estructura de memòria que conté diferents camps que utilitza el MSX-DOS per poder manipulars les pistes del disquet i trobar els bytes que formen el fitxer designat. El Fusion-C ja defineix el FCB i per llegir només cal que indiquem el nom del fitxer.
Com passem una imatge de PC a bytes que puguin ser llegits per l'MSX?
Hi ha una aplicació multiplataforma que es diu MSX Viewer que carrega una imatge des de PC i la converteix en diferents formats per l'MSX, depenent de la pantalla que es vulgui utlilitzar. També fa la conversió inversa, carrega una imatge en format MSX i la guarda en un format comú avui en dia (bmp).
Jo utilitzo un script en python que converteix una imatge en format PC a una imatge en screen5 (ó screen7). No retalla la imatge, només converteix els colors, per tant, la imatge original ha d'estar en la resolució corresponent. Aquest script utilitza la llibreria opencv i l'algoritme de K-Means per reduir la imatge a 16 colors. Aquest script el podeu trobar en el següent gitlab.
Codi per carregar imatge a VRAM
Ja estic cansat de tanta teoria. Podem veure el codi d'una vegada? I tant, aquí el teniu. Aquest codi es pot trobar al gitlab de MoltSXalats.
Les línies de 5-7 contenen els includes que utilitzarem en aquest fitxer. Les següents línies fins al 14 són les variables que utilitzarem. Quan es compila amb sdcc (al menys fins la versió 4.1), és millor utilitzar variables globals que no pas locals, ja que utilitza menys línies de codi. Això ho vaig poder comprovar amb el joc Bricks, el text de l'scroll de la pantalla inicial, vaig definir els arrays de caràcters dins la funció de pintar el text i el temps de compilació i el tamany resultant del fitxer van créixer enormement. Després les vaig treure i les vaig posar en global i va tornar a la normalitat. No dubteu en provar-ho si teniu un moment.
Bé, tornant al codi que ens ocupa, tenim la variable file que és de tipus FCB i que coincideix amb l'estructura del File Control Block que hem explicat anteriorment i que servirà per a que el DOS guardi la informació del fitxer. Després definim un buffer, que és el que anirem omplint amb les dades llegides del disquet per poder passar-les de la RAM a la VRAM. L'hem definit de tamany de 20 línies de l'screen 5: cada línia són 256 pixels, cada pixel són 4 bits i 8 bits fan un byte; així tenim 20*256*4/8 = 2560 bytes (un char equival a un byte). Aquest número l'he escollit perquè és el que hi havia a l'exemple de Fusion-C LoadScreen5Image.c. Però no he fet cap avaluació comparativa amb altres quantitats per veure quin va més ràpid. Després tenim la variable mypalette que serà l'encarregada de guardar els 16 colors i té l'estructura que necessita la funció SetPalette del Fusion-C.
Del 16-34 tenim la funció FT_SetName que s'encarrega d'omplir els camps del FCB amb el nom del fitxer a processar. Deprés fins a la línia 57 tenim el control d'errors del DOS FT_errorhandler.
I ara ja arribem a la funció FT_LoadSc5Image que s'encarrega de llegir del disquet i passar-ho a la VRAM. Té com a paràmetres el nom del fitxer que volem carregar, la coordenada vertical d'on volem començar, el buffer a on anirem guardant les dades i l'amplada de línia (que serà de 256 per screen 5 ó de 512 per screen 7) i el tamany del buffer anterior.
Definim la variable rd que contindrà els bytes llegits del disquet i la inicialitzem amb un valor diferent de 0. Posem el nom del fitxer al FCB i intentem accedir a ell amb la funció fcb_open del Fusion-C. Si ha donat error processem aquest missatge i acabem.
A la línia 71 llegim els primers 7 bytes, però no els utilitzem per res, ja que tant el binari de la imatge obtingut amb l'script o amb el del MSX Viewer contenen aquests bytes que no són informació de pixels de la imatge.
Les línies 75-80 contenen el bucle que anirà llegint del disquet i passant-ho a VRAM. Aquest bucle s'anirà fent fins que ja no llegim més bytes del disquet (rd). Ara sí que llegim a la línia 77 tants bytes com tamany té el buffer, si el fitxer no és múltiple de tamany_buffer, l'última lectura retornarà a rd un valor menor, i a la següent ja serà 0 i acabarem el bucle. Un cop tenim els bytes al buffer, la funció HMMC carrega tot de bytes de la RAM a la VRAM, primer li indiquem a on es troben aquests bytes, després la coordenada X origen (sempre serà 0 perquè hem fet múltiples de línia), la coordenada y que anirà variant de 20 en 20 (que és el tamany de línia que havíem llegit) i finalment li passem l'amplada i l'alçada del bloc de la imatge a copiar, en aquest cas els 256 de l'screen 5 i les 20 línies d'alçada.
La següent funció, LoadPalette, és l'encarregada de passar la paleta de colors al VDP, té un funcionament semblant a la de carregar imatge, però aquí en comptes de passar de la RAM a la VRAM, carreguem un vector RAM I cridem la funció del Fusion-C LoadPalette que s'encarrega d'omplir els registres del cep amb els valors RGB corresponents. Comencem indicant al DOS el nom del fitxer que volem carregar, fem la detecció d'errors i ens saltem els primers 7 bytes que no aporten informació que ens interessi i després carreguem els 24 bytes amb la informació RGB de la paleta a un array.
Com que 24 bytes? 16 colors per 3 components de color (RGB) fan 48 i no pas 24! Teniu raó, però s'ha de tenir en compte que l'script en Python comprimeix aquesta informació, ja que cada component pot prendre valors del 0 al 7 i per això només són necessaris 3 bits, aprofitant doncs per cada byte la definició de dues components de color. Així doncs si tenim la definició de 48 components necessitem 24 bytes.
Les línies de 98 a 100 s'encarreguen de passar d'aquests 24 bytes a 48 per després passar-ho al format que necessita el Fusion-C a les línies 102-107, i cridem la funció SetPalette que escriurà els registres del VDP amb la definició dels colors.
La funció FT_LoadPalette_MSXViewer serveix per carregar la paleta obtinguda amb el MSX Viewer, comença com totes les que llegeixen dades del disquet, definint el nom del fitxer i obrir-lo per lectura. A diferència de l'anterior funció, aquí hem de saltar més bytes perquè el MSX Viewer genera dos fitxers, un amb els índex de colors dels pixels (igual que l'obtingut amb l'script) i el de la paleta és un binari per ser executat un cop carregada la imatge. Estudiant el fitxer generat, ens n'adonem que la informació de la paleta comença a partir del byte 0x30 (48 en decimal) i que no està comprimida, amb el que el bucle d'adaptació és molt més senzill (línies 145-148). Finalment cridem la funció SetPalette per guardar la paleta al VDP.
Abans de la funció main tenim una funció que només s'encarrega de cridar les imatges. Si la imatge hagués estat cread amb l'script de python, hauríem d'utilitzar la funció FT_LoadPalette amb els mateixos paràmetres que té la funció FT_LoadPalette_MSXViewer a la línia 158. La línia 159 és innecessària.
I ja tenim la funció main que escull el mode de pantalla, crida la funció que carrega imatges, espera una tecla i acaba, retornant a la pantalla de mode escriptura (screen 0), canviant la paleta a la dels colors original i retornant els valors als registres amb Exit(0).
Comentaris finals
Pàgines VRAM
Quan executeu el programa, veureu que va carregant la imatge a trompicons, aquests són els passos de carregar a memòria i després posar a visualitzar. Per evitar-ho, es pot carregar la imatge a una pàgina oculta de la VRAM, indicant que en lloc de començar a la y=0 comenci a la y=256.
Però què és això de les pàgines de la VRAM? Els pixels de la imatge a mostrar per l'MSX és de 256 * 212. Cada pixel es guarda en 4 bits (1/2 byte) amb el que tenim 256*212/2 = 27136 = 0x6A00 bytes. El VDP dels MSX2 té 128Kb (131072 = 0x20000 bytes) amb el que hi caben 4 pàgines en memòria (27136*4= 108544) < 131072. Un cop tens una pàgina plena amb els valors dels pixels, es pot indicar al MSX que mostri aquella pàgina, en basic era la comanda set page, però ara en C tenim la funció SetDisplayPage.
El següent gràfic mostra un esquema de les pàgines i les adreces de VRAM utilitzades pel VDP (extret de la wiki del msx.org):
Un altre lloc a on està molt ben explicat és en el manual del Fusion-C. A on també apareix la zona de la pàgina 0 a on es guarda la informació dels sprites i de la paleta. Podeu comprovar que les altres pàgines tenen més bytes que no pas pixels a mostrar. Aquests pixels queden ocults i es poden utilitzar per guardar imatges de l'animació del personatge principal, o lletres per fer algun rètol, etc.
Clica aquí per a veure l'exemple funcionant.
Comments