// video.h // The Voyager's video device. // Written by Anachronaut #ifndef VIDEO_H #define VIDEO_H #include // ---- What this is ---- // // A tile engine. The CPU writes cell indices and the device expands them into pixels, which // is the difference between a screen costing 2,000 bytes a frame and 64,000 - and at a // megahertz that is the difference between a screen and no screen at all. // // It follows that COLOUR DEPTH IS FREE AT FRAME TIME. The map is the same size whether the // tiles behind it are one bit deep or eight, because the depth lives in tile memory, which // is written once when a program loads and not sixty times a second. So the tiles are eight // bits: an 8x8 cell is 64 pixels and each one picks independently out of 256 colours, with // no per-cell limit of the kind that made a Spectrum two and C64 multicolour four. // // ---- The device brings memory ---- // // One bank, registered the way the disk's buffer is, so it costs a program nothing in Data // Memory and keeps what is in it between frames. A program blits the region that changed // and the rest stays as it was, which is the whole reason this is a bank rather than a // window onto a port. #define VIDEO_MEMORY_BYTES 0x10000 // Tile memory: 256 tiles of 8x8, one byte a pixel. #define VIDEO_TILE_BASE 0x0000 #define VIDEO_TILE_BYTES 64 #define VIDEO_TILE_COUNT 256 // ---- The map, one page a row ---- // // A row is padded to exactly 256 bytes whether the mode uses all of it or not, and that is // not waste, it is arithmetic. THE MACHINE HAS NO MULTIPLY. On a 40 column screen every // cursor move would otherwise need row times 40 in software, which is a tax on the most // common operation in the whole system. At a page a row the address needs no arithmetic at // all: the row number IS the high byte and the doubled column IS the low byte. // // It also frees the geometry from having to be a power of two, which is what lets the // pixel resolution be whatever looks right. #define VIDEO_MAP_BASE 0x4000 #define VIDEO_MAP_STRIDE 256 #define VIDEO_MAP_ROWS 128 #define VIDEO_MAP_COLUMNS (VIDEO_MAP_STRIDE / 2) // Two bytes to a cell: which tile, and how to colour it. #define VIDEO_CELL_BYTES 2 // ---- The palette ---- // // Four bytes an entry rather than three, for the same reason a map row is a page: entry n // begins at n times four, which is a shift. Three would need a multiply the machine does // not have. The fourth byte is unused and reads as whatever was put there. #define VIDEO_PALETTE_BASE 0xC000 #define VIDEO_PALETTE_BYTES 4 #define VIDEO_PALETTE_SIZE 256 // ---- Modes ---- // // Both are 8x8 cells over the same engine; only how many of them differ. The pixel count // costs the CPU nothing, because it only ever writes the map - which is why the larger mode // is affordable at all. #define VIDEO_MODE_40x25 0 #define VIDEO_MODE_80x50 1 #define VIDEO_MODE_COUNT 2 #define VIDEO_CELL_PIXELS 8 #define VIDEO_MAX_WIDTH (80 * VIDEO_CELL_PIXELS) #define VIDEO_MAX_HEIGHT (50 * VIDEO_CELL_PIXELS) // ---- Ports ---- // // Sixteen, like the controller, and it interrupts on its base the way the disk established. // Nothing interrupts yet; the frame interrupt is the next rung. #define VIDEO_STATUS 0x30 #define VIDEO_MODE 0x31 #define VIDEO_COLUMNS 0x32 #define VIDEO_ROWS 0x33 #define VIDEO_SCROLL 0x34 void videoReset(void); uint8_t *videoMemory(uint32_t *capacity); uint8_t videoWrite(uint8_t value, uint8_t port); uint8_t videoRead(uint8_t port); // Turns what is in video memory into pixels. A pure function of that memory, so the same // contents give the same picture with nobody watching - which is what lets the suite check // a screen on a machine that has no display. void videoRender(void); // The pixels the last render produced, three bytes each, red then green then blue. const uint8_t *videoPixels(int *width, int *height); // Renders and writes a binary PPM. Returns 0 if it worked. int videoWriteImage(const char *path); #endif // VIDEO_H