From b7f319aea16e15a37d864470d3fcb00a1beedbfe Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Fri, 6 Oct 2023 00:55:50 +0100 Subject: [PATCH] protocol: Add display_info to gamescope-control protocol --- protocol/gamescope-control.xml | 41 +++++++++++++++++---- src/drm.cpp | 67 +++++++++++++++++++++++++++++++++- src/drm.hpp | 5 +++ src/steamcompmgr.cpp | 59 ++++++++++++++++++++++++++++++ src/steamcompmgr.hpp | 2 + src/wlserver.cpp | 18 ++++++++- src/wlserver.hpp | 4 ++ 7 files changed, 185 insertions(+), 11 deletions(-) diff --git a/protocol/gamescope-control.xml b/protocol/gamescope-control.xml index 6c23437..7447e76 100644 --- a/protocol/gamescope-control.xml +++ b/protocol/gamescope-control.xml @@ -29,7 +29,7 @@ it. - + @@ -38,15 +38,40 @@ + - - Says whether a feature is supported and the version. - - - - - + + Says whether a feature is supported and the version. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/drm.cpp b/src/drm.cpp index d9a508b..d0a5526 100644 --- a/src/drm.cpp +++ b/src/drm.cpp @@ -38,6 +38,8 @@ extern "C" { #include "libdisplay-info/cta.h" } +#include "gamescope-control-protocol.h" + struct drm_t g_DRM = {}; uint32_t g_nDRMFormat = DRM_FORMAT_INVALID; @@ -68,6 +70,55 @@ drm_screen_type drm_get_connector_type(drmModeConnector *connector); static void drm_unset_mode( struct drm_t *drm ); static void drm_unset_connector( struct drm_t *drm ); +static uint32_t steam_deck_display_rates[] = +{ + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, +}; + +static uint32_t get_conn_display_info_flags(struct drm_t *drm, struct connector *connector) +{ + if (!connector) + return 0; + + uint32_t flags = 0; + if ( drm_get_connector_type(connector->connector) == DRM_SCREEN_TYPE_INTERNAL ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; + if ( connector->vrr_capable ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; + if ( connector->metadata.supportsST2084 ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; + + return flags; +} + +static void update_connector_display_info_wl(struct drm_t *drm) +{ + if ( !drm->connector ) + return; + + auto& conn = drm->connector; + + wlserver_lock(); + for ( const auto &control : wlserver.gamescope_controls ) + { + uint32_t flags = get_conn_display_info_flags( drm, drm->connector ); + + struct wl_array display_rates; + wl_array_init(&display_rates); + if ( conn->valid_display_rates.size() ) + { + size_t size = conn->valid_display_rates.size() * sizeof(uint32_t); + uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); + memcpy( ptr, conn->valid_display_rates.data(), size ); + } + gamescope_control_send_active_display_info( control, drm->connector->name, drm->connector->make, drm->connector->model, flags, &display_rates ); + wl_array_release(&display_rates); + } + wlserver_unlock(); +} + inline uint64_t drm_calc_s31_32(float val) { // S31.32 sign-magnitude @@ -83,7 +134,7 @@ inline uint64_t drm_calc_s31_32(float val) uint64_t sign_part : 1; } s31_32_bits; uint64_t s31_32; - } color;4 + } color; color.s31_32_bits.sign_part = val < 0 ? 1 : 0; color.s31_32_bits.integral = uint64_t( integral ); @@ -824,6 +875,9 @@ static void parse_edid( drm_t *drm, struct connector *conn) (strcmp(conn->make_pnp, "VLV") == 0 && strcmp(conn->model, "ANX7530 U") == 0) || (strcmp(conn->make_pnp, "VLV") == 0 && strcmp(conn->model, "Jupiter") == 0); + if ( conn->is_steam_deck_display ) + conn->valid_display_rates = std::span(steam_deck_display_rates); + drm_hdr_parse_edid(drm, conn, edid); di_info_destroy(info); @@ -1226,6 +1280,8 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) if (!initial) create_patched_edid(best->edid_data.data(), best->edid_data.size(), drm, best); + update_connector_display_info_wl( drm ); + return true; } @@ -3112,3 +3168,12 @@ void drm_get_native_colorimetry( struct drm_t *drm, *outputEncodingEOTF = EOTF_Gamma22; } } + + +std::span drm_get_valid_refresh_rates( struct drm_t *drm ) +{ + if (drm && drm->connector) + return drm->connector->valid_display_rates; + + return std::span{}; +} diff --git a/src/drm.hpp b/src/drm.hpp index 4d8afc6..e56849a 100644 --- a/src/drm.hpp +++ b/src/drm.hpp @@ -8,6 +8,8 @@ #include #include +#include + #include "color_helpers.h" // Josh: Okay whatever, this header isn't @@ -146,6 +148,7 @@ struct connector { char *make; char *model; bool is_steam_deck_display; + std::span valid_display_rates{}; int target_refresh; bool vrr_capable; @@ -336,6 +339,8 @@ void drm_get_native_colorimetry( struct drm_t *drm, displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ); +std::span drm_get_valid_refresh_rates( struct drm_t *drm ); + extern bool g_bSupportsAsyncFlips; /* from CTA-861-G */ diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 6485e75..81e0553 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -752,6 +752,63 @@ static int g_nDynamicRefreshRate[DRM_SCREEN_TYPE_COUNT] = { 0, 0 }; // Delay to stop modes flickering back and forth. static const uint64_t g_uDynamicRefreshDelay = 600'000'000; // 600ms +static int g_nCombinedAppRefreshCycleOverride[DRM_SCREEN_TYPE_COUNT] = { 0, 0 }; + +static void update_app_target_refresh_cycle() +{ + if ( BIsNested() ) + { + g_nDynamicRefreshRate[ DRM_SCREEN_TYPE_INTERNAL ] = 0; + g_nSteamCompMgrTargetFPS = g_nCombinedAppRefreshCycleOverride[ DRM_SCREEN_TYPE_INTERNAL ]; + return; + } + + static drm_screen_type last_type; + static int last_target_fps; + static bool first = true; + + drm_screen_type type = drm_get_screen_type( &g_DRM ); + int target_fps = g_nCombinedAppRefreshCycleOverride[type]; + + if ( !first && type == last_type && last_target_fps == target_fps ) + { + return; + } + + last_type = type; + last_target_fps = target_fps; + first = false; + + if ( !target_fps ) + { + g_nDynamicRefreshRate[ type ] = 0; + g_nSteamCompMgrTargetFPS = 0; + return; + } + + auto rates = drm_get_valid_refresh_rates( &g_DRM ); + + g_nDynamicRefreshRate[ type ] = 0; + g_nSteamCompMgrTargetFPS = target_fps; + + // Find highest mode to do refresh doubling with. + for ( auto rate = rates.rbegin(); rate != rates.rend(); rate++ ) + { + if (*rate % target_fps == 0) + { + g_nDynamicRefreshRate[ type ] = *rate; + g_nSteamCompMgrTargetFPS = target_fps; + return; + } + } +} + +void steamcompmgr_set_app_refresh_cycle_override( drm_screen_type type, int override_fps ) +{ + g_nCombinedAppRefreshCycleOverride[ type ] = override_fps; + update_app_target_refresh_cycle(); +} + static int g_nRuntimeInfoFd = -1; bool g_bFSRActive = false; @@ -2410,6 +2467,8 @@ paint_all(bool async) pw_buffer = dequeue_pipewire_buffer(); #endif + update_app_target_refresh_cycle(); + int nDynamicRefresh = g_nDynamicRefreshRate[drm_get_screen_type( &g_DRM )]; int nTargetRefresh = nDynamicRefresh && steamcompmgr_window_should_limit_fps( global_focus.focusWindow )// && !global_focus.overlayWindow diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp index 89e621c..164fd82 100644 --- a/src/steamcompmgr.hpp +++ b/src/steamcompmgr.hpp @@ -148,3 +148,5 @@ MouseCursor *steamcompmgr_get_current_cursor(); MouseCursor *steamcompmgr_get_server_cursor(uint32_t serverId); extern int g_nAsyncFlipsEnabled; + +extern void steamcompmgr_set_app_refresh_cycle_override( drm_screen_type type, int override_fps ); diff --git a/src/wlserver.cpp b/src/wlserver.cpp index b73e3d6..eef2f9a 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -65,7 +65,7 @@ extern "C" { static LogScope wl_log("wlserver"); -static struct wlserver_t wlserver = { +struct wlserver_t wlserver = { .touch_down_ids = {} }; @@ -866,13 +866,24 @@ static void create_gamescope_pipewire( void ) // +static void gamescope_control_set_app_target_refresh_cycle( struct wl_client *client, struct wl_resource *resource, uint32_t fps, uint32_t flags ) +{ + auto display_type = DRM_SCREEN_TYPE_EXTERNAL; + if ( flags & GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_INTERNAL_DISPLAY ) + display_type = DRM_SCREEN_TYPE_INTERNAL; + + steamcompmgr_set_app_refresh_cycle_override( display_type, fps ); +} + static void gamescope_control_handle_destroy( struct wl_client *client, struct wl_resource *resource ) { + std::erase_if(wlserver.gamescope_controls, [=](auto control) { return control == resource; }); wl_resource_destroy( resource ); } static const struct gamescope_control_interface gamescope_control_impl = { .destroy = gamescope_control_handle_destroy, + .set_app_target_refresh_cycle = gamescope_control_set_app_target_refresh_cycle, }; static void gamescope_control_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id ) @@ -882,12 +893,15 @@ static void gamescope_control_bind( struct wl_client *client, void *data, uint32 // Send feature support gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_RESHADE_SHADERS, 1, 0 ); + gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DISPLAY_INFO, 1, 0 ); gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DONE, 0, 0 ); + + wlserver.gamescope_controls.push_back(resource); } static void create_gamescope_control( void ) { - uint32_t version = 1; + uint32_t version = 2; wl_global_create( wlserver.display, &gamescope_control_interface, version, NULL, gamescope_control_bind ); } diff --git a/src/wlserver.hpp b/src/wlserver.hpp index 6bb47a2..07f36d6 100644 --- a/src/wlserver.hpp +++ b/src/wlserver.hpp @@ -136,8 +136,12 @@ struct wlserver_t { std::atomic xdg_dirty; std::mutex xdg_commit_lock; std::vector xdg_commit_queue; + + std::vector gamescope_controls; }; +extern struct wlserver_t wlserver; + std::vector wlserver_xdg_commit_queue(); struct wlserver_keyboard {