Files
packages/anda/games/terra-gamescope/handheld.patch
T
Raboneko 23852fb5d9 Update gamescope (#6444) (#6449)
(cherry picked from commit d5350cea38)

Co-authored-by: Pornpipat Popum <cappy@cappuchino.xyz>
2025-09-21 01:17:11 -05:00

2051 lines
80 KiB
Diff
Executable File

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 22 Nov 2024 01:37:48 +0100
Subject: [NA] add dev script
---
sync.sh | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100755 sync.sh
diff --git a/sync.sh b/sync.sh
new file mode 100755
index 000000000000..8dd5815d4aeb
--- /dev/null
+++ b/sync.sh
@@ -0,0 +1,21 @@
+if [ -z "$1" ]; then
+ echo "Usage: $0 <host>"
+ exit 1
+fi
+
+HOST=$1
+RSYNC="rsync -rv --exclude .git --exclude venv --exclude __pycache__'"
+USER=${USER:-bazzite}
+
+set -e
+
+meson build/ -Dforce_fallback_for=stb,libdisplay-info,libliftoff,wlroots,vkroots -Denable_openvr_support=false
+ninja -C build/
+scp build/src/gamescope ${HOST}:gamescope
+
+ssh $HOST /bin/bash << EOF
+ sudo rpm-ostree usroverlay --hotfix
+ sudo mv ~/gamescope /usr/bin/gamescope
+ bazzite-session-select gamescope
+ # sudo reboot
+EOF
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Anderson <ruinairas1992@gmail.com>
Date: Fri, 17 May 2024 21:56:55 -0500
Subject: feat: add --custom-refresh-rates option (+ fixes)
Commit originally by Matthew, external fixes by Kyle, and new system check
move by Antheas.
Co-authored-by: Kyle Gospodnetich <me@kylegospodneti.ch>
Co-authored-by: Antheas Kapenekakis <git@antheas.dev>
---
src/Backends/DRMBackend.cpp | 4 +++-
src/main.cpp | 31 +++++++++++++++++++++++++++++++
src/main.hpp | 2 ++
3 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 1ec2699821f0..c8e821314dc4 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2342,8 +2342,10 @@ namespace gamescope
}
else
{
+ if ( g_customRefreshRates.size() > 0 && GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL )
+ m_Mutable.ValidDynamicRefreshRates = g_customRefreshRates;
// Unknown display, see if there are any other refresh rates in the EDID we can get.
- if ( GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL || cv_drm_allow_dynamic_modes_for_external_display )
+ else if ( GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL || cv_drm_allow_dynamic_modes_for_external_display )
{
const drmModeModeInfo *pPreferredMode = find_mode( m_pConnector.get(), 0, 0, 0 );
diff --git a/src/main.cpp b/src/main.cpp
index cdb35c3b2518..d63b1fe50cc6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -133,6 +133,7 @@ const struct option *gamescope_options = (struct option[]){
{ "fade-out-duration", required_argument, nullptr, 0 },
{ "force-orientation", required_argument, nullptr, 0 },
{ "force-windows-fullscreen", no_argument, nullptr, 0 },
+ { "custom-refresh-rates", required_argument, nullptr, 0 },
{ "disable-color-management", no_argument, nullptr, 0 },
{ "sdr-gamut-wideness", required_argument, nullptr, 0 },
@@ -207,6 +208,7 @@ const char usage[] =
" --hdr-itm-target-nits set the target luminace of the inverse tone mapping process.\n"
" Default: 1000 nits, Max: 10000 nits\n"
" --framerate-limit Set a simple framerate limit. Used as a divisor of the refresh rate, rounds down eg 60 / 59 -> 60fps, 60 / 25 -> 30fps. Default: 0, disabled.\n"
+ " --custom-refresh-rates Set custom refresh rates for the output. eg: 60,90,110-120\n"
" --mangoapp Launch with the mangoapp (mangohud) performance overlay enabled. You should use this instead of using mangohud on the game or gamescope.\n"
" --adaptive-sync Enable adaptive sync if available (variable rate refresh)\n"
"\n"
@@ -460,6 +462,33 @@ static float parse_float(const char *str, const char *optionName)
}
}
+std::vector<uint32_t> g_customRefreshRates;
+// eg: 60,60,90,110-120
+static std::vector<uint32_t> parse_custom_refresh_rates( const char *str )
+{
+ std::vector<uint32_t> rates;
+ char *token = strtok( strdup(str), ",");
+ while (token)
+ {
+ char *dash = strchr(token, '-');
+ if (dash)
+ {
+ uint32_t start = atoi(token);
+ uint32_t end = atoi(dash + 1);
+ for (uint32_t i = start; i <= end; i++)
+ {
+ rates.push_back(i);
+ }
+ }
+ else
+ {
+ rates.push_back(atoi(token));
+ }
+ token = strtok(nullptr, ",");
+ }
+ return rates;
+}
+
struct sigaction handle_signal_action = {};
void ShutdownGamescope()
@@ -783,6 +812,8 @@ int main(int argc, char **argv)
g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg );
} else if (strcmp(opt_name, "force-orientation") == 0) {
g_DesiredInternalOrientation = force_orientation( optarg );
+ } else if (strcmp(opt_name, "custom-refresh-rates") == 0) {
+ g_customRefreshRates = parse_custom_refresh_rates( optarg );
} else if (strcmp(opt_name, "sharpness") == 0 ||
strcmp(opt_name, "fsr-sharpness") == 0) {
g_upscaleFilterSharpness = parse_integer( optarg, opt_name );
diff --git a/src/main.hpp b/src/main.hpp
index 2e6fb833af12..390c04a63ecd 100644
--- a/src/main.hpp
+++ b/src/main.hpp
@@ -3,6 +3,7 @@
#include <getopt.h>
#include <atomic>
+#include <vector>
extern const char *gamescope_optstring;
extern const struct option *gamescope_options;
@@ -28,6 +29,7 @@ extern bool g_bGrabbed;
extern float g_mouseSensitivity;
extern const char *g_sOutputName;
+extern std::vector<uint32_t> g_customRefreshRates;
enum class GamescopeUpscaleFilter : uint32_t
{
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Renn <8340896+AkazaRenn@users.noreply.github.com>
Date: Fri, 11 Oct 2024 17:48:26 +0200
Subject: fix(deck): Use super + 1/2 for Overlay/QAM
Replaces the patch for CTRL + 1/2 for Overlay/QAM with Super + 1/2 and
allows for CTRL for a smooth transition.
Suggested-by: Antheas Kapenekakis <git@antheas.dev>
---
src/wlserver.cpp | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index 4d8546eed51f..56a9f25cd03a 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -296,6 +296,9 @@ static void wlserver_handle_modifiers(struct wl_listener *listener, void *data)
bump_input_counter();
}
+// false if GS_ENABLE_CTRL_12 exists and is 0, true otherwise
+bool env_gs_enable_ctrl_12 = getenv("GS_ENABLE_CTRL_12") ? (getenv("GS_ENABLE_CTRL_12")[0] != '0') : true;
+
static void wlserver_handle_key(struct wl_listener *listener, void *data)
{
struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard;
@@ -319,7 +322,14 @@ static void wlserver_handle_key(struct wl_listener *listener, void *data)
keysym == XKB_KEY_XF86AudioLowerVolume ||
keysym == XKB_KEY_XF86AudioRaiseVolume ||
keysym == XKB_KEY_XF86PowerOff;
- if ( ( event->state == WL_KEYBOARD_KEY_STATE_PRESSED || event->state == WL_KEYBOARD_KEY_STATE_RELEASED ) && forbidden_key )
+
+ // Check for steam overlay key (ctrl/super + 1/2)
+ bool is_steamshortcut =
+ ((env_gs_enable_ctrl_12 && (keyboard->modifiers.depressed & WLR_MODIFIER_CTRL)) ||
+ (keyboard->modifiers.depressed & WLR_MODIFIER_LOGO)) &&
+ (keysym == XKB_KEY_1 || keysym == XKB_KEY_2);
+
+ if ( ( event->state == WL_KEYBOARD_KEY_STATE_PRESSED || event->state == WL_KEYBOARD_KEY_STATE_RELEASED ) && (forbidden_key || is_steamshortcut) )
{
// Always send volume+/- to root server only, to avoid it reaching the game.
struct wlr_surface *old_kb_surf = wlserver.kb_focus_surface;
@@ -328,6 +338,17 @@ static void wlserver_handle_key(struct wl_listener *listener, void *data)
{
wlserver_keyboardfocus( new_kb_surf, false );
wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard );
+
+ if (is_steamshortcut)
+ {
+ // send ctrl down modifier to trigger the overlay
+ wlr_keyboard_modifiers ctrl_down_modifier;
+ ctrl_down_modifier.depressed = WLR_MODIFIER_CTRL;
+ ctrl_down_modifier.latched = WLR_MODIFIER_CTRL;
+ ctrl_down_modifier.locked = WLR_MODIFIER_CTRL;
+ wlr_seat_keyboard_notify_modifiers(wlserver.wlr.seat, &ctrl_down_modifier);
+ }
+
wlr_seat_keyboard_notify_key( wlserver.wlr.seat, event->time_msec, event->keycode, event->state );
wlserver_keyboardfocus( old_kb_surf, false );
return;
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: honjow <honjow311@gmail.com>
Date: Wed, 16 Oct 2024 00:23:58 +0800
Subject: fix(external): fix crash when using external touchscreens
---
src/wlserver.cpp | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index 56a9f25cd03a..4d6e8de55ba4 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -2766,8 +2766,12 @@ static void apply_touchscreen_orientation(double *x, double *y )
double tx = 0;
double ty = 0;
- // Use internal screen always for orientation purposes.
- switch ( GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL )->GetCurrentOrientation() )
+ auto orientation = GAMESCOPE_PANEL_ORIENTATION_AUTO;
+ if ( GetBackend() && GetBackend()->GetCurrentConnector( ) )
+ {
+ orientation = GetBackend()->GetCurrentConnector()->GetCurrentOrientation();
+ }
+ switch ( orientation )
{
default:
case GAMESCOPE_PANEL_ORIENTATION_AUTO:
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Mon, 14 Oct 2024 22:42:20 +0200
Subject: fix(display-config): always fill in mutable refresh rates
Assume the user is not lying to us when they fill in dynamic_refresh_rates
and that gamescope will work with e.g., CVT, so accept it even if no
custom modeline generation has been provided.
---
src/Backends/DRMBackend.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index c8e821314dc4..a919c61f9f8d 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2258,7 +2258,9 @@ namespace gamescope
sol::optional<sol::table> otDynamicRefreshRates = tTable["dynamic_refresh_rates"];
sol::optional<sol::function> ofnDynamicModegen = tTable["dynamic_modegen"];
- if ( otDynamicRefreshRates && ofnDynamicModegen )
+ if ( otDynamicRefreshRates && !ofnDynamicModegen )
+ m_Mutable.ValidDynamicRefreshRates = TableToVector<uint32_t>( *otDynamicRefreshRates );
+ else if ( otDynamicRefreshRates && ofnDynamicModegen )
{
m_Mutable.ValidDynamicRefreshRates = TableToVector<uint32_t>( *otDynamicRefreshRates );
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Wed, 30 Oct 2024 00:39:03 +0100
Subject: fix(battery): run at half hz while at steamUI with atom
---
src/steamcompmgr.cpp | 53 +++++++++++++++++++++++++++++++++++---------
src/xwayland_ctx.hpp | 2 ++
2 files changed, 45 insertions(+), 10 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index b0cf080e0642..07e45b19fc61 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -172,6 +172,8 @@ uint32_t g_reshade_technique_idx = 0;
bool g_bSteamIsActiveWindow = false;
bool g_bForceInternal = false;
+bool b_bForceFrameLimit = false;
+bool g_bRefreshHalveEnable = false;
namespace gamescope
{
@@ -928,6 +930,7 @@ uint64_t g_uCurrentBasePlaneCommitID = 0;
bool g_bCurrentBasePlaneIsFifo = false;
static int g_nSteamCompMgrTargetFPS = 0;
+static int g_nSteamCompMgrTargetFPSreq = 0;
static uint64_t g_uDynamicRefreshEqualityTime = 0;
static int g_nDynamicRefreshRate[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT] = { 0, 0 };
// Delay to stop modes flickering back and forth.
@@ -947,7 +950,7 @@ static void _update_app_target_refresh_cycle()
int target_fps = g_nCombinedAppRefreshCycleOverride[type];
g_nDynamicRefreshRate[ type ] = 0;
- g_nSteamCompMgrTargetFPS = 0;
+ g_nSteamCompMgrTargetFPSreq = 0;
if ( !target_fps )
{
@@ -956,7 +959,7 @@ static void _update_app_target_refresh_cycle()
if ( g_nCombinedAppRefreshCycleChangeFPS[ type ] )
{
- g_nSteamCompMgrTargetFPS = target_fps;
+ g_nSteamCompMgrTargetFPSreq = target_fps;
}
if ( g_nCombinedAppRefreshCycleChangeRefresh[ type ] )
@@ -977,9 +980,9 @@ static void _update_app_target_refresh_cycle()
static void update_app_target_refresh_cycle()
{
- int nPrevFPSLimit = g_nSteamCompMgrTargetFPS;
+ int nPrevFPSLimit = g_nSteamCompMgrTargetFPSreq;
_update_app_target_refresh_cycle();
- if ( !!g_nSteamCompMgrTargetFPS != !!nPrevFPSLimit )
+ if ( !!g_nSteamCompMgrTargetFPSreq != !!nPrevFPSLimit )
update_runtime_info();
}
@@ -5316,7 +5319,7 @@ update_runtime_info()
if ( g_nRuntimeInfoFd < 0 )
return;
- uint32_t limiter_enabled = g_nSteamCompMgrTargetFPS != 0 ? 1 : 0;
+ uint32_t limiter_enabled = g_nSteamCompMgrTargetFPSreq != 0 ? 1 : 0;
pwrite( g_nRuntimeInfoFd, &limiter_enabled, sizeof( limiter_enabled ), 0 );
}
@@ -5379,7 +5382,7 @@ static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vb
{
bool bCloseEnough = std::abs( g_nSteamCompMgrTargetFPS - nRefreshHz ) < 2;
- if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && w && !bCloseEnough )
+ if ( g_nSteamCompMgrTargetFPS && (bShouldLimitFPS || b_bForceFrameLimit) && w && !bCloseEnough )
{
uint64_t schedule = w->last_commit_first_latch_time + g_SteamCompMgrLimitedAppRefreshCycle;
@@ -5397,7 +5400,7 @@ static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vb
}
else
{
- if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && nRefreshHz > nTargetFPS )
+ if ( g_nSteamCompMgrTargetFPS && (bShouldLimitFPS || b_bForceFrameLimit) && nRefreshHz > nTargetFPS )
{
int nVblankDivisor = nRefreshHz / nTargetFPS;
@@ -5796,7 +5799,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
}
if ( ev->atom == ctx->atoms.gamescopeFPSLimit )
{
- g_nSteamCompMgrTargetFPS = get_prop( ctx, ctx->root, ctx->atoms.gamescopeFPSLimit, 0 );
+ g_nSteamCompMgrTargetFPSreq = get_prop( ctx, ctx->root, ctx->atoms.gamescopeFPSLimit, 0 );
update_runtime_info();
}
for (int i = 0; i < gamescope::GAMESCOPE_SCREEN_TYPE_COUNT; i++)
@@ -6227,6 +6230,10 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
MakeFocusDirty();
}
}
+ if ( ev->atom == ctx->atoms.gamescopeFrameHalveAtom )
+ {
+ g_bRefreshHalveEnable = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeFrameHalveAtom, 0 );
+ }
}
static int
@@ -6603,7 +6610,7 @@ void handle_presented_for_window( steamcompmgr_win_t* w )
uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank;
- uint64_t refresh_cycle = g_nSteamCompMgrTargetFPS && steamcompmgr_window_should_limit_fps( w )
+ uint64_t refresh_cycle = g_nSteamCompMgrTargetFPS && (steamcompmgr_window_should_limit_fps( w ) || b_bForceFrameLimit)
? g_SteamCompMgrLimitedAppRefreshCycle
: g_SteamCompMgrAppRefreshCycle;
@@ -7462,6 +7469,8 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_
ctx->atoms.primarySelection = XInternAtom(ctx->dpy, "PRIMARY", false);
ctx->atoms.targets = XInternAtom(ctx->dpy, "TARGETS", false);
+ ctx->atoms.gamescopeFrameHalveAtom = XInternAtom( ctx->dpy, "GAMESCOPE_STEAMUI_HALFHZ", false );;
+
ctx->root_width = DisplayWidth(ctx->dpy, ctx->scr);
ctx->root_height = DisplayHeight(ctx->dpy, ctx->scr);
@@ -7883,7 +7892,7 @@ steamcompmgr_main(int argc, char **argv)
} else if (strcmp(opt_name, "hdr-itm-target-nits") == 0) {
g_flHDRItmTargetNits = atof(optarg);
} else if (strcmp(opt_name, "framerate-limit") == 0) {
- g_nSteamCompMgrTargetFPS = atoi(optarg);
+ g_nSteamCompMgrTargetFPSreq = atoi(optarg);
} else if (strcmp(opt_name, "reshade-effect") == 0) {
g_reshade_effect = optarg;
} else if (strcmp(opt_name, "reshade-technique-idx") == 0) {
@@ -8051,6 +8060,30 @@ steamcompmgr_main(int argc, char **argv)
// as a question.
const bool bIsVBlankFromTimer = vblank;
+ // Halve refresh rate on SteamUI
+ bool isSteam = false;
+ for (auto &iter : g_VirtualConnectorFocuses)
+ {
+ global_focus_t *pFocus = &iter.second;
+ if (window_is_steam( pFocus->focusWindow )) {
+ isSteam = true;
+ break;
+ }
+ }
+ if ( g_bRefreshHalveEnable && isSteam ) {
+ int nRealRefreshHz = gamescope::ConvertmHzToHz( g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh );
+ if (nRealRefreshHz > 100 && (g_nSteamCompMgrTargetFPSreq > 50 || !g_nSteamCompMgrTargetFPSreq)) {
+ g_nSteamCompMgrTargetFPS = nRealRefreshHz / 2;
+ b_bForceFrameLimit = true;
+ } else {
+ g_nSteamCompMgrTargetFPS = g_nSteamCompMgrTargetFPSreq;
+ b_bForceFrameLimit = false;
+ }
+ } else {
+ g_nSteamCompMgrTargetFPS = g_nSteamCompMgrTargetFPSreq;
+ b_bForceFrameLimit = false;
+ }
+
// We can always vblank if VRR.
const bool bVRR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive();
if ( bVRR )
diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp
index df2af70d19ae..e4eec9fa0c48 100644
--- a/src/xwayland_ctx.hpp
+++ b/src/xwayland_ctx.hpp
@@ -246,6 +246,8 @@ struct xwayland_ctx_t final : public gamescope::IWaitable
Atom clipboard;
Atom primarySelection;
Atom targets;
+
+ Atom gamescopeFrameHalveAtom;
} atoms;
bool HasQueuedEvents();
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Joshua Tam <297250+joshuatam@users.noreply.github.com>
Date: Fri, 6 Dec 2024 16:51:02 +0800
Subject: feat(intel): add rotation shader for rotating output
---
src/Backends/DRMBackend.cpp | 35 +++++++++-
src/main.cpp | 7 ++
src/main.hpp | 2 +
src/meson.build | 1 +
src/rendervulkan.cpp | 126 ++++++++++++++++++++++++++++++-----
src/rendervulkan.hpp | 6 +-
src/shaders/cs_rotation.comp | 53 +++++++++++++++
src/wlserver.cpp | 5 ++
8 files changed, 216 insertions(+), 19 deletions(-)
create mode 100644 src/shaders/cs_rotation.comp
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index a919c61f9f8d..a099185e7cdc 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -1609,6 +1609,10 @@ static void update_drm_effective_orientations( struct drm_t *drm, const drmModeM
if ( pDRMInternalConnector != drm->pConnector )
pInternalMode = find_mode( pDRMInternalConnector->GetModeConnector(), 0, 0, 0 );
+ if ( g_bUseRotationShader ) {
+ g_bEnableDRMRotationShader = true;
+ }
+
pDRMInternalConnector->UpdateEffectiveOrientation( pInternalMode );
}
@@ -1620,6 +1624,10 @@ static void update_drm_effective_orientations( struct drm_t *drm, const drmModeM
if ( pDRMExternalConnector != drm->pConnector )
pExternalMode = find_mode( pDRMExternalConnector->GetModeConnector(), 0, 0, 0 );
+ if ( g_bUseRotationShader ) {
+ g_bEnableDRMRotationShader = false;
+ }
+
pDRMExternalConnector->UpdateEffectiveOrientation( pExternalMode );
}
}
@@ -1835,7 +1843,7 @@ LiftoffStateCacheEntry FrameInfoToLiftoffStateCacheEntry( struct drm_t *drm, con
uint64_t crtcW = srcWidth / frameInfo->layers[ i ].scale.x;
uint64_t crtcH = srcHeight / frameInfo->layers[ i ].scale.y;
- if (g_bRotated)
+ if (g_bRotated && !g_bEnableDRMRotationShader)
{
int64_t imageH = frameInfo->layers[ i ].tex->contentHeight() / frameInfo->layers[ i ].scale.y;
@@ -2136,6 +2144,17 @@ namespace gamescope
void CDRMConnector::UpdateEffectiveOrientation( const drmModeModeInfo *pMode )
{
+ if (g_bEnableDRMRotationShader)
+ {
+ drm_log.infof("Using rotation shader");
+ if (g_DesiredInternalOrientation == GAMESCOPE_PANEL_ORIENTATION_270) {
+ m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_180;
+ } else {
+ m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_0;
+ }
+ return;
+ }
+
if ( this->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL && g_DesiredInternalOrientation != GAMESCOPE_PANEL_ORIENTATION_AUTO )
{
m_ChosenOrientation = g_DesiredInternalOrientation;
@@ -3185,6 +3204,13 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode )
g_bRotated = false;
g_nOutputWidth = mode->hdisplay;
g_nOutputHeight = mode->vdisplay;
+
+ if (g_bEnableDRMRotationShader) {
+ g_bRotated = true;
+ g_nOutputWidth = mode->vdisplay;
+ g_nOutputHeight = mode->hdisplay;
+ }
+
break;
case GAMESCOPE_PANEL_ORIENTATION_90:
case GAMESCOPE_PANEL_ORIENTATION_270:
@@ -3449,6 +3475,11 @@ namespace gamescope
bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap);
+ if (g_bEnableDRMRotationShader)
+ {
+ bNeedsFullComposite = true;
+ }
+
bool bDoComposite = true;
if ( !bNeedsFullComposite && !bWantsPartialComposite )
{
@@ -3539,7 +3570,7 @@ namespace gamescope
if ( bDefer && !!( g_uCompositeDebug & CompositeDebugFlag::Markers ) )
g_uCompositeDebug |= CompositeDebugFlag::Markers_Partial;
- std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite );
+ std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite, nullptr, true, nullptr, g_bEnableDRMRotationShader );
m_bWasCompositing = true;
diff --git a/src/main.cpp b/src/main.cpp
index d63b1fe50cc6..cfd4cc11d179 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -131,6 +131,7 @@ const struct option *gamescope_options = (struct option[]){
{ "composite-debug", no_argument, nullptr, 0 },
{ "disable-xres", no_argument, nullptr, 'x' },
{ "fade-out-duration", required_argument, nullptr, 0 },
+ { "use-rotation-shader", required_argument, nullptr, 0 },
{ "force-orientation", required_argument, nullptr, 0 },
{ "force-windows-fullscreen", no_argument, nullptr, 0 },
{ "custom-refresh-rates", required_argument, nullptr, 0 },
@@ -194,6 +195,7 @@ const char usage[] =
" -e, --steam enable Steam integration\n"
" --xwayland-count create N xwayland servers\n"
" --prefer-vk-device prefer Vulkan device for compositing (ex: 1002:7300)\n"
+ " --use-rotation-shader use rotation shader for rotating the screen\n"
" --force-orientation rotate the internal display (left, right, normal, upsidedown)\n"
" --force-windows-fullscreen force windows inside of gamescope to be the size of the nested display (fullscreen)\n"
" --cursor-scale-height if specified, sets a base output height to linearly scale the cursor against.\n"
@@ -355,6 +357,9 @@ static gamescope::GamescopeModeGeneration parse_gamescope_mode_generation( const
}
}
+bool g_bUseRotationShader = false;
+bool g_bEnableDRMRotationShader = false;
+
GamescopePanelOrientation g_DesiredInternalOrientation = GAMESCOPE_PANEL_ORIENTATION_AUTO;
static GamescopePanelOrientation force_orientation(const char *str)
{
@@ -812,6 +817,8 @@ int main(int argc, char **argv)
g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg );
} else if (strcmp(opt_name, "force-orientation") == 0) {
g_DesiredInternalOrientation = force_orientation( optarg );
+ } else if (strcmp(opt_name, "use-rotation-shader") == 0) {
+ g_bUseRotationShader = true;
} else if (strcmp(opt_name, "custom-refresh-rates") == 0) {
g_customRefreshRates = parse_custom_refresh_rates( optarg );
} else if (strcmp(opt_name, "sharpness") == 0 ||
diff --git a/src/main.hpp b/src/main.hpp
index 390c04a63ecd..e7b857d44b0d 100644
--- a/src/main.hpp
+++ b/src/main.hpp
@@ -22,6 +22,8 @@ extern bool g_bForceRelativeMouse;
extern int g_nOutputRefresh; // mHz
extern bool g_bOutputHDREnabled;
extern bool g_bForceInternal;
+extern bool g_bUseRotationShader;
+extern bool g_bEnableDRMRotationShader;
extern bool g_bFullscreen;
diff --git a/src/meson.build b/src/meson.build
index a3dfdabd7366..36c073ec214e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -70,6 +70,7 @@ shader_src = [
'shaders/cs_nis.comp',
'shaders/cs_nis_fp16.comp',
'shaders/cs_rgb_to_nv12.comp',
+ 'shaders/cs_rotation.comp',
]
spirv_shaders = glsl_generator.process(shader_src)
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
index f79d26e0c139..b19f9bf101b4 100644
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -48,6 +48,7 @@
#include "cs_nis.h"
#include "cs_nis_fp16.h"
#include "cs_rgb_to_nv12.h"
+#include "cs_rotation.h"
#define A_CPU
#include "shaders/ffx_a.h"
@@ -923,6 +924,7 @@ bool CVulkanDevice::createShaders()
SHADER(NIS, cs_nis);
}
SHADER(RGB_TO_NV12, cs_rgb_to_nv12);
+ SHADER(ROTATION, cs_rotation);
#undef SHADER
for (uint32_t i = 0; i < shaderInfos.size(); i++)
@@ -1153,6 +1155,7 @@ void CVulkanDevice::compileAllPipelines()
SHADER(EASU, 1, 1, 1);
SHADER(NIS, 1, 1, 1);
SHADER(RGB_TO_NV12, 1, 1, 1);
+ SHADER(ROTATION, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, k_nMaxBlurLayers);
#undef SHADER
for (auto& info : pipelineInfos) {
@@ -3249,8 +3252,16 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
uint32_t uDRMFormat = pOutput->uOutputFormat;
+ uint32_t l_nOutputWidth = g_nOutputWidth;
+ uint32_t l_nOutputHeight = g_nOutputHeight;
+
+ if (g_bEnableDRMRotationShader) {
+ l_nOutputWidth = g_nOutputHeight;
+ l_nOutputHeight = g_nOutputWidth;
+ }
+
pOutput->outputImages[0] = new CVulkanTexture();
- bool bSuccess = pOutput->outputImages[0]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags );
+ bool bSuccess = pOutput->outputImages[0]->BInit( l_nOutputWidth, l_nOutputHeight, 1u, uDRMFormat, outputImageflags );
if ( bSuccess != true )
{
vk_log.errorf( "failed to allocate buffer for KMS" );
@@ -3258,7 +3269,7 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
}
pOutput->outputImages[1] = new CVulkanTexture();
- bSuccess = pOutput->outputImages[1]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags );
+ bSuccess = pOutput->outputImages[1]->BInit( l_nOutputWidth, l_nOutputHeight, 1u, uDRMFormat, outputImageflags );
if ( bSuccess != true )
{
vk_log.errorf( "failed to allocate buffer for KMS" );
@@ -3266,7 +3277,7 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
}
pOutput->outputImages[2] = new CVulkanTexture();
- bSuccess = pOutput->outputImages[2]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags );
+ bSuccess = pOutput->outputImages[2]->BInit( l_nOutputWidth, l_nOutputHeight, 1u, uDRMFormat, outputImageflags );
if ( bSuccess != true )
{
vk_log.errorf( "failed to allocate buffer for KMS" );
@@ -3281,7 +3292,7 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
uint32_t uPartialDRMFormat = pOutput->uOutputFormatOverlay;
pOutput->outputImagesPartialOverlay[0] = new CVulkanTexture();
- bool bSuccess = pOutput->outputImagesPartialOverlay[0]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[0].get() );
+ bool bSuccess = pOutput->outputImagesPartialOverlay[0]->BInit( l_nOutputWidth, l_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[0].get() );
if ( bSuccess != true )
{
vk_log.errorf( "failed to allocate buffer for KMS" );
@@ -3289,7 +3300,7 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
}
pOutput->outputImagesPartialOverlay[1] = new CVulkanTexture();
- bSuccess = pOutput->outputImagesPartialOverlay[1]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[1].get() );
+ bSuccess = pOutput->outputImagesPartialOverlay[1]->BInit( l_nOutputWidth, l_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[1].get() );
if ( bSuccess != true )
{
vk_log.errorf( "failed to allocate buffer for KMS" );
@@ -3297,7 +3308,7 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
}
pOutput->outputImagesPartialOverlay[2] = new CVulkanTexture();
- bSuccess = pOutput->outputImagesPartialOverlay[2]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[2].get() );
+ bSuccess = pOutput->outputImagesPartialOverlay[2]->BInit( l_nOutputWidth, l_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[2].get() );
if ( bSuccess != true )
{
vk_log.errorf( "failed to allocate buffer for KMS" );
@@ -3427,6 +3438,28 @@ static void update_tmp_images( uint32_t width, uint32_t height )
}
}
+static void update_rotated_images( uint32_t width, uint32_t height )
+{
+ if ( g_output.rotatedOutput != nullptr
+ && width == g_output.rotatedOutput->width()
+ && height == g_output.rotatedOutput->height() )
+ {
+ return;
+ }
+
+ CVulkanTexture::createFlags createFlags;
+ createFlags.bSampled = true;
+ createFlags.bStorage = true;
+
+ g_output.rotatedOutput = new CVulkanTexture();
+ bool bSuccess = g_output.rotatedOutput->BInit( width, height, 1u, DRM_FORMAT_ARGB8888, createFlags, nullptr );
+
+ if ( !bSuccess )
+ {
+ vk_log.errorf( "failed to create rotated output" );
+ return;
+ }
+}
static bool init_nis_data()
{
@@ -3903,7 +3936,7 @@ extern uint32_t g_reshade_technique_idx;
ReshadeEffectPipeline *g_pLastReshadeEffect = nullptr;
-std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamescope::Rc<CVulkanTexture> pPipewireTexture, bool partial, gamescope::Rc<CVulkanTexture> pOutputOverride, bool increment, std::unique_ptr<CVulkanCmdBuffer> pInCommandBuffer )
+std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamescope::Rc<CVulkanTexture> pPipewireTexture, bool partial, gamescope::Rc<CVulkanTexture> pOutputOverride, bool increment, std::unique_ptr<CVulkanCmdBuffer> pInCommandBuffer, bool applyRotation )
{
EOTF outputTF = frameInfo->outputEncodingEOTF;
if (!frameInfo->applyOutputColorMgmt)
@@ -3978,7 +4011,15 @@ std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamesco
cmdBuffer->setTextureSrgb(0, true);
cmdBuffer->setSamplerUnnormalized(0, false);
cmdBuffer->setSamplerNearest(0, false);
- cmdBuffer->bindTarget(compositeImage);
+
+ if (applyRotation) {
+ // Make a rotatedOutput with normal dimensions
+ update_rotated_images(currentOutputWidth, currentOutputHeight); // 2560x1600
+ cmdBuffer->bindTarget(g_output.rotatedOutput);
+ } else {
+ cmdBuffer->bindTarget(compositeImage);
+ }
+
cmdBuffer->uploadConstants<RcasPushData_t>(frameInfo, g_upscaleFilterSharpness / 10.0f);
cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup));
@@ -4021,7 +4062,15 @@ std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamesco
cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, nisFrameInfo.layerCount, nisFrameInfo.ycbcrMask(), 0u, nisFrameInfo.colorspaceMask(), outputTF ));
bind_all_layers(cmdBuffer.get(), &nisFrameInfo);
- cmdBuffer->bindTarget(compositeImage);
+
+ if (applyRotation) {
+ // Make a rotatedOutput with normal dimensions
+ update_rotated_images(currentOutputWidth, currentOutputHeight); // 2560x1600
+ cmdBuffer->bindTarget(g_output.rotatedOutput);
+ } else {
+ cmdBuffer->bindTarget(compositeImage);
+ }
+
cmdBuffer->uploadConstants<BlitPushData_t>(&nisFrameInfo);
int pixelsPerGroup = 8;
@@ -4059,7 +4108,15 @@ std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamesco
type = frameInfo->blurLayer0 == BLUR_MODE_COND ? SHADER_TYPE_BLUR_COND : SHADER_TYPE_BLUR;
cmdBuffer->bindPipeline(g_device.pipeline(type, frameInfo->layerCount, frameInfo->ycbcrMask(), blur_layer_count, frameInfo->colorspaceMask(), outputTF ));
bind_all_layers(cmdBuffer.get(), frameInfo);
- cmdBuffer->bindTarget(compositeImage);
+
+ if (applyRotation) {
+ // Make a rotatedOutput with normal dimensions
+ update_rotated_images(currentOutputWidth, currentOutputHeight); // 2560x1600
+ cmdBuffer->bindTarget(g_output.rotatedOutput);
+ } else {
+ cmdBuffer->bindTarget(compositeImage);
+ }
+
cmdBuffer->bindTexture(VKR_BLUR_EXTRA_SLOT, g_output.tmpOutput);
cmdBuffer->setTextureSrgb(VKR_BLUR_EXTRA_SLOT, !useSrgbView); // Inverted because it chooses whether to view as linear (sRGB view) or sRGB (raw view). It's horrible. I need to change it.
cmdBuffer->setSamplerUnnormalized(VKR_BLUR_EXTRA_SLOT, true);
@@ -4069,14 +4126,51 @@ std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamesco
}
else
{
- cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), outputTF ));
- bind_all_layers(cmdBuffer.get(), frameInfo);
- cmdBuffer->bindTarget(compositeImage);
- cmdBuffer->uploadConstants<BlitPushData_t>(frameInfo);
+ if (applyRotation) {
+ cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_ROTATION, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), outputTF ));
+ bind_all_layers(cmdBuffer.get(), frameInfo);
+ cmdBuffer->bindTarget(compositeImage);
+ cmdBuffer->uploadConstants<BlitPushData_t>(frameInfo);
- const int pixelsPerGroup = 8;
+ const int pixelsPerGroup = 8;
- cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup));
+ cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup));
+ } else {
+ cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), outputTF ));
+ bind_all_layers(cmdBuffer.get(), frameInfo);
+ cmdBuffer->bindTarget(compositeImage);
+ cmdBuffer->uploadConstants<BlitPushData_t>(frameInfo);
+
+ const int pixelsPerGroup = 8;
+
+ cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup));
+ }
+ }
+
+ if (applyRotation)
+ {
+ if (g_output.rotatedOutput != nullptr) {
+ // Rotate the final output
+ // TODO: may need rework with another rotation shader for blur, fsr and nis
+ cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_ROTATION, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), outputTF));
+ bind_all_layers(cmdBuffer.get(), frameInfo);
+
+ // if (frameInfo->blurLayer0) {
+ // bool useSrgbView = frameInfo->layers[0].colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR;
+ //
+ // cmdBuffer->bindTexture(VKR_BLUR_EXTRA_SLOT, g_output.rotatedOutput);
+ // cmdBuffer->setTextureSrgb(VKR_BLUR_EXTRA_SLOT, !useSrgbView);
+ // cmdBuffer->setSamplerUnnormalized(VKR_BLUR_EXTRA_SLOT, true);
+ // cmdBuffer->setSamplerNearest(VKR_BLUR_EXTRA_SLOT, false);
+ // }
+
+ cmdBuffer->bindTarget(compositeImage);
+ cmdBuffer->uploadConstants<BlitPushData_t>(frameInfo);
+
+ const int pixelsPerGroup = 8;
+
+ cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup));
+ }
}
if ( pPipewireTexture != nullptr )
diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp
index 63cc6029ac5f..93a4a6027f55 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -408,7 +408,7 @@ gamescope::OwningRc<CVulkanTexture> vulkan_create_texture_from_dmabuf( struct wl
gamescope::OwningRc<CVulkanTexture> vulkan_create_texture_from_bits( uint32_t width, uint32_t height, uint32_t contentWidth, uint32_t contentHeight, uint32_t drmFormat, CVulkanTexture::createFlags texCreateFlags, void *bits );
gamescope::OwningRc<CVulkanTexture> vulkan_create_texture_from_wlr_buffer( struct wlr_buffer *buf, gamescope::OwningRc<gamescope::IBackendFb> pBackendFb );
-std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamescope::Rc<CVulkanTexture> pScreenshotTexture, bool partial, gamescope::Rc<CVulkanTexture> pOutputOverride = nullptr, bool increment = true, std::unique_ptr<CVulkanCmdBuffer> pInCommandBuffer = nullptr );
+std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamescope::Rc<CVulkanTexture> pScreenshotTexture, bool partial, gamescope::Rc<CVulkanTexture> pOutputOverride = nullptr, bool increment = true, std::unique_ptr<CVulkanCmdBuffer> pInCommandBuffer = nullptr, bool applyRotation = false );
void vulkan_wait( uint64_t ulSeqNo, bool bReset );
gamescope::Rc<CVulkanTexture> vulkan_get_last_output_image( bool partial, bool defer );
gamescope::Rc<CVulkanTexture> vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown);
@@ -545,6 +545,9 @@ struct VulkanOutput_t
// NIS
gamescope::OwningRc<CVulkanTexture> nisScalerImage;
gamescope::OwningRc<CVulkanTexture> nisUsmImage;
+
+ // Rotated
+ gamescope::OwningRc<CVulkanTexture> rotatedOutput;
};
@@ -557,6 +560,7 @@ enum ShaderType {
SHADER_TYPE_RCAS,
SHADER_TYPE_NIS,
SHADER_TYPE_RGB_TO_NV12,
+ SHADER_TYPE_ROTATION,
SHADER_TYPE_COUNT
};
diff --git a/src/shaders/cs_rotation.comp b/src/shaders/cs_rotation.comp
new file mode 100644
index 000000000000..1a47fd505748
--- /dev/null
+++ b/src/shaders/cs_rotation.comp
@@ -0,0 +1,53 @@
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_scalar_block_layout : require
+
+#include "descriptor_set.h"
+
+layout(
+ local_size_x = 8,
+ local_size_y = 8,
+ local_size_z = 1) in;
+
+#include "blit_push_data.h"
+#include "composite.h"
+
+vec4 sampleLayer(uint layerIdx, vec2 uv) {
+ if ((c_ycbcrMask & (1 << layerIdx)) != 0)
+ return sampleLayer(s_ycbcr_samplers[layerIdx], layerIdx, uv, false);
+ return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true);
+}
+
+void main() {
+ uvec2 coord = uvec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
+ uvec2 outSize = imageSize(dst);
+ float outWidth = outSize.y;
+ float outHeight = outSize.x;
+
+ vec2 uv = vec2(coord);
+ vec4 outputValue = vec4(255.0f);
+
+ if (c_layerCount > 0) {
+ outputValue = sampleLayer(0, uv) * u_opacity[0];
+ }
+
+ for (int i = 1; i < c_layerCount; i++) {
+ vec4 layerColor = sampleLayer(i, uv);
+ // wl_surfaces come with premultiplied alpha, so that's them being
+ // premultiplied by layerColor.a.
+ // We need to then multiply that by the layer's opacity to get to our
+ // final premultiplied state.
+ // For the other side of things, we need to multiply by (1.0f - (layerColor.a * opacity))
+ float opacity = u_opacity[i];
+ float layerAlpha = opacity * layerColor.a;
+ outputValue = layerColor * opacity + outputValue * (1.0f - layerAlpha);
+ }
+
+ outputValue.rgb = encodeOutputColor(outputValue.rgb);
+
+ // Rotate the pixel coordinates counter-clockwise by 90 degrees
+ ivec2 rotatedCoord = ivec2(coord.y, outWidth - coord.x - 1);
+
+ imageStore(dst, rotatedCoord, outputValue);
+}
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index 4d6e8de55ba4..a694b5245a97 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -2793,6 +2793,11 @@ static void apply_touchscreen_orientation(double *x, double *y )
break;
}
+ if (g_bEnableDRMRotationShader) {
+ tx = 1.0 - *y;
+ ty = *x;
+ }
+
*x = tx;
*y = ty;
}
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: brainantifreeze <you@example.com>
Date: Thu, 19 Dec 2024 09:16:15 +0000
Subject: feat(nvidia): fix crash with current driver
add layer env var to hide present wait ext
see: https://github.com/ValveSoftware/gamescope/pull/1671
---
layer/VkLayer_FROG_gamescope_wsi.cpp | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp
index af0c2009930b..263cbc37bd88 100644
--- a/layer/VkLayer_FROG_gamescope_wsi.cpp
+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp
@@ -183,6 +183,16 @@ namespace GamescopeWSILayer {
return s_ensureMinImageCount;
}
+ static bool getHidePresentWait() {
+ static bool s_hidePresentWait = []() -> bool {
+ if (auto hide = parseEnv<bool>("GAMESCOPE_WSI_HIDE_PRESENT_WAIT_EXT")) {
+ return *hide;
+ }
+ return false;
+ }();
+ return s_hidePresentWait;
+ }
+
// Taken from Mesa, licensed under MIT.
//
// No real reason to rewrite this code,
@@ -589,7 +599,11 @@ namespace GamescopeWSILayer {
createInfo.ppEnabledExtensionNames = enabledExts.data();
setenv("vk_xwayland_wait_ready", "false", 0);
- setenv("vk_khr_present_wait", "true", 0);
+ if (getHidePresentWait()) {
+ setenv("vk_khr_present_wait", "false", 0);
+ } else {
+ setenv("vk_khr_present_wait", "true", 0);
+ }
VkResult result = pfnCreateInstanceProc(&createInfo, pAllocator, pInstance);
if (result != VK_SUCCESS)
@@ -899,6 +913,10 @@ namespace GamescopeWSILayer {
const vkroots::VkInstanceDispatch* pDispatch,
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceFeatures2* pFeatures) {
+ if (getHidePresentWait()) {
+ fprintf(stderr, "[Gamescope WSI] Removing VkPhysicalDevicePresentWaitFeaturesKHR because GAMESCOPE_WSI_HIDE_PRESENT_WAIT_EXT is set\n");
+ vkroots::RemoveFromChain<VkPhysicalDevicePresentWaitFeaturesKHR>(pFeatures);
+ }
pDispatch->GetPhysicalDeviceFeatures2(physicalDevice, pFeatures);
}
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sun, 23 Feb 2025 02:16:55 +0100
Subject: feat(display): add asus z13
---
.../00-gamescope/displays/asus.z13.lcd.lua | 57 +++++++++++++++++++
1 file changed, 57 insertions(+)
create mode 100644 scripts/00-gamescope/displays/asus.z13.lcd.lua
diff --git a/scripts/00-gamescope/displays/asus.z13.lcd.lua b/scripts/00-gamescope/displays/asus.z13.lcd.lua
new file mode 100644
index 000000000000..891f1ea9ca6f
--- /dev/null
+++ b/scripts/00-gamescope/displays/asus.z13.lcd.lua
@@ -0,0 +1,57 @@
+gamescope.config.known_displays.asusz13_lcd = {
+ pretty_name = "Asus Z13 LCD",
+ dynamic_refresh_rates = {
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
+ 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
+ 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
+ 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
+ 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140,
+ 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
+ 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168,
+ 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180
+ },
+
+ -- Detailed Timing Descriptors:
+ -- DTD 1: 1920x1200 120.002 Hz 8:5 151.683 kHz 315.500 MHz (172 mm x 107 mm)
+ -- Modeline "1920x1200_120.00" 315.500 1920 1968 2000 2080 1200 1254 1260 1264 -HSync -VSync
+ -- DTD 2: 1920x1200 60.001 Hz 8:5 75.841 kHz 157.750 MHz (172 mm x 107 mm)
+ -- Modeline "1920x1200_60.00" 157.750 1920 1968 2000 2080 1200 1254 1260 1264 -HSync -VSync
+ dynamic_modegen = function(base_mode, refresh)
+ debug("Generating mode "..refresh.."Hz with fixed pixel clock")
+ local vfps = {
+ 4886, 4751, 4620, 4495, 4375, 4259, 4147, 4040, 3936, 3836, 3739, 3646,
+ 3556, 3468, 3384, 3302, 3223, 3146, 3072, 2999, 2929, 2861, 2795, 2731,
+ 2668, 2608, 2548, 2491, 2435, 2380, 2327, 2275, 2225, 2175, 2127, 2080,
+ 2035, 1990, 1946, 1903, 1862, 1821, 1781, 1742, 1704, 1667, 1630, 1594,
+ 1559, 1525, 1491, 1458, 1426, 1395, 1364, 1333, 1303, 1274, 1245, 1217,
+ 1190, 1162, 1136, 1110, 1084, 1059, 1034, 1010, 986, 962, 939, 916, 894,
+ 872, 850, 829, 808, 787, 767, 747, 727, 708, 689, 670, 652, 634, 616,
+ 598, 581, 563, 547, 530, 513, 497, 481, 466, 450, 435, 420, 405, 390,
+ 376, 361, 347, 333, 320, 306, 293, 279, 266, 254, 241, 228, 216, 204,
+ 192, 180, 168, 156, 145, 133, 122, 111, 100, 89, 78, 68, 57, 47, 36,
+ 26, 16, 6
+ }
+ local vfp = vfps[zero_index(refresh - 48)]
+ if vfp == nil then
+ warn("Couldn't do refresh "..refresh.." on ROG Ally")
+ return base_mode
+ end
+
+ local mode = base_mode
+
+ gamescope.modegen.adjust_front_porch(mode, vfp)
+ mode.vrefresh = gamescope.modegen.calc_vrefresh(mode)
+
+ --debug(inspect(mode))
+ return mode
+ end,
+ matches = function(display)
+ if display.vendor == "TMA" and display.model == "TL134ADXP03" then
+ debug("[z13] Matched vendor: "..display.vendor.." model: "..display.model.." product:"..display.product)
+ return 5000
+ end
+ return -1
+ end
+}
+debug("Registered Lenovo Legion Go S LCD as a known display")
\ No newline at end of file
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Wed, 23 Apr 2025 22:51:54 +0200
Subject: feat(display): consider vporch to avoid timing issues
---
src/Backends/DRMBackend.cpp | 8 ++++++++
src/main.cpp | 1 +
src/main.hpp | 1 +
src/vblankmanager.cpp | 6 +-----
4 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index a099185e7cdc..d09030e0cf5e 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -213,6 +213,11 @@ namespace gamescope
return nRefresh;
}
+ static int32_t GetVblankNs(const drmModeModeInfo *mode)
+ {
+ return (mode->vsync_start - mode->vdisplay) * 1'000'000'000ll / mode->vrefresh / mode->vtotal;
+ }
+
template <typename T>
using CAutoDeletePtr = std::unique_ptr<T, void(*)(T*)>;
@@ -3194,6 +3199,9 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode )
g_nOutputRefresh = gamescope::GetModeRefresh( mode );
g_nDynamicRefreshHz = 0;
+ g_nsVsync = gamescope::GetVblankNs( mode );
+ drm_log.infof("Vblank ns: %lu", g_nsVsync);
+
update_drm_effective_orientations(drm, mode);
switch ( drm->pConnector->GetCurrentOrientation() )
diff --git a/src/main.cpp b/src/main.cpp
index cfd4cc11d179..0d88e3a2cded 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -291,6 +291,7 @@ int g_nNestedDisplayIndex = 0;
uint32_t g_nOutputWidth = 0;
uint32_t g_nOutputHeight = 0;
int g_nOutputRefresh = 0;
+long g_nsVsync = 0;
bool g_bOutputHDREnabled = false;
bool g_bFullscreen = false;
diff --git a/src/main.hpp b/src/main.hpp
index e7b857d44b0d..e6f8ff133689 100644
--- a/src/main.hpp
+++ b/src/main.hpp
@@ -20,6 +20,7 @@ extern uint32_t g_nOutputWidth;
extern uint32_t g_nOutputHeight;
extern bool g_bForceRelativeMouse;
extern int g_nOutputRefresh; // mHz
+extern long g_nsVsync; // ns
extern bool g_bOutputHDREnabled;
extern bool g_bForceInternal;
extern bool g_bUseRotationShader;
diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp
index f036d000a8e2..e388374c98ba 100644
--- a/src/vblankmanager.cpp
+++ b/src/vblankmanager.cpp
@@ -95,8 +95,6 @@ namespace gamescope
VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive )
{
- const GamescopeScreenType eScreenType = GetBackend()->GetScreenType();
-
const int nRefreshRate = GetRefresh();
const uint64_t ulRefreshInterval = mHzToRefreshCycle( nRefreshRate );
@@ -113,9 +111,7 @@ namespace gamescope
// to not account for vertical front porch when dealing with the vblank
// drm_commit is going to target?
// Need to re-test that.
- const uint64_t ulRedZone = eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL
- ? m_ulVBlankDrawBufferRedZone
- : std::min<uint64_t>( m_ulVBlankDrawBufferRedZone, ( m_ulVBlankDrawBufferRedZone * 60'000 * nRefreshRate ) / 60'000 );
+ const uint64_t ulRedZone = m_ulVBlankDrawBufferRedZone + g_nsVsync;
const uint64_t ulDecayAlpha = m_ulVBlankRateOfDecayPercentage; // eg. 980 = 98%
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sun, 22 Jun 2025 15:18:19 +0200
Subject: feat: add Legion Go S display with all framerates
---
.../displays/lenovo.legiongos.lcd.lua | 71 +++++++++++--------
1 file changed, 42 insertions(+), 29 deletions(-)
diff --git a/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua b/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
index 32f776c17f3d..057850f374f8 100644
--- a/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
+++ b/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
@@ -1,44 +1,58 @@
-local legiongos_lcd_refresh_rates = {
- 52, 53, 54, 56, 57, 58, 59,
- 60, 61, 62, 63, 64, 65, 67, 68, 69,
- 70,
- 102, 103, 104, 105, 106, 107, 108, 109,
- 111, 112, 113, 114, 115, 116, 117, 118, 119,
- 120
-}
-
gamescope.config.known_displays.legiongos_lcd = {
pretty_name = "Lenovo Legion Go S LCD",
hdr = {
- -- The Legion Go S panel does not support HDR.
+ -- Setup some fallbacks for undocking with HDR, meant
+ -- for the internal panel. It does not support HDR.
supported = false,
force_enabled = false,
- eotf = gamescope.eotf.gamma22,
- max_content_light_level = 500,
- max_frame_average_luminance = 500,
- min_content_light_level = 0.5
+ eotf = gamescope.eotf.gamma22,
+ max_content_light_level = 500,
+ max_frame_average_luminance = 500,
+ min_content_light_level = 0.5
},
- -- 60Hz has a different pixel clock than 120Hz in the EDID with VRR disabled,
- -- and the panel is not responsive to tuning VFPs. To cover the non-VRR
- -- limiter, an LCD Deck-style dynamic modegen method works best.
- dynamic_refresh_rates = legiongos_lcd_refresh_rates,
+
+ dynamic_refresh_rates = {
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
+ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 91, 92, 93, 94, 95, 96, 97,
+ 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
+ 118, 119, 120
+ },
+
+ -- Detailed Timing Descriptors:
+ -- DTD 1: 1920x1200 120.002 Hz 8:5 151.683 kHz 315.500 MHz (172 mm x 107 mm)
+ -- Modeline "1920x1200_120.00" 315.500 1920 1968 2000 2080 1200 1254 1260 1264 -HSync -VSync
+ -- DTD 2: 1920x1200 60.001 Hz 8:5 75.841 kHz 157.750 MHz (172 mm x 107 mm)
+ -- Modeline "1920x1200_60.00" 157.750 1920 1968 2000 2080 1200 1254 1260 1264 -HSync -VSync
dynamic_modegen = function(base_mode, refresh)
- debug("Generating mode "..refresh.."Hz for Lenovo Legion Go S LCD")
- local mode = base_mode
+ debug("Generating mode "..refresh.."Hz with fixed pixel clock")
+ local vfps = {
+ 1950, 1885, 1824, 1764, 1707, 1652, 1599, 1548, 1499, 1451, 1405,
+ 1361, 1318, 1277, 1237, 1198, 1160, 1124, 1088, 1054, 1021, 988,
+ 957, 927, 897, 868, 840, 813, 786, 760, 735, 710, 686, 663, 640,
+ 618, 596, 575, 554, 534, 514, 495, 476, 457, 439, 421, 404, 387,
+ 370, 354, 338, 322, 307, 292, 277, 263, 249, 235, 221, 208, 195,
+ 182, 169, 157, 145, 133, 121, 109, 98, 87, 76, 65, 54
+ }
+ local vfp = vfps[zero_index(refresh - 48)]
+ if vfp == nil then
+ warn("Couldn't do refresh "..refresh.." on ROG Ally")
+ return base_mode
+ end
- -- These are only tuned for 1920x1200.
- gamescope.modegen.set_resolution(mode, 1920, 1200)
+ local mode = base_mode
- -- hfp, hsync, hbp
- gamescope.modegen.set_h_timings(mode, 48, 36, 80)
- -- vfp, vsync, vbp
- gamescope.modegen.set_v_timings(mode, 54, 6, 4)
- mode.clock = gamescope.modegen.calc_max_clock(mode, refresh)
+ gamescope.modegen.adjust_front_porch(mode, vfp)
mode.vrefresh = gamescope.modegen.calc_vrefresh(mode)
--debug(inspect(mode))
return mode
end,
+
+
matches = function(display)
local lcd_types = {
{ vendor = "CSW", model = "PN8007QB1-1", product = 0x0800 },
@@ -56,5 +70,4 @@ gamescope.config.known_displays.legiongos_lcd = {
return -1
end
}
-debug("Registered Lenovo Legion Go S LCD as a known display")
---debug(inspect(gamescope.config.known_displays.legiongos_lcd))
+debug("Registered Lenovo Legion Go S LCD as a known display")
\ No newline at end of file
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 29 Aug 2025 16:45:39 +0200
Subject: feat: add DPMS support through an Atom
---
src/Backends/DRMBackend.cpp | 15 ++++++++++++++-
src/rendervulkan.hpp | 2 ++
src/steamcompmgr.cpp | 18 +++++++++++++++---
src/xwayland_ctx.hpp | 1 +
4 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index d09030e0cf5e..2861f30aaf66 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2827,6 +2827,9 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI
drm->needs_modeset = true;
}
+ if (drm->pCRTC && drm->pCRTC->GetProperties().ACTIVE->GetCurrentValue() != !frameInfo->dpms)
+ drm->needs_modeset = true;
+
drm_colorspace uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT;
const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ;
@@ -2894,6 +2897,9 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI
needs_modeset = true;
}
+ if ( frameInfo->dpms )
+ bSleep = true;
+
if ( !bSleep )
{
if ( drm->pCRTC != nullptr )
@@ -2973,7 +2979,13 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI
if ( drm->pCRTC && !bSleep )
{
- drm->pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 1u, true );
+ if ( frameInfo->dpms ) {
+ // We can't disable a CRTC if it's already disabled
+ if (drm->pCRTC->GetProperties().ACTIVE->GetCurrentValue() != 0)
+ drm->pCRTC->GetProperties().ACTIVE->SetPendingValue(drm->req, 0, true);
+ }
+ else
+ drm->pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 1u, true );
drm->pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, drm->pending.mode_id ? drm->pending.mode_id->GetBlobValue() : 0lu, true );
if ( drm->pCRTC->GetProperties().VRR_ENABLED )
@@ -3594,6 +3606,7 @@ namespace gamescope
FrameInfo_t presentCompFrameInfo = {};
presentCompFrameInfo.allowVRR = pFrameInfo->allowVRR;
+ presentCompFrameInfo.dpms = pFrameInfo->dpms;
presentCompFrameInfo.outputEncodingEOTF = pFrameInfo->outputEncodingEOTF;
if ( bNeedsFullComposite )
diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp
index 93a4a6027f55..0833fc46ffd7 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -292,6 +292,8 @@ struct FrameInfo_t
bool applyOutputColorMgmt; // drm only
EOTF outputEncodingEOTF;
+ bool dpms;
+
int layerCount;
struct Layer_t
{
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 07e45b19fc61..f25f810f9387 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -174,6 +174,8 @@ bool g_bSteamIsActiveWindow = false;
bool g_bForceInternal = false;
bool b_bForceFrameLimit = false;
bool g_bRefreshHalveEnable = false;
+bool g_bDPMS = false;
+bool g_bDPMS_set = false;
namespace gamescope
{
@@ -2421,7 +2423,7 @@ gamescope::ConVar<bool> cv_paint_cursor_plane{ "paint_cursor_plane", true };
gamescope::ConVar<bool> cv_paint_mura_plane{ "paint_mura_plane", true };
static void
-paint_all( global_focus_t *pFocus, bool async )
+paint_all(global_focus_t *pFocus, bool async, bool dpms)
{
if ( !pFocus )
return;
@@ -2479,6 +2481,7 @@ paint_all( global_focus_t *pFocus, bool async )
frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF;
frameInfo.allowVRR = cv_adaptive_sync;
frameInfo.bFadingOut = fadingOut;
+ frameInfo.dpms = dpms;
// If the window we'd paint as the base layer is the streaming client,
// find the video underlay and put it up first in the scenegraph
@@ -6234,6 +6237,10 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
{
g_bRefreshHalveEnable = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeFrameHalveAtom, 0 );
}
+ if (ev->atom == ctx->atoms.gamescopeDPMS)
+ {
+ g_bDPMS = !!get_prop(ctx, ctx->root, ctx->atoms.gamescopeDPMS, 0);
+ }
}
static int
@@ -7470,6 +7477,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_
ctx->atoms.targets = XInternAtom(ctx->dpy, "TARGETS", false);
ctx->atoms.gamescopeFrameHalveAtom = XInternAtom( ctx->dpy, "GAMESCOPE_STEAMUI_HALFHZ", false );;
+ ctx->atoms.gamescopeDPMS = XInternAtom(ctx->dpy, "GAMESCOPE_DPMS", false);
ctx->root_width = DisplayWidth(ctx->dpy, ctx->scr);
ctx->root_height = DisplayHeight(ctx->dpy, ctx->scr);
@@ -8611,10 +8619,14 @@ steamcompmgr_main(int argc, char **argv)
bShouldPaint = false;
}
+ if ( g_bDPMS != g_bDPMS_set && vblank )
+ bShouldPaint = true;
+
if ( bShouldPaint )
{
- paint_all( pPaintFocus, eFlipType == FlipType::Async );
-
+ paint_all( pPaintFocus, eFlipType == FlipType::Async, g_bDPMS );
+
+ g_bDPMS_set = g_bDPMS;
bPainted = true;
}
}
diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp
index e4eec9fa0c48..2347cbb3340c 100644
--- a/src/xwayland_ctx.hpp
+++ b/src/xwayland_ctx.hpp
@@ -248,6 +248,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable
Atom targets;
Atom gamescopeFrameHalveAtom;
+ Atom gamescopeDPMS;
} atoms;
bool HasQueuedEvents();
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sun, 29 Jun 2025 13:16:59 +0200
Subject: update misyltoad urls
---
.gitmodules | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.gitmodules b/.gitmodules
index ec7d4e430ee8..17ba783f809b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,12 +1,12 @@
[submodule "subprojects/wlroots"]
path = subprojects/wlroots
- url = https://github.com/Joshua-Ashton/wlroots.git
+ url = https://github.com/misyltoad/wlroots.git
[submodule "subprojects/libliftoff"]
path = subprojects/libliftoff
url = https://gitlab.freedesktop.org/emersion/libliftoff.git
[submodule "subprojects/vkroots"]
path = subprojects/vkroots
- url = https://github.com/Joshua-Ashton/vkroots
+ url = https://github.com/misyltoad/vkroots
[submodule "subprojects/libdisplay-info"]
path = subprojects/libdisplay-info
url = https://gitlab.freedesktop.org/emersion/libdisplay-info
@@ -15,7 +15,7 @@
url = https://github.com/ValveSoftware/openvr.git
[submodule "src/reshade"]
path = src/reshade
- url = https://github.com/Joshua-Ashton/reshade
+ url = https://github.com/misyltoad/reshade
[submodule "thirdparty/SPIRV-Headers"]
path = thirdparty/SPIRV-Headers
url = https://github.com/KhronosGroup/SPIRV-Headers/
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 29 Aug 2025 15:31:34 +0200
Subject: fix: drain timer fds to avoid epoll_wait returning constantly
Currently, if the VBlank or VRR timers expire without being rescheduled,
their FDs remain readable, causing epoll_wait to return instantly.|Drain
the FDs in the OnPollIn handlers to prevent this.
---
src/waitable.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/waitable.h b/src/waitable.h
index 30edf8f52deb..36ac61ee7233 100644
--- a/src/waitable.h
+++ b/src/waitable.h
@@ -182,6 +182,11 @@ namespace gamescope
ArmTimer( 0ul, false );
}
+ void OnPollIn()
+ {
+ IWaitable::Drain(m_nFD);
+ }
+
int GetFD()
{
return m_nFD;
@@ -200,6 +205,7 @@ namespace gamescope
void OnPollIn() final
{
+ ITimerWaitable::OnPollIn();
m_fnPollFunc();
}
private:
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sat, 30 Aug 2025 15:12:39 +0200
Subject: fix(intel): allow night mode and color adjustment via compositing
---
src/Backends/DRMBackend.cpp | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 2861f30aaf66..3c4e5907b2a9 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -3481,6 +3481,17 @@ namespace gamescope
bNeedsFullComposite |= pFrameInfo->bFadingOut;
bNeedsFullComposite |= !g_reshade_effect.empty();
+ if ( !SupportsColorManagement() ) {
+ // Fuzzy match default values to see if we need to composite
+ bNeedsFullComposite |= g_ColorMgmt.pending.nightmode.amount != 0.0f;
+ bNeedsFullComposite |= g_ColorMgmt.pending.outputVirtualWhite.x > 0 &&
+ abs(g_ColorMgmt.pending.outputVirtualWhite.x - 0.3127f) > 0.001f;
+ bNeedsFullComposite |= g_ColorMgmt.pending.outputVirtualWhite.y > 0 &&
+ abs(g_ColorMgmt.pending.outputVirtualWhite.y - 0.3290f) > 0.001f;
+ bNeedsFullComposite |= g_ColorMgmt.pending.sdrGamutWideness >= 0 &&
+ abs(g_ColorMgmt.pending.sdrGamutWideness - 0.5f) > 0.02f;
+ }
+
if ( g_bOutputHDREnabled )
{
bNeedsFullComposite |= g_bHDRItmEnable;
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sat, 30 Aug 2025 15:32:00 +0200
Subject: fix(hdr): disable PQ on handheld internal displays
For some reason, the PQ transfer function does not work on current
handhelds. So use gamma 22 instead. This allows us to skip creating
configs and read the HDR metadata from the displays.
---
src/Backends/DRMBackend.cpp | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 3c4e5907b2a9..83da1bfe231b 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2465,7 +2465,13 @@ namespace gamescope
pHDRStaticMetadata && pHDRStaticMetadata->eotfs && pHDRStaticMetadata->eotfs->pq )
{
m_Mutable.HDR.bExposeHDRSupport = true;
- m_Mutable.HDR.eOutputEncodingEOTF = EOTF_PQ;
+ if (GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL)
+ // Current handheld internal displays have issues
+ // with PQ, e.g., Ayaneo 3, Steam Deck etc.
+ // Use Gamma 2.2 as the safest option for now.
+ m_Mutable.HDR.eOutputEncodingEOTF = EOTF_Gamma22;
+ else
+ m_Mutable.HDR.eOutputEncodingEOTF = EOTF_PQ;
m_Mutable.HDR.uMaxContentLightLevel =
pHDRStaticMetadata->desired_content_max_luminance
? nits_to_u16( pHDRStaticMetadata->desired_content_max_luminance )
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 29 Aug 2025 17:17:06 +0200
Subject: chore: use system glm, stb
---
meson.build | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/meson.build b/meson.build
index 5e5bd4cdc7c4..802e4a052bcd 100644
--- a/meson.build
+++ b/meson.build
@@ -50,10 +50,8 @@ dep_x11 = dependency('x11')
dep_wayland = dependency('wayland-client')
vulkan_dep = dependency('vulkan')
-glm_proj = subproject('glm')
-glm_dep = glm_proj.get_variable('glm_dep')
-stb_proj = subproject('stb')
-stb_dep = stb_proj.get_variable('stb_dep')
+glm_dep = dependency('glm')
+stb_dep = dependency('stb')
if get_option('enable_openvr_support')
openvr_dep = dependency('openvr', version: '>= 2.7', required : false)
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 29 Aug 2025 19:04:17 +0200
Subject: Revert "mangoapp: plumb engineName"
This reverts commit b9f20436d1bdf7bd8212541817b254e1b4c8eb1e.
---
layer/VkLayer_FROG_gamescope_wsi.cpp | 9 +--------
protocol/gamescope-swapchain.xml | 1 -
src/WaylandServer/WaylandServerLegacy.h | 1 -
src/mangoapp.cpp | 6 ------
src/steamcompmgr.cpp | 6 ------
src/steamcompmgr.hpp | 1 -
src/steamcompmgr_shared.hpp | 2 --
src/wlserver.cpp | 4 +---
8 files changed, 2 insertions(+), 28 deletions(-)
diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp
index 263cbc37bd88..d1835a4c7487 100644
--- a/layer/VkLayer_FROG_gamescope_wsi.cpp
+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp
@@ -404,7 +404,6 @@ namespace GamescopeWSILayer {
struct GamescopeInstanceData {
wl_display* display;
uint32_t appId = 0;
- std::string engineName;
GamescopeLayerClient::Flags flags = 0;
};
VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE(GamescopeInstance, VkInstance);
@@ -631,14 +630,9 @@ namespace GamescopeWSILayer {
{
uint32_t appId = clientAppId();
- std::string engineName;
- if (pCreateInfo->pApplicationInfo && pCreateInfo->pApplicationInfo->pEngineName)
- engineName = pCreateInfo->pApplicationInfo->pEngineName;
-
auto state = GamescopeInstance::create(*pInstance, GamescopeInstanceData {
.display = display,
.appId = appId,
- .engineName = engineName,
.flags = defaultLayerClientFlags(pCreateInfo->pApplicationInfo, appId),
});
@@ -1275,8 +1269,7 @@ namespace GamescopeWSILayer {
uint32_t(pCreateInfo->imageColorSpace),
uint32_t(pCreateInfo->compositeAlpha),
uint32_t(pCreateInfo->preTransform),
- uint32_t(pCreateInfo->clipped),
- gamescopeInstance->engineName.c_str());
+ uint32_t(pCreateInfo->clipped));
return VK_SUCCESS;
}
diff --git a/protocol/gamescope-swapchain.xml b/protocol/gamescope-swapchain.xml
index 58ac8463b752..91be3fc02d67 100644
--- a/protocol/gamescope-swapchain.xml
+++ b/protocol/gamescope-swapchain.xml
@@ -89,7 +89,6 @@
<arg name="vk_composite_alpha" type="uint" summary="VkCompositeAlphaFlagBitsKHR of swapchain"/>
<arg name="vk_pre_transform" type="uint" summary="VkSurfaceTransformFlagBitsKHR of swapchain"/>
<arg name="vk_clipped" type="uint" summary="clipped (VkBool32) of swapchain"/>
- <arg name="vk_engine_name" type="string" summary="Engine name"/>
</request>
<request name="set_present_mode">
diff --git a/src/WaylandServer/WaylandServerLegacy.h b/src/WaylandServer/WaylandServerLegacy.h
index 63ee2ca17e8c..0facb7dc8b1e 100644
--- a/src/WaylandServer/WaylandServerLegacy.h
+++ b/src/WaylandServer/WaylandServerLegacy.h
@@ -29,7 +29,6 @@ struct wlserver_vk_swapchain_feedback
VkCompositeAlphaFlagBitsKHR vk_composite_alpha;
VkSurfaceTransformFlagBitsKHR vk_pre_transform;
VkBool32 vk_clipped;
- std::shared_ptr<std::string> vk_engine_name;
std::shared_ptr<gamescope::BackendBlob> hdr_metadata_blob;
};
diff --git a/src/mangoapp.cpp b/src/mangoapp.cpp
index d8e1ce7edafe..91e01bc275c6 100644
--- a/src/mangoapp.cpp
+++ b/src/mangoapp.cpp
@@ -31,7 +31,6 @@ struct mangoapp_msg_v1 {
uint16_t displayRefresh;
bool bAppWantsHDR : 1;
bool bSteamFocused : 1;
- char engineName[40];
// WARNING: Always ADD fields, never remove or repurpose fields
} __attribute__((packed)) mangoapp_msg_v1;
@@ -61,11 +60,6 @@ void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uin
mangoapp_msg_v1.displayRefresh = (uint16_t) gamescope::ConvertmHzToHz( g_nOutputRefresh );
mangoapp_msg_v1.bAppWantsHDR = g_bAppWantsHDRCached;
mangoapp_msg_v1.bSteamFocused = g_focusedBaseAppId == 769;
- memset(mangoapp_msg_v1.engineName, 0, sizeof(mangoapp_msg_v1.engineName));
- if (focusWindow_engine)
- focusWindow_engine->copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char));
- else
- std::string("gamescope").copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char));
msgsnd(msgid, &mangoapp_msg_v1, sizeof(mangoapp_msg_v1) - sizeof(mangoapp_msg_v1.hdr.msg_type), IPC_NOWAIT);
}
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index f25f810f9387..d5a6a4af51df 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -1026,7 +1026,6 @@ int g_BlurRadius = 5;
unsigned int g_BlurFadeStartTime = 0;
pid_t focusWindow_pid;
-std::shared_ptr<std::string> focusWindow_engine = nullptr;
focus_t g_steamcompmgr_xdg_focus;
std::vector<std::shared_ptr<steamcompmgr_win_t>> g_steamcompmgr_xdg_wins;
@@ -6396,9 +6395,6 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co
uint32_t j;
for ( j = 0; j < w->commit_queue.size(); j++ )
{
- if (w->commit_queue[ j ]->feedback.has_value())
- w->engineName = w->commit_queue[ j ]->feedback->vk_engine_name;
-
if ( w->commit_queue[ j ]->commitID == commitID )
{
gpuvis_trace_printf( "commit %lu done", w->commit_queue[ j ]->commitID );
@@ -6443,8 +6439,6 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co
if ( !cv_paint_debug_pause_base_plane )
g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ];
hasRepaint = true;
-
- focusWindow_engine = w->engineName;
}
if ( w == pFocus->overrideWindow )
diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp
index 98e927296483..2f489a26a0aa 100644
--- a/src/steamcompmgr.hpp
+++ b/src/steamcompmgr.hpp
@@ -144,7 +144,6 @@ struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( gamescope_xw
extern gamescope::VBlankTime g_SteamCompMgrVBlankTime;
extern pid_t focusWindow_pid;
-extern std::shared_ptr<std::string> focusWindow_engine;
void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server);
void gamescope_set_selection(std::string contents, GamescopeSelection eSelection);
diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp
index 5a2198d97bed..4027013148b2 100644
--- a/src/steamcompmgr_shared.hpp
+++ b/src/steamcompmgr_shared.hpp
@@ -149,8 +149,6 @@ struct steamcompmgr_win_t {
bool unlockedForFrameCallback = false;
bool receivedDoneCommit = false;
- std::shared_ptr<std::string> engineName;
-
std::vector< gamescope::Rc<commit_t> > commit_queue;
std::shared_ptr<std::vector< uint32_t >> icon;
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index a694b5245a97..42c6bc0c2430 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -862,8 +862,7 @@ static void gamescope_swapchain_swapchain_feedback( struct wl_client *client, st
uint32_t vk_colorspace,
uint32_t vk_composite_alpha,
uint32_t vk_pre_transform,
- uint32_t vk_clipped,
- const char *vk_engine_name)
+ uint32_t vk_clipped)
{
wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource );
if ( wl_info )
@@ -875,7 +874,6 @@ static void gamescope_swapchain_swapchain_feedback( struct wl_client *client, st
.vk_composite_alpha = VkCompositeAlphaFlagBitsKHR(vk_composite_alpha),
.vk_pre_transform = VkSurfaceTransformFlagBitsKHR(vk_pre_transform),
.vk_clipped = VkBool32(vk_clipped),
- .vk_engine_name = std::make_shared<std::string>(vk_engine_name),
.hdr_metadata_blob = nullptr,
});
}
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sun, 31 Aug 2025 20:55:02 +0200
Subject: fix: separate blend tf to its own check
Currently, blend tf and other color mgmt checks are combined into one
check. However, new hardware does not support the blend tf for a plane,
causing loss of all color mgmt features. Separate the checks so that
color mgmt can still be used on such hardware.
Also, add the other function checks to drm_supports_color_mgmt so that
there are no edge cases where only subproperties are supported.
---
src/Backends/DRMBackend.cpp | 40 +++++++++++++++++++++++++++++--------
1 file changed, 32 insertions(+), 8 deletions(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 83da1bfe231b..c1a714569bf2 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -559,6 +559,7 @@ extern std::string g_reshade_effect;
bool drm_update_color_mgmt(struct drm_t *drm);
bool drm_supports_color_mgmt(struct drm_t *drm);
+bool drm_supports_srgb_to_pq(struct drm_t *drm);
bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn );
struct drm_color_ctm2 {
@@ -2691,13 +2692,15 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo
}
}
- if ( drm_supports_color_mgmt( drm ) )
+ if ( drm_supports_srgb_to_pq( drm ) )
{
if (!cv_drm_debug_disable_blend_tf && !bSinglePlane)
liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", drm->pending.output_tf );
else
- liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT );
-
+ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT );
+ }
+ if ( drm_supports_color_mgmt( drm ) )
+ {
if (!cv_drm_debug_disable_ctm && frameInfo->layers[i].ctm != nullptr)
liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_CTM", frameInfo->layers[i].ctm->GetBlobValue() );
else
@@ -2712,13 +2715,16 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo
liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_ENCODING" );
liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" );
+ if ( drm_supports_srgb_to_pq( drm ) )
+ {
+ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT );
+ }
if ( drm_supports_color_mgmt( drm ) )
{
liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT );
liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_LUT", 0 );
liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_TF", 0 );
liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_LUT3D", 0 );
- liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT );
liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 );
}
}
@@ -2870,7 +2876,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI
bool bSinglePlane = frameInfo->layerCount < 2 && cv_drm_single_plane_optimizations;
- if ( drm_supports_color_mgmt( &g_DRM ) && frameInfo->applyOutputColorMgmt )
+ if ( drm_supports_srgb_to_pq( &g_DRM ) && frameInfo->applyOutputColorMgmt )
{
if ( !cv_drm_debug_disable_output_tf && !bSinglePlane )
{
@@ -3371,7 +3377,20 @@ bool drm_supports_color_mgmt(struct drm_t *drm)
if ( !drm->pPrimaryPlane )
return false;
- return drm->pPrimaryPlane->GetProperties().AMD_PLANE_CTM.has_value() && drm->pPrimaryPlane->GetProperties().AMD_PLANE_BLEND_TF.has_value();
+ return drm->pPrimaryPlane->GetProperties().AMD_PLANE_CTM.has_value() && // dm->dc->caps.color.mpc.gamut_remap
+ drm->pPrimaryPlane->GetProperties().AMD_PLANE_SHAPER_LUT.has_value() && // dpp_color_caps.hw_3d_lut
+ drm->pPrimaryPlane->GetProperties().AMD_PLANE_DEGAMMA_TF.has_value(); // dpp_color_caps.dgam_ram || dpp_color_caps.gamma_corr
+}
+
+bool drm_supports_srgb_to_pq(struct drm_t *drm)
+{
+ if ( g_bForceDisableColorMgmt )
+ return false;
+
+ if ( !drm->pPrimaryPlane )
+ return false;
+
+ return drm->pPrimaryPlane->GetProperties().AMD_PLANE_BLEND_TF.has_value(); // dpp_color_caps.ogam_ram
}
std::span<const uint32_t> drm_get_valid_refresh_rates( struct drm_t *drm )
@@ -3501,12 +3520,12 @@ namespace gamescope
if ( g_bOutputHDREnabled )
{
bNeedsFullComposite |= g_bHDRItmEnable;
- if ( !SupportsColorManagement() )
+ if ( !SupportsSRGBtoPQ() )
bNeedsFullComposite |= ( pFrameInfo->layerCount > 1 || pFrameInfo->layers[0].colorspace != GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ );
}
else
{
- if ( !SupportsColorManagement() )
+ if ( !SupportsSRGBtoPQ() )
bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace );
}
@@ -3915,6 +3934,11 @@ namespace gamescope
return drm_supports_color_mgmt( &g_DRM );
}
+ bool SupportsSRGBtoPQ() const
+ {
+ return drm_supports_srgb_to_pq( &g_DRM );
+ }
+
int Commit( const FrameInfo_t *pFrameInfo )
{
drm_t *drm = &g_DRM;
--
2.51.0
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Colin Kinloch <colin.kinloch@collabora.com>
Date: Fri, 1 Aug 2025 16:06:23 +0100
Subject: WaylandBackend: Check features before using color management
This stops compositors lacking required features, transfer functions or
primaries from triggering protocol errors on start.
---
src/Backends/WaylandBackend.cpp | 32 ++++++++++++++++++--------------
1 file changed, 18 insertions(+), 14 deletions(-)
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index 664ed0eac070..1171980ba61d 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -1990,13 +1990,14 @@ namespace gamescope
return false;
if ( !Algorithm::Contains( m_WPColorManagerFeatures.eFeatures, WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES ) )
return false;
+ if ( !Algorithm::Contains( m_WPColorManagerFeatures.eFeatures, WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB ) )
+ return false;
// Transfer Functions
if ( !Algorithm::Contains( m_WPColorManagerFeatures.eTransferFunctions, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB ) )
return false;
if ( !Algorithm::Contains( m_WPColorManagerFeatures.eTransferFunctions, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ ) )
return false;
- // TODO: Need scRGB
// Primaries
if ( !Algorithm::Contains( m_WPColorManagerFeatures.ePrimaries, WP_COLOR_MANAGER_V1_PRIMARIES_SRGB ) )
@@ -2006,6 +2007,22 @@ namespace gamescope
return true;
}();
+
+ if ( m_WPColorManagerFeatures.bSupportsGamescopeColorManagement )
+ {
+ // HDR10.
+ {
+ wp_image_description_creator_params_v1 *pParams = wp_color_manager_v1_create_parametric_creator( m_pWPColorManager );
+ wp_image_description_creator_params_v1_set_primaries_named( pParams, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 );
+ wp_image_description_creator_params_v1_set_tf_named( pParams, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ );
+ m_pWPImageDescriptions[ GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ] = wp_image_description_creator_params_v1_create( pParams );
+ }
+
+ // scRGB
+ {
+ m_pWPImageDescriptions[ GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ] = wp_color_manager_v1_create_windows_scrgb( m_pWPColorManager );
+ }
+ }
}
m_pLibDecor = libdecor_new( m_pDisplay, &s_LibDecorInterface );
@@ -2476,19 +2493,6 @@ namespace gamescope
{
m_pWPColorManager = (wp_color_manager_v1 *)wl_registry_bind( pRegistry, uName, &wp_color_manager_v1_interface, 1u );
wp_color_manager_v1_add_listener( m_pWPColorManager, &s_WPColorManagerListener, this );
-
- // HDR10.
- {
- wp_image_description_creator_params_v1 *pParams = wp_color_manager_v1_create_parametric_creator( m_pWPColorManager );
- wp_image_description_creator_params_v1_set_primaries_named( pParams, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 );
- wp_image_description_creator_params_v1_set_tf_named( pParams, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ );
- m_pWPImageDescriptions[ GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ] = wp_image_description_creator_params_v1_create( pParams );
- }
-
- // scRGB
- {
- m_pWPImageDescriptions[ GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ] = wp_color_manager_v1_create_windows_scrgb( m_pWPColorManager );
- }
}
else if ( !strcmp( pInterface, zwp_pointer_constraints_v1_interface.name ) )
{
--
2.51.0