summaryrefslogtreecommitdiff
path: root/hosts/iroha/gentoo/patches/gui-wm
diff options
context:
space:
mode:
Diffstat (limited to 'hosts/iroha/gentoo/patches/gui-wm')
-rw-r--r--hosts/iroha/gentoo/patches/gui-wm/gamescope/1826.diff84
-rw-r--r--hosts/iroha/gentoo/patches/gui-wm/gamescope/740_rebase.diff693
2 files changed, 777 insertions, 0 deletions
diff --git a/hosts/iroha/gentoo/patches/gui-wm/gamescope/1826.diff b/hosts/iroha/gentoo/patches/gui-wm/gamescope/1826.diff
new file mode 100644
index 0000000..69c85e8
--- /dev/null
+++ b/hosts/iroha/gentoo/patches/gui-wm/gamescope/1826.diff
@@ -0,0 +1,84 @@
+diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp
+index 7e68a44fb..65e35f525 100644
+--- a/src/Backends/WaylandBackend.cpp
++++ b/src/Backends/WaylandBackend.cpp
+@@ -2002,24 +2002,32 @@ namespace gamescope
+ // Prefer opaque for composition on the Wayland backend.
+
+ uint32_t u8BitFormat = DRM_FORMAT_INVALID;
+- if ( SupportsFormat( DRM_FORMAT_XRGB8888 ) )
+- u8BitFormat = DRM_FORMAT_XRGB8888;
+- else if ( SupportsFormat( DRM_FORMAT_XBGR8888 ) )
+- u8BitFormat = DRM_FORMAT_XBGR8888;
+- else if ( SupportsFormat( DRM_FORMAT_ARGB8888 ) )
+- u8BitFormat = DRM_FORMAT_ARGB8888;
+- else if ( SupportsFormat( DRM_FORMAT_ABGR8888 ) )
+- u8BitFormat = DRM_FORMAT_ABGR8888;
++ for (int i = 0; i < 2 && u8BitFormat == DRM_FORMAT_INVALID; i++)
++ {
++ bool invalidOnly = i == 0;
++ if ( SupportsFormat( DRM_FORMAT_XRGB8888, invalidOnly ) )
++ u8BitFormat = DRM_FORMAT_XRGB8888;
++ else if ( SupportsFormat( DRM_FORMAT_XBGR8888, invalidOnly ) )
++ u8BitFormat = DRM_FORMAT_XBGR8888;
++ else if ( SupportsFormat( DRM_FORMAT_ARGB8888, invalidOnly ) )
++ u8BitFormat = DRM_FORMAT_ARGB8888;
++ else if ( SupportsFormat( DRM_FORMAT_ABGR8888, invalidOnly ) )
++ u8BitFormat = DRM_FORMAT_ABGR8888;
++ }
+
+ uint32_t u10BitFormat = DRM_FORMAT_INVALID;
+- if ( SupportsFormat( DRM_FORMAT_XBGR2101010 ) )
+- u10BitFormat = DRM_FORMAT_XBGR2101010;
+- else if ( SupportsFormat( DRM_FORMAT_XRGB2101010 ) )
+- u10BitFormat = DRM_FORMAT_XRGB2101010;
+- else if ( SupportsFormat( DRM_FORMAT_ABGR2101010 ) )
+- u10BitFormat = DRM_FORMAT_ABGR2101010;
+- else if ( SupportsFormat( DRM_FORMAT_ARGB2101010 ) )
+- u10BitFormat = DRM_FORMAT_ARGB2101010;
++ for (int i = 0; i < 2 && u10BitFormat == DRM_FORMAT_INVALID; i++)
++ {
++ bool invalidOnly = i == 0;
++ if ( SupportsFormat( DRM_FORMAT_XBGR2101010, invalidOnly ) )
++ u10BitFormat = DRM_FORMAT_XBGR2101010;
++ else if ( SupportsFormat( DRM_FORMAT_XRGB2101010, invalidOnly ) )
++ u10BitFormat = DRM_FORMAT_XRGB2101010;
++ else if ( SupportsFormat( DRM_FORMAT_ABGR2101010, invalidOnly ) )
++ u10BitFormat = DRM_FORMAT_ABGR2101010;
++ else if ( SupportsFormat( DRM_FORMAT_ARGB2101010, invalidOnly ) )
++ u10BitFormat = DRM_FORMAT_ARGB2101010;
++ }
+
+ assert( u8BitFormat != DRM_FORMAT_INVALID );
+
+diff --git a/src/backend.h b/src/backend.h
+index 29219dbc8..2767546cd 100644
+--- a/src/backend.h
++++ b/src/backend.h
+@@ -309,9 +309,11 @@ namespace gamescope
+
+ virtual bool UsesModifiers() const = 0;
+ virtual std::span<const uint64_t> GetSupportedModifiers( uint32_t uDrmFormat ) const = 0;
+- inline bool SupportsFormat( uint32_t uDrmFormat ) const
++ inline bool SupportsFormat( uint32_t uDrmFormat, bool invalidOnly ) const
+ {
+- return Algorithm::Contains( this->GetSupportedModifiers( uDrmFormat ), DRM_FORMAT_MOD_INVALID );
++ if ( invalidOnly )
++ return Algorithm::Contains( this->GetSupportedModifiers( uDrmFormat ), DRM_FORMAT_MOD_INVALID );
++ return !this->GetSupportedModifiers( uDrmFormat ).empty();
+ }
+
+ virtual IBackendConnector *GetCurrentConnector() = 0;
+diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp
+index b8412b8fd..f41ef4c9b 100644
+--- a/src/rendervulkan.cpp
++++ b/src/rendervulkan.cpp
+@@ -2814,7 +2814,7 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat)
+ }
+ else
+ {
+- if ( GetBackend()->UsesModifiers() && !GetBackend()->SupportsFormat( drmFormat ) )
++ if ( GetBackend()->UsesModifiers() && !GetBackend()->SupportsFormat( drmFormat, true ) )
+ return false;
+
+ wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, DRM_FORMAT_MOD_INVALID );
diff --git a/hosts/iroha/gentoo/patches/gui-wm/gamescope/740_rebase.diff b/hosts/iroha/gentoo/patches/gui-wm/gamescope/740_rebase.diff
new file mode 100644
index 0000000..79a87eb
--- /dev/null
+++ b/hosts/iroha/gentoo/patches/gui-wm/gamescope/740_rebase.diff
@@ -0,0 +1,693 @@
+diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp
+index 1ec2699..26c0afc 100644
+--- a/src/Backends/DRMBackend.cpp
++++ b/src/Backends/DRMBackend.cpp
+@@ -3424,6 +3424,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 1f0e822..6756835 100644
+--- a/src/Backends/OpenVRBackend.cpp
++++ b/src/Backends/OpenVRBackend.cpp
+@@ -1431,6 +1431,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 2bb8fb0..87b1bed 100644
+--- a/src/Backends/SDLBackend.cpp
++++ b/src/Backends/SDLBackend.cpp
+@@ -736,7 +736,7 @@ namespace gamescope
+ if ( event.key.keysym.mod & KMOD_LGUI )
+ {
+ uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode );
+- const uint32_t shortcutKeys[] = {KEY_F, KEY_N, KEY_B, KEY_U, KEY_Y, KEY_I, KEY_O, KEY_S, KEY_G};
++ const uint32_t shortcutKeys[] = {KEY_F, KEY_N, KEY_B, KEY_K, KEY_U, KEY_Y, KEY_I, KEY_O, KEY_S, KEY_G};
+ const bool isShortcutKey = std::find(std::begin(shortcutKeys), std::end(shortcutKeys), key) != std::end(shortcutKeys);
+ if ( isShortcutKey )
+ {
+@@ -764,6 +764,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 664ed0e..d942cd6 100644
+--- a/src/Backends/WaylandBackend.cpp
++++ b/src/Backends/WaylandBackend.cpp
+@@ -1022,6 +1022,7 @@ namespace gamescope
+ bNeedsFullComposite |= cv_composite_force;
+ bNeedsFullComposite |= pFrameInfo->useFSRLayer0;
+ bNeedsFullComposite |= pFrameInfo->useNISLayer0;
++ bNeedsFullComposite |= pFrameInfo->useBICUBICLayer0;
+ bNeedsFullComposite |= pFrameInfo->blurLayer0;
+ bNeedsFullComposite |= bNeedsCompositeFromFilter;
+ bNeedsFullComposite |= g_bColorSliderInUse;
+@@ -2877,6 +2878,16 @@ namespace gamescope
+ return;
+ }
+
++ case KEY_K:
++ {
++ if ( !bPressed )
++ {
++ g_wantedDownscaleFilter = (g_wantedDownscaleFilter == GamescopeDownscaleFilter::BICUBIC) ?
++ GamescopeDownscaleFilter::LINEAR : GamescopeDownscaleFilter::BICUBIC;
++ }
++ return;
++ }
++
+ case KEY_Y:
+ {
+ if ( !bPressed )
+diff --git a/src/main.cpp b/src/main.cpp
+index cdb35c3..78e072b 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>
+@@ -297,11 +298,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;
+
+@@ -406,6 +410,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) {
+@@ -736,7 +788,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 2e6fb83..f8cc030 100644
+--- a/src/main.hpp
++++ b/src/main.hpp
+@@ -40,6 +40,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.
+@@ -57,10 +69,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 a3dfdab..5d3019f 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 f79d26e..cc7edbb 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"
+@@ -52,6 +53,7 @@
+ #define A_CPU
+ #include "shaders/ffx_a.h"
+ #include "shaders/ffx_fsr1.h"
++#include "shaders/bicubic.h"
+
+ #include "reshade_effect_manager.hpp"
+
+@@ -912,6 +914,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);
+@@ -1151,6 +1154,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);
+ #undef SHADER
+@@ -3732,6 +3736,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;
+@@ -3950,7 +3965,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 63cc602..4092e82 100644
+--- a/src/rendervulkan.hpp
++++ b/src/rendervulkan.hpp
+@@ -281,6 +281,7 @@ struct FrameInfo_t
+ {
+ bool useFSRLayer0;
+ bool useNISLayer0;
++ bool useBICUBICLayer0;
+ bool bFadingOut;
+ BlurMode blurLayer0;
+ int blurRadius;
+@@ -556,6 +557,7 @@ enum ShaderType {
+ SHADER_TYPE_EASU,
+ SHADER_TYPE_RCAS,
+ SHADER_TYPE_NIS,
++ SHADER_TYPE_BICUBIC,
+ SHADER_TYPE_RGB_TO_NV12,
+
+ SHADER_TYPE_COUNT
+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 Mitchell–Netravali 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 c1d49fd..44cb223 100644
+--- a/src/steamcompmgr.cpp
++++ b/src/steamcompmgr.cpp
+@@ -1015,6 +1015,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;
+@@ -2555,6 +2556,8 @@ paint_all( global_focus_t *pFocus, bool async )
+ paint_window(w, w, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders, 1.0f, override);
+
+ bool needsScaling = frameInfo.layers[0].scale.x < 0.999f && frameInfo.layers[0].scale.y < 0.999f;
++ bool needsDownScaling = true;
++ frameInfo.useBICUBICLayer0 = g_downscaleFilter == GamescopeDownscaleFilter::BICUBIC && needsDownScaling;
+ frameInfo.useFSRLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::FSR && needsScaling;
+ frameInfo.useNISLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::NIS && needsScaling;
+ }
+@@ -2696,6 +2699,7 @@ paint_all( global_focus_t *pFocus, bool async )
+ }
+
+ frameInfo.useFSRLayer0 = false;
++ frameInfo.useBICUBICLayer0 = false;
+ frameInfo.useNISLayer0 = false;
+ }
+
+@@ -5761,6 +5765,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;
+ }
+@@ -7391,6 +7398,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 );
+@@ -7645,6 +7653,7 @@ extern int g_nPreferredOutputWidth;
+ extern int g_nPreferredOutputHeight;
+
+ static bool g_bWasFSRActive = false;
++static bool g_bWasBicubicActive = false;
+
+ bool g_bAppWantsHDRCached = false;
+
+@@ -8091,6 +8100,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;
++ }
++
+ static gamescope::VirtualConnectorStrategy s_eLastVirtualConnectorStrategy = gamescope::cv_backend_virtual_connector_strategy;
+ gamescope::VirtualConnectorStrategy eVirtualConnectorStrategy = gamescope::cv_backend_virtual_connector_strategy;
+
+@@ -8439,6 +8458,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 98e9272..ea7de13 100644
+--- a/src/steamcompmgr.hpp
++++ b/src/steamcompmgr.hpp
+@@ -129,6 +129,7 @@ extern float focusedWindowOffsetX;
+ extern float focusedWindowOffsetY;
+
+ 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 df2af70..e212a11 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;