fix(gamescope): keep Fill scaling and magnification visible (#1634)

* fix(gamescope): preserve signed plane positions

* fix(gamescope): pin validated scaling component
This commit is contained in:
Kurt Himebauch authored and GitHub committed 2026-10-26 16:06:40 -04:00
1 parent aa17b0553d
commit 73daaf7adc
4 files changed
+131 -3

No files matched your search

+22
View File
@@ -99,3 +99,25 @@ The patch can keep the result for `DROIDDECK_WSI_BYPASS_CHECK_MS` milliseconds.
or 0 keeps the per-present check; the app sets 100 only in Performance mode and 0
otherwise. With 100, a window resize or a flip/obscure change is seen up to 100 ms
later than before; surface creation and capability queries still check every time.
## 0118 Wayland: preserve signed plane positions
Fill scaling and Steam magnification can place the image at negative x/y coordinates.
The Wayland backend converted these signed positions through an unsigned size helper:
at host scale 120, -120 became 35791275 and the image moved off-screen while overlays
could remain visible. A separate signed conversion uses a 64-bit intermediate and
retains the existing ceiling convention; viewport sizes keep their original helper.
The component build runs `wayland_scaling` for negative crop offsets, positive/zero
positions, fractional host scales and wide intermediates. This patch may be worth
upstreaming: the same unsigned position calls were present in ValveSoftware/gamescope
3.16.29 and master `d175688ad4ec490dc7b0695c5c62f8807684a1f1` when checked on
2026-10-26. Carry it here until an upstream release containing the fix is adopted.
Device validation on AYN Thor (1280x720 session, 1920x1080 Android output):
800x600 and 1600x600 source windows retained their image through Auto, Fit,
Stretch, Fill, Integer and 2x Steam magnification, including returning to Fit.
The old component lost the image in Fill and magnification. The tested executable
matches the pinned p14 archive and the locally assembled APK. Hardware checks used
native Gamescope properties; the physical Back+LB shortcut was not independently
confirmed. The original APK and runtime were restored after validation.
+1 -1
View File
@@ -39,7 +39,7 @@ else
fi
PREP
makepkg -A -s --noconfirm --skipchecksums --skippgpcheck
meson test -C src/build --no-rebuild --print-errorlogs cursor_routing
meson test -C src/build --no-rebuild --print-errorlogs cursor_routing wayland_scaling
ls -l *.pkg.tar.*
"
cd "$WORK"
@@ -0,0 +1,106 @@
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index d00644f..40164ba 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -11,6 +11,7 @@
#include "waitable.h"
#include "Utils/TempFiles.h"
#include "Utils/CursorRouting.h"
+#include "Utils/WaylandScaling.h"
#include <cstring>
#include <unordered_map>
@@ -1618,8 +1619,8 @@ namespace gamescope
{
wl_subsurface_set_position(
m_pSubsurface,
- WaylandScaleToLogical( oState->nDestX, uScale ),
- WaylandScaleToLogical( oState->nDestY, uScale ) );
+ gamescope::WaylandPositionToLogical( oState->nDestX, uScale ),
+ gamescope::WaylandPositionToLogical( oState->nDestY, uScale ) );
}
// The x/y here does nothing? Why? What is it for...
// Use the subsurface set_position thing instead.
diff --git a/src/Utils/WaylandScaling.h b/src/Utils/WaylandScaling.h
new file mode 100644
index 0000000..d488e39
--- /dev/null
+++ b/src/Utils/WaylandScaling.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <cstdint>
+
+namespace gamescope
+{
+ constexpr int32_t WaylandPositionToLogical( int32_t nValue, uint32_t uFactor )
+ {
+ const int64_t nScaled = int64_t( nValue ) * 120;
+ return int32_t( nScaled / uFactor + ( nScaled % uFactor > 0 ) );
+ }
+}
diff --git a/tests/meson.build b/tests/meson.build
index 1ccdf7d..d86bf9e 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -8,7 +8,7 @@ endforeach
gamescope_tests = executable(
'gamescope_tests',
- unittest_src + ['test_convar.cpp', 'test_cursor_routing.cpp', 'test_focus_placement.cpp', 'test_mangoapp_config.cpp', 'test_mangoapp_control.cpp', 'test_process.cpp', 'test_upscale.cpp', 'test_utils_parsers.cpp', 'test_utils_string.cpp', 'test_vrclient_detect.cpp'],
+ unittest_src + ['test_convar.cpp', 'test_cursor_routing.cpp', 'test_focus_placement.cpp', 'test_mangoapp_config.cpp', 'test_mangoapp_control.cpp', 'test_process.cpp', 'test_upscale.cpp', 'test_utils_parsers.cpp', 'test_utils_string.cpp', 'test_vrclient_detect.cpp', 'test_wayland_scaling.cpp'],
include_directories: [srcdir, sol2_include],
dependencies: [catch2_dep, luajit_dep, cap_dep, drm_dep, glm_dep, wlroots_dep],
cpp_args: gamescope_cpp_args,
@@ -18,6 +18,7 @@ reaper_test_helper = executable('reaper_test_helper', 'reaper_test_helper.cpp')
test('convar', gamescope_tests, args: ['[convar]'])
test('cursor_routing', gamescope_tests, args: ['[cursor_routing]'])
+test('wayland_scaling', gamescope_tests, args: ['[wayland_scaling]'])
test('focus_placement', gamescope_tests, args: ['[focus_placement]'])
test('mangoapp_config', gamescope_tests, args: ['[mangoapp_config]'])
test('mangoapp_control', gamescope_tests, args: ['[mangoapp_control]'])
diff --git a/tests/test_wayland_scaling.cpp b/tests/test_wayland_scaling.cpp
new file mode 100644
index 0000000..498f55b
--- /dev/null
+++ b/tests/test_wayland_scaling.cpp
@@ -0,0 +1,38 @@
+#include <catch2/catch_test_macros.hpp>
+
+#include "Utils/WaylandScaling.h"
+
+#include <cmath>
+#include <limits>
+
+using namespace gamescope;
+
+TEST_CASE("cropped Wayland planes retain negative positions", "[wayland_scaling]")
+{
+ REQUIRE( WaylandPositionToLogical( -120, 120 ) == -120 );
+ REQUIRE( WaylandPositionToLogical( -640, 120 ) == -640 );
+ REQUIRE( WaylandPositionToLogical( -360, 120 ) == -360 );
+ REQUIRE( WaylandPositionToLogical( 0, 120 ) == 0 );
+ REQUIRE( WaylandPositionToLogical( 160, 120 ) == 160 );
+}
+
+TEST_CASE("Wayland positions round up at fractional host scales", "[wayland_scaling]")
+{
+ const int32_t positions[] = { -100000000, -1000000, -640, -120, -1, 0, 1, 120, 640, 1000000, 100000000 };
+ const uint32_t scales[] = { 120, 150, 180, 240, 360, 480 };
+ for ( uint32_t uScale : scales )
+ {
+ for ( int32_t nPosition : positions )
+ {
+ CAPTURE( nPosition, uScale );
+ const int32_t nExpected = int32_t( std::ceil( (long double)( nPosition ) * 120 / uScale ) );
+ REQUIRE( WaylandPositionToLogical( nPosition, uScale ) == nExpected );
+ }
+ }
+}
+
+TEST_CASE("Wayland position conversion widens before multiplying", "[wayland_scaling]")
+{
+ REQUIRE( WaylandPositionToLogical( std::numeric_limits<int32_t>::min(), 120 ) == std::numeric_limits<int32_t>::min() );
+ REQUIRE( WaylandPositionToLogical( std::numeric_limits<int32_t>::max(), 120 ) == std::numeric_limits<int32_t>::max() );
+}
+2 -2
View File
@@ -1,3 +1,3 @@
GAMESCOPE_REPO=Droid-Deck/DroidDeck-Components
GAMESCOPE_TAG=gamescope-3.16.29-p13
GAMESCOPE_SHA256=93c789dc9e08828496f1caa2849d56acdf205838de723a24b296547a29c0b6af
GAMESCOPE_TAG=gamescope-3.16.29-p14
GAMESCOPE_SHA256=f0f2f33767c883bcfe66f28c303fddc1564f91a915fb7cedd31eb823570b254c