Release 1.1.3 with game window and Gamescope lifecycle fixes

This commit is contained in:
2026-10-25 20:47:19 +01:00
parent b6de80e758
commit 1f2aad0ee2
42 changed files with 9291 additions and 153 deletions

View File

@@ -0,0 +1,34 @@
From: PT Linux Launcher maintainers
Subject: Keep the nested game window visible during its login transition
PristonTale replaces its startup presentation while entering the login screen
and asks the nested X11 window manager to mark the game window iconic. Gamescope
3.16.20 acknowledges that request by publishing IconicState even though the
surface stays mapped. Wine then hides or resizes the client, which also changes
the outer Gamescope presentation.
When the launcher opts in through PTLINUX_GAMESCOPE_KEEP_WINDOWS_VISIBLE, keep
the nested client in NormalState. This is scoped to the launcher environment;
Gamescope retains its upstream behavior for every other invocation. Desktop
minimize and focus operate on the outer Wayland window and remain unaffected.
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -5361,8 +5361,17 @@ static void
handle_wm_change_state(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, XClientMessageEvent *ev)
{
long state = ev->data.l[0];
+ static const bool bKeepNestedWindowsVisible = env_to_bool(
+ getenv( "PTLINUX_GAMESCOPE_KEEP_WINDOWS_VISIBLE" ) );
if (state == ICCCM_ICONIC_STATE) {
+ if ( bKeepNestedWindowsVisible )
+ {
+ xwm_log.debugf("Ignoring WM_CHANGE_STATE to ICONIC for window 0x%lx", w->xwayland().id);
+ set_wm_state( ctx, w->xwayland().id, ICCCM_NORMAL_STATE );
+ return;
+ }
+
xwm_log.debugf("Faking WM_CHANGE_STATE to ICONIC for window 0x%lx", w->xwayland().id);
set_wm_state( ctx, w->xwayland().id, ICCCM_ICONIC_STATE );
} else {

View File

@@ -0,0 +1,80 @@
From: Matthew Schwartz <matthew.schwartz@linux.dev>
Subject: Lazily allocate shared Vulkan output images
Origin: upstream, commit e383171ff1c0f7b5b37f05f2bb67f7c5282b087f
Backport the upstream lazy-allocation change to Gamescope 3.16.20. Native
Wayland presentation normally sends the game's DMA-BUF directly to the host
compositor, so allocating six output-sized Vulkan images at startup and after
every host-window configure is unnecessary. Allocate those images only if a
frame actually needs full Vulkan composition.
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -3345,6 +3345,31 @@ static bool vulkan_make_output_images( VulkanOutput_t *pOutput )
return true;
}
+// Without a swapchain the shared images are ours to make, so they wait for their first use.
+static bool vulkan_defer_output_images()
+{
+ return !GetBackend()->UsesVulkanSwapchain();
+}
+
+static bool vulkan_ensure_output_images()
+{
+ if ( !vulkan_defer_output_images() )
+ return true;
+
+ VulkanOutput_t *pOutput = &g_output;
+ if ( !pOutput->outputImages.empty() && pOutput->outputImages[0] )
+ return true;
+
+ vk_log.debugf( "Making the shared output images on first use" );
+ if ( vulkan_make_output_images( pOutput ) )
+ return true;
+
+ // A half-made set would pass the check above, so the next use tries again from nothing.
+ pOutput->outputImages.clear();
+ pOutput->outputImagesPartialOverlay.clear();
+ return false;
+}
+
bool vulkan_remake_output_images()
{
VulkanOutput_t *pOutput = &g_output;
@@ -3355,6 +3380,14 @@ bool vulkan_remake_output_images()
for (auto& pScreenshotImage : pOutput->pScreenshotImages)
pScreenshotImage = nullptr;
+ if ( vulkan_defer_output_images() )
+ {
+ pOutput->outputImages.clear();
+ pOutput->outputImagesPartialOverlay.clear();
+ pOutput->temporaryHackyBlankImage = vulkan_create_debug_blank_texture();
+ return true;
+ }
+
bool bRet = vulkan_make_output_images( pOutput );
assert( bRet );
return bRet;
@@ -3433,7 +3466,9 @@ bool vulkan_make_output()
return false;
}
- if ( !vulkan_make_output_images( pOutput ) )
+ if ( vulkan_defer_output_images() )
+ pOutput->temporaryHackyBlankImage = vulkan_create_debug_blank_texture();
+ else if ( !vulkan_make_output_images( pOutput ) )
return false;
}
@@ -3980,6 +4015,9 @@ std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamesco
g_reshadeManager.clear();
}
+ if ( !pOutputOverride && !vulkan_ensure_output_images() )
+ return std::nullopt;
+
gamescope::Rc<CVulkanTexture> compositeImage;
if ( pOutputOverride )
compositeImage = pOutputOverride;

View File

@@ -0,0 +1,21 @@
From: PT Linux Launcher maintainers
Subject: Use a backing surface for letter-boxing
Backport upstream commit 66450376038864700f9b4721343b4d15b29c4198 so
native Wayland presentation covers the complete configured output when the
game layer is unity-scaled with a non-zero letterbox offset.
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -1081,7 +1081,10 @@ namespace gamescope
bool bNeedsBacking = true;
if ( pFrameInfo->layerCount >= 1 )
{
- if ( pFrameInfo->layers[0].isScreenSize() && !pFrameInfo->layers[0].hasAlpha() )
+ if ( pFrameInfo->layers[0].isScreenSize() &&
+ close_enough( pFrameInfo->layers[0].offset.x, 0.0f ) &&
+ close_enough( pFrameInfo->layers[0].offset.y, 0.0f ) &&
+ !pFrameInfo->layers[0].hasAlpha() )
bNeedsBacking = false;
}

View File

@@ -0,0 +1,64 @@
From: PT Linux Launcher maintainers
Subject: Keep client-side decoration clicks out of nested applications
Bug: https://github.com/ValveSoftware/gamescope/issues/2299
Forwarded: https://github.com/ValveSoftware/gamescope/issues/2299
On compositors without xdg-decoration support, libdecor creates client-side
surfaces owned by the Gamescope Wayland connection. Pointer button callbacks
for those titlebar and resize surfaces consequently also reach Gamescope's
input thread. Forwarding a decoration press to the nested application can
leave its button permanently held because libdecor consumes the matching
release while performing a move, resize, or maximize operation.
Only begin nested button sequences while the pointer is over Gamescope's
actual toplevel surface. Track those presses so their releases are delivered
even if the pointer or keyboard focus changes during a game-originated drag.
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -562,6 +562,7 @@
std::atomic<std::shared_ptr<zwp_relative_pointer_v1>> m_pRelativePointer = nullptr;
std::unordered_set<uint32_t> m_uScancodesHeld;
+ std::unordered_set<uint32_t> m_uMouseButtonsHeld;
void Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion );
static const wl_registry_listener s_RegistryListener;
@@ -3091,13 +3092,33 @@
}
void CWaylandInputThread::Wayland_Pointer_Button( wl_pointer *pPointer, uint32_t uSerial, uint32_t uTime, uint32_t uButton, uint32_t uState )
{
- // Don't do any motion/movement stuff if we don't have kb focus
- if ( !cv_wayland_mouse_warp_without_keyboard_focus && !m_bKeyboardEntered )
- return;
+ const bool bPressed = uState == WL_POINTER_BUTTON_STATE_PRESSED;
+ const bool bWasPressed = m_uMouseButtonsHeld.contains( uButton );
+
+ if ( bPressed )
+ {
+ // libdecor's client-side decorations share our Wayland seat, but
+ // their button events must not be sent to the nested application.
+ if ( !m_bMouseEntered || bWasPressed )
+ return;
+
+ // Don't start an application click without keyboard focus.
+ if ( !cv_wayland_mouse_warp_without_keyboard_focus && !m_bKeyboardEntered )
+ return;
+ }
+ else if ( !bWasPressed )
+ {
+ return;
+ }
wlserver_lock();
- wlserver_mousebutton( uButton, uState == WL_POINTER_BUTTON_STATE_PRESSED, ++m_uFakeTimestamp );
+ wlserver_mousebutton( uButton, bPressed, ++m_uFakeTimestamp );
wlserver_unlock();
+
+ if ( bPressed )
+ m_uMouseButtonsHeld.emplace( uButton );
+ else
+ m_uMouseButtonsHeld.erase( uButton );
}
void CWaylandInputThread::Wayland_Pointer_Axis( wl_pointer *pPointer, uint32_t uTime, uint32_t uAxis, wl_fixed_t fValue )
{

View File

@@ -0,0 +1,50 @@
From: PT Linux Launcher maintainers
Subject: Avoid idle waits for direct Wayland resize configures
The lazy-output-images backport leaves no shared output images allocated while
Gamescope is presenting the game directly as Wayland planes. A host-window
configure nevertheless calls vulkan_remake_output_images(), whose unconditional
device-idle wait and output-sized blank-texture recreation stall the compositor
thread for every intermediate drag size.
Wait only when an output or screenshot image actually exists. The transparent
fallback texture is unused by the non-session Wayland backend, so retain it
across Wayland size changes; session backends preserve the existing recreation
behavior. If a frame later requires full composition, the lazy allocation path
creates correctly sized output images before rendering it.
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -3374,7 +3374,21 @@ static bool vulkan_ensure_output_images()
bool vulkan_remake_output_images()
{
VulkanOutput_t *pOutput = &g_output;
- g_device.waitIdle();
+ bool bHasLiveImages =
+ !pOutput->outputImages.empty() && pOutput->outputImages[0];
+ if ( !bHasLiveImages )
+ {
+ for ( const auto &pScreenshotImage : pOutput->pScreenshotImages )
+ {
+ if ( pScreenshotImage )
+ {
+ bHasLiveImages = true;
+ break;
+ }
+ }
+ }
+ if ( bHasLiveImages )
+ g_device.waitIdle();
pOutput->nOutImage = 0;
@@ -3386,7 +3400,8 @@ bool vulkan_remake_output_images()
{
pOutput->outputImages.clear();
pOutput->outputImagesPartialOverlay.clear();
- pOutput->temporaryHackyBlankImage = vulkan_create_debug_blank_texture();
+ if ( GetBackend()->IsSessionBased() || !pOutput->temporaryHackyBlankImage )
+ pOutput->temporaryHackyBlankImage = vulkan_create_debug_blank_texture();
return true;
}