Files
packages/anda/games/terra-gamescope/handheld.patch
T
Raboneko 433e624dec gamescope: rebase to 3.16.3 (#6175) (#6178)
(cherry picked from commit 3f91aab635)

Co-authored-by: Pornpipat Popum <cappy@cappuchino.xyz>
2025-08-27 03:08:20 -05:00

4438 lines
162 KiB
Diff
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Joshua Ashton <misyl@froggi.es>
Date: Thu, 21 Nov 2024 07:18:22 +0000
Subject: Revert "steamcompmgr: Fix crash when using magnifier and game
recording"
This reverts commit 611a47683f8304ae7a128347a2237df345482fcd.
---
src/steamcompmgr.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index a8f44d1ef2da..0a1f2b263b21 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -1974,7 +1974,7 @@ paint_window_commit( const gamescope::Rc<commit_t> &lastCommit, steamcompmgr_win
drawYOffset += w->GetGeometry().nY * currentScaleRatio_y;
}
- if ( cursor && zoomScaleRatio != 1.0 )
+ if ( zoomScaleRatio != 1.0 )
{
drawXOffset += (((int)sourceWidth / 2) - cursor->x()) * currentScaleRatio_x;
drawYOffset += (((int)sourceHeight / 2) - cursor->y()) * currentScaleRatio_y;
@@ -2200,10 +2200,10 @@ static void paint_pipewire()
s_ulLastOverrideCommitId = ulOverrideCommitId;
// Paint the windows we have onto the Pipewire stream.
- paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, global_focus.cursor, 0, 1.0f, pFocus->overrideWindow );
+ paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, 0, 1.0f, pFocus->overrideWindow );
if ( pFocus->overrideWindow && !pFocus->focusWindow->isSteamStreamingClient )
- paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, global_focus.cursor, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow );
+ paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow );
gamescope::Rc<CVulkanTexture> pRGBTexture = s_pPipewireBuffer->texture->isYcbcr()
? vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, false, DRM_FORMAT_XRGB2101010 )
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Joshua Ashton <misyl@froggi.es>
Date: Thu, 21 Nov 2024 07:19:00 +0000
Subject: backend: Hack
---
src/backend.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend.cpp b/src/backend.cpp
index 8a6bbe8ed944..2411d4ebdc48 100644
--- a/src/backend.cpp
+++ b/src/backend.cpp
@@ -56,7 +56,7 @@ namespace gamescope
CBaseBackendFb::~CBaseBackendFb()
{
// I do not own the client buffer, but I released that in DecRef.
- assert( !HasLiveReferences() );
+ //assert( !HasLiveReferences() );
}
uint32_t CBaseBackendFb::IncRef()
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Date: Fri, 31 Jan 2025 17:21:47 -0800
Subject: layer: Fix 32-bit layer crash
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Some fprintf pointers added in "layer: Fix oldSwapchain when going in/out
of XWayland bypassing" will crash when executed in the 32-bit WSI layer.
GCC also warns about the pointer usage when compiling the 32-bit layer:
"warning: format %p expects argument of type void*, but argument 3
has type VkSwapchainKHR {aka long long unsigned int} [-Wformat=]"
To keep it simple, let's just reinterpret_cast the problematic pointers
to void*.
Closes: #1718
Closes: #1736
---
layer/VkLayer_FROG_gamescope_wsi.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp
index 718a2604f318..5bd1408bf780 100644
--- a/layer/VkLayer_FROG_gamescope_wsi.cpp
+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp
@@ -1076,9 +1076,9 @@ namespace GamescopeWSILayer {
gamescope_swapchain_destroy(state->object);
}
GamescopeSwapchain::remove(swapchain);
- fprintf(stderr, "[Gamescope WSI] Destroying swapchain: %p\n", swapchain);
+ fprintf(stderr, "[Gamescope WSI] Destroying swapchain: %p\n", reinterpret_cast<void*>(swapchain));
pDispatch->DestroySwapchainKHR(device, swapchain, pAllocator);
- fprintf(stderr, "[Gamescope WSI] Destroyed swapchain: %p\n", swapchain);
+ fprintf(stderr, "[Gamescope WSI] Destroyed swapchain: %p\n", reinterpret_cast<void*>(swapchain));
}
static VkResult CreateSwapchainKHR(
@@ -1160,7 +1160,7 @@ namespace GamescopeWSILayer {
fprintf(stderr, "[Gamescope WSI] Creating swapchain for xid: 0x%0x - oldSwapchain: %p - provided minImageCount: %u - minImageCount: %u - format: %s - colorspace: %s - flip: %s\n",
gamescopeSurface->window,
- pCreateInfo->oldSwapchain,
+ reinterpret_cast<void*>(pCreateInfo->oldSwapchain),
pCreateInfo->minImageCount,
minImageCount,
vkroots::helpers::enumString(pCreateInfo->imageFormat),
@@ -1241,7 +1241,7 @@ namespace GamescopeWSILayer {
fprintf(stderr, "[Gamescope WSI] Created swapchain for xid: 0x%0x swapchain: %p - imageCount: %u\n",
gamescopeSurface->window,
- *pSwapchain,
+ reinterpret_cast<void*>(*pSwapchain),
imageCount);
gamescope_swapchain_swapchain_feedback(
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Date: Sun, 5 Jan 2025 11:05:42 +0900
Subject: main: cleanup args
---
src/main.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main.cpp b/src/main.cpp
index 9dff5c4d8560..cd251af559e1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -70,6 +70,7 @@ const struct option *gamescope_options = (struct option[]){
{ "expose-wayland", no_argument, 0 },
{ "mouse-sensitivity", required_argument, nullptr, 's' },
{ "mangoapp", no_argument, nullptr, 0 },
+ { "adaptive-sync", no_argument, nullptr, 0 },
{ "backend", required_argument, nullptr, 0 },
@@ -88,7 +89,6 @@ const struct option *gamescope_options = (struct option[]){
{ "default-touch-mode", required_argument, nullptr, 0 },
{ "generate-drm-mode", required_argument, nullptr, 0 },
{ "immediate-flips", no_argument, nullptr, 0 },
- { "adaptive-sync", no_argument, nullptr, 0 },
{ "framerate-limit", required_argument, nullptr, 0 },
// openvr options
@@ -203,6 +203,7 @@ const char usage[] =
" 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"
" --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"
"Nested mode options:\n"
" -o, --nested-unfocused-refresh game refresh rate when unfocused\n"
@@ -213,11 +214,10 @@ const char usage[] =
" --display-index forces gamescope to use a specific display in nested mode."
"\n"
"Embedded mode options:\n"
- " -O, --prefer-output list of connectors in order of preference\n"
+ " -O, --prefer-output list of connectors in order of preference (ex: DP-1,DP-2,DP-3,HDMI-A-1)\n"
" --default-touch-mode 0: hover, 1: left, 2: right, 3: middle, 4: passthrough\n"
" --generate-drm-mode DRM mode generation algorithm (cvt, fixed)\n"
" --immediate-flips Enable immediate flips, may result in tearing\n"
- " --adaptive-sync Enable adaptive sync if available (variable rate refresh)\n"
"\n"
#if HAVE_OPENVR
"VR mode options:\n"
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20Bernon?= <rbernon@codeweavers.com>
Date: Wed, 29 Jan 2025 11:16:47 +0100
Subject: steamcompmgr: Set WM_STATE property on map and unmap notify events.
This is mandated by the ICCCM specification and Wine now depends on it.
---
src/steamcompmgr.cpp | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 0a1f2b263b21..2544acfb0501 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -3378,6 +3378,14 @@ found:;
return vecPossibleFocusWindows;
}
+static void set_wm_state( xwayland_ctx_t *ctx, Window win, uint32_t state )
+{
+ uint32_t wmState[] = { state, None };
+ XChangeProperty(ctx->dpy, win, ctx->atoms.WMStateAtom, ctx->atoms.WMStateAtom, 32,
+ PropModeReplace, (unsigned char *)wmState,
+ sizeof(wmState) / sizeof(wmState[0]));
+}
+
void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win_t* > &vecPossibleFocusWindows )
{
xwayland_ctx_t *ctx = this;
@@ -3438,10 +3446,7 @@ void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win
{
/* Some games (e.g. DOOM Eternal) don't react well to being put back as
* iconic, so never do that. Only take them out of iconic. */
- uint32_t wmState[] = { ICCCM_NORMAL_STATE, None };
- XChangeProperty(ctx->dpy, ctx->focus.focusWindow->xwayland().id, ctx->atoms.WMStateAtom, ctx->atoms.WMStateAtom, 32,
- PropModeReplace, (unsigned char *)wmState,
- sizeof(wmState) / sizeof(wmState[0]));
+ set_wm_state( ctx, ctx->focus.focusWindow->xwayland().id, ICCCM_NORMAL_STATE );
gpuvis_trace_printf( "determine_and_apply_focus focus %lu", ctx->focus.focusWindow->xwayland().id );
@@ -4227,6 +4232,8 @@ map_win(xwayland_ctx_t* ctx, Window id, unsigned long sequence)
}
MakeFocusDirty();
+
+ set_wm_state( ctx, w->xwayland().id, ICCCM_NORMAL_STATE );
}
static void
@@ -4251,6 +4258,7 @@ unmap_win(xwayland_ctx_t *ctx, Window id, bool fade)
MakeFocusDirty();
finish_unmap_win(ctx, w);
+ set_wm_state( ctx, w->xwayland().id, ICCCM_WITHDRAWN_STATE );
}
uint32_t
@@ -4817,10 +4825,7 @@ handle_wm_change_state(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, XClientMessag
* agreed on it; immediately revert to normal state to avoid being
* stuck in a paused state. */
xwm_log.debugf("Rejecting WM_CHANGE_STATE to ICONIC for window 0x%lx", w->xwayland().id);
- uint32_t wmState[] = { ICCCM_NORMAL_STATE, None };
- XChangeProperty(ctx->dpy, w->xwayland().id, ctx->atoms.WMStateAtom, ctx->atoms.WMStateAtom, 32,
- PropModeReplace, (unsigned char *)wmState,
- sizeof(wmState) / sizeof(wmState[0]));
+ set_wm_state( ctx, w->xwayland().id, ICCCM_NORMAL_STATE );
} else {
xwm_log.debugf("Unhandled WM_CHANGE_STATE to %ld for window 0x%lx", state, w->xwayland().id);
}
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Autumn Ashton <misyl@froggi.es>
Date: Tue, 1 Apr 2025 23:37:29 +0100
Subject: rendervulkan: Fix scaled YUV coming out weird
Our linear emulation doesn't fully support unnormalized right now.
---
src/rendervulkan.hpp | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp
index d8a24e93795a..a3a11a7ba96f 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -325,6 +325,9 @@ struct FrameInfo_t
}
bool viewConvertsToLinearAutomatically() const {
+ if (isYcbcr())
+ return true;
+
return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR ||
colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ||
colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU;
@@ -362,7 +365,7 @@ struct FrameInfo_t
uint32_t result = 0;
for (int i = 0; i < layerCount; i++)
{
- result |= layers[ i ].colorspace << (i * GamescopeAppTextureColorspace_Bits);
+result |= layers[ i ].colorspace << (i * GamescopeAppTextureColorspace_Bits);
}
return result;
}
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Autumn Ashton <misyl@froggi.es>
Date: Wed, 2 Apr 2025 01:09:56 +0100
Subject: steamcompmgr: Fix pipewire stream being incorrect size on external
displays when scaled up
---
src/steamcompmgr.cpp | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 2544acfb0501..7371a3905b56 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -2199,6 +2199,17 @@ static void paint_pipewire()
s_ulLastFocusCommitId = ulFocusCommitId;
s_ulLastOverrideCommitId = ulOverrideCommitId;
+ uint32_t uWidth = s_pPipewireBuffer->texture->width();
+ uint32_t uHeight = s_pPipewireBuffer->texture->height();
+
+ const uint32_t uCompositeDebugBackup = g_uCompositeDebug;
+ const uint32_t uBackupWidth = currentOutputWidth;
+ const uint32_t uBackupHeight = currentOutputHeight;
+
+ g_uCompositeDebug = 0;
+ currentOutputWidth = uWidth;
+ currentOutputHeight = uHeight;
+
// Paint the windows we have onto the Pipewire stream.
paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, 0, 1.0f, pFocus->overrideWindow );
@@ -2206,13 +2217,11 @@ static void paint_pipewire()
paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow );
gamescope::Rc<CVulkanTexture> pRGBTexture = s_pPipewireBuffer->texture->isYcbcr()
- ? vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, false, DRM_FORMAT_XRGB2101010 )
+ ? vulkan_acquire_screenshot_texture( uWidth, uHeight, false, DRM_FORMAT_XRGB2101010 )
: gamescope::Rc<CVulkanTexture>{ s_pPipewireBuffer->texture };
gamescope::Rc<CVulkanTexture> pYUVTexture = s_pPipewireBuffer->texture->isYcbcr() ? s_pPipewireBuffer->texture : nullptr;
- uint32_t uCompositeDebugBackup = g_uCompositeDebug;
- g_uCompositeDebug = 0;
std::optional<uint64_t> oPipewireSequence = vulkan_screenshot( &frameInfo, pRGBTexture, pYUVTexture );
// If we ever want the fat compositing path, use this.
@@ -2220,6 +2229,9 @@ static void paint_pipewire()
g_uCompositeDebug = uCompositeDebugBackup;
+ currentOutputWidth = uBackupWidth;
+ currentOutputHeight = uBackupHeight;
+
if ( oPipewireSequence )
{
vulkan_wait( *oPipewireSequence, true );
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Autumn Ashton <misyl@froggi.es>
Date: Wed, 2 Apr 2025 01:55:47 +0100
Subject: steamcompmgr: VRR frame limiting
---
src/steamcompmgr.cpp | 116 +++++++++++++++++++++++++++---------
src/steamcompmgr_shared.hpp | 2 +
2 files changed, 91 insertions(+), 27 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 7371a3905b56..d91cc45b5be5 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -898,10 +898,6 @@ bool g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = false;
bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w )
{
- // VRR + FPS Limit needs another approach.
- if ( GetBackend()->IsVRRActive() )
- return false;
-
return w && !window_is_steam( w ) && !w->isOverlay && !w->isExternalOverlay;
}
@@ -5076,6 +5072,8 @@ steamcompmgr_flush_frame_done( steamcompmgr_win_t *w )
w->unlockedForFrameCallback = false;
w->receivedDoneCommit = false;
+ w->last_commit_first_latch_time = timespec_to_nanos(now);
+
// Acknowledge commit once.
wlserver_lock();
@@ -5093,35 +5091,58 @@ steamcompmgr_flush_frame_done( steamcompmgr_win_t *w )
}
}
-static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vblank_idx )
-{
- if ( GetBackend()->IsVRRActive() )
- return true;
+static std::optional<uint64_t> s_oLowestFPSLimitScheduleVRR;
+static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vblank_idx, steamcompmgr_win_t *w = nullptr, uint64_t now = 0 )
+{
bool bSendCallback = true;
int nRefreshHz = gamescope::ConvertmHzToHz( g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh );
int nTargetFPS = g_nSteamCompMgrTargetFPS;
- if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && nRefreshHz > nTargetFPS )
+
+ if ( GetBackend()->IsVRRActive() )
{
- int nVblankDivisor = nRefreshHz / nTargetFPS;
+ bool bCloseEnough = std::abs( g_nSteamCompMgrTargetFPS - nRefreshHz ) < 2;
+
+ if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && w && !bCloseEnough )
+ {
+ uint64_t schedule = w->last_commit_first_latch_time + g_SteamCompMgrLimitedAppRefreshCycle;
+
+ static constexpr uint64_t k_ulVRRScheduleFudge = 200'000; // 0.2ms
+ if ( now + k_ulVRRScheduleFudge < schedule )
+ {
+ bSendCallback = false;
- if ( vblank_idx % nVblankDivisor != 0 )
- bSendCallback = false;
+ if ( !s_oLowestFPSLimitScheduleVRR )
+ s_oLowestFPSLimitScheduleVRR = schedule;
+ else
+ s_oLowestFPSLimitScheduleVRR = std::min( *s_oLowestFPSLimitScheduleVRR, schedule );
+ }
+ }
+ }
+ else
+ {
+ if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && nRefreshHz > nTargetFPS )
+ {
+ int nVblankDivisor = nRefreshHz / nTargetFPS;
+
+ if ( vblank_idx % nVblankDivisor != 0 )
+ bSendCallback = false;
+ }
}
return bSendCallback;
}
-static bool steamcompmgr_should_vblank_window( steamcompmgr_win_t *w, uint64_t vblank_idx )
+static bool steamcompmgr_should_vblank_window( steamcompmgr_win_t *w, uint64_t vblank_idx, uint64_t now )
{
- return steamcompmgr_should_vblank_window( steamcompmgr_window_should_limit_fps( w ), vblank_idx );
+ return steamcompmgr_should_vblank_window( steamcompmgr_window_should_limit_fps( w ), vblank_idx, w, now );
}
static void
-steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx )
+steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx, uint64_t now )
{
- if ( steamcompmgr_should_vblank_window( w, vblank_idx ) )
+ if ( steamcompmgr_should_vblank_window( w, vblank_idx, now ) )
{
w->unlockedForFrameCallback = true;
}
@@ -6145,12 +6166,27 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vb
uint64_t now = get_time_in_nanos();
- vblank = vblank && steamcompmgr_should_vblank_window( true, vblank_idx );
-
// very fast loop yes
for ( auto& entry : ctx->doneCommits.listCommitsDone )
{
- if (entry.fifo && (!vblank || fifo_win_seqs.count(entry.winSeq) > 0))
+ bool entry_vblank = vblank;
+
+ if ( GetBackend()->IsVRRActive() )
+ {
+ for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next )
+ {
+ if (w->seq != entry.winSeq)
+ continue;
+
+ entry_vblank = entry_vblank && steamcompmgr_should_vblank_window( true, vblank_idx, w, now );
+ }
+ }
+ else
+ {
+ entry_vblank = entry_vblank && steamcompmgr_should_vblank_window( true, vblank_idx );
+ }
+
+ if (entry.fifo && (!entry_vblank || fifo_win_seqs.count(entry.winSeq) > 0))
{
commits_before_their_time.push_back( entry );
continue;
@@ -7414,6 +7450,11 @@ void LaunchNestedChildren( char **ppPrimaryChildArgv )
}
}
+static gamescope::CTimerFunction g_FPSLimitVRRTimer{ []
+{
+ // do nothing.
+}};
+
void
steamcompmgr_main(int argc, char **argv)
{
@@ -7547,6 +7588,7 @@ steamcompmgr_main(int argc, char **argv)
}
g_SteamCompMgrWaiter.AddWaitable( &GetVBlankTimer() );
+ g_SteamCompMgrWaiter.AddWaitable( &g_FPSLimitVRRTimer );
GetVBlankTimer().ArmNextVBlank( true );
{
@@ -7722,18 +7764,20 @@ steamcompmgr_main(int argc, char **argv)
if ( vblank )
{
{
+ uint64_t now = get_time_in_nanos();
+
gamescope_xwayland_server_t *server = NULL;
for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++)
{
for (steamcompmgr_win_t *w = server->ctx->list; w; w = w->xwayland().next)
{
- steamcompmgr_latch_frame_done( w, vblank_idx );
+ steamcompmgr_latch_frame_done( w, vblank_idx, now );
}
}
for ( const auto& xdg_win : g_steamcompmgr_xdg_wins )
{
- steamcompmgr_latch_frame_done( xdg_win.get(), vblank_idx );
+ steamcompmgr_latch_frame_done( xdg_win.get(), vblank_idx, now );
}
}
}
@@ -7757,18 +7801,36 @@ steamcompmgr_main(int argc, char **argv)
steamcompmgr_check_xdg(vblank, vblank_idx);
+ if ( s_oLowestFPSLimitScheduleVRR )
+ {
+ g_FPSLimitVRRTimer.ArmTimer( *s_oLowestFPSLimitScheduleVRR );
+ s_oLowestFPSLimitScheduleVRR = std::nullopt;
+ }
+
if ( vblank )
{
vblank_idx++;
int nRealRefreshmHz = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh;
- int nRealRefreshHz = gamescope::ConvertmHzToHz( nRealRefreshmHz );
- int nTargetFPS = g_nSteamCompMgrTargetFPS ? g_nSteamCompMgrTargetFPS : nRealRefreshHz;
- nTargetFPS = std::min<int>( nTargetFPS, nRealRefreshHz );
- int nVblankDivisor = nRealRefreshHz / nTargetFPS;
-
g_SteamCompMgrAppRefreshCycle = gamescope::mHzToRefreshCycle( nRealRefreshmHz );
- g_SteamCompMgrLimitedAppRefreshCycle = g_SteamCompMgrAppRefreshCycle * nVblankDivisor;
+ g_SteamCompMgrLimitedAppRefreshCycle = g_SteamCompMgrAppRefreshCycle;
+ if ( g_nSteamCompMgrTargetFPS )
+ {
+ int nRealRefreshHz = gamescope::ConvertmHzToHz( nRealRefreshmHz );
+ int nTargetFPS = g_nSteamCompMgrTargetFPS;
+ nTargetFPS = std::min<int>( nTargetFPS, nRealRefreshHz );
+
+ if ( GetBackend()->IsVRRActive() )
+ {
+ g_SteamCompMgrLimitedAppRefreshCycle = gamescope::mHzToRefreshCycle( gamescope::ConvertHztomHz( nTargetFPS ) );
+ }
+ else
+ {
+ int nVblankDivisor = nRealRefreshHz / nTargetFPS;
+
+ g_SteamCompMgrLimitedAppRefreshCycle = g_SteamCompMgrAppRefreshCycle * nVblankDivisor;
+ }
+ }
}
// Handle presentation-time stuff
diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp
index 095694e4937a..f300eb94d954 100644
--- a/src/steamcompmgr_shared.hpp
+++ b/src/steamcompmgr_shared.hpp
@@ -127,6 +127,8 @@ struct steamcompmgr_win_t {
bool maybe_a_dropdown = false;
bool outdatedInteractiveFocus = false;
+ uint64_t last_commit_first_latch_time = 0;
+
bool hasHwndStyle = false;
uint32_t hwndStyle = 0;
bool hasHwndStyleEx = false;
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Colin Kinloch <colin.kinloch@collabora.com>
Date: Sat, 21 Dec 2024 17:08:12 +0000
Subject: scripts: Derive script path from meson prefix
---
meson.build | 1 +
src/Script/Script.cpp | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/meson.build b/meson.build
index c4924c7afb44..00a1cb42bde4 100644
--- a/meson.build
+++ b/meson.build
@@ -68,6 +68,7 @@ endif
add_project_arguments(
'-DHAVE_PIPEWIRE=@0@'.format(pipewire_dep.found().to_int()),
'-DHAVE_OPENVR=@0@'.format(openvr_dep.found().to_int()),
+ '-DSCRIPT_DIR="@0@"'.format(prefix / data_dir / 'gamescope/scripts'),
language: 'cpp',
)
diff --git a/src/Script/Script.cpp b/src/Script/Script.cpp
index a104ee993bc9..142371b33e0f 100644
--- a/src/Script/Script.cpp
+++ b/src/Script/Script.cpp
@@ -130,7 +130,7 @@ namespace gamescope
}
else
{
- RunFolder( "/usr/share/gamescope/scripts", true );
+ RunFolder( SCRIPT_DIR, true );
RunFolder( "/etc/gamescope/scripts", true );
}
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Date: Wed, 30 Oct 2024 14:56:18 -0700
Subject: script: Lenovo Legion Go LCD display configuration
Add support for the Lenovo Legion Go handheld, which features a rotated
1600x2560 panel that reports 60Hz and 144Hz modes in the EDID. VRR and
HDR are not supported, and only one panel model is known to be in use.
Modes other than 60Hz and 144Hz have a small chance of causing a locked
touchscreen until another modeset is performed or have artifacts during
modesetting, so extra modes are not included. The dynamic_modegen section
is filled out in case users decide to add their own refresh rates in a
local table.
This configuration has been tested with:
* SteamOS Main 20241025.1000 with kernel 6.8.12-valve3
* Arch Linux with kernel 6.12-rc5
Signed-off-by: Matthew Schwartz <matthew.schwartz@linux.dev>
---
.../displays/lenovo.legiongo.lcd.lua | 45 +++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 scripts/00-gamescope/displays/lenovo.legiongo.lcd.lua
diff --git a/scripts/00-gamescope/displays/lenovo.legiongo.lcd.lua b/scripts/00-gamescope/displays/lenovo.legiongo.lcd.lua
new file mode 100644
index 000000000000..2360cfe35cb2
--- /dev/null
+++ b/scripts/00-gamescope/displays/lenovo.legiongo.lcd.lua
@@ -0,0 +1,45 @@
+gamescope.config.known_displays.lenovo_legiongo_lcd = {
+ pretty_name = "Lenovo Legion Go LCD",
+ dynamic_refresh_rates = {
+ 60, 144
+ },
+ 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
+ },
+ -- Use the EDID colorimetry for now, but someone should check
+ -- if the EDID colorimetry truly matches what the display is capable of.
+ dynamic_modegen = function(base_mode, refresh)
+ debug("Generating mode "..refresh.."Hz for Lenovo Legion Go LCD")
+ local mode = base_mode
+
+ -- These are only tuned for 1600x2560
+ gamescope.modegen.set_resolution(mode, 1600, 2560)
+
+ -- Horizontal timings: Hfront, Hsync, Hback
+ gamescope.modegen.set_h_timings(mode, 60, 30, 130)
+ -- Vertical timings: Vfront, Vsync, Vback
+ gamescope.modegen.set_v_timings(mode, 30, 4, 96)
+
+ mode.clock = gamescope.modegen.calc_max_clock(mode, refresh)
+ mode.vrefresh = gamescope.modegen.calc_vrefresh(mode)
+
+ return mode
+ end,
+ matches = function(display)
+ -- There is only a single panel in use on the Lenovo Legion Go.
+ if display.vendor == "LEN" and display.model == "Go Display" and display.product == 0x0001 then
+ debug("[lenovo_legiongo_lcd] Matched vendor: "..display.vendor.." model: "..display.model.." product: "..display.product)
+ return 5000
+ end
+ return -1
+ end
+}
+debug("Registered Lenovo Legion Go LCD as a known display")
+--debug(inspect(gamescope.config.known_displays.lenovo_legiongo_lcd))
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aarron Lee <aarron-lee@users.noreply.github.com>
Date: Wed, 9 Oct 2024 23:19:15 -0400
Subject: script: GPD Win 4 display configuration
This introduces a display configuration for the GPD Win 4 handheld.
Most of this display configuration was derived from the edid as-is,
excluding the dynamic_refresh_rates.
All refresh rates were all manually tested with the Steam slider,
and the device functioned as expected through multiple games.
This was tested on two separate GPD Win 4 devices.
Tested on:
Model DMI
- G1618-04
Distro
- Bazzite 40
Kernels
- 6.9.12-210.fsync.fc40.x86_64
- 6.11.2-201.fsync.fc40.x86_64
All refresh rates were tested with the following games:
- Ghost of Tsushima
- Nier Automata
- Metaphor: ReFantazio (Demo)
- Boomerang Fu
These games were tested with 35hz, 40hz, 50hz,
and had no observed issues:
- Crosscode
- Cult of Lamb
- Dave the Diver
- MDA Rain Code Plus
- Shantae and the Seven Sirens
---
.../00-gamescope/displays/gpd.win4.lcd.lua | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 scripts/00-gamescope/displays/gpd.win4.lcd.lua
diff --git a/scripts/00-gamescope/displays/gpd.win4.lcd.lua b/scripts/00-gamescope/displays/gpd.win4.lcd.lua
new file mode 100644
index 000000000000..5f5eec898c3b
--- /dev/null
+++ b/scripts/00-gamescope/displays/gpd.win4.lcd.lua
@@ -0,0 +1,60 @@
+-- colorimetry from edid
+local gpd_win4_lcd_colorimetry = {
+ r = { x = 0.6250, y = 0.3398 },
+ g = { x = 0.2802, y = 0.5947 },
+ b = { x = 0.1552, y = 0.0703 },
+ w = { x = 0.2832, y = 0.2978 }
+}
+
+gamescope.config.known_displays.gpd_win4_lcd = {
+ pretty_name = "GPD Win 4",
+ dynamic_refresh_rates = {
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60
+ },
+ hdr = {
+ supported = false,
+ force_enabled = false,
+ eotf = gamescope.eotf.gamma22,
+ max_content_light_level = 400,
+ max_frame_average_luminance = 400,
+ min_content_light_level = 0.5
+ },
+ colorimetry = gpd_win4_lcd_colorimetry,
+ dynamic_modegen = function(base_mode, refresh)
+ debug("Generating mode "..refresh.."Hz for GPD Win 4")
+ local mode = base_mode
+
+ gamescope.modegen.set_resolution(mode, 1920, 1080)
+
+ -- Horizontal timings: Hfront, Hsync, Hback
+ gamescope.modegen.set_h_timings(mode, 72, 8, 16)
+ -- Vertical timings: Vfront, Vsync, Vback
+ gamescope.modegen.set_v_timings(mode, 14, 3, 13)
+
+ mode.clock = gamescope.modegen.calc_max_clock(mode, refresh)
+ mode.vrefresh = gamescope.modegen.calc_vrefresh(mode)
+
+ return mode
+ end,
+ matches = function(display)
+ -- There are multiple revisions of the GPD Win 4
+ -- They all should have the same panel
+ -- lcd_types is just in case there are different panels
+ local lcd_types = {
+ { vendor = "GPD", model = "G1618-04" },
+ }
+
+ for index, value in ipairs(lcd_types) do
+ if value.vendor == display.vendor and value.model == display.model then
+ debug("[gpd_win4_lcd] Matched vendor: "..value.vendor.." model: "..value.model)
+ return 5000
+ end
+ end
+
+ return -1
+ end
+}
+debug("Registered GPD Win 4 as a known display")
+--debug(inspect(gamescope.config.known_displays.gpd_win4_lcd))
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Autumn Ashton <misyl@froggi.es>
Date: Wed, 2 Apr 2025 03:15:44 +0100
Subject: DRMBackend: Read the EDID's modes for dynamic refresh rate modes by
default
Should supercede https://github.com/ValveSoftware/gamescope/pull/1627 and allow this feature on some handhelds that just expose modes in the EDID.
---
src/Backends/DRMBackend.cpp | 32 ++++++++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 0b121e84167a..37185856b7ec 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2142,6 +2142,8 @@ namespace gamescope
bool bHasKnownColorimetry = false;
bool bHasKnownHDRInfo = false;
+ m_Mutable.ValidDynamicRefreshRates.clear();
+ m_Mutable.fnDynamicModeGenerator = nullptr;
{
CScriptScopedLock script;
@@ -2155,8 +2157,6 @@ namespace gamescope
(int)oKnownDisplay->first.size(), oKnownDisplay->first.data(),
(int)psvPrettyName.size(), psvPrettyName.data() );
- m_Mutable.fnDynamicModeGenerator = nullptr;
- m_Mutable.ValidDynamicRefreshRates.clear();
sol::optional<sol::table> otDynamicRefreshRates = tTable["dynamic_refresh_rates"];
sol::optional<sol::function> ofnDynamicModegen = tTable["dynamic_modegen"];
@@ -2243,6 +2243,34 @@ namespace gamescope
bHasKnownHDRInfo = true;
}
}
+ else
+ {
+ // Unknown display, see if there are any other refresh rates in the EDID we can get.
+ if ( GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL )
+ {
+ const drmModeModeInfo *pPreferredMode = find_mode( m_pConnector.get(), 0, 0, 0 );
+
+ if ( pPreferredMode )
+ {
+ // See if the EDID has any modes for us.
+ for (int i = 0; i < m_pConnector->count_modes; i++)
+ {
+ const drmModeModeInfo *pMode = &m_pConnector->modes[i];
+
+ if ( pMode->hdisplay != pPreferredMode->hdisplay || pMode->vdisplay != pPreferredMode->vdisplay )
+ continue;
+
+
+ if ( !Algorithm::Contains( m_Mutable.ValidDynamicRefreshRates, pMode->vrefresh ) )
+ {
+ m_Mutable.ValidDynamicRefreshRates.push_back( pMode->vrefresh );
+ }
+ }
+
+ std::sort( m_Mutable.ValidDynamicRefreshRates.begin(), m_Mutable.ValidDynamicRefreshRates.end() );
+ }
+ }
+ }
}
if ( !bHasKnownColorimetry )
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: sharkautarch <128002472+sharkautarch@users.noreply.github.com>
Date: Tue, 3 Dec 2024 13:31:37 -0500
Subject: wlserver: wlserver_run(): ensure waylock is released when
wl_event_loop_dispatch returns ret<0
---
src/wlserver.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index 78a86ee0e2e2..4ce9511352d0 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -1956,6 +1956,7 @@ void wlserver_run(void)
wl_display_flush_clients(wlserver.display);
int ret = wl_event_loop_dispatch(wlserver.event_loop, 0);
if (ret < 0) {
+ wlserver_unlock();
break;
}
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Autumn Ashton <misyl@froggi.es>
Date: Wed, 2 Apr 2025 04:29:16 +0100
Subject: DRMBackend: Expose data string from EDID to matches function
---
src/Backends/DRMBackend.cpp | 12 ++++++++++--
src/Script/Script.cpp | 3 ++-
src/Script/Script.h | 2 +-
3 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 37185856b7ec..06ebbe7255d4 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -287,6 +287,7 @@ namespace gamescope
const char *GetName() const override { return m_Mutable.szName; }
const char *GetMake() const override { return m_Mutable.pszMake; }
const char *GetModel() const override { return m_Mutable.szModel; }
+ const char *GetDataString() const { return m_Mutable.szDataString; }
uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; }
std::span<const uint32_t> GetValidDynamicRefreshRates() const override { return m_Mutable.ValidDynamicRefreshRates; }
const displaycolorimetry_t& GetDisplayColorimetry() const { return m_Mutable.DisplayColorimetry; }
@@ -392,6 +393,7 @@ namespace gamescope
char szName[32]{};
char szMakePNP[4]{};
char szModel[16]{};
+ char szDataString[16]{};
const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP.
std::vector<uint32_t> ValidDynamicRefreshRates{};
DRMModeGenerator fnDynamicModeGenerator;
@@ -2128,13 +2130,19 @@ namespace gamescope
for ( size_t i = 0; pDescriptors[i] != nullptr; i++ )
{
const di_edid_display_descriptor *pDesc = pDescriptors[i];
- if ( di_edid_display_descriptor_get_tag( pDesc ) == DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME )
+ const di_edid_display_descriptor_tag eTag = di_edid_display_descriptor_get_tag( pDesc );
+ if ( eTag == DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME )
{
// Max length of di_edid_display_descriptor_get_string is 14
// m_szModel is 16 bytes.
const char *pszModel = di_edid_display_descriptor_get_string( pDesc );
strncpy( m_Mutable.szModel, pszModel, sizeof( m_Mutable.szModel ) );
}
+ else if ( eTag == DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING )
+ {
+ const char *pszDataString = di_edid_display_descriptor_get_string( pDesc );
+ strncpy( m_Mutable.szDataString, pszDataString, sizeof( m_Mutable.szDataString ) );
+ }
}
drm_log.infof("Connector %s -> %s - %s", m_Mutable.szName, m_Mutable.szMakePNP, m_Mutable.szModel );
@@ -2147,7 +2155,7 @@ namespace gamescope
{
CScriptScopedLock script;
- auto oKnownDisplay = script.Manager().Gamescope().Config.LookupDisplay( script, m_Mutable.szMakePNP, pProduct->product, m_Mutable.szModel );
+ auto oKnownDisplay = script.Manager().Gamescope().Config.LookupDisplay( script, m_Mutable.szMakePNP, pProduct->product, m_Mutable.szModel, m_Mutable.szDataString );
if ( oKnownDisplay )
{
sol::table tTable = oKnownDisplay->second;
diff --git a/src/Script/Script.cpp b/src/Script/Script.cpp
index 142371b33e0f..ceb1f80e3a77 100644
--- a/src/Script/Script.cpp
+++ b/src/Script/Script.cpp
@@ -247,7 +247,7 @@ namespace gamescope
// GamescopeScript_t
//
- std::optional<std::pair<std::string_view, sol::table>> GamescopeScript_t::Config_t::LookupDisplay( CScriptScopedLock &script, std::string_view psvVendor, uint16_t uProduct, std::string_view psvModel )
+ std::optional<std::pair<std::string_view, sol::table>> GamescopeScript_t::Config_t::LookupDisplay( CScriptScopedLock &script, std::string_view psvVendor, uint16_t uProduct, std::string_view psvModel, std::string_view psvDataString )
{
int nMaxPrority = -1;
std::optional<std::pair<std::string_view, sol::table>> oOutDisplay;
@@ -256,6 +256,7 @@ namespace gamescope
tDisplay["vendor"] = psvVendor;
tDisplay["product"] = uProduct;
tDisplay["model"] = psvModel;
+ tDisplay["data_string"] = psvDataString;
for ( auto iter : KnownDisplays )
{
diff --git a/src/Script/Script.h b/src/Script/Script.h
index 6eebb66a9f36..7c856a75e512 100644
--- a/src/Script/Script.h
+++ b/src/Script/Script.h
@@ -30,7 +30,7 @@ namespace gamescope
sol::table KnownDisplays;
- std::optional<std::pair<std::string_view, sol::table>> LookupDisplay( CScriptScopedLock &script, std::string_view psvVendor, uint16_t uProduct, std::string_view psvModel );
+ std::optional<std::pair<std::string_view, sol::table>> LookupDisplay( CScriptScopedLock &script, std::string_view psvVendor, uint16_t uProduct, std::string_view psvModel, std::string_view psvDataString );
} Config;
};
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Pierre-Loup A. Griffais" <pgriffais@valvesoftware.com>
Date: Tue, 15 Apr 2025 15:56:02 -0700
Subject: steamcompmgr: avoid a crash with pipewire+magnification
It won't render the correct offset for now, but that's better than crashing.
Probably magnification should be ignore when painting for pipewire?
---
src/steamcompmgr.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index d91cc45b5be5..d0a069e69c60 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -1972,8 +1972,8 @@ paint_window_commit( const gamescope::Rc<commit_t> &lastCommit, steamcompmgr_win
if ( zoomScaleRatio != 1.0 )
{
- drawXOffset += (((int)sourceWidth / 2) - cursor->x()) * currentScaleRatio_x;
- drawYOffset += (((int)sourceHeight / 2) - cursor->y()) * currentScaleRatio_y;
+ drawXOffset += (((int)sourceWidth / 2) - (cursor ? cursor->x() : 0)) * currentScaleRatio_x;
+ drawYOffset += (((int)sourceHeight / 2) - (cursor ? cursor->y() : 0)) * currentScaleRatio_y;
}
}
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Date: Sun, 6 Apr 2025 20:35:54 -0700
Subject: build: add workaround to build with CMake 4.0
OpenVR's CMakelist does not support CMake 4.0 yet, causing build failures
in gamescope. Until a new OpenVR SDK is released, let's make sure gamescope
stays buildable in the meantime with a workaround which can be removed in
the future.
Closes: #1785
---
meson.build | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/meson.build b/meson.build
index 00a1cb42bde4..cc07a59ca99a 100644
--- a/meson.build
+++ b/meson.build
@@ -55,7 +55,10 @@ if get_option('enable_openvr_support')
if not openvr_dep.found()
cmake = import('cmake')
openvr_var = cmake.subproject_options()
- openvr_var.add_cmake_defines({'USE_LIBCXX': false})
+ openvr_var.add_cmake_defines({'USE_LIBCXX': false,
+ #HACK: remove me when openvr supports CMake 4.0
+ 'CMAKE_POLICY_VERSION_MINIMUM': '3.5'})
+ #ENDHACK
openvr_var.set_override_option('warning_level', '0')
openvr_proj = cmake.subproject('openvr', options : openvr_var)
openvr_dep = openvr_proj.dependency('openvr_api')
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Autumn Ashton <misyl@froggi.es>
Date: Fri, 25 Apr 2025 16:17:00 +0100
Subject: WaylandBackend: Fix initial scale for Wayland surfaces
---
src/Backends/WaylandBackend.cpp | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index 08af8bca1b99..da43d03987a8 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -272,6 +272,7 @@ namespace gamescope
std::vector<wl_output *> m_pOutputs;
bool m_bNeedsDecorCommit = false;
uint32_t m_uFractionalScale = 120;
+ bool m_bHasRecievedScale = false;
std::mutex m_PlaneStateLock;
std::optional<WaylandPlaneState> m_oCurrentPlaneState;
@@ -1358,14 +1359,31 @@ namespace gamescope
void CWaylandPlane::Wayland_FractionalScale_PreferredScale( wp_fractional_scale_v1 *pFractionalScale, uint32_t uScale )
{
- if ( m_uFractionalScale != uScale )
+ bool bDirty = false;
+
+ static uint32_t s_uGlobalFractionalScale = 120;
+ if ( s_uGlobalFractionalScale != uScale )
{
- g_nOutputWidth = ( g_nOutputWidth * uScale ) / m_uFractionalScale;
- g_nOutputHeight = ( g_nOutputHeight * uScale ) / m_uFractionalScale;
+ if ( m_bHasRecievedScale )
+ {
+ g_nOutputWidth = ( g_nOutputWidth * uScale ) / m_uFractionalScale;
+ g_nOutputHeight = ( g_nOutputHeight * uScale ) / m_uFractionalScale;
+ }
+
+ s_uGlobalFractionalScale = uScale;
+ bDirty = true;
+ }
+ if ( m_uFractionalScale != uScale )
+ {
m_uFractionalScale = uScale;
- force_repaint();
+ bDirty = true;
}
+
+ m_bHasRecievedScale = true;
+
+ if ( bDirty )
+ force_repaint();
}
////////////////
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Roberto=20de=20Souza?= <jose.souza@intel.com>
Date: Wed, 9 Apr 2025 14:01:13 -0700
Subject: rendervulkan: Append VK_STRUCTURE_TYPE_WSI_MEMORY_ALLOCATE_INFO_MESA
when creating scanout VkImages
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
It already appends VK_STRUCTURE_TYPE_WSI_IMAGE_CREATE_INFO_MESA when
creating scanout images to make other Vulkan drivers works, so lets
also append VK_STRUCTURE_TYPE_WSI_MEMORY_ALLOCATE_INFO_MESA to make
ANV+Xe KMD work.
Signed-off-by: José Roberto de Souza <jose.souza@intel.com>
---
src/rendervulkan.cpp | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
index 7efcc0dbd8a9..b8412b8fdf2f 100644
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -163,6 +163,7 @@ Target *pNextFind(const Base *base, VkStructureType sType)
}
#define VK_STRUCTURE_TYPE_WSI_IMAGE_CREATE_INFO_MESA (VkStructureType)1000001002
+#define VK_STRUCTURE_TYPE_WSI_MEMORY_ALLOCATE_INFO_MESA (VkStructureType)1000001003
struct wsi_image_create_info {
VkStructureType sType;
@@ -173,6 +174,11 @@ struct wsi_image_create_info {
const uint64_t *modifiers;
};
+struct wsi_memory_allocate_info {
+ VkStructureType sType;
+ const void *pNext;
+ bool implicit_sync;
+};
// DRM doesn't have 32bit floating point formats, so add our own
#define DRM_FORMAT_ABGR32323232F fourcc_code('A', 'B', '8', 'F')
@@ -2215,6 +2221,15 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin
VkImportMemoryFdInfoKHR importMemoryInfo = {};
VkExportMemoryAllocateInfo memory_export_info = {};
VkMemoryDedicatedAllocateInfo memory_dedicated_info = {};
+ struct wsi_memory_allocate_info memory_wsi_info = {};
+
+ if ( flags.bFlippable == true )
+ {
+ memory_wsi_info = {
+ .sType = VK_STRUCTURE_TYPE_WSI_MEMORY_ALLOCATE_INFO_MESA,
+ .pNext = std::exchange(allocInfo.pNext, &memory_wsi_info),
+ };
+ }
if ( flags.bExportable == true || pDMA != nullptr )
{
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MithicSpirit <rpc01234@gmail.com>
Date: Thu, 30 Jan 2025 15:54:26 -0500
Subject: WaylandBackend: prevent crash after closing window
Whenever a window was closed, gamescope would segfault due to calling
IsSurfacePlane with null (from Wayland_Pointer_Leave, and maybe a few
other places). This is addressed by having IsSurfacePlane short-circuit
if it's passed null.
HACK: I feel like IsSurfacePlane shouldn't ever be called with a null
pointer, but this is the easiest way to solve this for now, and the code
needs refactoring anyway.
---
src/Backends/WaylandBackend.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index da43d03987a8..22f8ee5c8fac 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -75,7 +75,9 @@ static inline uint32_t WaylandScaleToLogical( uint32_t pValue, uint32_t pFactor
}
static bool IsSurfacePlane( wl_surface *pSurface ) {
- return wl_proxy_get_tag( (wl_proxy *)pSurface ) == &GAMESCOPE_plane_tag;
+ // HACK: this probably should never be called with a null pointer, but it
+ // was happening after a window was closed.
+ return pSurface && (wl_proxy_get_tag( (wl_proxy *)pSurface ) == &GAMESCOPE_plane_tag);
}
#define WAYLAND_NULL() []<typename... Args> ( void *pData, Args... args ) { }
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Date: Tue, 1 Apr 2025 23:03:56 -0700
Subject: script: fixup Ally config to support BOE panels
Also made some style fixups. Verified working on both BOE
and TMX panel models.
---
.../displays/asus.rogally.lcd.lua | 35 ++++++++++++-------
1 file changed, 23 insertions(+), 12 deletions(-)
diff --git a/scripts/00-gamescope/displays/asus.rogally.lcd.lua b/scripts/00-gamescope/displays/asus.rogally.lcd.lua
index 11ba7cc30f9b..40b5188b5c73 100644
--- a/scripts/00-gamescope/displays/asus.rogally.lcd.lua
+++ b/scripts/00-gamescope/displays/asus.rogally.lcd.lua
@@ -10,7 +10,7 @@ local rogally_lcd_refresh_rates = {
}
gamescope.config.known_displays.rogally_lcd = {
- pretty_name = "ASUS ROG Ally/Ally X LCD",
+ pretty_name = "ASUS ROG Ally / ROG Ally X LCD",
hdr = {
-- Setup some fallbacks for undocking with HDR, meant
-- for the internal panel. It does not support HDR.
@@ -21,16 +21,14 @@ gamescope.config.known_displays.rogally_lcd = {
max_frame_average_luminance = 500,
min_content_light_level = 0.5
},
- -- Use the EDID colorimetry for now, but someone should check
- -- if the EDID colorimetry truly matches what the display is capable of.
dynamic_refresh_rates = rogally_lcd_refresh_rates,
- -- Follow the Steam Deck OLED style for modegen by variang the VFP (Vertical Front Porch)
+ -- Follow the Steam Deck OLED style for modegen by varying the VFP (Vertical Front Porch)
--
-- Given that this display is VRR and likely has an FB/Partial FB in the DDIC:
-- it should be able to handle this method, and it is more optimal for latency
-- than elongating the clock.
dynamic_modegen = function(base_mode, refresh)
- debug("Generating mode "..refresh.."Hz for ROG Ally with fixed pixel clock")
+ debug("Generating mode "..refresh.."Hz for ASUS ROG Ally / ROG Ally X LCD with fixed pixel clock")
local vfps = {
1771, 1720, 1655, 1600, 1549,
1499, 1455, 1405, 1361, 1320,
@@ -50,7 +48,7 @@ gamescope.config.known_displays.rogally_lcd = {
}
local vfp = vfps[zero_index(refresh - 48)]
if vfp == nil then
- warn("Couldn't do refresh "..refresh.." on ROG Ally")
+ warn("Couldn't do refresh "..refresh.." on ASUS ROG Ally / ROG Ally X LCD")
return base_mode
end
@@ -62,15 +60,28 @@ gamescope.config.known_displays.rogally_lcd = {
--debug(inspect(mode))
return mode
end,
- -- There is only a single panel model in use across both
- -- ROG Ally + ROG Ally X.
matches = function(display)
- if display.vendor == "TMX" and display.model == "TL070FVXS01-0" and display.product == 0x0002 then
- debug("[rogally_lcd] Matched vendor: "..display.vendor.." model: "..display.model.." product:"..display.product)
- return 5000
+ -- There are two panels used across the ROG Ally and ROG Ally X
+ -- with the same timings, but the model names are in different
+ -- parts of the EDID.
+ local lcd_types = {
+ { vendor = "TMX", model = "TL070FVXS01-0", product = 0x0002 },
+ { vendor = "BOE", data_string = "TS070FHM-LU0", product = 0x0C33 },
+ }
+
+ for index, value in ipairs(lcd_types) do
+ -- We only match if the vendor and product match exactly, plus either model or data_string
+ if value.vendor == display.vendor and value.product == display.product then
+ if (value.model and value.model == display.model)
+ or (value.data_string and value.data_string == display.data_string) then
+ debug("[rogally_lcd] Matched vendor: "..value.vendor.." model: "..(value.model or value.data_string).." product: "..value.product)
+ return 5000
+ end
+ end
end
+
return -1
end
}
-debug("Registered ASUS ROG Ally/Ally X LCD as a known display")
+debug("Registered ASUS ROG Ally / ROG Ally X LCD as a known display")
--debug(inspect(gamescope.config.known_displays.rogally_lcd))
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Colin Kinloch <colin.kinloch@collabora.com>
Date: Sat, 21 Dec 2024 17:24:31 +0000
Subject: scripts: Search GAMESCOPE_SCRIPT_PATH for scripts
Adds GAMESCOPE_SCRIPT_PATH as a colon separated list of paths to search for scripts in.
It's also added to the meson devenv which allows developers to test changes by running:
`meson devenv -C _build`
---
meson.build | 4 ++++
src/Script/Script.cpp | 10 ++++++++++
2 files changed, 14 insertions(+)
diff --git a/meson.build b/meson.build
index cc07a59ca99a..562ee1585a6b 100644
--- a/meson.build
+++ b/meson.build
@@ -102,3 +102,7 @@ endif
# Handle default script/config stuff
meson.add_install_script('default_scripts_install.sh')
+
+devenv = environment()
+devenv.set('GAMESCOPE_SCRIPT_PATH', join_paths(meson.current_source_dir(), 'scripts'))
+meson.add_devenv(devenv)
diff --git a/src/Script/Script.cpp b/src/Script/Script.cpp
index ceb1f80e3a77..2d3cd47bb8ec 100644
--- a/src/Script/Script.cpp
+++ b/src/Script/Script.cpp
@@ -124,10 +124,20 @@ namespace gamescope
void CScriptManager::RunDefaultScripts()
{
+ const char *sScriptPathEnv = getenv("GAMESCOPE_SCRIPT_PATH");
+
if ( cv_script_use_local_scripts )
{
RunFolder( "../scripts", true );
}
+ else if ( sScriptPathEnv )
+ {
+ std::vector<std::string_view> sScriptPaths = gamescope::Split( sScriptPathEnv, ":" );
+ for ( const auto &sScriptPath : sScriptPaths )
+ {
+ RunFolder( sScriptPath, true );
+ }
+ }
else
{
RunFolder( SCRIPT_DIR, true );
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Paul Gofman <pgofman@codeweavers.com>
Date: Mon, 7 Apr 2025 17:42:30 -0600
Subject: steamcompmgr: Set receivedDoneCommit even if the commit is not for
current surface in update_wayland_res().
---
src/steamcompmgr.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index d0a069e69c60..e40f95715746 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -6469,8 +6469,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re
wlserver_lock();
wlr_buffer_unlock( buf );
wlserver_unlock();
-
- // Don't mark as recieve done commit, it was for the wrong surface.
+ w->receivedDoneCommit = true;
return;
}
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "kingstom.chen" <kingstom.chen@gmail.com>
Date: Tue, 18 Feb 2025 08:59:06 +0800
Subject: Force wrap file usage for stb and glm dependencies
the `dependency()` for stb and glm first searched for system-installed versions,
which could an incompatible version (e.g. `stb_image_resize2.h`), it may break the build.
By forcing the use of the subproject wrap files, it will prevent breaking changes
due to unpredictable system dependency versions.
---
src/meson.build | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/meson.build b/src/meson.build
index 74fc0334d47e..f35f7ef0cb94 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -19,11 +19,14 @@ xkbcommon = dependency('xkbcommon')
thread_dep = dependency('threads')
cap_dep = dependency('libcap', required: get_option('rt_cap'))
epoll_dep = dependency('epoll-shim', required: false)
-glm_dep = dependency('glm')
sdl2_dep = dependency('SDL2', required: get_option('sdl2_backend'))
-stb_dep = dependency('stb')
avif_dep = dependency('libavif', version: '>=1.0.0', required: get_option('avif_screenshots'))
+glm_proj = subproject('glm')
+glm_dep = glm_proj.get_variable('glm_dep')
+stb_proj = subproject('stb')
+stb_dep = stb_proj.get_variable('stb_dep')
+
wlroots_dep = dependency(
'wlroots',
version: ['>= 0.18.0', '< 0.19.0'],
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Date: Thu, 3 Apr 2025 14:08:50 -0700
Subject: script: Lenovo Legion Go S LCD display configuration
This configuration covers the non-VRR limiter of the Lenovo Legion Go S.
In the EDID, only 60Hz and 120Hz are listed as valid modes with different
pixel clocks. Because of this, an LCD Deck style for dynamic modegen works best.
The refresh rates within this configuration were tested with hundreds of modesets
on my Z2 Go model with a CSW panel.
---
.../displays/lenovo.legiongos.lcd.lua | 59 +++++++++++++++++++
1 file changed, 59 insertions(+)
create mode 100644 scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
diff --git a/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua b/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
new file mode 100644
index 000000000000..6263478c0517
--- /dev/null
+++ b/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
@@ -0,0 +1,59 @@
+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.
+ 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
+ },
+ -- 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_modegen = function(base_mode, refresh)
+ debug("Generating mode "..refresh.."Hz for Lenovo Legion Go S LCD")
+ local mode = base_mode
+
+ -- These are only tuned for 1920x1200.
+ gamescope.modegen.set_resolution(mode, 1920, 1200)
+
+ -- 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)
+ 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 },
+ { vendor = "BOE", model = "NS080WUM-LX1", product = 0x0C00 },
+ }
+
+ for index,value in ipairs(lcd_types) do
+ if value.vendor == display.vendor and value.model == display.model and value.product == display.product then
+ debug("[legiongos_lcd] Matched vendor: "..display.vendor.." model: "..display.model.." product: "..display.product)
+ return 5000
+ end
+ end
+
+ return -1
+ end
+}
+debug("Registered Lenovo Legion Go S LCD as a known display")
+--debug(inspect(gamescope.config.known_displays.legiongos_lcd))
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Date: Wed, 9 Apr 2025 14:22:16 -0700
Subject: script: add additional BOE panel
Some BOE units have panels with different product codes but
identical model names.
---
scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua | 1 +
1 file changed, 1 insertion(+)
diff --git a/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua b/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
index 6263478c0517..32f776c17f3d 100644
--- a/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
+++ b/scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua
@@ -43,6 +43,7 @@ gamescope.config.known_displays.legiongos_lcd = {
local lcd_types = {
{ vendor = "CSW", model = "PN8007QB1-1", product = 0x0800 },
{ vendor = "BOE", model = "NS080WUM-LX1", product = 0x0C00 },
+ { vendor = "BOE", model = "NS080WUM-LX1", product = 0x0CFF },
}
for index,value in ipairs(lcd_types) do
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Attila Fidan <dev@print0.net>
Date: Wed, 29 Jan 2025 07:51:13 +0000
Subject: WaylandBackend: Don't assert on non-xkb-v1 keymaps
Long story short, there are some edge cases where sway may send
no_keymap to clients when a virtual keyboard is created on the seat,
in specific circumstances. It will later send the xkb keymap before any
key events are sent. Other clients simply ignore non-xkb-v1 keymaps (or
the lack of a keymap), they don't assert. So gamescope should do the
same.
---
src/Backends/WaylandBackend.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index 22f8ee5c8fac..3207a6b9f7d4 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -2748,7 +2748,8 @@ namespace gamescope
// Ideally we'd use this to influence our keymap to clients, eg. x server.
defer( close( nFd ) );
- assert( uFormat == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 );
+ if ( uFormat != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 )
+ return;
char *pMap = (char *)mmap( nullptr, uSize, PROT_READ, MAP_PRIVATE, nFd, 0 );
if ( !pMap || pMap == MAP_FAILED )
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: M Stoeckl <code@mstoeckl.com>
Date: Tue, 21 Jan 2025 16:08:47 -0500
Subject: main: Give error message on invalid integer or float argument
---
src/main.cpp | 54 +++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 41 insertions(+), 13 deletions(-)
diff --git a/src/main.cpp b/src/main.cpp
index cd251af559e1..58bede8582fd 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -425,6 +425,34 @@ static enum gamescope::GamescopeBackend parse_backend_name(const char *str)
}
}
+static int parse_integer(const char *str, const char *optionName)
+{
+ auto result = gamescope::Parse<int>(str);
+ if ( result.has_value() )
+ {
+ return result.value();
+ }
+ else
+ {
+ fprintf( stderr, "gamescope: invalid value for --%s, \"%s\" is either not an integer or is far too large\n", optionName, str );
+ exit(1);
+ }
+}
+
+static float parse_float(const char *str, const char *optionName)
+{
+ auto result = gamescope::Parse<float>(str);
+ if ( result.has_value() )
+ {
+ return result.value();
+ }
+ else
+ {
+ fprintf( stderr, "gamescope: invalid value for --%s, \"%s\" could not be interpreted as a real number\n", optionName, str );
+ exit(1);
+ }
+}
+
struct sigaction handle_signal_action = {};
void ShutdownGamescope()
@@ -677,25 +705,25 @@ int main(int argc, char **argv)
const char *opt_name;
switch (o) {
case 'w':
- g_nNestedWidth = atoi( optarg );
+ g_nNestedWidth = parse_integer( optarg, "nested-width" );
break;
case 'h':
- g_nNestedHeight = atoi( optarg );
+ g_nNestedHeight = parse_integer( optarg, "nested-height" );
break;
case 'r':
- g_nNestedRefresh = gamescope::ConvertHztomHz( atoi( optarg ) );
+ g_nNestedRefresh = gamescope::ConvertHztomHz( parse_integer( optarg, "nested-refresh" ) );
break;
case 'W':
- g_nPreferredOutputWidth = atoi( optarg );
+ g_nPreferredOutputWidth = parse_integer( optarg, "output-width" );
break;
case 'H':
- g_nPreferredOutputHeight = atoi( optarg );
+ g_nPreferredOutputHeight = parse_integer( optarg, "output-height" );
break;
case 'o':
- g_nNestedUnfocusedRefresh = gamescope::ConvertHztomHz( atoi( optarg ) );
+ g_nNestedUnfocusedRefresh = gamescope::ConvertHztomHz( parse_integer( optarg, "nested-unfocused-refresh" ) );
break;
case 'm':
- g_flMaxWindowScale = atof( optarg );
+ g_flMaxWindowScale = parse_float( optarg, "max-scale" );
break;
case 'S':
g_wantedUpscaleScaler = parse_upscaler_scaler(optarg);
@@ -716,7 +744,7 @@ int main(int argc, char **argv)
g_bGrabbed = true;
break;
case 's':
- g_mouseSensitivity = atof( optarg );
+ g_mouseSensitivity = parse_float( optarg, "mouse-sensitivity" );
break;
case 'e':
steamMode = true;
@@ -734,21 +762,21 @@ int main(int argc, char **argv)
} else if (strcmp(opt_name, "disable-color-management") == 0) {
g_bForceDisableColorMgmt = true;
} else if (strcmp(opt_name, "xwayland-count") == 0) {
- g_nXWaylandCount = atoi( optarg );
+ g_nXWaylandCount = parse_integer( optarg, opt_name );
} else if (strcmp(opt_name, "composite-debug") == 0) {
cv_composite_debug |= CompositeDebugFlag::Markers;
cv_composite_debug |= CompositeDebugFlag::PlaneBorders;
} else if (strcmp(opt_name, "hdr-debug-heatmap") == 0) {
cv_composite_debug |= CompositeDebugFlag::Heatmap;
} else if (strcmp(opt_name, "default-touch-mode") == 0) {
- gamescope::cv_touch_click_mode = (gamescope::TouchClickMode) atoi( optarg );
+ gamescope::cv_touch_click_mode = (gamescope::TouchClickMode) parse_integer( optarg, opt_name );
} else if (strcmp(opt_name, "generate-drm-mode") == 0) {
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, "sharpness") == 0 ||
strcmp(opt_name, "fsr-sharpness") == 0) {
- g_upscaleFilterSharpness = atoi( optarg );
+ g_upscaleFilterSharpness = parse_integer( optarg, opt_name );
} else if (strcmp(opt_name, "rt") == 0) {
g_bRt = true;
} else if (strcmp(opt_name, "prefer-vk-device") == 0) {
@@ -762,7 +790,7 @@ int main(int argc, char **argv)
} else if (strcmp(opt_name, "force-grab-cursor") == 0) {
g_bForceRelativeMouse = true;
} else if (strcmp(opt_name, "display-index") == 0) {
- g_nNestedDisplayIndex = atoi( optarg );
+ g_nNestedDisplayIndex = parse_integer( optarg, opt_name );
} else if (strcmp(opt_name, "adaptive-sync") == 0) {
cv_adaptive_sync = true;
} else if (strcmp(opt_name, "expose-wayland") == 0) {
@@ -770,7 +798,7 @@ int main(int argc, char **argv)
} else if (strcmp(opt_name, "backend") == 0) {
eCurrentBackend = parse_backend_name( optarg );
} else if (strcmp(opt_name, "cursor-scale-height") == 0) {
- g_nCursorScaleHeight = atoi(optarg);
+ g_nCursorScaleHeight = parse_integer(optarg, opt_name);
} else if (strcmp(opt_name, "mangoapp") == 0) {
g_bLaunchMangoapp = true;
}
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Joshua Ashton <joshua@froggi.es>
Date: Sat, 7 Sep 2024 22:22:22 +0100
Subject: protocol: Add gamescope-action-binding protocol
---
protocol/gamescope-action-binding.xml | 85 ++++++++++
protocol/meson.build | 1 +
src/Apps/gamescope_hotkey_example.cpp | 179 +++++++++++++++++++++
src/WaylandServer/GamescopeActionBinding.h | 163 +++++++++++++++++++
src/WaylandServer/WaylandDecls.h | 3 +
src/meson.build | 2 +
src/wlserver.cpp | 58 ++++++-
7 files changed, 489 insertions(+), 2 deletions(-)
create mode 100644 protocol/gamescope-action-binding.xml
create mode 100644 src/Apps/gamescope_hotkey_example.cpp
create mode 100644 src/WaylandServer/GamescopeActionBinding.h
diff --git a/protocol/gamescope-action-binding.xml b/protocol/gamescope-action-binding.xml
new file mode 100644
index 000000000000..2164cb87ad84
--- /dev/null
+++ b/protocol/gamescope-action-binding.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="gamescope_action_binding">
+
+ <copyright>
+ Copyright © 2024 Valve Corporation
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <description summary="gamescope-specific protocol">
+ This is a private Gamescope protocol. Regular Wayland clients must not use
+ it.
+ </description>
+
+ <interface name="gamescope_action_binding_manager" version="1">
+ <request name="destroy" type="destructor"></request>
+
+ <request name="create_action_binding">
+ <arg name="callback" type="new_id" interface="gamescope_action_binding" summary="new action binding object"/>
+ </request>
+ </interface>
+
+ <interface name="gamescope_action_binding" version="1">
+ <request name="destroy" type="destructor"></request>
+
+ <enum name="arm_flag" bitfield="true">
+ <description summary="arm flags">
+ Flags that control how the action is armed.
+ </description>
+ <entry name="one_shot" value="0x1" summary="disarm this action immediately after trigger. unrelated to niko"/>
+ <entry name="no_block" value="0x2" summary="don't block the result of this shortcut being seen by the app and keep processing hotkeys"/>
+ </enum>
+
+ <enum name="trigger_flag" bitfield="true">
+ <description summary="arm flags">
+ Flags that say how the action was triggered.
+ </description>
+
+ <entry name="keyboard" value="0x1" summary="action was triggered by keyboard trigger"/>
+ </enum>
+
+ <request name="set_description">
+ <arg name="description" type="string" summary="human-readable description as to what the action is for, used for debugging purposes."/>
+ </request>
+
+ <request name="add_keyboard_trigger">
+ <arg name="keysyms" type="array" summary="array of xkb_keysym_t's"/>
+ </request>
+
+ <request name="clear_triggers">
+ </request>
+
+ <request name="arm">
+ <arg name="arm_flags" type="uint" enum="arm_flag" summary="combination of 'arm_flag' values"/>
+ </request>
+
+ <request name="disarm">
+ </request>
+
+ <event name="triggered">
+ <arg name="sequence" type="uint" summary="global sequence no of actions that have been trigged"/>
+ <arg name="time_lo" type="uint" summary="lower bits of 64-bit timestamp in nanos (CLOCK_MONOTONIC)"/>
+ <arg name="time_hi" type="uint" summary="upper bits of 64-bit timestamp in nanos (CLOCK_MONOTONIC)"/>
+ <arg name="trigger_flags" type="uint" enum="trigger_flag" summary="flags for this trigger"/>
+ </event>
+ </interface>
+
+</protocol>
diff --git a/protocol/meson.build b/protocol/meson.build
index dbce92edce52..9f75f188af52 100644
--- a/protocol/meson.build
+++ b/protocol/meson.build
@@ -36,6 +36,7 @@ protocols = [
'gamescope-reshade.xml',
'gamescope-swapchain.xml',
'gamescope-private.xml',
+ 'gamescope-action-binding.xml',
# wlroots protocols
'wlr-layer-shell-unstable-v1.xml',
diff --git a/src/Apps/gamescope_hotkey_example.cpp b/src/Apps/gamescope_hotkey_example.cpp
new file mode 100644
index 000000000000..ffd88bdfa2c8
--- /dev/null
+++ b/src/Apps/gamescope_hotkey_example.cpp
@@ -0,0 +1,179 @@
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <span>
+#include <optional>
+#include "convar.h"
+#include "Utils/Version.h"
+
+#include <span>
+
+#include <wayland-client.h>
+#include <gamescope-action-binding-client-protocol.h>
+
+// TODO: Consolidate
+#define WAYLAND_NULL() []<typename... Args> ( void *pData, Args... args ) { }
+#define WAYLAND_USERDATA_TO_THIS(type, name) []<typename... Args> ( void *pData, Args... args ) { type *pThing = (type *)pData; pThing->name( std::forward<Args>(args)... ); }
+
+namespace gamescope
+{
+ class CActionBinding
+ {
+ public:
+ bool Init( gamescope_action_binding_manager *pManager, std::span<uint32_t> pKeySyms )
+ {
+ Shutdown();
+
+ m_pBinding = gamescope_action_binding_manager_create_action_binding( pManager );
+ if ( !m_pBinding )
+ return false;
+
+ wl_array array;
+ wl_array_init(&array);
+ for ( uint32_t uKeySym : pKeySyms )
+ {
+ uint32_t *pKeySymPtr = (uint32_t *)wl_array_add(&array, sizeof(uint32_t) );
+ *pKeySymPtr = uKeySym;
+ }
+
+ gamescope_action_binding_add_listener( m_pBinding, &s_BindingListener, (void *)this );
+ gamescope_action_binding_add_keyboard_trigger( m_pBinding, &array );
+ gamescope_action_binding_set_description( m_pBinding, "My Example Hotkey :)" );
+ gamescope_action_binding_arm( m_pBinding, 0 );
+
+ return true;
+ }
+
+ void Shutdown()
+ {
+ if ( m_pBinding )
+ {
+ gamescope_action_binding_destroy( m_pBinding );
+ m_pBinding = nullptr;
+ }
+ }
+
+ void Wayland_Triggered( gamescope_action_binding *pBinding, uint32_t uSequence, uint32_t uTriggerFlags, uint32_t uTimeLo, uint32_t uTimeHi )
+ {
+ fprintf( stderr, "Hotkey pressed!" );
+ }
+
+ private:
+ gamescope_action_binding *m_pBinding = nullptr;
+
+ static const gamescope_action_binding_listener s_BindingListener;
+ };
+
+ const gamescope_action_binding_listener CActionBinding::s_BindingListener =
+ {
+ .triggered = WAYLAND_USERDATA_TO_THIS( CActionBinding, Wayland_Triggered ),
+ };
+
+ class GamescopeHotkeyExample
+ {
+ public:
+ GamescopeHotkeyExample();
+ ~GamescopeHotkeyExample();
+
+ bool Init();
+ void Run();
+ private:
+ wl_display *m_pDisplay = nullptr;
+ gamescope_action_binding_manager *m_pActionBindingManager = nullptr;
+
+ void Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion );
+ static const wl_registry_listener s_RegistryListener;
+ };
+
+ GamescopeHotkeyExample::GamescopeHotkeyExample()
+ {
+ }
+
+ GamescopeHotkeyExample::~GamescopeHotkeyExample()
+ {
+ }
+
+ bool GamescopeHotkeyExample::Init()
+ {
+ const char *pDisplayName = getenv( "GAMESCOPE_WAYLAND_DISPLAY" );
+ if ( !pDisplayName || !*pDisplayName )
+ pDisplayName = "gamescope-0";
+
+ if ( !( m_pDisplay = wl_display_connect( pDisplayName ) ) )
+ {
+ fprintf( stderr, "Failed to open GAMESCOPE_WAYLAND_DISPLAY.\n" );
+ return false;
+ }
+
+ {
+ wl_registry *pRegistry;
+ if ( !( pRegistry = wl_display_get_registry( m_pDisplay ) ) )
+ {
+ fprintf( stderr, "Failed to get wl_registry.\n" );
+ return false;
+ }
+
+ wl_registry_add_listener( pRegistry, &s_RegistryListener, (void *)this );
+ wl_display_roundtrip( m_pDisplay );
+ wl_display_roundtrip( m_pDisplay );
+
+ if ( !m_pActionBindingManager )
+ {
+ fprintf( stderr, "Failed to get Gamescope binding manager\n" );
+ return false;
+ }
+
+ wl_registry_destroy( pRegistry );
+ }
+
+ return true;
+ }
+
+ void GamescopeHotkeyExample::Run()
+ {
+ // Add a test hotkey of Shift + P.
+ std::vector<uint32_t> uKeySyms = { 0xffe1, 0x0070 }; // XKB_KEY_Shift_L + XKB_KEY_p
+
+ CActionBinding binding;
+ if ( !binding.Init( m_pActionBindingManager, uKeySyms ) )
+ return;
+
+ wl_display_flush( m_pDisplay );
+
+ for ( ;; )
+ {
+ wl_display_dispatch( m_pDisplay );
+ }
+ }
+
+ void GamescopeHotkeyExample::Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion )
+ {
+ if ( !strcmp( pInterface, gamescope_action_binding_manager_interface.name ) )
+ {
+ m_pActionBindingManager = (decltype(m_pActionBindingManager)) wl_registry_bind( pRegistry, uName, &gamescope_action_binding_manager_interface, uVersion );
+ }
+ }
+
+ const wl_registry_listener GamescopeHotkeyExample::s_RegistryListener =
+ {
+ .global = WAYLAND_USERDATA_TO_THIS( GamescopeHotkeyExample, Wayland_Registry_Global ),
+ .global_remove = WAYLAND_NULL(),
+ };
+
+ static int RunHotkeyExample( int argc, char *argv[] )
+ {
+ gamescope::GamescopeHotkeyExample hotkeyExample;
+ if ( !hotkeyExample.Init() )
+ return 1;
+
+ hotkeyExample.Run();
+
+ return 0;
+ }
+}
+
+int main( int argc, char *argv[] )
+{
+ return gamescope::RunHotkeyExample( argc, argv );
+}
diff --git a/src/WaylandServer/GamescopeActionBinding.h b/src/WaylandServer/GamescopeActionBinding.h
new file mode 100644
index 000000000000..81aab05dcd22
--- /dev/null
+++ b/src/WaylandServer/GamescopeActionBinding.h
@@ -0,0 +1,163 @@
+#pragma once
+
+#include "WaylandProtocol.h"
+
+#include "gamescope-action-binding-protocol.h"
+
+#include <string>
+#include <span>
+#include <unordered_set>
+
+#include "convar.h"
+#include "Utils/Algorithm.h"
+
+#include "wlr_begin.hpp"
+#include <xkbcommon/xkbcommon.h>
+#include <wlr/types/wlr_keyboard.h>
+#include "wlr_end.hpp"
+
+using namespace std::literals;
+
+uint64_t get_time_in_nanos();
+
+namespace gamescope::WaylandServer
+{
+ struct Keybind_t
+ {
+ std::unordered_set<xkb_keysym_t> setKeySyms;
+ };
+
+ ///////////////////////////
+ // CGamescopeActionBinding
+ ///////////////////////////
+ class CGamescopeActionBinding : public CWaylandResource
+ {
+ public:
+ WL_PROTO_DEFINE( gamescope_action_binding, 1 );
+
+ CGamescopeActionBinding( WaylandResourceDesc_t desc )
+ : CWaylandResource( desc )
+ {
+ s_Bindings.push_back( this );
+ }
+
+ ~CGamescopeActionBinding()
+ {
+ std::erase_if( s_Bindings, [this]( CGamescopeActionBinding *pBinding ){ return pBinding == this; } );
+ }
+
+ // gamescope_action_binding
+
+ void SetDescription( const char *pszDescription )
+ {
+ m_sDescription = pszDescription;
+ }
+
+ void AddKeyboardTrigger( wl_array *pKeysymsArray )
+ {
+ size_t zKeysymCount = pKeysymsArray->size / sizeof( xkb_keysym_t );
+
+ std::span<const xkb_keysym_t> pKeysyms = std::span<const xkb_keysym_t> {
+ reinterpret_cast<const xkb_keysym_t *>( pKeysymsArray->data ),
+ zKeysymCount };
+
+ std::unordered_set<xkb_keysym_t> setKeySyms;
+ for ( xkb_keysym_t uKeySym : pKeysyms )
+ {
+ setKeySyms.emplace( uKeySym );
+ }
+
+ m_KeyboardTriggers.emplace_back( std::move( setKeySyms ) );
+ }
+
+ void ClearTriggers()
+ {
+ m_KeyboardTriggers.clear();
+ }
+
+ void Arm( uint32_t uArmFlags )
+ {
+ m_ouArmFlags = uArmFlags;
+ }
+
+ void Disarm()
+ {
+ m_ouArmFlags = std::nullopt;
+ }
+
+ //
+
+ bool IsArmed() { return m_ouArmFlags != std::nullopt; }
+ std::span<Keybind_t> GetKeyboardTriggers() { return m_KeyboardTriggers; }
+
+ bool Execute()
+ {
+ if ( !IsArmed() )
+ return false;
+
+ uint32_t uArmFlags = *m_ouArmFlags;
+ bool bBlockInput = !!( uArmFlags & GAMESCOPE_ACTION_BINDING_ARM_FLAG_NO_BLOCK );
+
+ uint32_t uTriggerFlags = GAMESCOPE_ACTION_BINDING_TRIGGER_FLAG_KEYBOARD;
+
+ uint64_t ulNow = get_time_in_nanos();
+
+ static uint32_t s_uSequence = 0;
+ uint32_t uTimeLo = static_cast<uint32_t>( ulNow & 0xffffffff );
+ uint32_t uTimeHi = static_cast<uint32_t>( ulNow >> 32 );
+ gamescope_action_binding_send_triggered( GetResource(), s_uSequence++, uTriggerFlags, uTimeLo, uTimeHi );
+
+ if ( uArmFlags & GAMESCOPE_ACTION_BINDING_ARM_FLAG_ONE_SHOT )
+ Disarm();
+
+ return bBlockInput;
+ }
+
+ static std::span<CGamescopeActionBinding *> GetBindings()
+ {
+ return s_Bindings;
+ }
+
+ private:
+ std::string m_sDescription;
+ std::vector<Keybind_t> m_KeyboardTriggers;
+
+ std::optional<uint32_t> m_ouArmFlags;
+
+ static std::vector<CGamescopeActionBinding *> s_Bindings;
+ };
+
+ const struct gamescope_action_binding_interface CGamescopeActionBinding::Implementation =
+ {
+ .destroy = WL_PROTO_DESTROY(),
+ .set_description = WL_PROTO( CGamescopeActionBinding, SetDescription ),
+ .add_keyboard_trigger = WL_PROTO( CGamescopeActionBinding, AddKeyboardTrigger ),
+ .clear_triggers = WL_PROTO( CGamescopeActionBinding, ClearTriggers ),
+ .arm = WL_PROTO( CGamescopeActionBinding, Arm ),
+ .disarm = WL_PROTO( CGamescopeActionBinding, Disarm ),
+ };
+
+ std::vector<CGamescopeActionBinding *> CGamescopeActionBinding::s_Bindings;
+
+ //////////////////////////////////
+ // CGamescopeActionBindingManager
+ //////////////////////////////////
+ class CGamescopeActionBindingManager : public CWaylandResource
+ {
+ public:
+ WL_PROTO_DEFINE( gamescope_action_binding_manager, 1 );
+ WL_PROTO_DEFAULT_CONSTRUCTOR();
+
+ void CreateActionBinding( uint32_t uId )
+ {
+ CWaylandResource::Create<CGamescopeActionBinding>( m_pClient, m_uVersion, uId );
+ }
+ };
+
+ const struct gamescope_action_binding_manager_interface CGamescopeActionBindingManager::Implementation =
+ {
+ .destroy = WL_PROTO_DESTROY(),
+ .create_action_binding = WL_PROTO( CGamescopeActionBindingManager, CreateActionBinding ),
+ };
+
+}
diff --git a/src/WaylandServer/WaylandDecls.h b/src/WaylandServer/WaylandDecls.h
index e8fd9343192b..e43623aa6a74 100644
--- a/src/WaylandServer/WaylandDecls.h
+++ b/src/WaylandServer/WaylandDecls.h
@@ -15,4 +15,7 @@ namespace gamescope::WaylandServer
class CReshadeManager;
using CReshade = CWaylandProtocol<CReshadeManager>;
+ class CGamescopeActionBindingManager;
+ using CGamescopeActionBindingProtocol = CWaylandProtocol<CGamescopeActionBindingManager>;
+
}
diff --git a/src/meson.build b/src/meson.build
index f35f7ef0cb94..842768ce7ce4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -222,3 +222,5 @@ executable('gamescope_color_microbench', ['color_bench.cpp', 'color_helpers.cpp'
executable('gamescope_color_tests', ['color_tests.cpp', 'color_helpers.cpp'], gamescope_core_src, gamescope_version, dependencies:[glm_dep])
executable('gamescopectl', ['Apps/gamescopectl.cpp'], gamescope_core_src, gamescope_version, protocols_client_src, dependencies: [dep_wayland], install:true )
+
+executable('gamescope_hotkey_example', ['Apps/gamescope_hotkey_example.cpp'], gamescope_core_src, gamescope_version, protocols_client_src, dependencies: [dep_wayland, xkbcommon], install: false )
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index 4ce9511352d0..ffaf7aff7343 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -20,6 +20,7 @@
#include "WaylandServer/WaylandProtocol.h"
#include "WaylandServer/LinuxDrmSyncobj.h"
#include "WaylandServer/Reshade.h"
+#include "WaylandServer/GamescopeActionBinding.h"
#include "wlr_begin.hpp"
#include <wlr/backend.h>
@@ -108,6 +109,7 @@ static void wlserver_update_cursor_constraint();
static void handle_pointer_constraint(struct wl_listener *listener, void *data);
static void wlserver_constrain_cursor( struct wlr_pointer_constraint_v1 *pNewConstraint );
struct wlr_surface *wlserver_surface_to_main_surface( struct wlr_surface *pSurface );
+void wlserver_process_hotkeys( wlr_keyboard *keyboard, uint32_t key, bool press );
std::vector<ResListEntry_t>& gamescope_xwayland_server_t::retrieve_commits()
{
@@ -306,6 +308,9 @@ static void wlserver_handle_key(struct wl_listener *listener, void *data)
}
#endif
+ // TODO: Remove the below hack when Steam is shipping
+ // `gamescope_action_binding_manager` in Steam Stable
+ // as it can just use a keybind to grab these always.
bool forbidden_key =
keysym == XKB_KEY_XF86AudioLowerVolume ||
keysym == XKB_KEY_XF86AudioRaiseVolume ||
@@ -324,6 +329,8 @@ static void wlserver_handle_key(struct wl_listener *listener, void *data)
return;
}
}
+
+ wlserver_process_hotkeys( keyboard->wlr, event->state == WL_KEYBOARD_KEY_STATE_PRESSED, event->time_msec );
wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard->wlr );
wlr_seat_keyboard_notify_key( wlserver.wlr.seat, event->time_msec, event->keycode, event->state );
@@ -1748,6 +1755,8 @@ bool wlserver_init( void ) {
create_reshade();
+ new gamescope::WaylandServer::CGamescopeActionBindingProtocol( wlserver.display );
+
create_gamescope_xwayland();
create_gamescope_swapchain_factory_v2();
@@ -2038,12 +2047,57 @@ void wlserver_keyboardfocus( struct wlr_surface *surface, bool bConstrain )
}
}
+void wlserver_process_hotkeys( wlr_keyboard *keyboard, uint32_t key, bool press )
+{
+ xkb_keycode_t keycode = key + 8;
+ xkb_keysym_t keysym = xkb_state_key_get_one_sym( keyboard->xkb_state, keycode );
+
+ static std::unordered_set<xkb_keysym_t> s_setPressedKeySyms;
+ if ( press )
+ {
+ s_setPressedKeySyms.emplace( keysym );
+ }
+ else
+ {
+ s_setPressedKeySyms.erase( keysym );
+ }
+
+ {
+ using namespace gamescope::WaylandServer;
+
+ std::span<CGamescopeActionBinding *> ppBindings = CGamescopeActionBinding::GetBindings();
+
+ for ( CGamescopeActionBinding *pBinding : ppBindings )
+ {
+ if ( !pBinding->IsArmed() )
+ continue;
+
+ std::span<Keybind_t> pKeybinds = pBinding->GetKeyboardTriggers();
+ for ( const Keybind_t &keybind : pKeybinds )
+ {
+ if ( !pBinding->IsArmed() )
+ break;
+
+ if ( s_setPressedKeySyms != keybind.setKeySyms )
+ continue;
+
+ if ( pBinding->Execute() )
+ return;
+ }
+ }
+ }
+}
+
void wlserver_key( uint32_t key, bool press, uint32_t time )
{
assert( wlserver_is_lock_held() );
- assert( wlserver.wlr.virtual_keyboard_device != nullptr );
- wlr_seat_set_keyboard( wlserver.wlr.seat, wlserver.wlr.virtual_keyboard_device );
+ wlr_keyboard *keyboard = wlserver.wlr.virtual_keyboard_device;
+
+ wlserver_process_hotkeys( keyboard, key, press );
+
+ assert( keyboard != nullptr );
+ wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard );
wlr_seat_keyboard_notify_key( wlserver.wlr.seat, time, key, press );
bump_input_counter();
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samuel Dionne-Riel <samuel@dionne-riel.com>
Date: Mon, 14 Oct 2024 20:50:45 -0400
Subject: wlserver: Re-hook pausing on session pause
During the refactor in 88eb1b477d8b1efbe6d7087dcde74052dad84049, the
handle_session_active function lost the ultimate role of *causing a
pause* when the session became inactive.
The duty of pausing the session was given the `DirtyState` function on
the backend, which now uses the same "moral" condition to set the paused
state (`g_DRM.paused = !wlsession_active();`)...
... except that now `DirtyState` state is only called when the session
is resumed. In turn, this means that on session suspend, nothing ends-up
pausing the DRM backend anymore!
This change unconditionally calls `DirtyState`, which in turn does the
accounting for pausing the backend. Actually, it conditionally passes
`false` to the argument to force nothing.
This fixes what ends-up causing `drmModeAtomicCommit: Permission denied`
when moving to another VT from gamescope's.
---
src/wlserver.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index ffaf7aff7343..bb87703162be 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -1347,8 +1347,7 @@ bool wlsession_active()
static void handle_session_active( struct wl_listener *listener, void *data )
{
- if (wlserver.wlr.session->active)
- GetBackend()->DirtyState( true, true );
+ GetBackend()->DirtyState( wlserver.wlr.session->active, wlserver.wlr.session->active );
wl_log.infof( "Session %s", wlserver.wlr.session->active ? "resumed" : "paused" );
}
#endif
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Autumn Ashton <misyl@froggi.es>
Date: Sat, 1 Mar 2025 22:35:56 +0000
Subject: steamcompmgr: Fix icon/title being spam set
Overlays should not get an appid, that's just for focus id logic.
---
src/steamcompmgr.cpp | 23 ++++++++++++++++++++---
src/steamcompmgr_shared.hpp | 6 ++++++
2 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index e40f95715746..f718832ea5c2 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -3961,8 +3961,10 @@ determine_and_apply_focus()
if ( global_focus.focusWindow )
{
GetBackend()->GetNestedHints()->SetVisible( true );
- GetBackend()->GetNestedHints()->SetTitle( global_focus.focusWindow->title );
- GetBackend()->GetNestedHints()->SetIcon( global_focus.focusWindow->icon );
+ if ( global_focus.focusWindow != previous_focus.focusWindow ) {
+ GetBackend()->GetNestedHints()->SetTitle( global_focus.focusWindow->title );
+ GetBackend()->GetNestedHints()->SetIcon( global_focus.focusWindow->icon );
+ }
}
else
{
@@ -4200,9 +4202,15 @@ map_win(xwayland_ctx_t* ctx, Window id, unsigned long sequence)
{
w->appID = w->xwayland().id;
}
+
w->isOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.overlayAtom, 0);
w->isExternalOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.externalOverlayAtom, 0);
+ // misyl: Disable appID for overlay types, as parts of the code don't expect that focus-wise.
+ // Fixes mangoapp usage when nested, and not in SteamOS.
+ if ( w->IsAnyOverlay() )
+ w->appID = 0;
+
get_size_hints(ctx, w);
get_net_wm_state(ctx, w);
@@ -4462,6 +4470,9 @@ add_win(xwayland_ctx_t *ctx, Window id, Window prev, unsigned long sequence)
new_win->appID = id;
}
+ if ( new_win->IsAnyOverlay() )
+ new_win->appID = 0;
+
Window transientFor = None;
if ( XGetTransientForHint( ctx->dpy, id, &transientFor ) )
{
@@ -4693,7 +4704,7 @@ damage_win(xwayland_ctx_t *ctx, XDamageNotifyEvent *de)
if (!w)
return;
- if ((w->isOverlay || w->isExternalOverlay) && !w->opacity)
+ if (w->IsAnyOverlay() && !w->opacity)
return;
// First damage event we get, compute focus; we only want to focus damaged
@@ -5295,6 +5306,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
xwm_log.errorf( "appid clash was %u now %u", w->appID, appID );
}
w->appID = appID;
+ if ( w->IsAnyOverlay() )
+ w->appID = 0;
MakeFocusDirty();
}
@@ -5305,6 +5318,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
if (w)
{
w->isOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.overlayAtom, 0);
+ if ( w->IsAnyOverlay() )
+ w->appID = 0;
MakeFocusDirty();
}
}
@@ -5314,6 +5329,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
if (w)
{
w->isExternalOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.externalOverlayAtom, 0);
+ if ( w->IsAnyOverlay() )
+ w->appID = 0;
MakeFocusDirty();
}
}
diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp
index f300eb94d954..989d09d50c4a 100644
--- a/src/steamcompmgr_shared.hpp
+++ b/src/steamcompmgr_shared.hpp
@@ -116,6 +116,12 @@ struct steamcompmgr_win_t {
uint32_t appID = 0;
bool isOverlay = false;
bool isExternalOverlay = false;
+
+ bool IsAnyOverlay() const
+ {
+ return isOverlay || isExternalOverlay;
+ }
+
bool isFullscreen = false;
bool isSysTrayIcon = false;
bool sizeHintsSpecified = false;
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Autumn Ashton <misyl@froggi.es>
Date: Sun, 2 Mar 2025 00:36:38 +0000
Subject: steamcompmgr: Fix Steam sidebars with recent icon fix
---
src/steamcompmgr.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index f718832ea5c2..e46c34bbfbc0 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -4208,7 +4208,7 @@ map_win(xwayland_ctx_t* ctx, Window id, unsigned long sequence)
// misyl: Disable appID for overlay types, as parts of the code don't expect that focus-wise.
// Fixes mangoapp usage when nested, and not in SteamOS.
- if ( w->IsAnyOverlay() )
+ if ( w->isExternalOverlay )
w->appID = 0;
get_size_hints(ctx, w);
@@ -4470,7 +4470,7 @@ add_win(xwayland_ctx_t *ctx, Window id, Window prev, unsigned long sequence)
new_win->appID = id;
}
- if ( new_win->IsAnyOverlay() )
+ if ( new_win->isExternalOverlay )
new_win->appID = 0;
Window transientFor = None;
@@ -5306,7 +5306,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
xwm_log.errorf( "appid clash was %u now %u", w->appID, appID );
}
w->appID = appID;
- if ( w->IsAnyOverlay() )
+ if ( w->isExternalOverlay )
w->appID = 0;
MakeFocusDirty();
@@ -5318,7 +5318,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
if (w)
{
w->isOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.overlayAtom, 0);
- if ( w->IsAnyOverlay() )
+ if ( w->isExternalOverlay )
w->appID = 0;
MakeFocusDirty();
}
@@ -5329,7 +5329,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
if (w)
{
w->isExternalOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.externalOverlayAtom, 0);
- if ( w->IsAnyOverlay() )
+ if ( w->isExternalOverlay )
w->appID = 0;
MakeFocusDirty();
}
--
2.50.1
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.50.1
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 06ebbe7255d4..ffce5d7d8448 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2253,8 +2253,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 )
+ else if ( GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL )
{
const drmModeModeInfo *pPreferredMode = find_mode( m_pConnector.get(), 0, 0, 0 );
diff --git a/src/main.cpp b/src/main.cpp
index 58bede8582fd..1443f49b51e9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -129,6 +129,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 },
@@ -202,6 +203,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"
@@ -453,6 +455,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()
@@ -774,6 +803,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.50.1
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 | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index bb87703162be..8b58050fd6d7 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -292,6 +292,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 wlserver_keyboard *keyboard = wl_container_of( listener, keyboard, key );
@@ -315,7 +318,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->wlr->modifiers.depressed & WLR_MODIFIER_CTRL)) ||
+ (keyboard->wlr->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;
@@ -323,9 +333,22 @@ static void wlserver_handle_key(struct wl_listener *listener, void *data)
if ( new_kb_surf )
{
wlserver_keyboardfocus( new_kb_surf, false );
- wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard->wlr );
+ wlr_seat_set_keyboard( wlserver.wlr.seat, wlserver.wlr.virtual_keyboard_device );
+
+ 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 );
+ wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard->wlr );
+
return;
}
}
--
2.50.1
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 8b58050fd6d7..0b99c498e55d 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -2552,8 +2552,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.50.1
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 ffce5d7d8448..c4f358fbc933 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2169,7 +2169,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.50.1
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 | 44 ++++++++++++++++++++++++++++++++++----------
src/xwayland_ctx.hpp | 2 ++
2 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index e46c34bbfbc0..3b3a106b13e9 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -165,6 +165,8 @@ uint32_t g_reshade_technique_idx = 0;
bool g_bSteamIsActiveWindow = false;
bool g_bForceInternal = false;
+bool b_bForceFrameLimit = false;
+bool g_bRefreshHalveEnable = false;
static std::vector< steamcompmgr_win_t* > GetGlobalPossibleFocusWindows();
static bool
@@ -791,6 +793,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.
@@ -810,7 +813,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 )
{
@@ -819,7 +822,7 @@ static void _update_app_target_refresh_cycle()
if ( g_nCombinedAppRefreshCycleChangeFPS[ type ] )
{
- g_nSteamCompMgrTargetFPS = target_fps;
+ g_nSteamCompMgrTargetFPSreq = target_fps;
}
if ( g_nCombinedAppRefreshCycleChangeRefresh[ type ] )
@@ -840,9 +843,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();
}
@@ -5052,7 +5055,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 );
}
@@ -5115,7 +5118,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;
@@ -5133,7 +5136,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;
@@ -5516,7 +5519,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++)
@@ -5943,6 +5946,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
@@ -6300,7 +6307,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;
@@ -7124,6 +7131,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);
@@ -7542,7 +7551,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) {
@@ -7667,6 +7676,21 @@ steamcompmgr_main(int argc, char **argv)
// as a question.
const bool bIsVBlankFromTimer = vblank;
+ if ( g_bRefreshHalveEnable && window_is_steam( global_focus.focusWindow ) ) {
+ // Halve refresh rate on SteamUI
+ 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()->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.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Wed, 13 Nov 2024 17:22:05 +0100
Subject: feat: add DPMS support through an Atom
---
src/Backends/DRMBackend.cpp | 16 +++++++++++++---
src/rendervulkan.hpp | 2 ++
src/steamcompmgr.cpp | 15 ++++++++++++---
src/xwayland_ctx.hpp | 1 +
4 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index c4f358fbc933..856b03fc3747 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2702,6 +2702,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;
@@ -2757,7 +2760,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI
uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK;
// We do internal refcounting with these events
- if ( drm->pCRTC != nullptr )
+ if ( !frameInfo->dpms && drm->pCRTC != nullptr)
flags |= DRM_MODE_PAGE_FLIP_EVENT;
if ( async || g_bForceAsyncFlips )
@@ -2830,7 +2833,13 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI
if ( drm->pCRTC )
{
- 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 )
@@ -2861,7 +2870,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI
drm->flags = flags;
int ret;
- if ( drm->pCRTC == nullptr ) {
+ if (frameInfo->dpms || drm->pCRTC == nullptr ) {
ret = 0;
} else if ( drm->bUseLiftoff ) {
ret = drm_prepare_liftoff( drm, frameInfo, needs_modeset );
@@ -3424,6 +3433,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 a3a11a7ba96f..0f8cba8516c0 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -281,6 +281,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 3b3a106b13e9..fcdc9ac1559f 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -167,6 +167,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;
static std::vector< steamcompmgr_win_t* > GetGlobalPossibleFocusWindows();
static bool
@@ -2254,7 +2256,7 @@ bool ShouldDrawCursor()
}
static void
-paint_all(bool async)
+paint_all( bool async, bool dpms )
{
gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0);
xwayland_ctx_t *root_ctx = root_server->ctx.get();
@@ -2305,6 +2307,7 @@ paint_all(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
@@ -5950,6 +5953,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
@@ -7132,6 +7139,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);
@@ -8127,9 +8135,10 @@ steamcompmgr_main(int argc, char **argv)
bShouldPaint = true;
}
- if ( bShouldPaint )
+ if ( bShouldPaint || (g_bDPMS != g_bDPMS_set && vblank) )
{
- paint_all( eFlipType == FlipType::Async );
+ g_bDPMS_set = g_bDPMS;
+ paint_all( eFlipType == FlipType::Async, g_bDPMS );
hasRepaint = false;
hasRepaintNonBasePlane = false;
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.50.1
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 856b03fc3747..e1547f4a1a9b 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -1530,6 +1530,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 );
}
@@ -1541,6 +1545,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 );
}
}
@@ -1754,7 +1762,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;
@@ -2047,6 +2055,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;
@@ -3068,6 +3087,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:
@@ -3327,6 +3353,11 @@ namespace gamescope
bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap);
+ if (g_bEnableDRMRotationShader)
+ {
+ bNeedsFullComposite = true;
+ }
+
bool bDoComposite = true;
if ( !bNeedsFullComposite && !bWantsPartialComposite )
{
@@ -3417,7 +3448,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 1443f49b51e9..c96b8f0ac39e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -127,6 +127,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 },
@@ -190,6 +191,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"
@@ -348,6 +350,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)
{
@@ -803,6 +808,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 842768ce7ce4..0a0e958ba313 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -73,6 +73,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 b8412b8fdf2f..d833d0093830 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"
@@ -904,6 +905,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++)
@@ -1134,6 +1136,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) {
@@ -3229,8 +3232,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" );
@@ -3238,7 +3249,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" );
@@ -3246,7 +3257,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" );
@@ -3261,7 +3272,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" );
@@ -3269,7 +3280,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" );
@@ -3277,7 +3288,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" );
@@ -3407,6 +3418,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()
{
@@ -3873,7 +3906,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)
@@ -3948,7 +3981,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));
@@ -3991,7 +4032,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;
@@ -4029,7 +4078,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);
@@ -4039,14 +4096,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 0f8cba8516c0..911ca7ea208c 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -396,7 +396,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);
@@ -533,6 +533,9 @@ struct VulkanOutput_t
// NIS
gamescope::OwningRc<CVulkanTexture> nisScalerImage;
gamescope::OwningRc<CVulkanTexture> nisUsmImage;
+
+ // Rotated
+ gamescope::OwningRc<CVulkanTexture> rotatedOutput;
};
@@ -545,6 +548,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 0b99c498e55d..d3ae7beb7b1f 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -2579,6 +2579,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.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Thu, 13 Mar 2025 19:04:51 +0100
Subject: fix(hdr): remove PQ from internal panels and allow disabling for
externals
New steam update forces HDR mode. The cursed patching gamescope does is
not supported for all displays, especially internal ones. So disable by
default on internal panels and allow disabling on externals.
---
src/Backends/DRMBackend.cpp | 2 +-
src/main.cpp | 1 +
src/steamcompmgr.cpp | 3 +++
src/steamcompmgr.hpp | 1 +
4 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index e1547f4a1a9b..d7107325ad59 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2367,7 +2367,7 @@ namespace gamescope
}
}
- if ( pColorimetry && pColorimetry->bt2020_rgb &&
+ if ( g_bHDRPqEnable && GetScreenType() != GAMESCOPE_SCREEN_TYPE_INTERNAL && pColorimetry && pColorimetry->bt2020_rgb &&
pHDRStaticMetadata && pHDRStaticMetadata->eotfs && pHDRStaticMetadata->eotfs->pq )
{
m_Mutable.HDR.bExposeHDRSupport = true;
diff --git a/src/main.cpp b/src/main.cpp
index c96b8f0ac39e..58230054ce53 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -199,6 +199,7 @@ const char usage[] =
" If this is not set, and there is a HDR client, it will be tonemapped SDR.\n"
" --sdr-gamut-wideness Set the 'wideness' of the gamut for SDR comment. 0 - 1.\n"
" --hdr-sdr-content-nits set the luminance of SDR content in nits. Default: 400 nits.\n"
+ " --hdr-pq-disable disable HDR metadata detection for PQ EOTFs. IE disable HDR for external panels but not ones added through config. \n"
" --hdr-itm-enabled enable SDR->HDR inverse tone mapping. only works for SDR input.\n"
" --hdr-itm-sdr-nits set the luminance of SDR content in nits used as the input for the inverse tone mapping process.\n"
" Default: 100 nits, Max: 1000 nits\n"
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index fcdc9ac1559f..86df7eaf681f 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -363,6 +363,7 @@ bool g_bVRRInUse_CachedValue = false;
bool g_bSupportsHDR_CachedValue = false;
bool g_bForceHDR10OutputDebug = false;
gamescope::ConVar<bool> cv_hdr_enabled{ "hdr_enabled", false, "Whether or not HDR is enabled if it is available." };
+bool g_bHDRPqEnable = true;
bool g_bHDRItmEnable = false;
int g_nCurrentRefreshRate_CachedValue = 0;
@@ -7548,6 +7549,8 @@ steamcompmgr_main(int argc, char **argv)
g_bForceHDRSupportDebug = true;
} else if (strcmp(opt_name, "hdr-debug-force-output") == 0) {
g_bForceHDR10OutputDebug = true;
+ } else if (strcmp(opt_name, "hdr-pq-disabled") == 0 || strcmp(opt_name, "hdr-pq-disable") == 0) {
+ g_bHDRPqEnable = false;
} else if (strcmp(opt_name, "hdr-itm-enabled") == 0 || strcmp(opt_name, "hdr-itm-enable") == 0) {
g_bHDRItmEnable = true;
} else if (strcmp(opt_name, "sdr-gamut-wideness") == 0) {
diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp
index 9f384c461ca4..0df01c0ed0e3 100644
--- a/src/steamcompmgr.hpp
+++ b/src/steamcompmgr.hpp
@@ -38,6 +38,7 @@ static const uint32_t g_zposOverlay = 3;
static const uint32_t g_zposCursor = 4;
static const uint32_t g_zposMuraCorrection = 5;
+extern bool g_bHDRPqEnable;
extern bool g_bHDRItmEnable;
extern bool g_bForceHDRSupportDebug;
--
2.50.1
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 5bd1408bf780..495bbc070ff8 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,
@@ -588,7 +598,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)
@@ -893,6 +907,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.50.1
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.50.1
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 d7107325ad59..bcfc56dcbe20 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -114,6 +114,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*)>;
@@ -3077,6 +3082,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 58230054ce53..b11e67c0c9b7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -285,6 +285,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 2fd0ec45ef81..ceb7829127c3 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.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sun, 4 May 2025 22:45:14 +0200
Subject: Revert "Force wrap file usage for stb and glm dependencies"
This reverts commit b01717437797ee97a6a9810ddfc69153b3861df1.
---
src/meson.build | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/meson.build b/src/meson.build
index 0a0e958ba313..8a3a7237c787 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -19,14 +19,11 @@ xkbcommon = dependency('xkbcommon')
thread_dep = dependency('threads')
cap_dep = dependency('libcap', required: get_option('rt_cap'))
epoll_dep = dependency('epoll-shim', required: false)
+glm_dep = dependency('glm')
sdl2_dep = dependency('SDL2', required: get_option('sdl2_backend'))
+stb_dep = dependency('stb')
avif_dep = dependency('libavif', version: '>=1.0.0', required: get_option('avif_screenshots'))
-glm_proj = subproject('glm')
-glm_dep = glm_proj.get_variable('glm_dep')
-stb_proj = subproject('stb')
-stb_dep = stb_proj.get_variable('stb_dep')
-
wlroots_dep = dependency(
'wlroots',
version: ['>= 0.18.0', '< 0.19.0'],
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sat, 17 May 2025 03:06:20 +0200
Subject: fix: prevent external overlays from pulling focus
---
src/steamcompmgr.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 86df7eaf681f..be69ce4b391f 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -6132,8 +6132,8 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co
hasRepaintNonBasePlane = true;
}
- // If this is an external overlay, repaint
- if ( w == global_focus.externalOverlayWindow && w->opacity != TRANSLUCENT )
+ // External overlays, e.g., mangohud, should not be able to repaint when VRR is on
+ if ( !GetBackend()->IsVRRActive() && w == global_focus.externalOverlayWindow && w->opacity != TRANSLUCENT )
{
hasRepaintNonBasePlane = true;
}
--
2.50.1
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.50.1
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.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Sun, 29 Jun 2025 13:17:14 +0200
Subject: switch to bazzite fork for wlroots
---
.gitmodules | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.gitmodules b/.gitmodules
index 17ba783f809b..2ae6cd101faf 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,6 @@
[submodule "subprojects/wlroots"]
path = subprojects/wlroots
- url = https://github.com/misyltoad/wlroots.git
+ url = https://github.com/bazzite-org/wlroots.git
[submodule "subprojects/libliftoff"]
path = subprojects/libliftoff
url = https://gitlab.freedesktop.org/emersion/libliftoff.git
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Date: Sun, 22 Jun 2025 10:39:19 -0700
Subject: steamcompmgr: track FSR state with preemptive upscaling
g_bFSRActive was only being applied to the first frame of preemptive
upscaling, which meant that the FSR badge would quickly flicker from on
to off when preemptive upscaling was active.
To account for this, track the state of preemptive upscaling and use
it to activate g_bFSRActive when applicable.
---
src/steamcompmgr.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index be69ce4b391f..f867335557f9 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -2509,6 +2509,9 @@ paint_all( bool async, bool dpms )
}
g_bFSRActive = frameInfo.useFSRLayer0;
+ if ( const auto& heldCommit = g_HeldCommits[HELD_COMMIT_BASE]; heldCommit && heldCommit->upscaledTexture ) {
+ g_bFSRActive = ( heldCommit->upscaledTexture->eFilter == GamescopeUpscaleFilter::FSR );
+ }
g_bFirstFrame = false;
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Georg Lehmann <dadschoorse@gmail.com>
Date: Mon, 7 Aug 2023 18:54:01 +0200
Subject: rendervulkan: account for ycbcr descriptor count when creating
descriptor pool
---
src/rendervulkan.cpp | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
index d833d0093830..ac5c16dab035 100644
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -847,6 +847,25 @@ bool CVulkanDevice::createPools()
return false;
}
+ VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
+ .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM,
+ .type = VK_IMAGE_TYPE_2D,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_SAMPLED_BIT,
+ };
+
+ VkSamplerYcbcrConversionImageFormatProperties ycbcrProps = {
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES,
+ };
+
+ VkImageFormatProperties2 imageFormatProps = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
+ .pNext = &ycbcrProps,
+ };
+
+ res = vk.GetPhysicalDeviceImageFormatProperties2( physDev(), &imageFormatInfo, &imageFormatProps );
+
VkDescriptorPoolSize poolSizes[3] {
{
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
@@ -858,7 +877,7 @@ bool CVulkanDevice::createPools()
},
{
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
- uint32_t(m_descriptorSets.size()) * ((2 * VKR_SAMPLER_SLOTS) + (2 * VKR_LUT3D_COUNT)),
+ uint32_t(m_descriptorSets.size()) * (((ycbcrProps.combinedImageSamplerDescriptorCount + 1) * VKR_SAMPLER_SLOTS) + (2 * VKR_LUT3D_COUNT)),
},
};
--
2.50.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Wed, 20 Aug 2025 22:07:52 +0200
Subject: feat: add Ayaneo 3 display
---
.../displays/ayaneo.ayaneo3.oled.lua | 69 +++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 scripts/00-gamescope/displays/ayaneo.ayaneo3.oled.lua
diff --git a/scripts/00-gamescope/displays/ayaneo.ayaneo3.oled.lua b/scripts/00-gamescope/displays/ayaneo.ayaneo3.oled.lua
new file mode 100644
index 000000000000..9f6f725560b6
--- /dev/null
+++ b/scripts/00-gamescope/displays/ayaneo.ayaneo3.oled.lua
@@ -0,0 +1,69 @@
+local panel_id = "aya_fhd_oled"
+local panel_name = "AYA FHD OLED Panel"
+
+local panel_models = {
+ { vendor = "AYA", model = "AYAOLED_FHD" },
+}
+
+local panel_refresh_rates = { 60, 90, 120, 144 }
+
+local panel_hdr = {
+ supported = true,
+ force_enabled = true,
+ eotf = gamescope.eotf.ST2084,
+ max_content_light_level = 993.486,
+ max_frame_average_luminance = 400,
+ min_content_light_level = 0.007
+}
+
+
+gamescope.config.known_displays[panel_id] = {
+ pretty_name = panel_name,
+
+ -- These tables are optional
+ colorimetry = (panel_colorimetry ~= nil) and panel_colorimetry,
+ dynamic_refresh_rates = (panel_refresh_rates ~= nil) and panel_refresh_rates,
+ hdr = (panel_hdr ~= nil) and panel_hdr,
+
+ dynamic_modegen = function(base_mode, refresh)
+ local mode = base_mode
+ debug("["..panel_id.."] Switching mode to "..mode.hdisplay.."x"..mode.vdisplay.."@"..refresh.."Hz")
+
+ -- Override blanking intervals if defined
+ if panel_resolutions ~= nil then
+ for i, res in ipairs(panel_resolutions) do
+ if res.width == mode.hdisplay and res.height == mode.vdisplay then
+
+ if res.hfp ~= nil and res.hsync ~= nil and res.hbp ~= nil then
+ gamescope.modegen.set_h_timings(mode, set_res.hfp, set_res.hsync, set_res.hbp)
+ debug("["..panel_id.."] Overriding horizontal blanking interval")
+ end
+
+ if res.vfp ~= nil and res.vsync ~= nil and res.vbp ~= nil then
+ gamescope.modegen.set_v_timings(mode, set_res.vfp, set_res.vsync, set_res.vbp)
+ debug("["..panel_id.."] Overriding vertical blanking interval")
+ end
+
+ -- No need to iterate anymore
+ break
+ end
+ end
+ end
+
+ mode.clock = gamescope.modegen.calc_max_clock(mode, refresh)
+ mode.vrefresh = gamescope.modegen.calc_vrefresh(mode)
+
+ return mode
+ end,
+
+ matches = function(display)
+ for i, panel in ipairs(panel_models) do
+ if panel.vendor == display.vendor and panel.model == display.model then
+ debug("["..panel_id.."] Matched vendor: "..display.vendor.." model: "..display.model)
+ return 4000
+ end
+ end
+
+ return -1
+ end
+}
\ No newline at end of file
--
2.50.1