From fc9e85aea07187ef43bfda99eaef498666a8fc20 Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Sun, 12 Oct 2025 00:20:22 +0200 Subject: [PATCH] fast wide scale --- Firmware/sdk/apps/gameboy/src/gameboy_app.cpp | 97 ++++++++++++++++--- 1 file changed, 83 insertions(+), 14 deletions(-) diff --git a/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp b/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp index 5bf966c..c3edd72 100644 --- a/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp +++ b/Firmware/sdk/apps/gameboy/src/gameboy_app.cpp @@ -245,7 +245,7 @@ public: private: enum class Mode { Browse, Running }; - enum class ScaleMode { Original, FullHeight }; + enum class ScaleMode { Original, FullHeight, FullHeightWide }; struct PerfTracker { enum class CallbackKind { RomRead, CartRamRead, CartRamWrite, LcdDraw, Error }; @@ -492,10 +492,16 @@ private: (((LCD_WIDTH * cardboy::sdk::kDisplayHeight + LCD_HEIGHT / 2) / LCD_HEIGHT) + 7) & ~7; static constexpr int kFullHeightOffsetX = (((cardboy::sdk::kDisplayWidth - kFullHeightScaledWidth) / 2) / 8) * 8; + static constexpr int kFullHeightWideWidth = LCD_WIDTH * 2; + static constexpr int kFullHeightWideOffsetX = (((cardboy::sdk::kDisplayWidth - kFullHeightWideWidth) / 2) / 8) * 8; + static_assert(kFullHeightScaledWidth % 8 == 0); static_assert(kFullHeightOffsetX % 8 == 0); static_assert(kOriginalOffsetX % 8 == 0); static_assert(kFullHeightOffsetX + kFullHeightScaledWidth <= cardboy::sdk::kDisplayWidth); + static_assert(kFullHeightWideWidth % 8 == 0); + static_assert(kFullHeightWideOffsetX % 8 == 0); + static_assert(kFullHeightWideOffsetX + kFullHeightWideWidth <= cardboy::sdk::kDisplayWidth); inline static constexpr std::array kFullHeightColumnBounds = []() constexpr { std::array bounds{}; @@ -704,12 +710,21 @@ private: } void toggleScaleMode() { - if (scaleMode == ScaleMode::Original) - scaleMode = ScaleMode::FullHeight; - else - scaleMode = ScaleMode::Original; + switch (scaleMode) { + case ScaleMode::Original: + scaleMode = ScaleMode::FullHeight; + setStatus("Scale: Full height"); + break; + case ScaleMode::FullHeight: + scaleMode = ScaleMode::FullHeightWide; + setStatus("Scale: Full height 2x"); + break; + case ScaleMode::FullHeightWide: + scaleMode = ScaleMode::Original; + setStatus("Scale: Original"); + break; + } frameDirty = true; - setStatus(scaleMode == ScaleMode::FullHeight ? "Scale: Full height" : "Scale: Original"); } void handleBrowserInput(const InputState& input) { @@ -980,11 +995,23 @@ private: char fpsValueBuf[16]; std::snprintf(fpsValueBuf, sizeof(fpsValueBuf), "%u", static_cast(fpsCurrent)); const std::string fpsValue(fpsValueBuf); - const std::string fpsLabel = "FPS"; - const std::string fpsText = fpsValue + " FPS"; - const std::string scaleHint = (scaleMode == ScaleMode::FullHeight) ? "START+B NORMAL" : "START+B SCALE"; + const std::string fpsLabel = "FPS"; + const std::string fpsText = fpsValue + " FPS"; - if (scaleMode == ScaleMode::FullHeight) { + std::string scaleHint; + switch (scaleMode) { + case ScaleMode::Original: + scaleHint = "START+B FULL"; + break; + case ScaleMode::FullHeight: + scaleHint = "START+B WIDE"; + break; + case ScaleMode::FullHeightWide: + scaleHint = "START+B NORMAL"; + break; + } + + if (scaleMode == ScaleMode::FullHeight || scaleMode == ScaleMode::FullHeightWide) { const int textScale = 1; const int screenHeight = framebuffer.height(); const int screenWidth = framebuffer.width(); @@ -1241,6 +1268,40 @@ private: } } + static void drawLineFullHeightWide(GameboyApp& self, const uint8_t pixels[160], int srcLine) { + Framebuffer& fb = self.framebuffer; + const int yStart = kFullHeightRowBounds[static_cast(srcLine)]; + const int yEnd = kFullHeightRowBounds[static_cast(srcLine) + 1]; + + CARDBOY_CHECK(yEnd > yStart); + CARDBOY_CHECK((kFullHeightWideOffsetX % 8) == 0); + + for (int dstY = yStart; dstY < yEnd; ++dstY) { + const int yParity = dstY & 1; + int dstX = kFullHeightWideOffsetX; + + for (int srcX = 0; srcX < LCD_WIDTH; srcX += 4) { + const uint8_t p0 = pixels[srcX + 0]; + const uint8_t p1 = pixels[srcX + 1]; + const uint8_t p2 = pixels[srcX + 2]; + const uint8_t p3 = pixels[srcX + 3]; + + const uint8_t p4a = static_cast(p0 | (p0 << 2) | (p1 << 4) | (p1 << 6)); + const uint8_t p4b = static_cast(p2 | (p2 << 2) | (p3 << 4) | (p3 << 6)); + + const int xParity = dstX & 1; + const uint8_t n0 = kNibbleLUT[yParity][xParity][p4a]; + const uint8_t n1 = kNibbleLUT[yParity][xParity][p4b]; + const uint8_t pack = static_cast((n0 << 4) | (n1 & 0x0F)); + + fb.drawBits8(dstX, dstY, pack); + dstX += 8; + } + + CARDBOY_CHECK(dstX == kFullHeightWideOffsetX + kFullHeightWideWidth); + } + } + public: static uint8_t romRead(struct gb_s* gb, const uint_fast32_t addr) { @@ -1297,10 +1358,18 @@ private: Framebuffer& fb = self->framebuffer; fb.frameReady(); - if (self->scaleMode == ScaleMode::FullHeight) - drawLineFullHeight(*self, pixels, static_cast(line)); - else - drawLineOriginal(*self, pixels, static_cast(line)); + switch (self->scaleMode) { + case ScaleMode::FullHeight: + drawLineFullHeight(*self, pixels, static_cast(line)); + break; + case ScaleMode::FullHeightWide: + drawLineFullHeightWide(*self, pixels, static_cast(line)); + break; + case ScaleMode::Original: + default: + drawLineOriginal(*self, pixels, static_cast(line)); + break; + } } static const char* initErrorToString(enum gb_init_error_e err) {