fix: Use HALF_REFRESH for sleep and boot instead of FULL (#2588)

Changes all sleep screen display refreshes and boot screens 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. Also adds X3 wb_gc
passive byte (the one-byte 0x54 to 0x00, matching stock's passive WB
table in the gc nudge bank): Every X3 grayscale render, reader AA,
covers, and the sleep-cover nudge, gets it.
This commit is contained in:
Justin Mitchell
2026-07-13 17:06:09 -04:00
committed by GitHub
parent 2a19eb9e0d
commit c5787d1cc1
2 changed files with 13 additions and 7 deletions
+12 -6
View File
@@ -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);
}