diff --git a/Programs/CosmOS/Apps/Grid.asm b/Programs/CosmOS/Apps/Grid.asm index 033041d..00f2ff5 100644 --- a/Programs/CosmOS/Apps/Grid.asm +++ b/Programs/CosmOS/Apps/Grid.asm @@ -37,12 +37,23 @@ start: ; fail - it succeeds, and the disk's buffer quietly becomes the screen. Every read the ; filesystem made after that came out of video memory, so the shell found an empty disk ; and could not start anything by name. Nothing said a word. + ; TWO BANKS, because the screen has two. The atlas holds the tiles and the palette and is + ; written when a program starts; the screen holds the map and is written as things move. + ; They are separate memories, so a bank number reaches one or the other and never both - + ; which is the whole of what this program had to learn when they were split apart. INIA 0d4 OUTA 0xE3 ; DestBank: the number it will answer to. INIA 0x30 OUTA 0xE2 ; SourceLow: the port of the device that owns it. INIA 0x03 - OUTA 0xE8 ; RegisterBank. + OUTA 0xE8 ; RegisterBank. Four is the atlas. + + INIA 0d5 + OUTA 0xE3 + INIA 0x3A + OUTA 0xE2 ; And the port that owns the screen. + INIA 0x03 + OUTA 0xE8 ; RegisterBank. Five is the map. ; ---- Asking for the screen back afterwards ---- ; @@ -153,7 +164,7 @@ finished: OUTA 0x34 OUTA 0x36 ; The origins, or the shell looks at a corner of the map. - INIA 0d4 + INIA 0d5 OUTA 0xE3 INIA 0x40 OUTA 0xE4 @@ -350,7 +361,7 @@ putMap: STA.0 everyRow: - INIA 0d4 + INIA 0d5 OUTA 0xE3 SETD.0 MapRow LDA.0 diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index 842d4ee..162e079 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -5147,7 +5147,13 @@ screenBank: INIA 0x30 OUTA 0xE2 INIA 0x03 - OUTA 0xE8 + OUTA 0xE8 ; Four is the atlas: the tiles and the palette. + INIA 0d5 + OUTA 0xE3 + INIA 0x3A + OUTA 0xE2 + INIA 0x03 + OUTA 0xE8 ; Five is the screen: the map, or a bitmap. RET ; Back to the drive whoever called was standing on. @@ -5178,10 +5184,42 @@ screenPageDirect: ADD RET +; ---- And which of the screen's two banks that page is in ---- +; +; The tiles and the palette are the atlas and the map is the screen, so the PAGE NUMBER +; ALREADY SAYS WHICH - 0x00 to 0x3F and 0xFC to 0xFF one way, 0x40 to 0xBF the other. Nothing +; has to be remembered alongside the page, and there is no second list to keep in step with +; screenPageFor above. +; +; IN Q AND NOT A, for the same reason screenPageFor answers there: RET puts A back the way it +; found it, so a subroutine that answered in A would answer with the argument it was given. +screenBankFor: + INIB 0x40 + CCF + SUB + BRC screenBankAtlas ; Borrowed, so it is below 0x40 and is a tile. + INIB 0xC0 + CCF + SUB + BRC screenBankScreen ; Borrowed, so it is below 0xC0 and is the map. +screenBankAtlas: + INIA 0d4 + RSTB + CCF + ADD + RET +screenBankScreen: + INIA 0d5 + RSTB + CCF + ADD + RET + ; A holds a page of video memory. Its 256 bytes come into ScreenBlock. screenFromVideo: - PSHA - INIA 0d4 + CALL screenBankFor ; A is the page and RET hands it back; Q comes back the bank. + PSHA ; The page, which the bank is about to want A for. + MVQA OUTA 0xE0 POPA OUTA 0xE1 @@ -5197,8 +5235,9 @@ screenFromVideo: ; A holds a page of video memory. ScreenBlock goes back into it. screenToVideo: + CALL screenBankFor PSHA - INIA 0d4 + MVQA OUTA 0xE3 POPA OUTA 0xE4 diff --git a/Programs/Examples/colours.asm b/Programs/Examples/colours.asm index 2c1032d..f1bfe3d 100644 --- a/Programs/Examples/colours.asm +++ b/Programs/Examples/colours.asm @@ -80,8 +80,10 @@ nextBank: ; three bytes into the palette - which means reaching video memory, which means the ; controller. - ; Give the screen's memory a bank number. The screen answers on port 0x30, and bank 3 is - ; the first number software is allowed to hand out: 0, 1 and 2 belong to the machine. + ; Give the screen's memory a bank number. The screen brings TWO banks and this only wants + ; one of them: port 0x30 owns the atlas, where the tiles and the palette are, and the + ; palette is all this touches. Bank 3 is the first number software is allowed to hand out: + ; 0, 1 and 2 belong to the machine. INIA 0d3 OUTA 0xE3 ; DestBank: the number being given INIA 0x30 @@ -89,7 +91,7 @@ nextBank: INIA 0x03 OUTA 0xE8 ; Command: RegisterBank - ; The palette sits at the top of video memory, at 0xFC00, and entry n is at n times + ; The palette sits at the top of the atlas, at 0xFC00, and entry n is at n times ; four. Bank 2's ink is entry 2 * 16 + 1, which is 33, and 33 * 4 is 132 - so 0xFC84. INIA 0xFC OUTA 0xE4 ; DestHigh diff --git a/Programs/Examples/picture.asm b/Programs/Examples/picture.asm index 92a85fb..0da8434 100644 --- a/Programs/Examples/picture.asm +++ b/Programs/Examples/picture.asm @@ -13,13 +13,13 @@ ; animate a whole screen in; it is the mode to draw a picture in and then leave alone, or to ; change a corner of. ; -; It lives over the top of tile memory and the map, because there is nowhere else for it: the -; bank is 65,536 bytes and the picture is 64,000 of them. Going to bitmap mode does not clear -; the text screen, it stops calling it one - and coming back finds the tiles holding whatever -; the picture put there. Taking the screen means taking it. +; It lives over the top of the map, in the SCREEN bank, because that is the bank whose +; contents change: a picture and a map are the same memory called two different things in two +; different modes. Going to bitmap mode does not clear the text screen, it stops calling it +; one - and coming back finds the map holding whatever the picture put there. ; -; The palette is the one thing that means the same in both, which is why it sits at the very -; top, out of the way of everything. +; WHAT IT DOES NOT COST IS THE FONT. The tiles and the palette are in the other bank and a +; picture cannot reach them, which is what lets this program draw and then say something. #Program @@ -27,17 +27,31 @@ start: ; Video memory is the screen's, not this program's, so it is reached the way every device's ; memory is: given a bank number, then written through the memory controller. Banks 0, 1 ; and 2 belong to the machine, so 3 is the first one software may hand out. + ; TWO OF THEM, because the screen brings two banks and this program writes to both: the + ; palette is in the atlas and the picture is in the screen. INIA 0d3 OUTA 0xE3 ; DestBank: the number being given INIA 0x30 OUTA 0xE2 ; SourceLow: the port that owns the memory INIA 0x03 - OUTA 0xE8 ; Command: RegisterBank + OUTA 0xE8 ; Command: RegisterBank. Three is the atlas. + + INIA 0d4 + OUTA 0xE3 + INIA 0x3A + OUTA 0xE2 + INIA 0x03 + OUTA 0xE8 ; And four is the screen. ; ---- Two hundred and fifty six colours ---- ; ; Entry n at 0xFC00 plus n times four. Writing the controller's Data port puts a byte at ; the destination and steps it on, so the whole palette is one address and a loop. + ; A already holds 3, because RegisterBank is command 3 and the bank wanted is bank 3. + ; Leaving the line out would turn a program that says which bank it means into one that + ; works by coincidence, and the coincidence breaks the day either number changes. + INIA 0d3 ; splitlint[redundant-assignment]: said rather than coincided + OUTA 0xE3 ; The ATLAS, which registering the second bank moved off INIA 0xFC OUTA 0xE4 RSTA @@ -67,6 +81,8 @@ palette: ; DESTINATION PAST WHAT IT TOUCHED, so the address is set once here and never worked out ; again - which matters, because working out where row n begins would be n times 320 and ; this machine has no multiply. + INIA 0d4 + OUTA 0xE3 ; Into the SCREEN bank now, which is where a picture is RSTA OUTA 0xE4 OUTA 0xE5 ; Dest 0x0000, the top left corner diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index 9942c96..0d3559e 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -1304,11 +1304,12 @@ uint8_t *deviceMemory(uint8_t port, uint32_t *capacity) { *capacity = DISK_BLOCK_BYTES; return diskBuffer; } - if (port == PORT_VIDEO) { - // Tiles, the map and the palette, in one bank. A program blits the part that - // changed and the rest stays as it was, which is the whole reason the screen is a - // bank rather than a window onto a port. - return videoMemory(capacity); + if (port == PORT_VIDEO || port == VIDEO_SCREEN) { + // Two banks: the atlas of tiles and colours on the base port, and the map or the + // bitmap on its own. A program blits the part that changed and the rest stays as it + // was, which is the whole reason the screen is memory rather than a window onto a + // port - and having two means a picture costs the map and not the font. + return videoMemory(port, capacity); } return NULL; } @@ -1369,8 +1370,18 @@ static const DeviceRecord *deviceOnPort(uint8_t port) { return deviceOnPort(PORT_DISK); } if (port > PORT_VIDEO && port <= PORT_VIDEO_TOP) { - // Sixteen ports, one device, and the same rule again. - return deviceOnPort(PORT_VIDEO); + // Sixteen ports, one device, and the same rule again - with one difference, because + // this device owns TWO banks. Forwarding the whole block to the base record used to + // say that all sixteen ports brought memory, which was harmless only while nobody + // believed it: a program that enumerated the block and registered everything + // claiming memory would have faulted on the fourteen that have none. + // + // So the block answers honestly. The screen port says it brings memory because it + // does, and the rest of the block says it does not. + static const DeviceRecord videoScreenRecord = + { VIDEO_SCREEN, DEVICE_VIDEO, DEVICE_FLAG_HAS_MEMORY }; + static const DeviceRecord videoPlainRecord = { PORT_VIDEO, DEVICE_VIDEO, 0 }; + return (port == VIDEO_SCREEN) ? &videoScreenRecord : &videoPlainRecord; } if (port > PORT_SOUND && port <= PORT_SOUND_TOP) { return deviceOnPort(PORT_SOUND); diff --git a/Source/Emulator/video.c b/Source/Emulator/video.c index 08f41f0..e2f78f2 100644 --- a/Source/Emulator/video.c +++ b/Source/Emulator/video.c @@ -11,7 +11,11 @@ // The bank the device brings. Registered by whoever enumerates the hardware, reached only // through the memory controller, and never by the CPU directly - the same arrangement the // disk's buffer has always had. -static uint8_t videoRAM[VIDEO_MEMORY_BYTES]; +// The two banks. Which one an address is in is a property of the address and never of the +// mode: tiles and the palette are always in the atlas, the map and a bitmap always in the +// screen. That is what makes the split cost nothing to think about at a call site. +static uint8_t videoAtlas[VIDEO_MEMORY_BYTES]; +static uint8_t videoScreen[VIDEO_MEMORY_BYTES]; static uint8_t mode; // Which map row is drawn at the top. THE MAP IS A RING: rendering row r reads map row @@ -129,7 +133,7 @@ void videoLoadFont(void) { // can ask for it. A program that defined a tile of its own above the font and then wanted // its text back would have lost the tile to get it. for (int glyph = 0; glyph < CONSOLE_FONT_GLYPHS && glyph < VIDEO_TILE_COUNT; glyph++) { - uint8_t *tile = videoRAM + VIDEO_TILE_BASE + glyph * VIDEO_TILE_BYTES; + uint8_t *tile = videoAtlas + VIDEO_TILE_BASE + glyph * VIDEO_TILE_BYTES; for (int y = 0; y < CONSOLE_FONT_BYTES; y++) { const unsigned char row = consoleFont[glyph * CONSOLE_FONT_BYTES + y]; for (int x = 0; x < VIDEO_CELL_PIXELS; x++) { @@ -140,7 +144,7 @@ void videoLoadFont(void) { } void videoLoadPalette(void) { - uint8_t *palette = videoRAM + VIDEO_PALETTE_BASE; + uint8_t *palette = videoAtlas + VIDEO_PALETTE_BASE; for (int bank = 0; bank < 8; bank++) { // Colour on black, and then the same colour as paper with black ink, sixteen banks // apart so that one bit turns either into the other. @@ -165,7 +169,7 @@ void videoPutCell(int screenRow, int column, uint8_t tile, uint8_t attribute) { // less than a cell, and there is no such thing as less than a cell to write into. const int mapRow = (scroll + screenRow) % VIDEO_MAP_ROWS; const int mapColumn = (scrollColumn + column) % VIDEO_MAP_COLUMNS; - uint8_t *cell = videoRAM + VIDEO_MAP_BASE + mapRow * VIDEO_MAP_STRIDE + uint8_t *cell = videoScreen + VIDEO_MAP_BASE + mapRow * VIDEO_MAP_STRIDE + mapColumn * VIDEO_CELL_BYTES; cell[0] = tile; cell[1] = attribute; @@ -178,11 +182,12 @@ void videoScrollUp(void) { // hundred rows of what has already been said, still sitting in the map. const int bottom = rowsFor(mode) - 1; const int mapRow = (scroll + bottom) % VIDEO_MAP_ROWS; - memset(videoRAM + VIDEO_MAP_BASE + mapRow * VIDEO_MAP_STRIDE, 0, VIDEO_MAP_STRIDE); + memset(videoScreen + VIDEO_MAP_BASE + mapRow * VIDEO_MAP_STRIDE, 0, VIDEO_MAP_STRIDE); } void videoReset(void) { - memset(videoRAM, 0, sizeof(videoRAM)); + memset(videoAtlas, 0, sizeof(videoAtlas)); + memset(videoScreen, 0, sizeof(videoScreen)); mode = VIDEO_MODE_40x25; scroll = 0; scrollColumn = 0; @@ -201,9 +206,17 @@ void videoReset(void) { videoLoadPalette(); } -uint8_t *videoMemory(uint32_t *capacity) { +uint8_t *videoMemory(uint8_t port, uint32_t *capacity) { *capacity = VIDEO_MEMORY_BYTES; - return videoRAM; + if (port == VIDEO_STATUS) { + return videoAtlas; + } + if (port == VIDEO_SCREEN) { + return videoScreen; + } + // Every other port in the block owns no memory. Saying so is what stops a bank being + // registered onto one of them and pointing at nothing. + return NULL; } uint8_t videoWrite(uint8_t value, uint8_t port) { @@ -320,8 +333,8 @@ void videoRender(void) { // No tile to look up and no attribute to add: the byte IS the palette index. Which // is the whole difference between the two kinds of screen - a tile mode costs the // CPU the number of cells that changed, and this costs it the number of pixels. - const uint8_t *palette = videoRAM + VIDEO_PALETTE_BASE; - const uint8_t *from = videoRAM + VIDEO_BITMAP_BASE; + const uint8_t *palette = videoAtlas + VIDEO_PALETTE_BASE; + const uint8_t *from = videoScreen + VIDEO_BITMAP_BASE; uint8_t *out = pixels; for (int at = 0; at < VIDEO_BITMAP_WIDTH * VIDEO_BITMAP_HEIGHT; at++) { const uint8_t *entry = palette + from[at] * VIDEO_PALETTE_BYTES; @@ -351,7 +364,7 @@ void videoRender(void) { // The ring. Rows that scrolled off the top are still in the map, which is what // makes scrollback free rather than something the console has to keep itself. const int mapRow = (scroll + row) % VIDEO_MAP_ROWS; - const uint8_t *cells = videoRAM + VIDEO_MAP_BASE + mapRow * VIDEO_MAP_STRIDE; + const uint8_t *cells = videoScreen + VIDEO_MAP_BASE + mapRow * VIDEO_MAP_STRIDE; for (int column = 0; column <= columns; column++) { const int mapColumn = (scrollColumn + column) % VIDEO_MAP_COLUMNS; const uint8_t tile = cells[mapColumn * VIDEO_CELL_BYTES]; @@ -377,7 +390,7 @@ void videoRender(void) { // tile that wants all 256 colours simply leaves the nibble at zero and gets // them. One adder in hardware, and neither use costs the other anything. const uint8_t bank = (uint8_t)((attribute & 0x0F) << 4); - const uint8_t *art = videoRAM + VIDEO_TILE_BASE + tile * VIDEO_TILE_BYTES; + const uint8_t *art = videoAtlas + VIDEO_TILE_BASE + tile * VIDEO_TILE_BYTES; for (int y = 0; y < VIDEO_CELL_PIXELS; y++) { // Where this row of the cell lands once the view has been slid up by the // fine offset. Negative means it is the part of the top cell that is off @@ -395,7 +408,7 @@ void videoRender(void) { // high end of the palette with a nibble set comes round the bottom, // which is what an adder does and what the manual says it does. const uint8_t index = (uint8_t)(art[y * VIDEO_CELL_PIXELS + x] + bank); - const uint8_t *entry = videoRAM + VIDEO_PALETTE_BASE + const uint8_t *entry = videoAtlas + VIDEO_PALETTE_BASE + index * VIDEO_PALETTE_BYTES; uint8_t *out = pixels + (atY * width + atX) * 3; out[0] = entry[0]; diff --git a/Source/Emulator/video.h b/Source/Emulator/video.h index 5c50709..c984b04 100644 --- a/Source/Emulator/video.h +++ b/Source/Emulator/video.h @@ -19,16 +19,31 @@ // 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 ---- +// ---- The device brings memory, in two banks ---- // -// One bank, registered the way the disk's buffer is, so it costs a program nothing in Data +// Registered the way the disk's buffer is, so the screen 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 +// and the rest stays as it was, which is the whole reason this is memory rather than a // window onto a port. - +// +// TWO BANKS AND NOT ONE, because the two halves of a screen are written at completely +// different rates. Tiles and colours are an ATLAS: put there when a program loads and then +// left alone. The map is a SCREEN: rewritten as often as anything moves. Sharing one bank +// made them compete for 64K they did not both need, and it had a worse consequence than +// being cramped - a bitmap took the whole bank, so ENTERING BITMAP MODE DESTROYED THE FONT. +// A program could not draw a picture and then say anything about it. +// +// Split, each gets a whole 64K and neither can tread on the other. A bitmap now overwrites +// the map, which is the same memory meaning a different thing in a different mode and is +// exactly what it should overwrite. The tiles behind the text survive it. +// +// Each bank is the same size, and every address below says which of the two it is in. #define VIDEO_MEMORY_BYTES 0x10000 -// Tile memory: 256 tiles of 8x8, one byte a pixel. +// ---- In the ATLAS bank ---- +// +// Tile memory: 256 tiles of 8x8, one byte a pixel. Everything above it is free, and is +// where more tiles and the sprite table are going. #define VIDEO_TILE_BASE 0x0000 #define VIDEO_TILE_BYTES 64 #define VIDEO_TILE_COUNT 256 @@ -43,6 +58,11 @@ // // It also frees the geometry from having to be a power of two, which is what lets the // pixel resolution be whatever looks right. +// +// IN THE SCREEN BANK, and still at 0x4000 rather than at the bottom of a bank it now has to +// itself. Moving it would have been tidier and would have meant changing which bank a +// program registers AND which address it writes to in the same breath - so a screen that +// came out wrong would have had two possible causes. The address costs nothing where it is. #define VIDEO_MAP_BASE 0x4000 #define VIDEO_MAP_STRIDE 256 #define VIDEO_MAP_ROWS 128 @@ -59,8 +79,12 @@ // tiles rather than on top of them would need a second bank for no reason except tidiness. // // What it costs is that the two do not coexist. Going to bitmap mode does not clear the text -// screen; it stops calling it a text screen. Coming back finds the tiles and the map holding -// whatever the picture put there, which is what taking the screen means. +// screen; it stops calling it a text screen. Coming back finds the map holding whatever the +// picture put there, which is what taking the screen means. +// +// IN THE SCREEN BANK, so what a picture costs is the map and nothing else. THE TILES AND THE +// PALETTE ARE IN THE OTHER BANK AND SURVIVE IT, which is what lets a program draw a picture +// and then put text back on the screen without reloading the character generator first. #define VIDEO_BITMAP_BASE 0x0000 #define VIDEO_BITMAP_WIDTH 320 #define VIDEO_BITMAP_HEIGHT 200 @@ -71,9 +95,12 @@ // 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. // -// At the TOP of video memory, clear of everything else, because it is the one thing that has -// to mean the same in every mode - a bitmap needs colours as much as a tile does, and 64,000 -// bytes of picture leaves nowhere in the middle for it to hide. +// At the TOP of the ATLAS bank, clear of everything else, because it is the one thing that +// has to mean the same in every mode - a bitmap needs colours as much as a tile does, and it +// is written when a program loads rather than per frame, which is what the atlas is for. +// +// Being out of the screen bank is what leaves a bitmap the WHOLE of one: 64,000 bytes of +// picture in 65,536, with nothing it has to dodge. #define VIDEO_PALETTE_BASE 0xFC00 #define VIDEO_PALETTE_BYTES 4 #define VIDEO_PALETTE_SIZE 256 @@ -149,6 +176,19 @@ int videoTextRows(void); // Copy the sixteen ink and paper pairs back, leaving the rest of the palette alone. #define VIDEO_COMMAND_PALETTE 0x02 +// ---- The port that owns the screen bank ---- +// +// A bank is registered by naming THE PORT THAT OWNS IT, which the controller settled long +// before the screen had two of them. So a device with two banks needs two ports that own +// memory, and needs no new mechanism at all: the base port owns the atlas, and this one +// owns the screen. +// +// The base port keeps the atlas rather than the screen because tiles have been at 0x0000 +// since there was a screen at all, and whichever way round this went, one of the two +// meanings had to move. Nothing is READ OR WRITTEN here - it is a name for a bank, and the +// registry is where a program finds out it brings one. +#define VIDEO_SCREEN 0x3A + // Eight pixels to a cell, so three bits say where inside one the view begins. #define VIDEO_FINE_MASK 0x07 @@ -225,7 +265,10 @@ void videoPutCell(int screenRow, int column, uint8_t tile, uint8_t attribute); // 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); +// The memory behind one of the device's two ports: VIDEO_STATUS for the atlas, VIDEO_SCREEN +// for the screen. NULL for any other port, because the rest of the block owns no memory and +// registering a bank onto one would put a number in the table that leads nowhere. +uint8_t *videoMemory(uint8_t port, uint32_t *capacity); uint8_t videoWrite(uint8_t value, uint8_t port); uint8_t videoRead(uint8_t port); diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index b4a33bb..a373409 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -615,18 +615,41 @@ It follows that colour depth is free. The map is the same size whatever is behin ### Video Memory: -One bank, brought by the device and reached only through the memory controller, like the disk's buffer. It keeps what is in it between frames, so a program writes the part that changed and the rest stays as it was. +**Two banks**, brought by the device and reached only through the memory controller, like the disk's buffer. They keep what is in them between frames, so a program writes the part that changed and the rest stays as it was. -| Address | Holds | +Two rather than one because the two halves of a screen are written at completely different rates. The **atlas** is tiles and colours: put there when a program loads and then left alone. The **screen** is the map: rewritten as often as anything moves. + +| Bank | Address | Holds | +| --- | --- | --- | +| Atlas | 0x0000 - 0x3FFF | Tile memory. 256 tiles of 8 by 8, one byte a pixel, so tile n begins at n times 64. | +| Atlas | 0x4000 - 0xFBFF | Free. | +| Atlas | 0xFC00 - 0xFFFF | The palette. 256 entries of four bytes: red, green, blue, and one unused. | +| Screen | 0x0000 - 0x3FFF | Free in a tile mode. | +| Screen | 0x4000 - 0xBFFF | The map. 128 rows of 256 bytes. | +| Screen | 0x0000 - 0xF9FF | In bitmap mode, the picture instead: 64,000 bytes, one to a pixel. | + +**The bitmap is the same memory as the map**, which is what shared video memory has always been. Going to bitmap mode does not clear the text screen - it stops calling it one, and coming back finds the map holding whatever the picture put there. + +**What a picture no longer costs is the font.** The tiles and the palette are in the other bank, where a bitmap cannot reach them, so a program can draw a picture and then put readable text back on the screen without asking the character generator for its glyphs again. While the two shared a bank, drawing anything destroyed them. + +The palette is in the atlas, at the top and out of the way, because it is written when a program loads rather than per frame - and because being out of the screen bank is what leaves a bitmap the whole of one. + +Which bank is which is a property of the **address**, never of the mode: tiles and the palette are always in the atlas, the map and a bitmap always in the screen. + +### Naming Them: + +A bank is registered by naming the port that owns it, so a device with two banks needs two ports that own memory. The screen has them: + +| Port | Owns | | --- | --- | -| 0x0000 - 0x3FFF | Tile memory. 256 tiles of 8 by 8, one byte a pixel, so tile n begins at n times 64. | -| 0x4000 - 0xBFFF | The map. 128 rows of 256 bytes. | -| 0x0000 - 0xF9FF | In bitmap mode, the picture instead: 64,000 bytes, one to a pixel. | -| 0xFC00 - 0xFFFF | The palette. 256 entries of four bytes: red, green, blue, and one unused. | +| 0x30 | The atlas. | +| 0x3A | The screen. | -**The bitmap is the same memory as the tiles and the map**, which is what shared video memory has always been, and there is nowhere else it could be: 64,000 bytes of picture in a 65,536 byte bank leaves room for nothing beside it. Going to bitmap mode does not clear the text screen - it stops calling it one, and coming back finds the tiles and the map holding whatever the picture put there. +Nothing is read or written at 0x3A - it is a name for a bank, and the bus registry is where a program finds out it brings one. Asking the registry about the block gives an honest answer: those two ports say they bring memory and the other fourteen say they do not. -The palette is at the top, out of the way of both, because it is the one thing that means the same in every mode. +The base port owns the atlas because tiles have been at 0x0000 since there was a screen at all. `Programs/Examples/picture.asm` registers both, and is the shortest thing to read that does. + +**Registering the second one moves DestBank**, which is worth saying because it is easy to be caught by: RegisterBank takes the number being handed out in DestBank, so a program that registers two banks and then writes without setting DestBank again writes into the second one. **A map row is a page whether the mode fills it or not**, and that is arithmetic rather than waste. This machine has no multiply, so on a 40 column screen every cursor move would otherwise cost a `row times 40` in software - a tax on the most common operation in the system. At a page a row there is no arithmetic at all: the row number is the high byte of the address and the doubled column is the low byte. @@ -653,6 +676,8 @@ The high nibble is reserved and should be left at zero, so that a meaning can be | 0x36 | Scroll column. Which of the map's 128 columns is drawn at the left. | | 0x37 | Fine X. How many pixels into that column the screen begins, 0 to 7. | | 0x38 | Fine Y. How many pixels into that row the screen begins, 0 to 7. | +| 0x39 | Command. Bit 0 copies the font back, bit 1 the sixteen colour schemes. | +| 0x3A | Owns the screen bank. Not read or written. | | Mode | Screen | Cells | | --- | --- | --- | diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index 1cd9a3e..8a371e4 100644 --- a/Tests/expected/cosmosCrossDisk.out +++ b/Tests/expected/cosmosCrossDisk.out @@ -19,7 +19,7 @@ Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 Break.sbx 149 -Grid.sbx 559 +Grid.sbx 571 Press.sbx 872 Mode.sbx 48 Crash.sbx 632 diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index 5ed403e..7e81177 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -9,7 +9,7 @@ Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 Break.sbx 149 -Grid.sbx 559 +Grid.sbx 571 Press.sbx 872 Mode.sbx 48 Crash.sbx 632 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 7bb9e29..e263af5 100644 --- a/Tests/expected/cosmosFault.out +++ b/Tests/expected/cosmosFault.out @@ -26,7 +26,7 @@ Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 Break.sbx 149 -Grid.sbx 559 +Grid.sbx 571 Press.sbx 872 Mode.sbx 48 Crash.sbx 632 diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index 3622678..548dd92 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -16,7 +16,7 @@ Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 Break.sbx 149 -Grid.sbx 559 +Grid.sbx 571 Press.sbx 872 Mode.sbx 48 Crash.sbx 632 diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index acca83a..cda18db 100644 --- a/Tests/expected/cosmosMonitor.out +++ b/Tests/expected/cosmosMonitor.out @@ -102,7 +102,7 @@ Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 Break.sbx 149 -Grid.sbx 559 +Grid.sbx 571 Press.sbx 872 Mode.sbx 48 Crash.sbx 632 diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index 97a24b1..f162ae8 100644 --- a/Tests/expected/cosmosMonitorRun.out +++ b/Tests/expected/cosmosMonitorRun.out @@ -21,7 +21,7 @@ Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 Break.sbx 149 -Grid.sbx 559 +Grid.sbx 571 Press.sbx 872 Mode.sbx 48 Crash.sbx 632 diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index 5f3486b..a2f47d6 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -9,7 +9,7 @@ Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 Break.sbx 149 -Grid.sbx 559 +Grid.sbx 571 Press.sbx 872 Mode.sbx 48 Crash.sbx 632 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index e4043b2..d32518d 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -7,7 +7,7 @@ Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 Break.sbx 149 -Grid.sbx 559 +Grid.sbx 571 Press.sbx 872 Mode.sbx 48 Crash.sbx 632 diff --git a/Tests/video.sh b/Tests/video.sh index e5a42e4..0276cd0 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -56,7 +56,13 @@ start: INIA 0x30 OUTA 0xE2 INIA 0x03 - OUTA 0xE8 ; Video memory becomes bank 3 + OUTA 0xE8 ; The atlas - tiles and palette - becomes bank 3 + INIA 0d4 + OUTA 0xE3 + INIA 0x3A + OUTA 0xE2 + INIA 0x03 + OUTA 0xE8 ; And the screen - the map, or a bitmap - becomes bank 4 ASM # ---- Said rather than assumed ---- # @@ -64,20 +70,30 @@ ASM # run, so palette entry 0 is the console's paper rather than black. A check that wanted # black and got paper would be a check that had quietly depended on a default. These # tests are about the device, so they set what they are about to look at. - poke 0xFC00 0x00; poke 0xFC01 0x00; poke 0xFC02 0x00 + pokeAtlas 0xFC00 0x00; pokeAtlas 0xFC01 0x00; pokeAtlas 0xFC02 0x00 } -poke() { - # poke