feat(wayland): add DMA-BUF software fast-path to the EGL backend (#10339)

Signed-off-by: Sang Tran <sang.tran.jc@renesas.com>
Signed-off-by: Vu Dang <vu.dang.te@renesas.com>
This commit is contained in:
vudangRVC
2026-08-04 16:10:04 +02:00
committed by GitHub
parent 06c5fb6b52
commit 24bd5f28fc
11 changed files with 1114 additions and 13 deletions
+5
View File
@@ -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
@@ -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
+3 -2
View File
@@ -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()
+2 -2
View File
@@ -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")
+8
View File
@@ -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
+1
View File
@@ -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",
+5
View File
@@ -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
File diff suppressed because it is too large Load Diff
+337
View File
@@ -0,0 +1,337 @@
/**
* @file lv_wayland_dmabuf.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_wayland_dmabuf.h"
#if LV_USE_WAYLAND && LV_WAYLAND_USE_DMABUF
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <drm/drm_fourcc.h>
#include <wayland_linux_dmabuf.h>
/**********************
* 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, &params_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*/
+121
View File
@@ -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 <wayland-client.h>
#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*/
+1
View File
@@ -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"