Properly swap bytes and draw to display for direct mode in LVGL9 (BSP-700) - #609
Properly swap bytes and draw to display for direct mode in LVGL9 (BSP-700)#609kevinresol wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
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_areato 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_arealacks 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_areato 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
iffor consistency with the project’s coding style, i.e., useif (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) {
c3f8ca8 to
994e065
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ 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.
| } else { | ||
| size_t len = lv_area_get_size(area); | ||
| lv_draw_sw_rgb565_swap(color_map, len); | ||
| } |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit 994e065. Configure here.
There was a problem hiding this comment.
What is said here is true, but it is only preserving the existing model and is not in scope of this PR


ESP-BSP Pull Request checklist
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:
swap_bytestreatedcolor_mapas a partial crop (lv_draw_sw_rgb565_swapoverareasize). In direct mode,color_mapis the full framebuffer base, so that swapped the wrong span of pixels.esp_lcd_panel_draw_bitmaponly for RGB/MIPI-DSI. Other panel types (e.g. SPI/I80) used areadraw_bitmapwith an unoffset full-framecolor_map, which is incorrect for direct/full_refresh buffer layout.Changes (
esp_lvgl_portLVGL9)lvgl_port_draw_sw_rgb565_swap_area()swaps RGB565 endianness only within the dirtyareawhendirect_modeandswap_bytesare enabled. Partial mode keeps the existing full-areaswap.direct_modeorfull_refresh, on the last flush of a frame, always draw fullscreen(0,0)→(hor,ver)for all panel types (not only RGB/DSI).trans_semwait after that draw still applies only to RGB/DSI.lv_disp_flush_ready— unchanged from master: still gated for RGB and for DSI under direct/full_refresh. SPI/I80 continue to complete via panel IOon_color_trans_done(avoids double-ready / early buffer reuse with DMA).Non-goals / notes
lv_disp_flush_readyunconditionally at end of flush (original draft did; rebased out for SPI safety).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_modeandswap_bytes, the flush path now swaps endianness only inside the dirtyareavialvgl_port_draw_sw_rgb565_swap_area()instead of treatingcolor_mapas 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_semblocking after that draw remains RGB/DSI only.Flush ready: Intermediate direct/full flushes that skip a draw call
lv_disp_flush_readywhen 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.