diff --git a/source/PicoRam.h b/source/PicoRam.h index 401ea918..8df5ccda 100644 --- a/source/PicoRam.h +++ b/source/PicoRam.h @@ -181,6 +181,7 @@ struct drawState_t { uint8_t lineInvalid; + //hardware extension uint8_t unknown05f36; uint8_t unknown05f37; @@ -196,29 +197,41 @@ struct drawState_t { struct hwState_t { //audio hardware mods + //0x5f40 uint8_t half_rate; + //0x5f41 uint8_t reverb; + //0x5f42 uint8_t distort; + //0x5f43 uint8_t lowpass; - + //0x5f44..0x5f4b uint32_t rngState[2]; - + //0x5f4c..0x5f53 uint8_t buttonStates[8]; - - uint8_t unknownInputBlock[8]; - + //0x5f54 + uint8_t spriteSheetMemMapping; + //0x5f55 + uint8_t screenDataMemMapping; + //0x5f56 + uint8_t mapMemMapping; + //0x5f57 + uint8_t widthOfTheMap; + //0x5f58..0x5f5b + uint8_t printAttributes[4]; + //0x5f5c uint8_t btnpRepeatDelay; - + //0x5f5d uint8_t btnpRepeatInterval; - + //0x5f5e uint8_t colorBitmask; - + //0x5f5f uint8_t alternatePaletteFlag; - + //0x5f60..0x5f6f uint8_t alternatePaletteMap[16]; - + //0x5f70..0x5f7f uint8_t alternatePaletteScreenLineBitfield[16]; - + //0x5f80..0x5fff uint8_t gpioPins[128]; }; @@ -230,9 +243,13 @@ struct PicoRam void Reset() { memset(data, 0, 0x4300); //leave general use memory - memset(data + 0x5600, 0, 0x8000 - 0x5600); + memset(data + 0x5600, 0, 0x10000 - 0x5600); //colorBitmask starts at 255 hwState.colorBitmask = 0xff; + hwState.spriteSheetMemMapping = 0x00; + hwState.screenDataMemMapping = 0x60; + hwState.mapMemMapping = 0x20; + hwState.widthOfTheMap = 128; } union @@ -260,8 +277,10 @@ struct PicoRam hwState_t hwState; uint8_t screenBuffer[128 * 64]; + + uint8_t userData[0x8000]; }; - uint8_t data[0x8000]; + uint8_t data[0x10000]; }; }; \ No newline at end of file diff --git a/source/graphics.cpp b/source/graphics.cpp index 369ed787..7054a4c1 100644 --- a/source/graphics.cpp +++ b/source/graphics.cpp @@ -73,8 +73,15 @@ Graphics::Graphics(std::string fontdata, PicoRam* memory) { uint8_t* Graphics::GetP8FrameBuffer(){ - //TODO: replace with ram's screen buffer - return _memory->screenBuffer; + return _memory->hwState.screenDataMemMapping == 0 + ? _memory->spriteSheetData + : _memory->screenBuffer; +} + +uint8_t* Graphics::GetP8SpriteSheetBuffer(){ + return _memory->hwState.spriteSheetMemMapping == 0x60 + ? _memory->screenBuffer + : _memory->spriteSheetData; } uint8_t* Graphics::GetScreenPaletteMap(){ @@ -105,7 +112,7 @@ void Graphics::copySpriteToScreen( auto &drawState = _memory->drawState; auto &hwState = _memory->hwState; - auto &screenBuffer = _memory->screenBuffer; + uint8_t *screenBuffer = GetP8FrameBuffer(); const uint8_t writeMask = hwState.colorBitmask & 15; const uint8_t readMask = hwState.colorBitmask >> 4; @@ -256,7 +263,7 @@ void Graphics::copyStretchSpriteToScreen( auto &drawState = _memory->drawState; auto &hwState = _memory->hwState; - auto &screenBuffer = _memory->screenBuffer; + uint8_t *screenBuffer = GetP8FrameBuffer(); const uint8_t writeMask = hwState.colorBitmask & 15; const uint8_t readMask = hwState.colorBitmask >> 4; @@ -529,7 +536,7 @@ void Graphics::_setPixelFromSprite(int x, int y, uint8_t col) { auto &drawState = _memory->drawState; auto &hwState = _memory->hwState; - auto &screenBuffer = _memory->screenBuffer; + uint8_t *screenBuffer = GetP8FrameBuffer(); //col = getDrawPalMappedColor(col); col = drawState.drawPaletteMap[col & 0x0f] & 0x0f; @@ -555,7 +562,7 @@ void Graphics::_setPixelFromPen(int x, int y) { auto &drawState = _memory->drawState; auto &hwState = _memory->hwState; - auto &screenBuffer = _memory->screenBuffer; + uint8_t *screenBuffer = GetP8FrameBuffer(); uint8_t col = drawState.color; @@ -609,7 +616,7 @@ void Graphics::cls() { void Graphics::cls(uint8_t color) { color = color & 15; uint8_t val = color << 4 | color; - memset(_memory->screenBuffer, val, sizeof(_memory->screenBuffer)); + memset(GetP8FrameBuffer(), val, sizeof(_memory->screenBuffer)); _memory->drawState.text_x = 0; _memory->drawState.text_y = 0; @@ -638,7 +645,7 @@ uint8_t Graphics::pget(int x, int y){ applyCameraToPoint(&x, &y); if (isOnScreen(x, y)){ - return getPixelNibble(x, y, _memory->screenBuffer); + return getPixelNibble(x, y, GetP8FrameBuffer()); } return 0; @@ -744,6 +751,7 @@ void Graphics::_private_h_line(int x1, int x2, int y){ void Graphics::_private_v_line (int y1, int y2, int x){ auto &drawState = _memory->drawState; auto &hwState = _memory->hwState; + uint8_t * screenBuffer = GetP8FrameBuffer(); if (!(x >= drawState.clip_xb && x < drawState.clip_xe)) { return; @@ -776,7 +784,7 @@ void Graphics::_private_v_line (int y1, int y2, int x){ for (int16_t y = miny; y <= maxy; ++y) { int pixIdx = COMBINED_IDX(x, y); - auto &data = _memory->screenBuffer[pixIdx]; + auto &data = screenBuffer[pixIdx]; data = (data & mask) | nibble; } } @@ -1354,7 +1362,7 @@ void Graphics::spr( int spr_y = (n / 16) * 8; int16_t spr_w = (int16_t)(w * (fix32)8); int16_t spr_h = (int16_t)(h * (fix32)8); - copySpriteToScreen(_memory->spriteSheetData, x, y, spr_x, spr_y, spr_w, spr_h, flip_x, flip_y); + copySpriteToScreen(GetP8SpriteSheetBuffer(), x, y, spr_x, spr_y, spr_w, spr_h, flip_x, flip_y); } void Graphics::sspr( @@ -1369,7 +1377,7 @@ void Graphics::sspr( bool flip_x = false, bool flip_y = false) { - copyStretchSpriteToScreen(_memory->spriteSheetData, sx, sy, sw, sh, dx, dy, dw, dh, flip_x, flip_y); + copyStretchSpriteToScreen(GetP8SpriteSheetBuffer(), sx, sy, sw, sh, dx, dy, dw, dh, flip_x, flip_y); } bool Graphics::fget(uint8_t n, uint8_t f){ @@ -1394,11 +1402,11 @@ void Graphics::fset(uint8_t n, uint8_t v){ } uint8_t Graphics::sget(uint8_t x, uint8_t y){ - return getPixelNibble(x, y, _memory->spriteSheetData); + return getPixelNibble(x, y, GetP8SpriteSheetBuffer()); } void Graphics::sset(uint8_t x, uint8_t y, uint8_t c){ - setPixelNibble(x, y, c, _memory->spriteSheetData); + setPixelNibble(x, y, c, GetP8SpriteSheetBuffer()); } std::tuple Graphics::camera() { @@ -1437,30 +1445,61 @@ std::tuple Graphics::clip(int x, int y, int } -//map methods heavily based on tac08 implementation uint8_t Graphics::mget(int celx, int cely){ - if (celx < 0 || celx >= 128 || cely < 0 || cely >= 64) - return 0; + const bool bigMap = _memory->hwState.mapMemMapping >= 0x80; + const int mapW = _memory->hwState.widthOfTheMap; + const int idx = cely * mapW + celx; - if (cely < 32) { - return _memory->mapData[cely * 128 + celx]; + if (idx < 0) { + return 0; } - else if (cely < 64){ - return _memory->spriteSheetData[cely* 128 + celx]; + + if (bigMap){ + const int mapLocation = _memory->hwState.mapMemMapping << 8; + const int mapSize = 0x10000 - mapLocation; + if (idx >= mapSize){ + return 0; + } + const int offset = 0x8000 - mapSize; + return _memory->userData[offset + idx]; + } + else { + if (idx < 4096) { + return _memory->mapData[idx]; + } + else if (idx < 8192){ + return _memory->spriteSheetData[idx]; + } } return 0; } void Graphics::mset(int celx, int cely, uint8_t snum){ - if (celx < 0 || celx >= 128 || cely < 0 || cely >= 64) + const bool bigMap = _memory->hwState.mapMemMapping >= 0x80; + const int mapW = _memory->hwState.widthOfTheMap; + const int idx = cely * mapW + celx; + + if (idx < 0) { return; + } - if (cely < 32) { - _memory->mapData[cely * 128 + celx] = snum; + if (bigMap){ + const int mapLocation = _memory->hwState.mapMemMapping << 8; + const int mapSize = 0x10000 - mapLocation; + if (idx >= mapSize){ + return; + } + const int offset = 0x8000 - mapSize; + _memory->userData[offset + idx] = snum; } - else if (cely < 64){ - _memory->spriteSheetData[cely* 128 + celx] = snum; + else { + if (idx < 4096) { + _memory->mapData[idx] = snum; + } + else if (idx < 8192) { + _memory->spriteSheetData[idx] = snum; + } } } diff --git a/source/graphics.h b/source/graphics.h index 0090eb6e..b85d83ba 100644 --- a/source/graphics.h +++ b/source/graphics.h @@ -98,6 +98,7 @@ class Graphics { Graphics(std::string fontdata, PicoRam* memory); uint8_t* GetP8FrameBuffer(); + uint8_t* GetP8SpriteSheetBuffer(); uint8_t* GetScreenPaletteMap(); Color* GetPaletteColors(); diff --git a/source/vm.cpp b/source/vm.cpp index 84f50b72..9da8dcc5 100644 --- a/source/vm.cpp +++ b/source/vm.cpp @@ -619,9 +619,10 @@ bool Vm::ExecuteLua(string luaString, string callbackFunction){ return true; } +const int MemoryUpperBound = 0x10000; uint8_t Vm::vm_peek(int addr){ - if (addr < 0 || addr > 0x8000){ + if (addr < 0 || addr > MemoryUpperBound){ return 0; } @@ -634,10 +635,10 @@ int16_t Vm::vm_peek2(int addr){ for (int i = 0; i < 2; ++i) { /* This code handles partial reads by adding zeroes */ - if (addr + i < 0x8000) + if (addr + i < MemoryUpperBound) bits |= _memory->data[addr + i] << (8 * i); - else if (addr + i >= 0x8000) - bits |= _memory->data[addr + i - 0x8000] << (8 * i); + else if (addr + i >= MemoryUpperBound) + bits |= _memory->data[addr + i - MemoryUpperBound] << (8 * i); } return bits; @@ -650,10 +651,10 @@ fix32 Vm::vm_peek4(int addr){ for (int i = 0; i < 4; ++i) { /* This code handles partial reads by adding zeroes */ - if (addr + i < 0x8000) + if (addr + i < MemoryUpperBound) bits |= _memory->data[addr + i] << (8 * i); - else if (addr + i >= 0x8000) - bits |= _memory->data[addr + i - 0x8000] << (8 * i); + else if (addr + i >= MemoryUpperBound) + bits |= _memory->data[addr + i - MemoryUpperBound] << (8 * i); } return fix32::frombits(bits); @@ -661,7 +662,7 @@ fix32 Vm::vm_peek4(int addr){ void Vm::vm_poke(int addr, uint8_t value){ //todo: check how pico 8 handles out of bounds - if (addr < 0 || addr > 0x8000){ + if (addr < 0 || addr > MemoryUpperBound){ return; } @@ -669,7 +670,7 @@ void Vm::vm_poke(int addr, uint8_t value){ } void Vm::vm_poke2(int addr, int16_t value){ - if (addr < 0 || addr > 0x8000 - 1){ + if (addr < 0 || addr > MemoryUpperBound - 1){ return; } @@ -679,7 +680,7 @@ void Vm::vm_poke2(int addr, int16_t value){ } void Vm::vm_poke4(int addr, fix32 value){ - if (addr < 0 || addr > 0x8000 - 3){ + if (addr < 0 || addr > MemoryUpperBound - 3){ return; } diff --git a/test/graphicstests.cpp b/test/graphicstests.cpp index a1dfea5d..e547e87b 100644 --- a/test/graphicstests.cpp +++ b/test/graphicstests.cpp @@ -2134,6 +2134,276 @@ TEST_CASE("graphics class behaves as expected") { checkPoints(graphics, expectedPoints); } */ + + SUBCASE("Remap spritesheet to screen"){ + graphics->cls(); + graphics->print("zo", 8, 0); + picoRam.hwState.spriteSheetMemMapping = 0x60; + + graphics->spr(1, 0, 64, 1.0, 1.0, false, false); + + std::vector expectedPoints = { + {0,64,6}, + {1,64,6}, + {2,64,6}, + {3,64,0}, + {4,64,0}, + {5,64,6}, + {6,64,6}, + {7,64,0}, + {8,64,0}, + {0,65,0}, + {1,65,0}, + {2,65,6}, + {3,65,0}, + {4,65,6}, + {5,65,0}, + {6,65,6}, + {7,65,0}, + {8,65,0}, + {0,66,0}, + {1,66,6}, + {2,66,0}, + {3,66,0}, + {4,66,6}, + {5,66,0}, + {6,66,6}, + {7,66,0}, + {8,66,0}, + {0,67,6}, + {1,67,0}, + {2,67,0}, + {3,67,0}, + {4,67,6}, + {5,67,0}, + {6,67,6}, + {7,67,0}, + {8,67,0}, + {0,68,6}, + {1,68,6}, + {2,68,6}, + {3,68,0}, + {4,68,6}, + {5,68,6}, + {6,68,0}, + {7,68,0}, + {8,68,0}, + {0,69,0}, + {1,69,0}, + {2,69,0}, + {3,69,0}, + {4,69,0}, + {5,69,0}, + {6,69,0}, + {7,69,0}, + {8,69,0}, + {0,70,0}, + {1,70,0}, + {2,70,0}, + {3,70,0}, + {4,70,0}, + {5,70,0}, + {6,70,0}, + {7,70,0}, + {8,70,0}, + {0,71,0}, + {1,71,0}, + {2,71,0}, + {3,71,0}, + {4,71,0}, + {5,71,0}, + {6,71,0}, + {7,71,0}, + {8,71,0}, + {0,72,0}, + {1,72,0}, + {2,72,0}, + {3,72,0}, + {4,72,0}, + {5,72,0}, + {6,72,0}, + {7,72,0}, + {8,72,0} + }; + + checkPoints(graphics, expectedPoints); + } + SUBCASE("Remap screen to spritesheet"){ + graphics->cls(); + graphics->sset(125,125,4); + graphics->sset(126,126,5); + graphics->sset(127,127,6); + graphics->sset(128,128,13); + + picoRam.hwState.screenDataMemMapping = 0x00; + + std::vector expectedPoints = { + {124,124,0}, + {125,124,0}, + {126,124,0}, + {127,124,0}, + {128,124,0}, + {124,125,0}, + {125,125,4}, + {126,125,0}, + {127,125,0}, + {128,125,0}, + {124,126,0}, + {125,126,0}, + {126,126,5}, + {127,126,0}, + {128,126,0}, + {124,127,0}, + {125,127,0}, + {126,127,0}, + {127,127,6}, + {128,127,0}, + {124,128,0}, + {125,128,0}, + {126,128,0}, + {127,128,0}, + {128,128,0} + + }; + + checkPoints(graphics, expectedPoints); + } + SUBCASE("change map width"){ + picoRam.data[0x5f57] = 4; + + for(int y = 0; y < 8; y++){ + for(int x = 0; x < 32; x++) { + graphics->sset(x,y,(x/8) + 1); + } + } + + graphics->mset(0, 0, 1); + graphics->mset(0, 1, 2); + graphics->mset(1, 0, 2); + graphics->mset(1, 1, 1); + + graphics->mset(2, 2046, 2); + graphics->mset(2, 2047, 3); + graphics->mset(3, 2046, 3); + graphics->mset(3, 2047, 2); + + graphics->cls(); + + graphics->map(0, 0, 0, 0, 4, 8); + graphics->map(0, 2040, 64, 64, 4, 8); + + CHECK_EQ(picoRam.hwState.widthOfTheMap, 4); + CHECK_EQ(graphics->mget(0,0), 1); + CHECK_EQ(graphics->mget(3,2047), 2); + + std::vector expectedPoints = { + {6,6,2}, + {14,6,3}, + {6,14,3}, + {14,14,2}, + + {82,118,3}, + {90,118,4}, + {82,126,4}, + {90,126,3}, + }; + + checkPoints(graphics, expectedPoints); + } +SUBCASE("use extended ram for big map"){ + picoRam.data[0x5f56] = 0x80; + + for(int y = 0; y < 8; y++){ + for(int x = 64; x < 96; x++) { + graphics->sset(x,y,(x/8) + 1); + } + } + + graphics->mset(0, 0, 8); + graphics->mset(0, 1, 9); + graphics->mset(1, 0, 9); + graphics->mset(1, 1, 8); + + graphics->mset(126, 254, 9); + graphics->mset(126, 255, 10); + graphics->mset(127, 254, 10); + graphics->mset(127, 255, 9); + + graphics->cls(); + + graphics->map(0, 0, 8, 8, 2, 2); + graphics->map(126, 254, 112, 112, 2, 2); + + CHECK_EQ(picoRam.hwState.mapMemMapping, 0x80); + CHECK_EQ(graphics->mget(0,0), 8); + CHECK_EQ(picoRam.data[0x8000], 8); + CHECK_EQ(picoRam.userData[0], 8); + CHECK_EQ(graphics->mget(127,255), 9); + CHECK_EQ(picoRam.data[0xFFFF], 9); + CHECK_EQ(picoRam.userData[0x7FFF], 9); + + std::vector expectedPoints = { + {12,12,9}, + {20,12,10}, + {12,20,10}, + {20,20,9}, + + {116,116,10}, + {124,116,11}, + {116,124,11}, + {124,124,10}, + }; + + checkPoints(graphics, expectedPoints); + } + SUBCASE("use only part of extended ram for big map"){ + picoRam.data[0x5f56] = 0xb6; + //18944 total map bytes, 148 lines (using default 128 width) + + for(int y = 0; y < 8; y++){ + for(int x = 64; x < 96; x++) { + graphics->sset(x,y,(x/8) + 3); + } + } + + graphics->mset(0, 0, 8); + graphics->mset(0, 1, 9); + graphics->mset(1, 0, 9); + graphics->mset(1, 1, 8); + + graphics->mset(126, 146, 9); + graphics->mset(126, 147, 10); + graphics->mset(127, 146, 10); + graphics->mset(127, 147, 9); + + graphics->cls(); + + graphics->map(0, 0, 8, 8, 2, 2); + graphics->map(126, 146, 112, 112, 2, 2); + + CHECK_EQ(picoRam.hwState.mapMemMapping, 0xb6); + CHECK_EQ(picoRam.data[0x5f56], 0xb6); + CHECK_EQ(graphics->mget(0,0), 8); + CHECK_EQ(picoRam.data[0xb600], 8); + CHECK_EQ(graphics->mget(127,147), 9); + CHECK_EQ(picoRam.data[0xFFFF], 9); + + std::vector expectedPoints = { + {12,12,11}, + {20,12,12}, + {12,20,12}, + {20,20,11}, + + {116,116,12}, + {124,116,13}, + {116,124,13}, + {124,124,12}, + }; + + checkPoints(graphics, expectedPoints); + } + + //general teardown diff --git a/test/vmtests.cpp b/test/vmtests.cpp index 4be3221e..18d35eec 100644 --- a/test/vmtests.cpp +++ b/test/vmtests.cpp @@ -14,6 +14,7 @@ TEST_CASE("Vm memory functions") { StubHost* stubHost = new StubHost(); PicoRam* memory = new PicoRam(); + memory->Reset(); Graphics* graphics = new Graphics(get_font_data(), memory); Input* input = new Input(memory); Audio* audio = new Audio(memory); @@ -23,18 +24,27 @@ TEST_CASE("Vm memory functions") { SUBCASE("memory data stats with 0"){ CHECK(memory->data[0] == 0); } - SUBCASE("resetting memory zeroes out everything except general use and color bitmask"){ - for(int i = 0; i < 0x8000; ++i) { + SUBCASE("resetting memory zeroes out everything except general use, color bitmask, screen data mapping, and map width"){ + for(int i = 0; i < 0x10000; ++i) { memory->data[i] = i & 255; } memory->Reset(); bool correctValues = true; - for(int i = 0; i < 0x8000; ++i) { + for(int i = 0; i < 0x10000; ++i) { if (i == 0x5f5e) { correctValues &= memory->data[i] == 255; } + else if (i == 0x5f55) { + correctValues &= memory->data[i] == 0x60; + } + else if (i == 0x5f56) { + correctValues &= memory->data[i] == 0x20; + } + else if (i == 0x5f57) { + correctValues &= memory->data[i] == 128; + } else if (i < 0x4300 || i > 0x55ff) { correctValues &= memory->data[i] == 0; } @@ -43,10 +53,10 @@ TEST_CASE("Vm memory functions") { CHECK(correctValues); } SUBCASE("size of PicoRam struct corrct"){ - CHECK(sizeof(PicoRam) == 0x8000); + CHECK(sizeof(PicoRam) == 0x10000); } SUBCASE("size of data correct"){ - CHECK(sizeof(memory->data) == 0x8000); + CHECK(sizeof(memory->data) == 0x10000); } SUBCASE("simple peek and poke"){ vm->vm_poke(2, 232); @@ -272,10 +282,10 @@ TEST_CASE("Vm memory functions") { CHECK_EQ(input->btn(2), false); CHECK_EQ(input->btn(3), true); } - SUBCASE("poking unknown input block") { - vm->vm_poke(0x5f54, 53); + SUBCASE("poking print attributes") { + vm->vm_poke(0x5f58, 53); - CHECK_EQ(memory->hwState.unknownInputBlock[0], 53); + CHECK_EQ(memory->hwState.printAttributes[0], 53); } SUBCASE("poking btnp repeat delay") { vm->vm_poke(0x5f5c, 10); @@ -312,7 +322,7 @@ TEST_CASE("Vm memory functions") { CHECK_EQ(memory->hwState.gpioPins[0], 192); } - SUBCASE("poking screen data (first two pixels") { + SUBCASE("poking screen data (first two pixels)") { //195: 1100 0011 (left pixel 3, right pixel 12) vm->vm_poke(0x6000, 195); @@ -320,7 +330,7 @@ TEST_CASE("Vm memory functions") { CHECK_EQ(graphics->pget(0, 0), 3); CHECK_EQ(graphics->pget(1, 0), 12); } - SUBCASE("poking screen data (last two pixels") { + SUBCASE("poking screen data (last two pixels)") { //210: 1101 0010 vm->vm_poke(0x7fff, 210); @@ -328,6 +338,18 @@ TEST_CASE("Vm memory functions") { CHECK_EQ(graphics->pget(126, 127), 2); CHECK_EQ(graphics->pget(127, 127), 13); } + SUBCASE("poking user data (first byte)") { + vm->vm_poke(0x8000, 129); + + CHECK_EQ(memory->data[0x8000], 129); + CHECK_EQ(memory->userData[0], 129); + } + SUBCASE("poking user data (last byte)") { + vm->vm_poke(0xffff, 211); + + CHECK_EQ(memory->data[0xffff], 211); + CHECK_EQ(memory->userData[32767], 211); + } SUBCASE("Loading cart copies cart rom to memory") { vm->LoadCart("cartparsetest.p8.png");