From afa0e389aa641b0f44504effc16bcff022481a22 Mon Sep 17 00:00:00 2001 From: zoltanvb Date: Fri, 19 May 2023 12:05:57 +0200 Subject: [PATCH] Display server for KMS Add a display server construct for DRM/KMS mode. The main use is to provide resolution changes (including automatic refresh rate switch) for this configuration, i.e. DRM context and OpenGL drivers. To enable refresh rate restoration after automatic refresh rate change, av_info->timing_fps is also adjusted on core close / RA exit. No effects expected on CRTSwitchRes. --- Makefile.common | 3 +- gfx/display_servers/dispserv_kms.c | 233 +++++++++++++++++++++++++++++ gfx/drivers_context/drm_ctx.c | 21 ++- gfx/video_defines.h | 3 +- gfx/video_display_server.h | 1 + gfx/video_driver.c | 36 +++-- griffin/griffin.c | 1 + intl/msg_hash_us.h | 6 +- msg_hash.h | 1 + retroarch.c | 12 +- runloop.c | 7 +- 11 files changed, 303 insertions(+), 21 deletions(-) create mode 100644 gfx/display_servers/dispserv_kms.c diff --git a/Makefile.common b/Makefile.common index cde29a815d..37544e28f2 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1391,7 +1391,8 @@ OBJ += gfx/drivers_context/gfx_null_ctx.o ifeq ($(HAVE_KMS), 1) HAVE_AND_WILL_USE_DRM = 1 - OBJ += gfx/drivers_context/drm_ctx.o + OBJ += gfx/drivers_context/drm_ctx.o \ + gfx/display_servers/dispserv_kms.o ifeq ($(HAVE_ODROIDGO2), 1) OBJ += gfx/drivers_context/drm_go2_ctx.o diff --git a/gfx/display_servers/dispserv_kms.c b/gfx/display_servers/dispserv_kms.c new file mode 100644 index 0000000000..e434501847 --- /dev/null +++ b/gfx/display_servers/dispserv_kms.c @@ -0,0 +1,233 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2017 - Daniel De Matteis + * Copyright (C) 2016-2019 - Brad Parker + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include + +#include +#include + +#include +#include + +#ifdef HAVE_CONFIG_H +#include "../../config.h" +#endif + + +#include "../video_display_server.h" +#include "../../retroarch.h" +#include "../video_crt_switch.h" /* Needed to set aspect for low resolution in Linux */ +#include "../common/drm_common.h" +#include "../../verbosity.h" + +typedef struct +{ + int crt_name_id; + int monitor_index; + unsigned opacity; + uint8_t flags; + char crt_name[16]; + char new_mode[256]; + char old_mode[256]; + char orig_output[256]; +} dispserv_kms_t; + +static bool kms_display_server_set_resolution(void *data, + unsigned width, unsigned height, int int_hz, float hz, + int center, int monitor_index, int xoffset, int padjust) +{ + unsigned curr_width = 0; + unsigned curr_height = 0; + unsigned curr_bpp = 0; + float curr_refreshrate = 0; + bool retval = false; + int reinit_flags = DRIVERS_CMD_ALL; + dispserv_kms_t *dispserv = (dispserv_kms_t*)data; + + if (!dispserv) + return false; + + if (g_drm_mode) + { + curr_refreshrate = drm_calc_refresh_rate(g_drm_mode); + curr_width = g_drm_mode->hdisplay; + curr_height = g_drm_mode->vdisplay; + curr_bpp = 32; + } + RARCH_DBG("[DRM]: Display server set resolution - incoming: %d x %d, %f Hz\n",width, height, hz); + + if (width == 0) + width = curr_width; + if (height == 0) + height = curr_height; + if (curr_bpp == 0) + curr_bpp = curr_bpp; + if (hz == 0) + hz = curr_refreshrate; + + /* set core refresh from hz */ + video_monitor_set_refresh_rate(hz); + + RARCH_DBG("[DRM]: Display server set resolution - actual: %d x %d, %f Hz\n",width, height, hz); + + retval = video_driver_set_video_mode(width, height, true); + + /* Reinitialize drivers. */ + command_event(CMD_EVENT_REINIT, &reinit_flags); + + return retval; +} + +/* TODO: move to somewhere common as it is reused from dispserv_win32.c */ +/* Display resolution list qsort helper function */ +static int resolution_list_qsort_func( + const video_display_config_t *a, const video_display_config_t *b) +{ + char str_a[64]; + char str_b[64]; + + if (!a || !b) + return 0; + + str_a[0] = str_b[0] = '\0'; + + snprintf(str_a, sizeof(str_a), "%04dx%04d (%d Hz)", + a->width, + a->height, + a->refreshrate); + + snprintf(str_b, sizeof(str_b), "%04dx%04d (%d Hz)", + b->width, + b->height, + b->refreshrate); + + return strcasecmp(str_a, str_b); +} + +static void *kms_display_server_get_resolution_list( + void *data, unsigned *len) +{ + unsigned i = 0; + unsigned j = 0; + unsigned count = 0; + unsigned curr_width = 0; + unsigned curr_height = 0; + unsigned curr_bpp = 0; + float curr_refreshrate = 0; + unsigned curr_orientation = 0; + struct video_display_config *conf = NULL; + + + if (g_drm_mode) + { + curr_refreshrate = drm_calc_refresh_rate(g_drm_mode); + curr_width = g_drm_mode->hdisplay; + curr_height = g_drm_mode->vdisplay; + curr_bpp = 32; + } + + *len = g_drm_connector->count_modes; + if (!(conf = (struct video_display_config*) + calloc(*len, sizeof(struct video_display_config)))) + return NULL; + + for (i = 0, j = 0; (int)i < g_drm_connector->count_modes; i++) + { + conf[j].width = g_drm_connector->modes[i].hdisplay; + conf[j].height = g_drm_connector->modes[i].vdisplay; + conf[j].bpp = 32; + conf[j].refreshrate = floor(drm_calc_refresh_rate(&g_drm_connector->modes[i])); + conf[j].idx = j; + conf[j].current = false; + + if ( (conf[j].width == curr_width) + && (conf[j].height == curr_height) + && (conf[j].bpp == curr_bpp) + && (drm_calc_refresh_rate(&g_drm_connector->modes[i]) == curr_refreshrate) + ) + conf[j].current = true; + j++; + } + + qsort( + conf, count, + sizeof(video_display_config_t), + (int (*)(const void *, const void *)) + resolution_list_qsort_func); + + return conf; +} + +/* TODO: screen orientation has support in DRM via planes, although not really exposed via xf86drm */ +#if 0 +static void kms_display_server_set_screen_orientation(void *data, + enum rotation rotation) +{ +} + +static enum rotation kms_display_server_get_screen_orientation(void *data) +{ + int i, j; + enum rotation rotation = ORIENTATION_NORMAL; + dispserv_kms_t *dispserv = (dispserv_kms_t*)data; + return rotation; +} +#endif + +static void* kms_display_server_init(void) +{ + dispserv_kms_t *dispserv = (dispserv_kms_t*)calloc(1, sizeof(*dispserv)); + + if (dispserv) + return dispserv; + return NULL; +} + +static void kms_display_server_destroy(void *data) +{ + dispserv_kms_t *dispserv = (dispserv_kms_t*)data; + if (dispserv) + free(dispserv); +} + +static bool kms_display_server_set_window_opacity(void *data, unsigned opacity) +{ + return true; +} + +static uint32_t kms_display_server_get_flags(void *data) +{ + uint32_t flags = 0; + BIT32_SET(flags, DISPSERV_CTX_CRT_SWITCHRES); + + return flags; +} + +const video_display_server_t dispserv_kms = { + kms_display_server_init, + kms_display_server_destroy, + kms_display_server_set_window_opacity, + NULL, /* set_window_progress */ + NULL, /* set window decorations */ + kms_display_server_set_resolution, + kms_display_server_get_resolution_list, + NULL, /* get output options */ + NULL, /* kms_display_server_set_screen_orientation */ + NULL, /* kms_display_server_get_screen_orientation */ + kms_display_server_get_flags, + "kms" +}; diff --git a/gfx/drivers_context/drm_ctx.c b/gfx/drivers_context/drm_ctx.c index 5b59e4c129..c11c5e90c8 100644 --- a/gfx/drivers_context/drm_ctx.c +++ b/gfx/drivers_context/drm_ctx.c @@ -634,6 +634,19 @@ static void gfx_ctx_drm_get_video_size(void *data, *height = drm->fb_height; } +static void gfx_ctx_drm_get_video_output_size(void *data, + unsigned *width, unsigned *height, char *desc, size_t desc_len) +{ + gfx_ctx_drm_data_t *drm = (gfx_ctx_drm_data_t*)data; + + if (!drm) + return; + + *width = drm->fb_width; + *height = drm->fb_height; + +} + static void free_drm_resources(gfx_ctx_drm_data_t *drm) { if (!drm) @@ -783,6 +796,8 @@ nextgpu: g_drm_fd = fd; + video_driver_display_type_set(RARCH_DISPLAY_KMS); + return drm; error: @@ -831,7 +846,10 @@ static bool gfx_ctx_drm_set_video_mode(void *data, return true; } if ((width == 0 && height == 0) || !fullscreen) + { g_drm_mode = &g_drm_connector->modes[0]; + RARCH_WARN("[KMS]: Falling back to mode 0 (default)\n"); + } else { /* check if custom HDMI timings were asked */ @@ -916,6 +934,7 @@ static bool gfx_ctx_drm_set_video_mode(void *data, error: gfx_ctx_drm_destroy_resources(drm); + RARCH_ERR("[KMS]: Error when switching mode.\n"); if (drm) free(drm); @@ -1074,7 +1093,7 @@ const gfx_ctx_driver_t gfx_ctx_drm = { gfx_ctx_drm_set_video_mode, gfx_ctx_drm_get_video_size, drm_get_refresh_rate, - NULL, /* get_video_output_size */ + gfx_ctx_drm_get_video_output_size, NULL, /* get_video_output_prev */ NULL, /* get_video_output_next */ NULL, /* get_metrics */ diff --git a/gfx/video_defines.h b/gfx/video_defines.h index 6bf67c7ccb..0dcdc694e3 100644 --- a/gfx/video_defines.h +++ b/gfx/video_defines.h @@ -101,7 +101,8 @@ enum rarch_display_type /* video_display => N/A, video_window => HWND */ RARCH_DISPLAY_WIN32, RARCH_DISPLAY_WAYLAND, - RARCH_DISPLAY_OSX + RARCH_DISPLAY_OSX, + RARCH_DISPLAY_KMS }; enum font_driver_render_api diff --git a/gfx/video_display_server.h b/gfx/video_display_server.h index 52392a1736..f29ac527e3 100644 --- a/gfx/video_display_server.h +++ b/gfx/video_display_server.h @@ -99,6 +99,7 @@ enum rotation video_display_server_get_screen_orientation(void); extern const video_display_server_t dispserv_win32; extern const video_display_server_t dispserv_x11; +extern const video_display_server_t dispserv_kms; extern const video_display_server_t dispserv_android; RETRO_END_DECLS diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 160029c4c1..3b03f81fe7 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -686,21 +686,22 @@ void video_monitor_compute_fps_statistics(uint64_t void video_monitor_set_refresh_rate(float hz) { - char msg[128]; + char msg[256]; + char rate[8]; settings_t *settings = config_get_ptr(); - /* TODO/FIXME - localize */ - size_t _len = strlcpy(msg, "Setting refresh rate to", sizeof(msg)); - msg[_len ] = ':'; - msg[++_len] = ' '; - msg[++_len] = '\0'; - _len += snprintf(msg + _len, sizeof(msg) - _len, "%.3f", hz); - msg[_len ] = ' '; - msg[_len+1] = 'H'; - msg[_len+2] = 'z'; - msg[_len+3] = '.'; - msg[_len+4] = '\0'; + + /* Avoid message spamming if there is no change. */ + if (settings->floats.video_refresh_rate == hz) + return; + + snprintf(rate, sizeof(rate), "%.3f", hz); + snprintf(msg, sizeof(msg), + msg_hash_to_str(MSG_VIDEO_REFRESH_RATE_CHANGED), rate); + + /* Message is visible for twice the usual duration */ + /* as modeswitch will cause monitors to go blank for a while */ if (settings->bools.notification_show_refresh_rate) - runloop_msg_queue_push(msg, 1, 180, false, NULL, + runloop_msg_queue_push(msg, 1, 360, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); RARCH_LOG("[Video]: %s\n", msg); @@ -1064,6 +1065,11 @@ void* video_display_server_init(enum rarch_display_type type) case RARCH_DISPLAY_X11: #if defined(HAVE_X11) current_display_server = &dispserv_x11; +#endif + break; + case RARCH_DISPLAY_KMS: +#if defined(HAVE_KMS) + current_display_server = &dispserv_kms; #endif break; default: @@ -1140,6 +1146,7 @@ bool video_display_server_set_resolution(unsigned width, unsigned height, int int_hz, float hz, int center, int monitor_index, int xoffset, int padjust) { video_driver_state_t *video_st = &video_driver_st; + RARCH_DBG("[Video]: Display server set resolution, hz: %f\n",hz); if (current_display_server && current_display_server->set_resolution) return current_display_server->set_resolution( video_st->current_display_server_data, width, height, int_hz, @@ -1254,6 +1261,7 @@ void video_switch_refresh_rate_maybe( bool video_display_server_set_refresh_rate(float hz) { video_driver_state_t *video_st = &video_driver_st; + RARCH_DBG("[Video]: Display server set refresh rate %f\n", hz); if (current_display_server && current_display_server->set_resolution) return current_display_server->set_resolution( video_st->current_display_server_data, 0, 0, (int)hz, @@ -1270,7 +1278,7 @@ void video_display_server_restore_refresh_rate(void) if (!refresh_rate_original || refresh_rate_current == refresh_rate_original) return; - + RARCH_DBG("[Video]: Display server restore refresh rate %f\n", refresh_rate_original); video_monitor_set_refresh_rate(refresh_rate_original); video_display_server_set_refresh_rate(refresh_rate_original); } diff --git a/griffin/griffin.c b/griffin/griffin.c index 3b5e667c72..9e7eb0bf65 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -311,6 +311,7 @@ VIDEO CONTEXT #if defined(HAVE_KMS) #include "../gfx/drivers_context/drm_ctx.c" +#include "../gfx/display_servers/dispserv_kms.c" #endif #if defined(HAVE_EGL) diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index d984796d37..359b576236 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -14713,6 +14713,10 @@ MSG_HASH( MSG_VRR_RUNLOOP_DISABLED, "Sync to exact content framerate disabled." ) +MSG_HASH( + MSG_VIDEO_REFRESH_RATE_CHANGED, + "Video refresh rate changed to %s Hz." + ) /* Lakka */ @@ -14797,7 +14801,7 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_SUBLABEL_SCREEN_RESOLUTION, - "Select display mode." + "Select display mode (Restart required)" ) MSG_HASH( MENU_ENUM_LABEL_VALUE_SHUTDOWN, diff --git a/msg_hash.h b/msg_hash.h index 548e47197b..240f7eb5ac 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -560,6 +560,7 @@ enum msg_hash_enums MSG_FAILED_TO_ENTER_GAMEMODE_LINUX, MSG_VRR_RUNLOOP_ENABLED, MSG_VRR_RUNLOOP_DISABLED, + MSG_VIDEO_REFRESH_RATE_CHANGED, MSG_IOS_TOUCH_MOUSE_ENABLED, MSG_IOS_TOUCH_MOUSE_DISABLED, diff --git a/retroarch.c b/retroarch.c index 125f693280..f029da52c8 100644 --- a/retroarch.c +++ b/retroarch.c @@ -802,6 +802,7 @@ void drivers_init( bool windowed_fullscreen = settings->bools.video_fullscreen && settings->bools.video_windowed_fullscreen; bool all_fullscreen = settings->bools.video_fullscreen || settings->bools.video_windowed_fullscreen; + /* Making a switch from PC standard 60 Hz to NTSC 59.94 is excluded by the last condition. */ if ( refresh_rate > 0.0 && !settings->uints.crt_switch_resolution && !settings->bools.vrr_runloop_enable @@ -7013,6 +7014,7 @@ bool retroarch_main_quit(void) video_driver_state_t*video_st = video_state_get_ptr(); settings_t *settings = config_get_ptr(); bool config_save_on_exit = settings->bools.config_save_on_exit; + struct retro_system_av_info *av_info = &video_st->av_info; /* Restore video driver before saving */ video_driver_restore_cached(settings); @@ -7052,9 +7054,15 @@ bool retroarch_main_quit(void) /* Restore original refresh rate, if it has been changed * automatically in SET_SYSTEM_AV_INFO */ - if (video_st->video_refresh_rate_original) - video_display_server_restore_refresh_rate(); + if (video_st->video_refresh_rate_original) + { + RARCH_DBG("[Video]: Restoring original refresh rate: %f Hz\n", video_st->video_refresh_rate_original); + /* Set the av_info fps also to the original refresh rate */ + /* to avoid re-initialization problems */ + av_info->timing.fps = video_st->video_refresh_rate_original; + video_display_server_restore_refresh_rate(); + } if (!(runloop_st->flags & RUNLOOP_FLAG_SHUTDOWN_INITIATED)) { /* Save configs before quitting diff --git a/runloop.c b/runloop.c index 49124726c1..a396ddc3a6 100644 --- a/runloop.c +++ b/runloop.c @@ -3912,8 +3912,13 @@ void runloop_event_deinit_core(void) /* Restore original refresh rate, if it has been changed * automatically in SET_SYSTEM_AV_INFO */ if (video_st->video_refresh_rate_original) + { + /* Set the av_info fps also to the original refresh rate */ + /* to avoid re-initialization problems */ + struct retro_system_av_info *av_info = &video_st->av_info; + av_info->timing.fps = video_st->video_refresh_rate_original; video_display_server_restore_refresh_rate(); - + } /* Recalibrate frame delay target */ if (settings->bools.video_frame_delay_auto) video_st->frame_delay_target = 0;