FIGnition development started just over a year ago and as normal for all my developments I document my work in a journal as I go along. I first started my FIGnition journal with an article about Tiled graphics.
Tiled GraphicsTiled graphics have an illustrious history, mostly because they were used extensively for Games Consoles when RAM was limited. The 6502-based Nintendo Entertainment System, NES used a sophisticated Tile-based system. Arcade consoles such as PacMan also used tiled graphics as is revealed nicely when you exceed the 256th level. Many AVR based games consoles use a form of Tile-graphics, because Tile graphics are a form of character-based graphics and so you can save RAM by only requiring a character RAM table, where each byte points to a graphical tile in Flash.
At the time I was going to use an AtMega328, which provides 2Kb of RAM, so I was willing to allocate 1.5Kb to video. In Tile mode, the Video display of 240x192 is divided into 16x8 pixel regions called tileRefs. There’s 15x24 of these:
This way I only need half the number of bytes for the text display. The rest of memory is divided into tiles each of which occupies 16b. There are 73.5 tiles. The last 0.5 of a tile contains meta-data for the game mode: The offset for TextField 1 (2 bytes); the offset for TextField 2 (2 bytes) + 4b spare.
Each tileRef is a single byte. Byte values in the range 0..127 are text tileRefs, they point to a pair of characters at offsetForTextField(tileRef.y/8)+tileRef*2. These characters are displayed like normal ROM characters, and can be true video or inverse. Thus we can cover the entire screen with characters, but we only need as much character data as necessary. Typically a game will need a status line at the top, requiring 2 tiles.
Byte values in the range 128..255 are graphics tileRefs, they simply point to a 16x8 bitmapped tile at offset: tileRef*16.
When I moved to using an AtMega168 I reimagined it at a new resolution: 192x192 = 12x24 tiles, using 288b leaving space for about 55 tiles.
The difficulty with Tile mode is that it's simply very awkward. FIGnition is supposed to programmed by relative novices, so the graphics interface has to be pretty simple. Instead the graphics engine required on top of a Tiled mode requires the programmer to think about how many of the 55 tiles might be free and the underlying firmware has to keep track of allocating and deallocating tiles. So, in the end, despite the fact that it looks appealing (and I spent quite a number of hours working on the concepts), it was dumped and I wouldn't even implement a version of it if FIGnition used an AtMega328 (which is not planned).
Medium Resolution GraphicsAnother option, of course is to use medium resolution bitmapped graphics. Here we'd simply use the existing video memory to provide a better graphical resolution, because the text mode only uses a maximum of 4-bits per byte for graphics.
We can work out the consequences easily: there's 728 bytes of Video+UDG memory, so we could support a sensible maximum of: 5824pixels, which at a 3/4 aspect ratio gives 88 x 66 pixels. We can see that although this would be better than the current 50 x 48, it would just give us some pretty boring graphics and wouldn't be worthwhile since no-one would want to use it.
High Resolution GraphicsIn the end I decided to go straight to High-Resolution 160x160 pixels graphics - that is if you can call 160x160 high resolution, which Commodore did for their Vic-20 ;-)
The apparent problem is that there isn't enough internal RAM for this resolution: you'd need 3200 bytes. Now you could get enough RAM if you used main RAM, but there are two main issues you'd need to deal with:
- The main FIGnition RAM is Serial RAM and it's really slow, running at 1µs/byte maximum vs an effective 100ns/byte for internal RAM (because you need to process it using 2 cycle instructions at best).
- The serial RAM is used to run normal Forth programs, so you'd be interrupting access from Forth and the SPI-based Serial RAM simply isn't designed to be interrupted, because there's no way of reading the internal address register on the chip, which means that if the main code was changing the current address being read from, which involves sending a command and the Video interrupt routine ran, then there's no way for the video routine to know what the main code was trying to do and no way to restore the RAM properly to its previous state.
Secondly, and this is really handy - Take note geeks! It turns out that we can interrupt Serial RAM access whenever we assert or deassert the SRAM's Chip Select Line. This is done by activating the PCINT1 interrupt on pin change. So, whenever we try to jump to another SRAM address and we should be fetching video, PCINT1 will be activated and we can interrupt the main code's use of SRAM without incurring any performance penalty at other times; and we don't need to remember what the old state of the SRAM was, because when we return from the interrupt, the main code will set the SRAM address correctly.
So, this is how it'll be done! Tune in to part 2 soon!