From 379f49d1656408e7f155aebf227bb17efdf41725 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Mon, 13 Jul 2026 01:08:05 -0400 Subject: [PATCH] Use HALF_REFRESH for sleep screens instead of FULL Changes all sleep screen display refreshes from FULL_REFRESH to HALF_REFRESH to match OEM firmware behavior. The stock firmware uses a single-pass 0xD7 waveform for sleep screens, not the multi-flash GC waveform (0xF7) that FULL_REFRESH triggers. For grayscale mode, HALF_REFRESH is required because the gray nudge LUT is calibrated against the pixel charge state left by the single-pass waveform; using FULL_REFRESH causes blotchy noise in gray areas. --- src/activities/boot_sleep/SleepActivity.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index e7ff4317..a181df16 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -149,6 +149,10 @@ void SleepActivity::renderCustomSleepScreen() const { renderDefaultSleepScreen(); } +// Sleep screens paint with a single HALF refresh (stock parity): the OEM X4 +// firmware's only clean refresh in normal operation is the single-pass 0xD7 +// sequence, used once for the sleep image. It never runs the multi-flash GC +// waveform (0xF7) that FULL_REFRESH selects (#2471's blinking complaint). void SleepActivity::renderDefaultSleepScreen() const { const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); @@ -163,7 +167,7 @@ void SleepActivity::renderDefaultSleepScreen() const { renderer.invertScreen(); } - renderer.displayBuffer(HalDisplay::FULL_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); } void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { @@ -219,11 +223,13 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { } if (hasGreyscale) { - // OEM grayscale pipeline base: use a full sleep-screen paint so the panel - // enters deep sleep from a clean B/W baseline before the gray nudge refresh. - renderer.displayGrayscaleBase(HalDisplay::FULL_REFRESH); + // OEM grayscale pipeline base. Must stay HALF: the gray nudge LUT is + // calibrated against the pixel state the single-pass HALF waveform leaves + // behind. A FULL (GC) base parks pixels in a different charge state and + // the differential nudge then lands unevenly (blotchy noise in gray areas). + renderer.displayGrayscaleBase(HalDisplay::HALF_REFRESH); } else { - renderer.displayBuffer(HalDisplay::FULL_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); } if (hasGreyscale) { @@ -331,5 +337,5 @@ void SleepActivity::renderLastScreenSleepScreen() const { void SleepActivity::renderBlankSleepScreen() const { renderer.clearScreen(); - renderer.displayBuffer(HalDisplay::FULL_REFRESH); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); }