bazzite/spec_files/gamescope/handheld.patch

2440 lines
93 KiB
Diff
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: 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 0000000..676d652
--- /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
+ninja -C build/
+scp build/src/gamescope ${HOST}:gamescope
+
+ssh $HOST /bin/bash << EOF
+ sudo rpm-ostree usroverlay
+ sudo mv ~/gamescope /usr/bin/gamescope
+ bazzite-session-select gamescope
+ # sudo reboot
+EOF
--
2.47.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 | 5 +++++
src/main.cpp | 31 +++++++++++++++++++++++++++++++
src/main.hpp | 2 ++
3 files changed, 38 insertions(+)
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 0b121e8..75c3258 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2243,6 +2243,11 @@ namespace gamescope
bHasKnownHDRInfo = true;
}
}
+ else if ( g_customRefreshRates.size() > 0 && GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL ) {
+ // Only apply custom refresh rates as a fallback, allowing a graceful transition to the new system.
+ m_Mutable.ValidDynamicRefreshRates = g_customRefreshRates;
+ return;
+ }
}
if ( !bHasKnownColorimetry )
diff --git a/src/main.cpp b/src/main.cpp
index 9dff5c4..8381889 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"
"\n"
"Nested mode options:\n"
@@ -425,6 +427,33 @@ static enum gamescope::GamescopeBackend parse_backend_name(const char *str)
}
}
+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()
@@ -746,6 +775,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 = atoi( optarg );
diff --git a/src/main.hpp b/src/main.hpp
index 2e6fb83..390c04a 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.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Alesh Slovak <alesh@playtron.one>
Date: Thu, 26 Sep 2024 07:13:24 -0400
Subject: fix(vrr): Revert "steamcompmgr: Move outdatedInteractiveFocus to
window"
This reverts commit 299bc3410dcfd46da5e3c988354b60ed3a356900.
---
src/steamcompmgr.cpp | 39 +++++++++++++++++++++++--------------
src/steamcompmgr_shared.hpp | 1 -
2 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 18be94d..cd7e8ac 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -3299,7 +3299,7 @@ found:;
if ( window_has_commits( focus ) )
out->focusWindow = focus;
else
- focus->outdatedInteractiveFocus = true;
+ out->outdatedInteractiveFocus = true;
// Always update X's idea of focus, but still dirty
// the it being outdated so we can resolve that globally later.
@@ -6050,28 +6050,37 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co
// Window just got a new available commit, determine if that's worth a repaint
// If this is an overlay that we're presenting, repaint
- if ( w == global_focus.overlayWindow && w->opacity != TRANSLUCENT )
+ if ( gameFocused )
{
- hasRepaintNonBasePlane = true;
- }
+ if ( w == global_focus.overlayWindow && w->opacity != TRANSLUCENT )
+ {
+ hasRepaintNonBasePlane = true;
+ }
- if ( w == global_focus.notificationWindow && w->opacity != TRANSLUCENT )
- {
- hasRepaintNonBasePlane = true;
+ if ( w == global_focus.notificationWindow && w->opacity != TRANSLUCENT )
+ {
+ hasRepaintNonBasePlane = true;
+ }
}
-
- // If this is an external overlay, repaint
- if ( w == global_focus.externalOverlayWindow && w->opacity != TRANSLUCENT )
+ if ( ctx )
{
- hasRepaintNonBasePlane = true;
+ if ( ctx->focus.outdatedInteractiveFocus )
+ {
+ MakeFocusDirty();
+ ctx->focus.outdatedInteractiveFocus = false;
+ }
}
-
- if ( w->outdatedInteractiveFocus )
+ if ( global_focus.outdatedInteractiveFocus )
{
MakeFocusDirty();
- w->outdatedInteractiveFocus = false;
- }
+ global_focus.outdatedInteractiveFocus = false;
+ // If this is an external overlay, repaint
+ if ( w == global_focus.externalOverlayWindow && w->opacity != TRANSLUCENT )
+ {
+ hasRepaintNonBasePlane = true;
+ }
+ }
// If this is the main plane, repaint
if ( w == global_focus.focusWindow && !w->isSteamStreamingClient )
{
diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp
index 095694e..e41fad9 100644
--- a/src/steamcompmgr_shared.hpp
+++ b/src/steamcompmgr_shared.hpp
@@ -125,7 +125,6 @@ struct steamcompmgr_win_t {
unsigned int requestedHeight = 0;
bool is_dialog = false;
bool maybe_a_dropdown = false;
- bool outdatedInteractiveFocus = false;
bool hasHwndStyle = false;
uint32_t hwndStyle = 0;
--
2.47.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 | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index 78a86ee..99df8aa 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -290,6 +290,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 );
@@ -310,7 +313,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;
@@ -319,6 +329,13 @@ static void wlserver_handle_key(struct wl_listener *listener, void *data)
{
wlserver_keyboardfocus( new_kb_surf, false );
wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard->wlr );
+ if (is_steamshortcut)
+ {
+ // send ctrl down modifier to trigger the overlay
+ wlr_keyboard_modifiers ctrl_down_modifier;
+ ctrl_down_modifier.depressed = WLR_MODIFIER_CTRL;
+ wlr_seat_keyboard_notify_modifiers(wlserver.wlr.seat, &ctrl_down_modifier);
+ }
wlr_seat_keyboard_notify_key( wlserver.wlr.seat, event->time_msec, event->keycode, event->state );
wlserver_keyboardfocus( old_kb_surf, false );
return;
--
2.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 11 Oct 2024 17:52:48 +0200
Subject: fix: allow for disabling touch atom click
Causes issues in certain devices (or not anymore?).
Parameter option by Kyle.
Co-authored-by: Kyle Gospodnetich <me@kylegospodneti.ch>
---
src/main.cpp | 2 ++
src/steamcompmgr.cpp | 5 ++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/main.cpp b/src/main.cpp
index 8381889..a76b51b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -128,6 +128,7 @@ const struct option *gamescope_options = (struct option[]){
{ "disable-xres", no_argument, nullptr, 'x' },
{ "fade-out-duration", required_argument, nullptr, 0 },
{ "force-orientation", required_argument, nullptr, 0 },
+ { "disable-touch-click", no_argument, nullptr, 0 },
{ "force-windows-fullscreen", no_argument, nullptr, 0 },
{ "custom-refresh-rates", required_argument, nullptr, 0 },
@@ -188,6 +189,7 @@ const char usage[] =
" -T, --stats-path write statistics to path\n"
" -C, --hide-cursor-delay hide cursor image after delay\n"
" -e, --steam enable Steam integration\n"
+ " --disable-touch-click disable touchscreen tap acting as a click\n"
" --xwayland-count create N xwayland servers\n"
" --prefer-vk-device prefer Vulkan device for compositing (ex: 1002:7300)\n"
" --force-orientation rotate the internal display (left, right, normal, upsidedown)\n"
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index cd7e8ac..3f3d499 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -197,6 +197,7 @@ update_runtime_info();
gamescope::ConVar<bool> cv_adaptive_sync( "adaptive_sync", false, "Whether or not adaptive sync is enabled if available." );
gamescope::ConVar<bool> cv_adaptive_sync_ignore_overlay( "adaptive_sync_ignore_overlay", false, "Whether or not to ignore overlay planes for pushing commits with adaptive sync." );
gamescope::ConVar<int> cv_adaptive_sync_overlay_cycles( "adaptive_sync_overlay_cycles", 1, "Number of vblank cycles to ignore overlay repaints before forcing a commit with adaptive sync." );
+gamescope::ConVar<bool> cv_disable_touch_click{ "disable_touch_click", false, "Prevents touchscreen taps acting as clicks" };
uint64_t g_SteamCompMgrLimitedAppRefreshCycle = 16'666'666;
uint64_t g_SteamCompMgrAppRefreshCycle = 16'666'666;
@@ -5185,7 +5186,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
MakeFocusDirty();
}
}
- if (ev->atom == ctx->atoms.steamTouchClickModeAtom )
+ if (ev->atom == ctx->atoms.steamTouchClickModeAtom && !cv_disable_touch_click )
{
gamescope::cv_touch_click_mode = (gamescope::TouchClickMode) get_prop(ctx, ctx->root, ctx->atoms.steamTouchClickModeAtom, 0u );
}
@@ -7482,6 +7483,8 @@ steamcompmgr_main(int argc, char **argv)
g_reshade_technique_idx = atoi(optarg);
} else if (strcmp(opt_name, "mura-map") == 0) {
set_mura_overlay(optarg);
+ } else if (strcmp(opt_name, "disable-touch-click") == 0) {
+ cv_disable_touch_click = true;
}
break;
case '?':
--
2.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 11 Oct 2024 21:56:54 +0200
Subject: fix(intel-gpu): allow for (enabling) hacky texture
Disabling hacky texture will use more hardware planes, causing some devices to composite yielding lower fps. Required for intel to work
---
src/main.cpp | 2 ++
src/steamcompmgr.cpp | 5 ++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/main.cpp b/src/main.cpp
index a76b51b..84e05a9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -128,6 +128,7 @@ const struct option *gamescope_options = (struct option[]){
{ "disable-xres", no_argument, nullptr, 'x' },
{ "fade-out-duration", required_argument, nullptr, 0 },
{ "force-orientation", required_argument, nullptr, 0 },
+ { "enable-hacky-texture", no_argument, nullptr, 0 },
{ "disable-touch-click", no_argument, nullptr, 0 },
{ "force-windows-fullscreen", no_argument, nullptr, 0 },
{ "custom-refresh-rates", required_argument, nullptr, 0 },
@@ -189,6 +190,7 @@ const char usage[] =
" -T, --stats-path write statistics to path\n"
" -C, --hide-cursor-delay hide cursor image after delay\n"
" -e, --steam enable Steam integration\n"
+ " --enable-hacky-texture enable hacky texture on hw that support it\n"
" --disable-touch-click disable touchscreen tap acting as a click\n"
" --xwayland-count create N xwayland servers\n"
" --prefer-vk-device prefer Vulkan device for compositing (ex: 1002:7300)\n"
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 3f3d499..5345b55 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -147,6 +147,7 @@ static lut3d_t g_tmpLut3d;
extern int g_nDynamicRefreshHz;
bool g_bForceHDRSupportDebug = false;
+bool g_bHackyEnabled = false;
extern float g_flInternalDisplayBrightnessNits;
extern float g_flHDRItmSdrNits;
extern float g_flHDRItmTargetNits;
@@ -2412,7 +2413,7 @@ paint_all(bool async)
if ( overlay == global_focus.inputFocusWindow )
update_touch_scaling( &frameInfo );
}
- else if ( !GetBackend()->UsesVulkanSwapchain() && GetBackend()->IsSessionBased() )
+ else if ( g_bHackyEnabled && !GetBackend()->UsesVulkanSwapchain() && GetBackend()->IsSessionBased() )
{
auto tex = vulkan_get_hacky_blank_texture();
if ( tex != nullptr )
@@ -7485,6 +7486,8 @@ steamcompmgr_main(int argc, char **argv)
set_mura_overlay(optarg);
} else if (strcmp(opt_name, "disable-touch-click") == 0) {
cv_disable_touch_click = true;
+ } else if (strcmp(opt_name, "enable-hacky-texture") == 0) {
+ g_bHackyEnabled = true;
}
break;
case '?':
--
2.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 11 Oct 2024 23:01:13 +0200
Subject: fix: re-add external orientation options to not break current
sessions (incl. applying ext. orientation)
---
src/main.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/main.cpp b/src/main.cpp
index 84e05a9..2398535 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -129,6 +129,8 @@ const struct option *gamescope_options = (struct option[]){
{ "fade-out-duration", required_argument, nullptr, 0 },
{ "force-orientation", required_argument, nullptr, 0 },
{ "enable-hacky-texture", no_argument, nullptr, 0 },
+ { "force-panel-type", required_argument, nullptr, 0 },
+ { "force-external-orientation", required_argument, nullptr, 0 },
{ "disable-touch-click", no_argument, nullptr, 0 },
{ "force-windows-fullscreen", no_argument, nullptr, 0 },
{ "custom-refresh-rates", required_argument, nullptr, 0 },
@@ -777,7 +779,7 @@ int main(int argc, char **argv)
gamescope::cv_touch_click_mode = (gamescope::TouchClickMode) atoi( optarg );
} else if (strcmp(opt_name, "generate-drm-mode") == 0) {
g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg );
- } else if (strcmp(opt_name, "force-orientation") == 0) {
+ } else if (strcmp(opt_name, "force-orientation") == 0 || strcmp(opt_name, "force-external-orientation") == 0) {
g_DesiredInternalOrientation = force_orientation( optarg );
} else if (strcmp(opt_name, "custom-refresh-rates") == 0) {
g_customRefreshRates = parse_custom_refresh_rates( optarg );
--
2.47.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 99df8aa..5e8f516 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -2492,8 +2492,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.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 11 Oct 2024 23:47:59 +0200
Subject: feat(vrr): allow for setting refresh rate if the internal display
allows
For the Ally, we have a set of VFP that work to set the refresh rate.
They can also be used for VRR but gamescope does not currently allow for
it. Therefore, bypass some checks to allow it to work just for this usecase.
---
src/main.cpp | 2 ++
src/steamcompmgr.cpp | 7 +++++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/main.cpp b/src/main.cpp
index 2398535..0621c65 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -132,6 +132,7 @@ const struct option *gamescope_options = (struct option[]){
{ "force-panel-type", required_argument, nullptr, 0 },
{ "force-external-orientation", required_argument, nullptr, 0 },
{ "disable-touch-click", no_argument, nullptr, 0 },
+ { "enable-vrr-modesetting", no_argument, nullptr, 0 },
{ "force-windows-fullscreen", no_argument, nullptr, 0 },
{ "custom-refresh-rates", required_argument, nullptr, 0 },
@@ -194,6 +195,7 @@ const char usage[] =
" -e, --steam enable Steam integration\n"
" --enable-hacky-texture enable hacky texture on hw that support it\n"
" --disable-touch-click disable touchscreen tap acting as a click\n"
+ " --enable-vrr-modesetting enable setting framerate while VRR is on in the internal display\n"
" --xwayland-count create N xwayland servers\n"
" --prefer-vk-device prefer Vulkan device for compositing (ex: 1002:7300)\n"
" --force-orientation rotate the internal display (left, right, normal, upsidedown)\n"
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 5345b55..6544cf0 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -148,6 +148,7 @@ extern int g_nDynamicRefreshHz;
bool g_bForceHDRSupportDebug = false;
bool g_bHackyEnabled = false;
+bool g_bVRRModesetting = false;
extern float g_flInternalDisplayBrightnessNits;
extern float g_flHDRItmSdrNits;
extern float g_flHDRItmTargetNits;
@@ -899,7 +900,7 @@ bool g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = false;
bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w )
{
// VRR + FPS Limit needs another approach.
- if ( GetBackend()->IsVRRActive() )
+ if ( GetBackend()->IsVRRActive() && !(g_bVRRModesetting && GetBackend()->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL) )
return false;
return w && !window_is_steam( w ) && !w->isOverlay && !w->isExternalOverlay;
@@ -923,7 +924,7 @@ steamcompmgr_user_has_any_game_open()
bool steamcompmgr_window_should_refresh_switch( steamcompmgr_win_t *w )
{
- if ( GetBackend()->IsVRRActive() )
+ if ( GetBackend()->IsVRRActive() && !(g_bVRRModesetting && GetBackend()->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL))
return false;
if ( g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive )
@@ -7486,6 +7487,8 @@ steamcompmgr_main(int argc, char **argv)
set_mura_overlay(optarg);
} else if (strcmp(opt_name, "disable-touch-click") == 0) {
cv_disable_touch_click = true;
+ } else if (strcmp(opt_name, "enable-vrr-modesetting") == 0) {
+ g_bVRRModesetting = true;
} else if (strcmp(opt_name, "enable-hacky-texture") == 0) {
g_bHackyEnabled = true;
}
--
2.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 11 Oct 2024 19:09:05 +0200
Subject: feat: add external option that now only lies to steam
Previously, there was a force-panel option that allowed for VRR.
However, this is no longer the case and VRR works fine.
This option still allows for scaling the display though. So, create a
variant of the patch that only does that.
---
src/main.cpp | 16 ++++++++++++++++
src/steamcompmgr.cpp | 2 +-
src/steamcompmgr.hpp | 1 +
src/wlserver.cpp | 2 +-
src/wlserver.hpp | 1 +
5 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/main.cpp b/src/main.cpp
index 0621c65..056e1c1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -199,6 +199,7 @@ const char usage[] =
" --xwayland-count create N xwayland servers\n"
" --prefer-vk-device prefer Vulkan device for compositing (ex: 1002:7300)\n"
" --force-orientation rotate the internal display (left, right, normal, upsidedown)\n"
+ " --force-panel-type lie to steam that the screen is external\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"
" --hdr-enabled enable HDR output (needs Gamescope WSI layer enabled for support from clients)\n"
@@ -373,6 +374,19 @@ static GamescopePanelOrientation force_orientation(const char *str)
}
}
+bool g_FakeExternal = false;
+static bool force_panel_type_external(const char *str)
+{
+ if (strcmp(str, "internal") == 0) {
+ return false;
+ } else if (strcmp(str, "external") == 0) {
+ return true;
+ } else {
+ fprintf( stderr, "gamescope: invalid value for --force-panel-type\n" );
+ exit(1);
+ }
+}
+
static enum GamescopeUpscaleScaler parse_upscaler_scaler(const char *str)
{
if (strcmp(str, "auto") == 0) {
@@ -783,6 +797,8 @@ int main(int argc, char **argv)
g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg );
} else if (strcmp(opt_name, "force-orientation") == 0 || strcmp(opt_name, "force-external-orientation") == 0) {
g_DesiredInternalOrientation = force_orientation( optarg );
+ } else if (strcmp(opt_name, "force-panel-type") == 0) {
+ g_FakeExternal = force_panel_type_external( optarg );
} 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/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 6544cf0..d541f70 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -7198,7 +7198,7 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr)
if (needs_flush)
*needs_flush = true;
- if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL )
+ if ( !g_FakeExternal && GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL )
{
XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayModeListExternal);
diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp
index 9f384c4..30e48e8 100644
--- a/src/steamcompmgr.hpp
+++ b/src/steamcompmgr.hpp
@@ -127,6 +127,7 @@ extern float focusedWindowScaleY;
extern float focusedWindowOffsetX;
extern float focusedWindowOffsetY;
+extern bool g_FakeExternal;
extern bool g_bFSRActive;
extern uint32_t inputCounter;
diff --git a/src/wlserver.cpp b/src/wlserver.cpp
index 5e8f516..1eeaa25 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -1078,7 +1078,7 @@ static uint32_t get_conn_display_info_flags()
return 0;
uint32_t flags = 0;
- if ( pConn->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL )
+ if ( pConn->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && !g_FakeExternal )
flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY;
if ( pConn->SupportsVRR() )
flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR;
diff --git a/src/wlserver.hpp b/src/wlserver.hpp
index 0569472..104f7a2 100644
--- a/src/wlserver.hpp
+++ b/src/wlserver.hpp
@@ -190,6 +190,7 @@ struct wlserver_t {
};
extern struct wlserver_t wlserver;
+extern bool g_FakeExternal;
std::vector<ResListEntry_t> wlserver_xdg_commit_queue();
--
2.47.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 75c3258..f014be9 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2161,7 +2161,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.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 25 Oct 2024 21:22:10 +0200
Subject: fix(vrr): allow frame limiter to work with VRR enabled
Down to 48hz, modeset the correct framerate. Below 48hz,
disable VRR and use the classic frame limiter.
---
src/steamcompmgr.cpp | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index d541f70..8e5ad6a 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -165,6 +165,7 @@ uint32_t g_reshade_technique_idx = 0;
bool g_bSteamIsActiveWindow = false;
bool g_bForceInternal = false;
+bool g_bVRRRequested = false;
static std::vector< steamcompmgr_win_t* > GetGlobalPossibleFocusWindows();
static bool
@@ -827,6 +828,28 @@ static void _update_app_target_refresh_cycle()
{
auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates();
+ if (g_bVRRModesetting) {
+ if (g_bVRRRequested) {
+ // If modeset VRR, go upwards to match the refresh rate 1-1. Refresh
+ // doubling would hurt us here by breaking the frame limiter.
+ for ( auto rate = rates.begin(); rate != rates.end(); rate++ )
+ {
+ if ((int)*rate == target_fps)
+ {
+ g_nDynamicRefreshRate[ type ] = *rate;
+ // Enable VRR as we have the correct refresh rate
+ cv_adaptive_sync = true;
+ return;
+ }
+ }
+ // Otherwise, disable VRR as we can't match the refresh rate 1-1
+ // (e.g., below 48hz).
+ cv_adaptive_sync = false;
+ } else {
+ cv_adaptive_sync = false;
+ }
+ }
+
// Find highest mode to do refresh doubling with.
for ( auto rate = rates.rbegin(); rate != rates.rend(); rate++ )
{
@@ -5522,8 +5545,11 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
}
if ( ev->atom == ctx->atoms.gamescopeVRREnabled )
{
- bool enabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeVRREnabled, 0 );
- cv_adaptive_sync = enabled;
+ g_bVRRRequested = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeVRREnabled, 0 );
+ // Try to match refresh rate and have that set the cv_adaptive_sync only if it can
+ if (g_bVRRModesetting) update_app_target_refresh_cycle();
+ // otherwise, fall back to original behavior
+ else cv_adaptive_sync = g_bVRRRequested;
}
if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal )
{
--
2.47.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 and disable VRR V2 +
param
---
src/steamcompmgr.cpp | 43 ++++++++++++++++++++++++++++++++-----------
1 file changed, 32 insertions(+), 11 deletions(-)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 8e5ad6a..bc238e4 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -166,6 +166,9 @@ uint32_t g_reshade_technique_idx = 0;
bool g_bSteamIsActiveWindow = false;
bool g_bForceInternal = false;
bool g_bVRRRequested = false;
+bool g_bVRRCanEnable = false;
+bool b_bForceFrameLimit = false;
+bool g_bRefreshHalveEnable = false;
static std::vector< steamcompmgr_win_t* > GetGlobalPossibleFocusWindows();
static bool
@@ -793,6 +796,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.
@@ -812,7 +816,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 )
{
@@ -821,7 +825,7 @@ static void _update_app_target_refresh_cycle()
if ( g_nCombinedAppRefreshCycleChangeFPS[ type ] )
{
- g_nSteamCompMgrTargetFPS = target_fps;
+ g_nSteamCompMgrTargetFPSreq = target_fps;
}
if ( g_nCombinedAppRefreshCycleChangeRefresh[ type ] )
@@ -838,15 +842,15 @@ static void _update_app_target_refresh_cycle()
{
g_nDynamicRefreshRate[ type ] = *rate;
// Enable VRR as we have the correct refresh rate
- cv_adaptive_sync = true;
+ g_bVRRCanEnable = true;
return;
}
}
// Otherwise, disable VRR as we can't match the refresh rate 1-1
// (e.g., below 48hz).
- cv_adaptive_sync = false;
+ g_bVRRCanEnable = false;
} else {
- cv_adaptive_sync = false;
+ g_bVRRCanEnable = false;
}
}
@@ -864,9 +868,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 +5056,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 );
}
@@ -5109,7 +5113,7 @@ static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vb
int nRefreshHz = gamescope::ConvertmHzToHz( g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh );
int nTargetFPS = g_nSteamCompMgrTargetFPS;
- if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && nRefreshHz > nTargetFPS )
+ if ( g_nSteamCompMgrTargetFPS && (bShouldLimitFPS || b_bForceFrameLimit) && nRefreshHz > nTargetFPS )
{
int nVblankDivisor = nRefreshHz / nTargetFPS;
@@ -5485,7 +5489,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++)
@@ -5549,7 +5553,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
// Try to match refresh rate and have that set the cv_adaptive_sync only if it can
if (g_bVRRModesetting) update_app_target_refresh_cycle();
// otherwise, fall back to original behavior
- else cv_adaptive_sync = g_bVRRRequested;
+ else g_bVRRCanEnable = g_bVRRRequested;
}
if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal )
{
@@ -7634,6 +7638,23 @@ 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 and disable vrr on SteamUI
+ cv_adaptive_sync = false;
+ int nRealRefreshHz = gamescope::ConvertmHzToHz( g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh );
+ if (nRealRefreshHz > 100 && g_nSteamCompMgrTargetFPSreq > 34) {
+ g_nSteamCompMgrTargetFPS = nRealRefreshHz / 2;
+ b_bForceFrameLimit = true;
+ } else {
+ g_nSteamCompMgrTargetFPS = g_nSteamCompMgrTargetFPSreq;
+ b_bForceFrameLimit = false;
+ }
+ } else {
+ cv_adaptive_sync = g_bVRRCanEnable;
+ g_nSteamCompMgrTargetFPS = g_nSteamCompMgrTargetFPSreq;
+ b_bForceFrameLimit = false;
+ }
+
// We can always vblank if VRR.
const bool bVRR = GetBackend()->IsVRRActive();
if ( bVRR )
--
2.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Antheas Kapenekakis <git@antheas.dev>
Date: Fri, 1 Nov 2024 17:27:54 +0100
Subject: feat(battery): add atom for controlling frame halving
---
src/steamcompmgr.cpp | 6 ++++++
src/xwayland_ctx.hpp | 2 ++
2 files changed, 8 insertions(+)
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index bc238e4..d968de5 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -5919,6 +5919,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
@@ -7095,6 +7099,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);
diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp
index df2af70..e4eec9f 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.47.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 f014be9..6bb0b88 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -2669,6 +2669,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;
@@ -2724,7 +2727,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 )
@@ -2797,7 +2800,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 )
@@ -2828,7 +2837,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 );
@@ -3391,6 +3400,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 d8a24e9..00f5ece 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 d968de5..86773db 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -169,6 +169,8 @@ bool g_bVRRRequested = false;
bool g_bVRRCanEnable = 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
@@ -2271,7 +2273,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();
@@ -2322,6 +2324,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
@@ -5923,6 +5926,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
@@ -7100,6 +7107,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);
@@ -8067,9 +8075,10 @@ steamcompmgr_main(int argc, char **argv)
bShouldPaint = false;
}
- 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 e4eec9f..2347cbb 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.47.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: add rotation shader for rotating output
---
src/Backends/DRMBackend.cpp | 29 +++++++-
src/main.cpp | 6 ++
src/main.hpp | 1 +
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, 208 insertions(+), 19 deletions(-)
create mode 100644 src/shaders/cs_rotation.comp
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 6bb0b88..506963d 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -1752,7 +1752,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_bUseRotationShader)
{
int64_t imageH = frameInfo->layers[ i ].tex->contentHeight() / frameInfo->layers[ i ].scale.y;
@@ -2045,6 +2045,17 @@ namespace gamescope
void CDRMConnector::UpdateEffectiveOrientation( const drmModeModeInfo *pMode )
{
+ if (g_bUseRotationShader)
+ {
+ 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;
@@ -3035,6 +3046,15 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode )
g_bRotated = false;
g_nOutputWidth = mode->hdisplay;
g_nOutputHeight = mode->vdisplay;
+
+ if (g_bUseRotationShader && drm->pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL) {
+ g_bRotated = true;
+ g_nOutputWidth = mode->vdisplay;
+ g_nOutputHeight = mode->hdisplay;
+ } else {
+ g_bUseRotationShader = false;
+ }
+
break;
case GAMESCOPE_PANEL_ORIENTATION_90:
case GAMESCOPE_PANEL_ORIENTATION_270:
@@ -3294,6 +3314,11 @@ namespace gamescope
bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap);
+ if (g_bUseRotationShader)
+ {
+ bNeedsFullComposite = true;
+ }
+
bool bDoComposite = true;
if ( !bNeedsFullComposite && !bWantsPartialComposite )
{
@@ -3384,7 +3409,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_bUseRotationShader );
m_bWasCompositing = true;
diff --git a/src/main.cpp b/src/main.cpp
index 056e1c1..f61c88f 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 },
{ "enable-hacky-texture", no_argument, nullptr, 0 },
{ "force-panel-type", required_argument, nullptr, 0 },
@@ -198,6 +199,7 @@ const char usage[] =
" --enable-vrr-modesetting enable setting framerate while VRR is on in the internal display\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-panel-type lie to steam that the screen is external\n"
" --force-windows-fullscreen force windows inside of gamescope to be the size of the nested display (fullscreen)\n"
@@ -357,6 +359,8 @@ static gamescope::GamescopeModeGeneration parse_gamescope_mode_generation( const
}
}
+bool g_bUseRotationShader = false;
+
GamescopePanelOrientation g_DesiredInternalOrientation = GAMESCOPE_PANEL_ORIENTATION_AUTO;
static GamescopePanelOrientation force_orientation(const char *str)
{
@@ -797,6 +801,8 @@ int main(int argc, char **argv)
g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg );
} else if (strcmp(opt_name, "force-orientation") == 0 || strcmp(opt_name, "force-external-orientation") == 0) {
g_DesiredInternalOrientation = force_orientation( optarg );
+ } else if (strcmp(opt_name, "use-rotation-shader") == 0) {
+ g_bUseRotationShader = true;
} else if (strcmp(opt_name, "force-panel-type") == 0) {
g_FakeExternal = force_panel_type_external( optarg );
} else if (strcmp(opt_name, "custom-refresh-rates") == 0) {
diff --git a/src/main.hpp b/src/main.hpp
index 390c04a..2464afa 100644
--- a/src/main.hpp
+++ b/src/main.hpp
@@ -22,6 +22,7 @@ extern bool g_bForceRelativeMouse;
extern int g_nOutputRefresh; // mHz
extern bool g_bOutputHDREnabled;
extern bool g_bForceInternal;
+extern bool g_bUseRotationShader;
extern bool g_bFullscreen;
diff --git a/src/meson.build b/src/meson.build
index 74fc033..d4ff3ea 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -70,6 +70,7 @@ shader_src = [
'shaders/cs_nis.comp',
'shaders/cs_nis_fp16.comp',
'shaders/cs_rgb_to_nv12.comp',
+ 'shaders/cs_rotation.comp',
]
spirv_shaders = glsl_generator.process(shader_src)
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
index 54d7608..10d6c78 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"
@@ -898,6 +899,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++)
@@ -1128,6 +1130,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) {
@@ -3214,8 +3217,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_bUseRotationShader) {
+ 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" );
@@ -3223,7 +3234,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" );
@@ -3231,7 +3242,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" );
@@ -3246,7 +3257,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" );
@@ -3254,7 +3265,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" );
@@ -3262,7 +3273,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" );
@@ -3392,6 +3403,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()
{
@@ -3856,7 +3889,7 @@ std::optional<uint64_t> vulkan_screenshot( const struct FrameInfo_t *frameInfo,
extern std::string g_reshade_effect;
extern uint32_t g_reshade_technique_idx;
-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)
@@ -3928,7 +3961,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));
@@ -3971,7 +4012,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;
@@ -4009,7 +4058,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);
@@ -4019,14 +4076,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 00f5ece..8cffcbe 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -393,7 +393,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);
@@ -530,6 +530,9 @@ struct VulkanOutput_t
// NIS
gamescope::OwningRc<CVulkanTexture> nisScalerImage;
gamescope::OwningRc<CVulkanTexture> nisUsmImage;
+
+ // Rotated
+ gamescope::OwningRc<CVulkanTexture> rotatedOutput;
};
@@ -542,6 +545,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 0000000..1a47fd5
--- /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 1eeaa25..5aa986a 100644
--- a/src/wlserver.cpp
+++ b/src/wlserver.cpp
@@ -2519,6 +2519,11 @@ static void apply_touchscreen_orientation(double *x, double *y )
break;
}
+ if (g_bUseRotationShader) {
+ tx = 1.0 - *y;
+ ty = *x;
+ }
+
*x = tx;
*y = ty;
}
--
2.47.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: fix(nvidia): allow disabling Vulkan extension for nvidia to work
This adds a workaround for #1592 which removes the
VkPhysicalDevicePresentWaitFeaturesKHR extension in
the layer if the environment variable
GAMESCOPE_WSI_HIDE_PRESENT_WAIT_EXT is set.
This resolves the current freezing issue on nVidia
in dx12 (without having to set
VKD3D_DISABLE_EXTENSIONS), dx11 (without having
to patch DXVK not to use the extension) and in
native vulkan games.
---
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 718a260..f33da7f 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.47.1
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Ruan E. Formigoni" <ruanformigoni@gmail.com>
Date: Thu, 2 Jan 2025 17:07:36 +0100
Subject: feat: implemented bicubic downscaling
From https://github.com/ValveSoftware/gamescope/pull/740
---
README.md | 1 +
src/Backends/DRMBackend.cpp | 1 +
src/Backends/OpenVRBackend.cpp | 1 +
src/Backends/SDLBackend.cpp | 4 +
src/Backends/WaylandBackend.cpp | 1 +
src/main.cpp | 54 +++++++++-
src/main.hpp | 15 +++
src/meson.build | 1 +
src/rendervulkan.cpp | 61 ++++++++++-
src/rendervulkan.hpp | 2 +
src/shaders/bicubic.h | 44 ++++++++
src/shaders/cs_bicubic.comp | 177 ++++++++++++++++++++++++++++++++
src/shaders/descriptor_set.h | 1 +
src/steamcompmgr.cpp | 23 +++++
src/steamcompmgr.hpp | 1 +
src/xwayland_ctx.hpp | 1 +
16 files changed, 386 insertions(+), 2 deletions(-)
create mode 100644 src/shaders/bicubic.h
create mode 100644 src/shaders/cs_bicubic.comp
diff --git a/README.md b/README.md
index 97dea45..fefb2a0 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,7 @@ See `gamescope --help` for a full list of options.
* `-o`: set a frame-rate limit for the game when unfocused. Specified in frames per second. Defaults to unlimited.
* `-F fsr`: use AMD FidelityFX™ Super Resolution 1.0 for upscaling
* `-F nis`: use NVIDIA Image Scaling v1.0.3 for upscaling
+* `-F bicubic`: use a bicubic filter for downscaling
* `-S integer`: use integer scaling.
* `-S stretch`: use stretch scaling, the game will fill the window. (e.g. 4:3 to 16:9)
* `-b`: create a border-less window.
diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
index 506963d..98bdb71 100644
--- a/src/Backends/DRMBackend.cpp
+++ b/src/Backends/DRMBackend.cpp
@@ -3293,6 +3293,7 @@ namespace gamescope
bNeedsFullComposite |= bWasFirstFrame;
bNeedsFullComposite |= pFrameInfo->useFSRLayer0;
bNeedsFullComposite |= pFrameInfo->useNISLayer0;
+ bNeedsFullComposite |= pFrameInfo->useBICUBICLayer0;
bNeedsFullComposite |= pFrameInfo->blurLayer0;
bNeedsFullComposite |= bNeedsCompositeFromFilter;
bNeedsFullComposite |= !k_bUseCursorPlane && bDrewCursor;
diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp
index c39caa5..96a3d01 100644
--- a/src/Backends/OpenVRBackend.cpp
+++ b/src/Backends/OpenVRBackend.cpp
@@ -554,6 +554,7 @@ namespace gamescope
bNeedsFullComposite |= cv_composite_force;
bNeedsFullComposite |= pFrameInfo->useFSRLayer0;
bNeedsFullComposite |= pFrameInfo->useNISLayer0;
+ bNeedsFullComposite |= pFrameInfo->useBICUBICLayer0;
bNeedsFullComposite |= pFrameInfo->blurLayer0;
bNeedsFullComposite |= bNeedsCompositeFromFilter;
bNeedsFullComposite |= g_bColorSliderInUse;
diff --git a/src/Backends/SDLBackend.cpp b/src/Backends/SDLBackend.cpp
index 6d50f8d..c24b864 100644
--- a/src/Backends/SDLBackend.cpp
+++ b/src/Backends/SDLBackend.cpp
@@ -719,6 +719,10 @@ namespace gamescope
case KEY_B:
g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR;
break;
+ case KEY_K:
+ g_wantedDownscaleFilter = (g_wantedDownscaleFilter == GamescopeDownscaleFilter::BICUBIC) ?
+ GamescopeDownscaleFilter::LINEAR : GamescopeDownscaleFilter::BICUBIC;
+ break;
case KEY_U:
g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::FSR) ?
GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::FSR;
diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
index f90c096..9f136dd 100644
--- a/src/Backends/WaylandBackend.cpp
+++ b/src/Backends/WaylandBackend.cpp
@@ -1624,6 +1624,7 @@ namespace gamescope
bNeedsFullComposite |= cv_composite_force;
bNeedsFullComposite |= pFrameInfo->useFSRLayer0;
bNeedsFullComposite |= pFrameInfo->useNISLayer0;
+ bNeedsFullComposite |= pFrameInfo->useBICUBICLayer0;
bNeedsFullComposite |= pFrameInfo->blurLayer0;
bNeedsFullComposite |= bNeedsCompositeFromFilter;
bNeedsFullComposite |= g_bColorSliderInUse;
diff --git a/src/main.cpp b/src/main.cpp
index f61c88f..06a3bca 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -7,6 +7,7 @@
#include <mutex>
#include <vector>
#include <cstring>
+#include <sstream>
#include <string>
#if defined(__linux__)
#include <sys/capability.h>
@@ -303,11 +304,14 @@ bool g_bGrabbed = false;
float g_mouseSensitivity = 1.0;
GamescopeUpscaleFilter g_upscaleFilter = GamescopeUpscaleFilter::LINEAR;
+GamescopeDownscaleFilter g_downscaleFilter = GamescopeDownscaleFilter::LINEAR;
GamescopeUpscaleScaler g_upscaleScaler = GamescopeUpscaleScaler::AUTO;
GamescopeUpscaleFilter g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR;
+GamescopeDownscaleFilter g_wantedDownscaleFilter = GamescopeDownscaleFilter::LINEAR;
GamescopeUpscaleScaler g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO;
int g_upscaleFilterSharpness = 2;
+GamescopeBicubicParams g_bicubicParams;
gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT;
@@ -427,6 +431,54 @@ static enum GamescopeUpscaleFilter parse_upscaler_filter(const char *str)
}
}
+static enum GamescopeDownscaleFilter parse_downscaler_filter(const char *str)
+{
+ std::string_view arg{str};
+
+ // If the string is just 'bicubic' use default values
+ if ( arg == "bicubic" ) {
+ return GamescopeDownscaleFilter::BICUBIC;
+ }
+
+ // Arguments start after ':'
+ if ( auto search = arg.find(':'); search == std::string::npos ) {
+ fprintf( stderr, "gamescope: invalid argument for --filter=bicubic:float,float\n" );
+ exit(1);
+ } else {
+ arg = std::string_view(arg.data() + search + 1);
+ }
+
+ // Push arguments to stream
+ std::stringstream ss;
+ ss << arg;
+
+ // Validate arguments from stream
+ double b, c;
+ char comma;
+ if ((ss >> b >> comma >> c) && (comma == ',')) {
+ // clamp values
+ b = std::clamp(b, 0.0, 1.0);
+ c = std::clamp(c, 0.0, 1.0);
+ // Ovewrite default global parameters
+ g_bicubicParams.b = b;
+ g_bicubicParams.c = c;
+ // Set downscaler filters
+ return GamescopeDownscaleFilter::BICUBIC;
+ }
+
+ fprintf( stderr, "gamescope: invalid value for --filter\n" );
+ exit(1);
+}
+
+static void parse_filter(const char *str)
+{
+ if (std::string_view{str}.starts_with("bicubic")) {
+ g_wantedDownscaleFilter = parse_downscaler_filter(str);
+ } else {
+ g_wantedUpscaleFilter = parse_upscaler_filter(str);
+ }
+}
+
static enum gamescope::GamescopeBackend parse_backend_name(const char *str)
{
if (strcmp(str, "auto") == 0) {
@@ -756,7 +808,7 @@ int main(int argc, char **argv)
g_wantedUpscaleScaler = parse_upscaler_scaler(optarg);
break;
case 'F':
- g_wantedUpscaleFilter = parse_upscaler_filter(optarg);
+ parse_filter(optarg);
break;
case 'b':
g_bBorderlessOutputWindow = true;
diff --git a/src/main.hpp b/src/main.hpp
index 2464afa..040d04c 100644
--- a/src/main.hpp
+++ b/src/main.hpp
@@ -43,6 +43,18 @@ enum class GamescopeUpscaleFilter : uint32_t
FROM_VIEW = 0xF, // internal
};
+enum class GamescopeDownscaleFilter : uint32_t
+{
+ LINEAR = 0,
+ BICUBIC,
+};
+
+struct GamescopeBicubicParams
+{
+ float b = 0.3f;
+ float c = 0.3f;
+};
+
static constexpr bool DoesHardwareSupportUpscaleFilter( GamescopeUpscaleFilter eFilter )
{
// Could do nearest someday... AMDGPU DC supports custom tap placement to an extent.
@@ -60,10 +72,13 @@ enum class GamescopeUpscaleScaler : uint32_t
};
extern GamescopeUpscaleFilter g_upscaleFilter;
+extern GamescopeDownscaleFilter g_downscaleFilter;
extern GamescopeUpscaleScaler g_upscaleScaler;
extern GamescopeUpscaleFilter g_wantedUpscaleFilter;
+extern GamescopeDownscaleFilter g_wantedDownscaleFilter;
extern GamescopeUpscaleScaler g_wantedUpscaleScaler;
extern int g_upscaleFilterSharpness;
+extern GamescopeBicubicParams g_bicubicParams;
extern bool g_bBorderlessOutputWindow;
diff --git a/src/meson.build b/src/meson.build
index d4ff3ea..341bace 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -66,6 +66,7 @@ shader_src = [
'shaders/cs_composite_rcas.comp',
'shaders/cs_easu.comp',
'shaders/cs_easu_fp16.comp',
+ 'shaders/cs_bicubic.comp',
'shaders/cs_gaussian_blur_horizontal.comp',
'shaders/cs_nis.comp',
'shaders/cs_nis_fp16.comp',
diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
index 10d6c78..8b31c1e 100644
--- a/src/rendervulkan.cpp
+++ b/src/rendervulkan.cpp
@@ -44,6 +44,7 @@
#include "cs_composite_rcas.h"
#include "cs_easu.h"
#include "cs_easu_fp16.h"
+#include "cs_bicubic.h"
#include "cs_gaussian_blur_horizontal.h"
#include "cs_nis.h"
#include "cs_nis_fp16.h"
@@ -53,6 +54,7 @@
#define A_CPU
#include "shaders/ffx_a.h"
#include "shaders/ffx_fsr1.h"
+#include "shaders/bicubic.h"
#include "reshade_effect_manager.hpp"
@@ -888,6 +890,7 @@ bool CVulkanDevice::createShaders()
SHADER(BLUR_COND, cs_composite_blur_cond);
SHADER(BLUR_FIRST_PASS, cs_gaussian_blur_horizontal);
SHADER(RCAS, cs_composite_rcas);
+ SHADER(BICUBIC, cs_bicubic);
if (m_bSupportsFp16)
{
SHADER(EASU, cs_easu_fp16);
@@ -1128,6 +1131,7 @@ void CVulkanDevice::compileAllPipelines()
SHADER(BLUR_FIRST_PASS, 1, 2, 1);
SHADER(RCAS, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, 1);
SHADER(EASU, 1, 1, 1);
+ SHADER(BICUBIC, 1, 1, 1);
SHADER(NIS, 1, 1, 1);
SHADER(RGB_TO_NV12, 1, 1, 1);
SHADER(ROTATION, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, k_nMaxBlurLayers);
@@ -3724,6 +3728,17 @@ struct EasuPushData_t
}
};
+struct BicubicPushData_t
+{
+ uvec4_t Const0;
+ uvec4_t Const1;
+
+ BicubicPushData_t(float B, float C, uint32_t inputX, uint32_t inputY, uint32_t tempX, uint32_t tempY, uint32_t winX, uint32_t winY)
+ {
+ BicubicCon(&Const0.x, &Const1.x, B*10, C*10, inputX, inputY, tempX, tempY, winX, winY);
+ }
+};
+
struct RcasPushData_t
{
uvec2_t u_layer0Offset;
@@ -3933,7 +3948,51 @@ std::optional<uint64_t> vulkan_composite( struct FrameInfo_t *frameInfo, gamesco
for (uint32_t i = 0; i < EOTF_Count; i++)
cmdBuffer->bindColorMgmtLuts(i, frameInfo->shaperLut[i], frameInfo->lut3D[i]);
- if ( frameInfo->useFSRLayer0 )
+ if ( frameInfo->useBICUBICLayer0 )
+ {
+ uint32_t inputX = frameInfo->layers[0].tex->width();
+ uint32_t inputY = frameInfo->layers[0].tex->height();
+
+ uint32_t tempX = frameInfo->layers[0].integerWidth();
+ uint32_t tempY = frameInfo->layers[0].integerHeight();
+
+ update_tmp_images(tempX, tempY);
+
+ cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BICUBIC, frameInfo->layerCount, frameInfo->ycbcrMask()));
+ cmdBuffer->bindTarget(g_output.tmpOutput);
+ cmdBuffer->bindTexture(0, frameInfo->layers[0].tex);
+ cmdBuffer->setTextureSrgb(0, true);
+ cmdBuffer->setSamplerUnnormalized(0, false);
+ cmdBuffer->setSamplerNearest(0, false);
+ cmdBuffer->uploadConstants<BicubicPushData_t>(g_bicubicParams.b
+ , g_bicubicParams.c
+ , inputX
+ , inputY
+ , tempX
+ , tempY
+ , currentOutputWidth
+ , currentOutputHeight
+ );
+
+ int pixelsPerGroup = 16;
+
+ cmdBuffer->dispatch(div_roundup(tempX, pixelsPerGroup), div_roundup(tempY, pixelsPerGroup));
+
+ struct FrameInfo_t bicFrameInfo = *frameInfo;
+ bicFrameInfo.layers[0].tex = g_output.tmpOutput;
+ bicFrameInfo.layers[0].scale.x = 1.0f;
+ bicFrameInfo.layers[0].scale.y = 1.0f;
+
+ cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, bicFrameInfo.layerCount, bicFrameInfo.ycbcrMask()));
+ bind_all_layers(cmdBuffer.get(), &bicFrameInfo);
+ cmdBuffer->bindTarget(compositeImage);
+ cmdBuffer->uploadConstants<BlitPushData_t>(&bicFrameInfo);
+
+ pixelsPerGroup = 8;
+
+ cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup));
+ }
+ else if ( frameInfo->useFSRLayer0 )
{
uint32_t inputX = frameInfo->layers[0].tex->width();
uint32_t inputY = frameInfo->layers[0].tex->height();
diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp
index 8cffcbe..91e2af7 100644
--- a/src/rendervulkan.hpp
+++ b/src/rendervulkan.hpp
@@ -270,6 +270,7 @@ struct FrameInfo_t
{
bool useFSRLayer0;
bool useNISLayer0;
+ bool useBICUBICLayer0;
bool bFadingOut;
BlurMode blurLayer0;
int blurRadius;
@@ -544,6 +545,7 @@ enum ShaderType {
SHADER_TYPE_EASU,
SHADER_TYPE_RCAS,
SHADER_TYPE_NIS,
+ SHADER_TYPE_BICUBIC,
SHADER_TYPE_RGB_TO_NV12,
SHADER_TYPE_ROTATION,
diff --git a/src/shaders/bicubic.h b/src/shaders/bicubic.h
new file mode 100644
index 0000000..8117e87
--- /dev/null
+++ b/src/shaders/bicubic.h
@@ -0,0 +1,44 @@
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+//
+//
+// BICUBIC IMAGE SCALING
+//
+//
+//------------------------------------------------------------------------------------------------------------------------------
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+// CONSTANT SETUP
+//==============================================================================================================================
+// Call to setup required constant values (works on CPU or GPU).
+A_STATIC void BicubicCon(
+outAU4 con0,
+outAU4 con1,
+// Configurable parameters
+AU1 B,
+AU1 C,
+// This the rendered image resolution
+AF1 inputRenderedSizeX,
+AF1 inputRenderedSizeY,
+// This is the resolution of the resource containing the input image (useful for dynamic resolution)
+AF1 inputCurrentSizeX,
+AF1 inputCurrentSizeY,
+// This is the window width / height
+AF1 outputTargetSizeX,
+AF1 outputTargetSizeY)
+{
+ // Input/Output size information
+ con0[0]=AU1_AF1(inputRenderedSizeX);
+ con0[1]=AU1_AF1(inputRenderedSizeY);
+ con0[2]=AU1_AF1(inputCurrentSizeX);
+ con0[3]=AU1_AF1(inputCurrentSizeY);
+
+ // Viewport pixel position to normalized image space.
+ con1[0]=AU1_AF1(outputTargetSizeX);
+ con1[1]=AU1_AF1(outputTargetSizeY);
+ con1[2]=B;
+ con1[3]=C;
+}
diff --git a/src/shaders/cs_bicubic.comp b/src/shaders/cs_bicubic.comp
new file mode 100644
index 0000000..2b6dfb8
--- /dev/null
+++ b/src/shaders/cs_bicubic.comp
@@ -0,0 +1,177 @@
+// References
+// https://www.codeproject.com/Articles/236394/Bi-Cubic-and-Bi-Linear-Interpolation-with-GLSL
+// https://stackoverflow.com/questions/13501081/efficient-bicubic-filtering-code-in-glsl
+// https://web.archive.org/web/20180927181721/http://www.java-gaming.org/index.php?topic=35123.0
+// https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
+
+#version 460
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
+#extension GL_EXT_scalar_block_layout : require
+
+#include "descriptor_set.h"
+
+layout(
+ local_size_x = 64,
+ local_size_y = 1,
+ local_size_z = 1) in;
+
+// Push constant is a mechanism in modern OpenGL that allows passing small amounts of frequently
+// updated data to the shader without needing to bind a buffer
+layout(binding = 0, scalar)
+uniform layers_t {
+ uvec4 c0, c1;
+};
+
+#define A_GPU 1
+#define A_GLSL 1
+#define A_HALF 1
+#include "ffx_a.h"
+#include "bicubic.h"
+
+// The MitchellNetravali filters or BC-splines
+// https://en.wikipedia.org/wiki/Mitchell%E2%80%93Netravali_filters
+// Conditionals are slow in GPU code, so to represent 0 <= f < 1 and 1 <= f < 2
+// the P(d) form shown in the wikipedia page is used
+vec4 mitchellNetravaliWeights(float f, float B, float C)
+{
+ float w0 = ((12.0 - 9.0 * B - 6.0 * C) * pow(f, 3.0)) +
+ ((-18.0 + 12.0 * B + 6.0 * C) * pow(f, 2.0)) +
+ (6.0 - 2.0 * B);
+
+ float w1 = ((-B - 6.0 * C) * pow(f - 1.0, 3.0)) +
+ ((6.0 * B + 30.0 * C) * pow(f - 1.0, 2.0)) +
+ ((-12.0 * B - 48.0 * C) * (f - 1.0)) +
+ (8.0 * B + 24.0 * C);
+
+ float w2 = ((12.0 - 9.0 * B - 6.0 * C) * pow(1.0 - f, 3.0)) +
+ ((-18.0 + 12.0 * B + 6.0 * C) * pow(1.0 - f, 2.0)) +
+ (6.0 - 2.0 * B);
+
+ float w3 = ((-B - 6.0 * C) * pow(2.0 - f, 3.0)) +
+ ((6.0 * B + 30.0 * C) * pow(2.0 - f, 2.0)) +
+ ((-12.0 * B - 48.0 * C) * (2.0 - f)) +
+ (8.0 * B + 24.0 * C);
+
+ return vec4(w0, w1, w2, w3);
+}
+
+// https://stackoverflow.com/questions/13501081/efficient-bicubic-filtering-code-in-glsl
+// https://web.archive.org/web/20180927181721/http://www.java-gaming.org/index.php?topic=35123.0
+// This is an efficient method to implement bicubic filtering, it takes
+// advantage of the fact that the bilinear approach gives the weighted average
+// of a 2x2 area.
+vec4 textureBicubic(sampler2D splr, vec2 texCoords)
+{
+ vec2 texSize = textureSize(splr, 0);
+ vec2 invTexSize = 1.0 / texSize;
+
+ // Converts normalized coordinates into pixel-space coordinate
+ // Example: If texCoords is (0.5, 0.5), and the texture size is (1920, 1080), the result will be
+ // (960, 540)—the center of the texture in pixel space.
+ // Subtracting 0.5 ensures that you're sampling from the center of the texel rather than its corner
+ // Example: Assume we have a 3x3 texture and texCoords = (0.5, 0.5):
+ // [0,0][1,0][2,0]
+ // [0,1][1,1][2,1]
+ // [0,2][1,2][2,2]
+ // texCoords * texSize - 0.5 maps to (1.5, 1.5), which is between (1,1) and (2,2), then
+ // subtracts 0.5 to move it to (1.0, 1.0)—the center of the texel
+ texCoords = texCoords * texSize - 0.5;
+
+ // Get B and C that were pushed from the user input (or default values)
+ float B = c1[2] / 10.0f;
+ float C = c1[3] / 10.0f;
+
+ // Get the fractional part of the coordinates
+ // They are used in Mitchell Netravali's strategy to calculate the interpolation weights,
+ // i.e., how much influence the neighboring vertices have on the final pixel value
+ vec2 fxy = fract(texCoords);
+ texCoords -= fxy;
+
+ // Calculate bicubic weights
+ // These weights represent how much influence each neighboring texel in the 4x4 grid will have
+ // on the final interpolated pixel value
+ vec4 xweights = mitchellNetravaliWeights(fxy.x, B, C);
+ vec4 yweights = mitchellNetravaliWeights(fxy.y, B, C);
+
+ // Modify the current texture coordinates to have an offset in texels for each coordinate
+ // E.g. texCoords + vec(-1.0, 0.0) is a texel to the left
+ // texCoords + vec(1.0, 0.0) is a texel to the right
+ // texCoords + vec(0.0, 1.0) is a texel downwards
+ // texCoords + vec(0.0, -1.0) is a texel upwards
+ vec4 offsetTexels = texCoords.xxyy;
+ offsetTexels += vec2 (-1.0, +1.0).xyxy;
+ // Normalize weights to range between (0,1)
+ // vec4 sumWeights = vec4(xweights.xz + xweights.yw, yweights.xz + yweights.yw);
+ // vec4 normalizedWeights = vec4 (xweights.yw, yweights.yw) / sumWeights;
+ vec4 sumWeights = vec4(xweights.x + xweights.y, xweights.z + xweights.w, yweights.x + yweights.y, yweights.z + yweights.w);
+ vec4 normalizedWeights = vec4 (xweights.y, xweights.w, yweights.y, yweights.w) / sumWeights;
+ // Use the weights to influence the sampling position inside each texel
+ // Each texel has a size from (0,1)
+ vec4 offsetSampler = offsetTexels + normalizedWeights;
+ // Go back to normalized space
+ offsetSampler *= invTexSize.xxyy;
+ // Perform the sampling
+ vec4 sample0 = texture(splr, offsetSampler.xz);
+ vec4 sample1 = texture(splr, offsetSampler.yz);
+ vec4 sample2 = texture(splr, offsetSampler.xw);
+ vec4 sample3 = texture(splr, offsetSampler.yw);
+
+ // Now we perform linear interpolation in the selected points
+ // The mix(a, b, t) function in GLSL performs linear interpolation between a and b based on the
+ // parameter t, t is between 0 and 1
+ // https://registry.khronos.org/OpenGL-Refpages/gl4/html/mix.xhtml
+
+ // Here we want to normalize sx and sy to between 0 and 1 (t value)
+ float sx = sumWeights.x / (sumWeights.x + sumWeights.y);
+ float sy = sumWeights.z / (sumWeights.z + sumWeights.w);
+
+ return mix(
+ mix(sample3, sample2, sx), mix(sample1, sample0, sx)
+ , sy);
+}
+
+void bicPass(uvec2 pos)
+{
+ // Retrieve pushed values
+ AF2 inputRenderedSize = AF2_AU2(c0.xy);
+ AF2 inputCurrentSize = AF2_AU2(c0.zw);
+ AF2 outputTargetSize = AF2_AU2(c1.xy);
+
+ // ARcpF1(x) == 1.0 / x
+ // scaleFactor is the division between the rendered image and the size it should have at the end
+ // E.g.: Rendered 1920x1080, window size is 960x540, then scaleFactor is 2x2
+ AF2 scaleFactor = inputRenderedSize * vec2(ARcpF1(inputCurrentSize.x), ARcpF1(inputCurrentSize.y));
+
+ // The parameter pos of this function is used to iterate over the output image (e.g. 960x540)
+ // The position of the processed pixel should be taken from the rendered image (e.g. 1920x1080)
+ // 10x10 in the output, corresponds to 20x20 in the original image
+ AF2 positionPixel=AF2(pos)*scaleFactor;
+
+ // Normalize the image space to be between [0,1]
+ positionPixel=positionPixel*vec2(ARcpF1(inputRenderedSize.x),ARcpF1(inputRenderedSize.y));
+
+ // Apply the bicubic algorithm in the normalized pixel position
+ vec4 bicPass = textureBicubic(s_samplers[0], positionPixel);
+
+ imageStore(dst, ivec2(pos), bicPass);
+}
+
+
+void main()
+{
+ // AMD recommends to use this swizzle and to process 4 pixel per invocation
+ // for better cache utilisation
+ uvec2 pos = ARmp8x8(gl_LocalInvocationID.x) + uvec2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
+
+ bicPass(pos);
+ pos.x += 8u;
+ bicPass(pos);
+ pos.y += 8u;
+ bicPass(pos);
+ pos.x -= 8u;
+ bicPass(pos);
+}
+
+/* vim: set expandtab ft=cpp fdm=marker ts=4 sw=4 tw=100 et :*/
diff --git a/src/shaders/descriptor_set.h b/src/shaders/descriptor_set.h
index f2b8527..64cc1c9 100644
--- a/src/shaders/descriptor_set.h
+++ b/src/shaders/descriptor_set.h
@@ -21,6 +21,7 @@ const int filter_nearest = 1;
const int filter_fsr = 2;
const int filter_nis = 3;
const int filter_pixel = 4;
+const int filter_bicubic = 5;
const int filter_from_view = 255;
const int EOTF_Gamma22 = 0;
diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp
index 86773db..f88576f 100644
--- a/src/steamcompmgr.cpp
+++ b/src/steamcompmgr.cpp
@@ -906,6 +906,7 @@ gamescope::ConCommand cc_debug_set_fps_limit( "debug_set_fps_limit", "Set refres
static int g_nRuntimeInfoFd = -1;
bool g_bFSRActive = false;
+bool g_bBicubicActive = false;
BlurMode g_BlurMode = BLUR_MODE_OFF;
BlurMode g_BlurModeOld = BLUR_MODE_OFF;
@@ -2389,6 +2390,10 @@ paint_all(bool async, bool dpms)
paint_window(w, w, &frameInfo, global_focus.cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders, 1.0f, override);
bool needsScaling = frameInfo.layers[0].scale.x < 0.999f && frameInfo.layers[0].scale.y < 0.999f;
+ // Temporarily allow upscaling as well
+ // bool needsDownScaling = frameInfo.layers[0].scale.x > 1.001f && frameInfo.layers[0].scale.y > 1.001f;
+ bool needsDownScaling = true;
+ frameInfo.useBICUBICLayer0 = g_downscaleFilter == GamescopeDownscaleFilter::BICUBIC && needsDownScaling;
frameInfo.useFSRLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::FSR && needsScaling;
frameInfo.useNISLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::NIS && needsScaling;
}
@@ -2521,10 +2526,12 @@ paint_all(bool async, bool dpms)
}
frameInfo.useFSRLayer0 = false;
+ frameInfo.useBICUBICLayer0 = false;
frameInfo.useNISLayer0 = false;
}
g_bFSRActive = frameInfo.useFSRLayer0;
+ g_bBicubicActive = frameInfo.useBICUBICLayer0;
g_bFirstFrame = false;
@@ -5445,6 +5452,9 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev)
g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO;
g_wantedUpscaleFilter = GamescopeUpscaleFilter::NIS;
break;
+ case 5:
+ g_wantedDownscaleFilter = GamescopeDownscaleFilter::BICUBIC;
+ break;
}
hasRepaint = true;
}
@@ -7023,6 +7033,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_
ctx->atoms.gamescopeLowLatency = XInternAtom( ctx->dpy, "GAMESCOPE_LOW_LATENCY", false );
ctx->atoms.gamescopeFSRFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_FSR_FEEDBACK", false );
+ ctx->atoms.gamescopeBicubicFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_BICUBIC_FEEDBACK", false );
ctx->atoms.gamescopeBlurMode = XInternAtom( ctx->dpy, "GAMESCOPE_BLUR_MODE", false );
ctx->atoms.gamescopeBlurRadius = XInternAtom( ctx->dpy, "GAMESCOPE_BLUR_RADIUS", false );
@@ -7281,6 +7292,7 @@ extern int g_nPreferredOutputWidth;
extern int g_nPreferredOutputHeight;
static bool g_bWasFSRActive = false;
+static bool g_bWasBicubicActive = false;
bool g_bAppWantsHDRCached = false;
@@ -7695,6 +7707,16 @@ steamcompmgr_main(int argc, char **argv)
flush_root = true;
}
+ if ( g_bBicubicActive != g_bWasBicubicActive )
+ {
+ uint32_t active = g_bBicubicActive ? 1 : 0;
+ XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeBicubicFeedback, XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *)&active, 1 );
+
+ g_bWasBicubicActive = g_bBicubicActive;
+ flush_root = true;
+ }
+
if (global_focus.IsDirty())
determine_and_apply_focus();
@@ -7931,6 +7953,7 @@ steamcompmgr_main(int argc, char **argv)
g_bSteamIsActiveWindow = false;
g_upscaleScaler = g_wantedUpscaleScaler;
g_upscaleFilter = g_wantedUpscaleFilter;
+ g_downscaleFilter = g_wantedDownscaleFilter;
}
// If we're in the middle of a fade, then keep us
diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp
index 30e48e8..5679a0c 100644
--- a/src/steamcompmgr.hpp
+++ b/src/steamcompmgr.hpp
@@ -129,6 +129,7 @@ extern float focusedWindowOffsetY;
extern bool g_FakeExternal;
extern bool g_bFSRActive;
+extern bool g_bBicubicActive;
extern uint32_t inputCounter;
extern uint64_t g_lastWinSeq;
diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp
index 2347cbb..bc38c98 100644
--- a/src/xwayland_ctx.hpp
+++ b/src/xwayland_ctx.hpp
@@ -164,6 +164,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable
Atom gamescopeLowLatency;
Atom gamescopeFSRFeedback;
+ Atom gamescopeBicubicFeedback;
Atom gamescopeBlurMode;
Atom gamescopeBlurRadius;
--
2.47.1