Cyberdecks

Reference / Paper

Graphics in the PicoMite VGA — MMBasic Graphics Paper

Read the PDF ↗

Author
Volhout (The Back Shed forum)
Type
Reference / Paper
  • picomite vga
  • mmbasic
  • graphics
  • framebuffer
  • layers

← Back to the Reference Library

Graphics in the PicoMite VGA — MMBasic Graphics Paper

Graphics in the PicoMite VGA How Graphics can be used in MMBasic on the PicoMiteVGA Written by: Volhout on The Back Shed Forum Revision 3.3 Introduction The PicoMite is a standalone computer running MMBasic. The full story can be read in: https://geoffg.net/picomitevga.html The base for these computers is the Raspberry Pi Pico, a small module based on the RP2040 or RP2350 chip. This paper tries to explain how graphics can be used in MMBasic in the PicoMite. Most of this is transparent between the different flavors of PicoMite, but is essentially based on the PicoMite VGA. Reference is given to PETSCII ROBOTS game, that uses the presented method. Graphics layers To the user, the video screen shows as a 2D object, a flat surface. That is called a layer. To the programmer the video screen is a piece of memory. That is called a (frame)buffer. These terms are used in the below text, and refer to the same. Layer N The PicoMite starts up using only 1 graphics layer (layer N -or- framebuffer N). In the VGA version this is your visible screen, when you use an LCD, this is the LCD screen. If you draw a line or print text to the screen, it is on layer N. It is Important to understand that the difference between the VGA and LCD displays is that the N layer in an LCD is INSIDE the LCD itself, in its native color depth. For an ILI9341 this is 16 bit color (RGB5:6:5). The VGA version keeps the information inside the RP2040 chip’s memory, and this is 4 bit resolution (RGB1:2:1). Page 2 PicoMite Graphics Framebuffer F If you write to the screen often, it is possible you will see screen artefacts. To avoid these artefacts you can do all the writing to an intermediate buffer (FRAMEBUFFER F), and when you are ready doing all the graphics update you can copy the framebuffer F to the N layer (FRAMEBUFFER COPY F,N). Note that framebuffer F is inside the RP2040 chip and is RGB 1:2:1. If you use an LCD the PicoMite converts RGB1:2:1 to RGB 5:6:5 automatically. Framebuffer F uses 38kbyte of RAM. Framebuffer F is never directly visible. It is just a memory block that can contain RGB 1:2:1 data. Layer L / Framebuffer L Especially for gaming it is nice to have multiple graphics layers. PicoMite offers one layer L on top (overlaying) the N layer, called Framebuffer L. When you use layer L, it is visually in front of layer N, and can obstruct the vision to layer N. For that reason, layer L is featured with a “transparent color”. If you paint layer L with that transparent color, you see the graphics of layer N. If you use any other color, you see the pixels of layer L. The transparent color is default RGB(BLACK), but can be altered to any of the 16 colors the PicoMite supports. RGB(Magenta) is often used. FRAMEBUFFER L is physically inside the RP2040/2350 chip, and is 38kbyte in size. In the VGA version, merging of N and L is automatic. Use of framebuffer F is optional. In the LCD version (remember, both L and N are in a different chip) merging cannot be done between N and L. The LCD version does the merging on framebuffers F and L, and then writes the result to the physical display N. Framebuffer N is available by default. Other framebuffers can be enabled with : FRAMEBUFFER LAYER (for L) FRAMEBUFFER CREATE (for F). Not all framebuffers are available in all video modes. This tutorial uses mode 2 (320x240 in 16 colours). Graphical elements In essence there are 2 different graphical formats that can be used in the PicoMite: Tiles and Sprites. Throughout this tutorial these names are used. A tile is a rectangular graphical object that is in native color space (i.e. RGB 1:2:1). It can be copied onto any layer and is directly visible. A tile can be small, or screen size. The PicoMite can load a tile from a storage device (SD card) with LOAD IMAGE “filename.bmp” and display it on the active layer. As a bonus, LOAD IMAGE will also convert 24 bit RGB to RGB1:2:1 on the fly. The file must be BMP format. PicoMite Graphics Page 3 Some tiles from PETSCII robotsP A Sprite is a rectangular graphical object that consists of color indexes (indices). Each pixel is not represented by the a RGB value, but by a palette index. This can be very confusing for the PicoMite since it supports 16 colors (RGB 1:2:1) but also happens to support 16 color indexes in the sprite file format. And these are NOT the same for reason of backward compatibility to CMM1, CMM2. The sprite data in a file cannot be copied onto the screen directly. It must be converted to RGB values. Sprite data in a file consists of a header, indicating dimensions of the sprite (i.e. 16x8 pixels) and then a list of (i.e. 128(16x8)) color indexes. Sprites are powerful elements, they can restore the background when moved, there is collision detection when they move. The PicoMite now supports 64 sprites (increased from 31 in the 5.09.00b0 beta release) Sprites must be loaded into memory with SPRITE LOAD “filename”,sprite_number. After that the sprite can be displayed with SPRITE WRITE sprite_number,x,y (hard write) or SPRITE SHOW sprite_number,x,y (remembers background) and SPRITE MOVE. Some sprites with Magenta background from PETSCII robots. Memory The RP2040 /2350 has a limited amount of RAM that must be shared between system, video, variables, stack, buffers (i.e. audio) etc. Therefore provisions exist to also use flash memory for MMBasic programs. Page 4 PicoMite Graphics The flash banks can be used to store MMBasic programs, and even execute from them. You can CHAIN programs. Details are beyond the scope of this document. Just know they exist. MMBasic also supports a LIBRARY. The exact same flash memory block that is reserved for BANK3 can be used as an extension of the program memory, with limitations. The limitation is that you cannot edit the program in the library. You can save CSUB’s, SUB’s and FUNCTIONS in the library that are “bug free” and use them from the main program. Because the library is active when the MMBasic program runs, you can only access the library from the command line. If you have debugged code in your program memory, transfer it to the library with LIBRARY SAVE. LIBRARY LIST shows what is in the library, and LIBRARY DELETE erases the library and returns the flash memory to flash bank3. CSUB Some of you may remember that in the 80’s there where basic programs with lots of DATA statements at the end, that no-one knew what they meant, but that were Poked into memory, and then executed (CALL xxx). In essence that was machine code represented as hexadecimal numbers. A CSUB is exactly that. The CSUB contains in hexadecimal form machine code instructions for the ARM processor. MMBasic is a bit more advanced, in that you do not need to remember where to POKE, and what to CALL. Poking is done automatically when you run the program, and by just using the name of the CSUB, the CALL xxx is executed. Concluding: at RUN time, any CSUB hexadecimal code is written into memory in binary form, to prepare it for execution by the ARM. We are using that feature, but do not execute the binary, instead use it for blit. Similar when you LIBRARY SAVE a program that contains a CSUB, the binary data is written in the library (not the hexadecimal text representation). Implement Graphics: preparations We now have the knowledge to understand the graphics used in advanced games as PETSCII ROBOTS. PETSCII ROBOTS is (for MMBasic) a large game. The basic program is only 70kbyte, but it uses data (maps, graphics, sounds) that are over 800kbyte in size. PicoMite Graphics Page 5 The game uses over 100 sprites, and roughly 300 tiles. With the limitation of 31 Sprites maximum, when the game was written, the above knowledge was used to implement the game. Below are game design decisions that were taken to make the game fit in the PicoMite. 1/ Use only framebuffer L in the VGA version, to save RAM for variable storage (maps/attributes). This has the disadvantage that you may see minor corruptions in the video while updating the screen. Provisions are made in the program to minimize this by synchronizing heavy screen updates with vertical sync, using FRAMEBUFFER WAIT. To make this effective, it is essential to execute all code that writes to video memory close together. Do not litter the whole code with writing to video memory. PETSCII uses 2 SUBs, executed every game loop. One that writes everything on the N layer, and a second that writes everything on the L layer. And these SUBs are tuned for speed. 2/ Store all graphical elements (tiles/sprites) in binary format. MMBasic has highly optimized memory copy routines from flash memory to video RAM in BLIT MEMORY. The BLIT MEMORY function is fast, but cannot restore backgrounds or detect collisions of sprites. The detection of collisions is performed in MMBasic on the basis of X and Y coordinates of tiles and sprites. Restoring of the background can be avoided by using 2 layers. The N layer for the world (world map) built using tiles. Use the L layer, with magenta as transparent color, to write the sprites. When they move, the N layer is not affected. Store Sprites and Tiles in binary format. When running a CSUB, or saving it to the library, the hexadecimal data is converted to binary form. Matherp has written a basic program that converts SPRITE files (containing header and color indexes) into header+hexadecimal RGB 1:2:1 data mimicked as a CSUB. 'program to convert all the sprite files in a directory to a CSUB using compression where appropriate Option explicit Option default none Const separatesubs% = 0 Dim offset% Dim fname$=Dir$("*.spr",FILE) Open "tile0_csub.bas" For output As #2 Open "tile0_index.txt" For output As #3 If separatesubs%=0 Then Print #2,"CSUB TILE0" Print #2,"00000000" offset%=0 EndIf Do If fname$<>"" Then code fname$ fname$=Dir$() Loop Until fname$="" If separatesubs%=0 Then Print #2,"END CSUB" Page 6 PicoMite Graphics Close #2 Close #3 'convert the file f$ to a compressed CSUB Sub code f$ Local i%,j%,h%,l%,w%,n%,s%,il% Local a$,o$,oc$ Open f$ For input As #1 Line Input #1,a$ 'process the dimensions and count w%=Val(Field$(a$,1,",")) n%=Val(Field$(a$,2,",")) h%=Val(Field$(a$,3,",")) If h%=0 Then h%=w% i%=Instr(f$,".") o$=Left$(f$,i%-1) If separatesubs%=1 Then Print #2,"CSUB "+o$ Print #2,"00000000" offset%=0 ' Else ' Print #2,"'"+o$ EndIf Local obuff%(w%*h%\8+128),buff%(w%*h%\8+128) For s%=1 To n% 'process all the sprites in a file Print #3,Str$(offset%) Print #2,"'Offset ";offset% For l%=1 To h% a$="'" Do While Left$(a$,1)="'" 'skip comments Line Input #1,a$ Loop 'make sure all lines are the correct length If Len(a$)<w% Then Inc a$,Space$(w%-Len(a$)) If Len(a$)>w% Then a$=Left$(a$,w%) LongString append buff%(),a$ 'get all the file into a single longstring Next l% j%=0 For i%=1 To LLen(buff%()) LongString append obuff%(),mycol$(LGetStr$(buff%(),i%,1)) Next i% LongString clear buff%() il%=(LLen(obuff%())+7)\8 * 8 i%=0 Do While i%<w%*h% 'compress the data j%=LGetByte(obuff%(),i%) l%=1 Inc i% Do While LGetByte(obuff%(),i%)=j% And l%<15 Inc l% Inc i% Loop LongString append buff%(), Hex$(l%)+Chr$(j%) Loop 'the output must be a multiple of 8 nibbles LongString append buff%(),Left$("00000000",8-(LLen(buff%()) Mod 8)) If LLen(buff%())<il% Then 'compressed version is smaller so use it Print #2,"'"+o$; PicoMite Graphics Page 7 Print #2," is compressed" Print #2,Hex$(h%+&H8000,4)+Hex$(w%,4) j%=0 For i%=8 To LLen(buff%()) Step 8 'reverse the order o$=LGetStr$(buff%(),i%,1) Inc o$,LGetStr$(buff%(),i%-1,1) Inc o$,LGetStr$(buff%(),i%-2,1) Inc o$,LGetStr$(buff%(),i%-3,1) Inc o$,LGetStr$(buff%(),i%-4,1) Inc o$,LGetStr$(buff%(),i%-5,1) Inc o$,LGetStr$(buff%(),i%-6,1) Inc o$,LGetStr$(buff%(),i%-7,1) Inc j% If j%=8 Then Print #2,o$ j%=0 Else Print #2,o$+" "; EndIf Next i% If j%<>0 Then Print #2,"" Inc offset%,4+LLen(buff%())\2 Else Print #2,"'"+o$; Print #2," is uncompressed" Print #2,Hex$(h%,4)+Hex$(w%,4) LongString append obuff%(),Left$("00000000",8-(LLen(obuff%()) Mod 8)) j%=0 For i%=8 To LLen(obuff%()) Step 8 'reverse the order o$=LGetStr$(obuff%(),i%,1) Inc o$,LGetStr$(obuff%(),i%-1,1) Inc o$,LGetStr$(obuff%(),i%-2,1) Inc o$,LGetStr$(obuff%(),i%-3,1) Inc o$,LGetStr$(obuff%(),i%-4,1) Inc o$,LGetStr$(obuff%(),i%-5,1) Inc o$,LGetStr$(obuff%(),i%-6,1) Inc o$,LGetStr$(obuff%(),i%-7,1) Inc j% If j%=8 Then Print #2,o$ j%=0 Else Print #2,o$+" "; EndIf Next i% If j%<>0 Then Print #2,"" Inc offset%,4+LLen(obuff%())\2 EndIf LongString clear obuff%() LongString clear buff%() Next s% Close #1 If separatesubs%=1 Then Print #2,"END CSUB" End Sub ' 'converts the Ascii colour from the Maximite standard to PicoMite standard Function mycol$(c$) Static cols%(15)=(0,1,6,7,8,9,14,15,2,3,4,5,10,11,12,13) Local i% Page 8 PicoMite Graphics If c$=" " Then c$="0" i%=Val("&H"+c$) mycol$=Hex$(cols%(i%)) End Function . This program converts all sprites in “filename.spr” to one “xxxx_csub.bas” file and an “xxxx_index.txt” file that contains all pointers to the elements in the CSUB after conversion to binary form. The relative positions are referenced to the CSUB start address. The CSUB name (i.e. CSUB TILE0) is essential for this, since there will be many CSUB’s. Note that it processes the files in the directory in ascending order. Therefore it is best to number the sprites in their file name, so the order in the CSUB is predictable. Then “LOAD xxxx_csub.bas” to load the file in program memory, and “LIBRARY SAVE” to write it as binary data in flash in the library. Now we have binary RGB 1:2:1 data in flash, at an unknown location. You can save multiple xxxx_csub.bas files to the library, until the library is full. To find the absolute address of each CSUB in memory, you retrieve the CSUB start address: Address% = PEEK(CFUNADDR csub_name) ‘ i.e. PEEK(CFUNADDR TILE0). It is best to build an array with absolute addresses for the BLIT MEMORY function. The example below creates an absolute index for the 6 (0…5) sprites in the CSUB called HEALTH. 'get start addresses Local hlt=Peek(cfunaddr HEALTH) 'build global index file Dim health_index(5) Open "sprites/hlt_index.txt" For input As #1 For i=0 To 5 Input #1,a$ health_index(i)=hlt+Val(a$) Next Close #1 Now you can use BLIT MEMORY health_index(3),X,Y to copy the 3rd sprite to (X,Y) on the active layer set by FRAMEBUFFER WRITE L/N From LIBRARY to FLASH BANK Above method uses the LIBRARY for binary data. That requires you to prepare the library manually using the command line which is not convenient for a game. Remember however that LIBRARY and FLASH BANK3 use the exact same locations in flash, but work differently. Programs in the LIBRARY can be part of your active program, and therefore CSUB’s have a meaning in the main program. FLASH BANK3 is not part of the active program, and therefore the CSUB has no meaning. But the start address of flash bank 3 (which can change between revisions for MMBasic) can be requested by flash_address% = MM.INFO(FLASH ADDRESS 3). This is the memory start address for flash bank 3, and it is also the memory start address for the library. PicoMite Graphics Page 9 Instead of using an index based on the CSUB, adapt it to be an index that refers to the start of the flash bank. Add an offset to each index (the difference between “flash address” and “csub address for that csub”), and create a new index file that refers to the start address in flash. Do this for all CSUB’s. Now you have an index file ”flash_index.txt” that references each and every graphical element (sprite/tile) to the FLASH BANK start address. Then save the library to SD card as a binary file LIBRARY DISK SAVE “filename.bin” And keep this library together with the index file. Both are useless without the other. Now we can clean up the library (delete), and can use the flash space for FLASH BANK 3. Implement Graphics: The GAME In the MMBasic game program we can now load the binary in flash bank 1,2 or 3, and all graphical elements can be found by adding the index in ”flash_index.txt” to the flash start address of that particular bank you choose to use in the game. Example: FLASH DISK LOAD 2, filename.bin,O Flash_address%=MM.INFO(FLASH ADDRESS 2) ‘ loads the binary file in flash bank 2 ‘ gives the start address of bank 2 After that, create an array abs_index%() with absolute indexes adding flash_address% to all relative indexes read from the index file “flash_index.txt”. Then simply: BLIT MEMORY abs_index(i),X,Y Caveats 1/ Documentation One important thing is to take record of what sprite/tile belongs to what index. For the development of PETSCII ROBOTS it was useful to keep a graphical record, like this (it shows the tile in relation to its number). Page 10 PicoMite Graphics 2/ SPRITES You need all graphical elements as sprite files (indexed colors and dimension header). There may be multiple ways to realize this, but the way used for PETSCII ROBOTS is by loading a BMP file on the screen, and then pixel for pixel calculating the color index. Then writing the file back to disk as a sprite file. Essentially, the following program, written by Martin_H, performs this function. It batch processes 86 sprites in 24x24 size. '-------------------'Information of Source here FN$="spritesMix_bearbeitet.bmp" W=24 H=24 num=86 '-------------------' Dim Col(15):Restore colors:For f%=1 To 15:Read Col(f%):Next f% cls load bmp FN$ x=0 y=0 For TNR=0 to num-1 tn$="sprites3\SP3"+hex$(tnr,3)+".SPR" open tn$ for output as #1 print #1,str$(W);",1,";STR$(H) for y1=y to y+H-1 WT$="" for x1=x to x+W-1 C=Pixel(x1,y1):cl=0 for n= 0 to 15:if C=col(n) then cl=n Next wt$=wt$+hex$(cl,1) Next ?#1, wt$ next PicoMite Graphics Page 11 box x,y,w,h,,rgb (white) close #1 inc y,h:if y>383 then y=0:inc x,w next TNR colors: '--Colorscheme accordung to Spritecolors Data RGB(BLUE),RGB(GREEN),RGB(CYAN),RGB(RED) Data RGB(MAGENTA),RGB(YELLOW),RGB(WHITE),RGB(MYRTLE) Data RGB(COBALT) ,RGB(MIDGREEN),RGB(CERULEAN),RGB(RUST) Data RGB(FUCHSIA),RGB(BROWN),RGB(LILAC) Programs like this take quite a lot of time per sprite/tile, so this is best run on MMB4W on a PC, and not on the PicoMite itself. In case you want to run it on PicoMite, change “load bmp FN$” to “load image FN$” 3/ MEMORY MMB4W does not support a LIBRARY, creating the binary must be executed on the PicoMite itself. To load the xxxx_csub.bas file into memory, and save it to the library, you need program memory for both the hexadecimal text presentation, and the binary result in RAM. Therefore you can only process 60kbyte size xxxx_csub.bas files in 100kbyte program space. This is why the tiles and sprites are distributed over several files/folders, so xxxx_csub.bas files stay within these requirements. Building your own Map For PETSCII ROBOTS the map file and elementary tiles where available. To make your own map and tiles, or adapt existing ones to the right format, below procedure can be used. 1. Create a Tile Image Description: The first step involves creating an image that contains all the tiles you will use in your game. Tools Needed: paint.net (or any similar drawing program). https://www.getpaint.net/ Steps: Design your tiles, making sure each tile fits within a specific grid (e.g., 24x16 pixels per tile). Convert the image to the 16 RGB121 colors used by PicoMite VGA. You can usually do this by reducing the color palette in your drawing program. Some colors that do not exist in this form must be created by dithering. example Page 12 PicoMite Graphics 2. Create the Map Description: After you have your tile image in the correct color format, you need to assemble these tiles into level maps. Tools Needed: TileEd (a tile map editor). https://www.mapeditor.org/ Steps: Open TileEd and load your tile image. Use TileEd to arrange your tiles into the desired level maps. This involves placing tiles in the correct positions to create the layout of each level. Once you are satisfied with your level maps, export them as CSV files. TileEd will generate a CSV file that lists the tile numbers in the order they appear in your map. CSV to MAP Description: The CSV files need to be converted into .map files that your game can use. This involves transforming the tile numbers into 8Bit values. Tools Needed: A custom conversion tool (a small script or program you write). Steps: Here we have a small program that reads the CSV file. The program converts each tile number into its corresponding value, so we use only one byte per tile. Save the output as a .map file. A .MAP file starts with the word MAP followed by one byte for the width and one byte for the height of the map. The actual data therefore starts from byte 6 of the file. This allows you to use different sized maps for different levels and it helps you in your program to find out how big the map is for each level 'read csv and write to MAP cls R=0:C=0 dim integer a(10) open "intro.csv" for Input as #1 open "intro.MAP" For Output as #3 open "tmp.bin" for output as #2 nr$="" do PicoMite Graphics Page 13 n$=input$(1,1) if n$<>"," and n$<>chr$(13) then nr$=nr$+n$ else inc c print #2,chr$(val(nr$); print hex$(val(nr$),2);:nr$="":if n$=chr$(13) then ?: Inc r end if loop until eof (#1) close #2 close #1 C=C/R print #3,"MAP"+CHR$(C);Chr$(R); open "tmp.bin" for input as #2 do print #3,input$(1,2); loop until eof(#2) close #2 close #3 kill "tmp.bin" Final words As the above write-up shows, there is quite a lot of work involved in the preparation, so it is preferable to start with a well-defined set of graphical elements. It easily takes one whole hour to do this process for 400 elements (as PETSCII ROBOTS). But the reward is a fast graphics system. In PETSCII the graphics take up only 30% of the game loop. If you are new to the procedure, start with a graphics set of few graphical elements, until you master the process. Page 14 PicoMite Graphics