From 7c3b1c0c62e4d49f2625c74c77c5b6b322d0d366 Mon Sep 17 00:00:00 2001 From: Martin Stumpf Date: Wed, 16 Oct 2024 16:42:15 +0200 Subject: [PATCH 1/4] display: add `frame_incomplete` to `display_buffer_descriptor` Introduces support for double-buffered/latched displays. Currently, every write has to be presented to the user immediately, which negates the advantage of latched displays to prevent frame tearing. Now, GUI managers can indicate whether the current `display_write` call is the last call of the frame or not, allowing displays to group writes to a single present. Signed-off-by: Martin Stumpf --- doc/releases/release-notes-4.1.rst | 4 ++++ include/zephyr/drivers/display.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/doc/releases/release-notes-4.1.rst b/doc/releases/release-notes-4.1.rst index 202fdb20489ba..f0db5e8cbd9d4 100644 --- a/doc/releases/release-notes-4.1.rst +++ b/doc/releases/release-notes-4.1.rst @@ -92,6 +92,10 @@ Drivers and Sensors * Display + * Added flag ``frame_incomplete`` to ``display_write`` that indicates whether a write is the last + write of the frame, allowing display drivers to implement double buffering / tearing enable + signal handling (:github:`81250`) + * Ethernet * Flash diff --git a/include/zephyr/drivers/display.h b/include/zephyr/drivers/display.h index 067d441d4551b..8fd26978beab4 100644 --- a/include/zephyr/drivers/display.h +++ b/include/zephyr/drivers/display.h @@ -127,6 +127,8 @@ struct display_buffer_descriptor { uint16_t height; /** Number of pixels between consecutive rows in the data buffer */ uint16_t pitch; + /** Indicates that this is not the last write buffer of the frame */ + bool frame_incomplete; }; /** From a0b223ad5b2ce237668d416bcbe3587000a95afd Mon Sep 17 00:00:00 2001 From: Martin Stumpf Date: Thu, 17 Oct 2024 10:25:17 +0200 Subject: [PATCH 2/4] lvgl: add `frame_incomplete` information to `display_write` In frames with multiple writes (officially supported through `CONFIG_LV_Z_VDB_SIZE`) the display needs to be signalled that the current frame is over and the content should be displayed. This allows displays to present the UI without tearing artifacts. Signed-off-by: Martin Stumpf --- doc/releases/release-notes-4.1.rst | 3 +++ modules/lvgl/lvgl_display.c | 2 ++ modules/lvgl/lvgl_display_mono.c | 13 ++++++++----- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/doc/releases/release-notes-4.1.rst b/doc/releases/release-notes-4.1.rst index f0db5e8cbd9d4..cd207e67a7ed2 100644 --- a/doc/releases/release-notes-4.1.rst +++ b/doc/releases/release-notes-4.1.rst @@ -282,6 +282,9 @@ Trusted Firmware-M LVGL **** +* Added ``frame_incomplete`` support to indicate whether a write is the last + write of the frame (:github:`81250`) + Tests and Samples ***************** diff --git a/modules/lvgl/lvgl_display.c b/modules/lvgl/lvgl_display.c index 604cd0078d806..39f3da1f795df 100644 --- a/modules/lvgl/lvgl_display.c +++ b/modules/lvgl/lvgl_display.c @@ -25,6 +25,7 @@ void lvgl_flush_thread_entry(void *arg1, void *arg2, void *arg3) k_msgq_get(&flush_queue, &flush, K_FOREVER); data = (struct lvgl_disp_data *)flush.disp_drv->user_data; + flush.desc.frame_incomplete = !lv_disp_flush_is_last(flush.disp_drv); display_write(data->display_dev, flush.x, flush.y, &flush.desc, flush.buf); @@ -132,6 +133,7 @@ void lvgl_flush_display(struct lvgl_display_flush *request) struct lvgl_disp_data *data = (struct lvgl_disp_data *)request->disp_drv->user_data; + request->desc.frame_incomplete = !lv_disp_flush_is_last(request->disp_drv); display_write(data->display_dev, request->x, request->y, &request->desc, request->buf); lv_disp_flush_ready(request->disp_drv); diff --git a/modules/lvgl/lvgl_display_mono.c b/modules/lvgl/lvgl_display_mono.c index 07f84b7d19efc..b6b7669962733 100644 --- a/modules/lvgl/lvgl_display_mono.c +++ b/modules/lvgl/lvgl_display_mono.c @@ -14,7 +14,6 @@ void lvgl_flush_cb_mono(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color uint16_t h = area->y2 - area->y1 + 1; struct lvgl_disp_data *data = (struct lvgl_disp_data *)disp_drv->user_data; const struct device *display_dev = data->display_dev; - struct display_buffer_descriptor desc; const bool is_epd = data->cap.screen_info & SCREEN_INFO_EPD; const bool is_last = lv_disp_flush_is_last(disp_drv); @@ -29,10 +28,14 @@ void lvgl_flush_cb_mono(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color data->blanking_on = true; } - desc.buf_size = (w * h) / 8U; - desc.width = w; - desc.pitch = w; - desc.height = h; + struct display_buffer_descriptor desc = { + .buf_size = (w * h) / 8U, + .width = w, + .pitch = w, + .height = h, + .frame_incomplete = !is_last, + }; + display_write(display_dev, area->x1, area->y1, &desc, (void *)color_p); if (data->cap.screen_info & SCREEN_INFO_DOUBLE_BUFFER) { display_write(display_dev, area->x1, area->y1, &desc, (void *)color_p); From 541f4e1d6da235fd1e79d46189efb2c84de99d86 Mon Sep 17 00:00:00 2001 From: Martin Stumpf Date: Tue, 29 Oct 2024 21:05:28 +0100 Subject: [PATCH 3/4] generic: add `frame_incomplete` where missing The newly introduced `frame_incomplete` flag of `display_buffer_descriptor` needed to be added at several places to avoid uninitialized memory. Signed-off-by: Martin Stumpf --- drivers/display/display_gc9x01x.c | 1 + drivers/display/display_ili9xxx.c | 1 + samples/drivers/display/src/main.c | 15 +++++++++++++++ samples/drivers/video/capture/src/main.c | 12 ++++++------ subsys/fb/cfb.c | 11 ++++++----- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/drivers/display/display_gc9x01x.c b/drivers/display/display_gc9x01x.c index 963e0dbe7a69a..4ff5e990f07ea 100644 --- a/drivers/display/display_gc9x01x.c +++ b/drivers/display/display_gc9x01x.c @@ -541,6 +541,7 @@ static int gc9x01x_write(const struct device *dev, const uint16_t x, const uint1 mipi_desc.width = desc->width; /* Per MIPI API, pitch must always match width */ mipi_desc.pitch = desc->width; + mipi_desc.frame_incomplete = desc->frame_incomplete; ret = gc9x01x_transmit(dev, GC9X01X_CMD_MEMWR, NULL, 0); if (ret < 0) { diff --git a/drivers/display/display_ili9xxx.c b/drivers/display/display_ili9xxx.c index a13786122fb32..2cdd4d6cafb39 100644 --- a/drivers/display/display_ili9xxx.c +++ b/drivers/display/display_ili9xxx.c @@ -163,6 +163,7 @@ static int ili9xxx_write(const struct device *dev, const uint16_t x, mipi_desc.width = desc->width; /* Per MIPI API, pitch must always match width */ mipi_desc.pitch = desc->width; + mipi_desc.frame_incomplete = desc->frame_incomplete; r = ili9xxx_transmit(dev, ILI9XXX_RAMWR, NULL, 0); if (r < 0) { diff --git a/samples/drivers/display/src/main.c b/samples/drivers/display/src/main.c index fb81955c78aad..8cbfb64040ac1 100644 --- a/samples/drivers/display/src/main.c +++ b/samples/drivers/display/src/main.c @@ -297,6 +297,14 @@ int main(void) buf_desc.width = capabilities.x_resolution; buf_desc.height = h_step; + /* + * The following writes will only render parts of the image, + * so turn this option on. + * This allows double-buffered displays to hold the pixels + * back until the image is complete. + */ + buf_desc.frame_incomplete = true; + for (int idx = 0; idx < capabilities.y_resolution; idx += h_step) { /* * Tweaking the height value not to draw outside of the display. @@ -323,6 +331,13 @@ int main(void) y = 0; display_write(display_dev, x, y, &buf_desc, buf); + /* + * This is the last write of the frame, so turn this off. + * Double-buffered displays will now present the new image + * to the user. + */ + buf_desc.frame_incomplete = false; + fill_buffer_fnc(BOTTOM_RIGHT, 0, buf, buf_size); x = capabilities.x_resolution - rect_w; y = capabilities.y_resolution - rect_h; diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index 6134b7e56d01a..66d495188698f 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -70,12 +70,12 @@ static inline void video_display_frame(const struct device *const display_dev, const struct video_buffer *const vbuf, const struct video_format fmt) { - struct display_buffer_descriptor buf_desc; - - buf_desc.buf_size = vbuf->bytesused; - buf_desc.width = fmt.width; - buf_desc.pitch = buf_desc.width; - buf_desc.height = vbuf->bytesused / fmt.pitch; + struct display_buffer_descriptor buf_desc = { + .buf_size = vbuf->bytesused, + .width = fmt.width, + .pitch = buf_desc.width, + .height = vbuf->bytesused / fmt.pitch, + }; display_write(display_dev, 0, vbuf->line_offset, &buf_desc, vbuf->buffer); } diff --git a/subsys/fb/cfb.c b/subsys/fb/cfb.c index 7dc3a3b5cfc91..7ba0882c90bca 100644 --- a/subsys/fb/cfb.c +++ b/subsys/fb/cfb.c @@ -460,17 +460,18 @@ int cfb_framebuffer_finalize(const struct device *dev) { const struct display_driver_api *api = dev->api; const struct char_framebuffer *fb = &char_fb; - struct display_buffer_descriptor desc; int err; if (!fb || !fb->buf) { return -ENODEV; } - desc.buf_size = fb->size; - desc.width = fb->x_res; - desc.height = fb->y_res; - desc.pitch = fb->x_res; + struct display_buffer_descriptor desc = { + .buf_size = fb->size, + .width = fb->x_res, + .height = fb->y_res, + .pitch = fb->x_res, + }; if (!(fb->pixel_format & PIXEL_FORMAT_MONO10) != !(fb->inverted)) { cfb_invert(fb); From df130b8cf913f813aa7b334b450470ec8834018b Mon Sep 17 00:00:00 2001 From: Martin Stumpf Date: Sun, 10 Nov 2024 15:25:54 +0100 Subject: [PATCH 4/4] drivers: display: display_sdl: implement display_show Adds frame synchronization to every frame. This prevents frame tearing. Signed-off-by: Martin Stumpf --- doc/releases/release-notes-4.1.rst | 2 ++ drivers/display/display_sdl.c | 6 +++--- drivers/display/display_sdl_bottom.c | 11 ++++++----- drivers/display/display_sdl_bottom.h | 14 ++++++-------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/doc/releases/release-notes-4.1.rst b/doc/releases/release-notes-4.1.rst index cd207e67a7ed2..c8cfe497b5c28 100644 --- a/doc/releases/release-notes-4.1.rst +++ b/doc/releases/release-notes-4.1.rst @@ -95,6 +95,8 @@ Drivers and Sensors * Added flag ``frame_incomplete`` to ``display_write`` that indicates whether a write is the last write of the frame, allowing display drivers to implement double buffering / tearing enable signal handling (:github:`81250`) + * Added ``frame_incomplete`` handling to SDL display driver (:dtcompatible:`zephyr,sdl-dc`) + (:github:`81250`) * Ethernet diff --git a/drivers/display/display_sdl.c b/drivers/display/display_sdl.c index 04370c855026f..2c88e16bd7514 100644 --- a/drivers/display/display_sdl.c +++ b/drivers/display/display_sdl.c @@ -260,9 +260,9 @@ static int sdl_display_write(const struct device *dev, const uint16_t x, sdl_display_write_bgr565(disp_data->buf, desc, buf); } - sdl_display_write_bottom(desc->height, desc->width, x, y, - disp_data->renderer, disp_data->mutex, disp_data->texture, - disp_data->buf, disp_data->display_on); + sdl_display_write_bottom(desc->height, desc->width, x, y, disp_data->renderer, + disp_data->mutex, disp_data->texture, disp_data->buf, + disp_data->display_on, desc->frame_incomplete); return 0; } diff --git a/drivers/display/display_sdl_bottom.c b/drivers/display/display_sdl_bottom.c index 0e995a341c9a3..e0fbd4a5fbb5f 100644 --- a/drivers/display/display_sdl_bottom.c +++ b/drivers/display/display_sdl_bottom.c @@ -5,6 +5,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "display_sdl_bottom.h" + #include #include #include @@ -64,10 +66,9 @@ int sdl_display_init_bottom(uint16_t height, uint16_t width, uint16_t zoom_pct, return 0; } -void sdl_display_write_bottom(const uint16_t height, const uint16_t width, - const uint16_t x, const uint16_t y, - void *renderer, void *mutex, void *texture, - uint8_t *buf, bool display_on) +void sdl_display_write_bottom(const uint16_t height, const uint16_t width, const uint16_t x, + const uint16_t y, void *renderer, void *mutex, void *texture, + uint8_t *buf, bool display_on, bool frame_incomplete) { SDL_Rect rect; int err; @@ -85,7 +86,7 @@ void sdl_display_write_bottom(const uint16_t height, const uint16_t width, SDL_UpdateTexture(texture, &rect, buf, 4 * rect.w); - if (display_on) { + if (display_on && !frame_incomplete) { SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); diff --git a/drivers/display/display_sdl_bottom.h b/drivers/display/display_sdl_bottom.h index 54973777f1be0..721cfa2b5a81e 100644 --- a/drivers/display/display_sdl_bottom.h +++ b/drivers/display/display_sdl_bottom.h @@ -23,14 +23,12 @@ extern "C" { int sdl_display_init_bottom(uint16_t height, uint16_t width, uint16_t zoom_pct, bool use_accelerator, void **window, void **renderer, void **mutex, void **texture, void **read_texture); -void sdl_display_write_bottom(const uint16_t height, const uint16_t width, - const uint16_t x, const uint16_t y, - void *renderer, void *mutex, void *texture, - uint8_t *buf, bool display_on); -int sdl_display_read_bottom(const uint16_t height, const uint16_t width, - const uint16_t x, const uint16_t y, - void *renderer, void *buf, uint16_t pitch, - void *mutex, void *texture, void **read_texture); +void sdl_display_write_bottom(const uint16_t height, const uint16_t width, const uint16_t x, + const uint16_t y, void *renderer, void *mutex, void *texture, + uint8_t *buf, bool display_on, bool frame_incomplete); +int sdl_display_read_bottom(const uint16_t height, const uint16_t width, const uint16_t x, + const uint16_t y, void *renderer, void *buf, uint16_t pitch, + void *mutex, void *texture, void *read_texture); void sdl_display_blanking_off_bottom(void *renderer, void *texture); void sdl_display_blanking_on_bottom(void *renderer); void sdl_display_cleanup_bottom(void **window, void **renderer, void **mutex, void **texture,