diff --git a/doc/releases/release-notes-4.1.rst b/doc/releases/release-notes-4.1.rst index 202fdb20489ba..c8cfe497b5c28 100644 --- a/doc/releases/release-notes-4.1.rst +++ b/doc/releases/release-notes-4.1.rst @@ -92,6 +92,12 @@ 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`) + * Added ``frame_incomplete`` handling to SDL display driver (:dtcompatible:`zephyr,sdl-dc`) + (:github:`81250`) + * Ethernet * Flash @@ -278,6 +284,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/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/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, 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; }; /** 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); 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);