From 3f0a190f63336a57c76b6b3d8b2c6d7a66c633c5 Mon Sep 17 00:00:00 2001 From: Crazypedia Date: Thu, 30 Jul 2026 20:25:07 -0400 Subject: [PATCH] fix(gdeq031t10): only commit the shadow after the panel confirms shadow_framebuffer records what the panel is currently showing, but both refresh paths updated it before the refresh was confirmed, so a failed trigger or a BUSY timeout left it describing content that was never displayed. That misdescription compounds: the shadow is sent to the controller as CMD_DATA_START_OLD, so the per-pixel transitions get computed from an image the panel never held, and draw_bitmap() diffs against it, so a region matching a wrong shadow is treated as clean and never repainted. Both paths now commit only after wait_while_busy() confirms. refresh_full() also sends new data straight from the render buffer, which drops a full-framebuffer copy from the path. Co-Authored-By: Claude Opus 5 --- .../gdeq031t10-module/source/gdeq031t10.cpp | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/Drivers/gdeq031t10-module/source/gdeq031t10.cpp b/Drivers/gdeq031t10-module/source/gdeq031t10.cpp index e797cbf11..d34593ecb 100644 --- a/Drivers/gdeq031t10-module/source/gdeq031t10.cpp +++ b/Drivers/gdeq031t10-module/source/gdeq031t10.cpp @@ -226,9 +226,9 @@ static void refresh_full(Gdeq031t10Internal* internal, bool mirror_180, enum Gde return; } - LOG_I(TAG, "memcpy render_bitmap -> shadow_buffer"); - std::memcpy(internal->shadow_framebuffer, render_bitmap, FRAMEBUFFER_SIZE); - if (!write_command(internal, CMD_DATA_START_NEW) || !write_data(internal, internal->shadow_framebuffer, FRAMEBUFFER_SIZE)) { + // Sent straight from the render buffer: the shadow still has to describe what the panel + // is showing right now until this refresh is confirmed below. + if (!write_command(internal, CMD_DATA_START_NEW) || !write_data(internal, render_bitmap, FRAMEBUFFER_SIZE)) { LOG_E(TAG, "Failed to send new frame data"); return; } @@ -238,7 +238,13 @@ static void refresh_full(Gdeq031t10Internal* internal, bool mirror_180, enum Gde return; } delay_millis(1); // datasheet requires >=200us settle before polling BUSY - if (!wait_while_busy(internal)) { + if (wait_while_busy(internal)) { + // Confirmed: the panel now shows this frame, so the shadow can too. + std::memcpy(internal->shadow_framebuffer, render_bitmap, FRAMEBUFFER_SIZE); + } else { + // Leave the shadow alone: the panel did not confirm, so it still holds the previous + // frame. Claiming otherwise would make the next update compute its transitions from + // content the panel never displayed, and hide the difference from the change scan. LOG_E(TAG, "Full refresh did not complete"); } @@ -295,15 +301,13 @@ static void refresh_window(Gdeq031t10Internal* internal, bool mirror_180, const return; } - // New region: render data, and update the shadow for this region as we go. + // New region: render data only. The shadow still has to describe what the panel is + // showing until this refresh is confirmed below. n = 0; for (int row = first_row; row <= last_row; row++) { const size_t base = static_cast(row) * BYTES_PER_ROW + first_byte_col; - for (int c = 0; c < width_bytes; c++) { - const uint8_t value = render_bitmap[base + c]; - internal->region_buffer[n++] = value; - internal->shadow_framebuffer[base + c] = value; - } + std::memcpy(&internal->region_buffer[n], &render_bitmap[base], width_bytes); + n += width_bytes; } if (!write_command(internal, CMD_DATA_START_NEW) || !write_data(internal, internal->region_buffer, n)) { LOG_E(TAG, "Failed to send new window data"); @@ -311,14 +315,25 @@ static void refresh_window(Gdeq031t10Internal* internal, bool mirror_180, const return; } + bool confirmed = false; if (write_command(internal, CMD_DISPLAY_REFRESH)) { delay_millis(1); - if (!wait_while_busy(internal)) { + confirmed = wait_while_busy(internal); + if (!confirmed) { LOG_E(TAG, "Window refresh did not complete"); } } else { LOG_E(TAG, "Failed to trigger window refresh"); } + + // Only commit the region the panel confirmed; on failure the shadow keeps describing + // the previous content, so the next change scan still sees this region as dirty. + if (confirmed) { + for (int row = first_row; row <= last_row; row++) { + const size_t base = static_cast(row) * BYTES_PER_ROW + first_byte_col; + std::memcpy(&internal->shadow_framebuffer[base], &render_bitmap[base], width_bytes); + } + } write_command(internal, CMD_PARTIAL_OUT); }