feat(drm): add ability to select display mode (#8992)

This commit is contained in:
André Costa
2025-10-06 17:32:10 +02:00
committed by GitHub
parent 2bf7436e3a
commit 35ecac5c09
6 changed files with 226 additions and 1 deletions
@@ -55,7 +55,6 @@ Basic Usage
#include "lvgl/lvgl.h"
#include "lvgl/demos/lv_demos.h"
#include "lvgl/drivers/drm/lv_linux_drm.h"
int main(void)
{
@@ -152,3 +151,116 @@ No special setup is required beyond the basic DRM initialization shown in :ref:`
For a detailed overview of EGL usage and configuration, see :ref:`egl_driver`.
Selecting Display Mode
----------------------
.. note::
Custom mode selection is currently only supported when using DRM with EGL
(``LV_LINUX_DRM_USE_EGL`` enabled). When using DRM without EGL, the driver
will always use the preferred display mode.
By default, the DRM driver automatically selects the preferred display mode for the connected display. However, you can customize this behavior by providing a mode selection callback.
Custom Mode Selection
~~~~~~~~~~~~~~~~~~~~~
To implement custom mode selection logic, define a callback function and register it with :cpp:func:`lv_linux_drm_set_mode_cb`:
.. code-block:: c
#include "lvgl/lvgl.h"
/* Custom mode selection callback */
size_t my_mode_selector(lv_display_t * disp, const lv_linux_drm_mode_t * modes, size_t mode_count)
{
/* Example: Select the first 1920x1080@60Hz mode */
for(size_t i = 0; i < mode_count; i++) {
int32_t width = lv_linux_drm_mode_get_horizontal_resolution(&modes[i]);
int32_t height = lv_linux_drm_mode_get_vertical_resolution(&modes[i]);
int32_t refresh = lv_linux_drm_mode_get_refresh_rate(&modes[i]);
if(width == 1920 && height == 1080 && refresh == 60) {
return i; /* Return the index of the selected mode */
}
}
/* Fallback: return the first mode */
return 0;
}
int main(void)
{
lv_init();
lv_display_t * disp = lv_linux_drm_create();
/* Set custom mode selection callback */
lv_linux_drm_set_mode_cb(disp, my_mode_selector);
lv_linux_drm_set_file(disp, "/dev/dri/card0", -1);
/* ... rest of your application ... */
}
The callback receives an array of available modes and must return the index of the desired mode.
Mode Information API
~~~~~~~~~~~~~~~~~~~~
The following functions are available to query mode properties:
- :cpp:func:`lv_linux_drm_mode_get_horizontal_resolution` - Get width in pixels
- :cpp:func:`lv_linux_drm_mode_get_vertical_resolution` - Get height in pixels
- :cpp:func:`lv_linux_drm_mode_get_refresh_rate` - Get refresh rate in Hz
- :cpp:func:`lv_linux_drm_mode_is_preferred` - Check if mode is the display's preferred/native mode
Example: Selecting Preferred Mode
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
size_t select_preferred_mode(lv_display_t * disp, const lv_linux_drm_mode_t * modes, size_t mode_count)
{
/* Find and select the preferred mode */
for(size_t i = 0; i < mode_count; i++) {
if(lv_linux_drm_mode_is_preferred(&modes[i])) {
return i;
}
}
/* If no preferred mode found, return the first mode */
return 0;
}
Example: Selecting Highest Resolution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: c
size_t select_highest_resolution(lv_display_t * disp, const lv_linux_drm_mode_t * modes, size_t mode_count)
{
size_t best_index = 0;
int32_t max_pixels = 0;
for(size_t i = 0; i < mode_count; i++) {
int32_t width = lv_linux_drm_mode_get_horizontal_resolution(&modes[i]);
int32_t height = lv_linux_drm_mode_get_vertical_resolution(&modes[i]);
int32_t pixels = width * height;
if(pixels > max_pixels) {
max_pixels = pixels;
best_index = i;
}
}
return best_index;
}
Notes
~~~~~
- The mode selection callback is called before the display is initialized.
- If no callback is set, the driver uses the preferred mode by default.
- Ensure the callback always returns a valid index (0 to ``mode_count - 1``).
- To restore default behavior, call :cpp:func:`lv_linux_drm_set_mode_cb` with ``NULL`` as the callback.
+6
View File
@@ -251,6 +251,12 @@ void lv_linux_drm_set_file(lv_display_t * disp, const char * file, int64_t conne
hor_res, ver_res, lv_display_get_dpi(disp));
}
void lv_linux_drm_set_mode_cb(lv_display_t * disp, lv_linux_drm_select_mode_cb_t callback)
{
LV_UNUSED(disp);
LV_UNUSED(callback);
LV_LOG_WARN("DRM without EGL support doesn't currently support setting a mode selection callback");
}
/**********************
* STATIC FUNCTIONS
**********************/
+53
View File
@@ -17,6 +17,7 @@ extern "C" {
#include "../../../display/lv_display.h"
#if LV_USE_LINUX_DRM
#include <xf86drmMode.h>
/*********************
* DEFINES
@@ -26,6 +27,19 @@ extern "C" {
* TYPEDEFS
**********************/
typedef drmModeModeInfo lv_linux_drm_mode_t;
/**
* Callback function type for selecting a DRM display mode
* @param disp pointer to the display object
* @param modes array of available DRM modes
* @param mode_count number of modes in the array
* @return index of the selected mode from the modes array
*/
typedef size_t (*lv_linux_drm_select_mode_cb_t)(lv_display_t * disp,
const lv_linux_drm_mode_t * modes,
size_t mode_count);
/**********************
* GLOBAL PROTOTYPES
**********************/
@@ -64,6 +78,45 @@ void lv_linux_drm_set_file(lv_display_t * disp, const char * file, int64_t conne
*/
char * lv_linux_drm_find_device_path(void);
/**
* Set a callback function for custom DRM mode selection to override the default mode selection behavior
*
* The default mode selection behavior is selecting the native mode
*
* @param disp pointer to the display object
* @param callback function to be called when a display mode needs to be selected,
* or NULL to use the default mode selection behavior
*/
void lv_linux_drm_set_mode_cb(lv_display_t * disp, lv_linux_drm_select_mode_cb_t callback);
/**
* Get the horizontal resolution of a DRM mode
* @param mode pointer to the DRM mode object
* @return horizontal resolution in pixels, or 0 if mode is invalid
*/
int32_t lv_linux_drm_mode_get_horizontal_resolution(const lv_linux_drm_mode_t * mode);
/**
* Get the vertical resolution of a DRM mode
* @param mode pointer to the DRM mode object
* @return vertical resolution in pixels, or 0 if mode is invalid
*/
int32_t lv_linux_drm_mode_get_vertical_resolution(const lv_linux_drm_mode_t * mode);
/**
* Get the refresh rate of a DRM mode
* @param mode pointer to the DRM mode object
* @return refresh rate in Hz, or 0 if mode is invalid
*/
int32_t lv_linux_drm_mode_get_refresh_rate(const lv_linux_drm_mode_t * mode);
/**
* Check if a DRM mode is the preferred mode for the display
* @param mode pointer to the DRM mode object
* @return true if this is the preferred/native mode, false otherwise
*/
bool lv_linux_drm_mode_is_preferred(const lv_linux_drm_mode_t * mode);
/**********************
* MACROS
**********************/
@@ -12,6 +12,7 @@
#if LV_USE_LINUX_DRM
#include <dirent.h>
#include <xf86drmMode.h>
#include "lv_linux_drm.h"
#include "../../../stdlib/lv_sprintf.h"
@@ -92,4 +93,36 @@ static char * find_by_class(void)
}
int32_t lv_linux_drm_mode_get_horizontal_resolution(const lv_linux_drm_mode_t * mode)
{
if(!mode) {
return 0;
}
return mode->hdisplay;
}
int32_t lv_linux_drm_mode_get_vertical_resolution(const lv_linux_drm_mode_t * mode)
{
if(!mode) {
return 0;
}
return mode->vdisplay;
}
int32_t lv_linux_drm_mode_get_refresh_rate(const lv_linux_drm_mode_t * mode)
{
if(!mode) {
return 0;
}
return mode->vrefresh;
}
bool lv_linux_drm_mode_is_preferred(const lv_linux_drm_mode_t * mode)
{
if(!mode) {
return false;
}
return (mode->type & DRM_MODE_TYPE_PREFERRED) != 0;
}
#endif /*LV_USE_LINUX_DRM*/
@@ -141,6 +141,15 @@ void lv_linux_drm_set_file(lv_display_t * display, const char * file, int64_t co
lv_display_add_event_cb(ctx->display, event_cb, LV_EVENT_DELETE, NULL);
}
void lv_linux_drm_set_mode_cb(lv_display_t * disp, lv_linux_drm_select_mode_cb_t callback)
{
if(!disp) {
LV_LOG_ERROR("Cannot set a mode select callback on a NULL display");
return;
}
lv_drm_ctx_t * ctx = lv_display_get_driver_data(disp);
ctx->mode_select_cb = callback;
}
/**********************
* STATIC FUNCTIONS
@@ -621,6 +630,16 @@ static drmModeConnector * drm_get_connector(lv_drm_ctx_t * ctx)
static drmModeModeInfo * drm_get_mode(lv_drm_ctx_t * ctx)
{
LV_ASSERT_NULL(ctx->drm_connector);
if(ctx->mode_select_cb) {
size_t mode_index = ctx->mode_select_cb(ctx->display, (lv_linux_drm_mode_t *)ctx->drm_connector->modes,
(size_t)ctx->drm_connector->count_modes);
if(mode_index >= (size_t)ctx->drm_connector->count_modes) {
LV_LOG_ERROR("Failed to select drm mode. User select callback return an invalid mode index");
return NULL;
}
return &ctx->drm_connector->modes[mode_index];
}
drmModeModeInfo * best_mode = NULL;
uint32_t best_area = 0;
@@ -23,6 +23,7 @@ extern "C" {
#include "../../opengles/lv_opengles_texture_private.h"
#include "../../opengles/lv_opengles_egl.h"
#include "../../opengles/lv_opengles_egl_private.h"
#include "lv_linux_drm.h"
/*********************
* DEFINES
@@ -50,6 +51,7 @@ typedef struct {
struct gbm_bo * gbm_bo_flipped;
struct gbm_bo * gbm_bo_presented;
lv_linux_drm_select_mode_cb_t mode_select_cb;
int fd;
bool crtc_isset;
} lv_drm_ctx_t;