// 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); // ---- What the console needs to draw with ---- // // The Voyager's console is a display controller: it takes a byte stream and puts glyphs on // the screen, the way a video terminal's character generator does. That is a real kind of // chip rather than an emulator convenience - but it does mean the console and a program // drawing graphics are writing one screen, because a machine has one screen. // // The font is expanded into tile memory at reset rather than stored expanded: 1,088 bytes // of one-bit rows against 16 kilobytes of tiles. void videoLoadFont(void); // ---- The cursor ---- // // Drawn by the device rather than by whatever is presenting, because on a machine with a // screen the cursor IS a hardware feature - a display controller blinks it from a counter, // and one drawn by the window would not be in a picture the machine saved. // // It blinks on the machine's own clock, so the phase is a pure function of the cycle count // and a screen saved at a given cycle is the same screen every time. #define VIDEO_BLINK_CYCLES 500000 void videoSetCursor(int row, int column, int visible); // The machine's clock, for anything that has to know time has passed. void videoTick(unsigned long now); int videoColumns(void); int videoRows(void); // Screen coordinates, not map coordinates. The ring is the device's business, and a caller // that had to know where the origin was would have to be told every time it moved. void videoPutCell(int screenRow, int column, uint8_t tile, uint8_t attribute); // Moves the origin on by a row and clears the one that has just come into view at the // bottom - which is holding whatever was there 128 rows ago, since the map is a ring. void videoScrollUp(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