From d8e943080b3b7399c50b237a0f86ad8b4532c576 Mon Sep 17 00:00:00 2001 From: Andrew Dunai Date: Tue, 30 Apr 2024 02:20:35 +0300 Subject: [PATCH] sdk: implement interlacing keira: add optional interlacing for NES --- firmware/keira/src/app.cpp | 4 +++- firmware/keira/src/app.h | 6 ++++-- firmware/keira/src/appmanager.cpp | 6 +++++- firmware/keira/src/apps/nes/driver.cpp | 15 +++++++++++++++ firmware/keira/src/apps/nes/driver.h | 5 +++++ firmware/keira/src/apps/nes/nesapp.cpp | 6 +++++- sdk/lib/lilka/src/lilka/display.cpp | 9 +++++++++ sdk/lib/lilka/src/lilka/display.h | 1 + 8 files changed, 47 insertions(+), 5 deletions(-) diff --git a/firmware/keira/src/app.cpp b/firmware/keira/src/app.cpp index 34a166aa..c77fd48d 100644 --- a/firmware/keira/src/app.cpp +++ b/firmware/keira/src/app.cpp @@ -12,7 +12,8 @@ App::App(const char* name, uint16_t x, uint16_t y, uint16_t w, uint16_t h) : isDrawQueued(false), backCanvasMutex(xSemaphoreCreateMutex()), stackSize(8192), - appCore(0) { + appCore(0), + frame(0) { // Clear buffers canvas->fillScreen(0); backCanvas->fillScreen(0); @@ -88,6 +89,7 @@ void App::queueDraw() { canvas = backCanvas; backCanvas = temp; isDrawQueued = true; + frame++; xSemaphoreGive(backCanvasMutex); taskYIELD(); } diff --git a/firmware/keira/src/app.h b/firmware/keira/src/app.h index 74d7dbf0..99c2658d 100644 --- a/firmware/keira/src/app.h +++ b/firmware/keira/src/app.h @@ -4,9 +4,10 @@ #include #include -typedef enum { +typedef enum : uint32_t { APP_FLAG_NONE = 0, - APP_FLAG_FULLSCREEN = 1, + APP_FLAG_FULLSCREEN = 1 << 0, + APP_FLAG_INTERLACED = 1 << 1, } AppFlags; /// Клас, що представляє додаток для Кіри. @@ -80,6 +81,7 @@ class App { void releaseBackCanvas(); lilka::Canvas* backCanvas; + uint64_t frame; private: static void _run(void* data); diff --git a/firmware/keira/src/appmanager.cpp b/firmware/keira/src/appmanager.cpp index cc719f31..75fc78c1 100644 --- a/firmware/keira/src/appmanager.cpp +++ b/firmware/keira/src/appmanager.cpp @@ -131,7 +131,11 @@ void AppManager::loop() { renderToast(topApp->backCanvas); } if (app->needsRedraw()) { - lilka::display.drawCanvas(app->backCanvas); + if (app->flags & AppFlags::APP_FLAG_INTERLACED) { + lilka::display.drawCanvasInterlaced(app->backCanvas, app->frame % 2); + } else { + lilka::display.drawCanvas(app->backCanvas); + } app->markClean(); } app->releaseBackCanvas(); diff --git a/firmware/keira/src/apps/nes/driver.cpp b/firmware/keira/src/apps/nes/driver.cpp index ee338a0b..edaba555 100644 --- a/firmware/keira/src/apps/nes/driver.cpp +++ b/firmware/keira/src/apps/nes/driver.cpp @@ -76,12 +76,26 @@ void Driver::freeFrite(int numDirties, rect_t* dirtyRects) { bmp_destroy(&bitmap); } +bool odd = true; + void Driver::customBlit(bitmap_t* bmp, int numDirties, rect_t* dirtyRects) { last_frame_duration = micros() - last_render; last_render = micros(); lilka::Canvas* canvas = app->canvas; +#ifdef INTERLACED + for (int y = odd ? 1 : 0; y < frame_height; y += 2) { + const uint8_t* line = bmp->line[y]; + for (int x = 0; x < frame_width; x++) { + uint8_t index = line[x]; + uint16_t color = nesPalette[index]; + canvas->writePixelPreclipped(x + frame_x, y + frame_y, color); + // app->canvas->drawPixel(x + frame_x, y + frame_y, color); + } + } + odd = !odd; +#else for (int y = 0; y < frame_height; y++) { const uint8_t* line = bmp->line[y]; for (int x = 0; x < frame_width; x++) { @@ -91,6 +105,7 @@ void Driver::customBlit(bitmap_t* bmp, int numDirties, rect_t* dirtyRects) { // app->canvas->drawPixel(x + frame_x, y + frame_y, color); } } +#endif // Serial.println("Draw 1 took " + String(micros() - last_render) + "us"); diff --git a/firmware/keira/src/apps/nes/driver.h b/firmware/keira/src/apps/nes/driver.h index db0db066..fb57c1fd 100644 --- a/firmware/keira/src/apps/nes/driver.h +++ b/firmware/keira/src/apps/nes/driver.h @@ -9,6 +9,11 @@ extern "C" { #include } +// Розкоментуйте, якщо ви хочете використовувати інтерлейс. +// Це збільшує FPS з ~30 до ~50 і дає більш ретро-вигляд та мерехтіння, але багатьом людям цей ефект може не сподобатися. +// Історично, більшість старих телевізорів використовували інтерлейс, тому цей ефект зробить гру більш автентичною. +// #define NESAPP_INTERLACED + class Driver { public: static void setNesApp(NesApp* app); diff --git a/firmware/keira/src/apps/nes/nesapp.cpp b/firmware/keira/src/apps/nes/nesapp.cpp index 4ca6d7f3..e98edcfd 100644 --- a/firmware/keira/src/apps/nes/nesapp.cpp +++ b/firmware/keira/src/apps/nes/nesapp.cpp @@ -4,7 +4,11 @@ NesApp::NesApp(String path) : App("NES", 0, 0, lilka::display.width(), lilka::display.height()) { argv[0] = new char[path.length() + 1]; strcpy(argv[0], path.c_str()); - setFlags(AppFlags::APP_FLAG_FULLSCREEN); +#ifdef NESAPP_INTERLACED + setFlags(static_cast(AppFlags::APP_FLAG_FULLSCREEN | AppFlags::APP_FLAG_INTERLACED)); +#else + setFlags(static_cast(AppFlags::APP_FLAG_FULLSCREEN)); +#endif } NesApp::~NesApp() { diff --git a/sdk/lib/lilka/src/lilka/display.cpp b/sdk/lib/lilka/src/lilka/display.cpp index bee42145..1bd1e85c 100644 --- a/sdk/lib/lilka/src/lilka/display.cpp +++ b/sdk/lib/lilka/src/lilka/display.cpp @@ -229,6 +229,15 @@ uint8_t* Display::getFont() { return u8g2Font; } +void Display::drawCanvasInterlaced(Canvas* canvas, bool odd) { + this->startWrite(); + for (int y = odd ? 1 : 0; y < canvas->height(); y += 2) { + this->writeAddrWindow(canvas->x(), canvas->y() + y, canvas->width(), 1); + this->writePixels(canvas->getFramebuffer() + y * canvas->width(), canvas->width()); + } + this->endWrite(); +} + void Canvas::draw16bitRGBBitmapWithTranColor( int16_t x, int16_t y, const uint16_t bitmap[], uint16_t transparent_color, int16_t w, int16_t h ) { diff --git a/sdk/lib/lilka/src/lilka/display.h b/sdk/lib/lilka/src/lilka/display.h index 15189f4b..52e53c26 100644 --- a/sdk/lib/lilka/src/lilka/display.h +++ b/sdk/lib/lilka/src/lilka/display.h @@ -266,6 +266,7 @@ class Display : public Arduino_ST7789, public GFX { int16_t x, int16_t y, const uint16_t bitmap[], uint16_t transparent_color, int16_t w, int16_t h ); uint8_t* getFont(); + void drawCanvasInterlaced(Canvas* canvas, bool odd); private: const void* splash;