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.
This commit is contained in:
zoltanvb 2023-05-19 12:05:57 +02:00 committed by LibretroAdmin
parent 6be1ef5d85
commit afa0e389aa
11 changed files with 303 additions and 21 deletions

View file

@ -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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <compat/strl.h>
#include <string/stdstring.h>
#include <sys/types.h>
#include <unistd.h>
#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"
};

View file

@ -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 */

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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)

View file

@ -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,

View file

@ -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,

View file

@ -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

View file

@ -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;