├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml └── workflows │ └── main.yml ├── .gitmodules ├── LICENSE ├── README.md ├── default_scripts_install.sh ├── layer ├── VkLayer_FROG_gamescope_wsi.cpp ├── VkLayer_FROG_gamescope_wsi.json.in ├── meson.build ├── vulkan_operators.hpp └── xcb_helpers.hpp ├── meson.build ├── meson_options.txt ├── protocol ├── color-management-v1.xml ├── frog-color-management-v1.xml ├── gamescope-action-binding.xml ├── gamescope-control.xml ├── gamescope-input-method.xml ├── gamescope-pipewire.xml ├── gamescope-private.xml ├── gamescope-reshade.xml ├── gamescope-swapchain.xml ├── gamescope-xwayland.xml ├── meson.build ├── wlr-layer-shell-unstable-v1.xml └── xdg-toplevel-icon-v1.xml ├── scripts ├── 00-gamescope │ ├── common │ │ ├── inspect.lua │ │ ├── modegen.lua │ │ └── util.lua │ └── displays │ │ ├── asus.rogally.lcd.lua │ │ ├── deckhd.steamdeck.deckhd-lcd.lua │ │ ├── gpd.win4.lcd.lua │ │ ├── lenovo.legiongo.lcd.lua │ │ ├── lenovo.legiongos.lcd.lua │ │ ├── valve.steamdeck.lcd.lua │ │ └── valve.steamdeck.oled.lua └── README.md ├── src ├── Apps │ ├── gamescope_hotkey_example.cpp │ ├── gamescopectl.cpp │ ├── gamescopereaper.cpp │ └── gamescopestream.cpp ├── Backends │ ├── DRMBackend.cpp │ ├── HeadlessBackend.cpp │ ├── OpenVRBackend.cpp │ ├── SDLBackend.cpp │ └── WaylandBackend.cpp ├── BufferMemo.cpp ├── BufferMemo.h ├── GamescopeVersion.h.in ├── InputEmulation.cpp ├── InputEmulation.h ├── LibInputHandler.cpp ├── LibInputHandler.h ├── Ratio.h ├── Script │ ├── Script.cpp │ └── Script.h ├── Timeline.cpp ├── Timeline.h ├── Utils │ ├── Algorithm.h │ ├── Defer.h │ ├── Dict.h │ ├── NonCopyable.h │ ├── Process.cpp │ ├── Process.h │ ├── TempFiles.cpp │ ├── TempFiles.h │ ├── Version.cpp │ └── Version.h ├── WaylandServer │ ├── GamescopeActionBinding.h │ ├── LinuxDrmSyncobj.h │ ├── Reshade.h │ ├── WaylandDecls.h │ ├── WaylandProtocol.h │ ├── WaylandResource.h │ └── WaylandServerLegacy.h ├── backend.cpp ├── backend.h ├── backends.h ├── color_bench.cpp ├── color_helpers.cpp ├── color_helpers.h ├── color_helpers_impl.h ├── color_tests.cpp ├── commit.cpp ├── commit.h ├── convar.cpp ├── convar.h ├── docs │ └── Steam Deck Display Pipeline.png ├── drm_include.h ├── edid.cpp ├── edid.h ├── gamescope_base_edid.bin ├── gamescope_shared.h ├── gpuvis_trace_utils.h ├── hdmi.h ├── ime.cpp ├── ime.hpp ├── layer_defines.h ├── log.cpp ├── log.hpp ├── main.cpp ├── main.hpp ├── mangoapp.cpp ├── meson.build ├── messagey.h ├── modegen.cpp ├── modegen.hpp ├── pipewire.cpp ├── pipewire.hpp ├── pipewire_gamescope.hpp ├── rc.h ├── refresh_rate.h ├── rendervulkan.cpp ├── rendervulkan.hpp ├── reshade_effect_manager.cpp ├── reshade_effect_manager.hpp ├── sdlscancodetable.hpp ├── shaders │ ├── NVIDIAImageScaling │ │ ├── NIS │ │ │ ├── NIS_Config.h │ │ │ ├── NIS_Main.glsl │ │ │ ├── NIS_Main.hlsl │ │ │ └── NIS_Scaler.h │ │ ├── README.md │ │ └── licence.txt │ ├── blit_push_data.h │ ├── blur.h │ ├── colorimetry.h │ ├── composite.h │ ├── cs_composite_blit.comp │ ├── cs_composite_blur.comp │ ├── cs_composite_blur_cond.comp │ ├── cs_composite_rcas.comp │ ├── cs_easu.comp │ ├── cs_easu_fp16.comp │ ├── cs_gaussian_blur_horizontal.comp │ ├── cs_nis.comp │ ├── cs_nis_fp16.comp │ ├── cs_rgb_to_nv12.comp │ ├── descriptor_set.h │ ├── descriptor_set_constants.h │ ├── ffx_a.h │ ├── ffx_fsr1.h │ ├── heatmap.h │ └── shaderfilter.h ├── steamcompmgr.cpp ├── steamcompmgr.hpp ├── steamcompmgr_shared.hpp ├── vblankmanager.cpp ├── vblankmanager.hpp ├── vulkan_include.h ├── waitable.h ├── win32_styles.h ├── wlr_begin.hpp ├── wlr_end.hpp ├── wlserver.cpp ├── wlserver.hpp ├── x11cursor.cpp └── xwayland_ctx.hpp ├── subprojects ├── .gitignore ├── glm.wrap ├── packagefiles │ ├── glm │ │ └── meson.build │ └── stb │ │ └── meson.build └── stb.wrap └── thirdparty └── sol ├── config.hpp ├── forward.hpp └── sol.hpp /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = false 7 | indent_style = tab 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Issue report 2 | description: File an issue report 3 | body: 4 | - type: checkboxes 5 | attributes: 6 | label: Is there an existing issue for this? 7 | description: Please search to see if an issue already exists for the bug you encountered. 8 | options: 9 | - label: I have searched the existing issues 10 | required: true 11 | - type: checkboxes 12 | attributes: 13 | label: Are you using any gamescope patches or a forked version of gamescope? 14 | description: Please confirm any issues occur on upstream gamescope without any patches before filing an issue here. 15 | options: 16 | - label: The issue occurs on upstream gamescope without any modifications 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Current Behavior 21 | description: A concise description of the issue you're experiencing. 22 | validations: 23 | required: false 24 | - type: textarea 25 | attributes: 26 | label: Steps To Reproduce 27 | description: Steps to reproduce the issue. 28 | placeholder: | 29 | 1. Launch Dota 2 from Steam with the gamescope launch command `gamescope -f -r 120 -- %command%`... 30 | 2. Enter a bot match 31 | 3. Move the cursor around 32 | validations: 33 | required: false 34 | - type: textarea 35 | attributes: 36 | label: Hardware information 37 | description: | 38 | examples: 39 | - **Distro**: SteamOS 3.6.15 (`cat /etc/os-release`) 40 | - **CPU**: 32-core AMD Ryzen Threadripper 7970X (`inxi` or `cat /proc/cpuinfo`) 41 | - **GPU**: Advanced Micro Devices [AMD/ATI] Navi 31 [Radeon RX 7900 XT/7900 XTX/7900M] (`lspci -nn | grep VGA` or `lshw -C display -numeric` or `vulkaninfo --summary | grep deviceName` 42 | - **Driver Version**: Mesa 24.2.3 or NVIDIA 560.35.03 (`vulkaninfo --summary | grep driverInfo` or `nvidia-smi`) 43 | value: | 44 | - Distro: 45 | - CPU: 46 | - GPU: 47 | - Driver Version: 48 | render: markdown 49 | validations: 50 | required: false 51 | - type: textarea 52 | attributes: 53 | label: Software information 54 | description: | 55 | examples: 56 | - **Desktop environment**: KDE 6.1.5 57 | - **Session type**: wayland (`echo $XDG_SESSION_TYPE`) 58 | - **Gamescope version**: gamescope version 3.15.9-8-gddf0d76 (gcc 14.2.1) (find this with `gamescope --version`) 59 | - **Gamescope launch command(s)**: `gamescope -f -h 2160 -w 7680 -r 120 -- %command%` 60 | value: | 61 | - Desktop environment: 62 | - Session type: 63 | - Gamescope version: 64 | - Gamescope launch command(s): 65 | render: markdown 66 | validations: 67 | required: false 68 | - type: checkboxes 69 | id: backend 70 | attributes: 71 | label: Which gamescope backends have the issue you are reporting? 72 | description: You may select more than one. 73 | options: 74 | - label: Wayland (default for nested gamescope) 75 | - label: DRM (default for embedded gamescope, i.e. gamescope-session) 76 | - label: SDL 77 | - label: OpenVR 78 | validations: 79 | required: true 80 | - type: textarea 81 | attributes: 82 | label: Logging, screenshots, or anything else 83 | description: | 84 | Please include any relevant logging or screenshots that will give us more context about the issue you are reporting. 85 | 86 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 87 | validations: 88 | required: false 89 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Share ideas for new features 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Feature request 7 | description: Share your idea for a new feature within gamescope 8 | validations: 9 | required: false 10 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | workflow_dispatch: 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | container: archlinux:base-devel 12 | steps: 13 | - name: Prepare 14 | run: | 15 | pacman-key --init 16 | pacman -Syu --noconfirm 17 | pacman -S --noconfirm git meson clang glslang libcap wlroots \ 18 | sdl2 vulkan-headers libx11 libxmu libxcomposite libxrender libxres \ 19 | libxtst libxkbcommon libdrm libinput wayland-protocols benchmark \ 20 | xorg-xwayland pipewire cmake \ 21 | libavif libheif aom rav1e libdecor libxdamage \ 22 | luajit 23 | - uses: actions/checkout@v4 24 | with: 25 | submodules: recursive 26 | - name: Build with gcc 27 | run: | 28 | export CC=gcc CXX=g++ 29 | meson build-gcc/ -Dinput_emulation=disabled --werror --auto-features=enabled 30 | ninja -C build-gcc/ 31 | - name: Build with gcc (no vr) 32 | run: | 33 | export CC=gcc CXX=g++ 34 | meson build-gcc-novr/ -Dinput_emulation=disabled -Denable_openvr_support=false --werror --auto-features=enabled 35 | ninja -C build-gcc-novr/ 36 | # - name: Build with clang 37 | # run: | 38 | # export CC=clang CXX=clang++ 39 | # meson build-clang/ -Dinput_emulation=disabled --werror --auto-features=enabled 40 | # ninja -C build-clang/ 41 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "subprojects/wlroots"] 2 | path = subprojects/wlroots 3 | url = https://github.com/Joshua-Ashton/wlroots.git 4 | [submodule "subprojects/libliftoff"] 5 | path = subprojects/libliftoff 6 | url = https://gitlab.freedesktop.org/emersion/libliftoff.git 7 | [submodule "subprojects/vkroots"] 8 | path = subprojects/vkroots 9 | url = https://github.com/Joshua-Ashton/vkroots 10 | [submodule "subprojects/libdisplay-info"] 11 | path = subprojects/libdisplay-info 12 | url = https://gitlab.freedesktop.org/emersion/libdisplay-info 13 | [submodule "subprojects/openvr"] 14 | path = subprojects/openvr 15 | url = https://github.com/ValveSoftware/openvr.git 16 | [submodule "src/reshade"] 17 | path = src/reshade 18 | url = https://github.com/Joshua-Ashton/reshade 19 | [submodule "thirdparty/SPIRV-Headers"] 20 | path = thirdparty/SPIRV-Headers 21 | url = https://github.com/KhronosGroup/SPIRV-Headers/ 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2013-2022, Valve Corporation 4 | Copyright (c) 2022, NVIDIA CORPORATION 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | ======================================================== 29 | 30 | Tetrahedal 3D LUT code from OpenColorIO 31 | 32 | Licensed under BSD 3-Clause "New" or "Revised" License: 33 | 34 | Copyright Contributors to the OpenColorIO Project. 35 | 36 | Redistribution and use in source and binary forms, with or without 37 | modification, are permitted provided that the following conditions are 38 | met: 39 | 40 | * Redistributions of source code must retain the above copyright 41 | notice, this list of conditions and the following disclaimer. 42 | * Redistributions in binary form must reproduce the above copyright 43 | notice, this list of conditions and the following disclaimer in the 44 | documentation and/or other materials provided with the distribution. 45 | * Neither the name of the copyright holder nor the names of its 46 | contributors may be used to endorse or promote products derived from 47 | this software without specific prior written permission. 48 | 49 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 50 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 51 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 52 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 53 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 54 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 55 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 56 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 57 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 58 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 59 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 60 | 61 | ======================================================== 62 | 63 | Reshade effects compiler from Reshade 64 | 65 | Copyright 2014 Patrick Mours. All rights reserved. 66 | 67 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 68 | 69 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 70 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 71 | Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 72 | 73 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## gamescope: the micro-compositor formerly known as steamcompmgr 2 | 3 | In an embedded session usecase, gamescope does the same thing as steamcompmgr, but with less extra copies and latency: 4 | 5 | - It's getting game frames through Wayland by way of Xwayland, so there's no copy within X itself before it gets the frame. 6 | - It can use DRM/KMS to directly flip game frames to the screen, even when stretching or when notifications are up, removing another copy. 7 | - When it does need to composite with the GPU, it does so with async Vulkan compute, meaning you get to see your frame quick even if the game already has the GPU busy with the next frame. 8 | 9 | It also runs on top of a regular desktop, the 'nested' usecase steamcompmgr didn't support. 10 | 11 | - Because the game is running in its own personal Xwayland sandbox desktop, it can't interfere with your desktop and your desktop can't interfere with it. 12 | - You can spoof a virtual screen with a desired resolution and refresh rate as the only thing the game sees, and control/resize the output as needed. This can be useful in exotic display configurations like ultrawide or multi-monitor setups that involve rotation. 13 | 14 | It runs on Mesa + AMD or Intel, and could be made to run on other Mesa/DRM drivers with minimal work. AMD requires Mesa 20.3+, Intel requires Mesa 21.2+. For NVIDIA's proprietary driver, version 515.43.04+ is required (make sure the `nvidia-drm.modeset=1` kernel parameter is set). 15 | 16 | If running RadeonSI clients with older cards (GFX8 and below), currently have to set `R600_DEBUG=nodcc`, or corruption will be observed until the stack picks up DRM modifiers support. 17 | 18 | ## Building 19 | 20 | ``` 21 | git submodule update --init 22 | meson setup build/ 23 | ninja -C build/ 24 | build/gamescope -- 25 | ``` 26 | 27 | Install with: 28 | 29 | ``` 30 | meson install -C build/ --skip-subprojects 31 | ``` 32 | 33 | ## Keyboard shortcuts 34 | 35 | * **Super + F** : Toggle fullscreen 36 | * **Super + N** : Toggle nearest neighbour filtering 37 | * **Super + U** : Toggle FSR upscaling 38 | * **Super + Y** : Toggle NIS upscaling 39 | * **Super + I** : Increase FSR sharpness by 1 40 | * **Super + O** : Decrease FSR sharpness by 1 41 | * **Super + S** : Take screenshot (currently goes to `/tmp/gamescope_$DATE.png`) 42 | * **Super + G** : Toggle keyboard grab 43 | 44 | ## Examples 45 | 46 | On any X11 or Wayland desktop, you can set the Steam launch arguments of your game as follows: 47 | 48 | ```sh 49 | # Upscale a 720p game to 1440p with integer scaling 50 | gamescope -h 720 -H 1440 -S integer -- %command% 51 | 52 | # Limit a vsynced game to 30 FPS 53 | gamescope -r 30 -- %command% 54 | 55 | # Run the game at 1080p, but scale output to a fullscreen 3440×1440 pillarboxed ultrawide window 56 | gamescope -w 1920 -h 1080 -W 3440 -H 1440 -b -- %command% 57 | ``` 58 | 59 | ## Options 60 | 61 | See `gamescope --help` for a full list of options. 62 | 63 | * `-W`, `-H`: set the resolution used by gamescope. Resizing the gamescope window will update these settings. Ignored in embedded mode. If `-H` is specified but `-W` isn't, a 16:9 aspect ratio is assumed. Defaults to 1280×720. 64 | * `-w`, `-h`: set the resolution used by the game. If `-h` is specified but `-w` isn't, a 16:9 aspect ratio is assumed. Defaults to the values specified in `-W` and `-H`. 65 | * `-r`: set a frame-rate limit for the game. Specified in frames per second. Defaults to unlimited. 66 | * `-o`: set a frame-rate limit for the game when unfocused. Specified in frames per second. Defaults to unlimited. 67 | * `-F fsr`: use AMD FidelityFX™ Super Resolution 1.0 for upscaling 68 | * `-F nis`: use NVIDIA Image Scaling v1.0.3 for upscaling 69 | * `-S integer`: use integer scaling. 70 | * `-S stretch`: use stretch scaling, the game will fill the window. (e.g. 4:3 to 16:9) 71 | * `-b`: create a border-less window. 72 | * `-f`: create a full-screen window. 73 | 74 | ## Reshade support 75 | 76 | Gamescope supports a subset of Reshade effects/shaders using the `--reshade-effect [path]` and `--reshade-technique-idx [idx]` command line parameters. 77 | 78 | This provides an easy way to do shader effects (ie. CRT shader, film grain, debugging HDR with histograms, etc) on top of whatever is being displayed in Gamescope without having to hook into the underlying process. 79 | 80 | Uniform/shader options can be modified programmatically via the `gamescope-reshade` wayland interface. Otherwise, they will just use their initializer values. 81 | 82 | Using Reshade effects will increase latency as there will be work performed on the general gfx + compute queue as opposed to only using the realtime async compute queue which can run in tandem with the game's gfx work. 83 | 84 | Using Reshade effects is **highly discouraged** for doing simple transformations which can be achieved with LUTs/CTMs which are possible to do in the DC (Display Core) on AMDGPU at scanout time, or with the current regular async compute composite path. 85 | The looks system where you can specify your own 3D LUTs would be a better alternative for such transformations. 86 | 87 | Pull requests for improving Reshade compatibility support are appreciated. 88 | 89 | ## Status of Gamescope Packages 90 | 91 | [![Packaging status](https://repology.org/badge/vertical-allrepos/gamescope.svg)](https://repology.org/project/gamescope/versions) 92 | -------------------------------------------------------------------------------- /default_scripts_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Remove old Gamescope default configs and add our own. 4 | mkdir -p "${DESTDIR}/${MESON_INSTALL_PREFIX}/share/gamescope" 5 | rm -rf "${DESTDIR}/${MESON_INSTALL_PREFIX}/share/gamescope/scripts" || true 6 | cp -r "${MESON_SOURCE_ROOT}/scripts" "${DESTDIR}/${MESON_INSTALL_PREFIX}/share/gamescope/scripts" 7 | -------------------------------------------------------------------------------- /layer/VkLayer_FROG_gamescope_wsi.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "file_format_version" : "1.0.0", 3 | "layer" : { 4 | "name": "VK_LAYER_FROG_gamescope_wsi_@family@", 5 | "type": "GLOBAL", 6 | "api_version": "1.3.221", 7 | "library_path": "@lib_dir@/libVkLayer_FROG_gamescope_wsi_@family@.so", 8 | "implementation_version": "1", 9 | "description": "Gamescope WSI (XWayland Bypass) Layer (@family@)", 10 | "functions": { 11 | "vkNegotiateLoaderLayerInterfaceVersion": "vkNegotiateLoaderLayerInterfaceVersion" 12 | }, 13 | "enable_environment": { 14 | "ENABLE_GAMESCOPE_WSI": "1" 15 | }, 16 | "disable_environment": { 17 | "DISABLE_GAMESCOPE_WSI": "1" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /layer/meson.build: -------------------------------------------------------------------------------- 1 | vkroots_dep = dependency('vkroots') 2 | dep_xcb = dependency('xcb') 3 | dep_x11_xcb = dependency('x11-xcb') 4 | wayland_client = dependency('wayland-client') 5 | 6 | gamescope_wsi_layer = shared_library('VkLayer_FROG_gamescope_wsi_' + build_machine.cpu_family(), 'VkLayer_FROG_gamescope_wsi.cpp', protocols_client_src, 7 | dependencies : [ vkroots_dep, dep_xcb, dep_x11, dep_x11_xcb, glm_dep, wayland_client ], 8 | install : true ) 9 | 10 | out_lib_dir = join_paths(prefix, lib_dir) 11 | 12 | configure_file( 13 | input : 'VkLayer_FROG_gamescope_wsi.json.in', 14 | output : 'VkLayer_FROG_gamescope_wsi.' + build_machine.cpu_family() + '.json', 15 | configuration : {'family' : build_machine.cpu_family(), 'lib_dir' : out_lib_dir }, 16 | install : true, 17 | install_dir : join_paths(data_dir, 'vulkan', 'implicit_layer.d'), 18 | ) -------------------------------------------------------------------------------- /layer/vulkan_operators.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline bool operator == ( 6 | const VkImageSubresourceRange& a, 7 | const VkImageSubresourceRange& b) { 8 | return a.aspectMask == b.aspectMask 9 | && a.baseMipLevel == b.baseMipLevel 10 | && a.levelCount == b.levelCount 11 | && a.baseArrayLayer == b.baseArrayLayer 12 | && a.layerCount == b.layerCount; 13 | } 14 | 15 | 16 | inline bool operator != ( 17 | const VkImageSubresourceRange& a, 18 | const VkImageSubresourceRange& b) { 19 | return !operator == (a, b); 20 | } 21 | 22 | 23 | inline bool operator == ( 24 | const VkImageSubresourceLayers& a, 25 | const VkImageSubresourceLayers& b) { 26 | return a.aspectMask == b.aspectMask 27 | && a.mipLevel == b.mipLevel 28 | && a.baseArrayLayer == b.baseArrayLayer 29 | && a.layerCount == b.layerCount; 30 | } 31 | 32 | 33 | inline bool operator != ( 34 | const VkImageSubresourceLayers& a, 35 | const VkImageSubresourceLayers& b) { 36 | return !operator == (a, b); 37 | } 38 | 39 | 40 | inline bool operator == (VkExtent3D a, VkExtent3D b) { 41 | return a.width == b.width 42 | && a.height == b.height 43 | && a.depth == b.depth; 44 | } 45 | 46 | 47 | inline bool operator != (VkExtent3D a, VkExtent3D b) { 48 | return a.width != b.width 49 | || a.height != b.height 50 | || a.depth != b.depth; 51 | } 52 | 53 | 54 | inline bool operator == (VkExtent2D a, VkExtent2D b) { 55 | return a.width == b.width 56 | && a.height == b.height; 57 | } 58 | 59 | 60 | inline bool operator != (VkExtent2D a, VkExtent2D b) { 61 | return a.width != b.width 62 | || a.height != b.height; 63 | } 64 | 65 | 66 | inline bool operator == (VkOffset3D a, VkOffset3D b) { 67 | return a.x == b.x 68 | && a.y == b.y 69 | && a.z == b.z; 70 | } 71 | 72 | 73 | inline bool operator != (VkOffset3D a, VkOffset3D b) { 74 | return a.x != b.x 75 | || a.y != b.y 76 | || a.z != b.z; 77 | } 78 | 79 | 80 | inline bool operator == (VkOffset2D a, VkOffset2D b) { 81 | return a.x == b.x 82 | && a.y == b.y; 83 | } 84 | 85 | 86 | inline bool operator != (VkOffset2D a, VkOffset2D b) { 87 | return a.x != b.x 88 | || a.y != b.y; 89 | } 90 | 91 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'gamescope', 3 | 'c', 4 | 'cpp', 5 | meson_version: '>=0.58.0', 6 | default_options: [ 7 | 'cpp_std=c++20', 8 | 'warning_level=2', 9 | 'force_fallback_for=wlroots,libliftoff,vkroots', 10 | ], 11 | ) 12 | 13 | fallbacks = get_option('force_fallback_for') 14 | if not (fallbacks.contains('wlroots') and fallbacks.contains('libliftoff') and fallbacks.contains('vkroots')) 15 | error('!!!"force_fallback_for" is missing entries!!!\n\tPlease do not remove entries from force_fallback_for if you are packaging the project.\n\tWe pull in these projects at specific commits/forks/builds for a reason.\n\tIf you are not packaging, remove this line to continue.') 16 | endif 17 | 18 | add_project_arguments([ 19 | '-DWLR_USE_UNSTABLE', 20 | ], language: 'cpp') 21 | 22 | cppc = meson.get_compiler('cpp') 23 | 24 | data_dir = get_option('datadir') 25 | prefix = get_option('prefix') 26 | lib_dir = get_option('libdir') 27 | 28 | add_project_arguments(cppc.get_supported_arguments([ 29 | '-Wno-unused-parameter', 30 | '-Wno-missing-field-initializers', 31 | '-Wno-c99-designator', 32 | '-Wno-invalid-offsetof', 33 | '-Wno-unused-const-variable', 34 | '-Wno-volatile', # glm warning 35 | '-Wno-deprecated-volatile', 36 | '-Wno-ignored-qualifiers', # reshade warning 37 | '-Wno-missing-braces', 38 | ]), language: 'cpp') 39 | 40 | add_project_arguments(cppc.get_supported_arguments([ 41 | '-fno-exceptions', 42 | '-ffast-math', 43 | ]), language: 'cpp') 44 | 45 | pipewire_dep = dependency('libpipewire-0.3', required: get_option('pipewire')) 46 | librt_dep = cppc.find_library('rt', required : get_option('pipewire')) 47 | hwdata_dep = dependency('hwdata', required : false) 48 | 49 | dep_x11 = dependency('x11') 50 | dep_wayland = dependency('wayland-client') 51 | vulkan_dep = dependency('vulkan') 52 | 53 | glm_proj = subproject('glm') 54 | glm_dep = glm_proj.get_variable('glm_dep') 55 | stb_proj = subproject('stb') 56 | stb_dep = stb_proj.get_variable('stb_dep') 57 | 58 | if get_option('enable_openvr_support') 59 | openvr_dep = dependency('openvr', version: '>= 2.7', required : false) 60 | if not openvr_dep.found() 61 | cmake = import('cmake') 62 | openvr_var = cmake.subproject_options() 63 | openvr_var.add_cmake_defines({'USE_LIBCXX': false, 64 | #HACK: remove me when openvr supports CMake 4.0 65 | 'CMAKE_POLICY_VERSION_MINIMUM': '3.5'}) 66 | #ENDHACK 67 | openvr_var.set_override_option('warning_level', '0') 68 | openvr_proj = cmake.subproject('openvr', options : openvr_var) 69 | openvr_dep = openvr_proj.dependency('openvr_api') 70 | endif 71 | else 72 | # null dep 73 | openvr_dep = dependency('', required : false) 74 | endif 75 | 76 | add_project_arguments( 77 | '-DHAVE_PIPEWIRE=@0@'.format(pipewire_dep.found().to_int()), 78 | '-DHAVE_OPENVR=@0@'.format(openvr_dep.found().to_int()), 79 | '-DSCRIPT_DIR="@0@"'.format(prefix / data_dir / 'gamescope/scripts'), 80 | language: 'cpp', 81 | ) 82 | 83 | if hwdata_dep.found() 84 | add_project_arguments( 85 | '-DHWDATA_PNP_IDS="@0@"'.format(hwdata_dep.get_variable('pkgdatadir') / 'pnp.ids'), 86 | language: 'cpp', 87 | ) 88 | else 89 | warning('Building without hwdata pnp id support.') 90 | endif 91 | 92 | # Vulkan headers are installed separately from the loader (which ships the 93 | # pkg-config file) 94 | if not cppc.check_header('vulkan/vulkan.h', dependencies: vulkan_dep) 95 | error('Missing vulkan-headers') 96 | endif 97 | 98 | subdir('protocol') 99 | 100 | if get_option('enable_gamescope_wsi_layer') 101 | subdir('layer') 102 | endif 103 | 104 | if get_option('enable_gamescope') 105 | subdir('src') 106 | endif 107 | 108 | # Handle default script/config stuff 109 | meson.add_install_script('default_scripts_install.sh') 110 | 111 | devenv = environment() 112 | devenv.set('GAMESCOPE_SCRIPT_PATH', join_paths(meson.current_source_dir(), 'scripts')) 113 | meson.add_devenv(devenv) 114 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('pipewire', type: 'feature', description: 'Screen capture via PipeWire') 2 | option('rt_cap', type: 'feature', description: 'Support for creating real-time threads + compute queues') 3 | option('drm_backend', type: 'feature', description: 'DRM Atomic Backend') 4 | option('sdl2_backend', type: 'feature', description: 'SDL2 Window Backend') 5 | option('avif_screenshots', type: 'feature', description: 'Support for saving .AVIF HDR screenshots') 6 | option('input_emulation' , type: 'feature', description: 'Support for XTest/Input Emulation with libei') 7 | option('enable_gamescope', type : 'boolean', value : true, description: 'Build Gamescope executable') 8 | option('enable_gamescope_wsi_layer', type : 'boolean', value : true, description: 'Build Gamescope layer') 9 | option('enable_openvr_support', type : 'boolean', value : true, description: 'OpenVR Integrations') 10 | option('benchmark', type: 'feature', description: 'Benchmark tools') 11 | -------------------------------------------------------------------------------- /protocol/gamescope-action-binding.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2024 Valve Corporation 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice (including the next 15 | paragraph) shall be included in all copies or substantial portions of the 16 | Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | 28 | This is a private Gamescope protocol. Regular Wayland clients must not use 29 | it. 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Flags that control how the action is armed. 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Flags that say how the action was triggered. 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /protocol/gamescope-pipewire.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2021 Valve Corporation 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice (including the next 14 | paragraph) shall be included in all copies or substantial portions of the 15 | Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | 26 | 27 | This is a private Gamescope protocol. Regular Wayland clients must not use 28 | it. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | This event advertises a PipeWire stream node identifier suitable for 37 | capturing the main output. 38 | 39 | A roundtrip after binding to the gamescope_pipewire global ensures this 40 | event has been received. 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /protocol/gamescope-private.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2023 Valve Corporation 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice (including the next 15 | paragraph) shall be included in all copies or substantial portions of the 16 | Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | 28 | This is a private Gamescope protocol, mainly for debugging. 29 | No stable ABI is guaranteed for this protocol, it is versioned with gamescope. 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /protocol/gamescope-reshade.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2024 Wayne Heaney 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice (including the next 15 | paragraph) shall be included in all copies or substantial portions of the 16 | Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | 28 | This protocol allows applications to load and interact with a reshade FX shader in gamescope. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | The effect will be disabled to allow an opportunity to set uniform variables before enabling it. 37 | 38 | 39 | 40 | 41 | 42 | 43 | This event alerts the client when an effect has been enabled. 44 | 45 | 46 | 47 | 48 | 49 | 50 | Enables the effect that was previously loaded by set_effect. 51 | 52 | 53 | 54 | 55 | 56 | Set the value of a uniform variable. Can be called before or after enabling the effect. 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Disables the effect that was previously enabled by enable_effect. 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /protocol/gamescope-xwayland.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copyright © 2021 Valve Corporation 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice (including the next 15 | paragraph) shall be included in all copies or substantial portions of the 16 | Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | 28 | This is a private Gamescope protocol. Regular Wayland clients must not use 29 | it. 30 | 31 | This protocol has been superceded by the 'gamescope-swapchain' protocol. 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Xwayland creates a wl_surface for each X11 window. It sends a 40 | WL_SURFACE_ID client message to indicate the mapping between the X11 41 | windows and the wl_surface objects. 42 | 43 | This request overrides this mapping for a given X11 window, allowing an 44 | X11 client to submit buffers via the Wayland protocol. The override 45 | only affects buffer submission. Everything else (e.g. input events) 46 | still uses Xwayland's WL_SURFACE_ID. 47 | 48 | x11_server is gotten by the GAMESCOPE_XWAYLAND_SERVER_ID property on the 49 | root window of the associated server. 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /protocol/meson.build: -------------------------------------------------------------------------------- 1 | fs = import('fs') 2 | 3 | wayland_scanner_dep = dependency('wayland-scanner', native: true) 4 | wayland_scanner_path = wayland_scanner_dep.get_variable(pkgconfig: 'wayland_scanner') 5 | wayland_scanner = find_program(wayland_scanner_path, native: true) 6 | 7 | wayland_protos = dependency('wayland-protocols', 8 | fallback: 'wayland-protocols', 9 | default_options: ['tests=false'], 10 | ) 11 | wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') 12 | 13 | protocols = [ 14 | # Upstream protocols 15 | wl_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml', 16 | wl_protocol_dir / 'stable/viewporter/viewporter.xml', 17 | wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', 18 | wl_protocol_dir / 'stable/presentation-time/presentation-time.xml', 19 | wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', 20 | wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', 21 | wl_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml', 22 | wl_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml', 23 | wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', 24 | wl_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', 25 | 26 | 'color-management-v1.xml', 27 | 28 | # Wayland Protocols not yet in a release. 29 | 'xdg-toplevel-icon-v1.xml', 30 | 31 | # Frog protocols 32 | 'frog-color-management-v1.xml', 33 | 34 | # Gamescope protocols 35 | 'gamescope-xwayland.xml', 36 | 'gamescope-pipewire.xml', 37 | 'gamescope-input-method.xml', 38 | 'gamescope-control.xml', 39 | 'gamescope-reshade.xml', 40 | 'gamescope-swapchain.xml', 41 | 'gamescope-private.xml', 42 | 'gamescope-action-binding.xml', 43 | 44 | # wlroots protocols 45 | 'wlr-layer-shell-unstable-v1.xml', 46 | ] 47 | 48 | protocols_client_src = [] 49 | protocols_server_src = [] 50 | 51 | foreach xml : protocols 52 | name = fs.name(xml).replace('.xml', '') 53 | 54 | code = custom_target( 55 | name + '-protocol.c', 56 | input: xml, 57 | output: '@BASENAME@-protocol.c', 58 | command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], 59 | ) 60 | 61 | server_header = custom_target( 62 | name + '-protocol.h', 63 | input: xml, 64 | output: '@BASENAME@-protocol.h', 65 | command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], 66 | ) 67 | 68 | client_header = custom_target( 69 | name + '-client-protocol.h', 70 | input: xml, 71 | output: '@BASENAME@-client-protocol.h', 72 | command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], 73 | ) 74 | 75 | protocols_server_src += [code, server_header] 76 | protocols_client_src += [code, client_header] 77 | endforeach 78 | -------------------------------------------------------------------------------- /scripts/00-gamescope/common/modegen.lua: -------------------------------------------------------------------------------- 1 | gamescope.modegen = {} 2 | 3 | gamescope.modegen.adjust_front_porch = function(mode, vfp) 4 | local vsync = mode.vsync_end - mode.vsync_start 5 | local vbp = mode.vtotal - mode.vsync_end 6 | 7 | mode.vsync_start = mode.vdisplay + vfp 8 | mode.vsync_end = mode.vsync_start + vsync 9 | mode.vtotal = mode.vsync_end + vbp 10 | end 11 | 12 | gamescope.modegen.set_h_timings = function(mode, hfp, hsync, hbp) 13 | mode.hsync_start = mode.hdisplay + hfp 14 | mode.hsync_end = mode.hsync_start + hsync 15 | mode.htotal = mode.hsync_end + hbp 16 | end 17 | 18 | gamescope.modegen.set_v_timings = function(mode, vfp, vsync, vbp) 19 | mode.vsync_start = mode.vdisplay + vfp 20 | mode.vsync_end = mode.vsync_start + vsync 21 | mode.vtotal = mode.vsync_end + vbp 22 | end 23 | 24 | gamescope.modegen.set_resolution = function(mode, width, height) 25 | mode.hdisplay = width 26 | mode.vdisplay = height 27 | end 28 | 29 | gamescope.modegen.calc_max_clock = function(mode, refresh) 30 | -- LuaJIT does not have // operator, sad face. 31 | return math.floor( ( ( mode.htotal * mode.vtotal * refresh ) + 999 ) / 1000 ) 32 | end 33 | 34 | gamescope.modegen.calc_vrefresh = function(mode, refresh) 35 | return math.floor( (1000 * mode.clock) / (mode.htotal * mode.vtotal) ) 36 | end 37 | -------------------------------------------------------------------------------- /scripts/00-gamescope/common/util.lua: -------------------------------------------------------------------------------- 1 | function zero_index(index) 2 | return index + 1 3 | end 4 | 5 | function error(text) 6 | gamescope.log(gamescope.log_priority.error, text) 7 | end 8 | 9 | function warn(text) 10 | gamescope.log(gamescope.log_priority.warning, text) 11 | end 12 | 13 | function info(text) 14 | gamescope.log(gamescope.log_priority.info, text) 15 | end 16 | 17 | function debug(text) 18 | gamescope.log(gamescope.log_priority.debug, text) 19 | end 20 | -------------------------------------------------------------------------------- /scripts/00-gamescope/displays/asus.rogally.lcd.lua: -------------------------------------------------------------------------------- 1 | local rogally_lcd_refresh_rates = { 2 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 3 | 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 4 | 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 5 | 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 6 | 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 7 | 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 8 | 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 9 | 118, 119, 120 10 | } 11 | 12 | gamescope.config.known_displays.rogally_lcd = { 13 | pretty_name = "ASUS ROG Ally / ROG Ally X LCD", 14 | hdr = { 15 | -- Setup some fallbacks for undocking with HDR, meant 16 | -- for the internal panel. It does not support HDR. 17 | supported = false, 18 | force_enabled = false, 19 | eotf = gamescope.eotf.gamma22, 20 | max_content_light_level = 500, 21 | max_frame_average_luminance = 500, 22 | min_content_light_level = 0.5 23 | }, 24 | dynamic_refresh_rates = rogally_lcd_refresh_rates, 25 | -- Follow the Steam Deck OLED style for modegen by varying the VFP (Vertical Front Porch) 26 | -- 27 | -- Given that this display is VRR and likely has an FB/Partial FB in the DDIC: 28 | -- it should be able to handle this method, and it is more optimal for latency 29 | -- than elongating the clock. 30 | dynamic_modegen = function(base_mode, refresh) 31 | debug("Generating mode "..refresh.."Hz for ASUS ROG Ally / ROG Ally X LCD with fixed pixel clock") 32 | local vfps = { 33 | 1771, 1720, 1655, 1600, 1549, 34 | 1499, 1455, 1405, 1361, 1320, 35 | 1279, 1224, 1200, 1162, 1120, 36 | 1088, 1055, 1022, 991, 958, 37 | 930, 900, 871, 845, 817, 38 | 794, 762, 740, 715, 690, 39 | 668, 647, 626, 605, 585, 40 | 566, 546, 526, 507, 488, 41 | 470, 452, 435, 419, 402, 42 | 387, 371, 355, 341, 326, 43 | 310, 297, 284, 269, 255, 44 | 244, 229, 217, 204, 194, 45 | 181, 172, 158, 147, 136, 46 | 123, 113, 104, 93, 83, 47 | 74, 64, 54 48 | } 49 | local vfp = vfps[zero_index(refresh - 48)] 50 | if vfp == nil then 51 | warn("Couldn't do refresh "..refresh.." on ASUS ROG Ally / ROG Ally X LCD") 52 | return base_mode 53 | end 54 | 55 | local mode = base_mode 56 | 57 | gamescope.modegen.adjust_front_porch(mode, vfp) 58 | mode.vrefresh = gamescope.modegen.calc_vrefresh(mode) 59 | 60 | --debug(inspect(mode)) 61 | return mode 62 | end, 63 | matches = function(display) 64 | -- There are two panels used across the ROG Ally and ROG Ally X 65 | -- with the same timings, but the model names are in different 66 | -- parts of the EDID. 67 | local lcd_types = { 68 | { vendor = "TMX", model = "TL070FVXS01-0", product = 0x0002 }, 69 | { vendor = "BOE", data_string = "TS070FHM-LU0", product = 0x0C33 }, 70 | } 71 | 72 | for index, value in ipairs(lcd_types) do 73 | -- We only match if the vendor and product match exactly, plus either model or data_string 74 | if value.vendor == display.vendor and value.product == display.product then 75 | if (value.model and value.model == display.model) 76 | or (value.data_string and value.data_string == display.data_string) then 77 | debug("[rogally_lcd] Matched vendor: "..value.vendor.." model: "..(value.model or value.data_string).." product: "..value.product) 78 | return 5000 79 | end 80 | end 81 | end 82 | 83 | return -1 84 | end 85 | } 86 | debug("Registered ASUS ROG Ally / ROG Ally X LCD as a known display") 87 | --debug(inspect(gamescope.config.known_displays.rogally_lcd)) 88 | -------------------------------------------------------------------------------- /scripts/00-gamescope/displays/deckhd.steamdeck.deckhd-lcd.lua: -------------------------------------------------------------------------------- 1 | gamescope.config.known_displays.steamdeck_deckhd_lcd = { 2 | pretty_name = "Steam Deck DeckHD - Unofficial Screen Replacement", 3 | dynamic_refresh_rates = { 4 | 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 5 | 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 6 | 60 7 | }, 8 | hdr = { 9 | -- Setup some fallbacks or undocking with HDR 10 | -- for this display. 11 | supported = false, 12 | force_enabled = false, 13 | eotf = gamescope.eotf.gamma22, 14 | max_content_light_level = 400, 15 | max_frame_average_luminance = 400, 16 | min_content_light_level = 0.5 17 | }, 18 | -- No colorimetry was provided in the original PR, 19 | -- and I have no idea if their BIOS mod/whatever has colorimetry 20 | -- in their edid. 21 | -- Someone that works on this or uses this should go fix that. 22 | dynamic_modegen = function(base_mode, refresh) 23 | debug("Generating mode "..refresh.."Hz for DeckHD") 24 | local mode = base_mode 25 | 26 | -- These are only tuned for 1200x1920. 27 | gamescope.modegen.set_resolution(mode, 1200, 1920) 28 | 29 | -- hfp, hsync, hbp 30 | gamescope.modegen.set_h_timings(mode, 40, 20, 40) 31 | -- vfp, vsync, vbp 32 | gamescope.modegen.set_v_timings(mode, 18, 2, 20) 33 | 34 | mode.clock = gamescope.modegen.calc_max_clock(mode, refresh) 35 | mode.vrefresh = gamescope.modegen.calc_vrefresh(mode) 36 | 37 | --debug(inspect(mode)) 38 | return mode 39 | end, 40 | matches = function(display) 41 | if display.vendor == "DHD" and display.model == "DeckHD-1200p" and display.product == 0x4001 then 42 | debug("[steamdeck_deckhd_lcd] Matched vendor: "..display.vendor.." model: "..display.model.." product:"..display.product) 43 | return 5000 44 | end 45 | 46 | return -1 47 | end 48 | } 49 | debug("Registered Steam Deck DeckHD - Unofficial Screen Replacement as a known display") 50 | --debug(inspect(gamescope.config.known_displays.steamdeck_deckhd_lcd)) 51 | -------------------------------------------------------------------------------- /scripts/00-gamescope/displays/gpd.win4.lcd.lua: -------------------------------------------------------------------------------- 1 | -- colorimetry from edid 2 | local gpd_win4_lcd_colorimetry = { 3 | r = { x = 0.6250, y = 0.3398 }, 4 | g = { x = 0.2802, y = 0.5947 }, 5 | b = { x = 0.1552, y = 0.0703 }, 6 | w = { x = 0.2832, y = 0.2978 } 7 | } 8 | 9 | gamescope.config.known_displays.gpd_win4_lcd = { 10 | pretty_name = "GPD Win 4", 11 | dynamic_refresh_rates = { 12 | 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 13 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 14 | 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 15 | }, 16 | hdr = { 17 | supported = false, 18 | force_enabled = false, 19 | eotf = gamescope.eotf.gamma22, 20 | max_content_light_level = 400, 21 | max_frame_average_luminance = 400, 22 | min_content_light_level = 0.5 23 | }, 24 | colorimetry = gpd_win4_lcd_colorimetry, 25 | dynamic_modegen = function(base_mode, refresh) 26 | debug("Generating mode "..refresh.."Hz for GPD Win 4") 27 | local mode = base_mode 28 | 29 | gamescope.modegen.set_resolution(mode, 1920, 1080) 30 | 31 | -- Horizontal timings: Hfront, Hsync, Hback 32 | gamescope.modegen.set_h_timings(mode, 72, 8, 16) 33 | -- Vertical timings: Vfront, Vsync, Vback 34 | gamescope.modegen.set_v_timings(mode, 14, 3, 13) 35 | 36 | mode.clock = gamescope.modegen.calc_max_clock(mode, refresh) 37 | mode.vrefresh = gamescope.modegen.calc_vrefresh(mode) 38 | 39 | return mode 40 | end, 41 | matches = function(display) 42 | -- There are multiple revisions of the GPD Win 4 43 | -- They all should have the same panel 44 | -- lcd_types is just in case there are different panels 45 | local lcd_types = { 46 | { vendor = "GPD", model = "G1618-04" }, 47 | } 48 | 49 | for index, value in ipairs(lcd_types) do 50 | if value.vendor == display.vendor and value.model == display.model then 51 | debug("[gpd_win4_lcd] Matched vendor: "..value.vendor.." model: "..value.model) 52 | return 5000 53 | end 54 | end 55 | 56 | return -1 57 | end 58 | } 59 | debug("Registered GPD Win 4 as a known display") 60 | --debug(inspect(gamescope.config.known_displays.gpd_win4_lcd)) 61 | -------------------------------------------------------------------------------- /scripts/00-gamescope/displays/lenovo.legiongo.lcd.lua: -------------------------------------------------------------------------------- 1 | gamescope.config.known_displays.lenovo_legiongo_lcd = { 2 | pretty_name = "Lenovo Legion Go LCD", 3 | dynamic_refresh_rates = { 4 | 60, 144 5 | }, 6 | hdr = { 7 | -- Setup some fallbacks for undocking with HDR, meant 8 | -- for the internal panel. It does not support HDR. 9 | supported = false, 10 | force_enabled = false, 11 | eotf = gamescope.eotf.gamma22, 12 | max_content_light_level = 500, 13 | max_frame_average_luminance = 500, 14 | min_content_light_level = 0.5 15 | }, 16 | -- Use the EDID colorimetry for now, but someone should check 17 | -- if the EDID colorimetry truly matches what the display is capable of. 18 | dynamic_modegen = function(base_mode, refresh) 19 | debug("Generating mode "..refresh.."Hz for Lenovo Legion Go LCD") 20 | local mode = base_mode 21 | 22 | -- These are only tuned for 1600x2560 23 | gamescope.modegen.set_resolution(mode, 1600, 2560) 24 | 25 | -- Horizontal timings: Hfront, Hsync, Hback 26 | gamescope.modegen.set_h_timings(mode, 60, 30, 130) 27 | -- Vertical timings: Vfront, Vsync, Vback 28 | gamescope.modegen.set_v_timings(mode, 30, 4, 96) 29 | 30 | mode.clock = gamescope.modegen.calc_max_clock(mode, refresh) 31 | mode.vrefresh = gamescope.modegen.calc_vrefresh(mode) 32 | 33 | return mode 34 | end, 35 | matches = function(display) 36 | -- There is only a single panel in use on the Lenovo Legion Go. 37 | if display.vendor == "LEN" and display.model == "Go Display" and display.product == 0x0001 then 38 | debug("[lenovo_legiongo_lcd] Matched vendor: "..display.vendor.." model: "..display.model.." product: "..display.product) 39 | return 5000 40 | end 41 | return -1 42 | end 43 | } 44 | debug("Registered Lenovo Legion Go LCD as a known display") 45 | --debug(inspect(gamescope.config.known_displays.lenovo_legiongo_lcd)) 46 | -------------------------------------------------------------------------------- /scripts/00-gamescope/displays/lenovo.legiongos.lcd.lua: -------------------------------------------------------------------------------- 1 | local legiongos_lcd_refresh_rates = { 2 | 52, 53, 54, 56, 57, 58, 59, 3 | 60, 61, 62, 63, 64, 65, 67, 68, 69, 4 | 70, 5 | 102, 103, 104, 105, 106, 107, 108, 109, 6 | 111, 112, 113, 114, 115, 116, 117, 118, 119, 7 | 120 8 | } 9 | 10 | gamescope.config.known_displays.legiongos_lcd = { 11 | pretty_name = "Lenovo Legion Go S LCD", 12 | hdr = { 13 | -- The Legion Go S panel does not support HDR. 14 | supported = false, 15 | force_enabled = false, 16 | eotf = gamescope.eotf.gamma22, 17 | max_content_light_level = 500, 18 | max_frame_average_luminance = 500, 19 | min_content_light_level = 0.5 20 | }, 21 | -- 60Hz has a different pixel clock than 120Hz in the EDID with VRR disabled, 22 | -- and the panel is not responsive to tuning VFPs. To cover the non-VRR 23 | -- limiter, an LCD Deck-style dynamic modegen method works best. 24 | dynamic_refresh_rates = legiongos_lcd_refresh_rates, 25 | dynamic_modegen = function(base_mode, refresh) 26 | debug("Generating mode "..refresh.."Hz for Lenovo Legion Go S LCD") 27 | local mode = base_mode 28 | 29 | -- These are only tuned for 1920x1200. 30 | gamescope.modegen.set_resolution(mode, 1920, 1200) 31 | 32 | -- hfp, hsync, hbp 33 | gamescope.modegen.set_h_timings(mode, 48, 36, 80) 34 | -- vfp, vsync, vbp 35 | gamescope.modegen.set_v_timings(mode, 54, 6, 4) 36 | mode.clock = gamescope.modegen.calc_max_clock(mode, refresh) 37 | mode.vrefresh = gamescope.modegen.calc_vrefresh(mode) 38 | 39 | --debug(inspect(mode)) 40 | return mode 41 | end, 42 | matches = function(display) 43 | local lcd_types = { 44 | { vendor = "CSW", model = "PN8007QB1-1", product = 0x0800 }, 45 | { vendor = "BOE", model = "NS080WUM-LX1", product = 0x0C00 }, 46 | { vendor = "BOE", model = "NS080WUM-LX1", product = 0x0CFF }, 47 | } 48 | 49 | for index,value in ipairs(lcd_types) do 50 | if value.vendor == display.vendor and value.model == display.model and value.product == display.product then 51 | debug("[legiongos_lcd] Matched vendor: "..display.vendor.." model: "..display.model.." product: "..display.product) 52 | return 5000 53 | end 54 | end 55 | 56 | return -1 57 | end 58 | } 59 | debug("Registered Lenovo Legion Go S LCD as a known display") 60 | --debug(inspect(gamescope.config.known_displays.legiongos_lcd)) 61 | -------------------------------------------------------------------------------- /scripts/00-gamescope/displays/valve.steamdeck.lcd.lua: -------------------------------------------------------------------------------- 1 | -- TODO: Make a vec2 class so we can alias .x and indices. 2 | 3 | local steamdeck_lcd_colorimetry_spec = { 4 | r = { x = 0.602, y = 0.355 }, 5 | g = { x = 0.340, y = 0.574 }, 6 | b = { x = 0.164, y = 0.121 }, 7 | w = { x = 0.3070, y = 0.3220 } 8 | } 9 | 10 | local steamdeck_lcd_colorimetry_measured = { 11 | r = { x = 0.603, y = 0.349 }, 12 | g = { x = 0.335, y = 0.571 }, 13 | b = { x = 0.163, y = 0.115 }, 14 | w = { x = 0.296, y = 0.307 } 15 | } 16 | 17 | gamescope.config.known_displays.steamdeck_lcd = { 18 | pretty_name = "Steam Deck LCD", 19 | dynamic_refresh_rates = { 20 | 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 21 | 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 22 | 60 23 | }, 24 | hdr = { 25 | -- Setup some fallbacks or undocking with HDR 26 | -- for this display. 27 | supported = false, 28 | force_enabled = false, 29 | eotf = gamescope.eotf.gamma22, 30 | max_content_light_level = 500, 31 | max_frame_average_luminance = 500, 32 | min_content_light_level = 0.5 33 | }, 34 | -- Use measured colorimetry instead. 35 | --colorimetry = steamdeck_lcd_colorimetry_spec, 36 | colorimetry = steamdeck_lcd_colorimetry_measured, 37 | dynamic_modegen = function(base_mode, refresh) 38 | debug("Generating mode "..refresh.."Hz for Steam Deck LCD") 39 | local mode = base_mode 40 | 41 | -- These are only tuned for 800x1280. 42 | gamescope.modegen.set_resolution(mode, 800, 1280) 43 | 44 | -- hfp, hsync, hbp 45 | gamescope.modegen.set_h_timings(mode, 40, 4, 40) 46 | -- vfp, vsync, vbp 47 | gamescope.modegen.set_v_timings(mode, 30, 4, 8) 48 | mode.clock = gamescope.modegen.calc_max_clock(mode, refresh) 49 | mode.vrefresh = gamescope.modegen.calc_vrefresh(mode) 50 | 51 | --debug(inspect(mode)) 52 | return mode 53 | end, 54 | matches = function(display) 55 | local lcd_types = { 56 | { vendor = "WLC", model = "ANX7530 U" }, 57 | { vendor = "ANX", model = "ANX7530 U" }, 58 | { vendor = "VLV", model = "ANX7530 U" }, 59 | { vendor = "VLV", model = "Jupiter" }, 60 | } 61 | 62 | for index, value in ipairs(lcd_types) do 63 | if value.vendor == display.vendor and value.model == display.model then 64 | debug("[steamdeck_lcd] Matched vendor: "..value.vendor.." model: "..value.model) 65 | return 5000 66 | end 67 | end 68 | 69 | return -1 70 | end 71 | } 72 | debug("Registered Steam Deck LCD as a known display") 73 | --debug(inspect(gamescope.config.known_displays.steamdeck_lcd)) 74 | -------------------------------------------------------------------------------- /scripts/00-gamescope/displays/valve.steamdeck.oled.lua: -------------------------------------------------------------------------------- 1 | local steamdeck_oled_hdr = { 2 | supported = true, 3 | force_enabled = true, 4 | eotf = gamescope.eotf.gamma22, 5 | max_content_light_level = 1000, 6 | max_frame_average_luminance = 800, 7 | min_content_light_level = 0 8 | } 9 | 10 | local steamdeck_oled_refresh_rates = { 11 | 45, 47, 48, 49, 12 | 50, 51, 53, 55, 56, 59, 13 | 60, 62, 64, 65, 66, 68, 14 | 72, 73, 76, 77, 78, 15 | 80, 81, 82, 84, 85, 86, 87, 88, 16 | 90 17 | } 18 | 19 | gamescope.config.known_displays.steamdeck_oled_sdc = { 20 | pretty_name = "Steam Deck OLED (SDC)", 21 | hdr = steamdeck_oled_hdr, 22 | dynamic_refresh_rates = steamdeck_oled_refresh_rates, 23 | dynamic_modegen = function(base_mode, refresh) 24 | debug("Generating mode "..refresh.."Hz for Steam Deck OLED (SDC)") 25 | local vfps = { 26 | 1321, 1264, 1209, 1157, 1106, 27 | 1058, 993, 967, 925, 883, 829, 805, 768, 732, 698, 28 | 665, 632, 601, 571, 542, 501, 486, 459, 433, 408, 29 | 383, 360, 337, 314, 292, 271, 250, 230, 210, 191, 30 | 173, 154, 137, 119, 102, 86, 70, 54, 38, 23, 31 | 9 32 | } 33 | local vfp = vfps[zero_index(refresh - 45)] 34 | if vfp == nil then 35 | warn("Couldn't do refresh "..refresh.." on Steam Deck OLED (SDC)") 36 | return base_mode 37 | end 38 | 39 | local mode = base_mode 40 | 41 | gamescope.modegen.adjust_front_porch(mode, vfp) 42 | mode.vrefresh = gamescope.modegen.calc_vrefresh(mode) 43 | 44 | --debug(inspect(mode)) 45 | return mode 46 | end, 47 | matches = function(display) 48 | if display.vendor == "VLV" and display.product == 0x3003 then 49 | debug("[steamdeck_oled_sdc] Matched VLV and product 0x3003") 50 | -- Higher priorty than LCD. 51 | return 5100 52 | end 53 | 54 | return -1 55 | end 56 | } 57 | debug("Registered Steam Deck OLED (SDC) as a known display") 58 | --debug(inspect(gamescope.config.known_displays.steamdeck_oled_sdc)) 59 | 60 | gamescope.config.known_displays.steamdeck_oled_boe = { 61 | pretty_name = "Steam Deck OLED (BOE)", 62 | hdr = steamdeck_oled_hdr, 63 | dynamic_refresh_rates = steamdeck_oled_refresh_rates, 64 | dynamic_modegen = function(base_mode, refresh) 65 | debug("Generating mode for "..refresh.." for Steam Deck OLED (BOE)") 66 | 67 | local vfps = { 68 | 1320, 1272, 1216, 1156, 1112, 69 | 1064, 992, 972, 928, 888, 828, 808, 772, 736, 700, 70 | 664, 636, 604, 572, 544, 500, 488, 460, 436, 408, 71 | 384, 360, 336, 316, 292, 272, 252, 228, 212, 192, 72 | 172, 152, 136, 120, 100, 84, 68, 52, 36, 20, 73 | 8 74 | } 75 | local vfp = vfps[zero_index(refresh - 45)] 76 | if vfp == nil then 77 | warn("Couldn't do refresh "..refresh.." on Steam Deck OLED (BOE)") 78 | return base_mode 79 | end 80 | 81 | local mode = base_mode 82 | 83 | gamescope.modegen.adjust_front_porch(mode, vfp) 84 | mode.vrefresh = gamescope.modegen.calc_vrefresh(mode) 85 | 86 | --debug(inspect(mode)) 87 | return mode 88 | end, 89 | matches = function(display) 90 | if display.vendor == "VLV" and display.product == 0x3004 then 91 | debug("[steamdeck_oled_boe] Matched VLV and product 0x3004") 92 | -- Higher priorty than LCD. 93 | return 5100 94 | end 95 | 96 | return -1 97 | end 98 | } 99 | debug("Registered Steam Deck OLED (BOE) as a known display") 100 | --debug(inspect(gamescope.config.known_displays.steamdeck_oled_boe)) 101 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Gamescope Script/Config Files 2 | 3 | ## ⚠️ Health Warning ⚠️ 4 | 5 | Gamescope scripting/configuration is currently experimental and subject to change massively. 6 | 7 | Scripts and configs working between revisions is not guaranteed to work, it should at least not crash... probably. 8 | 9 | ## The Basics 10 | 11 | Gamescope uses Lua for it's configuration and scripting system. 12 | 13 | Scripts ending in `.lua` are executed recursively in alphabetical order from the following directories: 14 | - `/usr/share/gamescope` 15 | - `/etc/gamescope` 16 | - `$XDG_CONFIG_DIR/gamescope` 17 | 18 | You can develop easily without overriding your installation by setting `script_use_local_scripts` which will eliminate `/usr/share/gamescope` and `/etc/gamescope` from being read, and instead read from `../config` of where Gamescope is run instead of those. 19 | 20 | When errors are encountered, it will simply output that to the terminal. There is no visual indicator of this currently. 21 | 22 | Things should mostly fail-safe, unless you actually made an egregious mistake in your config like setting the refresh rate to 0 or the colorimetry to all 0, 0 or something. 23 | 24 | # Making modifications as a user 25 | 26 | If you wish to make modifications that will persist as a user, simply make a new `.lua` file in `$XDG_CONFIG_DIR/gamescope` which is usually `$HOME/.config/gamescope` with what you want to change. 27 | 28 | For example, to make the Steam Deck LCD use spec colorimetry instead of the measured colorimetry you could create the following file `~/.config/gamescope/my_deck_lcd_colorimetry.lua` with the following contents: 29 | 30 | ```lua 31 | local steamdeck_lcd_colorimetry_spec = { 32 | r = { x = 0.602, y = 0.355 }, 33 | g = { x = 0.340, y = 0.574 }, 34 | b = { x = 0.164, y = 0.121 }, 35 | w = { x = 0.3070, y = 0.3220 } 36 | } 37 | 38 | gamescope.config.known_displays.steamdeck_lcd.colorimetry = steamdeck_lcd_colorimetry_spec 39 | ``` 40 | 41 | and it would override that. 42 | 43 | You could also place this in `/etc/gamescope` if you really want it to apply to all users/system-wide, but that would need root privelages. 44 | 45 | # Features 46 | 47 | Being able to set known displays (`gamescope.config.known_displays`) 48 | 49 | The ability to set convars. 50 | 51 | Hooks 52 | 53 | # Examples 54 | 55 | A script that will enable composite debug and force composition on and off every 60 frames. 56 | 57 | ```lua 58 | my_counter = 0 59 | 60 | gamescope.convars.composite_debug.value = 3 61 | 62 | gamescope.hook("OnPostPaint", function() 63 | my_counter = my_counter + 1 64 | 65 | if my_counter > 60 then 66 | gamescope.convars.composite_force.value = not gamescope.convars.composite_force.value 67 | my_counter = 0 68 | warn("Changed composite_force to "..tostring(gamescope.convars.composite_force.value)..".") 69 | end 70 | end) 71 | ``` 72 | 73 | # Hot Reloading? 74 | 75 | Coming soon... 76 | -------------------------------------------------------------------------------- /src/BufferMemo.cpp: -------------------------------------------------------------------------------- 1 | #include "BufferMemo.h" 2 | 3 | #include "wlserver.hpp" 4 | 5 | namespace gamescope 6 | { 7 | static LogScope memo_log{ "BufferMemo" }; 8 | 9 | ///////////////// 10 | // CBufferMemo 11 | ///////////////// 12 | 13 | CBufferMemo::CBufferMemo( CBufferMemoizer *pMemoizer, wlr_buffer *pBuffer, OwningRc pTexture ) 14 | : m_pMemoizer{ pMemoizer } 15 | , m_pBuffer{ pBuffer } 16 | , m_pVulkanTexture{ std::move( pTexture ) } 17 | { 18 | } 19 | 20 | CBufferMemo::~CBufferMemo() 21 | { 22 | wl_list_remove( &m_DeleteListener.link ); 23 | } 24 | 25 | void CBufferMemo::Finalize() 26 | { 27 | wlserver_lock(); 28 | wl_signal_add( &m_pBuffer->events.destroy, &m_DeleteListener ); 29 | wlserver_unlock(); 30 | } 31 | 32 | void CBufferMemo::OnBufferDestroyed( void *pUserData ) 33 | { 34 | assert( m_pVulkanTexture->GetRefCount() == 0 ); 35 | m_pMemoizer->UnmemoizeBuffer( m_pBuffer ); 36 | } 37 | 38 | /////////////////// 39 | // CBufferMemoizer 40 | /////////////////// 41 | 42 | OwningRc CBufferMemoizer::LookupVulkanTexture( wlr_buffer *pBuffer ) const 43 | { 44 | std::scoped_lock lock{ m_mutBufferMemos }; 45 | auto iter = m_BufferMemos.find( pBuffer ); 46 | if ( iter == m_BufferMemos.end() ) 47 | return nullptr; 48 | 49 | return iter->second.GetVulkanTexture(); 50 | } 51 | 52 | void CBufferMemoizer::MemoizeBuffer( wlr_buffer *pBuffer, OwningRc pTexture ) 53 | { 54 | memo_log.debugf( "Memoizing new buffer: wlr_buffer %p -> texture: %p", pBuffer, pTexture.get() ); 55 | 56 | // Can't hold m_mutBufferMemos while we finalize link from pMemo to buffer 57 | // as we can't have wlserver_lock held otherwise we can deadlock when 58 | // adding the wl_signal. 59 | // 60 | // This is fine as the lookups only happen on one thread, that calls this 61 | // or LookupVulkanTexture. 62 | CBufferMemo *pMemo = nullptr; 63 | { 64 | std::scoped_lock lock{ m_mutBufferMemos }; 65 | auto [ iter, bSuccess ] = m_BufferMemos.emplace( std::piecewise_construct, 66 | std::forward_as_tuple( pBuffer ), 67 | std::forward_as_tuple( this, pBuffer, std::move( pTexture ) ) ); 68 | 69 | assert( bSuccess ); 70 | pMemo = &iter->second; 71 | } 72 | pMemo->Finalize(); 73 | } 74 | 75 | void CBufferMemoizer::UnmemoizeBuffer( wlr_buffer *pBuffer ) 76 | { 77 | memo_log.debugf( "Unmemoizing buffer: wlr_buffer %p", pBuffer ); 78 | 79 | std::scoped_lock lock{ m_mutBufferMemos }; 80 | auto iter = m_BufferMemos.find( pBuffer ); 81 | assert( iter != m_BufferMemos.end() ); 82 | m_BufferMemos.erase( iter ); 83 | } 84 | } -------------------------------------------------------------------------------- /src/BufferMemo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rc.h" 4 | #include "rendervulkan.hpp" 5 | 6 | #include 7 | #include 8 | 9 | struct wl_listener; 10 | struct wlr_buffer; 11 | 12 | // TODO: Move to common code when we want it more. 13 | #define WAYLAND_LISTENER( member_listener, func ) \ 14 | wl_listener { .notify = []( wl_listener *pListener, void *pUserData ) { decltype( this ) pObject = wl_container_of( pListener, pObject, member_listener ); pObject->func( pUserData ); } } 15 | 16 | namespace gamescope 17 | { 18 | class CBufferMemoizer; 19 | class CBufferMemo; 20 | 21 | class CBufferMemo 22 | { 23 | public: 24 | CBufferMemo( CBufferMemoizer *pMemoizer, wlr_buffer *pBuffer, OwningRc pTexture ); 25 | ~CBufferMemo(); 26 | 27 | void Finalize(); 28 | 29 | CBufferMemoizer *GetMemoizer() const { return m_pMemoizer; } 30 | 31 | const OwningRc &GetVulkanTexture() const { return m_pVulkanTexture; } 32 | 33 | void OnBufferDestroyed( void *pUserData ); 34 | private: 35 | CBufferMemoizer *m_pMemoizer = nullptr; 36 | wlr_buffer *m_pBuffer = nullptr; 37 | wl_listener m_DeleteListener = WAYLAND_LISTENER( m_DeleteListener, OnBufferDestroyed ); 38 | 39 | // OwningRc to have a private reference: 40 | // So we can keep the CVulkanTexture, as public references 41 | // determine when we give the texture/buffer back to the app. 42 | OwningRc m_pVulkanTexture; 43 | }; 44 | 45 | class CBufferMemoizer 46 | { 47 | public: 48 | // Must return an OwningRc for the locking to make sense and not deadlock. 49 | OwningRc LookupVulkanTexture( wlr_buffer *pBuffer ) const; 50 | 51 | void MemoizeBuffer( wlr_buffer *pBuffer, OwningRc pTexture ); 52 | void UnmemoizeBuffer( wlr_buffer *pBuffer ); 53 | private: 54 | mutable std::mutex m_mutBufferMemos; 55 | std::unordered_map m_BufferMemos; 56 | }; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/GamescopeVersion.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gamescope 4 | { 5 | static constexpr const char k_szGamescopeVersion[] = "@VCS_TAG@"; 6 | } -------------------------------------------------------------------------------- /src/InputEmulation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "waitable.h" 4 | 5 | struct eis; 6 | 7 | namespace gamescope 8 | { 9 | class GamescopeInputServer final : public IWaitable 10 | { 11 | public: 12 | GamescopeInputServer(); 13 | ~GamescopeInputServer(); 14 | 15 | bool Init( const char *pszSocketPath ); 16 | 17 | virtual int GetFD() override; 18 | virtual void OnPollIn() override; 19 | private: 20 | eis *m_pEis = nullptr; 21 | int m_nFd = -1; 22 | 23 | double m_flScrollAccum[2]{}; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/LibInputHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "waitable.h" 4 | 5 | struct libinput_interface; 6 | struct udev; 7 | struct libinput; 8 | 9 | namespace gamescope 10 | { 11 | class CLibInputHandler final : public IWaitable 12 | { 13 | public: 14 | CLibInputHandler(); 15 | ~CLibInputHandler(); 16 | 17 | bool Init(); 18 | 19 | virtual int GetFD() override; 20 | virtual void OnPollIn() override; 21 | private: 22 | udev *m_pUdev = nullptr; 23 | libinput *m_pLibInput = nullptr; 24 | 25 | double m_flScrollAccum[2]{}; 26 | 27 | static const libinput_interface s_LibInputInterface; 28 | }; 29 | } -------------------------------------------------------------------------------- /src/Ratio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace gamescope 10 | { 11 | template 12 | class Ratio 13 | { 14 | public: 15 | Ratio(T num, T denom) 16 | { 17 | Set( num, denom ); 18 | } 19 | 20 | Ratio( std::string_view view ) 21 | { 22 | Set(0, 0); 23 | 24 | size_t colon = view.find(":"); 25 | 26 | if ( colon == std::string_view::npos ) 27 | return; 28 | 29 | std::string_view numStr = view.substr( 0, colon ); 30 | std::string_view denomStr = view.substr( colon + 1 ); 31 | 32 | T num = 0, denom = 0; 33 | std::from_chars( numStr.data(), numStr.data() + numStr.size(), num ); 34 | std::from_chars( denomStr.data(), denomStr.data() + denomStr.size(), denom ); 35 | 36 | Set( num, denom ); 37 | } 38 | 39 | T Num() const { return m_num; } 40 | T Denom() const { return m_denom; } 41 | 42 | bool IsUndefined() const { return m_denom == 0; } 43 | 44 | void Set( T num, T denom ) 45 | { 46 | const T gcd = std::gcd( num, denom ); 47 | 48 | if ( gcd == 0 ) 49 | { 50 | m_num = 0; 51 | m_denom = 0; 52 | 53 | return; 54 | } 55 | 56 | m_num = num / gcd; 57 | m_denom = denom / gcd; 58 | } 59 | 60 | bool operator == ( const Ratio& other ) const { return Num() == other.Num() && Denom() == other.Denom(); } 61 | bool operator != ( const Ratio& other ) const { return !(*this == other); } 62 | 63 | bool operator >= ( const Ratio& other ) const { return Num() * other.Denom() >= other.Num() * Denom(); } 64 | bool operator > ( const Ratio& other ) const { return Num() * other.Denom() > other.Num() * Denom(); } 65 | 66 | bool operator < ( const Ratio& other ) const { return !( *this >= other ); } 67 | bool operator <= ( const Ratio& other ) const { return !( *this > other ); } 68 | 69 | private: 70 | T m_num, m_denom; 71 | }; 72 | } -------------------------------------------------------------------------------- /src/Script/Script.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../Utils/Dict.h" 4 | 5 | #include 6 | 7 | #if HAVE_SCRIPTING 8 | 9 | #include 10 | 11 | namespace gamescope 12 | { 13 | class CScriptScopedLock; 14 | 15 | struct GamescopeScript_t 16 | { 17 | // Stores table entries, and caches sol::table 18 | // handles for things we use frequently. 19 | 20 | sol::table Base; 21 | 22 | struct ConVars_t 23 | { 24 | sol::table Base; 25 | } Convars; 26 | 27 | struct Config_t 28 | { 29 | sol::table Base; 30 | 31 | sol::table KnownDisplays; 32 | 33 | std::optional> LookupDisplay( CScriptScopedLock &script, std::string_view psvVendor, uint16_t uProduct, std::string_view psvModel, std::string_view psvDataString ); 34 | } Config; 35 | }; 36 | 37 | class CScriptManager 38 | { 39 | public: 40 | CScriptManager(); 41 | 42 | template 43 | void CallHook( std::string_view svName, Args&&... args ) 44 | { 45 | auto range = m_Hooks.equal_range( svName ); 46 | for ( auto iter = range.first; iter != range.second; iter++ ) 47 | { 48 | int32_t nPreviousScriptId = m_nCurrentScriptId; 49 | 50 | Hook_t *pHook = &iter->second; 51 | 52 | m_nCurrentScriptId = pHook->nScriptId; 53 | iter->second.fnCallback( std::forward( args )... ); 54 | m_nCurrentScriptId = nPreviousScriptId; 55 | } 56 | } 57 | 58 | void RunDefaultScripts(); 59 | 60 | void RunScriptText( std::string_view svContents ); 61 | 62 | void RunFile( std::string_view svPath ); 63 | bool RunFolder( std::string_view svPath, bool bRecursive = false ); 64 | 65 | void InvalidateAllHooks(); 66 | void InvalidateHooksForScript( int32_t nScriptId ); 67 | 68 | sol::state *operator->() { return &m_State; } 69 | 70 | sol::state &State() { return m_State; } 71 | GamescopeScript_t &Gamescope() { return m_Gamescope; } 72 | 73 | std::mutex &Mutex() { return m_mutMutex; } 74 | 75 | protected: 76 | static CScriptManager &GlobalScriptScope(); 77 | friend CScriptScopedLock; 78 | private: 79 | mutable std::mutex m_mutMutex; 80 | 81 | sol::state m_State; 82 | GamescopeScript_t m_Gamescope; 83 | 84 | struct Hook_t 85 | { 86 | sol::function fnCallback; 87 | int32_t nScriptId = -1; 88 | }; 89 | 90 | MultiDict m_Hooks; 91 | 92 | int32_t m_nCurrentScriptId = -1; 93 | 94 | static int32_t s_nNextScriptId; 95 | }; 96 | 97 | class CScriptScopedLock 98 | { 99 | public: 100 | CScriptScopedLock() 101 | : CScriptScopedLock{ CScriptManager::GlobalScriptScope() } 102 | { 103 | } 104 | 105 | CScriptScopedLock( CScriptManager &manager ) 106 | : m_Lock{ manager.Mutex() } 107 | , m_ScriptManager{ manager } 108 | { 109 | } 110 | 111 | ~CScriptScopedLock() 112 | { 113 | } 114 | 115 | CScriptManager &Manager() { return m_ScriptManager; } 116 | sol::state *State() { return &m_ScriptManager.State(); } 117 | 118 | sol::state *operator ->() { return State(); } 119 | private: 120 | std::scoped_lock m_Lock; 121 | CScriptManager &m_ScriptManager; 122 | }; 123 | 124 | template 125 | T TableToVec( const sol::table &table ) 126 | { 127 | if ( !table ) 128 | return T{}; 129 | 130 | T out{}; 131 | for ( int i = 0; i < T::length(); i++ ) 132 | { 133 | std::array ppsvIndices 134 | { 135 | "x", "y", "z", "w" 136 | }; 137 | 138 | sol::optional ofValue = table[ppsvIndices[i]]; 139 | out[i] = ofValue ? *ofValue : 0; 140 | } 141 | return out; 142 | } 143 | 144 | template 145 | std::vector TableToVector( const sol::table &table ) 146 | { 147 | std::vector out; 148 | 149 | if ( table ) 150 | { 151 | for ( auto &iter : table ) 152 | { 153 | sol::optional oValue = iter.second.as>(); 154 | if ( oValue ) 155 | out.emplace_back( *oValue ); 156 | } 157 | } 158 | 159 | return out; 160 | } 161 | 162 | #define SCRIPTDESC_TEMPLATE( type ) template 163 | #define DECLARE_SCRIPTDESC( type ) \ 164 | static sol::usertype< type > s_ScriptType; \ 165 | class CEnsureScriptTypeInstantiation { public: CEnsureScriptTypeInstantiation() { (void) type :: s_ScriptType; } } m_EnsureTemplateInstantiation_ScriptDesc; 166 | 167 | #define START_SCRIPTDESC( type, lua_name ) \ 168 | inline sol::usertype type::s_ScriptType = CScriptScopedLock()->new_usertype( lua_name 169 | #define START_SCRIPTDESC_ANON( type ) \ 170 | inline sol::usertype type::s_ScriptType = CScriptScopedLock()->new_usertype( typeid( type ).name() 171 | #define SCRIPTDESC( x, y ) , x, y 172 | #define END_SCRIPTDESC() ); 173 | } 174 | 175 | #else 176 | 177 | #define SCRIPTDESC_TEMPLATE( type ) 178 | #define DECLARE_SCRIPTDESC( x ) 179 | #define START_SCRIPTDESC( type, lua_name ) 180 | #define START_SCRIPTDESC_ANON( type ) 181 | #define SCRIPTDESC( x, y ) 182 | #define END_SCRIPTDESC() 183 | 184 | #endif -------------------------------------------------------------------------------- /src/Timeline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Timeline.h" 5 | #include "wlserver.hpp" 6 | #include "rendervulkan.hpp" 7 | 8 | #include "wlr_begin.hpp" 9 | #include 10 | #include 11 | #include "wlr_end.hpp" 12 | 13 | namespace gamescope 14 | { 15 | static LogScope s_TimelineLog( "timeline" ); 16 | 17 | static uint32_t SyncobjFdToHandle( int32_t nFd ) 18 | { 19 | int32_t nRet; 20 | uint32_t uHandle = 0; 21 | if ( ( nRet = drmSyncobjFDToHandle( g_device.drmRenderFd(), nFd, &uHandle ) ) < 0 ) 22 | return 0; 23 | 24 | return uHandle; 25 | } 26 | 27 | /*static*/ std::shared_ptr CTimeline::Create( const TimelineCreateDesc_t &desc ) 28 | { 29 | std::shared_ptr pSemaphore = g_device.CreateTimelineSemaphore( desc.ulStartingPoint, true ); 30 | if ( !pSemaphore ) 31 | return nullptr; 32 | 33 | return std::make_shared( pSemaphore->GetFd(), std::move( pSemaphore ) ); 34 | } 35 | 36 | CTimeline::CTimeline( int32_t nSyncobjFd, std::shared_ptr pSemaphore ) 37 | : CTimeline( nSyncobjFd, SyncobjFdToHandle( nSyncobjFd ), std::move( pSemaphore ) ) 38 | { 39 | } 40 | 41 | CTimeline::CTimeline( int32_t nSyncobjFd, uint32_t uSyncobjHandle, std::shared_ptr pSemaphore ) 42 | : m_nSyncobjFd{ nSyncobjFd } 43 | , m_uSyncobjHandle{ uSyncobjHandle } 44 | , m_pVkSemaphore{ std::move( pSemaphore ) } 45 | { 46 | } 47 | 48 | CTimeline::~CTimeline() 49 | { 50 | m_pVkSemaphore = nullptr; 51 | 52 | if ( m_uSyncobjHandle ) 53 | drmSyncobjDestroy( GetDrmRenderFD(), m_uSyncobjHandle ); 54 | if ( m_nSyncobjFd >= 0 ) 55 | close( m_nSyncobjFd ); 56 | } 57 | 58 | int32_t CTimeline::GetDrmRenderFD() 59 | { 60 | return g_device.drmRenderFd(); 61 | } 62 | 63 | std::shared_ptr CTimeline::ToVkSemaphore() 64 | { 65 | if ( !m_pVkSemaphore ) 66 | m_pVkSemaphore = g_device.ImportTimelineSemaphore( this ); 67 | 68 | return m_pVkSemaphore; 69 | } 70 | 71 | // CTimelinePoint 72 | 73 | template 74 | CTimelinePoint::CTimelinePoint( std::shared_ptr pTimeline, uint64_t ulPoint ) 75 | : m_pTimeline{ std::move( pTimeline ) } 76 | , m_ulPoint{ ulPoint } 77 | { 78 | } 79 | 80 | template 81 | CTimelinePoint::~CTimelinePoint() 82 | { 83 | if ( ShouldSignalOnDestruction() ) 84 | { 85 | const uint32_t uHandle = m_pTimeline->GetSyncobjHandle(); 86 | 87 | drmSyncobjTimelineSignal( m_pTimeline->GetDrmRenderFD(), &uHandle, &m_ulPoint, 1 ); 88 | } 89 | } 90 | 91 | template 92 | bool CTimelinePoint::Wait( int64_t lTimeout ) 93 | { 94 | uint32_t uHandle = m_pTimeline->GetSyncobjHandle(); 95 | 96 | int nRet = drmSyncobjTimelineWait( 97 | m_pTimeline->GetDrmRenderFD(), 98 | &uHandle, 99 | &m_ulPoint, 100 | 1, 101 | lTimeout, 102 | DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL, 103 | nullptr ); 104 | 105 | return nRet == 0; 106 | } 107 | 108 | // 109 | // Fence flags tl;dr 110 | // 0 -> Wait for signal on a materialized fence, -ENOENT if not materialized 111 | // DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE -> Wait only for materialization 112 | // DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT -> Wait for materialization + signal 113 | // 114 | template 115 | std::pair CTimelinePoint::CreateEventFd() 116 | { 117 | assert( Type == TimelinePointType::Acquire ); 118 | 119 | uint32_t uHandle = m_pTimeline->GetSyncobjHandle(); 120 | uint64_t ulSignalledPoint = 0; 121 | int nRet = drmSyncobjQuery( m_pTimeline->GetDrmRenderFD(), &uHandle, &ulSignalledPoint, 1u ); 122 | if ( nRet != 0 ) 123 | { 124 | s_TimelineLog.errorf_errno( "drmSyncobjQuery failed (%d)", nRet ); 125 | return k_InvalidEvent; 126 | } 127 | 128 | if ( ulSignalledPoint >= m_ulPoint ) 129 | { 130 | return k_AlreadySignalledEvent; 131 | } 132 | else 133 | { 134 | const int32_t nExplicitSyncEventFd = eventfd( 0, EFD_CLOEXEC ); 135 | if ( nExplicitSyncEventFd < 0 ) 136 | { 137 | s_TimelineLog.errorf_errno( "Failed to create eventfd (%d)", nExplicitSyncEventFd ); 138 | return k_InvalidEvent; 139 | } 140 | 141 | drm_syncobj_eventfd syncobjEventFd = 142 | { 143 | .handle = m_pTimeline->GetSyncobjHandle(), 144 | // Only valid flags are: DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE 145 | // -> Wait for fence materialization rather than signal. 146 | .flags = 0u, 147 | .point = m_ulPoint, 148 | .fd = nExplicitSyncEventFd, 149 | }; 150 | 151 | if ( drmIoctl( m_pTimeline->GetDrmRenderFD(), DRM_IOCTL_SYNCOBJ_EVENTFD, &syncobjEventFd ) != 0 ) 152 | { 153 | s_TimelineLog.errorf_errno( "DRM_IOCTL_SYNCOBJ_EVENTFD failed" ); 154 | close( nExplicitSyncEventFd ); 155 | return k_InvalidEvent; 156 | } 157 | 158 | return { nExplicitSyncEventFd, false }; 159 | } 160 | } 161 | 162 | template class CTimelinePoint; 163 | template class CTimelinePoint; 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/Timeline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Utils/NonCopyable.h" 9 | 10 | struct VulkanTimelineSemaphore_t; 11 | 12 | namespace gamescope 13 | { 14 | enum class TimelinePointType 15 | { 16 | Acquire, 17 | Release, 18 | }; 19 | 20 | struct TimelineCreateDesc_t 21 | { 22 | uint64_t ulStartingPoint = 0ul; 23 | }; 24 | 25 | class CTimeline : public NonCopyable 26 | { 27 | public: 28 | static std::shared_ptr Create( const TimelineCreateDesc_t &desc = {} ); 29 | 30 | // Inherits nSyncobjFd's ref. 31 | CTimeline( int32_t nSyncobjFd, std::shared_ptr pSemaphore = nullptr ); 32 | CTimeline( int32_t nSyncobjFd, uint32_t uSyncobjHandle, std::shared_ptr pSemaphore = nullptr ); 33 | 34 | CTimeline( CTimeline &&other ) 35 | : m_nSyncobjFd{ std::exchange( other.m_nSyncobjFd, -1 ) } 36 | , m_uSyncobjHandle{ std::exchange( other.m_uSyncobjHandle, 0 ) } 37 | { 38 | } 39 | ~CTimeline(); 40 | 41 | static int32_t GetDrmRenderFD(); 42 | 43 | bool IsValid() const { return m_uSyncobjHandle != 0; } 44 | 45 | int32_t GetSyncobjFd() const { return m_nSyncobjFd; } 46 | uint32_t GetSyncobjHandle() const { return m_uSyncobjHandle; } 47 | 48 | std::shared_ptr ToVkSemaphore(); 49 | 50 | private: 51 | int32_t m_nSyncobjFd = -1; 52 | uint32_t m_uSyncobjHandle = 0; 53 | 54 | std::shared_ptr m_pVkSemaphore; 55 | }; 56 | 57 | template 58 | class CTimelinePoint : public NonCopyable 59 | { 60 | public: 61 | static constexpr std::pair k_InvalidEvent = { -1, false }; 62 | static constexpr std::pair k_AlreadySignalledEvent = { -1, true }; 63 | 64 | CTimelinePoint( std::shared_ptr pTimeline, uint64_t ulPoint ); 65 | ~CTimelinePoint(); 66 | 67 | void SetPoint( uint64_t ulPoint ) { m_ulPoint = ulPoint; } 68 | uint64_t GetPoint() const { return m_ulPoint; } 69 | 70 | std::shared_ptr &GetTimeline() { return m_pTimeline; } 71 | const std::shared_ptr &GetTimeline() const { return m_pTimeline; } 72 | 73 | constexpr bool ShouldSignalOnDestruction() const { return Type == TimelinePointType::Release; } 74 | 75 | bool Wait( int64_t lTimeout = std::numeric_limits::max() ); 76 | 77 | std::pair CreateEventFd(); 78 | private: 79 | 80 | std::shared_ptr m_pTimeline; 81 | uint64_t m_ulPoint = 0; 82 | 83 | }; 84 | 85 | using CAcquireTimelinePoint = CTimelinePoint; 86 | using CReleaseTimelinePoint = CTimelinePoint; 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/Utils/Algorithm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace gamescope::Algorithm 8 | { 9 | template 10 | constexpr TObj *Begin( std::span span ) 11 | { 12 | return span.data(); 13 | } 14 | 15 | template 16 | constexpr TObj *End( std::span span ) 17 | { 18 | return Begin( span ) + span.size(); 19 | } 20 | 21 | template 22 | constexpr const TObj *Begin( const std::vector &vec ) 23 | { 24 | return vec.data(); 25 | } 26 | 27 | template 28 | constexpr const TObj *End( const std::vector &vec ) 29 | { 30 | return Begin( vec ) + vec.size(); 31 | } 32 | 33 | template 34 | constexpr TIter FindSimple( TIter pFirst, TIter pEnd, const TObj &obj ) 35 | { 36 | while ( pFirst != pEnd && *pFirst != obj ) 37 | ++pFirst; 38 | 39 | return pFirst; 40 | } 41 | 42 | template 43 | constexpr TIter FindByFour( TIter pFirst, TIter pEnd, const TObj &obj ) 44 | { 45 | typename std::iterator_traits< TIter >::difference_type ulTripCount = ( pEnd - pFirst ) >> 2; 46 | 47 | while ( ulTripCount-- > 0 ) 48 | { 49 | if ( pFirst[0] == obj ) 50 | return &pFirst[0]; 51 | 52 | if ( pFirst[1] == obj ) 53 | return &pFirst[1]; 54 | 55 | if ( pFirst[2] == obj ) 56 | return &pFirst[2]; 57 | 58 | if ( pFirst[3] == obj ) 59 | return &pFirst[3]; 60 | 61 | pFirst += 4; 62 | } 63 | 64 | switch ( pEnd - pFirst ) 65 | { 66 | case 3: 67 | { 68 | if ( pFirst[0] == obj ) 69 | return &pFirst[0]; 70 | 71 | if ( pFirst[1] == obj ) 72 | return &pFirst[1]; 73 | 74 | if ( pFirst[2] == obj ) 75 | return &pFirst[2]; 76 | 77 | return pEnd; 78 | } 79 | case 2: 80 | { 81 | if ( pFirst[0] == obj ) 82 | return &pFirst[0]; 83 | 84 | if ( pFirst[1] == obj ) 85 | return &pFirst[1]; 86 | 87 | return pEnd; 88 | } 89 | case 1: 90 | { 91 | if ( pFirst[0] == obj ) 92 | return &pFirst[0]; 93 | 94 | return pEnd; 95 | } 96 | case 0: 97 | { 98 | return pEnd; 99 | } 100 | default: 101 | { 102 | __builtin_unreachable(); 103 | } 104 | } 105 | } 106 | 107 | template 108 | constexpr TIter Find( TIter pFirst, TIter pEnd, const TObj &obj ) 109 | { 110 | return FindSimple( pFirst, pEnd, obj ); 111 | } 112 | 113 | template 114 | constexpr TIter Find( std::span span, const TObj &obj ) 115 | { 116 | return Find( Begin( span ), End( span ), obj ); 117 | } 118 | 119 | template 120 | constexpr TIter Find( const std::vector &vec, const TObj &obj ) 121 | { 122 | return Find( Begin( vec ), End( vec ), obj ); 123 | } 124 | 125 | template 126 | constexpr bool ContainsShortcut( TIter pFirst, TIter pEnd, const TObj &obj ) 127 | { 128 | return Find( pFirst, pEnd, obj ) != pEnd; 129 | } 130 | 131 | template 132 | constexpr bool ContainsNoShortcut( TIter pFirst, TIter pEnd, const TObj &obj ) 133 | { 134 | bool bFound = false; 135 | 136 | typename std::iterator_traits< TIter >::difference_type ulTripCount = ( pEnd - pFirst ) >> 2; 137 | 138 | while ( ulTripCount-- > 0 ) 139 | { 140 | bFound |= pFirst[0] == obj || 141 | pFirst[1] == obj || 142 | pFirst[2] == obj || 143 | pFirst[3] == obj; 144 | 145 | pFirst += 4; 146 | } 147 | 148 | switch ( pEnd - pFirst ) 149 | { 150 | case 3: 151 | { 152 | bFound |= pFirst[0] == obj || 153 | pFirst[1] == obj || 154 | pFirst[2] == obj; 155 | break; 156 | } 157 | case 2: 158 | { 159 | bFound |= pFirst[0] == obj || 160 | pFirst[1] == obj; 161 | break; 162 | } 163 | case 1: 164 | { 165 | bFound |= pFirst[0] == obj; 166 | break; 167 | } 168 | case 0: 169 | { 170 | break; 171 | } 172 | default: 173 | { 174 | __builtin_unreachable(); 175 | } 176 | } 177 | 178 | return bFound; 179 | } 180 | 181 | template 182 | constexpr bool Contains( TIter pFirst, TIter pEnd, const TObj &obj ) 183 | { 184 | return ContainsNoShortcut( pFirst, pEnd, obj ); 185 | } 186 | 187 | template 188 | constexpr bool Contains( std::span span, const TObj &obj ) 189 | { 190 | return Contains( Begin( span ), End( span ), obj ); 191 | } 192 | 193 | template 194 | constexpr bool Contains( const std::vector &vec, const TObj &obj ) 195 | { 196 | return Contains( Begin( vec ), End( vec ), obj ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Utils/Defer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gamescope 6 | { 7 | template 8 | class DeferHelper 9 | { 10 | public: 11 | DeferHelper( Func fnFunc ) 12 | : m_fnFunc{ std::move( fnFunc ) } 13 | { 14 | } 15 | 16 | ~DeferHelper() 17 | { 18 | m_fnFunc(); 19 | } 20 | 21 | private: 22 | Func m_fnFunc; 23 | }; 24 | 25 | } 26 | 27 | #define DEFER_1(x, y) x##y 28 | #define DEFER_2(x, y) DEFER_1(x, y) 29 | #define DEFER_3(x) DEFER_2(x, __COUNTER__) 30 | #define defer(code) auto DEFER_3(_defer_) = ::gamescope::DeferHelper( [&](){ code; } ) 31 | -------------------------------------------------------------------------------- /src/Utils/Dict.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gamescope 9 | { 10 | struct StringHash 11 | { 12 | using is_transparent = void; 13 | [[nodiscard]] size_t operator()( const char *string ) const { return std::hash{}( string ); } 14 | [[nodiscard]] size_t operator()( std::string_view string ) const { return std::hash{}( string ); } 15 | [[nodiscard]] size_t operator()( const std::string &string ) const { return std::hash{}( string ); } 16 | }; 17 | 18 | template 19 | using Dict = std::unordered_map>; 20 | 21 | template 22 | using MultiDict = std::unordered_multimap>; 23 | } 24 | -------------------------------------------------------------------------------- /src/Utils/NonCopyable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gamescope 4 | { 5 | 6 | class NonCopyable 7 | { 8 | public: 9 | NonCopyable() = default; 10 | NonCopyable(const NonCopyable &) = delete; 11 | 12 | void operator = (const NonCopyable &) = delete; 13 | }; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/Utils/Process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace gamescope::Process 10 | { 11 | void BecomeSubreaper(); 12 | void SetDeathSignal( int nSignal ); 13 | 14 | void KillAllChildren( pid_t nParentPid, int nSignal ); 15 | void KillProcess( pid_t nPid, int nSignal ); 16 | 17 | std::optional WaitForChild( pid_t nPid ); 18 | 19 | // Wait for all children to die, 20 | // but stop waiting if we hit a specific PID specified by onStopPid. 21 | // Returns true if we stopped because we hit the pid specified by onStopPid. 22 | // 23 | // Similar to what an `init` process would do. 24 | bool WaitForAllChildren( std::optional onStopPid = std::nullopt ); 25 | 26 | bool CloseFd( int nFd ); 27 | 28 | void RaiseFdLimit(); 29 | void RestoreFdLimit(); 30 | void ResetSignals(); 31 | 32 | void CloseAllFds( std::span nExcludedFds ); 33 | 34 | pid_t SpawnProcess( char **argv, std::function fnPreambleInChild = nullptr, bool bDoubleFork = false ); 35 | pid_t SpawnProcessInWatchdog( char **argv, bool bRespawn = false, std::function fnPreambleInChild = nullptr ); 36 | 37 | bool HasCapSysNice(); 38 | void SetNice( int nNice ); 39 | void RestoreNice(); 40 | 41 | bool SetRealtime(); 42 | void RestoreRealtime(); 43 | 44 | const char *GetProcessName(); 45 | 46 | } -------------------------------------------------------------------------------- /src/Utils/TempFiles.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "TempFiles.h" 12 | 13 | namespace gamescope 14 | { 15 | class CDeferUnlinks 16 | { 17 | public: 18 | void Add( std::string sPath ) 19 | { 20 | m_DeferredUnlinks.emplace_front( sPath ); 21 | } 22 | private: 23 | class CDeferUnlink 24 | { 25 | public: 26 | CDeferUnlink( std::string sPath ) 27 | : m_sPath{ std::move( sPath ) } 28 | { 29 | } 30 | 31 | ~CDeferUnlink() 32 | { 33 | unlink( m_sPath.c_str() ); 34 | } 35 | private: 36 | const std::string m_sPath; 37 | }; 38 | 39 | std::list m_DeferredUnlinks; 40 | }; 41 | static CDeferUnlinks s_DeferredUnlinks; 42 | 43 | int MakeTempFile( char ( &pszOutPath )[ PATH_MAX ], const char *pszTemplate, bool bDeferUnlink ) 44 | { 45 | const char *pXDGPath = getenv( "XDG_RUNTIME_DIR" ); 46 | if ( !pXDGPath || !*pXDGPath ) 47 | return -1; 48 | 49 | snprintf( pszOutPath, PATH_MAX, "%s/%s", pXDGPath, pszTemplate ); 50 | 51 | // Overwrites pszOutPath with the file path. 52 | int nFd = mkostemp( pszOutPath, O_CLOEXEC ); 53 | if ( nFd < 0 ) 54 | return -1; 55 | 56 | // Unlink so it gets destroyed when Gamescope dies. 57 | if ( bDeferUnlink ) 58 | { 59 | s_DeferredUnlinks.Add( pszOutPath ); 60 | } 61 | else 62 | { 63 | unlink( pszOutPath ); 64 | } 65 | 66 | return nFd; 67 | } 68 | 69 | FILE *MakeTempFile( char ( &pszOutPath )[ PATH_MAX ], const char *pszTemplate, const char *pszMode, bool bDeferUnlink ) 70 | { 71 | int nFd = MakeTempFile( pszOutPath, pszTemplate, bDeferUnlink ); 72 | if ( nFd < 0 ) 73 | return nullptr; 74 | 75 | FILE *pFile = fdopen( nFd, pszMode ); 76 | if ( !pFile ) 77 | { 78 | close( nFd ); 79 | return nullptr; 80 | } 81 | 82 | // fclose will close the file **and** the underlying file descriptor. 83 | return pFile; 84 | } 85 | } -------------------------------------------------------------------------------- /src/Utils/TempFiles.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace gamescope 7 | { 8 | static constexpr const char k_szGamescopeTempFileTemplate[] = "gamescope-temp-XXXXXXXX"; 9 | static constexpr const char k_szGamescopeTempShmTemplate[] = "gamescope-shm-XXXXXXXX"; 10 | static constexpr const char k_szGamescopeTempMangoappTemplate[] = "gamescope-mangoapp-XXXXXXXX"; 11 | static constexpr const char k_szGamescopeTempLimiterTemplate[] = "gamescope-limiter-XXXXXXXX"; 12 | 13 | int MakeTempFile( char ( &pszOutPath )[ PATH_MAX ], const char *pszTemplate, bool bDeferUnlink = false ); 14 | FILE *MakeTempFile( char ( &pszOutPath )[ PATH_MAX ], const char *pszTemplate, const char *pszMode, bool bDeferUnlink = false ); 15 | } -------------------------------------------------------------------------------- /src/Utils/Version.cpp: -------------------------------------------------------------------------------- 1 | #include "Version.h" 2 | #include "Process.h" 3 | 4 | #include "GamescopeVersion.h" 5 | 6 | #include "convar.h" 7 | 8 | namespace gamescope 9 | { 10 | void PrintVersion() 11 | { 12 | console_log.infof( "%s version %s", Process::GetProcessName(), gamescope::k_szGamescopeVersion ); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Utils/Version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gamescope 4 | { 5 | void PrintVersion(); 6 | } 7 | -------------------------------------------------------------------------------- /src/WaylandServer/GamescopeActionBinding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WaylandProtocol.h" 4 | 5 | #include "gamescope-action-binding-protocol.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "convar.h" 12 | #include "Utils/Algorithm.h" 13 | 14 | #include "wlr_begin.hpp" 15 | #include 16 | #include 17 | #include "wlr_end.hpp" 18 | 19 | using namespace std::literals; 20 | 21 | uint64_t get_time_in_nanos(); 22 | 23 | namespace gamescope::WaylandServer 24 | { 25 | struct Keybind_t 26 | { 27 | std::unordered_set setKeySyms; 28 | }; 29 | 30 | /////////////////////////// 31 | // CGamescopeActionBinding 32 | /////////////////////////// 33 | class CGamescopeActionBinding : public CWaylandResource 34 | { 35 | public: 36 | WL_PROTO_DEFINE( gamescope_action_binding, 1 ); 37 | 38 | CGamescopeActionBinding( WaylandResourceDesc_t desc ) 39 | : CWaylandResource( desc ) 40 | { 41 | s_Bindings.push_back( this ); 42 | } 43 | 44 | ~CGamescopeActionBinding() 45 | { 46 | std::erase_if( s_Bindings, [this]( CGamescopeActionBinding *pBinding ){ return pBinding == this; } ); 47 | } 48 | 49 | // gamescope_action_binding 50 | 51 | void SetDescription( const char *pszDescription ) 52 | { 53 | m_sDescription = pszDescription; 54 | } 55 | 56 | void AddKeyboardTrigger( wl_array *pKeysymsArray ) 57 | { 58 | size_t zKeysymCount = pKeysymsArray->size / sizeof( xkb_keysym_t ); 59 | 60 | std::span pKeysyms = std::span { 61 | reinterpret_cast( pKeysymsArray->data ), 62 | zKeysymCount }; 63 | 64 | std::unordered_set setKeySyms; 65 | for ( xkb_keysym_t uKeySym : pKeysyms ) 66 | { 67 | setKeySyms.emplace( uKeySym ); 68 | } 69 | 70 | m_KeyboardTriggers.emplace_back( std::move( setKeySyms ) ); 71 | } 72 | 73 | void ClearTriggers() 74 | { 75 | m_KeyboardTriggers.clear(); 76 | } 77 | 78 | void Arm( uint32_t uArmFlags ) 79 | { 80 | m_ouArmFlags = uArmFlags; 81 | } 82 | 83 | void Disarm() 84 | { 85 | m_ouArmFlags = std::nullopt; 86 | } 87 | 88 | // 89 | 90 | bool IsArmed() { return m_ouArmFlags != std::nullopt; } 91 | std::span GetKeyboardTriggers() { return m_KeyboardTriggers; } 92 | 93 | bool Execute() 94 | { 95 | if ( !IsArmed() ) 96 | return false; 97 | 98 | uint32_t uArmFlags = *m_ouArmFlags; 99 | bool bBlockInput = !!( uArmFlags & GAMESCOPE_ACTION_BINDING_ARM_FLAG_NO_BLOCK ); 100 | 101 | uint32_t uTriggerFlags = GAMESCOPE_ACTION_BINDING_TRIGGER_FLAG_KEYBOARD; 102 | 103 | uint64_t ulNow = get_time_in_nanos(); 104 | 105 | static uint32_t s_uSequence = 0; 106 | uint32_t uTimeLo = static_cast( ulNow & 0xffffffff ); 107 | uint32_t uTimeHi = static_cast( ulNow >> 32 ); 108 | gamescope_action_binding_send_triggered( GetResource(), s_uSequence++, uTriggerFlags, uTimeLo, uTimeHi ); 109 | 110 | if ( uArmFlags & GAMESCOPE_ACTION_BINDING_ARM_FLAG_ONE_SHOT ) 111 | Disarm(); 112 | 113 | return bBlockInput; 114 | } 115 | 116 | static std::span GetBindings() 117 | { 118 | return s_Bindings; 119 | } 120 | 121 | private: 122 | std::string m_sDescription; 123 | std::vector m_KeyboardTriggers; 124 | 125 | std::optional m_ouArmFlags; 126 | 127 | static std::vector s_Bindings; 128 | }; 129 | 130 | const struct gamescope_action_binding_interface CGamescopeActionBinding::Implementation = 131 | { 132 | .destroy = WL_PROTO_DESTROY(), 133 | .set_description = WL_PROTO( CGamescopeActionBinding, SetDescription ), 134 | .add_keyboard_trigger = WL_PROTO( CGamescopeActionBinding, AddKeyboardTrigger ), 135 | .clear_triggers = WL_PROTO( CGamescopeActionBinding, ClearTriggers ), 136 | .arm = WL_PROTO( CGamescopeActionBinding, Arm ), 137 | .disarm = WL_PROTO( CGamescopeActionBinding, Disarm ), 138 | }; 139 | 140 | std::vector CGamescopeActionBinding::s_Bindings; 141 | 142 | ////////////////////////////////// 143 | // CGamescopeActionBindingManager 144 | ////////////////////////////////// 145 | class CGamescopeActionBindingManager : public CWaylandResource 146 | { 147 | public: 148 | WL_PROTO_DEFINE( gamescope_action_binding_manager, 1 ); 149 | WL_PROTO_DEFAULT_CONSTRUCTOR(); 150 | 151 | void CreateActionBinding( uint32_t uId ) 152 | { 153 | CWaylandResource::Create( m_pClient, m_uVersion, uId ); 154 | } 155 | }; 156 | 157 | const struct gamescope_action_binding_manager_interface CGamescopeActionBindingManager::Implementation = 158 | { 159 | .destroy = WL_PROTO_DESTROY(), 160 | .create_action_binding = WL_PROTO( CGamescopeActionBindingManager, CreateActionBinding ), 161 | }; 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/WaylandServer/LinuxDrmSyncobj.h: -------------------------------------------------------------------------------- 1 | #include "WaylandProtocol.h" 2 | #include "WaylandServerLegacy.h" 3 | #include "../Timeline.h" 4 | 5 | #include "linux-drm-syncobj-v1-protocol.h" 6 | 7 | namespace gamescope::WaylandServer 8 | { 9 | 10 | class CLinuxDrmSyncobjTimeline : public CWaylandResource 11 | { 12 | public: 13 | WL_PROTO_DEFINE( wp_linux_drm_syncobj_timeline_v1, 1 ); 14 | 15 | CLinuxDrmSyncobjTimeline( WaylandResourceDesc_t desc, std::shared_ptr pTimeline ) 16 | : CWaylandResource( desc ) 17 | , m_pTimeline{ std::move( pTimeline ) } 18 | { 19 | } 20 | 21 | std::shared_ptr GetTimeline() const 22 | { 23 | return m_pTimeline; 24 | } 25 | 26 | private: 27 | std::shared_ptr m_pTimeline; 28 | }; 29 | 30 | const struct wp_linux_drm_syncobj_timeline_v1_interface CLinuxDrmSyncobjTimeline::Implementation = 31 | { 32 | .destroy = WL_PROTO_DESTROY(), 33 | }; 34 | 35 | class CLinuxDrmSyncobjSurface : public CWaylandResource 36 | { 37 | public: 38 | WL_PROTO_DEFINE( wp_linux_drm_syncobj_surface_v1, 1 ); 39 | 40 | CLinuxDrmSyncobjSurface( WaylandResourceDesc_t desc, wlserver_wl_surface_info *pWlSurfaceInfo ) 41 | : CWaylandResource( desc ) 42 | , m_pWlSurfaceInfo{ pWlSurfaceInfo } 43 | { 44 | } 45 | 46 | ~CLinuxDrmSyncobjSurface() 47 | { 48 | assert( m_pWlSurfaceInfo->pSyncobjSurface == this ); 49 | m_pWlSurfaceInfo->pSyncobjSurface = nullptr; 50 | } 51 | 52 | bool HasExplicitSync() const 53 | { 54 | return m_pAcquireTimeline || m_pReleaseTimeline; 55 | } 56 | 57 | std::shared_ptr ExtractAcquireTimelinePoint() const 58 | { 59 | if ( !m_pAcquireTimeline ) 60 | return nullptr; 61 | 62 | return std::make_shared( m_pAcquireTimeline, m_ulAcquirePoint ); 63 | } 64 | 65 | std::shared_ptr ExtractReleaseTimelinePoint() const 66 | { 67 | if ( !m_pReleaseTimeline ) 68 | return nullptr; 69 | 70 | return std::make_shared( m_pReleaseTimeline, m_ulReleasePoint ); 71 | } 72 | 73 | protected: 74 | 75 | void SetAcquirePoint( wl_resource *pWlTimeline, uint32_t uPointHi, uint32_t uPointLo ) 76 | { 77 | if ( !pWlTimeline ) 78 | { 79 | m_pAcquireTimeline = nullptr; 80 | return; 81 | } 82 | 83 | CLinuxDrmSyncobjTimeline *pTimeline = CWaylandResource::FromWlResource( pWlTimeline ); 84 | m_pAcquireTimeline = pTimeline->GetTimeline(); 85 | m_ulAcquirePoint = ToUint64( uPointHi, uPointLo ); 86 | } 87 | 88 | void SetReleasePoint( wl_resource *pWlTimeline, uint32_t uPointHi, uint32_t uPointLo ) 89 | { 90 | if ( !pWlTimeline ) 91 | { 92 | m_pReleaseTimeline = nullptr; 93 | return; 94 | } 95 | 96 | CLinuxDrmSyncobjTimeline *pTimeline = CWaylandResource::FromWlResource( pWlTimeline ); 97 | m_pReleaseTimeline = pTimeline->GetTimeline(); 98 | m_ulReleasePoint = ToUint64( uPointHi, uPointLo ); 99 | } 100 | 101 | private: 102 | wlserver_wl_surface_info *m_pWlSurfaceInfo = nullptr; 103 | 104 | std::shared_ptr m_pAcquireTimeline; 105 | uint64_t m_ulAcquirePoint = 0; 106 | 107 | std::shared_ptr m_pReleaseTimeline; 108 | uint64_t m_ulReleasePoint = 0; 109 | }; 110 | 111 | const struct wp_linux_drm_syncobj_surface_v1_interface CLinuxDrmSyncobjSurface::Implementation = 112 | { 113 | .destroy = WL_PROTO_DESTROY(), 114 | .set_acquire_point = WL_PROTO( CLinuxDrmSyncobjSurface, SetAcquirePoint ), 115 | .set_release_point = WL_PROTO( CLinuxDrmSyncobjSurface, SetReleasePoint ), 116 | }; 117 | 118 | class CLinuxDrmSyncobjManager : public CWaylandResource 119 | { 120 | public: 121 | WL_PROTO_DEFINE( wp_linux_drm_syncobj_manager_v1, 1 ); 122 | WL_PROTO_DEFAULT_CONSTRUCTOR(); 123 | 124 | void GetSurface( uint32_t uId, wl_resource *pSurfaceResource ) 125 | { 126 | struct wlr_surface *pWlrSurface = wlr_surface_from_resource( pSurfaceResource ); 127 | struct wlserver_wl_surface_info *pWlSurfaceInfo = get_wl_surface_info( pWlrSurface ); 128 | if ( !pWlSurfaceInfo ) 129 | { 130 | wl_client_post_implementation_error( m_pClient, "No wl_surface" ); 131 | return; 132 | } 133 | 134 | if ( pWlSurfaceInfo->pSyncobjSurface ) 135 | { 136 | wl_resource_post_error( m_pResource, 137 | WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_SURFACE_EXISTS, 138 | "wp_linux_drm_syncobj_surface_v1 already created for this surface" ); 139 | return; 140 | } 141 | 142 | pWlSurfaceInfo->pSyncobjSurface = CWaylandResource::Create( m_pClient, m_uVersion, uId, pWlSurfaceInfo ); 143 | } 144 | 145 | void ImportTimeline( uint32_t uId, int32_t nFd ) 146 | { 147 | // Transfer nFd to a new CTimeline. 148 | std::shared_ptr pTimeline = std::make_shared( nFd ); 149 | if ( !CheckAllocation( pTimeline ) ) 150 | return; 151 | 152 | if ( !pTimeline->IsValid() ) 153 | { 154 | wl_resource_post_error( m_pResource, 155 | WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_INVALID_TIMELINE, 156 | "Failed to import syncobj timeline" ); 157 | return; 158 | } 159 | 160 | CWaylandResource::Create( m_pClient, m_uVersion, uId, std::move( pTimeline ) ); 161 | } 162 | 163 | }; 164 | 165 | const struct wp_linux_drm_syncobj_manager_v1_interface CLinuxDrmSyncobjManager::Implementation = 166 | { 167 | .destroy = WL_PROTO_DESTROY(), 168 | .get_surface = WL_PROTO( CLinuxDrmSyncobjManager, GetSurface ), 169 | .import_timeline = WL_PROTO( CLinuxDrmSyncobjManager, ImportTimeline ), 170 | }; 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/WaylandServer/Reshade.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WaylandProtocol.h" 4 | 5 | #include "gamescope-reshade-protocol.h" 6 | #include "reshade_effect_manager.hpp" 7 | 8 | #include 9 | 10 | namespace gamescope::WaylandServer 11 | { 12 | class CReshadeManager : public CWaylandResource 13 | { 14 | public: 15 | WL_PROTO_DEFINE( gamescope_reshade, 1 ); 16 | WL_PROTO_DEFAULT_CONSTRUCTOR(); 17 | 18 | void EffectReadyCallback( const char* path ) 19 | { 20 | gamescope_reshade_send_effect_ready(GetResource(), path); 21 | } 22 | 23 | void SetEffect( const char *path ) 24 | { 25 | reshade_effect_manager_set_effect( path, 26 | [this]( const char* callbackPath ) { EffectReadyCallback( callbackPath ); } 27 | ); 28 | } 29 | 30 | void EnableEffect() 31 | { 32 | reshade_effect_manager_enable_effect(); 33 | } 34 | 35 | void SetUniformVariable( const char *key, struct wl_array *value ) 36 | { 37 | uint8_t* data_copy = new uint8_t[value->size]; 38 | std::memcpy(data_copy, value->data, value->size); 39 | reshade_effect_manager_set_uniform_variable( key, data_copy ); 40 | } 41 | 42 | void DisableEffect() 43 | { 44 | reshade_effect_manager_disable_effect(); 45 | } 46 | 47 | }; 48 | 49 | const struct gamescope_reshade_interface CReshadeManager::Implementation = 50 | { 51 | .destroy = WL_PROTO_DESTROY(), 52 | .set_effect = WL_PROTO( CReshadeManager, SetEffect ), 53 | .enable_effect = WL_PROTO( CReshadeManager, EnableEffect ), 54 | .set_uniform_variable = WL_PROTO( CReshadeManager, SetUniformVariable ), 55 | .disable_effect = WL_PROTO( CReshadeManager, DisableEffect ) 56 | }; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/WaylandServer/WaylandDecls.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gamescope::WaylandServer 4 | { 5 | class CWaylandResource; 6 | 7 | template 8 | class CWaylandProtocol; 9 | 10 | class CLinuxDrmSyncobjManager; 11 | class CLinuxDrmSyncobjSurface; 12 | class CLinuxDrmSyncobjTimeline; 13 | using CLinuxDrmSyncobj = CWaylandProtocol; 14 | 15 | class CReshadeManager; 16 | using CReshade = CWaylandProtocol; 17 | 18 | class CGamescopeActionBindingManager; 19 | using CGamescopeActionBindingProtocol = CWaylandProtocol; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/WaylandServer/WaylandProtocol.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WaylandResource.h" 4 | #include 5 | 6 | namespace gamescope::WaylandServer 7 | { 8 | 9 | template 10 | class CWaylandProtocol : public NonCopyable 11 | { 12 | public: 13 | CWaylandProtocol( wl_display *pDisplay ) 14 | : m_pDisplay{ pDisplay } 15 | { 16 | ( CreateGlobal(), ... ); 17 | } 18 | private: 19 | 20 | template 21 | void CreateGlobal() 22 | { 23 | wl_global *pGlobal = wl_global_create( m_pDisplay, T::Interface, T::Version, this, 24 | []( struct wl_client *pClient, void *pData, uint32_t uVersion, uint32_t uId ) 25 | { 26 | CWaylandProtocol *pProtocol = reinterpret_cast( pData ); 27 | pProtocol->Bind( pClient, uVersion, uId ); 28 | } ); 29 | 30 | m_pGlobals.emplace_back( pGlobal ); 31 | } 32 | 33 | template 34 | void Bind( struct wl_client *pClient, uint32_t uVersion, uint32_t uId ) 35 | { 36 | CWaylandResource::Create( pClient, uVersion, uId ); 37 | } 38 | 39 | wl_display *m_pDisplay = nullptr; 40 | std::vector m_pGlobals; 41 | }; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/WaylandServer/WaylandResource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "../Utils/NonCopyable.h" 6 | 7 | namespace gamescope::WaylandServer 8 | { 9 | 10 | #define WL_PROTO_NULL() [] ( Args... args ) { } 11 | #define WL_PROTO_DESTROY() [] ( wl_client *pClient, wl_resource *pResource, Args... args ) { wl_resource_destroy( pResource ); } 12 | #define WL_PROTO( type, name ) \ 13 | [] ( wl_client *pClient, wl_resource *pResource, Args... args ) \ 14 | { \ 15 | type *pThing = reinterpret_cast( wl_resource_get_user_data( pResource ) ); \ 16 | pThing->name( std::forward(args)... ); \ 17 | } 18 | 19 | #define WL_PROTO_DEFINE( type, version ) \ 20 | static constexpr uint32_t Version = version; \ 21 | static constexpr const struct wl_interface *Interface = & type##_interface; \ 22 | static const struct type##_interface Implementation; 23 | 24 | #define WL_PROTO_DEFAULT_CONSTRUCTOR() \ 25 | using CWaylandResource::CWaylandResource; 26 | 27 | struct WaylandResourceDesc_t 28 | { 29 | wl_client *pClient; 30 | wl_resource *pResource; 31 | uint32_t uVersion; 32 | }; 33 | 34 | class CWaylandResource : public NonCopyable 35 | { 36 | public: 37 | 38 | CWaylandResource( WaylandResourceDesc_t desc ) 39 | : m_pClient{ desc.pClient } 40 | , m_pResource{ desc.pResource } 41 | , m_uVersion{ desc.uVersion } 42 | { 43 | } 44 | 45 | ~CWaylandResource() 46 | { 47 | } 48 | 49 | template 50 | static bool CheckAllocation( const T &object, wl_client *pClient ) 51 | { 52 | if ( !object ) 53 | { 54 | wl_client_post_no_memory( pClient ); 55 | return false; 56 | } 57 | 58 | return true; 59 | } 60 | 61 | template 62 | bool CheckAllocation( const T &object ) 63 | { 64 | return CheckAllocation( object, m_pClient ); 65 | } 66 | 67 | template 68 | static T *FromWlResource( wl_resource *pResource ) 69 | { 70 | T *pObject = reinterpret_cast( wl_resource_get_user_data( pResource ) ); 71 | return pObject; 72 | } 73 | 74 | template 75 | static T *Create( wl_client *pClient, uint32_t uVersion, uint32_t uId, Args... args ) 76 | { 77 | wl_resource *pResource = wl_resource_create( pClient, T::Interface, uVersion, uId ); 78 | if ( !CheckAllocation( pResource, pClient ) ) 79 | return nullptr; 80 | 81 | WaylandResourceDesc_t desc = 82 | { 83 | .pClient = pClient, 84 | .pResource = pResource, 85 | .uVersion = uVersion, 86 | }; 87 | T *pThing = new T{ desc, std::forward(args)... }; 88 | if ( !CheckAllocation( pThing, pClient ) ) 89 | return nullptr; 90 | 91 | wl_resource_set_implementation( pResource, &T::Implementation, pThing, 92 | []( wl_resource *pResource ) 93 | { 94 | T *pObject = CWaylandResource::FromWlResource( pResource ); 95 | delete pObject; 96 | }); 97 | 98 | return pThing; 99 | } 100 | 101 | static uint64_t ToUint64( uint32_t uHi, uint32_t uLo ) 102 | { 103 | return (uint64_t(uHi) << 32) | uLo; 104 | } 105 | 106 | wl_client *GetClient() const { return m_pClient; } 107 | wl_resource *GetResource() const { return m_pResource; } 108 | uint32_t GetVersion() const { return m_uVersion; } 109 | protected: 110 | wl_client *m_pClient = nullptr; 111 | wl_resource *m_pResource = nullptr; 112 | uint32_t m_uVersion = 0; 113 | }; 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/WaylandServer/WaylandServerLegacy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "WaylandDecls.h" 5 | #include 6 | #include 7 | #include 8 | #include "vulkan_include.h" 9 | 10 | 11 | #include "wlr_begin.hpp" 12 | #include 13 | #include "wlr_end.hpp" 14 | 15 | 16 | namespace gamescope 17 | { 18 | class BackendBlob; 19 | } 20 | 21 | struct wlserver_x11_surface_info; 22 | struct wlserver_xdg_surface_info; 23 | 24 | struct wlserver_vk_swapchain_feedback 25 | { 26 | uint32_t image_count; 27 | VkFormat vk_format; 28 | VkColorSpaceKHR vk_colorspace; 29 | VkCompositeAlphaFlagBitsKHR vk_composite_alpha; 30 | VkSurfaceTransformFlagBitsKHR vk_pre_transform; 31 | VkBool32 vk_clipped; 32 | std::shared_ptr vk_engine_name; 33 | 34 | std::shared_ptr hdr_metadata_blob; 35 | }; 36 | 37 | 38 | struct wlserver_wl_surface_info 39 | { 40 | wlserver_x11_surface_info *x11_surface = nullptr; 41 | wlserver_xdg_surface_info *xdg_surface = nullptr; 42 | 43 | gamescope::WaylandServer::CLinuxDrmSyncobjSurface *pSyncobjSurface = nullptr; 44 | 45 | struct wlr_surface *wlr = nullptr; 46 | struct wl_listener commit; 47 | struct wl_listener destroy; 48 | 49 | std::shared_ptr swapchain_feedback = {}; 50 | std::optional oCurrentPresentMode; 51 | 52 | uint64_t sequence = 0; 53 | std::vector pending_presentation_feedbacks; 54 | 55 | std::vector gamescope_swapchains; 56 | std::optional present_id = std::nullopt; 57 | uint64_t desired_present_time = 0; 58 | 59 | uint64_t last_refresh_cycle = 0; 60 | }; 61 | 62 | wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf); 63 | -------------------------------------------------------------------------------- /src/backends.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gamescope 4 | { 5 | // Backend enum. 6 | enum GamescopeBackend 7 | { 8 | Auto, 9 | DRM, 10 | SDL, 11 | OpenVR, 12 | Headless, 13 | Wayland, 14 | }; 15 | 16 | // Backend forward declarations. 17 | class CSDLBackend; 18 | class CDRMBackend; 19 | class COpenVRBackend; 20 | class CHeadlessBackend; 21 | class CWaylandBackend; 22 | } 23 | -------------------------------------------------------------------------------- /src/color_helpers_impl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "color_helpers.h" 3 | 4 | namespace rendervulkan { 5 | static constexpr uint32_t s_nLutEdgeSize3d = 17; 6 | static constexpr uint32_t s_nLutSize1d = 4096; 7 | } 8 | 9 | namespace color_bench { 10 | static constexpr uint32_t nLutEdgeSize3d = 17; 11 | static constexpr uint32_t nLutSize1d = 4096; 12 | } 13 | 14 | namespace ns_color_tests { 15 | [[maybe_unused]] static constexpr uint32_t nLutEdgeSize3d = 17; 16 | } 17 | 18 | #ifdef COLOR_HELPERS_CPP 19 | REGISTER_LUT_EDGE_SIZE(rendervulkan::s_nLutEdgeSize3d); 20 | #endif -------------------------------------------------------------------------------- /src/commit.cpp: -------------------------------------------------------------------------------- 1 | #include "wlserver.hpp" 2 | #include "rendervulkan.hpp" 3 | #include "steamcompmgr.hpp" 4 | #include "commit.h" 5 | 6 | #include "gpuvis_trace_utils.h" 7 | 8 | extern gamescope::CAsyncWaiter> g_ImageWaiter; 9 | 10 | commit_t::commit_t() 11 | { 12 | static uint64_t maxCommmitID = 0; 13 | commitID = ++maxCommmitID; 14 | } 15 | commit_t::~commit_t() 16 | { 17 | { 18 | std::unique_lock lock( m_WaitableCommitStateMutex ); 19 | CloseFenceInternal(); 20 | } 21 | 22 | if ( vulkanTex != nullptr ) 23 | vulkanTex = nullptr; 24 | 25 | wlserver_lock(); 26 | if (!presentation_feedbacks.empty()) 27 | { 28 | wlserver_presentation_feedback_discard(surf, presentation_feedbacks); 29 | // presentation_feedbacks cleared by wlserver_presentation_feedback_discard 30 | } 31 | wlr_buffer_unlock( buf ); 32 | wlserver_unlock(); 33 | } 34 | 35 | GamescopeAppTextureColorspace commit_t::colorspace() const 36 | { 37 | VkColorSpaceKHR colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; 38 | if (feedback && vulkanTex) 39 | colorspace = feedback->vk_colorspace; 40 | 41 | if (!vulkanTex) 42 | return GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; 43 | 44 | return VkColorSpaceToGamescopeAppTextureColorSpace(vulkanTex->format(), colorspace); 45 | } 46 | 47 | int commit_t::GetFD() 48 | { 49 | return m_nCommitFence; 50 | } 51 | 52 | void commit_t::OnPollIn() 53 | { 54 | gpuvis_trace_end_ctx_printf( commitID, "wait fence" ); 55 | 56 | { 57 | std::unique_lock lock( m_WaitableCommitStateMutex ); 58 | if ( !CloseFenceInternal() ) 59 | return; 60 | } 61 | 62 | Signal(); 63 | 64 | nudge_steamcompmgr(); 65 | } 66 | 67 | void commit_t::Signal() 68 | { 69 | uint64_t frametime; 70 | if ( m_bMangoNudge ) 71 | { 72 | uint64_t now = get_time_in_nanos(); 73 | static uint64_t lastFrameTime = now; 74 | frametime = now - lastFrameTime; 75 | lastFrameTime = now; 76 | } 77 | 78 | // TODO: Move this so it's called in the main loop. 79 | // Instead of looping over all the windows like before. 80 | // When we get the new IWaitable stuff in there. 81 | { 82 | std::unique_lock< std::mutex > lock( m_pDoneCommits->listCommitsDoneLock ); 83 | m_pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ 84 | .winSeq = win_seq, 85 | .commitID = commitID, 86 | .desiredPresentTime = desired_present_time, 87 | .fifo = fifo, 88 | } ); 89 | } 90 | 91 | if ( m_bMangoNudge ) 92 | mangoapp_update( IsPerfOverlayFIFO() ? uint64_t(~0ull) : frametime, frametime, uint64_t(~0ull) ); 93 | } 94 | 95 | void commit_t::OnPollHangUp() 96 | { 97 | std::unique_lock lock( m_WaitableCommitStateMutex ); 98 | CloseFenceInternal(); 99 | } 100 | 101 | bool commit_t::IsPerfOverlayFIFO() 102 | { 103 | return fifo || is_steam; 104 | } 105 | 106 | // Returns true if we had a fence that was closed. 107 | bool commit_t::CloseFenceInternal() 108 | { 109 | if ( m_nCommitFence < 0 ) 110 | return false; 111 | 112 | // Will automatically remove from epoll! 113 | g_ImageWaiter.RemoveWaitable( this ); 114 | close( m_nCommitFence ); 115 | m_nCommitFence = -1; 116 | return true; 117 | } 118 | 119 | void commit_t::SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ) 120 | { 121 | std::unique_lock lock( m_WaitableCommitStateMutex ); 122 | CloseFenceInternal(); 123 | 124 | m_nCommitFence = nFence; 125 | m_bMangoNudge = bMangoNudge; 126 | m_pDoneCommits = pDoneCommits; 127 | } 128 | 129 | void calc_scale_factor(float &out_scale_x, float &out_scale_y, float sourceWidth, float sourceHeight); 130 | 131 | bool commit_t::ShouldPreemptivelyUpscale() 132 | { 133 | // Don't pre-emptively upscale if we are not a FIFO commit. 134 | // Don't want to FSR upscale 1000fps content. 135 | if ( !fifo ) 136 | return false; 137 | 138 | // If we support the upscaling filter in hardware, don't 139 | // pre-emptively do it via shaders. 140 | if ( DoesHardwareSupportUpscaleFilter( g_upscaleFilter ) ) 141 | return false; 142 | 143 | if ( !vulkanTex ) 144 | return false; 145 | 146 | float flScaleX = 1.0f; 147 | float flScaleY = 1.0f; 148 | // I wish this function was more programatic with its inputs, but it does do exactly what we want right now... 149 | // It should also return a std::pair or a glm uvec 150 | calc_scale_factor( flScaleX, flScaleY, vulkanTex->width(), vulkanTex->height() ); 151 | 152 | return !close_enough( flScaleX, 1.0f ) || !close_enough( flScaleY, 1.0f ); 153 | } 154 | -------------------------------------------------------------------------------- /src/commit.h: -------------------------------------------------------------------------------- 1 | #include "steamcompmgr_shared.hpp" 2 | #include "Utils/NonCopyable.h" 3 | 4 | #include 5 | #include "main.hpp" 6 | #include "rendervulkan.hpp" 7 | 8 | class CVulkanTexture; 9 | 10 | struct UpscaledTexture_t 11 | { 12 | GamescopeUpscaleFilter eFilter{}; 13 | GamescopeUpscaleScaler eScaler{}; 14 | uint32_t uOutputWidth = 0; 15 | uint32_t uOutputHeight = 0; 16 | gamescope::Rc pTexture{}; 17 | VkColorSpaceKHR colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; 18 | }; 19 | 20 | struct commit_t final : public gamescope::RcObject, public gamescope::IWaitable, public gamescope::NonCopyable 21 | { 22 | commit_t(); 23 | ~commit_t(); 24 | 25 | GamescopeAppTextureColorspace colorspace() const; 26 | 27 | // For waitable: 28 | int GetFD() final; 29 | void OnPollIn() final; 30 | void Signal(); 31 | void OnPollHangUp() final; 32 | 33 | bool IsPerfOverlayFIFO(); 34 | 35 | // Returns true if we had a fence that was closed. 36 | bool CloseFenceInternal(); 37 | void SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ); 38 | 39 | bool ShouldPreemptivelyUpscale(); 40 | 41 | struct wlr_buffer *buf = nullptr; 42 | gamescope::Rc vulkanTex; 43 | std::optional upscaledTexture; 44 | 45 | gamescope::Rc GetTexture( GamescopeUpscaleFilter eFilter, GamescopeUpscaleScaler eScaler, GamescopeAppTextureColorspace &colorspace ) 46 | { 47 | if ( upscaledTexture && 48 | upscaledTexture->eFilter == eFilter && 49 | upscaledTexture->eScaler == eScaler && 50 | upscaledTexture->uOutputWidth == g_nOutputWidth && 51 | upscaledTexture->uOutputHeight == g_nOutputHeight ) 52 | { 53 | colorspace = VkColorSpaceToGamescopeAppTextureColorSpace( upscaledTexture->pTexture->format(), upscaledTexture->colorspace ); 54 | return upscaledTexture->pTexture; 55 | } 56 | 57 | colorspace = this->colorspace(); 58 | return vulkanTex; 59 | } 60 | 61 | uint64_t commitID = 0; 62 | bool done = false; 63 | bool async = false; 64 | bool fifo = false; 65 | bool is_steam = false; 66 | std::optional feedback = std::nullopt; 67 | 68 | uint64_t win_seq = 0; 69 | struct wlr_surface *surf = nullptr; 70 | std::vector presentation_feedbacks; 71 | 72 | std::optional present_id = std::nullopt; 73 | uint64_t desired_present_time = 0; 74 | uint64_t earliest_present_time = 0; 75 | uint64_t present_margin = 0; 76 | 77 | std::mutex m_WaitableCommitStateMutex; 78 | int m_nCommitFence = -1; 79 | bool m_bMangoNudge = false; 80 | CommitDoneList_t *m_pDoneCommits = nullptr; // I hate this 81 | }; -------------------------------------------------------------------------------- /src/convar.cpp: -------------------------------------------------------------------------------- 1 | #include "convar.h" 2 | #include "Utils/Version.h" 3 | #include 4 | 5 | LogScope console_log("console"); 6 | 7 | extern void PrintGamescopeVersion(); 8 | 9 | namespace gamescope 10 | { 11 | ConCommand::ConCommand( std::string_view pszName, std::string_view pszDescription, ConCommandFunc func, bool bRegisterScript ) 12 | : m_pszName{ pszName } 13 | , m_pszDescription{ pszDescription } 14 | , m_Func{ func } 15 | { 16 | assert( !GetCommands().contains( pszName ) ); 17 | GetCommands()[ std::string( pszName ) ] = this; 18 | 19 | #if HAVE_SCRIPTING 20 | if ( bRegisterScript ) 21 | CScriptScopedLock().Manager().Gamescope().Convars.Base[pszName] = this; 22 | #endif 23 | } 24 | 25 | ConCommand::~ConCommand() 26 | { 27 | GetCommands().erase( GetCommands().find( m_pszName ) ); 28 | } 29 | 30 | bool ConCommand::Exec( std::span args ) 31 | { 32 | if ( args.size() < 1 ) 33 | { 34 | console_log.warnf( "No command specified." ); 35 | return false; 36 | } 37 | 38 | std::string_view commandName = args[0]; 39 | auto iter = GetCommands().find( commandName ); 40 | if ( iter == GetCommands().end() ) 41 | { 42 | console_log.warnf( "Command not found." ); 43 | return false; 44 | } 45 | 46 | iter->second->Invoke( args ); 47 | return true; 48 | } 49 | 50 | Dict& ConCommand::GetCommands() 51 | { 52 | static Dict s_Commands; 53 | return s_Commands; 54 | } 55 | 56 | static ConCommand cc_help("help", "List all Gamescope convars and commands", 57 | []( std::span args ) 58 | { 59 | auto &commands = ConCommand::GetCommands(); 60 | 61 | struct CommandHelp { std::string_view pszName; std::string_view pszDesc; }; 62 | std::vector commandHelps; 63 | for ( auto &command : commands ) 64 | commandHelps.emplace_back( command.second->GetName(), command.second->GetDescription() ); 65 | std::sort( commandHelps.begin(), commandHelps.end(), 66 | []( const CommandHelp &a, const CommandHelp &b ) 67 | { 68 | return a.pszName < b.pszName; 69 | }); 70 | 71 | for ( auto &help : commandHelps ) 72 | { 73 | console_log.infof( "%.*s: %.*s", 74 | (int)help.pszName.size(), help.pszName.data(), 75 | (int)help.pszDesc.size(), help.pszDesc.data() ); 76 | } 77 | }); 78 | 79 | static ConCommand cc_version("version", "Print current Gamescope version", 80 | []( std::span args ) 81 | { 82 | PrintVersion(); 83 | }); 84 | } -------------------------------------------------------------------------------- /src/docs/Steam Deck Display Pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ValveSoftware/gamescope/df15bcd81ec65e109c740019cacff06ac6fb07db/src/docs/Steam Deck Display Pipeline.png -------------------------------------------------------------------------------- /src/drm_include.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "wlr_begin.hpp" 9 | #include 10 | #include 11 | #include "wlr_end.hpp" 12 | 13 | #include "hdmi.h" 14 | 15 | // Josh: Okay whatever, this header isn't 16 | // available for whatever stupid reason. :v 17 | //#include 18 | enum drm_color_encoding { 19 | DRM_COLOR_YCBCR_BT601, 20 | DRM_COLOR_YCBCR_BT709, 21 | DRM_COLOR_YCBCR_BT2020, 22 | DRM_COLOR_ENCODING_MAX, 23 | }; 24 | 25 | enum drm_color_range { 26 | DRM_COLOR_YCBCR_LIMITED_RANGE, 27 | DRM_COLOR_YCBCR_FULL_RANGE, 28 | DRM_COLOR_RANGE_MAX, 29 | }; 30 | 31 | enum amdgpu_transfer_function { 32 | AMDGPU_TRANSFER_FUNCTION_DEFAULT, 33 | AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF, 34 | AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF, 35 | AMDGPU_TRANSFER_FUNCTION_PQ_EOTF, 36 | AMDGPU_TRANSFER_FUNCTION_IDENTITY, 37 | AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF, 38 | AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF, 39 | AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF, 40 | AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF, 41 | AMDGPU_TRANSFER_FUNCTION_BT709_OETF, 42 | AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF, 43 | AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF, 44 | AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF, 45 | AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF, 46 | AMDGPU_TRANSFER_FUNCTION_COUNT 47 | }; 48 | 49 | enum drm_panel_orientation { 50 | DRM_MODE_PANEL_ORIENTATION_UNKNOWN = -1, 51 | DRM_MODE_PANEL_ORIENTATION_NORMAL = 0, 52 | DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP, 53 | DRM_MODE_PANEL_ORIENTATION_LEFT_UP, 54 | DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, 55 | }; 56 | 57 | enum drm_colorspace { 58 | /* For Default case, driver will set the colorspace */ 59 | DRM_MODE_COLORIMETRY_DEFAULT = 0, 60 | /* CEA 861 Normal Colorimetry options */ 61 | DRM_MODE_COLORIMETRY_NO_DATA = 0, 62 | DRM_MODE_COLORIMETRY_SMPTE_170M_YCC = 1, 63 | DRM_MODE_COLORIMETRY_BT709_YCC = 2, 64 | /* CEA 861 Extended Colorimetry Options */ 65 | DRM_MODE_COLORIMETRY_XVYCC_601 = 3, 66 | DRM_MODE_COLORIMETRY_XVYCC_709 = 4, 67 | DRM_MODE_COLORIMETRY_SYCC_601 = 5, 68 | DRM_MODE_COLORIMETRY_OPYCC_601 = 6, 69 | DRM_MODE_COLORIMETRY_OPRGB = 7, 70 | DRM_MODE_COLORIMETRY_BT2020_CYCC = 8, 71 | DRM_MODE_COLORIMETRY_BT2020_RGB = 9, 72 | DRM_MODE_COLORIMETRY_BT2020_YCC = 10, 73 | /* Additional Colorimetry extension added as part of CTA 861.G */ 74 | DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 = 11, 75 | DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER = 12, 76 | /* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format */ 77 | DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED = 13, 78 | DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT = 14, 79 | DRM_MODE_COLORIMETRY_BT601_YCC = 15, 80 | DRM_MODE_COLORIMETRY_COUNT 81 | }; 82 | 83 | /* Content type options */ 84 | #define DRM_MODE_CONTENT_TYPE_NO_DATA 0 85 | #define DRM_MODE_CONTENT_TYPE_GRAPHICS 1 86 | #define DRM_MODE_CONTENT_TYPE_PHOTO 2 87 | #define DRM_MODE_CONTENT_TYPE_CINEMA 3 88 | #define DRM_MODE_CONTENT_TYPE_GAME 4 89 | -------------------------------------------------------------------------------- /src/edid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gamescope 9 | { 10 | struct BackendConnectorHDRInfo; 11 | 12 | const char *GetPatchedEdidPath(); 13 | void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ); 14 | std::vector GenerateSimpleEdid( uint32_t uWidth, uint32_t uHeight ); 15 | 16 | std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ); 17 | } -------------------------------------------------------------------------------- /src/gamescope_base_edid.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ValveSoftware/gamescope/df15bcd81ec65e109c740019cacff06ac6fb07db/src/gamescope_base_edid.bin -------------------------------------------------------------------------------- /src/gamescope_shared.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gamescope 6 | { 7 | class BackendBlob; 8 | 9 | enum GamescopeModeGeneration 10 | { 11 | GAMESCOPE_MODE_GENERATE_CVT, 12 | GAMESCOPE_MODE_GENERATE_FIXED, 13 | }; 14 | 15 | enum GamescopeScreenType 16 | { 17 | GAMESCOPE_SCREEN_TYPE_INTERNAL, 18 | GAMESCOPE_SCREEN_TYPE_EXTERNAL, 19 | 20 | GAMESCOPE_SCREEN_TYPE_COUNT 21 | }; 22 | } 23 | 24 | enum GamescopeAppTextureColorspace 25 | { 26 | GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR = 0, 27 | GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, 28 | GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB, 29 | GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ, 30 | GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, 31 | }; 32 | const uint32_t GamescopeAppTextureColorspace_Count = 5; 33 | const uint32_t GamescopeAppTextureColorspace_Bits = 3; 34 | 35 | inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) 36 | { 37 | return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB || 38 | colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; 39 | } 40 | 41 | enum GamescopeSelection 42 | { 43 | GAMESCOPE_SELECTION_CLIPBOARD, 44 | GAMESCOPE_SELECTION_PRIMARY, 45 | 46 | GAMESCOPE_SELECTION_COUNT, 47 | }; 48 | 49 | enum GamescopePanelOrientation 50 | { 51 | GAMESCOPE_PANEL_ORIENTATION_0, // normal 52 | GAMESCOPE_PANEL_ORIENTATION_270, // right 53 | GAMESCOPE_PANEL_ORIENTATION_90, // left 54 | GAMESCOPE_PANEL_ORIENTATION_180, // upside down 55 | 56 | GAMESCOPE_PANEL_ORIENTATION_AUTO, 57 | }; 58 | 59 | // Disable partial composition for now until we get 60 | // composite priorities working in libliftoff + also 61 | // use the proper libliftoff composite plane system. 62 | static constexpr bool kDisablePartialComposition = true; 63 | -------------------------------------------------------------------------------- /src/hdmi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /* from CTA-861-G */ 6 | #define HDMI_EOTF_SDR 0 7 | #define HDMI_EOTF_TRADITIONAL_HDR 1 8 | #define HDMI_EOTF_ST2084 2 9 | #define HDMI_EOTF_HLG 3 10 | 11 | #if HAVE_DRM 12 | #include 13 | #else 14 | /** 15 | * struct hdr_metadata_infoframe - HDR Metadata Infoframe Data. 16 | * 17 | * HDR Metadata Infoframe as per CTA 861.G spec. This is expected 18 | * to match exactly with the spec. 19 | * 20 | * Userspace is expected to pass the metadata information as per 21 | * the format described in this structure. 22 | */ 23 | struct hdr_metadata_infoframe { 24 | /** 25 | * @eotf: Electro-Optical Transfer Function (EOTF) 26 | * used in the stream. 27 | */ 28 | __u8 eotf; 29 | /** 30 | * @metadata_type: Static_Metadata_Descriptor_ID. 31 | */ 32 | __u8 metadata_type; 33 | /** 34 | * @display_primaries: Color Primaries of the Data. 35 | * These are coded as unsigned 16-bit values in units of 36 | * 0.00002, where 0x0000 represents zero and 0xC350 37 | * represents 1.0000. 38 | * @display_primaries.x: X cordinate of color primary. 39 | * @display_primaries.y: Y cordinate of color primary. 40 | */ 41 | struct { 42 | uint16_t x, y; 43 | } display_primaries[3]; 44 | /** 45 | * @white_point: White Point of Colorspace Data. 46 | * These are coded as unsigned 16-bit values in units of 47 | * 0.00002, where 0x0000 represents zero and 0xC350 48 | * represents 1.0000. 49 | * @white_point.x: X cordinate of whitepoint of color primary. 50 | * @white_point.y: Y cordinate of whitepoint of color primary. 51 | */ 52 | struct { 53 | uint16_t x, y; 54 | } white_point; 55 | /** 56 | * @max_display_mastering_luminance: Max Mastering Display Luminance. 57 | * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, 58 | * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. 59 | */ 60 | uint16_t max_display_mastering_luminance; 61 | /** 62 | * @min_display_mastering_luminance: Min Mastering Display Luminance. 63 | * This value is coded as an unsigned 16-bit value in units of 64 | * 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF 65 | * represents 6.5535 cd/m2. 66 | */ 67 | uint16_t min_display_mastering_luminance; 68 | /** 69 | * @max_cll: Max Content Light Level. 70 | * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, 71 | * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. 72 | */ 73 | uint16_t max_cll; 74 | /** 75 | * @max_fall: Max Frame Average Light Level. 76 | * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, 77 | * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. 78 | */ 79 | uint16_t max_fall; 80 | }; 81 | 82 | /** 83 | * struct hdr_output_metadata - HDR output metadata 84 | * 85 | * Metadata Information to be passed from userspace 86 | */ 87 | struct hdr_output_metadata { 88 | /** 89 | * @metadata_type: Static_Metadata_Descriptor_ID. 90 | */ 91 | uint32_t metadata_type; 92 | /** 93 | * @hdmi_metadata_type1: HDR Metadata Infoframe. 94 | */ 95 | union { 96 | struct hdr_metadata_infoframe hdmi_metadata_type1; 97 | }; 98 | }; 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/ime.hpp: -------------------------------------------------------------------------------- 1 | // Input Method Editor 2 | 3 | #pragma once 4 | 5 | #include "wlserver.hpp" 6 | 7 | void create_ime_manager(struct wlserver_t *wlserver); 8 | 9 | struct wlserver_input_method *create_local_ime(); 10 | void destroy_ime(struct wlserver_input_method * ime); 11 | void type_text(struct wlserver_input_method *ime, const char *text); 12 | -------------------------------------------------------------------------------- /src/layer_defines.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace GamescopeLayerClient 7 | { 8 | // GAMESCOPE_LAYER_CLIENT_FLAGS 9 | namespace Flag { 10 | static constexpr uint32_t DisableHDR = 1u << 0; 11 | static constexpr uint32_t ForceBypass = 1u << 1; 12 | static constexpr uint32_t FrameLimiterAware = 1u << 2; 13 | 14 | static constexpr uint32_t NoSuboptimal = 1u << 3; 15 | static constexpr uint32_t ForceSwapchainExtent = 1u << 4; 16 | } 17 | using Flags = uint32_t; 18 | } -------------------------------------------------------------------------------- /src/log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "Utils/Process.h" 9 | #include "Utils/Defer.h" 10 | #include "convar.h" 11 | #include "log.hpp" 12 | 13 | static constexpr std::string_view GetLogPriorityText( LogPriority ePriority ) 14 | { 15 | switch ( ePriority ) 16 | { 17 | case LOG_SILENT: return "[\e[0;37m" "Shh.." "\e[0m]"; 18 | case LOG_ERROR: return "[\e[0;31m" "Error" "\e[0m]"; 19 | case LOG_WARNING: return "[\e[0;33m" "Warn" "\e[0m] "; 20 | case LOG_DEBUG: return "[\e[0;35m" "Debug" "\e[0m]"; 21 | default: 22 | case LOG_INFO: return "[\e[0;34m" "Info" "\e[0m] "; 23 | } 24 | } 25 | 26 | static constexpr std::string_view GetLogName( LogPriority ePriority ) 27 | { 28 | switch ( ePriority ) 29 | { 30 | case LOG_SILENT: return "silent"; 31 | case LOG_ERROR: return "error"; 32 | case LOG_WARNING: return "warning"; 33 | case LOG_DEBUG: return "debug"; 34 | default: 35 | case LOG_INFO: return "info"; 36 | } 37 | } 38 | 39 | static constexpr LogPriority GetPriorityFromString( std::string_view psvScope ) 40 | { 41 | if ( psvScope == "silent" ) 42 | return LOG_SILENT; 43 | else if ( psvScope == "error" ) 44 | return LOG_ERROR; 45 | else if ( psvScope == "warning" ) 46 | return LOG_WARNING; 47 | else if ( psvScope == "debug" ) 48 | return LOG_DEBUG; 49 | else 50 | return LOG_INFO; 51 | } 52 | 53 | struct LogConVar_t 54 | { 55 | LogConVar_t( LogScope *pScope, std::string_view psvName, LogPriority eDefaultPriority ) 56 | : sName{ std::format( "log_{}", psvName ) } 57 | , sDescription{ std::format( "Max logging priority for the {} channel. Valid options are: [ silent, error, warning, debug, info ].", psvName ) } 58 | , convar 59 | { sName, std::string( GetLogName( eDefaultPriority ) ), sDescription, 60 | [ pScope ]( gamescope::ConVar &cvar ) 61 | { 62 | pScope->SetPriority( GetPriorityFromString( cvar ) ); 63 | }, 64 | } 65 | { 66 | 67 | } 68 | std::string sName; 69 | std::string sDescription; 70 | 71 | gamescope::ConVar convar; 72 | }; 73 | 74 | LogScope::LogScope( std::string_view psvName, LogPriority eMaxPriority ) 75 | : LogScope( psvName, psvName, eMaxPriority ) 76 | { 77 | } 78 | 79 | LogScope::LogScope( std::string_view psvName, std::string_view psvPrefix, LogPriority eMaxPriority ) 80 | : m_psvName{ psvName } 81 | , m_psvPrefix{ psvPrefix } 82 | , m_eMaxPriority{ eMaxPriority } 83 | , m_pEnableConVar{ std::make_unique( this, psvName, eMaxPriority ) } 84 | { 85 | } 86 | 87 | LogScope::~LogScope() 88 | { 89 | } 90 | 91 | bool LogScope::Enabled( LogPriority ePriority ) const 92 | { 93 | return ePriority <= m_eMaxPriority; 94 | } 95 | 96 | void LogScope::vlogf(enum LogPriority priority, const char *fmt, va_list args) 97 | { 98 | if ( !Enabled( priority ) ) 99 | return; 100 | 101 | char *buf = nullptr; 102 | vasprintf(&buf, fmt, args); 103 | if (!buf) 104 | return; 105 | defer( free(buf); ); 106 | 107 | std::string_view svBuf = buf; 108 | log(priority, svBuf); 109 | } 110 | 111 | void LogScope::log(enum LogPriority priority, std::string_view psvText) 112 | { 113 | if ( !Enabled( priority ) ) 114 | return; 115 | 116 | for (auto& listener : m_LoggingListeners) 117 | listener.second( priority, m_psvPrefix, psvText ); 118 | 119 | std::string_view psvLogName = GetLogPriorityText( priority ); 120 | if ( bPrefixEnabled ) 121 | fprintf(stderr, "[%s] %.*s \e[0;37m%.*s:\e[0m %.*s\n", 122 | gamescope::Process::GetProcessName(), 123 | (int)psvLogName.size(), psvLogName.data(), 124 | (int)this->m_psvPrefix.size(), this->m_psvPrefix.data(), 125 | (int)psvText.size(), psvText.data()); 126 | else 127 | fprintf(stderr, "%.*s\n", (int)psvText.size(), psvText.data()); 128 | } 129 | 130 | void LogScope::logf(enum LogPriority priority, const char *fmt, ...) { 131 | va_list args; 132 | va_start(args, fmt); 133 | this->vlogf(priority, fmt, args); 134 | va_end(args); 135 | } 136 | 137 | void LogScope::warnf(const char *fmt, ...) { 138 | va_list args; 139 | va_start(args, fmt); 140 | this->vlogf(LOG_WARNING, fmt, args); 141 | va_end(args); 142 | } 143 | 144 | void LogScope::errorf(const char *fmt, ...) { 145 | va_list args; 146 | va_start(args, fmt); 147 | this->vlogf(LOG_ERROR, fmt, args); 148 | va_end(args); 149 | } 150 | 151 | void LogScope::infof(const char *fmt, ...) { 152 | va_list args; 153 | va_start(args, fmt); 154 | this->vlogf(LOG_INFO, fmt, args); 155 | va_end(args); 156 | } 157 | 158 | void LogScope::debugf(const char *fmt, ...) { 159 | va_list args; 160 | va_start(args, fmt); 161 | this->vlogf(LOG_DEBUG, fmt, args); 162 | va_end(args); 163 | } 164 | 165 | void LogScope::errorf_errno(const char *fmt, ...) { 166 | const char *err = strerror(errno); 167 | 168 | static char buf[1024]; 169 | va_list args; 170 | va_start(args, fmt); 171 | vsnprintf(buf, sizeof(buf), fmt, args); 172 | va_end(args); 173 | 174 | this->logf(LOG_ERROR, "%s: %s", buf, err); 175 | } 176 | -------------------------------------------------------------------------------- /src/log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef __GNUC__ 11 | #define ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) 12 | #else 13 | #define ATTRIB_PRINTF(start, end) 14 | #endif 15 | 16 | enum LogPriority 17 | { 18 | LOG_SILENT, 19 | LOG_ERROR, 20 | LOG_WARNING, 21 | LOG_INFO, 22 | LOG_DEBUG, 23 | }; 24 | 25 | struct LogConVar_t; 26 | 27 | class LogScope 28 | { 29 | public: 30 | LogScope( std::string_view psvName, LogPriority eMaxPriority = LOG_INFO ); 31 | LogScope( std::string_view psvName, std::string_view psvPrefix, LogPriority eMaxPriority = LOG_INFO ); 32 | ~LogScope(); 33 | 34 | bool Enabled( LogPriority ePriority ) const; 35 | void SetPriority( LogPriority ePriority ) { m_eMaxPriority = ePriority; } 36 | 37 | void vlogf(enum LogPriority priority, const char *fmt, va_list args) ATTRIB_PRINTF(3, 0); 38 | void log(enum LogPriority priority, std::string_view psvText); 39 | 40 | void warnf(const char *fmt, ...) ATTRIB_PRINTF(2, 3); 41 | void errorf(const char *fmt, ...) ATTRIB_PRINTF(2, 3); 42 | void infof(const char *fmt, ...) ATTRIB_PRINTF(2, 3); 43 | void debugf(const char *fmt, ...) ATTRIB_PRINTF(2, 3); 44 | 45 | void errorf_errno(const char *fmt, ...) ATTRIB_PRINTF(2, 3); 46 | 47 | bool bPrefixEnabled = true; 48 | 49 | using LoggingListenerFunc = std::function; 50 | std::unordered_map m_LoggingListeners; 51 | 52 | private: 53 | void vprintf(enum LogPriority priority, const char *fmt, va_list args) ATTRIB_PRINTF(3, 0); 54 | void logf(enum LogPriority priority, const char *fmt, ...) ATTRIB_PRINTF(3, 4); 55 | 56 | std::string_view m_psvName; 57 | std::string_view m_psvPrefix; 58 | 59 | LogPriority m_eMaxPriority = LOG_INFO; 60 | 61 | std::unique_ptr m_pEnableConVar; 62 | }; 63 | -------------------------------------------------------------------------------- /src/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | extern const char *gamescope_optstring; 8 | extern const struct option *gamescope_options; 9 | 10 | extern std::atomic< bool > g_bRun; 11 | 12 | extern int g_nNestedWidth; 13 | extern int g_nNestedHeight; 14 | extern int g_nNestedRefresh; // mHz 15 | extern int g_nNestedUnfocusedRefresh; // mHz 16 | extern int g_nNestedDisplayIndex; 17 | 18 | extern uint32_t g_nOutputWidth; 19 | extern uint32_t g_nOutputHeight; 20 | extern bool g_bForceRelativeMouse; 21 | extern int g_nOutputRefresh; // mHz 22 | extern bool g_bOutputHDREnabled; 23 | extern bool g_bForceInternal; 24 | 25 | extern bool g_bFullscreen; 26 | 27 | extern bool g_bGrabbed; 28 | 29 | extern float g_mouseSensitivity; 30 | extern const char *g_sOutputName; 31 | 32 | enum class GamescopeUpscaleFilter : uint32_t 33 | { 34 | LINEAR = 0, 35 | NEAREST, 36 | FSR, 37 | NIS, 38 | PIXEL, 39 | 40 | FROM_VIEW = 0xF, // internal 41 | }; 42 | 43 | static constexpr bool DoesHardwareSupportUpscaleFilter( GamescopeUpscaleFilter eFilter ) 44 | { 45 | // Could do nearest someday... AMDGPU DC supports custom tap placement to an extent. 46 | 47 | return eFilter == GamescopeUpscaleFilter::LINEAR; 48 | } 49 | 50 | enum class GamescopeUpscaleScaler : uint32_t 51 | { 52 | AUTO, 53 | INTEGER, 54 | FIT, 55 | FILL, 56 | STRETCH, 57 | }; 58 | 59 | extern GamescopeUpscaleFilter g_upscaleFilter; 60 | extern GamescopeUpscaleScaler g_upscaleScaler; 61 | extern GamescopeUpscaleFilter g_wantedUpscaleFilter; 62 | extern GamescopeUpscaleScaler g_wantedUpscaleScaler; 63 | extern int g_upscaleFilterSharpness; 64 | 65 | extern bool g_bBorderlessOutputWindow; 66 | 67 | extern bool g_bExposeWayland; 68 | 69 | extern bool g_bRt; 70 | 71 | extern int g_nXWaylandCount; 72 | 73 | extern uint32_t g_preferVendorID; 74 | extern uint32_t g_preferDeviceID; 75 | 76 | -------------------------------------------------------------------------------- /src/mangoapp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "steamcompmgr.hpp" 7 | #include "refresh_rate.h" 8 | #include "main.hpp" 9 | 10 | static bool inited = false; 11 | static int msgid = 0; 12 | extern bool g_bAppWantsHDRCached; 13 | extern uint32_t g_focusedBaseAppId; 14 | 15 | struct mangoapp_msg_header { 16 | long msg_type; // Message queue ID, never change 17 | uint32_t version; // for major changes in the way things work // 18 | } __attribute__((packed)); 19 | 20 | struct mangoapp_msg_v1 { 21 | struct mangoapp_msg_header hdr; 22 | 23 | uint32_t pid; 24 | uint64_t app_frametime_ns; 25 | uint8_t fsrUpscale; 26 | uint8_t fsrSharpness; 27 | uint64_t visible_frametime_ns; 28 | uint64_t latency_ns; 29 | uint32_t outputWidth; 30 | uint32_t outputHeight; 31 | uint16_t displayRefresh; 32 | bool bAppWantsHDR : 1; 33 | bool bSteamFocused : 1; 34 | char engineName[40]; 35 | 36 | // WARNING: Always ADD fields, never remove or repurpose fields 37 | } __attribute__((packed)) mangoapp_msg_v1; 38 | 39 | void init_mangoapp(){ 40 | int key = ftok("mangoapp", 65); 41 | msgid = msgget(key, 0666 | IPC_CREAT); 42 | mangoapp_msg_v1.hdr.msg_type = 1; 43 | mangoapp_msg_v1.hdr.version = 1; 44 | mangoapp_msg_v1.fsrUpscale = 0; 45 | mangoapp_msg_v1.fsrSharpness = 0; 46 | inited = true; 47 | } 48 | 49 | void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns ) { 50 | if (!inited) 51 | init_mangoapp(); 52 | 53 | mangoapp_msg_v1.visible_frametime_ns = visible_frametime; 54 | mangoapp_msg_v1.fsrUpscale = g_bFSRActive; 55 | mangoapp_msg_v1.fsrSharpness = g_upscaleFilterSharpness; 56 | mangoapp_msg_v1.app_frametime_ns = app_frametime_ns; 57 | mangoapp_msg_v1.latency_ns = latency_ns; 58 | mangoapp_msg_v1.pid = focusWindow_pid; 59 | mangoapp_msg_v1.outputWidth = g_nOutputWidth; 60 | mangoapp_msg_v1.outputHeight = g_nOutputHeight; 61 | mangoapp_msg_v1.displayRefresh = (uint16_t) gamescope::ConvertmHzToHz( g_nOutputRefresh ); 62 | mangoapp_msg_v1.bAppWantsHDR = g_bAppWantsHDRCached; 63 | mangoapp_msg_v1.bSteamFocused = g_focusedBaseAppId == 769; 64 | memset(mangoapp_msg_v1.engineName, 0, sizeof(mangoapp_msg_v1.engineName)); 65 | if (focusWindow_engine) 66 | focusWindow_engine->copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char)); 67 | else 68 | std::string("gamescope").copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char)); 69 | msgsnd(msgid, &mangoapp_msg_v1, sizeof(mangoapp_msg_v1) - sizeof(mangoapp_msg_v1.hdr.msg_type), IPC_NOWAIT); 70 | } 71 | 72 | extern uint64_t g_uCurrentBasePlaneCommitID; 73 | extern bool g_bCurrentBasePlaneIsFifo; 74 | void mangoapp_output_update( uint64_t vblanktime ) 75 | { 76 | if ( !g_bCurrentBasePlaneIsFifo ) 77 | { 78 | return; 79 | } 80 | 81 | static uint64_t s_uLastBasePlaneCommitID = 0; 82 | if ( s_uLastBasePlaneCommitID != g_uCurrentBasePlaneCommitID ) 83 | { 84 | static uint64_t s_uLastBasePlaneUpdateVBlankTime = vblanktime; 85 | uint64_t last_frametime = s_uLastBasePlaneUpdateVBlankTime; 86 | uint64_t frametime = vblanktime - last_frametime; 87 | s_uLastBasePlaneUpdateVBlankTime = vblanktime; 88 | s_uLastBasePlaneCommitID = g_uCurrentBasePlaneCommitID; 89 | if ( last_frametime > vblanktime ) 90 | return; 91 | mangoapp_update( frametime, uint64_t(~0ull), uint64_t(~0ull) ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/modegen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "gamescope_shared.h" 9 | 10 | void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, 11 | float vrefresh, bool reduced, bool interlaced); 12 | void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, 13 | int vrefresh ); 14 | -------------------------------------------------------------------------------- /src/pipewire.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "rendervulkan.hpp" 8 | #include "pipewire_gamescope.hpp" 9 | 10 | struct pipewire_state { 11 | struct pw_loop *loop; 12 | struct pw_context *context; 13 | struct pw_core *core; 14 | bool running; 15 | 16 | struct pw_stream *stream; 17 | uint32_t stream_node_id; 18 | std::atomic streaming; 19 | struct spa_video_info_raw video_info; 20 | struct spa_gamescope gamescope_info; 21 | uint64_t focus_appid; 22 | bool dmabuf; 23 | int shm_stride; 24 | uint64_t seq; 25 | }; 26 | 27 | /** 28 | * PipeWire buffers are allocated by the PipeWire thread, and are temporarily 29 | * shared with the steamcompmgr thread (via dequeue_pipewire_buffer and 30 | * push_pipewire_buffer) for copying. 31 | */ 32 | struct pipewire_buffer { 33 | enum spa_data_type type; // SPA_DATA_MemFd or SPA_DATA_DmaBuf 34 | struct spa_video_info_raw video_info; 35 | struct spa_gamescope gamescope_info; 36 | gamescope::OwningRc texture; 37 | 38 | // Only used for SPA_DATA_MemFd 39 | struct { 40 | int stride; 41 | uint8_t *data; 42 | int fd; 43 | } shm; 44 | 45 | // The following fields are not thread-safe 46 | 47 | // The PipeWire buffer, or nullptr if it's been destroyed. 48 | std::atomic buffer; 49 | bool IsStale() const 50 | { 51 | return buffer == nullptr; 52 | } 53 | // We pass the buffer to the steamcompmgr thread for copying. This is set 54 | // to true if the buffer is currently owned by the steamcompmgr thread. 55 | bool copying; 56 | }; 57 | 58 | bool init_pipewire(void); 59 | uint32_t get_pipewire_stream_node_id(void); 60 | struct pipewire_buffer *dequeue_pipewire_buffer(void); 61 | bool pipewire_is_streaming(); 62 | void pipewire_destroy_buffer(struct pipewire_buffer *buffer); 63 | void push_pipewire_buffer(struct pipewire_buffer *buffer); 64 | void nudge_pipewire(void); 65 | -------------------------------------------------------------------------------- /src/pipewire_gamescope.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | enum { 7 | SPA_FORMAT_VIDEO_requested_size = 0x70000, 8 | SPA_FORMAT_VIDEO_gamescope_focus_appid = 0x70001, 9 | }; 10 | 11 | enum { 12 | SPA_META_requested_size_scale = 0x70000 13 | }; 14 | 15 | struct spa_gamescope 16 | { 17 | spa_rectangle requested_size; 18 | uint64_t focus_appid; 19 | }; 20 | 21 | static inline int 22 | spa_format_video_raw_parse_with_gamescope(const struct spa_pod *format, struct spa_video_info_raw *info, spa_gamescope *gamescope_info) 23 | { 24 | return spa_pod_parse_object(format, 25 | SPA_TYPE_OBJECT_Format, NULL, 26 | SPA_FORMAT_VIDEO_format, SPA_POD_Id(&info->format), 27 | SPA_FORMAT_VIDEO_modifier, SPA_POD_OPT_Long(&info->modifier), 28 | SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), 29 | SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), 30 | SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate), 31 | SPA_FORMAT_VIDEO_views, SPA_POD_OPT_Int(&info->views), 32 | SPA_FORMAT_VIDEO_interlaceMode, SPA_POD_OPT_Id(&info->interlace_mode), 33 | SPA_FORMAT_VIDEO_pixelAspectRatio, SPA_POD_OPT_Fraction(&info->pixel_aspect_ratio), 34 | SPA_FORMAT_VIDEO_multiviewMode, SPA_POD_OPT_Id(&info->multiview_mode), 35 | SPA_FORMAT_VIDEO_multiviewFlags, SPA_POD_OPT_Id(&info->multiview_flags), 36 | SPA_FORMAT_VIDEO_chromaSite, SPA_POD_OPT_Id(&info->chroma_site), 37 | SPA_FORMAT_VIDEO_colorRange, SPA_POD_OPT_Id(&info->color_range), 38 | SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_OPT_Id(&info->color_matrix), 39 | SPA_FORMAT_VIDEO_transferFunction, SPA_POD_OPT_Id(&info->transfer_function), 40 | SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_OPT_Id(&info->color_primaries), 41 | SPA_FORMAT_VIDEO_requested_size, SPA_POD_OPT_Rectangle(&gamescope_info->requested_size), 42 | SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_OPT_Long(&gamescope_info->focus_appid)); 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/refresh_rate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gamescope 6 | { 7 | constexpr int32_t ConvertHztomHz( int32_t nRefreshHz ) 8 | { 9 | return nRefreshHz * 1'000; 10 | } 11 | 12 | constexpr int32_t ConvertmHzToHz( int32_t nRefreshmHz ) 13 | { 14 | // Round to nearest when going to mHz. 15 | // Ceil seems to be wrong when we have 60.001 or 90.004 etc. 16 | // Floor seems to be bad if we have 143.99 17 | // So round to nearest. 18 | 19 | return ( nRefreshmHz + 499 ) / 1'000; 20 | } 21 | 22 | constexpr uint32_t ConvertHztomHz( uint32_t nRefreshHz ) 23 | { 24 | return nRefreshHz * 1'000; 25 | } 26 | 27 | constexpr uint32_t ConvertmHzToHz( uint32_t nRefreshmHz ) 28 | { 29 | return ( nRefreshmHz + 499 ) / 1'000; 30 | } 31 | 32 | constexpr float ConvertHztomHz( float flRefreshHz ) 33 | { 34 | return flRefreshHz * 1000.0f; 35 | } 36 | 37 | constexpr float ConvertmHzToHz( float nRefreshmHz ) 38 | { 39 | return ( nRefreshmHz ) / 1'000.0; 40 | } 41 | 42 | constexpr uint32_t RefreshCycleTomHz( int32_t nCycle ) 43 | { 44 | // Round cycle to nearest. 45 | return ( 1'000'000'000'000ul + ( nCycle / 2 ) - 1 ) / nCycle; 46 | } 47 | 48 | constexpr uint32_t mHzToRefreshCycle( int32_t nmHz ) 49 | { 50 | // Same thing. 51 | return RefreshCycleTomHz( nmHz ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/reshade_effect_manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rendervulkan.hpp" 4 | #include 5 | 6 | namespace reshadefx 7 | { 8 | struct module; 9 | } 10 | 11 | class ReshadeUniform; 12 | 13 | struct ReshadeCombinedImageSampler 14 | { 15 | VkSampler sampler; 16 | gamescope::Rc texture; 17 | }; 18 | 19 | struct ReshadeEffectKey 20 | { 21 | std::string path; 22 | 23 | uint32_t bufferWidth; 24 | uint32_t bufferHeight; 25 | GamescopeAppTextureColorspace bufferColorSpace; 26 | VkFormat bufferFormat; 27 | 28 | uint32_t techniqueIdx; 29 | 30 | bool operator==(const ReshadeEffectKey& other) const = default; 31 | bool operator!=(const ReshadeEffectKey& other) const = default; 32 | }; 33 | 34 | enum ReshadeDescriptorSets 35 | { 36 | GAMESCOPE_RESHADE_DESCRIPTOR_SET_UBO = 0, 37 | GAMESCOPE_RESHADE_DESCRIPTOR_SET_SAMPLED_IMAGES, 38 | GAMESCOPE_RESHADE_DESCRIPTOR_SET_STORAGE_IMAGES, 39 | 40 | GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT, 41 | }; 42 | 43 | namespace ReshadeEffectFlag 44 | { 45 | static constexpr uint32_t AlwaysScanout = 1u << 0; 46 | } 47 | using ReshadeEffectFlags = uint32_t; 48 | 49 | class ReshadeEffectPipeline 50 | { 51 | public: 52 | ReshadeEffectPipeline(); 53 | ~ReshadeEffectPipeline(); 54 | 55 | bool init(CVulkanDevice *device, const ReshadeEffectKey &key); 56 | void update(); 57 | uint64_t execute(gamescope::Rc inImage, gamescope::Rc *outImage); 58 | 59 | const ReshadeEffectKey& key() const { return m_key; } 60 | reshadefx::module *module() { return m_module.get(); } 61 | 62 | ReshadeEffectFlags flags() const { return m_flags; } 63 | 64 | gamescope::Rc findTexture(std::string_view name); 65 | 66 | private: 67 | ReshadeEffectKey m_key; 68 | CVulkanDevice *m_device; 69 | 70 | std::unique_ptr m_module; 71 | std::vector m_pipelines; 72 | std::vector> m_textures; 73 | gamescope::OwningRc m_rt; 74 | std::vector m_samplers; 75 | std::vector> m_uniforms; 76 | 77 | std::optional m_cmdBuffer = std::nullopt; 78 | VkBuffer m_buffer = VK_NULL_HANDLE; 79 | VkDeviceMemory m_bufferMemory = VK_NULL_HANDLE; 80 | void* m_mappedPtr = nullptr; 81 | 82 | VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; 83 | VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; 84 | 85 | VkDescriptorSetLayout m_descriptorSetLayouts[GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT] = {}; 86 | VkDescriptorSet m_descriptorSets[GAMESCOPE_RESHADE_DESCRIPTOR_SET_COUNT] = {}; 87 | 88 | ReshadeEffectFlags m_flags = 0; 89 | }; 90 | 91 | class ReshadeEffectManager 92 | { 93 | public: 94 | ReshadeEffectManager(); 95 | 96 | void init(CVulkanDevice *device); 97 | void clear(); 98 | ReshadeEffectPipeline* pipeline(const ReshadeEffectKey &key); 99 | 100 | private: 101 | ReshadeEffectKey m_lastKey{}; 102 | std::unique_ptr m_lastPipeline; 103 | CVulkanDevice *m_device; 104 | }; 105 | 106 | extern ReshadeEffectManager g_reshadeManager; 107 | 108 | 109 | void reshade_effect_manager_set_uniform_variable(const char *key, uint8_t* value); 110 | void reshade_effect_manager_set_effect(const char *path, std::function callback); 111 | void reshade_effect_manager_enable_effect(); 112 | void reshade_effect_manager_disable_effect(); -------------------------------------------------------------------------------- /src/shaders/NVIDIAImageScaling/NIS/NIS_Main.glsl: -------------------------------------------------------------------------------- 1 | // The MIT License(MIT) 2 | // 3 | // Copyright(c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files(the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions : 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | //--------------------------------------------------------------------------------- 23 | // NVIDIA Image Scaling SDK - v1.0.3 24 | //--------------------------------------------------------------------------------- 25 | // GLSL main example 26 | //--------------------------------------------------------------------------------- 27 | 28 | #version 450 29 | #extension GL_ARB_separate_shader_objects : enable 30 | #extension GL_ARB_shading_language_420pack : enable 31 | #extension GL_GOOGLE_include_directive : enable 32 | #extension GL_EXT_shader_16bit_storage : require 33 | #extension GL_EXT_shader_explicit_arithmetic_types : require 34 | 35 | #define NIS_GLSL 1 36 | 37 | #ifndef NIS_SCALER 38 | #define NIS_SCALER 1 39 | #endif 40 | 41 | layout(set=0,binding=0) uniform const_buffer 42 | { 43 | float kDetectRatio; 44 | float kDetectThres; 45 | float kMinContrastRatio; 46 | float kRatioNorm; 47 | 48 | float kContrastBoost; 49 | float kEps; 50 | float kSharpStartY; 51 | float kSharpScaleY; 52 | 53 | float kSharpStrengthMin; 54 | float kSharpStrengthScale; 55 | float kSharpLimitMin; 56 | float kSharpLimitScale; 57 | 58 | float kScaleX; 59 | float kScaleY; 60 | 61 | float kDstNormX; 62 | float kDstNormY; 63 | float kSrcNormX; 64 | float kSrcNormY; 65 | 66 | uint kInputViewportOriginX; 67 | uint kInputViewportOriginY; 68 | uint kInputViewportWidth; 69 | uint kInputViewportHeight; 70 | 71 | uint kOutputViewportOriginX; 72 | uint kOutputViewportOriginY; 73 | uint kOutputViewportWidth; 74 | uint kOutputViewportHeight; 75 | 76 | float reserved0; 77 | float reserved1; 78 | }; 79 | 80 | layout(set=0,binding=1) uniform sampler samplerLinearClamp; 81 | layout(set=0,binding=2) uniform texture2D in_texture; 82 | layout(set=0,binding=3) uniform writeonly image2D out_texture; 83 | 84 | #if NIS_SCALER 85 | layout(set=0,binding=4) uniform texture2D coef_scaler; 86 | layout(set=0,binding=5) uniform texture2D coef_usm; 87 | #endif 88 | 89 | #include "NIS_Scaler.h" 90 | 91 | layout(local_size_x=NIS_THREAD_GROUP_SIZE) in; 92 | void main() 93 | { 94 | #if NIS_SCALER 95 | NVScaler(gl_WorkGroupID.xy, gl_LocalInvocationID.x); 96 | #else 97 | NVSharpen(gl_WorkGroupID.xy, gl_LocalInvocationID.x); 98 | #endif 99 | } -------------------------------------------------------------------------------- /src/shaders/NVIDIAImageScaling/NIS/NIS_Main.hlsl: -------------------------------------------------------------------------------- 1 | // The MIT License(MIT) 2 | // 3 | // Copyright(c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files(the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions : 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | //--------------------------------------------------------------------------------- 23 | // NVIDIA Image Scaling SDK - v1.0.3 24 | //--------------------------------------------------------------------------------- 25 | // HLSL main example 26 | //--------------------------------------------------------------------------------- 27 | 28 | #define NIS_HLSL 1 29 | 30 | #ifndef NIS_SCALER 31 | #define NIS_SCALER 1 32 | #endif 33 | 34 | #ifndef NIS_DXC 35 | #define NIS_DXC 0 36 | #endif 37 | 38 | #if NIS_DXC 39 | #define NIS_PUSH_CONSTANT [[vk::push_constant]] 40 | #define NIS_BINDING(bindingIndex) [[vk::binding(bindingIndex, 0)]] 41 | #else 42 | #define NIS_PUSH_CONSTANT 43 | #define NIS_BINDING(bindingIndex) 44 | #endif 45 | 46 | 47 | NIS_BINDING(0) cbuffer cb : register(b0) 48 | { 49 | float kDetectRatio; 50 | float kDetectThres; 51 | float kMinContrastRatio; 52 | float kRatioNorm; 53 | 54 | float kContrastBoost; 55 | float kEps; 56 | float kSharpStartY; 57 | float kSharpScaleY; 58 | 59 | float kSharpStrengthMin; 60 | float kSharpStrengthScale; 61 | float kSharpLimitMin; 62 | float kSharpLimitScale; 63 | 64 | float kScaleX; 65 | float kScaleY; 66 | 67 | float kDstNormX; 68 | float kDstNormY; 69 | float kSrcNormX; 70 | float kSrcNormY; 71 | 72 | uint kInputViewportOriginX; 73 | uint kInputViewportOriginY; 74 | uint kInputViewportWidth; 75 | uint kInputViewportHeight; 76 | 77 | uint kOutputViewportOriginX; 78 | uint kOutputViewportOriginY; 79 | uint kOutputViewportWidth; 80 | uint kOutputViewportHeight; 81 | 82 | float reserved0; 83 | float reserved1; 84 | }; 85 | 86 | NIS_BINDING(1) SamplerState samplerLinearClamp : register(s0); 87 | #if NIS_NV12_SUPPORT 88 | NIS_BINDING(2) Texture2D in_texture_y : register(t0); 89 | NIS_BINDING(2) Texture2D in_texture_uv : register(t3); 90 | #else 91 | NIS_BINDING(2) Texture2D in_texture : register(t0); 92 | #endif 93 | NIS_BINDING(3) RWTexture2D out_texture : register(u0); 94 | #if NIS_SCALER 95 | NIS_BINDING(4) Texture2D coef_scaler : register(t1); 96 | NIS_BINDING(5) Texture2D coef_usm : register(t2); 97 | #endif 98 | 99 | 100 | 101 | #include "NIS_Scaler.h" 102 | 103 | [numthreads(NIS_THREAD_GROUP_SIZE, 1, 1)] 104 | void main(uint3 blockIdx : SV_GroupID, uint3 threadIdx : SV_GroupThreadID) 105 | { 106 | #if NIS_SCALER 107 | NVScaler(blockIdx.xy, threadIdx.x); 108 | #else 109 | NVSharpen(blockIdx.xy, threadIdx.x); 110 | #endif 111 | } 112 | -------------------------------------------------------------------------------- /src/shaders/NVIDIAImageScaling/licence.txt: -------------------------------------------------------------------------------- 1 | // The MIT License(MIT) 2 | // 3 | // Copyright(c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files(the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions : 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/shaders/blit_push_data.h: -------------------------------------------------------------------------------- 1 | layout(binding = 0, scalar) 2 | uniform layers_t { 3 | vec2 u_scale[VKR_MAX_LAYERS]; 4 | vec2 u_offset[VKR_MAX_LAYERS]; 5 | float u_opacity[VKR_MAX_LAYERS]; 6 | mat3x4 u_ctm[VKR_MAX_LAYERS]; 7 | uint u_borderMask; 8 | uint u_frameId; 9 | uint u_blur_radius; 10 | 11 | uint u_shaderFilter; 12 | 13 | // hdr 14 | float u_linearToNits; // sdr -> hdr 15 | float u_nitsToLinear; // hdr -> sdr 16 | float u_itmSdrNits; 17 | float u_itmTargetNits; 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /src/shaders/cs_composite_blit.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_scalar_block_layout : require 5 | 6 | #include "descriptor_set.h" 7 | 8 | layout( 9 | local_size_x = 8, 10 | local_size_y = 8, 11 | local_size_z = 1) in; 12 | 13 | #include "blit_push_data.h" 14 | #include "composite.h" 15 | 16 | vec4 sampleLayer(uint layerIdx, vec2 uv) { 17 | if ((c_ycbcrMask & (1 << layerIdx)) != 0) 18 | return sampleLayer(s_ycbcr_samplers[layerIdx], layerIdx, uv, false); 19 | return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true); 20 | } 21 | 22 | void main() { 23 | uvec2 coord = uvec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y); 24 | uvec2 outSize = imageSize(dst); 25 | 26 | if (coord.x >= outSize.x || coord.y >= outSize.y) 27 | return; 28 | 29 | vec2 uv = vec2(coord); 30 | vec4 outputValue = vec4(0.0f); 31 | 32 | if (checkDebugFlag(compositedebug_PlaneBorders)) 33 | outputValue = vec4(1.0f, 0.0f, 0.0f, 0.0f); 34 | 35 | if (c_layerCount > 0) { 36 | outputValue = sampleLayer(0, uv) * u_opacity[0]; 37 | } 38 | 39 | for (int i = 1; i < c_layerCount; i++) { 40 | vec4 layerColor = sampleLayer(i, uv); 41 | // wl_surfaces come with premultiplied alpha, so that's them being 42 | // premultiplied by layerColor.a. 43 | // We need to then multiply that by the layer's opacity to get to our 44 | // final premultiplied state. 45 | // For the other side of things, we need to multiply by (1.0f - (layerColor.a * opacity)) 46 | float opacity = u_opacity[i]; 47 | float layerAlpha = opacity * layerColor.a; 48 | outputValue = layerColor * opacity + outputValue * (1.0f - layerAlpha); 49 | } 50 | 51 | outputValue.rgb = encodeOutputColor(outputValue.rgb); 52 | imageStore(dst, ivec2(coord), outputValue); 53 | 54 | // Indicator to quickly tell if we're in the compositing path or not. 55 | if (checkDebugFlag(compositedebug_Markers)) 56 | compositing_debug(coord); 57 | } 58 | -------------------------------------------------------------------------------- /src/shaders/cs_composite_blur.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_scalar_block_layout : require 5 | 6 | #include "descriptor_set.h" 7 | 8 | layout( 9 | local_size_x = 8, 10 | local_size_y = 8, 11 | local_size_z = 1) in; 12 | 13 | #include "blit_push_data.h" 14 | 15 | #define BLUR_DONT_SCALE 1 16 | #include "composite.h" 17 | #include "blur.h" 18 | 19 | vec4 sampleLayer(uint layerIdx, vec2 uv) { 20 | if ((c_ycbcrMask & (1 << layerIdx)) != 0) 21 | return sampleLayer(s_ycbcr_samplers[layerIdx], layerIdx, uv, false); 22 | return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true); 23 | } 24 | 25 | void main() { 26 | uvec2 coord = uvec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y); 27 | uvec2 outSize = imageSize(dst); 28 | 29 | if (coord.x >= outSize.x || coord.y >= outSize.y) 30 | return; 31 | 32 | vec2 uv = vec2(coord); 33 | vec3 outputValue = vec3(0.0f); 34 | 35 | if (checkDebugFlag(compositedebug_PlaneBorders)) 36 | outputValue = vec3(1.0f, 0.0f, 0.0f); 37 | 38 | if (c_layerCount > 0) 39 | outputValue = gaussian_blur(s_samplers[VKR_BLUR_EXTRA_SLOT], 0, vec2(coord), u_blur_radius, true, true).rgb; 40 | 41 | for (int i = c_blur_layer_count; i < c_layerCount; i++) { 42 | vec4 layerColor = sampleLayer(i, uv); 43 | float opacity = u_opacity[i]; 44 | float layerAlpha = opacity * layerColor.a; 45 | outputValue = layerColor.rgb * opacity + outputValue * (1.0f - layerAlpha); 46 | } 47 | 48 | outputValue = encodeOutputColor(outputValue); 49 | imageStore(dst, ivec2(coord), vec4(outputValue, 0)); 50 | 51 | if (checkDebugFlag(compositedebug_Markers)) 52 | compositing_debug(coord); 53 | } 54 | -------------------------------------------------------------------------------- /src/shaders/cs_composite_blur_cond.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_scalar_block_layout : require 5 | 6 | #include "descriptor_set.h" 7 | 8 | layout( 9 | local_size_x = 8, 10 | local_size_y = 8, 11 | local_size_z = 1) in; 12 | 13 | #include "blit_push_data.h" 14 | 15 | #define BLUR_DONT_SCALE 1 16 | #include "composite.h" 17 | #include "blur.h" 18 | 19 | vec4 sampleLayer(uint layerIdx, vec2 uv) { 20 | if ((c_ycbcrMask & (1 << layerIdx)) != 0) 21 | return sampleLayer(s_ycbcr_samplers[layerIdx], layerIdx, uv, false); 22 | return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true); 23 | } 24 | 25 | void main() { 26 | uvec2 coord = uvec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y); 27 | uvec2 outSize = imageSize(dst); 28 | 29 | if (coord.x >= outSize.x || coord.y >= outSize.y) 30 | return; 31 | 32 | vec2 uv = vec2(coord); 33 | vec3 outputValue = vec3(0.0f); 34 | 35 | if (checkDebugFlag(compositedebug_PlaneBorders)) 36 | outputValue = vec3(1.0f, 0.0f, 0.0f); 37 | 38 | float finalRevAlpha = 1.0f; 39 | 40 | for (int i = c_blur_layer_count; i < c_layerCount; i++) { 41 | vec4 layerColor = sampleLayer(i, uv); 42 | float opacity = u_opacity[i]; 43 | float layerAlpha = opacity * layerColor.a; 44 | float revAlpha = (1.0f - layerAlpha); 45 | outputValue = layerColor.rgb * opacity + outputValue * revAlpha; 46 | finalRevAlpha *= revAlpha; 47 | } 48 | 49 | if (c_layerCount > 0) { 50 | if (finalRevAlpha < 0.95) { 51 | outputValue += gaussian_blur(s_samplers[VKR_BLUR_EXTRA_SLOT], 0, vec2(coord), u_blur_radius, true, true).rgb * finalRevAlpha; 52 | } else { 53 | outputValue = sampleLayer(0, uv).rgb * u_opacity[0]; 54 | for (int i = 1; i < c_blur_layer_count; i++) { 55 | vec4 layerColor = sampleLayer(i, uv); 56 | float opacity = u_opacity[i]; 57 | float layerAlpha = opacity * layerColor.a; 58 | outputValue = layerColor.rgb * opacity + outputValue * (1.0f - layerAlpha); 59 | } 60 | } 61 | } 62 | 63 | outputValue = encodeOutputColor(outputValue); 64 | imageStore(dst, ivec2(coord), vec4(outputValue, 0)); 65 | 66 | if (checkDebugFlag(compositedebug_Markers)) 67 | compositing_debug(coord); 68 | } 69 | -------------------------------------------------------------------------------- /src/shaders/cs_composite_rcas.comp: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_scalar_block_layout : require 5 | 6 | #include "descriptor_set.h" 7 | 8 | layout( 9 | local_size_x = 64, 10 | local_size_y = 1, 11 | local_size_z = 1) in; 12 | 13 | layout(binding = 0, scalar) 14 | uniform layers_t { 15 | uvec2 u_layer0Offset; 16 | vec2 u_scale[VKR_MAX_LAYERS - 1]; 17 | vec2 u_offset[VKR_MAX_LAYERS - 1]; 18 | float u_opacity[VKR_MAX_LAYERS]; 19 | mat3x4 u_ctm[VKR_MAX_LAYERS]; 20 | uint u_borderMask; 21 | uint u_frameId; 22 | uint u_c1; 23 | 24 | uint u_shaderFilter; 25 | 26 | // hdr 27 | float u_linearToNits; 28 | float u_nitsToLinear; 29 | float u_itmSdrNits; 30 | float u_itmTargetNits; 31 | }; 32 | 33 | #include "composite.h" 34 | 35 | #define A_GPU 1 36 | #define A_GLSL 1 37 | #include "ffx_a.h" 38 | #define FSR_RCAS_F 1 39 | vec4 FsrRcasLoadF(ivec2 p) { return texelFetch(s_samplers[0], ivec2(p), 0); } 40 | // our input is already srgb 41 | void FsrRcasInputF(inout float r, inout float g, inout float b) {} 42 | #include "ffx_fsr1.h" 43 | 44 | vec4 sampleLayer(uint layerIdx, vec2 uv) { 45 | if ((c_ycbcrMask & (1 << layerIdx)) != 0) 46 | return sampleLayerEx(s_ycbcr_samplers[layerIdx], layerIdx - 1, layerIdx, uv, false); 47 | return sampleLayerEx(s_samplers[layerIdx], layerIdx - 1, layerIdx, uv, true); 48 | } 49 | 50 | 51 | void rcasComposite(uvec2 pos) 52 | { 53 | vec3 outputValue = vec3(0.0f); 54 | 55 | if (checkDebugFlag(compositedebug_PlaneBorders)) 56 | outputValue = vec3(1.0f, 0.0f, 0.0f); 57 | 58 | if (c_layerCount > 0) { 59 | // this is actually signed, underflow will be filtered out by the branch below 60 | uvec2 rcasPos = pos + u_layer0Offset; 61 | uvec2 layer0Extent = uvec2(textureSize(s_samplers[0], 0)); 62 | 63 | if (all(lessThan(rcasPos, layer0Extent))) { 64 | FsrRcasF(outputValue.r, outputValue.g, outputValue.b, rcasPos, u_c1.xxxx); 65 | 66 | uint colorspace = get_layer_colorspace(0); 67 | if (colorspace == colorspace_linear) 68 | { 69 | // We don't use an sRGB view for FSR due to the spaces RCAS works in. 70 | colorspace = colorspace_sRGB; 71 | } 72 | 73 | outputValue.rgb = colorspace_plane_degamma_tf(outputValue.rgb, colorspace); 74 | outputValue.rgb = (vec4(outputValue.rgb, 1.0f) * u_ctm[0]).rgb; 75 | outputValue.rgb = apply_layer_color_mgmt(outputValue.rgb, 0, colorspace); 76 | outputValue *= u_opacity[0]; 77 | } 78 | } 79 | 80 | 81 | if (c_layerCount > 1) { 82 | vec2 uv = vec2(pos); 83 | 84 | for (int i = 1; i < c_layerCount; i++) { 85 | vec4 layerColor = sampleLayer(i, uv); 86 | float opacity = u_opacity[i]; 87 | float layerAlpha = opacity * layerColor.a; 88 | outputValue = layerColor.rgb * opacity + outputValue * (1.0f - layerAlpha); 89 | } 90 | } 91 | 92 | outputValue = encodeOutputColor(outputValue); 93 | imageStore(dst, ivec2(pos), vec4(outputValue, 0)); 94 | 95 | if (checkDebugFlag(compositedebug_Markers)) 96 | compositing_debug(pos); 97 | } 98 | 99 | void main() 100 | { 101 | // AMD recommends to use this swizzle and to process 4 pixel per invocation 102 | // for better cache utilisation 103 | uvec2 pos = ARmp8x8(gl_LocalInvocationID.x) + uvec2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); 104 | rcasComposite(pos); 105 | pos.x += 8u; 106 | rcasComposite(pos); 107 | pos.y += 8u; 108 | rcasComposite(pos); 109 | pos.x -= 8u; 110 | rcasComposite(pos); 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/shaders/cs_easu.comp: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_scalar_block_layout : require 5 | 6 | #include "descriptor_set.h" 7 | 8 | layout( 9 | local_size_x = 64, 10 | local_size_y = 1, 11 | local_size_z = 1) in; 12 | 13 | layout(binding = 0, scalar) 14 | uniform layers_t { 15 | uvec4 c1, c2, c3, c4; 16 | }; 17 | 18 | #define A_GPU 1 19 | #define A_GLSL 1 20 | #include "ffx_a.h" 21 | #define FSR_EASU_F 1 22 | AF4 FsrEasuRF(AF2 p){return AF4(textureGather(s_samplers[0], p, 0));} 23 | AF4 FsrEasuGF(AF2 p){return AF4(textureGather(s_samplers[0], p, 1));} 24 | AF4 FsrEasuBF(AF2 p){return AF4(textureGather(s_samplers[0], p, 2));} 25 | #include "ffx_fsr1.h" 26 | 27 | void easuPass(uvec2 pos) 28 | { 29 | vec3 color; 30 | FsrEasuF(color, pos, c1, c2, c3, c4); 31 | imageStore(dst, ivec2(pos), vec4(color, 1)); 32 | } 33 | 34 | void main() 35 | { 36 | // AMD recommends to use this swizzle and to process 4 pixel per invocation 37 | // for better cache utilisation 38 | uvec2 pos = ARmp8x8(gl_LocalInvocationID.x) + uvec2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); 39 | easuPass(pos); 40 | pos.x += 8u; 41 | easuPass(pos); 42 | pos.y += 8u; 43 | easuPass(pos); 44 | pos.x -= 8u; 45 | easuPass(pos); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/shaders/cs_easu_fp16.comp: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_shader_explicit_arithmetic_types_float16 : require 5 | #extension GL_EXT_scalar_block_layout : require 6 | 7 | #include "descriptor_set.h" 8 | 9 | layout( 10 | local_size_x = 64, 11 | local_size_y = 1, 12 | local_size_z = 1) in; 13 | 14 | layout(binding = 0, scalar) 15 | uniform layers_t { 16 | uvec4 c1, c2, c3, c4; 17 | }; 18 | 19 | #define A_GPU 1 20 | #define A_GLSL 1 21 | #define A_HALF 1 22 | #include "ffx_a.h" 23 | #define FSR_EASU_H 1 24 | f16vec4 FsrEasuRH(vec2 p) {return f16vec4(textureGather(s_samplers[0], p, 0));} 25 | f16vec4 FsrEasuGH(vec2 p) {return f16vec4(textureGather(s_samplers[0], p, 1));} 26 | f16vec4 FsrEasuBH(vec2 p) {return f16vec4(textureGather(s_samplers[0], p, 2));} 27 | #include "ffx_fsr1.h" 28 | 29 | void easuPass(uvec2 pos) 30 | { 31 | f16vec3 color; 32 | FsrEasuH(color, pos, c1, c2, c3, c4); 33 | imageStore(dst, ivec2(pos), vec4(color, 1)); 34 | } 35 | 36 | void main() 37 | { 38 | // AMD recommends to use this swizzle and to process 4 pixel per invocation 39 | // for better cache utilisation 40 | uvec2 pos = ARmp8x8(gl_LocalInvocationID.x) + uvec2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); 41 | easuPass(pos); 42 | pos.x += 8u; 43 | easuPass(pos); 44 | pos.y += 8u; 45 | easuPass(pos); 46 | pos.x -= 8u; 47 | easuPass(pos); 48 | } 49 | -------------------------------------------------------------------------------- /src/shaders/cs_gaussian_blur_horizontal.comp: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_scalar_block_layout : require 5 | 6 | #include "descriptor_set.h" 7 | 8 | layout( 9 | local_size_x = 8, 10 | local_size_y = 8, 11 | local_size_z = 1) in; 12 | 13 | layout(binding = 0, scalar) 14 | uniform layers_t { 15 | vec2 u_scale[VKR_MAX_LAYERS]; 16 | vec2 u_offset[VKR_MAX_LAYERS]; 17 | float u_opacity[VKR_MAX_LAYERS]; 18 | mat3x4 u_ctm[VKR_MAX_LAYERS]; 19 | uint u_borderMask; 20 | uint u_frameId; 21 | uint u_blur_radius; 22 | 23 | uint u_shaderFilter; 24 | 25 | // hdr 26 | float u_linearToNits; 27 | float u_nitsToLinear; 28 | float u_itmSdrNits; 29 | float u_itmTargetNits; 30 | }; 31 | 32 | #include "composite.h" 33 | #include "blur.h" 34 | 35 | void main() 36 | { 37 | uvec2 coord = uvec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y); 38 | vec2 pos = coord; 39 | 40 | vec3 outputValue = vec3(0); 41 | 42 | if (c_layerCount > 0) { 43 | if ((c_ycbcrMask & 1) != 0) 44 | outputValue = gaussian_blur(s_ycbcr_samplers[0], 0, pos, u_blur_radius, false, false).rgb * u_opacity[0]; 45 | else 46 | outputValue = gaussian_blur(s_samplers[0], 0, pos, u_blur_radius, false, true).rgb * u_opacity[0]; 47 | } 48 | 49 | for (int i = 1; i < c_layerCount; i++) { 50 | vec4 layerColor; 51 | // YCBCR technically has incorrect blending here but... meh. 52 | if ((c_ycbcrMask & (1 << i)) != 0) 53 | layerColor = gaussian_blur(s_ycbcr_samplers[i], i, pos, u_blur_radius, false, false); 54 | else 55 | layerColor = gaussian_blur(s_samplers[i], i, pos, u_blur_radius, false, true); 56 | 57 | float opacity = u_opacity[i]; 58 | float layerAlpha = opacity * layerColor.a; 59 | outputValue = layerColor.rgb * opacity + outputValue * (1.0f - layerAlpha); 60 | } 61 | 62 | uint colorspace = get_layer_colorspace(0); 63 | outputValue = colorspace_plane_regamma_tf(outputValue, colorspace); 64 | imageStore(dst, ivec2(coord), vec4(outputValue, 0)); 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/shaders/cs_nis.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | #extension GL_EXT_scalar_block_layout : require 5 | 6 | #define NIS_GLSL 1 7 | #define NIS_SCALER 1 8 | 9 | #include "descriptor_set.h" 10 | 11 | layout(binding = 0, scalar) 12 | uniform const_buffer 13 | { 14 | float kDetectRatio; 15 | float kDetectThres; 16 | float kMinContrastRatio; 17 | float kRatioNorm; 18 | 19 | float kContrastBoost; 20 | float kEps; 21 | float kSharpStartY; 22 | float kSharpScaleY; 23 | 24 | float kSharpStrengthMin; 25 | float kSharpStrengthScale; 26 | float kSharpLimitMin; 27 | float kSharpLimitScale; 28 | 29 | float kScaleX; 30 | float kScaleY; 31 | 32 | float kDstNormX; 33 | float kDstNormY; 34 | float kSrcNormX; 35 | float kSrcNormY; 36 | 37 | uint kInputViewportOriginX; 38 | uint kInputViewportOriginY; 39 | uint kInputViewportWidth; 40 | uint kInputViewportHeight; 41 | 42 | uint kOutputViewportOriginX; 43 | uint kOutputViewportOriginY; 44 | uint kOutputViewportWidth; 45 | uint kOutputViewportHeight; 46 | 47 | float reserved0; 48 | float reserved1; 49 | }; 50 | 51 | // These are the names the NIS shader uses to access the data needed to do the upscaling 52 | #define in_texture s_samplers[0] 53 | #define out_texture dst 54 | #define coef_scaler s_samplers[VKR_NIS_COEF_SCALER_SLOT] 55 | #define coef_usm s_samplers[VKR_NIS_COEF_USM_SLOT] 56 | 57 | // Gamescope is using combined image samplers so no need to specify a sampler 58 | #define sampler2D(x, sampler) (x) 59 | 60 | #include "NVIDIAImageScaling/NIS/NIS_Scaler.h" 61 | 62 | layout(local_size_x=NIS_THREAD_GROUP_SIZE, 63 | local_size_y = 1, 64 | local_size_z = 1) in; 65 | void main() 66 | { 67 | NVScaler(gl_WorkGroupID.xy, gl_LocalInvocationID.x); 68 | } -------------------------------------------------------------------------------- /src/shaders/cs_nis_fp16.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | #extension GL_EXT_shader_explicit_arithmetic_types_float16 : require 5 | #extension GL_EXT_scalar_block_layout : require 6 | 7 | #define NIS_GLSL 1 8 | #define NIS_USE_HALF_PRECISION 1 9 | #define NIS_SCALER 1 10 | 11 | #include "descriptor_set.h" 12 | 13 | layout(binding = 0, scalar) 14 | uniform const_buffer 15 | { 16 | float kDetectRatio; 17 | float kDetectThres; 18 | float kMinContrastRatio; 19 | float kRatioNorm; 20 | 21 | float kContrastBoost; 22 | float kEps; 23 | float kSharpStartY; 24 | float kSharpScaleY; 25 | 26 | float kSharpStrengthMin; 27 | float kSharpStrengthScale; 28 | float kSharpLimitMin; 29 | float kSharpLimitScale; 30 | 31 | float kScaleX; 32 | float kScaleY; 33 | 34 | float kDstNormX; 35 | float kDstNormY; 36 | float kSrcNormX; 37 | float kSrcNormY; 38 | 39 | uint kInputViewportOriginX; 40 | uint kInputViewportOriginY; 41 | uint kInputViewportWidth; 42 | uint kInputViewportHeight; 43 | 44 | uint kOutputViewportOriginX; 45 | uint kOutputViewportOriginY; 46 | uint kOutputViewportWidth; 47 | uint kOutputViewportHeight; 48 | 49 | float reserved0; 50 | float reserved1; 51 | }; 52 | 53 | // These are the names the NIS shader uses to access the data needed to do the upscaling 54 | #define in_texture s_samplers[0] 55 | #define out_texture dst 56 | #define coef_scaler s_samplers[VKR_NIS_COEF_SCALER_SLOT] 57 | #define coef_usm s_samplers[VKR_NIS_COEF_USM_SLOT] 58 | 59 | // Gamescope is using combined image samplers so no need to specify a sampler 60 | #define sampler2D(x, sampler) (x) 61 | 62 | #include "NVIDIAImageScaling/NIS/NIS_Scaler.h" 63 | 64 | layout(local_size_x=NIS_THREAD_GROUP_SIZE, 65 | local_size_y = 1, 66 | local_size_z = 1) in; 67 | void main() 68 | { 69 | NVScaler(gl_WorkGroupID.xy, gl_LocalInvocationID.x); 70 | } -------------------------------------------------------------------------------- /src/shaders/cs_rgb_to_nv12.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_scalar_block_layout : require 5 | 6 | #include "descriptor_set.h" 7 | 8 | layout( 9 | local_size_x = 8, 10 | local_size_y = 8, 11 | local_size_z = 1) in; 12 | 13 | // NV12 Format is: 14 | // YYYYYYYYYYYYYYY... 15 | // YYYYYYYYYYYYYYY... 16 | // UVUVUVUVUVUVUVU... 17 | 18 | const uint u_frameId = 0; 19 | const uint u_shaderFilter = filter_linear_emulated; 20 | const float u_linearToNits = 400.0f; 21 | const float u_nitsToLinear = 1.0f / 100.0f; 22 | const float u_itmSdrNits = 100.f; 23 | const float u_itmTargetNits = 1000.f; 24 | 25 | layout(binding = 0, scalar) 26 | uniform layers_t { 27 | vec2 u_scale[1]; 28 | vec2 u_offset[1]; 29 | float u_opacity[1]; 30 | mat3x4 u_ctm[1]; 31 | mat3x4 u_outputCTM; 32 | uint u_borderMask; 33 | uvec2 u_halfExtent; 34 | }; 35 | 36 | #include "composite.h" 37 | 38 | vec4 sampleLayer(uint layerIdx, vec2 uv) { 39 | return sampleLayer(s_samplers[layerIdx], layerIdx, uv, true); 40 | } 41 | 42 | vec3 applyColorMatrix(vec3 rgb, mat3x4 matrix) { 43 | return vec4(linearToSrgb(rgb), 1.0f) * matrix; 44 | } 45 | 46 | void main() { 47 | ivec3 thread_id = ivec3(gl_GlobalInvocationID); 48 | 49 | // todo: fix 50 | if (all(lessThan(thread_id.xy, ivec2(u_halfExtent.x, u_halfExtent.y)))) { 51 | vec2 offset_table[4] = { 52 | vec2(0, 0), vec2(1, 0), vec2(0, 1), vec2(1, 1), 53 | }; 54 | 55 | ivec2 chroma_uv = thread_id.xy; 56 | vec2 luma_uv = vec2(thread_id.xy * 2) + vec2(0.5f, 0.5f); 57 | 58 | vec3 color[4] = { 59 | sampleLayer(0, vec2(luma_uv.x + offset_table[0].x, luma_uv.y + offset_table[0].y)).rgb, 60 | sampleLayer(0, vec2(luma_uv.x + offset_table[1].x, luma_uv.y + offset_table[1].y)).rgb, 61 | sampleLayer(0, vec2(luma_uv.x + offset_table[2].x, luma_uv.y + offset_table[2].y)).rgb, 62 | sampleLayer(0, vec2(luma_uv.x + offset_table[3].x, luma_uv.y + offset_table[3].y)).rgb, 63 | }; 64 | 65 | vec3 avg_color = (color[0] + color[1] + color[2] + color[3]) / 4.0f; 66 | vec2 uv = applyColorMatrix(avg_color, u_outputCTM).yz; 67 | imageStore(dst_chroma, chroma_uv, vec4(uv, 0.0f, 1.0f)); 68 | 69 | for (int i = 0; i < 4; i++) { 70 | float y = applyColorMatrix(color[i], u_outputCTM).x; 71 | imageStore(dst_luma, ivec2(luma_uv + offset_table[i]), vec4(y, 0.0f, 0.0f, 1.0f)); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/shaders/descriptor_set.h: -------------------------------------------------------------------------------- 1 | #include "descriptor_set_constants.h" 2 | 3 | layout(constant_id = 0) const int c_layerCount = 1; 4 | layout(constant_id = 1) const uint c_ycbcrMask = 0; 5 | layout(constant_id = 2) const uint c_compositing_debug = 0; 6 | layout(constant_id = 3) const int c_blur_layer_count = 0; 7 | 8 | layout(constant_id = 4) const uint c_colorspaceMask = 0; 9 | layout(constant_id = 5) const uint c_output_eotf = 0; 10 | layout(constant_id = 7) const bool c_itm_enable = false; 11 | 12 | const int colorspace_linear = 0; 13 | const int colorspace_sRGB = 1; 14 | const int colorspace_scRGB = 2; 15 | const int colorspace_pq = 3; 16 | const int colorspace_passthru = 4; 17 | const int colorspace_max_bits = 3; 18 | 19 | const int filter_linear_emulated = 0; 20 | const int filter_nearest = 1; 21 | const int filter_fsr = 2; 22 | const int filter_nis = 3; 23 | const int filter_pixel = 4; 24 | const int filter_from_view = 255; 25 | 26 | const int EOTF_Gamma22 = 0; 27 | const int EOTF_PQ = 1; 28 | const int EOTF_Count = 2; 29 | 30 | // These can be changed but also modify the ones in renderervulkan.hpp 31 | const uint compositedebug_Markers = 1u << 0; 32 | const uint compositedebug_PlaneBorders = 1u << 1; 33 | const uint compositedebug_Heatmap = 1u << 2; 34 | const uint compositedebug_Heatmap_MSWCG = 1u << 3; // If compositedebug_Heatmap is set, use the MS WCG heatmap instead of Lilium 35 | const uint compositedebug_Heatmap_Hard = 1u << 4; // If compositedebug_Heatmap is set, use a heatmap with specialized hard flagging 36 | const uint compositedebug_Markers_Partial = 1u << 5; // If compositedebug_Heatmap is set, use a heatmap with specialized hard flagging 37 | //const uint compositedebug_Tonemap_Reinhard = 1u << 7; // Use Reinhard tonemapping instead of Uncharted. 38 | 39 | bool checkDebugFlag(uint flag) { 40 | return (c_compositing_debug & flag) != 0; 41 | } 42 | 43 | uint get_layer_colorspace(uint layerIdx) { 44 | return bitfieldExtract(c_colorspaceMask, int(layerIdx) * colorspace_max_bits, colorspace_max_bits); 45 | } 46 | 47 | layout(binding = 1, rgba8) writeonly uniform image2D dst; 48 | // alias 49 | layout(binding = 1, rgba8) writeonly uniform image2D dst_luma; 50 | layout(binding = 2, rgba8) writeonly uniform image2D dst_chroma; 51 | 52 | layout(binding = 3) uniform sampler2D s_samplers[VKR_SAMPLER_SLOTS]; 53 | layout(binding = 4) uniform sampler2D s_ycbcr_samplers[VKR_SAMPLER_SLOTS]; 54 | 55 | layout(binding = 5) uniform sampler1D s_shaperLut[VKR_LUT3D_COUNT]; 56 | layout(binding = 6) uniform sampler3D s_lut3D[VKR_LUT3D_COUNT]; 57 | -------------------------------------------------------------------------------- /src/shaders/descriptor_set_constants.h: -------------------------------------------------------------------------------- 1 | #ifndef DESCRIPTOR_SET_CONSTANTS_H_ 2 | #define DESCRIPTOR_SET_CONSTANTS_H_ 3 | 4 | #define VKR_TARGET_SLOTS 2u 5 | #define VKR_SAMPLER_SLOTS 16u 6 | #define VKR_MAX_LAYERS 6u 7 | 8 | #define VKR_BLUR_EXTRA_SLOT VKR_MAX_LAYERS 9 | #define VKR_NIS_COEF_SCALER_SLOT (VKR_BLUR_EXTRA_SLOT + 1u) 10 | #define VKR_NIS_COEF_USM_SLOT (VKR_NIS_COEF_SCALER_SLOT + 1u) 11 | 12 | #define VKR_LUT3D_COUNT 2 // Must match EOTF_Count 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/shaders/shaderfilter.h: -------------------------------------------------------------------------------- 1 | #ifndef SHADERFILTER_H 2 | #define SHADERFILTER_H 3 | 4 | const int shader_filter_max_bits = 4; 5 | 6 | uint get_layer_shaderfilter(uint layerIdx) { 7 | return bitfieldExtract(u_shaderFilter, int(layerIdx) * shader_filter_max_bits, shader_filter_max_bits); 8 | } 9 | 10 | #endif -------------------------------------------------------------------------------- /src/steamcompmgr.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "wlr_begin.hpp" 4 | #include 5 | #include 6 | #include 7 | #include "wlr_end.hpp" 8 | 9 | extern uint32_t currentOutputWidth; 10 | extern uint32_t currentOutputHeight; 11 | 12 | unsigned int get_time_in_milliseconds(void); 13 | uint64_t get_time_in_nanos(); 14 | void sleep_for_nanos(uint64_t nanos); 15 | void sleep_until_nanos(uint64_t nanos); 16 | timespec nanos_to_timespec( uint64_t ulNanos ); 17 | 18 | void steamcompmgr_main(int argc, char **argv); 19 | 20 | #include "rendervulkan.hpp" 21 | #include "wlserver.hpp" 22 | #include "vblankmanager.hpp" 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | struct _XDisplay; 30 | struct steamcompmgr_win_t; 31 | struct xwayland_ctx_t; 32 | class gamescope_xwayland_server_t; 33 | 34 | static const uint32_t g_zposBase = 0; 35 | static const uint32_t g_zposOverride = 1; 36 | static const uint32_t g_zposExternalOverlay = 2; 37 | static const uint32_t g_zposOverlay = 3; 38 | static const uint32_t g_zposCursor = 4; 39 | static const uint32_t g_zposMuraCorrection = 5; 40 | 41 | extern bool g_bHDRItmEnable; 42 | extern bool g_bForceHDRSupportDebug; 43 | 44 | extern EStreamColorspace g_ForcedNV12ColorSpace; 45 | 46 | struct CursorBarrierInfo 47 | { 48 | int x1 = 0; 49 | int y1 = 0; 50 | int x2 = 0; 51 | int y2 = 0; 52 | }; 53 | 54 | struct CursorBarrier 55 | { 56 | PointerBarrier obj = None; 57 | CursorBarrierInfo info = {}; 58 | }; 59 | 60 | class MouseCursor 61 | { 62 | public: 63 | explicit MouseCursor(xwayland_ctx_t *ctx); 64 | 65 | int x() const; 66 | int y() const; 67 | 68 | void paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, FrameInfo_t *frameInfo); 69 | void setDirty(); 70 | 71 | // Will take ownership of data. 72 | bool setCursorImage(char *data, int w, int h, int hx, int hy); 73 | bool setCursorImageByName(const char *name); 74 | 75 | void hide() 76 | { 77 | wlserver_lock(); 78 | wlserver_mousehide(); 79 | wlserver_unlock( false ); 80 | checkSuspension(); 81 | } 82 | 83 | void UpdatePosition(); 84 | 85 | bool isHidden() { return wlserver.bCursorHidden || m_imageEmpty; } 86 | bool imageEmpty() const { return m_imageEmpty; } 87 | 88 | void undirty() { getTexture(); } 89 | 90 | xwayland_ctx_t *getCtx() const { return m_ctx; } 91 | 92 | bool needs_server_flush() const { return m_needs_server_flush; } 93 | void inform_flush() { m_needs_server_flush = false; } 94 | 95 | void GetDesiredSize( int& nWidth, int &nHeight ); 96 | 97 | void checkSuspension(); 98 | 99 | bool IsConstrained() const { return m_bConstrained; } 100 | private: 101 | 102 | bool getTexture(); 103 | 104 | void updateCursorFeedback( bool bForce = false ); 105 | 106 | int m_x = 0, m_y = 0; 107 | bool m_bConstrained = false; 108 | int m_hotspotX = 0, m_hotspotY = 0; 109 | 110 | gamescope::OwningRc m_texture; 111 | bool m_dirty; 112 | uint64_t m_ulLastConnectorId = 0; 113 | bool m_imageEmpty; 114 | 115 | xwayland_ctx_t *m_ctx; 116 | 117 | bool m_bCursorVisibleFeedback = false; 118 | bool m_needs_server_flush = false; 119 | }; 120 | 121 | extern std::vector< wlr_surface * > wayland_surfaces_deleted; 122 | 123 | extern bool hasFocusWindow; 124 | 125 | // These are used for touch scaling, so it's really the window that's focused for touch 126 | extern float focusedWindowScaleX; 127 | extern float focusedWindowScaleY; 128 | extern float focusedWindowOffsetX; 129 | extern float focusedWindowOffsetY; 130 | 131 | extern bool g_bFSRActive; 132 | 133 | extern uint32_t inputCounter; 134 | extern uint64_t g_lastWinSeq; 135 | 136 | void nudge_steamcompmgr( void ); 137 | void force_repaint( void ); 138 | 139 | extern void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns ); 140 | struct wlr_surface *steamcompmgr_get_server_input_surface( size_t idx ); 141 | wlserver_vk_swapchain_feedback* steamcompmgr_get_base_layer_swapchain_feedback(); 142 | 143 | struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( gamescope_xwayland_server_t *xwayland_server, uint32_t xid ); 144 | 145 | extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; 146 | extern pid_t focusWindow_pid; 147 | extern std::shared_ptr focusWindow_engine; 148 | 149 | void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server); 150 | void gamescope_set_selection(std::string contents, GamescopeSelection eSelection); 151 | void gamescope_set_reshade_effect(std::string effect_path); 152 | void gamescope_clear_reshade_effect(); 153 | 154 | MouseCursor *steamcompmgr_get_current_cursor(); 155 | MouseCursor *steamcompmgr_get_server_cursor(uint32_t serverId); 156 | 157 | extern gamescope::ConVar cv_tearing_enabled; 158 | 159 | extern void steamcompmgr_set_app_refresh_cycle_override( gamescope::GamescopeScreenType type, int override_fps, bool change_refresh, bool change_fps_cap ); 160 | -------------------------------------------------------------------------------- /src/vblankmanager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "waitable.h" 5 | 6 | namespace gamescope 7 | { 8 | struct VBlankScheduleTime 9 | { 10 | // The expected time for the vblank we want to target. 11 | uint64_t ulTargetVBlank = 0; 12 | // The vblank offset by the redzone/scheduling calculation. 13 | // This is when we want to wake-up by to meet that vblank time above. 14 | uint64_t ulScheduledWakeupPoint = 0; 15 | }; 16 | 17 | struct VBlankTime 18 | { 19 | VBlankScheduleTime schedule; 20 | // This is when we woke-up either by the timerfd poll 21 | // or on the nudge thread. We use this to feed-back into 22 | // the draw time so we automatically account for th 23 | // CPU scheduler quantums. 24 | uint64_t ulWakeupTime = 0; 25 | }; 26 | 27 | class CVBlankTimer : public ITimerWaitable 28 | { 29 | public: 30 | static constexpr uint64_t kMilliSecInNanoSecs = 1'000'000ul; 31 | // VBlank timer defaults and starting values. 32 | // Anything time-related is nanoseconds unless otherwise specified. 33 | static constexpr uint64_t kStartingVBlankDrawTime = 3'000'000ul; 34 | static constexpr uint64_t kDefaultMinVBlankTime = 350'000ul; 35 | static constexpr uint64_t kDefaultVBlankRedZone = 1'650'000ul; 36 | static constexpr uint64_t kDefaultVBlankDrawTimeMinCompositing = 2'400'000ul; 37 | static constexpr uint64_t kDefaultVBlankRateOfDecayPercentage = 980ul; // 98% 38 | static constexpr uint64_t kVBlankRateOfDecayMax = 1000ul; // 100% 39 | 40 | static constexpr uint64_t kVRRFlushingTime = 300'000; 41 | 42 | CVBlankTimer(); 43 | ~CVBlankTimer(); 44 | 45 | int GetRefresh() const; 46 | uint64_t GetLastVBlank() const; 47 | uint64_t GetNextVBlank( uint64_t ulOffset ) const; 48 | 49 | VBlankScheduleTime CalcNextWakeupTime( bool bPreemptive ); 50 | void Reschedule(); 51 | 52 | std::optional ProcessVBlank(); 53 | void MarkVBlank( uint64_t ulNanos, bool bReArmTimer ); 54 | 55 | bool WasCompositing() const; 56 | void UpdateWasCompositing( bool bCompositing ); 57 | void UpdateLastDrawTime( uint64_t ulNanos ); 58 | 59 | void WaitToBeArmed(); 60 | void ArmNextVBlank( bool bPreemptive ); 61 | 62 | bool UsingTimerFD() const; 63 | int GetFD() final; 64 | void OnPollIn() final; 65 | private: 66 | void VBlankDebugSpew( uint64_t ulOffset, uint64_t ulDrawTime, uint64_t ulRedZone ); 67 | 68 | uint64_t m_ulTargetVBlank = 0; 69 | std::atomic m_ulLastVBlank = { 0 }; 70 | std::atomic m_bArmed = { false }; 71 | std::atomic m_bRunning = { true }; 72 | 73 | std::optional m_PendingVBlank; 74 | 75 | // Should have 0 contest, but just to be safe. 76 | // This also covers setting of m_bArmed, etc 77 | // so we keep in sequence. 78 | // m_bArmed is atomic so can still be .wait()'ed 79 | // on/read outside. 80 | // Does not cover m_ulLastVBlank, this is just atomic. 81 | std::mutex m_ScheduleMutex; 82 | VBlankScheduleTime m_TimerFDSchedule{}; 83 | 84 | std::thread m_NudgeThread; 85 | int m_nNudgePipe[2] = { -1, -1 }; 86 | 87 | ///////////////////////////// 88 | // Scheduling bits and bobs. 89 | ///////////////////////////// 90 | 91 | // Are we currently compositing? We may need 92 | // to push back to avoid clock feedback loops if so. 93 | // This is fed-back from steamcompmgr. 94 | std::atomic m_bCurrentlyCompositing = { false }; 95 | // This is the last time a 'draw' took from wake-up to page flip. 96 | // 3ms by default to get the ball rolling. 97 | // This is calculated by steamcompmgr/drm and fed-back to the vblank timer. 98 | std::atomic m_ulLastDrawTime = { kStartingVBlankDrawTime }; 99 | 100 | ////////////////////////////////// 101 | // VBlank timing tuneables below! 102 | ////////////////////////////////// 103 | 104 | // Internal rolling peak exponential avg. draw time. 105 | // This is updated in CalcNextWakeupTime when not 106 | // doing pre-emptive timer re-arms. 107 | uint64_t m_ulRollingMaxDrawTime = kStartingVBlankDrawTime; 108 | 109 | // This accounts for some time we cannot account for (which (I think) is the drm_commit -> triggering the pageflip) 110 | // It would be nice to make this lower if we can find a way to track that effectively 111 | // Perhaps the missing time is spent elsewhere, but given we track from the pipe write 112 | // to after the return from `drm_commit` -- I am very doubtful. 113 | // 1.3ms by default. (kDefaultMinVBlankTime) 114 | uint64_t m_ulMinVBlankTime = kDefaultMinVBlankTime; 115 | 116 | // The leeway we always apply to our buffer. 117 | // 0.3ms by default. (kDefaultVBlankRedZone) 118 | uint64_t m_ulVBlankDrawBufferRedZone = kDefaultVBlankRedZone; 119 | 120 | // The minimum drawtime to use when we are compositing. 121 | // Getting closer and closer to vblank when compositing means that we can get into 122 | // a feedback loop with our GPU clocks. Pick a sane minimum draw time. 123 | // 2.4ms by default. (kDefaultVBlankDrawTimeMinCompositing) 124 | uint64_t m_ulVBlankDrawTimeMinCompositing = kDefaultVBlankDrawTimeMinCompositing; 125 | 126 | // The rate of decay (as a percentage) of the rolling average -> current draw time 127 | // 930 = 93%. 128 | // 93% by default. (kDefaultVBlankRateOfDecayPercentage) 129 | uint64_t m_ulVBlankRateOfDecayPercentage = kDefaultVBlankRateOfDecayPercentage; 130 | 131 | void NudgeThread(); 132 | }; 133 | } 134 | 135 | gamescope::CVBlankTimer &GetVBlankTimer(); 136 | 137 | -------------------------------------------------------------------------------- /src/vulkan_include.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define VK_NO_PROTOTYPES 4 | #include 5 | -------------------------------------------------------------------------------- /src/win32_styles.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | static constexpr uint32_t WS_OVERLAPPED = 0x00000000u; 4 | static constexpr uint32_t WS_POPUP = 0x80000000u; 5 | static constexpr uint32_t WS_CHILD = 0x40000000u; 6 | static constexpr uint32_t WS_MINIMIZE = 0x20000000u; 7 | static constexpr uint32_t WS_VISIBLE = 0x10000000u; 8 | static constexpr uint32_t WS_DISABLED = 0x08000000u; 9 | static constexpr uint32_t WS_CLIPSIBLINGS = 0x04000000u; 10 | static constexpr uint32_t WS_CLIPCHILDREN = 0x02000000u; 11 | static constexpr uint32_t WS_MAXIMIZE = 0x01000000u; 12 | static constexpr uint32_t WS_BORDER = 0x00800000u; 13 | static constexpr uint32_t WS_DLGFRAME = 0x00400000u; 14 | static constexpr uint32_t WS_VSCROLL = 0x00200000u; 15 | static constexpr uint32_t WS_HSCROLL = 0x00100000u; 16 | static constexpr uint32_t WS_SYSMENU = 0x00080000u; 17 | static constexpr uint32_t WS_THICKFRAME = 0x00040000u; 18 | static constexpr uint32_t WS_GROUP = 0x00020000u; 19 | static constexpr uint32_t WS_TABSTOP = 0x00010000u; 20 | static constexpr uint32_t WS_MINIMIZEBOX = 0x00020000u; 21 | static constexpr uint32_t WS_MAXIMIZEBOX = 0x00010000u; 22 | static constexpr uint32_t WS_CAPTION = WS_BORDER | WS_DLGFRAME; 23 | static constexpr uint32_t WS_TILED = WS_OVERLAPPED; 24 | static constexpr uint32_t WS_ICONIC = WS_MINIMIZE; 25 | static constexpr uint32_t WS_SIZEBOX = WS_THICKFRAME; 26 | static constexpr uint32_t WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME| WS_MINIMIZEBOX | WS_MAXIMIZEBOX; 27 | static constexpr uint32_t WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU; 28 | static constexpr uint32_t WS_CHILDWINDOW = WS_CHILD; 29 | static constexpr uint32_t WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW; 30 | 31 | static constexpr uint32_t WS_EX_DLGMODALFRAME = 0x00000001u; 32 | static constexpr uint32_t WS_EX_DRAGDETECT = 0x00000002u; // Undocumented 33 | static constexpr uint32_t WS_EX_NOPARENTNOTIFY = 0x00000004u; 34 | static constexpr uint32_t WS_EX_TOPMOST = 0x00000008u; 35 | static constexpr uint32_t WS_EX_ACCEPTFILES = 0x00000010u; 36 | static constexpr uint32_t WS_EX_TRANSPARENT = 0x00000020u; 37 | static constexpr uint32_t WS_EX_MDICHILD = 0x00000040u; 38 | static constexpr uint32_t WS_EX_TOOLWINDOW = 0x00000080u; 39 | static constexpr uint32_t WS_EX_WINDOWEDGE = 0x00000100u; 40 | static constexpr uint32_t WS_EX_CLIENTEDGE = 0x00000200u; 41 | static constexpr uint32_t WS_EX_CONTEXTHELP = 0x00000400u; 42 | static constexpr uint32_t WS_EX_RIGHT = 0x00001000u; 43 | static constexpr uint32_t WS_EX_LEFT = 0x00000000u; 44 | static constexpr uint32_t WS_EX_RTLREADING = 0x00002000u; 45 | static constexpr uint32_t WS_EX_LTRREADING = 0x00000000u; 46 | static constexpr uint32_t WS_EX_LEFTSCROLLBAR = 0x00004000u; 47 | static constexpr uint32_t WS_EX_RIGHTSCROLLBAR = 0x00000000u; 48 | static constexpr uint32_t WS_EX_CONTROLPARENT = 0x00010000u; 49 | static constexpr uint32_t WS_EX_STATICEDGE = 0x00020000u; 50 | static constexpr uint32_t WS_EX_APPWINDOW = 0x00040000u; 51 | static constexpr uint32_t WS_EX_LAYERED = 0x00080000u; 52 | static constexpr uint32_t WS_EX_NOINHERITLAYOUT = 0x00100000u; 53 | static constexpr uint32_t WS_EX_NOREDIRECTIONBITMAP = 0x00200000u; 54 | static constexpr uint32_t WS_EX_LAYOUTRTL = 0x00400000u; 55 | static constexpr uint32_t WS_EX_COMPOSITED = 0x02000000u; 56 | static constexpr uint32_t WS_EX_NOACTIVATE = 0x08000000u; 57 | -------------------------------------------------------------------------------- /src/wlr_begin.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" { 4 | #define static 5 | #define class class_ 6 | #define namespace _namespace 7 | #define delete delete_ 8 | -------------------------------------------------------------------------------- /src/wlr_end.hpp: -------------------------------------------------------------------------------- 1 | #undef static 2 | #undef class 3 | #undef namespace 4 | #undef delete 5 | } 6 | -------------------------------------------------------------------------------- /src/x11cursor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "backend.h" 4 | #include "Utils/Defer.h" 5 | #include "xwayland_ctx.hpp" 6 | 7 | extern const char *g_pOriginalDisplay; 8 | 9 | namespace gamescope 10 | { 11 | std::shared_ptr GetX11HostCursor() 12 | { 13 | if ( !g_pOriginalDisplay ) 14 | return nullptr; 15 | 16 | Display *display = XOpenDisplay( g_pOriginalDisplay ); 17 | if ( !display ) 18 | return nullptr; 19 | defer( XCloseDisplay( display ) ); 20 | 21 | int xfixes_event, xfixes_error; 22 | if ( !XFixesQueryExtension( display, &xfixes_event, &xfixes_error ) ) 23 | { 24 | xwm_log.errorf("No XFixes extension on current compositor"); 25 | return nullptr; 26 | } 27 | 28 | XFixesCursorImage *image = XFixesGetCursorImage( display ); 29 | if ( !image ) 30 | return nullptr; 31 | defer( XFree( image ) ); 32 | 33 | // image->pixels is `unsigned long*` :/ 34 | // Thanks X11. 35 | std::vector cursorData = std::vector( image->width * image->height ); 36 | for (uint32_t y = 0; y < image->height; y++) 37 | { 38 | for (uint32_t x = 0; x < image->width; x++) 39 | { 40 | cursorData[y * image->width + x] = static_cast( image->pixels[image->height * y + x] ); 41 | } 42 | } 43 | 44 | return std::make_shared(INestedHints::CursorInfo 45 | { 46 | .pPixels = std::move( cursorData ), 47 | .uWidth = image->width, 48 | .uHeight = image->height, 49 | .uXHotspot = image->xhot, 50 | .uYHotspot = image->yhot, 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /subprojects/.gitignore: -------------------------------------------------------------------------------- 1 | glm-* 2 | packagecache 3 | stb 4 | -------------------------------------------------------------------------------- /subprojects/glm.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory = glm 3 | 4 | url = https://github.com/g-truc/glm.git 5 | revision = 0af55ccecd98d4e5a8d1fad7de25ba429d60e863 6 | depth = 1 7 | 8 | patch_directory = glm 9 | -------------------------------------------------------------------------------- /subprojects/packagefiles/glm/meson.build: -------------------------------------------------------------------------------- 1 | project('glm', 'cpp', version: '1.0.1', license: 'MIT') 2 | 3 | glm_dep = declare_dependency( 4 | include_directories: include_directories('.') 5 | ) 6 | 7 | meson.override_dependency('glm', glm_dep) 8 | -------------------------------------------------------------------------------- /subprojects/packagefiles/stb/meson.build: -------------------------------------------------------------------------------- 1 | project('stb', 'c', version : '0.1.0', license : 'MIT') 2 | 3 | stb_dep = declare_dependency( 4 | include_directories : include_directories('.'), 5 | version : meson.project_version() 6 | ) 7 | 8 | meson.override_dependency('stb', stb_dep) 9 | -------------------------------------------------------------------------------- /subprojects/stb.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory = stb 3 | 4 | url = https://github.com/nothings/stb.git 5 | revision = 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 6 | depth = 1 7 | 8 | patch_directory = stb 9 | -------------------------------------------------------------------------------- /thirdparty/sol/config.hpp: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2013-2020 Rapptz, ThePhD and contributors 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // This file was generated with a script. 23 | // Generated 2024-09-03 03:46:49.901324 UTC 24 | // This header was generated with sol v3.3.1 (revision 2b0d2fe8) 25 | // https://github.com/ThePhD/sol2 26 | 27 | #ifndef SOL_SINGLE_SOL_CONFIG_HPP 28 | #define SOL_SINGLE_SOL_CONFIG_HPP 29 | 30 | // beginning of sol/config.hpp 31 | 32 | /* Base, empty configuration file! 33 | 34 | To override, place a file in your include paths of the form: 35 | 36 | . (your include path here) 37 | | sol (directory, or equivalent) 38 | | config.hpp (your config.hpp file) 39 | 40 | So that when sol2 includes the file 41 | 42 | #include 43 | 44 | it gives you the configuration values you desire. Configuration values can be 45 | seen in the safety.rst of the doc/src, or at 46 | https://sol2.readthedocs.io/en/latest/safety.html ! You can also pass them through 47 | the build system, or the command line options of your compiler. 48 | 49 | */ 50 | 51 | #define SOL_NO_EXCEPTIONS 1 52 | 53 | // end of sol/config.hpp 54 | 55 | #endif // SOL_SINGLE_SOL_CONFIG_HPP 56 | --------------------------------------------------------------------------------