From b58979a766e8741a14c8d68d3df21cfdac2f3b76 Mon Sep 17 00:00:00 2001 From: Vladimir Smitka Date: Sat, 19 Sep 2026 17:49:56 +0000 Subject: [PATCH 1/2] picogame: restore the panel to RGB565 when a program ends A program that switched the panel to RGB444 (COLMOD 0x53) and then exited left it there: the panel keeps its pixel format, and the CircuitPython console writes RGB565, so the console came up sheared and colour-fringed - it looked like a broken display, not a setting. Remember the display picogame_set_pixel_format() switched, and put it back from cleanup_after_vm() while the bus is still alive (before reset_displays()). A released display reads as a None bus, the same check reset_displays() makes, so it is skipped. Seen on a PyBadge after a game ran in RGB444; affects every board built with CIRCUITPY_PICOGAME_RGB444. --- main.c | 9 +++++++++ shared-module/picogame/__init__.c | 18 ++++++++++++++++++ shared-module/picogame/__init__.h | 4 ++++ 3 files changed, 31 insertions(+) diff --git a/main.c b/main.c index 8b1ebd4b9fe..5828cbe1c27 100644 --- a/main.c +++ b/main.c @@ -80,6 +80,10 @@ #include "shared-bindings/epaperdisplay/EPaperDisplay.h" #endif +#if CIRCUITPY_PICOGAME_RGB444 +#include "shared-module/picogame/__init__.h" +#endif + #if CIRCUITPY_KEYPAD #include "shared-module/keypad/__init__.h" #endif @@ -374,6 +378,11 @@ static void cleanup_after_vm(mp_obj_t exception) { atexit_reset(); #endif + // Restore the panel's pixel format while the display bus is still alive. + #if CIRCUITPY_PICOGAME_RGB444 + picogame_reset(); + #endif + // Turn off the display and flush the filesystem before the heap disappears. #if CIRCUITPY_DISPLAYIO reset_displays(); diff --git a/shared-module/picogame/__init__.c b/shared-module/picogame/__init__.c index ddd700959ed..48b50e2b9d9 100644 --- a/shared-module/picogame/__init__.c +++ b/shared-module/picogame/__init__.c @@ -1029,6 +1029,10 @@ void picogame_set_invert(picogame_output_t *display, bool on) { // Set the panel pixel format (COLMOD 0x3A): rgb444 -> 12-bit RGB444 (0x53), else 16-bit RGB565 // (0x55). Asserting it on every Display construct also recovers from a previous program that left // the panel in the other format (survives soft reset). +// The display currently switched to RGB444, or NULL; picogame_reset() restores it to RGB565 at +// the end of the program. +static picogame_output_t *rgb444_display = NULL; + void picogame_set_pixel_format(picogame_output_t *display, bool rgb444) { uint8_t cmd = 0x3A; uint8_t param = rgb444 ? 0x53 : 0x55; @@ -1038,6 +1042,20 @@ void picogame_set_pixel_format(picogame_output_t *display, bool rgb444) { display->bus.send(display->bus.bus, DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, &cmd, 1); display->bus.send(display->bus.bus, DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, ¶m, 1); displayio_display_bus_end_transaction(&display->bus); + rgb444_display = rgb444 ? display : NULL; +} + +// Called at the end of a program, before reset_displays() takes the bus away. +void picogame_reset(void) { + if (rgb444_display == NULL) { + return; + } + // A released display has a None bus. + if (mp_obj_get_type(rgb444_display->bus.bus) == &mp_type_NoneType) { + rgb444_display = NULL; + return; + } + picogame_set_pixel_format(rgb444_display, false); // clears rgb444_display } // Pack a strip of `npix` (must be even) WIRE-order RGB565 pixels IN-PLACE to ST7789 12-bit RGB444 diff --git a/shared-module/picogame/__init__.h b/shared-module/picogame/__init__.h index a4377292219..716c8db557a 100644 --- a/shared-module/picogame/__init__.h +++ b/shared-module/picogame/__init__.h @@ -248,6 +248,10 @@ bool picogame_fb_take_invert_dirty(void); // Set panel pixel format (COLMOD): rgb444 -> 12-bit RGB444, else 16-bit RGB565. void picogame_set_pixel_format(picogame_output_t *display, bool rgb444); +// Put a panel left in RGB444 back to RGB565 at the end of a program, so the console stays +// readable. Call before reset_displays(). +void picogame_reset(void); + // Pack `npix` (even) wire-order RGB565 pixels in `buf` IN-PLACE to 12-bit RGB444; returns bytes. size_t picogame_pack_rgb444(uint16_t *buf, size_t npix); #endif From d65309510c9bfa484bfb781f77e1a87c8bcfb073 Mon Sep 17 00:00:00 2001 From: Vladimir Smitka Date: Sat, 19 Sep 2026 20:30:08 +0000 Subject: [PATCH 2/2] picogame: pack RGB444 on the generic strip path too pg.render() (and everything built on it: HudBar, TextBox, Menu) goes through picogame_render_region -> picogame_out_strip_send, which always sent the composited strip as RGB565. On a panel a fast Display had switched to 12-bit RGB444 the panel reads those bytes as RGB444, so the immediate output shears and colour-shifts while the scene underneath is fine (HUD band on a PyBadge and a PicoPad, 2026-09-19). Pack the strip in place with picogame_pack_rgb444 when the target is the panel picogame_set_pixel_format switched, and widen the region to even x bounds first, as the fast Display backends already do. +36 B text on pybadge, RGB444 boards only; nothing changes for boards without the flag. Verified with a HudBar + Tilemap check pattern on a PyBadge (SAMD51) and a PicoPad (RP2040): the HUD band is clean in 12-bit mode on both. --- shared-module/picogame/__init__.c | 25 +++++++++++++++++++++---- shared-module/picogame/__init__.h | 3 ++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/shared-module/picogame/__init__.c b/shared-module/picogame/__init__.c index 48b50e2b9d9..ee69e6ec4ac 100644 --- a/shared-module/picogame/__init__.c +++ b/shared-module/picogame/__init__.c @@ -976,6 +976,12 @@ bool picogame_strip_begin( return true; } +#if CIRCUITPY_PICOGAME_RGB444 +// The display currently switched to RGB444, or NULL. The strip path packs for it and +// picogame_reset() restores it to RGB565 at the end of the program. +static picogame_output_t *rgb444_display = NULL; +#endif + // --- output transport seam (busdisplay backend): the ONLY per-strip display ops the generic // picogame_render_region orchestrator below touches, so a non-CircuitPython port (MicroPython // framebuf/SPI) swaps just strip_begin + these two + set_invert/set_pixel_format. See __init__.h. @@ -993,6 +999,14 @@ void picogame_render_region( int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t background, int ox, int oy) { + #if CIRCUITPY_PICOGAME_RGB444 + // RGB444 packs 2 px into 3 bytes, so widen the region to even x bounds. + bool rgb444 = (display == rgb444_display); + if (rgb444) { + x0 &= ~1; + x1 = (x1 + 1) & ~1; + } + #endif int region_w, strip_h; int cx0 = x0, cy0 = y0, cx1 = x1, cy1 = y1; // strip_begin clamps these to the panel in place if (!picogame_strip_begin(display, &cx0, &cy0, &cx1, &cy1, buffer_pixels, ®ion_w, &strip_h)) { @@ -1001,7 +1015,13 @@ void picogame_render_region( for (int sy = cy0; sy < cy1; sy += strip_h) { int sh = picogame_imin(strip_h, cy1 - sy); mp_obj_t exc = picogame_blit_strip_layers(buffer, region_w, sy, sh, cx0, items, kinds, n, background, ox, oy); - picogame_out_strip_send(display, (uint8_t *)buffer, region_w * sh * 2); + size_t nbytes = (size_t)region_w * sh * 2; + #if CIRCUITPY_PICOGAME_RGB444 + if (rgb444) { + nbytes = picogame_pack_rgb444(buffer, (size_t)region_w * sh); // in place, 3/4 the bytes + } + #endif + picogame_out_strip_send(display, (uint8_t *)buffer, nbytes); if (exc != MP_OBJ_NULL) { // a StripDraw callback raised a BaseException: close the picogame_out_strip_end(display); // bus, then re-raise (Ctrl-C / reload) nlr_raise(MP_OBJ_TO_PTR(exc)); @@ -1029,9 +1049,6 @@ void picogame_set_invert(picogame_output_t *display, bool on) { // Set the panel pixel format (COLMOD 0x3A): rgb444 -> 12-bit RGB444 (0x53), else 16-bit RGB565 // (0x55). Asserting it on every Display construct also recovers from a previous program that left // the panel in the other format (survives soft reset). -// The display currently switched to RGB444, or NULL; picogame_reset() restores it to RGB565 at -// the end of the program. -static picogame_output_t *rgb444_display = NULL; void picogame_set_pixel_format(picogame_output_t *display, bool rgb444) { uint8_t cmd = 0x3A; diff --git a/shared-module/picogame/__init__.h b/shared-module/picogame/__init__.h index 716c8db557a..412856716b5 100644 --- a/shared-module/picogame/__init__.h +++ b/shared-module/picogame/__init__.h @@ -184,7 +184,8 @@ void picogame_blit_bitmap_affine( // // Strip-path contract a backend provides: // picogame_strip_begin - open a window for [x0,y0,x1,y1); return strip geometry -// picogame_out_strip_send - push one composited strip (region_w*sh px, wire RGB565) +// picogame_out_strip_send - push one composited strip (region_w*sh px, wire RGB565; packed to +// RGB444 first when that panel is in 12-bit mode) // picogame_out_strip_end - close the transaction // picogame_set_invert - panel hardware colour inversion (a free full-screen flash) // picogame_set_pixel_format - panel COLMOD (RGB565/RGB444), when CIRCUITPY_PICOGAME_RGB444