Skip to content

Properly swap bytes and draw to display for direct mode in LVGL9 (BSP-700) - #609

Open
kevinresol wants to merge 2 commits into
espressif:masterfrom
kevinresol:direct_mode_swap_bytes
Open

Properly swap bytes and draw to display for direct mode in LVGL9 (BSP-700)#609
kevinresol wants to merge 2 commits into
espressif:masterfrom
kevinresol:direct_mode_swap_bytes

Conversation

@kevinresol

@kevinresol kevinresol commented Jul 15, 2025

Copy link
Copy Markdown

ESP-BSP Pull Request checklist

Note: For new BSPs create a PR with this link.

  • Version of modified component bumped
  • CI passing

Change description

Problem

In LVGL9 direct mode / full_refresh, draw buffers are screen-sized and store a complete frame at absolute coordinates. The flush path had two issues:

  1. RGB565 swap_bytes treated color_map as a partial crop (lv_draw_sw_rgb565_swap over area size). In direct mode, color_map is the full framebuffer base, so that swapped the wrong span of pixels.
  2. Last-flush presentation used full-screen esp_lcd_panel_draw_bitmap only for RGB/MIPI-DSI. Other panel types (e.g. SPI/I80) used area draw_bitmap with an unoffset full-frame color_map, which is incorrect for direct/full_refresh buffer layout.

Changes (esp_lvgl_port LVGL9)

  1. Dirty-area byte swap — new lvgl_port_draw_sw_rgb565_swap_area() swaps RGB565 endianness only within the dirty area when direct_mode and swap_bytes are enabled. Partial mode keeps the existing full-area swap.
  2. Full-frame present on last flush — for direct_mode or full_refresh, on the last flush of a frame, always draw fullscreen (0,0)→(hor,ver) for all panel types (not only RGB/DSI).
  3. Transfer waittrans_sem wait after that draw still applies only to RGB/DSI.
  4. lv_disp_flush_readyunchanged from master: still gated for RGB and for DSI under direct/full_refresh. SPI/I80 continue to complete via panel IO on_color_trans_done (avoids double-ready / early buffer reuse with DMA).

Non-goals / notes

  • Does not call lv_disp_flush_ready unconditionally at end of flush (original draft did; rebased out for SPI safety).
  • Component version not bumped; entry added under CHANGELOG → Unreleased → Features.
  • Rebased onto current espressif:master.

Residual risk

SPI/I80 direct or full_refresh may transfer a full frame on each last flush (expected full-FB present cost). Color corruption if area-swap bounds were wrong — logic is limited to LVGL9 flush in esp_lvgl_port_disp.c.


Note

Medium Risk
Touches core display flush timing and buffer layout for direct/full_refresh across panel types; wrong bounds or ready signaling could cause corruption or hangs on SPI/I80 DMA paths.

Overview
Fixes LVGL9 flush behavior when direct mode or full refresh uses a screen-sized framebuffer with RGB565 byte swap.

RGB565 swap: With direct_mode and swap_bytes, the flush path now swaps endianness only inside the dirty area via lvgl_port_draw_sw_rgb565_swap_area() instead of treating color_map as a partial buffer. Partial rendering still uses the existing full-region swap.

Present: On the last flush of a frame, all panel types get a full-screen esp_lcd_panel_draw_bitmap(0,0→hor,ver) (not only RGB/MIPI-DSI). SPI/I80 no longer use area-scoped draws against an unoffset full frame. trans_sem blocking after that draw remains RGB/DSI only.

Flush ready: Intermediate direct/full flushes that skip a draw call lv_disp_flush_ready when no transfer started, so multi-area frames do not hang on SPI/I80 while DMA completion still uses the panel IO callback when a draw runs.

Reviewed by Cursor Bugbot for commit 5c0361a. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI review requested due to automatic review settings July 15, 2025 08:32
@CLAassistant

CLAassistant commented Jul 15, 2025

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR extends direct mode support in LVGL9 by performing byte swapping only within the dirty area and generalizing full-screen drawing to all display types. Key changes include:

  • Introduce lvgl_port_draw_sw_rgb565_swap_area to swap bytes per dirty region instead of the entire buffer.
  • Update the flush callback to call the new swap function when in direct mode and adjust drawing logic to always use full-screen bitmap transfers for direct mode and full refresh.
  • Add a changelog entry under “Unreleased” for the direct mode enhancements.

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c Added area-based byte swap function and updated flush/draw logic for direct mode.
components/esp_lvgl_port/CHANGELOG.md Recorded “Properly swap bytes and draw to display for direct mode in LVGL9” in Unreleased section.
Comments suppressed due to low confidence (4)

components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c:761

  • The new function lvgl_port_draw_sw_rgb565_swap_area lacks a doc comment explaining its purpose, parameters, and behavior. Consider adding a brief description to aid future maintainability.
static void lvgl_port_draw_sw_rgb565_swap_area(void *buf, uint32_t screen_width, uint32_t screen_height, const lv_area_t *area) {

components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c:761

  • There are no unit tests for lvgl_port_draw_sw_rgb565_swap_area to verify correct byte swapping across various area sizes and boundary conditions. Adding tests would help catch edge cases.
static void lvgl_port_draw_sw_rgb565_swap_area(void *buf, uint32_t screen_width, uint32_t screen_height, const lv_area_t *area) {

components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c:686

  • This unconditionally draws the full screen for any panel when in direct mode or full_refresh, which may overwrite regions on displays that support partial updates. Consider restoring region-based drawing for non-RGB/DSI panels or reintroducing a disp_type check.
    if (disp_ctx->flags.direct_mode || disp_ctx->flags.full_refresh) {

components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c:689

  • [nitpick] Add a space after if for consistency with the project’s coding style, i.e., use if (disp_ctx->disp_type ... for readability.
            if(disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI) {

@github-actions github-actions Bot changed the title Properly swap bytes and draw to display for direct mode in LVGL9 Properly swap bytes and draw to display for direct mode in LVGL9 (BSP-700) Jul 15, 2025
@kevinresol
kevinresol force-pushed the direct_mode_swap_bytes branch from c3f8ca8 to 994e065 Compare August 8, 2026 07:09

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 994e065. Configure here.

Comment thread components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c
} else {
size_t len = lv_area_get_size(area);
lv_draw_sw_rgb565_swap(color_map, len);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct mode bytes left swapped

Medium Severity

lvgl_port_draw_sw_rgb565_swap_area swaps dirty RGB565 pixels in the persistent direct-mode framebuffer and never restores native endianness after the panel consumes the data. Later LVGL blending, anti-aliasing, or dual-buffer sync can read those pixels as normal RGB565, producing incorrect colors on subsequent frames.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 994e065. Configure here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is said here is true, but it is only preserving the existing model and is not in scope of this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants