From 24bd5f28fcb6a434bd699bf0998107af349a5302 Mon Sep 17 00:00:00 2001 From: vudangRVC <154663422+vudangRVC@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:10:04 +0700 Subject: [PATCH] feat(wayland): add DMA-BUF software fast-path to the EGL backend (#10339) Signed-off-by: Sang Tran Signed-off-by: Vu Dang --- Kconfig | 5 + .../embedded_linux/drivers/wayland.mdx | 47 +- env_support/cmake/dependencies.cmake | 5 +- env_support/cmake/dependencies/wayland.cmake | 4 +- include/lvgl/config/lv_conf_internal.h | 8 + scripts/static_checks/check_headers.py | 1 + src/drivers/wayland/Kconfig | 5 + src/drivers/wayland/lv_wayland_backend_egl.c | 593 +++++++++++++++++- src/drivers/wayland/lv_wayland_dmabuf.c | 337 ++++++++++ src/drivers/wayland/lv_wayland_dmabuf.h | 121 ++++ src/lvgl_private.h | 1 + 11 files changed, 1114 insertions(+), 13 deletions(-) create mode 100644 src/drivers/wayland/lv_wayland_dmabuf.c create mode 100644 src/drivers/wayland/lv_wayland_dmabuf.h diff --git a/Kconfig b/Kconfig index 6a08238749..c3598aee90 100644 --- a/Kconfig +++ b/Kconfig @@ -2775,9 +2775,14 @@ config LV_WAYLAND_USE_SHM config LV_WAYLAND_USE_EGL bool select LV_USE_EGL + select LV_WAYLAND_USE_DMABUF if !LV_USE_DRAW_OPENGLES && !LV_USE_DRAW_NANOVG config LV_WAYLAND_USE_G2D bool + select LV_WAYLAND_USE_DMABUF + +config LV_WAYLAND_USE_DMABUF + bool endif # LV_USE_WAYLAND config LV_USE_WINDOWS diff --git a/docs/src/integration/embedded_linux/drivers/wayland.mdx b/docs/src/integration/embedded_linux/drivers/wayland.mdx index ca2a054c4b..8da6b3b230 100644 --- a/docs/src/integration/embedded_linux/drivers/wayland.mdx +++ b/docs/src/integration/embedded_linux/drivers/wayland.mdx @@ -71,13 +71,26 @@ enabling GPU-accelerated graphics on Wayland compositors that support the necess To enable the EGL backend: -1. Enable OpenGL ES support in your LVGL configuration: +1. Select the EGL backend explicitly. `LV_WAYLAND_AUTO_BACKEND` defaults to `1`, in + which case the driver falls back to the SHM backend unless a backend is + selected; set it to `0` and pick `LV_WAYLAND_BACKEND_EGL`: + + ```c + #define LV_WAYLAND_AUTO_BACKEND 0 + #define LV_WAYLAND_BACKEND LV_WAYLAND_BACKEND_EGL + ``` + + If you configure LVGL through Kconfig/menuconfig instead of `lv_conf.h`, pick + the **EGL (OpenGL ES, hardware-accelerated)** option under the Wayland driver's + "Rendering backend" choice instead. + +2. Enable OpenGL ES support in your LVGL configuration: ```c #define LV_USE_OPENGLES 1 ``` -2. Link your application with the `wayland-egl` library (add to your build system): +3. Link your application with the `wayland-egl` library (add to your build system): ```bash -lwayland-egl @@ -86,6 +99,32 @@ To enable the EGL backend: **Note:** If your target board has OpenGL ES 2.0 support, this backend can be used with LVGL's 3D rendering capabilities for glTF model visualization. See [3D/glTF Support](/integration/embedded_linux/opengl) for details on 3D rendering support. +#### DMA-BUF Software Fast-Path + +When the EGL backend is used for software rendering, it automatically uses the +Linux DMA-BUF protocol to present frames, improving performance on platforms +that support zero-copy buffer sharing. If the required support is unavailable, +the backend automatically falls back to the classic software path. + +**Requirements:** + +- A Wayland compositor with `zwp_linux_dmabuf_v1` support +- GPU/display driver support for DMA-BUF +- EGL/OpenGL ES support +- `libdrm` and `gbm` (used to allocate the shared buffers) +- The `linux-dmabuf` Wayland protocol generated at build time + +When building with LVGL's CMake build system, the `linux-dmabuf` protocol is +generated and `libdrm`/`gbm` are resolved automatically — no extra steps are +needed. For other build systems, generate the `linux-dmabuf` protocol with +`wayland-scanner` and link the additional libraries: + +```bash +-lwayland-egl +-ldrm +-lgbm +``` + ### G2D Backend The G2D backend leverages NXP's hardware-accelerated 2D graphics engine available on i.MX processors. This backend provides improved performance on NXP platforms. @@ -236,7 +275,9 @@ wayland-scanner client-header $SYSROOT/usr/share/wayland-protocols/stable/xdg-sh wayland-scanner private-code $SYSROOT/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml wayland_xdg_shell.c ``` -When `LV_WAYLAND_USE_DMABUF` is set to `1`, the following protocols must also be generated: +When using the [G2D backend](#g2d-backend), or the [EGL backend](#egl-backend)'s +[DMA-BUF software fast-path](#dma-buf-software-fast-path), the following protocols +must also be generated: ```sh wayland-scanner client-header $SYSROOT/usr/share/wayland-protocols/stable/linux-dmabuf/linux-dmabuf-v1.xml wayland_linux_dmabuf.h diff --git a/env_support/cmake/dependencies.cmake b/env_support/cmake/dependencies.cmake index 40c847a757..4d25b9622e 100644 --- a/env_support/cmake/dependencies.cmake +++ b/env_support/cmake/dependencies.cmake @@ -33,11 +33,12 @@ if(CONFIG_LV_USE_SDL) include(${CMAKE_CURRENT_LIST_DIR}/dependencies/sdl2.cmake) endif() -if(CONFIG_LV_USE_LINUX_DRM) +if(CONFIG_LV_USE_LINUX_DRM OR CONFIG_LV_WAYLAND_USE_DMABUF) include(${CMAKE_CURRENT_LIST_DIR}/dependencies/drm.cmake) endif() -if(CONFIG_LV_USE_LINUX_DRM_GBM_BUFFERS OR CONFIG_LV_LINUX_DRM_USE_EGL) +if(CONFIG_LV_USE_LINUX_DRM_GBM_BUFFERS OR CONFIG_LV_LINUX_DRM_USE_EGL OR + (CONFIG_LV_WAYLAND_USE_EGL AND CONFIG_LV_WAYLAND_USE_DMABUF)) include(${CMAKE_CURRENT_LIST_DIR}/dependencies/gbm.cmake) endif() diff --git a/env_support/cmake/dependencies/wayland.cmake b/env_support/cmake/dependencies/wayland.cmake index 8660a47dd7..86bf87fb18 100644 --- a/env_support/cmake/dependencies/wayland.cmake +++ b/env_support/cmake/dependencies/wayland.cmake @@ -150,8 +150,8 @@ endif() set(WAYLAND_PROTOCOL_SOURCES ${XDG_SHELL_SOURCE}) -# dmabuf (optional) -if(CONFIG_LV_WAYLAND_USE_G2D) +# dmabuf (optional) - used by the G2D backend and by the Wayland EGL DMA-BUF fast-path +if(CONFIG_LV_WAYLAND_USE_DMABUF) set(DMABUF_XML "${PROTOCOL_ROOT}/stable/linux-dmabuf/linux-dmabuf-v1.xml") set(DMABUF_HEADER "${PROTOCOLS_DIR}/wayland_linux_dmabuf.h") set(DMABUF_SOURCE "${PROTOCOLS_DIR}/wayland_linux_dmabuf.c") diff --git a/include/lvgl/config/lv_conf_internal.h b/include/lvgl/config/lv_conf_internal.h index c2dc0f3b41..a53f813c5e 100644 --- a/include/lvgl/config/lv_conf_internal.h +++ b/include/lvgl/config/lv_conf_internal.h @@ -4926,6 +4926,14 @@ #endif #endif +#ifndef LV_WAYLAND_USE_DMABUF + #if (((LV_WAYLAND_USE_EGL && !LV_USE_DRAW_OPENGLES && !LV_USE_DRAW_NANOVG && LV_USE_WAYLAND) || (LV_WAYLAND_USE_G2D && LV_USE_WAYLAND)) && (LV_USE_WAYLAND)) + #define LV_WAYLAND_USE_DMABUF 1 + #else + #define LV_WAYLAND_USE_DMABUF 0 + #endif +#endif + /* Optional user headers (LV_*_USE_CUSTOM_INCLUDE) overriding config macros. */ #if LV_OS_USE_CUSTOM_INCLUDE #include LV_OS_CUSTOM_INCLUDE diff --git a/scripts/static_checks/check_headers.py b/scripts/static_checks/check_headers.py index a0784b8ad7..26a4d21d7f 100755 --- a/scripts/static_checks/check_headers.py +++ b/scripts/static_checks/check_headers.py @@ -110,6 +110,7 @@ ALLOWED_EXTERNAL_HEADERS: set[str] = { "GL/glew.h", "GLES2/gl2.h", "GLES2/gl2ext.h", + "GLES2/gl2platform.h", "GLES3/gl3.h", "GLFW/glfw3.h", "LGFX_AUTODETECT.hpp", diff --git a/src/drivers/wayland/Kconfig b/src/drivers/wayland/Kconfig index a9ccfb82ea..3bdd93202c 100644 --- a/src/drivers/wayland/Kconfig +++ b/src/drivers/wayland/Kconfig @@ -64,8 +64,13 @@ config LV_WAYLAND_USE_SHM config LV_WAYLAND_USE_EGL bool select LV_USE_EGL + select LV_WAYLAND_USE_DMABUF if !LV_USE_DRAW_OPENGLES && !LV_USE_DRAW_NANOVG config LV_WAYLAND_USE_G2D bool + select LV_WAYLAND_USE_DMABUF + +config LV_WAYLAND_USE_DMABUF + bool endif # LV_USE_WAYLAND diff --git a/src/drivers/wayland/lv_wayland_backend_egl.c b/src/drivers/wayland/lv_wayland_backend_egl.c index edb80d19e8..b820b2b520 100644 --- a/src/drivers/wayland/lv_wayland_backend_egl.c +++ b/src/drivers/wayland/lv_wayland_backend_egl.c @@ -8,7 +8,7 @@ #include "lv_wayland_private.h" -#if LV_WAYLAND_USE_EGL +#if LV_WAYLAND_USE_EGL == 1 #include "../../display/lv_display_private.h" #include "../opengles/lv_opengles_texture_private.h" @@ -17,19 +17,84 @@ #include +/* The DMA-BUF fast-path is a software-rendering optimization. lv_conf_internal.h + * already derives LV_WAYLAND_USE_DMABUF internally and enables it only when + * no GPU draw unit is active, so it maps directly to the local guard below. */ +#if LV_WAYLAND_USE_DMABUF == 1 + #define LV_WL_EGL_DMABUF_ENABLED 1 +#else + #define LV_WL_EGL_DMABUF_ENABLED 0 +#endif + +#if LV_WL_EGL_DMABUF_ENABLED + #include "lv_wayland_dmabuf.h" + #include + #include + #include + #include LV_STDINT_INCLUDE + #include + #include + #include + #include + #include + #include + #include "../opengles/lv_opengles_private.h" + #include + #include +#endif /*LV_WL_EGL_DMABUF_ENABLED*/ + /********************* * DEFINES *********************/ +#if LV_WL_EGL_DMABUF_ENABLED + #define LV_WL_EGL_BUF_COUNT 2 +#endif + /********************** * TYPEDEFS **********************/ +#if LV_WL_EGL_DMABUF_ENABLED + +typedef void (GL_APIENTRYP PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)(GLenum target, GLeglImageOES image); typedef struct { + /* Shared DMA-BUF protocol state (wl_buffer + release tracking) */ + lv_wayland_dmabuf_buffer_t base; + + /* EGL/GBM allocator specific state */ + struct gbm_bo * bo; + int dmabuf_fd; + uint32_t stride; + uint32_t offset; + + EGLImageKHR egl_image; + GLuint texture_id; +} lv_wl_buffer_t; + +#endif /*LV_WL_EGL_DMABUF_ENABLED*/ + +typedef struct { + /* Used by the classic path (GPU render target, or software upload target). */ lv_opengles_texture_t texture; + struct wl_egl_window * egl_window; lv_opengles_egl_t * egl_ctx; + +#if LV_WL_EGL_DMABUF_ENABLED + lv_wl_buffer_t buffers[LV_WL_EGL_BUF_COUNT]; + /* Software draw buffers owned by this backend in DMA-BUF mode. */ + uint8_t * sw_buf[2]; + struct gbm_device * gbm_device; + int drm_fd; + int32_t width; + int32_t height; + uint8_t last_used; + /* Set at init time once the DMA-BUF path has been confirmed available. + * When false the classic software path above is used as a fallback. */ + bool use_dmabuf; +#endif } lv_wl_egl_display_data_t; /********************** @@ -58,11 +123,27 @@ static void wl_egl_destroy_window(void * driver_data, void * native_window); static size_t wl_egl_select_config_cb(void * driver_data, const lv_egl_config_t * configs, size_t config_count); static void wl_egl_flip_cb(void * driver_data, bool vsync); +#if LV_WL_EGL_DMABUF_ENABLED +static bool dmabuf_is_available(void); +static bool egl_dmabuf_setup(lv_wl_egl_display_data_t * ddata, lv_display_t * display, int32_t width, int32_t height); +static bool egl_dmabuf_reinit_buffers(lv_wl_egl_display_data_t * ddata, lv_display_t * display, + int32_t width, int32_t height); +static void egl_dmabuf_teardown(lv_wl_egl_display_data_t * ddata); + +static lv_wl_buffer_t * get_next_buffer(lv_wl_egl_display_data_t * ddata); + +static uint32_t lv_drm_cf_to_gbm_cf(uint32_t drm_cf); +static void load_egl_extensions(void); +static bool init_buffer(lv_wayland_dmabuf_ctx_t * dmabuf_ctx, lv_wl_buffer_t * buffer, uint32_t width, + uint32_t height, lv_color_format_t cf, lv_wl_egl_display_data_t * ddata); +static int open_drm_device(void); +static void delete_buffer(lv_opengles_egl_t * egl_ctx, lv_wl_buffer_t * buffer); +#endif /*LV_WL_EGL_DMABUF_ENABLED*/ + /********************** * STATIC VARIABLES **********************/ - static const struct wl_callback_listener frame_listener = { .done = frame_done, }; @@ -76,6 +157,13 @@ const lv_wayland_backend_ops_t wl_backend_ops = { .resize_display = wl_egl_resize_display, }; +#if LV_WL_EGL_DMABUF_ENABLED + + static lv_wayland_dmabuf_ctx_t ctx; + static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = NULL; + +#endif /*LV_WL_EGL_DMABUF_ENABLED*/ + /********************** * MACROS **********************/ @@ -98,12 +186,21 @@ static void frame_done(void * data, struct wl_callback * callback, uint32_t time static void * wl_egl_init(void) { +#if LV_WL_EGL_DMABUF_ENABLED + lv_wayland_dmabuf_ctx_init(&ctx); + return &ctx; +#else return NULL; +#endif } static void wl_egl_deinit(void * backend_ctx) { +#if LV_WL_EGL_DMABUF_ENABLED + lv_wayland_dmabuf_ctx_deinit((lv_wayland_dmabuf_ctx_t *)backend_ctx); +#else LV_UNUSED(backend_ctx); +#endif } static lv_wl_egl_display_data_t * egl_create_display_data(lv_display_t * display, @@ -115,11 +212,14 @@ static lv_wl_egl_display_data_t * egl_create_display_data(lv_display_t * display return NULL; } +#if LV_WL_EGL_DMABUF_ENABLED + ddata->drm_fd = -1; +#endif + /* Set the backend display data immediately as we will need it * in the EGL window creation callback */ lv_wayland_set_backend_display_data(display, ddata); - /* Create EGL context */ lv_egl_interface_t egl_interface = wl_egl_get_interface(display); ddata->egl_ctx = lv_opengles_egl_context_create(&egl_interface); @@ -128,6 +228,16 @@ static lv_wl_egl_display_data_t * egl_create_display_data(lv_display_t * display goto egl_ctx_err; } +#if LV_WL_EGL_DMABUF_ENABLED + /* Try the zero-copy DMA-BUF software fast-path. On any failure fall back + * transparently to the classic software path below. */ + if(egl_dmabuf_setup(ddata, display, width, height)) { + ddata->use_dmabuf = true; + return ddata; + } + LV_LOG_WARN("Wayland EGL: DMA-BUF unavailable, using the classic software path"); +#endif + /* Let the opengles texture driver handle the texture lifetime */ ddata->texture.is_texture_owner = true; @@ -153,7 +263,16 @@ static void egl_destroy_display_data(lv_wl_egl_display_data_t * ddata) return; } - lv_opengles_texture_deinit(&ddata->texture); +#if LV_WL_EGL_DMABUF_ENABLED + if(ddata->use_dmabuf) { + /* Buffers reference the EGL context, so tear them down before it. */ + egl_dmabuf_teardown(ddata); + } + else +#endif + { + lv_opengles_texture_deinit(&ddata->texture); + } if(ddata->egl_ctx) { lv_opengles_egl_context_destroy(ddata->egl_ctx); @@ -209,12 +328,14 @@ static void egl_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * wl_surface_commit(surface); } -#else +#else /*Software rendering*/ static void egl_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map) { - LV_UNUSED(px_map); LV_UNUSED(area); +#if !LV_WL_EGL_DMABUF_ENABLED + LV_UNUSED(px_map); +#endif if(!lv_display_flush_is_last(disp)) { lv_display_flush_ready(disp); @@ -232,6 +353,48 @@ static void egl_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * int32_t disp_width = lv_display_get_horizontal_resolution(disp); int32_t disp_height = lv_display_get_vertical_resolution(disp); +#if LV_WL_EGL_DMABUF_ENABLED + if(ddata->use_dmabuf) { + /* Zero-copy fast-path: upload the software framebuffer directly into a + * DMA-BUF backed texture and attach its wl_buffer to the surface. No + * full-screen quad pass and no eglSwapBuffers copy. */ + lv_wl_buffer_t * buf = get_next_buffer(ddata); + if(!buf) { + LV_LOG_ERROR("Failed to acquire a wayland window body buffer"); + lv_display_flush_ready(disp); + return; + } + + lv_color_format_t cf = lv_display_get_color_format(disp); + uint32_t stride = lv_draw_buf_width_to_stride(disp_width, cf); + + GL_CALL(glBindTexture(GL_TEXTURE_2D, buf->texture_id)); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / lv_color_format_get_size(cf))); +#if LV_COLOR_DEPTH == 16 + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, disp_width, disp_height, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, px_map)); +#elif LV_COLOR_DEPTH == 32 + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, disp_width, disp_height, + GL_BGRA, GL_UNSIGNED_BYTE, px_map)); +#endif + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); + + wl_surface_attach(surface, buf->base.wl_buffer, 0, 0); + wl_surface_damage(surface, 0, 0, disp_width, disp_height); + + struct wl_callback * callback = wl_surface_frame(surface); + wl_callback_add_listener(callback, &frame_listener, disp); + + wl_surface_commit(surface); + + buf->base.busy = true; + wl_display_flush(lv_wl_ctx.wl_display); + return; + } +#endif /*LV_WL_EGL_DMABUF_ENABLED*/ + lv_opengles_viewport(0, 0, lv_display_get_original_horizontal_resolution(disp), lv_display_get_original_vertical_resolution(disp)); @@ -277,6 +440,34 @@ static void * wl_egl_init_display(void * backend_ctx, lv_display_t * display, in return NULL; } +#if LV_WL_EGL_DMABUF_ENABLED + if(ddata->use_dmabuf) { + lv_color_format_t cf = lv_display_get_color_format(display); + uint32_t stride = lv_draw_buf_width_to_stride(width, cf); + uint32_t buf_size = stride * height; + + uint8_t * buf1 = lv_malloc(buf_size); + uint8_t * buf2 = lv_malloc(buf_size); + LV_ASSERT_MALLOC(buf1); + LV_ASSERT_MALLOC(buf2); + if(!buf1 || !buf2) { + LV_LOG_ERROR("Failed to allocate display buffer"); + lv_free(buf1); + lv_free(buf2); + egl_destroy_display_data(ddata); + return NULL; + } + + ddata->sw_buf[0] = buf1; + ddata->sw_buf[1] = buf2; + + lv_display_set_buffers(display, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_DIRECT); + lv_display_set_flush_cb(display, egl_flush_cb); + lv_display_set_flush_wait_cb(display, flush_wait_cb); + return ddata; + } +#endif /*LV_WL_EGL_DMABUF_ENABLED*/ + lv_display_set_flush_cb(display, egl_flush_cb); lv_display_set_flush_wait_cb(display, flush_wait_cb); lv_display_set_render_mode(display, LV_USE_DRAW_NANOVG ? LV_DISPLAY_RENDER_MODE_FULL : LV_DISPLAY_RENDER_MODE_DIRECT); @@ -289,6 +480,46 @@ static void * wl_egl_resize_display(void * backend_ctx, lv_display_t * display) LV_UNUSED(backend_ctx); lv_wl_egl_display_data_t * ddata = lv_wayland_get_backend_display_data(display); +#if LV_WL_EGL_DMABUF_ENABLED + if(ddata->use_dmabuf) { + int32_t width = lv_display_get_original_horizontal_resolution(display); + int32_t height = lv_display_get_original_vertical_resolution(display); + + lv_color_format_t cf = lv_display_get_color_format(display); + uint32_t stride = lv_draw_buf_width_to_stride(width, cf); + uint32_t buf_size = stride * height; + + uint8_t * buf1 = lv_malloc(buf_size); + uint8_t * buf2 = lv_malloc(buf_size); + LV_ASSERT_MALLOC(buf1); + LV_ASSERT_MALLOC(buf2); + if(!buf1 || !buf2) { + LV_LOG_ERROR("Failed to allocate display buffer for %dx%d", width, height); + lv_free(buf1); + lv_free(buf2); + lv_display_set_resolution(display, ddata->width, ddata->height); + return ddata; + } + + if(!egl_dmabuf_reinit_buffers(ddata, display, width, height)) { + LV_LOG_ERROR("Failed to recreate DMA-BUF buffers for %dx%d", width, height); + lv_free(buf1); + lv_free(buf2); + lv_display_set_resolution(display, ddata->width, ddata->height); + return ddata; + } + + lv_free(ddata->sw_buf[0]); + lv_free(ddata->sw_buf[1]); + ddata->sw_buf[0] = buf1; + ddata->sw_buf[1] = buf2; + + lv_display_set_buffers(display, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_DIRECT); + wl_egl_window_resize(ddata->egl_window, width, height, 0, 0); + return ddata; + } +#endif /*LV_WL_EGL_DMABUF_ENABLED*/ + int32_t width = lv_display_get_horizontal_resolution(display); int32_t height = lv_display_get_vertical_resolution(display); lv_result_t res = lv_opengles_texture_reshape(&ddata->texture, display, width, height); @@ -305,12 +536,26 @@ static void wl_egl_deinit_display(void * backend_ctx, lv_display_t * display) { LV_UNUSED(backend_ctx); lv_wl_egl_display_data_t * ddata = lv_wayland_get_backend_display_data(display); + +#if LV_WL_EGL_DMABUF_ENABLED + if(ddata && ddata->use_dmabuf) { + lv_free(ddata->sw_buf[0]); + lv_free(ddata->sw_buf[1]); + ddata->sw_buf[0] = NULL; + ddata->sw_buf[1] = NULL; + } +#endif + egl_destroy_display_data(ddata); } static void wl_egl_global_handler(void * backend_ctx, struct wl_registry * registry, uint32_t name, const char * interface, uint32_t version) { +#if LV_WL_EGL_DMABUF_ENABLED + lv_wayland_dmabuf_registry_handle_global((lv_wayland_dmabuf_ctx_t *)backend_ctx, + registry, name, interface, version); +#else LV_UNUSED(backend_ctx); LV_UNUSED(registry); LV_UNUSED(name); @@ -318,6 +563,7 @@ static void wl_egl_global_handler(void * backend_ctx, struct wl_registry * regis LV_UNUSED(version); /* No specific Wayland globals needed for basic EGL support */ +#endif } static lv_egl_interface_t wl_egl_get_interface(lv_display_t * display) @@ -419,4 +665,339 @@ static void wl_egl_flip_cb(void * driver_data, bool vsync) * through wl_surface_commit() which is called in the flush callback */ } +/*================================================================== + * DMA-BUF software fast-path + *==================================================================*/ + +#if LV_WL_EGL_DMABUF_ENABLED + +static bool dmabuf_is_available(void) +{ + if(!ctx.handler) { + LV_LOG_WARN("Compositor does not support zwp_linux_dmabuf_v1"); + return false; + } + +#if LV_COLOR_DEPTH == 16 + if(!ctx.supports_rgb565) { + LV_LOG_WARN("Compositor does not advertise DMA-BUF RGB565 support"); + return false; + } +#endif + + return true; +} + +static bool egl_dmabuf_setup(lv_wl_egl_display_data_t * ddata, lv_display_t * display, int32_t width, int32_t height) +{ + if(!dmabuf_is_available()) { + return false; + } + + /* Load glEGLImageTargetTexture2DOES */ + if(!glEGLImageTargetTexture2DOES) { + load_egl_extensions(); + } + if(!glEGLImageTargetTexture2DOES) { + return false; + } + + ddata->drm_fd = open_drm_device(); + if(ddata->drm_fd < 0) { + LV_LOG_ERROR("Failed to open DRM device"); + return false; + } + + ddata->gbm_device = gbm_create_device(ddata->drm_fd); + if(!ddata->gbm_device) { + LV_LOG_ERROR("Failed to create GBM device"); + close(ddata->drm_fd); + ddata->drm_fd = -1; + return false; + } + + /* Buffers not yet reached by init_buffer() must still report a closed fd, + * otherwise a teardown after a partial failure below would close fd 0. */ + for(size_t i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + ddata->buffers[i].dmabuf_fd = -1; + } + + lv_color_format_t cf = lv_display_get_color_format(display); + bool ok = true; + for(size_t i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + if(!init_buffer(&ctx, &ddata->buffers[i], width, height, cf, ddata)) { + ok = false; + } + } + ddata->last_used = 0; + + wl_display_flush(lv_wl_ctx.wl_display); + wl_display_roundtrip(lv_wl_ctx.wl_display); + for(size_t i = 0; i < LV_WL_EGL_BUF_COUNT; ++i) { + if(!ddata->buffers[i].base.wl_buffer) { + ok = false; + } + } + + if(!ok) { + LV_LOG_ERROR("DMA-BUF creation failed"); + egl_dmabuf_teardown(ddata); + return false; + } + + ddata->width = width; + ddata->height = height; + return true; +} + +static bool egl_dmabuf_reinit_buffers(lv_wl_egl_display_data_t * ddata, lv_display_t * display, + int32_t width, int32_t height) +{ + lv_wl_buffer_t new_buffers[LV_WL_EGL_BUF_COUNT]; + lv_memset(new_buffers, 0, sizeof(new_buffers)); + for(size_t i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + new_buffers[i].dmabuf_fd = -1; + } + + lv_color_format_t cf = lv_display_get_color_format(display); + bool ok = true; + for(size_t i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + if(!init_buffer(&ctx, &new_buffers[i], width, height, cf, ddata)) { + ok = false; + } + } + + wl_display_flush(lv_wl_ctx.wl_display); + wl_display_roundtrip(lv_wl_ctx.wl_display); + for(size_t i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + if(!new_buffers[i].base.wl_buffer) { + ok = false; + } + } + + if(!ok) { + for(int i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + delete_buffer(ddata->egl_ctx, &new_buffers[i]); + } + return false; + } + + /* All new buffers are ready: it is now safe to release the old ones. */ + for(int i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + delete_buffer(ddata->egl_ctx, &ddata->buffers[i]); + } + lv_memcpy(ddata->buffers, new_buffers, sizeof(new_buffers)); + ddata->last_used = 0; + ddata->width = width; + ddata->height = height; + + for(int i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + if(ddata->buffers[i].base.wl_buffer) { + wl_buffer_set_user_data(ddata->buffers[i].base.wl_buffer, &ddata->buffers[i].base); + } + } + + return true; +} + +static void egl_dmabuf_teardown(lv_wl_egl_display_data_t * ddata) +{ + for(int i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + delete_buffer(ddata->egl_ctx, &ddata->buffers[i]); + } + + if(ddata->gbm_device) { + gbm_device_destroy(ddata->gbm_device); + ddata->gbm_device = NULL; + } + if(ddata->drm_fd >= 0) { + close(ddata->drm_fd); + ddata->drm_fd = -1; + } +} + +static lv_wl_buffer_t * get_next_buffer(lv_wl_egl_display_data_t * ddata) +{ + /* Try to find a non-busy buffer. If all buffers are still owned by the + * compositor (busy), wait for a wl_buffer.release event to clear one. + * Bounded to 500ms so a compositor that never releases one drops this frame. */ + struct pollfd pfd = { .fd = wl_display_get_fd(lv_wl_ctx.wl_display), .events = POLLIN }; + uint32_t remaining_ms = 500; + + for(;;) { + for(int i = 0; i < LV_WL_EGL_BUF_COUNT; i++) { + int index = (ddata->last_used + i) % LV_WL_EGL_BUF_COUNT; + if(!ddata->buffers[index].base.busy) { + ddata->last_used = (index + 1) % LV_WL_EGL_BUF_COUNT; + return &ddata->buffers[index]; + } + } + + if(remaining_ms == 0) { + LV_LOG_WARN("Timed out waiting for a free DMA-BUF buffer, dropping frame"); + return NULL; + } + + /* All buffers are busy: flush pending requests and wait (bounded) for + * the compositor to release one via a wl_buffer.release event. */ + wl_display_flush(lv_wl_ctx.wl_display); + if(wl_display_prepare_read(lv_wl_ctx.wl_display) == 0) { + int step_ms = remaining_ms < 20 ? (int)remaining_ms : 20; + int ret = poll(&pfd, 1, step_ms); + remaining_ms -= step_ms; + if(ret > 0 && (pfd.revents & POLLIN)) { + wl_display_read_events(lv_wl_ctx.wl_display); + } + else { + wl_display_cancel_read(lv_wl_ctx.wl_display); + } + } + wl_display_dispatch_pending(lv_wl_ctx.wl_display); + } +} + +static uint32_t lv_drm_cf_to_gbm_cf(uint32_t drm_cf) +{ + switch(drm_cf) { + case DRM_FORMAT_XRGB8888: + return GBM_FORMAT_XRGB8888; + case DRM_FORMAT_ARGB8888: + return GBM_FORMAT_ARGB8888; + case DRM_FORMAT_RGB565: + return GBM_FORMAT_RGB565; + default: + return GBM_FORMAT_ARGB8888; + } +} + +static void load_egl_extensions(void) +{ + glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC) + eglGetProcAddress("glEGLImageTargetTexture2DOES"); + + if(!glEGLImageTargetTexture2DOES) { + LV_LOG_ERROR("Failed to load glEGLImageTargetTexture2DOES extension"); + } + else { + LV_LOG_INFO("Loaded glEGLImageTargetTexture2DOES extension"); + } +} + +static int open_drm_device(void) +{ + drmDevicePtr devices[64]; + int num_devices, fd = -1; + + num_devices = drmGetDevices2(0, devices, 64); + if(num_devices < 0) { + LV_LOG_ERROR("drmGetDevices2 failed: %s", strerror(-num_devices)); + return -1; + } + + /* Try to find a primary GPU device */ + for(int i = 0; i < num_devices; i++) { + drmDevicePtr device = devices[i]; + if(!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) + continue; + + fd = open(device->nodes[DRM_NODE_PRIMARY], O_RDWR); + if(fd >= 0) { + LV_LOG_INFO("Opened DRM device: %s", device->nodes[DRM_NODE_PRIMARY]); + break; + } + } + + drmFreeDevices(devices, num_devices); + if(fd < 0) { + LV_LOG_ERROR("Failed to open DRM device"); + } + + return fd; +} + +static bool init_buffer(lv_wayland_dmabuf_ctx_t * dmabuf_ctx, lv_wl_buffer_t * buffer, uint32_t width, + uint32_t height, lv_color_format_t cf, lv_wl_egl_display_data_t * ddata) +{ + uint32_t gbm_flags = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; + uint32_t drm_cf = lv_wayland_dmabuf_cf_to_drm(cf); + uint32_t gbm_cf = lv_drm_cf_to_gbm_cf(drm_cf); + + buffer->base.busy = false; + buffer->dmabuf_fd = -1; + + buffer->bo = gbm_bo_create(ddata->gbm_device, width, height, gbm_cf, gbm_flags); + if(!buffer->bo) { + LV_LOG_ERROR("Failed to create GBM buffer object"); + return false; + } + + buffer->stride = gbm_bo_get_stride_for_plane(buffer->bo, 0); + buffer->offset = gbm_bo_get_offset(buffer->bo, 0); + buffer->dmabuf_fd = gbm_bo_get_fd(buffer->bo); + if(buffer->dmabuf_fd < 0) { + LV_LOG_ERROR("Failed to export GBM buffer object as a DMA-BUF fd"); + return false; + } + + EGLint attribs[] = { + EGL_WIDTH, width, + EGL_HEIGHT, height, + EGL_LINUX_DRM_FOURCC_EXT, (EGLint)drm_cf, + EGL_DMA_BUF_PLANE0_FD_EXT, buffer->dmabuf_fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, (EGLint)buffer->offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)buffer->stride, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, (EGLint)(DRM_FORMAT_MOD_LINEAR & 0xFFFFFFFF), + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, (EGLint)(DRM_FORMAT_MOD_LINEAR >> 32), + EGL_NONE + }; + buffer->egl_image = eglCreateImageKHR(ddata->egl_ctx->egl_display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL, attribs); + if(buffer->egl_image == EGL_NO_IMAGE_KHR) { + LV_LOG_ERROR("Failed to create EGL image from DMA-BUF"); + return false; + } + + /* Create texture from EGL image */ + GL_CALL(glGenTextures(1, &buffer->texture_id)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, buffer->texture_id)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, buffer->egl_image); + GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); + + /* The wl_buffer is shared with the compositor through the common DMA-BUF + * helper; buffer->base.wl_buffer is filled in asynchronously. */ + lv_wayland_dmabuf_create_buffer(dmabuf_ctx, &buffer->base, buffer->dmabuf_fd, + width, height, buffer->stride, buffer->offset, drm_cf); + return true; +} + +static void delete_buffer(lv_opengles_egl_t * egl_ctx, lv_wl_buffer_t * buffer) +{ + if(buffer->base.wl_buffer) { + wl_buffer_destroy(buffer->base.wl_buffer); + buffer->base.wl_buffer = NULL; + } + if(buffer->texture_id) { + glDeleteTextures(1, &buffer->texture_id); + buffer->texture_id = 0; + } + if(buffer->egl_image) { + eglDestroyImageKHR(egl_ctx->egl_display, buffer->egl_image); + buffer->egl_image = NULL; + } + if(buffer->bo) { + gbm_bo_destroy(buffer->bo); + buffer->bo = NULL; + } + if(buffer->dmabuf_fd >= 0) { + close(buffer->dmabuf_fd); + buffer->dmabuf_fd = -1; + } +} + +#endif /*LV_WL_EGL_DMABUF_ENABLED*/ + #endif /*LV_WAYLAND_USE_EGL*/ diff --git a/src/drivers/wayland/lv_wayland_dmabuf.c b/src/drivers/wayland/lv_wayland_dmabuf.c new file mode 100644 index 0000000000..d58b611e08 --- /dev/null +++ b/src/drivers/wayland/lv_wayland_dmabuf.c @@ -0,0 +1,337 @@ +/** + * @file lv_wayland_dmabuf.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_wayland_dmabuf.h" + +#if LV_USE_WAYLAND && LV_WAYLAND_USE_DMABUF + +#include +#include +#include +#include +#include +#include + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void dmabuf_done(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback); +static void dmabuf_format_table(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + int32_t fd, uint32_t size); +static void dmabuf_main_device(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + struct wl_array * device); +static void dmabuf_tranche_done(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback); +static void dmabuf_tranche_target_device(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + struct wl_array * device); +static void dmabuf_tranche_formats(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + struct wl_array * indices); +static void dmabuf_tranche_flags(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + uint32_t flags); +static void dmabuf_modifiers(void * data, struct zwp_linux_dmabuf_v1 * zwp_linux_dmabuf, uint32_t format, + uint32_t modifier_hi, uint32_t modifier_lo); +static void dmabuf_format(void * data, struct zwp_linux_dmabuf_v1 * zwp_linux_dmabuf, uint32_t format); + +static void buffer_release(void * data, struct wl_buffer * buffer); +static void create_succeeded(void * data, struct zwp_linux_buffer_params_v1 * params, struct wl_buffer * new_buffer); +static void create_failed(void * data, struct zwp_linux_buffer_params_v1 * params); + +/********************** + * STATIC VARIABLES + **********************/ + +static const struct zwp_linux_dmabuf_feedback_v1_listener dmabuf_listener_v5 = { + .done = dmabuf_done, + .format_table = dmabuf_format_table, + .main_device = dmabuf_main_device, + .tranche_done = dmabuf_tranche_done, + .tranche_target_device = dmabuf_tranche_target_device, + .tranche_formats = dmabuf_tranche_formats, + .tranche_flags = dmabuf_tranche_flags, +}; + +static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener_v3 = { + .format = dmabuf_format, + .modifier = dmabuf_modifiers +}; + +static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { + .format = dmabuf_format +}; + +static const struct zwp_linux_buffer_params_v1_listener params_listener = { + .created = create_succeeded, + .failed = create_failed +}; + +static const struct wl_buffer_listener buffer_listener = { + .release = buffer_release +}; + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_wayland_dmabuf_ctx_init(lv_wayland_dmabuf_ctx_t * ctx) +{ + lv_memset(ctx, 0, sizeof(*ctx)); +} + +void lv_wayland_dmabuf_ctx_deinit(lv_wayland_dmabuf_ctx_t * ctx) +{ + if(!ctx) { + return; + } + if(ctx->feedback) { + zwp_linux_dmabuf_feedback_v1_destroy(ctx->feedback); + ctx->feedback = NULL; + } + if(ctx->handler) { + zwp_linux_dmabuf_v1_destroy(ctx->handler); + ctx->handler = NULL; + } +} + +bool lv_wayland_dmabuf_registry_handle_global(lv_wayland_dmabuf_ctx_t * ctx, struct wl_registry * registry, + uint32_t name, const char * interface, uint32_t version) +{ + if(!lv_streq(interface, zwp_linux_dmabuf_v1_interface.name)) { + return false; + } + + version = LV_MIN(version, (uint32_t)zwp_linux_dmabuf_v1_interface.version); + ctx->handler = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version); + + if(version >= 4) { + ctx->feedback = zwp_linux_dmabuf_v1_get_default_feedback(ctx->handler); + zwp_linux_dmabuf_feedback_v1_add_listener(ctx->feedback, &dmabuf_listener_v5, ctx); + } + else if(version < 3) { + zwp_linux_dmabuf_v1_add_listener(ctx->handler, &dmabuf_listener, ctx); + } + else if(version == 3) { + zwp_linux_dmabuf_v1_add_listener(ctx->handler, &dmabuf_listener_v3, ctx); + } + wl_display_roundtrip(lv_wl_ctx.wl_display); + + return true; +} + +uint32_t lv_wayland_dmabuf_cf_to_drm(lv_color_format_t cf) +{ + if(cf == LV_COLOR_FORMAT_UNKNOWN) { + return DRM_FORMAT_ARGB8888; /* Default to ARGB8888 */ + } + + switch(cf) { + case LV_COLOR_FORMAT_XRGB8888: + return DRM_FORMAT_XRGB8888; + case LV_COLOR_FORMAT_ARGB8888: + case LV_COLOR_FORMAT_ARGB8888_PREMULTIPLIED: + return DRM_FORMAT_ARGB8888; + case LV_COLOR_FORMAT_RGB565: + return DRM_FORMAT_RGB565; + default: + return DRM_FORMAT_ARGB8888; + } +} + +void lv_wayland_dmabuf_create_buffer(lv_wayland_dmabuf_ctx_t * ctx, lv_wayland_dmabuf_buffer_t * buf, + int dmabuf_fd, uint32_t width, uint32_t height, + uint32_t stride, uint32_t offset, uint32_t drm_cf) +{ + /* Will be set on the create callback if the creation is successful */ + buf->wl_buffer = NULL; + + struct zwp_linux_buffer_params_v1 * params = zwp_linux_dmabuf_v1_create_params(ctx->handler); + + zwp_linux_buffer_params_v1_add(params, dmabuf_fd, 0, offset, stride, 0, 0); + + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buf); + zwp_linux_buffer_params_v1_create(params, width, height, drm_cf, 0); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void buffer_release(void * data, struct wl_buffer * buffer) +{ + LV_LOG_TRACE("Buffer released"); + LV_UNUSED(buffer); + lv_wayland_dmabuf_buffer_t * buf = data; + buf->busy = false; +} + +static void create_succeeded(void * data, struct zwp_linux_buffer_params_v1 * params, struct wl_buffer * new_buffer) +{ + LV_LOG_TRACE("Buffer created successfully"); + lv_wayland_dmabuf_buffer_t * buf = data; + buf->wl_buffer = new_buffer; + + /* When not using explicit synchronization listen to wl_buffer.release + * for release notifications, otherwise we are going to use + * zwp_linux_buffer_release_v1. */ + wl_buffer_add_listener(buf->wl_buffer, &buffer_listener, buf); + + zwp_linux_buffer_params_v1_destroy(params); +} + +static void create_failed(void * data, struct zwp_linux_buffer_params_v1 * params) +{ + lv_wayland_dmabuf_buffer_t * buf = data; + buf->wl_buffer = NULL; + zwp_linux_buffer_params_v1_destroy(params); + LV_LOG_ERROR("Failed to create dmabuf buffer"); +} + +static void dmabuf_format_table(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + int32_t fd, uint32_t size) +{ + lv_wayland_dmabuf_ctx_t * ctx = data; + + LV_UNUSED(feedback); + + if(fd < 0 || size == 0) { + LV_LOG_ERROR("Invalid format table fd=%d size=%u", fd, size); + if(fd >= 0) { + close(fd); + } + return; + } + + /* Map the format table file descriptor */ + void * table = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if(table == MAP_FAILED) { + LV_LOG_ERROR("Failed to mmap format table: %s", strerror(errno)); + close(fd); + return; + } + + LV_LOG_TRACE("Received format table with fd %d and size %u", fd, size); + + /* Parse the format table - each entry is 16 bytes: 4 bytes format + 4 bytes padding + 8 bytes modifier */ + size_t num_formats = size / 16; + uint32_t * formats = (uint32_t *)table; + + for(size_t i = 0; i < num_formats; i++) { + /* Each entry is 4 uint32_t words */ + uint32_t format = formats[i * 4]; + if(format == DRM_FORMAT_RGB565) { + ctx->supports_rgb565 = true; + } + } + + /* Clean up */ + munmap(table, size); + close(fd); +} + +static void dmabuf_done(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback) +{ + LV_UNUSED(data); + LV_UNUSED(feedback); + + LV_LOG_TRACE("DMABUF feedback done"); + + /* This event marks the end of a feedback round. The client has received + * all the format and modifier pairs from all tranches. This allows + * the client to proceed with buffer allocation. */ +} + +static void dmabuf_main_device(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + struct wl_array * device) +{ + LV_UNUSED(data); + LV_UNUSED(feedback); + LV_UNUSED(device); + + LV_LOG_TRACE("DMABUF main device received (size: %zu)", device->size); + + /* This event advertises the main device that the server-side allocator + * will use for scanout. It should be used by clients as a hint for + * buffer allocation. */ +} + +static void dmabuf_tranche_done(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback) +{ + LV_UNUSED(data); + LV_UNUSED(feedback); + + LV_LOG_TRACE("DMABUF tranche done"); + + /* This event marks the end of a tranche. This allows the client to + * process the formats and modifiers it has received for this tranche. */ +} + +static void dmabuf_tranche_target_device(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + struct wl_array * device) +{ + LV_UNUSED(data); + LV_UNUSED(feedback); + LV_UNUSED(device); + + LV_LOG_TRACE("DMABUF tranche target device (size: %zu)", device->size); + + /* This event advertises the target device that the following tranche + * will apply to. */ +} + +static void dmabuf_tranche_formats(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + struct wl_array * indices) +{ + LV_UNUSED(data); + LV_UNUSED(feedback); + + LV_LOG_TRACE("DMABUF tranche formats (count: %zu)", indices->size / sizeof(uint16_t)); + + /* This event advertises the format + modifier pairs that the compositor + * supports for the current tranche. The indices are offsets into the + * format table sent earlier. */ + + if(indices->size > 0) { + /* If we don't have a format yet, we could parse the indices here + * to find a suitable format from the format table, but for now + * we rely on the format_table callback to set a format directly */ + LV_LOG_TRACE("Format indices received"); + } +} + +static void dmabuf_tranche_flags(void * data, struct zwp_linux_dmabuf_feedback_v1 * feedback, + uint32_t flags) +{ + LV_UNUSED(data); + LV_UNUSED(feedback); + LV_UNUSED(flags); + + LV_LOG_TRACE("DMABUF tranche flags: 0x%x", flags); + + /* This event advertises the flags for the current tranche. + * Flags can indicate special properties like scanout support. */ +} + +static void dmabuf_modifiers(void * data, struct zwp_linux_dmabuf_v1 * zwp_linux_dmabuf, uint32_t format, + uint32_t modifier_hi, uint32_t modifier_lo) +{ + LV_UNUSED(modifier_hi); + LV_UNUSED(modifier_lo); + dmabuf_format(data, zwp_linux_dmabuf, format); +} + +static void dmabuf_format(void * data, struct zwp_linux_dmabuf_v1 * zwp_linux_dmabuf, uint32_t format) +{ + lv_wayland_dmabuf_ctx_t * ctx = data; + + LV_UNUSED(zwp_linux_dmabuf); + if(format == DRM_FORMAT_RGB565) { + ctx->supports_rgb565 = true; + } +} + +#endif /*LV_USE_WAYLAND && LV_WAYLAND_USE_DMABUF*/ diff --git a/src/drivers/wayland/lv_wayland_dmabuf.h b/src/drivers/wayland/lv_wayland_dmabuf.h new file mode 100644 index 0000000000..0f372e1bb1 --- /dev/null +++ b/src/drivers/wayland/lv_wayland_dmabuf.h @@ -0,0 +1,121 @@ +/** + * @file lv_wayland_dmabuf.h + * + */ + +#ifndef LV_WAYLAND_DMABUF_H +#define LV_WAYLAND_DMABUF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "lv_wayland_private.h" + +#if LV_USE_WAYLAND && LV_WAYLAND_USE_DMABUF + +#include +#include LV_STDBOOL_INCLUDE +#include LV_STDINT_INCLUDE + +/********************** + * TYPEDEFS + **********************/ + +/** + * @brief Shared DMA-BUF protocol context. + * + * Holds the bound zwp_linux_dmabuf_v1 object and the capabilities detected + * during the feedback/format negotiation. One instance per backend is enough; + * it is created in the backend's init() and used as its backend context. + */ +typedef struct { + struct zwp_linux_dmabuf_v1 * handler; /**< Bound protocol object, NULL if unavailable */ + struct zwp_linux_dmabuf_feedback_v1 * feedback; /**< Default feedback proxy, NULL if unused (protocol < 4) */ + bool supports_rgb565; /**< XRGB8888 / ARGB8888 are always supported */ +} lv_wayland_dmabuf_ctx_t; + +/** + * @brief Per-buffer DMA-BUF protocol state. + * + * A backend embeds this in its own buffer structure and adds the + * allocator-specific fields (e.g. a gbm_bo + EGLImage, or an lv_draw_buf). + * The release tracking (@ref busy) and the wl_buffer lifetime are managed + * through this struct by the shared helper. + */ +typedef struct { + struct wl_buffer * wl_buffer; /**< Created asynchronously; NULL until ready or on failure */ + bool busy; /**< true while the compositor still owns the buffer */ +} lv_wayland_dmabuf_buffer_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * @brief Reset a DMA-BUF context to a clean state. + * @param ctx context to initialize + */ +void lv_wayland_dmabuf_ctx_init(lv_wayland_dmabuf_ctx_t * ctx); + +/** + * @brief Release the resources held by a DMA-BUF context. + * @param ctx context to deinitialize + */ +void lv_wayland_dmabuf_ctx_deinit(lv_wayland_dmabuf_ctx_t * ctx); + +/** + * @brief Handle a wl_registry global announcement. + * + * Binds the zwp_linux_dmabuf_v1 interface and starts the format/feedback + * negotiation when the matching global is advertised. + * + * @param ctx DMA-BUF context + * @param registry the wl_registry + * @param name numeric name of the global + * @param interface interface string + * @param version advertised interface version + * @return true if this global was the dmabuf interface and was handled + */ +bool lv_wayland_dmabuf_registry_handle_global(lv_wayland_dmabuf_ctx_t * ctx, struct wl_registry * registry, + uint32_t name, const char * interface, uint32_t version); + +/** + * @brief Convert an LVGL color format to a DRM fourcc code. + * @param cf LVGL color format + * @return the matching DRM_FORMAT_* code (defaults to ARGB8888) + */ +uint32_t lv_wayland_dmabuf_cf_to_drm(lv_color_format_t cf); + +/** + * @brief Create a wl_buffer from a DMA-BUF file descriptor. + * + * Wraps the zwp_linux_buffer_params_v1 sequence. The resulting wl_buffer is + * delivered asynchronously into @p buf->wl_buffer, so the caller must perform a + * wl_display roundtrip and then verify that @p buf->wl_buffer is non-NULL. The + * buffer's release listener clears @p buf->busy when the compositor is done. + * + * @param ctx DMA-BUF context + * @param buf per-buffer protocol state (embedded in the backend buffer) + * @param dmabuf_fd the DMA-BUF file descriptor to share + * @param width buffer width in pixels + * @param height buffer height in pixels + * @param stride row stride in bytes + * @param offset plane offset in bytes + * @param drm_cf DRM fourcc of the buffer + */ +void lv_wayland_dmabuf_create_buffer(lv_wayland_dmabuf_ctx_t * ctx, lv_wayland_dmabuf_buffer_t * buf, + int dmabuf_fd, uint32_t width, uint32_t height, + uint32_t stride, uint32_t offset, uint32_t drm_cf); + +#endif /*LV_USE_WAYLAND && LV_WAYLAND_USE_DMABUF*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_WAYLAND_DMABUF_H*/ diff --git a/src/lvgl_private.h b/src/lvgl_private.h index 6a5910ec2f..6cdaec9f5e 100644 --- a/src/lvgl_private.h +++ b/src/lvgl_private.h @@ -121,6 +121,7 @@ #include "drivers/sdl/lv_sdl_private.h" #include "drivers/uefi/lv_uefi_private.h" #include "drivers/wayland/lv_wayland_backend_private.h" +#include "drivers/wayland/lv_wayland_dmabuf.h" #include "drivers/wayland/lv_wayland_private.h" #include "drivers/windows/lv_windows_context.h" #include "drivers/windows/lv_windows_input_private.h"