diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..d777bedc82 --- /dev/null +++ b/.clang-format @@ -0,0 +1,67 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: true +BreakAfterOpenBracketFunction: true +BreakAfterOpenBracketIf: true +BreakAfterOpenBracketBracedList: true +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignOperands: Align +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: Always +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Always +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: true + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBraces: Custom +BreakConstructorInitializers: AfterColon +BreakConstructorInitializersBeforeComma: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +IncludeCategories: + - Regex: '^<.*' + Priority: 1 + - Regex: '^".*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: BeforeHash +IndentWidth: 4 +InsertNewlineAtEOF: true +NamespaceIndentation: All +SpaceAfterTemplateKeyword: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInParens: Custom +SpacesInParensOptions: + ExceptDoubleParentheses: true + InConditionalStatements: true + InCStyleCasts: true + InEmptyParentheses: true + Other: true +SpacesInSquareBrackets: true +TabWidth: 4 +... diff --git a/.editorconfig b/.editorconfig index dc59c75638..2b4436811a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,8 +1,9 @@ root = true [*] +charset = utf-8 end_of_line = lf +indent_style = space insert_final_newline = true -trim_trailing_whitespace = false -indent_style = tab tab_width = 4 +trim_trailing_whitespace = true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 48c8b7734e..fd5d112237 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: run: | pacman-key --init pacman -Syu --noconfirm - pacman -S --noconfirm git meson clang glslang libcap wlroots0.18 \ + pacman -S --noconfirm --needed git meson clang glslang libcap wlroots0.18 \ sdl2 vulkan-headers libx11 libxmu libxcomposite libxrender libxres \ libxtst libxkbcommon libdrm libinput wayland-protocols benchmark \ xorg-xwayland pipewire cmake \ @@ -24,29 +24,59 @@ jobs: - uses: actions/checkout@v6 with: submodules: recursive + - name: Generate file list for formatting + id: files + run: | + FILES=$(git ls-files src | grep -v -E '(reshade|shaders|\.o$|\.bin$|\.a$|\.so$|\.dylib$)') + echo "formatted_files=$FILES" >> $GITHUB_OUTPUT + - name: Format code with clang-format + run: | + echo "${{ steps.files.outputs.formatted_files }}" | xargs -P $(nproc) -- clang-format -i -- + - name: Commit formatted code + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + echo "${{ steps.files.outputs.formatted_files }}" | xargs -r git add -- + git commit -m "chore: clang-format [skip ci]" || true - name: Build with gcc run: | export CC=gcc CXX=g++ - meson setup build-gcc/ -Dinput_emulation=disabled --werror --auto-features=enabled - ninja -C build-gcc/ + meson setup -Dinput_emulation=disabled --werror --auto-features=enabled -- build-gcc + meson compile -C build-gcc - name: Build with gcc (no vr) run: | export CC=gcc CXX=g++ - meson setup build-gcc-novr/ -Dinput_emulation=disabled -Denable_openvr_support=false --werror --auto-features=enabled - ninja -C build-gcc-novr/ -# - name: Build with clang -# run: | -# export CC=clang CXX=clang++ -# meson setup build-clang/ -Dinput_emulation=disabled --werror --auto-features=enabled -# ninja -C build-clang/ - - name: Run unit tests and collect coverage + meson setup -Dinput_emulation=disabled -Denable_openvr_support=false --werror --auto-features=enabled -- build-gcc-novr + meson compile -C build-gcc-novr + - name: Build with clang + run: | + export CC=clang CXX=clang++ + meson setup -Dinput_emulation=disabled --werror --auto-features=enabled -- build-clang + meson compile -C build-clang + - name: Build with clang (no vr) + run: | + export CC=clang CXX=clang++ + meson setup -Dinput_emulation=disabled -Denable_openvr_support=false --werror --auto-features=enabled -- build-clang-novr + meson compile -C build-clang-novr + - name: Run unit tests and collect coverage (gcc) run: | export CC=gcc CXX=g++ meson configure -Db_coverage=true build-gcc meson test -C build-gcc --suite gamescope -v - gcovr -f src --xml -o coverage.xml -s build-gcc - - name: Upload coverage report + gcovr -f src --xml -o coverage-gcc.xml -s build-gcc + - name: Run unit tests and collect coverage (clang) + run: | + export CC=clang CXX=clang++ + meson configure -Db_coverage=true build-clang + meson test -C build-clang --suite gamescope -v + gcovr -f src --xml -o coverage-clang.xml -s build-clang/ --gcov-executable="llvm-cov gcov" + - name: Upload coverage report (gcc) + uses: actions/upload-artifact@v7 + with: + name: code coverage (gcc) + path: coverage-gcc.xml + - name: Upload coverage report (clang) uses: actions/upload-artifact@v7 with: - name: code coverage - path: coverage.xml + name: code coverage (clang) + path: coverage-clang.xml diff --git a/.gitmodules b/.gitmodules index ec7d4e430e..58c76ce230 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,21 +1,7 @@ -[submodule "subprojects/wlroots"] - path = subprojects/wlroots - url = https://github.com/Joshua-Ashton/wlroots.git -[submodule "subprojects/libliftoff"] - path = subprojects/libliftoff - url = https://gitlab.freedesktop.org/emersion/libliftoff.git -[submodule "subprojects/vkroots"] - path = subprojects/vkroots - url = https://github.com/Joshua-Ashton/vkroots -[submodule "subprojects/libdisplay-info"] - path = subprojects/libdisplay-info - url = https://gitlab.freedesktop.org/emersion/libdisplay-info -[submodule "subprojects/openvr"] - path = subprojects/openvr - url = https://github.com/ValveSoftware/openvr.git [submodule "src/reshade"] - path = src/reshade - url = https://github.com/Joshua-Ashton/reshade + path = src/reshade + url = https://github.com/misyltoad/reshade/ + [submodule "thirdparty/SPIRV-Headers"] - path = thirdparty/SPIRV-Headers - url = https://github.com/KhronosGroup/SPIRV-Headers/ + path = thirdparty/SPIRV-Headers + url = https://github.com/KhronosGroup/SPIRV-Headers/ diff --git a/meson.build b/meson.build index 49853b993f..79651e4f72 100644 --- a/meson.build +++ b/meson.build @@ -1,23 +1,24 @@ project( - 'gamescope', - 'c', - 'cpp', - meson_version: '>=0.58.0', - default_options: [ - 'cpp_std=c++20', - 'warning_level=2', - 'force_fallback_for=wlroots,libliftoff,vkroots', - ], + 'gamescope', + 'c', + 'cpp', + license : 'BSD-2-Clause', + meson_version : '>=0.58.0', + default_options : [ + 'cpp_std=c++20', + 'warning_level=2', + 'force_fallback_for=wlroots,libliftoff,vkroots', + ], ) fallbacks = get_option('force_fallback_for') if not (fallbacks.contains('wlroots') and fallbacks.contains('libliftoff') and fallbacks.contains('vkroots')) - 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.') + 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.') endif add_project_arguments([ - '-DWLR_USE_UNSTABLE', -], language: 'cpp') + '-DWLR_USE_UNSTABLE', + ], language : 'cpp') cppc = meson.get_compiler('cpp') @@ -25,88 +26,86 @@ data_dir = get_option('datadir') prefix = get_option('prefix') lib_dir = get_option('libdir') -add_project_arguments(cppc.get_supported_arguments([ - '-Wno-unused-parameter', - '-Wno-missing-field-initializers', - '-Wno-c99-designator', - '-Wno-invalid-offsetof', - '-Wno-unused-const-variable', - '-Wno-volatile', # glm warning - '-Wno-deprecated-volatile', - '-Wno-ignored-qualifiers', # reshade warning - '-Wno-missing-braces', -]), language: 'cpp') - -add_project_arguments(cppc.get_supported_arguments([ - '-fno-exceptions', - '-ffast-math', -]), language: 'cpp') - -pipewire_dep = dependency('libpipewire-0.3', required: get_option('pipewire')) +add_project_arguments( + cppc.get_supported_arguments( + [ + '-Wno-unused-parameter', + '-Wno-unused-result', + '-Wno-missing-field-initializers', + '-Wno-c99-designator', + '-Wno-invalid-offsetof', + '-Wno-unused-const-variable', + '-Wno-volatile', # glm warning + '-Wno-deprecated-volatile', + '-Wno-ignored-qualifiers', # reshade warning + '-Wno-missing-braces', + ] + ), + language : 'cpp' +) + +add_project_arguments( + cppc.get_supported_arguments( + [ + '-fno-exceptions', + '-ffast-math', + ] + ), + language : 'cpp' +) + +pipewire_dep = dependency('libpipewire-0.3', required : get_option('pipewire')) librt_dep = cppc.find_library('rt', required : get_option('pipewire')) hwdata_dep = dependency('hwdata', required : false) -dep_x11 = dependency('x11') -dep_wayland = dependency('wayland-client') -vulkan_dep = dependency('vulkan') - -glm_proj = subproject('glm') -glm_dep = glm_proj.get_variable('glm_dep') -stb_proj = subproject('stb') -stb_dep = stb_proj.get_variable('stb_dep') +dep_x11 = dependency('x11', fallback : 'x11') +dep_wayland = dependency('wayland-client', fallback : 'wayland-client') +vulkan_dep = dependency('vulkan', fallback : 'vulkan') +glm_dep = dependency('glm', fallback : 'glm') +stb_dep = dependency('stb', fallback : 'stb') +vkroots_dep = dependency('vkroots', fallback : 'vkroots') if get_option('enable_openvr_support') - openvr_dep = dependency('openvr', version: '>= 2.7', required : false) - if not openvr_dep.found() - cmake = import('cmake') - openvr_var = cmake.subproject_options() - openvr_var.add_cmake_defines({'USE_LIBCXX': false, - #HACK: remove me when openvr supports CMake 4.0 - 'CMAKE_POLICY_VERSION_MINIMUM': '3.5'}) - #ENDHACK - openvr_var.set_override_option('warning_level', '0') - openvr_proj = cmake.subproject('openvr', options : openvr_var) - openvr_dep = openvr_proj.dependency('openvr_api') - endif + openvr_dep = dependency('openvr', fallback : 'openvr') else - # null dep - openvr_dep = dependency('', required : false) + # null dep + openvr_dep = dependency('', required : false) endif add_project_arguments( - '-DHAVE_PIPEWIRE=@0@'.format(pipewire_dep.found().to_int()), - '-DHAVE_OPENVR=@0@'.format(openvr_dep.found().to_int()), - '-DSCRIPT_DIR="@0@"'.format(prefix / data_dir / 'gamescope/scripts'), - language: 'cpp', + '-DHAVE_PIPEWIRE=@0@'.format(pipewire_dep.found().to_int()), + '-DHAVE_OPENVR=@0@'.format(openvr_dep.found().to_int()), + '-DSCRIPT_DIR="@0@"'.format(prefix / data_dir / 'gamescope/scripts'), + language : 'cpp', ) if hwdata_dep.found() - add_project_arguments( - '-DHWDATA_PNP_IDS="@0@"'.format(hwdata_dep.get_variable('pkgdatadir') / 'pnp.ids'), - language: 'cpp', - ) + add_project_arguments( + '-DHWDATA_PNP_IDS="@0@"'.format(hwdata_dep.get_variable('pkgdatadir') / 'pnp.ids'), + language : 'cpp', + ) else - warning('Building without hwdata pnp id support.') + warning('Building without hwdata pnp id support.') endif # Vulkan headers are installed separately from the loader (which ships the # pkg-config file) -if not cppc.check_header('vulkan/vulkan.h', dependencies: vulkan_dep) - error('Missing vulkan-headers') +if not cppc.check_header('vulkan/vulkan.h', dependencies : vulkan_dep) + error('Missing vulkan-headers') endif subdir('protocol') if get_option('enable_gamescope_wsi_layer') - subdir('layer') + subdir('layer') endif if get_option('enable_gamescope') - subdir('src') + subdir('src') endif if get_option('enable_tests') - subdir('tests') + subdir('tests') endif diff --git a/meson_options.txt b/meson_options.txt index e906e51b7d..3d3a33574f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,11 +1,11 @@ -option('pipewire', type: 'feature', description: 'Screen capture via PipeWire') -option('rt_cap', type: 'feature', description: 'Support for creating real-time threads + compute queues') -option('drm_backend', type: 'feature', description: 'DRM Atomic Backend') -option('sdl2_backend', type: 'feature', description: 'SDL2 Window Backend') -option('avif_screenshots', type: 'feature', description: 'Support for saving .AVIF HDR screenshots') -option('input_emulation' , type: 'feature', description: 'Support for XTest/Input Emulation with libei') -option('enable_gamescope', type : 'boolean', value : true, description: 'Build Gamescope executable') -option('enable_gamescope_wsi_layer', type : 'boolean', value : true, description: 'Build Gamescope layer') -option('enable_openvr_support', type : 'boolean', value : true, description: 'OpenVR Integrations') -option('enable_tests', type : 'boolean', value : true, description: 'Build unit tests') -option('benchmark', type: 'feature', description: 'Benchmark tools') +option('pipewire', type : 'feature', description : 'Screen capture via PipeWire') +option('rt_cap', type : 'feature', description : 'Support for creating real-time threads + compute queues') +option('drm_backend', type : 'feature', description : 'DRM Atomic Backend') +option('sdl2_backend', type : 'feature', description : 'SDL2 Window Backend') +option('avif_screenshots', type : 'feature', description : 'Support for saving .AVIF HDR screenshots') +option('input_emulation' , type : 'feature', description : 'Support for XTest/Input Emulation with libei') +option('enable_gamescope', type : 'boolean', value : true, description : 'Build Gamescope executable') +option('enable_gamescope_wsi_layer', type : 'boolean', value : true, description : 'Build Gamescope layer') +option('enable_openvr_support', type : 'boolean', value : true, description : 'OpenVR Integrations') +option('enable_tests', type : 'boolean', value : true, description : 'Build unit tests') +option('benchmark', type : 'feature', description : 'Benchmark tools') diff --git a/src/Apps/gamescope_hotkey_example.cpp b/src/Apps/gamescope_hotkey_example.cpp index ffd88bdfa2..541813e1b3 100644 --- a/src/Apps/gamescope_hotkey_example.cpp +++ b/src/Apps/gamescope_hotkey_example.cpp @@ -1,51 +1,61 @@ #include #include +#include +#include #include #include -#include -#include -#include "convar.h" #include "Utils/Version.h" +#include "convar.h" #include -#include #include +#include // TODO: Consolidate -#define WAYLAND_NULL() [] ( void *pData, Args... args ) { } -#define WAYLAND_USERDATA_TO_THIS(type, name) [] ( void *pData, Args... args ) { type *pThing = (type *)pData; pThing->name( std::forward(args)... ); } +#define WAYLAND_NULL( ) []( void *pData, Args... args ) {} +#define WAYLAND_USERDATA_TO_THIS( type, name ) \ + []( void *pData, Args... args ) \ + { \ + type *pThing = ( type * )pData; \ + pThing->name( std::forward( args )... ); \ + } namespace gamescope { class CActionBinding { public: - bool Init( gamescope_action_binding_manager *pManager, std::span pKeySyms ) + bool Init( + gamescope_action_binding_manager *pManager, + std::span pKeySyms ) { - Shutdown(); + Shutdown( ); - m_pBinding = gamescope_action_binding_manager_create_action_binding( pManager ); - if ( !m_pBinding ) - return false; + m_pBinding = gamescope_action_binding_manager_create_action_binding( + pManager ); + if ( !m_pBinding ) return false; wl_array array; - wl_array_init(&array); + wl_array_init( &array ); for ( uint32_t uKeySym : pKeySyms ) { - uint32_t *pKeySymPtr = (uint32_t *)wl_array_add(&array, sizeof(uint32_t) ); + uint32_t *pKeySymPtr = + ( uint32_t * )wl_array_add( &array, sizeof( uint32_t ) ); *pKeySymPtr = uKeySym; } - gamescope_action_binding_add_listener( m_pBinding, &s_BindingListener, (void *)this ); + gamescope_action_binding_add_listener( + m_pBinding, &s_BindingListener, ( void * )this ); gamescope_action_binding_add_keyboard_trigger( m_pBinding, &array ); - gamescope_action_binding_set_description( m_pBinding, "My Example Hotkey :)" ); + gamescope_action_binding_set_description( + m_pBinding, "My Example Hotkey :)" ); gamescope_action_binding_arm( m_pBinding, 0 ); return true; } - void Shutdown() + void Shutdown( ) { if ( m_pBinding ) { @@ -54,10 +64,13 @@ namespace gamescope } } - void Wayland_Triggered( gamescope_action_binding *pBinding, uint32_t uSequence, uint32_t uTriggerFlags, uint32_t uTimeLo, uint32_t uTimeHi ) - { - fprintf( stderr, "Hotkey pressed!" ); - } + void Wayland_Triggered( + gamescope_action_binding *pBinding, + uint32_t uSequence, + uint32_t uTriggerFlags, + uint32_t uTimeLo, + uint32_t uTimeHi ) + { fprintf( stderr, "Hotkey pressed!" ); } private: gamescope_action_binding *m_pBinding = nullptr; @@ -65,40 +78,41 @@ namespace gamescope static const gamescope_action_binding_listener s_BindingListener; }; - const gamescope_action_binding_listener CActionBinding::s_BindingListener = - { - .triggered = WAYLAND_USERDATA_TO_THIS( CActionBinding, Wayland_Triggered ), - }; + const gamescope_action_binding_listener + CActionBinding::s_BindingListener = { + .triggered = + WAYLAND_USERDATA_TO_THIS( CActionBinding, Wayland_Triggered ), + }; class GamescopeHotkeyExample { public: - GamescopeHotkeyExample(); - ~GamescopeHotkeyExample(); + GamescopeHotkeyExample( ); + ~GamescopeHotkeyExample( ); + + bool Init( ); + void Run( ); - bool Init(); - void Run(); private: - wl_display *m_pDisplay = nullptr; + wl_display *m_pDisplay = nullptr; gamescope_action_binding_manager *m_pActionBindingManager = nullptr; - void Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ); + void Wayland_Registry_Global( + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ); static const wl_registry_listener s_RegistryListener; }; - GamescopeHotkeyExample::GamescopeHotkeyExample() - { - } + GamescopeHotkeyExample::GamescopeHotkeyExample( ) {} - GamescopeHotkeyExample::~GamescopeHotkeyExample() - { - } + GamescopeHotkeyExample::~GamescopeHotkeyExample( ) {} - bool GamescopeHotkeyExample::Init() + bool GamescopeHotkeyExample::Init( ) { const char *pDisplayName = getenv( "GAMESCOPE_WAYLAND_DISPLAY" ); - if ( !pDisplayName || !*pDisplayName ) - pDisplayName = "gamescope-0"; + if ( !pDisplayName || !*pDisplayName ) pDisplayName = "gamescope-0"; if ( !( m_pDisplay = wl_display_connect( pDisplayName ) ) ) { @@ -114,7 +128,8 @@ namespace gamescope return false; } - wl_registry_add_listener( pRegistry, &s_RegistryListener, (void *)this ); + wl_registry_add_listener( + pRegistry, &s_RegistryListener, ( void * )this ); wl_display_roundtrip( m_pDisplay ); wl_display_roundtrip( m_pDisplay ); @@ -130,14 +145,15 @@ namespace gamescope return true; } - void GamescopeHotkeyExample::Run() + void GamescopeHotkeyExample::Run( ) { // Add a test hotkey of Shift + P. - std::vector uKeySyms = { 0xffe1, 0x0070 }; // XKB_KEY_Shift_L + XKB_KEY_p + std::vector uKeySyms = { + 0xffe1, 0x0070 + }; // XKB_KEY_Shift_L + XKB_KEY_p CActionBinding binding; - if ( !binding.Init( m_pActionBindingManager, uKeySyms ) ) - return; + if ( !binding.Init( m_pActionBindingManager, uKeySyms ) ) return; wl_display_flush( m_pDisplay ); @@ -147,33 +163,40 @@ namespace gamescope } } - void GamescopeHotkeyExample::Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ) + void GamescopeHotkeyExample::Wayland_Registry_Global( + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ) { - if ( !strcmp( pInterface, gamescope_action_binding_manager_interface.name ) ) + if ( !strcmp( + pInterface, gamescope_action_binding_manager_interface.name ) ) { - m_pActionBindingManager = (decltype(m_pActionBindingManager)) wl_registry_bind( pRegistry, uName, &gamescope_action_binding_manager_interface, uVersion ); + m_pActionBindingManager = + ( decltype( m_pActionBindingManager ) )wl_registry_bind( + pRegistry, + uName, + &gamescope_action_binding_manager_interface, + uVersion ); } } - const wl_registry_listener GamescopeHotkeyExample::s_RegistryListener = - { - .global = WAYLAND_USERDATA_TO_THIS( GamescopeHotkeyExample, Wayland_Registry_Global ), - .global_remove = WAYLAND_NULL(), + const wl_registry_listener GamescopeHotkeyExample::s_RegistryListener = { + .global = WAYLAND_USERDATA_TO_THIS( + GamescopeHotkeyExample, Wayland_Registry_Global ), + .global_remove = WAYLAND_NULL( ), }; static int RunHotkeyExample( int argc, char *argv[] ) { gamescope::GamescopeHotkeyExample hotkeyExample; - if ( !hotkeyExample.Init() ) - return 1; + if ( !hotkeyExample.Init( ) ) return 1; - hotkeyExample.Run(); + hotkeyExample.Run( ); return 0; } -} +} // namespace gamescope int main( int argc, char *argv[] ) -{ - return gamescope::RunHotkeyExample( argc, argv ); -} +{ return gamescope::RunHotkeyExample( argc, argv ); } diff --git a/src/Apps/gamescope_type.c b/src/Apps/gamescope_type.c index 982adbfee0..177f08681c 100644 --- a/src/Apps/gamescope_type.c +++ b/src/Apps/gamescope_type.c @@ -2,8 +2,8 @@ #include #include #include -#include #include +#include #include #include "gamescope-input-method-client-protocol.h" @@ -11,145 +11,222 @@ // Based on wl-ime-type by Simon Ser static struct gamescope_input_method_manager *ime_manager = NULL; -static struct wl_seat *seat = NULL; +static struct wl_seat *seat = NULL; -static bool ime_unavailable = false; -static uint32_t ime_serial = 0; +static bool ime_unavailable = false; +static uint32_t ime_serial = 0; -static void noop(void *data, struct wl_registry *registry, uint32_t name) +static void noop( void *data, struct wl_registry *registry, uint32_t name ) { - (void)data; (void)registry; (void)name; + ( void )data; + ( void )registry; + ( void )name; } -static void ime_handle_done(void *data, struct gamescope_input_method *ime, uint32_t serial) +static void ime_handle_done( + void *data, struct gamescope_input_method *ime, uint32_t serial ) { - (void)data; (void)ime; - ime_serial = serial; + ( void )data; + ( void )ime; + ime_serial = serial; } -static void ime_handle_unavailable(void *data, struct gamescope_input_method *ime) +static void +ime_handle_unavailable( void *data, struct gamescope_input_method *ime ) { - (void)data; (void)ime; - ime_unavailable = true; + ( void )data; + ( void )ime; + ime_unavailable = true; } static const struct gamescope_input_method_listener ime_listener = { - .done = ime_handle_done, - .unavailable = ime_handle_unavailable, + .done = ime_handle_done, + .unavailable = ime_handle_unavailable, }; -static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *iface, uint32_t version) +static void registry_handle_global( + void *data, + struct wl_registry *registry, + uint32_t name, + const char *iface, + uint32_t version ) { - (void)data; (void)version; - - if (strcmp(iface, gamescope_input_method_manager_interface.name) == 0) { - ime_manager = wl_registry_bind(registry, name, &gamescope_input_method_manager_interface, 2); - } else if (seat == NULL && strcmp(iface, wl_seat_interface.name) == 0) { - seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); - } + ( void )data; + ( void )version; + + if ( strcmp( iface, gamescope_input_method_manager_interface.name ) == 0 ) + { + ime_manager = wl_registry_bind( + registry, name, &gamescope_input_method_manager_interface, 2 ); + } + else if ( seat == NULL && strcmp( iface, wl_seat_interface.name ) == 0 ) + { + seat = wl_registry_bind( registry, name, &wl_seat_interface, 1 ); + } } static const struct wl_registry_listener registry_listener = { - .global = registry_handle_global, - .global_remove = noop, + .global = registry_handle_global, + .global_remove = noop, }; -static int getch(void) { +static int getch( void ) +{ struct termios oldattr; - tcgetattr(STDIN_FILENO, &oldattr); + tcgetattr( STDIN_FILENO, &oldattr ); struct termios newattr = oldattr; - newattr.c_lflag &= ~(ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &newattr); - int ch = getchar(); - tcsetattr(STDIN_FILENO, TCSANOW, &oldattr); + newattr.c_lflag &= ~( ICANON | ECHO ); + tcsetattr( STDIN_FILENO, TCSANOW, &newattr ); + int ch = getchar( ); + tcsetattr( STDIN_FILENO, TCSANOW, &oldattr ); return ch; } -int main() +int main( ) { - const char *display_name = getenv("GAMESCOPE_WAYLAND_DISPLAY"); - if (!display_name) - display_name = "gamescope-0"; - - struct wl_display *display = wl_display_connect(display_name); - if (display == NULL) { - fprintf(stderr, "wl_display_connect failed\n"); - return 1; - } - - struct wl_registry *registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, NULL); - wl_display_roundtrip(display); - wl_registry_destroy(registry); - - if (seat == NULL) { - fprintf(stderr, "Compositor has no seat\n"); - return 1; - } - if (ime_manager == NULL) { - fprintf(stderr, "Compositor doesn't support input-method-unstable-v2\n"); - return 1; - } - - struct gamescope_input_method *ime = gamescope_input_method_manager_create_input_method(ime_manager, seat); - gamescope_input_method_add_listener(ime, &ime_listener, NULL); - - // Wait for the initial done/unavailable event - wl_display_roundtrip(display); - - if (ime_unavailable) { - fprintf(stderr, "IME is unavailable (maybe another IME is active?)\n"); - return 1; - } - - bool in_escape = false; - for (;;) - { - int ch = getch(); - - if (in_escape) { - if (ch == '[') - continue; - - switch(ch) { - case 'A': gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_UP); gamescope_input_method_commit(ime, ime_serial); break; - case 'B': gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_DOWN); gamescope_input_method_commit(ime, ime_serial); break; - case 'C': gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_RIGHT); gamescope_input_method_commit(ime, ime_serial); break; - case 'D': gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_LEFT); gamescope_input_method_commit(ime, ime_serial); break; - case '3': gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_DELETE_RIGHT); gamescope_input_method_commit(ime, ime_serial); getch(); break; - case '5': for (int i = 0; i < 128; i++) { gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_UP); gamescope_input_method_commit(ime, ime_serial); } break; - case '6': for (int i = 0; i < 128; i++) { gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_DOWN); gamescope_input_method_commit(ime, ime_serial); } break; - case 'H': for (int i = 0; i < 128; i++) { gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_LEFT); gamescope_input_method_commit(ime, ime_serial); } break; - case 'F': for (int i = 0; i < 128; i++) { gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_RIGHT); gamescope_input_method_commit(ime, ime_serial); } break; - } - - wl_display_roundtrip(display); - in_escape = false; - continue; - } - - if (ch == '\b' || ch == 0x7f) // delete - gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_DELETE_LEFT); - else if (ch == '\r') - continue; - else if (ch == '\n') - gamescope_input_method_set_action(ime, GAMESCOPE_INPUT_METHOD_ACTION_SUBMIT); - else if (ch == '\033') { // esc - in_escape = true; - continue; - } - else { - char str[2] = { (char)ch, '\0' }; - gamescope_input_method_set_string(ime, str); - } - - gamescope_input_method_commit(ime, ime_serial); - wl_display_roundtrip(display); - } - - gamescope_input_method_destroy(ime); - gamescope_input_method_manager_destroy(ime_manager); - wl_display_disconnect(display); - - return 0; + const char *display_name = getenv( "GAMESCOPE_WAYLAND_DISPLAY" ); + if ( !display_name ) display_name = "gamescope-0"; + + struct wl_display *display = wl_display_connect( display_name ); + if ( display == NULL ) + { + fprintf( stderr, "wl_display_connect failed\n" ); + return 1; + } + + struct wl_registry *registry = wl_display_get_registry( display ); + wl_registry_add_listener( registry, ®istry_listener, NULL ); + wl_display_roundtrip( display ); + wl_registry_destroy( registry ); + + if ( seat == NULL ) + { + fprintf( stderr, "Compositor has no seat\n" ); + return 1; + } + if ( ime_manager == NULL ) + { + fprintf( + stderr, "Compositor doesn't support input-method-unstable-v2\n" ); + return 1; + } + + struct gamescope_input_method *ime = + gamescope_input_method_manager_create_input_method( ime_manager, seat ); + gamescope_input_method_add_listener( ime, &ime_listener, NULL ); + + // Wait for the initial done/unavailable event + wl_display_roundtrip( display ); + + if ( ime_unavailable ) + { + fprintf( + stderr, "IME is unavailable (maybe another IME is active?)\n" ); + return 1; + } + + bool in_escape = false; + for ( ;; ) + { + int ch = getch( ); + + if ( in_escape ) + { + if ( ch == '[' ) continue; + + switch ( ch ) + { + case 'A': + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_UP ); + gamescope_input_method_commit( ime, ime_serial ); + break; + case 'B': + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_DOWN ); + gamescope_input_method_commit( ime, ime_serial ); + break; + case 'C': + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_RIGHT ); + gamescope_input_method_commit( ime, ime_serial ); + break; + case 'D': + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_LEFT ); + gamescope_input_method_commit( ime, ime_serial ); + break; + case '3': + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_DELETE_RIGHT ); + gamescope_input_method_commit( ime, ime_serial ); + getch( ); + break; + case '5': + for ( int i = 0; i < 128; i++ ) + { + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_UP ); + gamescope_input_method_commit( ime, ime_serial ); + } + break; + case '6': + for ( int i = 0; i < 128; i++ ) + { + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_DOWN ); + gamescope_input_method_commit( ime, ime_serial ); + } + break; + case 'H': + for ( int i = 0; i < 128; i++ ) + { + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_LEFT ); + gamescope_input_method_commit( ime, ime_serial ); + } + break; + case 'F': + for ( int i = 0; i < 128; i++ ) + { + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_MOVE_RIGHT ); + gamescope_input_method_commit( ime, ime_serial ); + } + break; + } + + wl_display_roundtrip( display ); + in_escape = false; + continue; + } + + if ( ch == '\b' || ch == 0x7f ) // delete + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_DELETE_LEFT ); + else if ( ch == '\r' ) + continue; + else if ( ch == '\n' ) + gamescope_input_method_set_action( + ime, GAMESCOPE_INPUT_METHOD_ACTION_SUBMIT ); + else if ( ch == '\033' ) + { // esc + in_escape = true; + continue; + } + else + { + char str[ 2 ] = { ( char )ch, '\0' }; + gamescope_input_method_set_string( ime, str ); + } + + gamescope_input_method_commit( ime, ime_serial ); + wl_display_roundtrip( display ); + } + + gamescope_input_method_destroy( ime ); + gamescope_input_method_manager_destroy( ime_manager ); + wl_display_disconnect( display ); + + return 0; } diff --git a/src/Apps/gamescopectl.cpp b/src/Apps/gamescopectl.cpp index f0d5b64a29..1f960fbe70 100644 --- a/src/Apps/gamescopectl.cpp +++ b/src/Apps/gamescopectl.cpp @@ -1,82 +1,103 @@ #include #include +#include +#include #include #include -#include -#include -#include "convar.h" #include "Utils/Version.h" +#include "convar.h" -#include #include #include +#include // TODO: Consolidate -#define WAYLAND_NULL() [] ( void *pData, Args... args ) { } -#define WAYLAND_USERDATA_TO_THIS(type, name) [] ( void *pData, Args... args ) { type *pThing = (type *)pData; pThing->name( std::forward(args)... ); } +#define WAYLAND_NULL( ) []( void *pData, Args... args ) {} +#define WAYLAND_USERDATA_TO_THIS( type, name ) \ + []( void *pData, Args... args ) \ + { \ + type *pThing = ( type * )pData; \ + pThing->name( std::forward( args )... ); \ + } namespace gamescope { struct GamescopeFeature { gamescope_control_feature eFeature; - uint32_t uVersion; - uint32_t uFlags; + uint32_t uVersion; + uint32_t uFlags; }; struct GamescopeActiveDisplayInfo { - std::string szConnectorName; - std::string szDisplayMake; - std::string szDisplayModel; - uint32_t uDisplayFlags; + std::string szConnectorName; + std::string szDisplayMake; + std::string szDisplayModel; + uint32_t uDisplayFlags; std::vector ValidRefreshRates; }; class GamescopeCtl { public: - GamescopeCtl(); - ~GamescopeCtl(); + GamescopeCtl( ); + ~GamescopeCtl( ); bool Init( bool bInitControl, bool bInitPrivate ); bool Execute( std::span args ); - std::span GetFeatures() { return std::span{ m_Features }; } - const std::optional &GetActiveDisplayInfo() { return m_ActiveDisplayInfo; } + std::span GetFeatures( ) + { return std::span{ m_Features }; } + const std::optional &GetActiveDisplayInfo( ) + { return m_ActiveDisplayInfo; } + private: bool m_bInitControl = false; bool m_bInitPrivate = false; - wl_display *m_pDisplay = nullptr; + wl_display *m_pDisplay = nullptr; gamescope_control *m_pGamescopeControl = nullptr; gamescope_private *m_pGamescopePrivate = nullptr; uint32_t m_uCommandCount = 0; - std::vector m_Features; + std::vector m_Features; std::optional m_ActiveDisplayInfo; - void Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ); + void Wayland_Registry_Global( + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ); static const wl_registry_listener s_RegistryListener; - void Wayland_GamescopeControl_FeatureSupport( gamescope_control *pGamescopeControl, uint32_t uFeature, uint32_t uVersion, uint32_t uFlags ); - void Wayland_GamescopeControl_ActiveDisplayInfo( gamescope_control *pGamescopeControl, const char *pConnectorName, const char *pDisplayMake, const char *pDisplayModel, uint32_t uDisplayFlags, wl_array *pValidRefreshRatesArray ); - void Wayland_GamescopeControl_ScreenshotTaken( gamescope_control *pGamescopeControl, const char *pPath ); + void Wayland_GamescopeControl_FeatureSupport( + gamescope_control *pGamescopeControl, + uint32_t uFeature, + uint32_t uVersion, + uint32_t uFlags ); + void Wayland_GamescopeControl_ActiveDisplayInfo( + gamescope_control *pGamescopeControl, + const char *pConnectorName, + const char *pDisplayMake, + const char *pDisplayModel, + uint32_t uDisplayFlags, + wl_array *pValidRefreshRatesArray ); + void Wayland_GamescopeControl_ScreenshotTaken( + gamescope_control *pGamescopeControl, const char *pPath ); static const gamescope_control_listener s_GamescopeControlListener; - void Wayland_GamescopePrivate_Log( gamescope_private *pGamescopePrivate, const char *pText ); - void Wayland_GamescopePrivate_CommandExecuted( gamescope_private *pGamescopePrivate ); + void Wayland_GamescopePrivate_Log( + gamescope_private *pGamescopePrivate, const char *pText ); + void Wayland_GamescopePrivate_CommandExecuted( + gamescope_private *pGamescopePrivate ); static const gamescope_private_listener s_GamescopePrivateListener; }; - GamescopeCtl::GamescopeCtl() - { - } + GamescopeCtl::GamescopeCtl( ) {} - GamescopeCtl::~GamescopeCtl() - { - } + GamescopeCtl::~GamescopeCtl( ) {} bool GamescopeCtl::Init( bool bInitControl, bool bInitPrivate ) { @@ -84,8 +105,7 @@ namespace gamescope m_bInitPrivate = bInitPrivate; const char *pDisplayName = getenv( "GAMESCOPE_WAYLAND_DISPLAY" ); - if ( !pDisplayName || !*pDisplayName ) - pDisplayName = "gamescope-0"; + if ( !pDisplayName || !*pDisplayName ) pDisplayName = "gamescope-0"; if ( !( m_pDisplay = wl_display_connect( pDisplayName ) ) ) { @@ -101,11 +121,13 @@ namespace gamescope return false; } - wl_registry_add_listener( pRegistry, &s_RegistryListener, (void *)this ); + wl_registry_add_listener( + pRegistry, &s_RegistryListener, ( void * )this ); wl_display_roundtrip( m_pDisplay ); wl_display_roundtrip( m_pDisplay ); - if ( !( !m_bInitControl || m_pGamescopeControl ) || !( !m_bInitPrivate || m_pGamescopePrivate ) ) + if ( !( !m_bInitControl || m_pGamescopeControl ) || + !( !m_bInitPrivate || m_pGamescopePrivate ) ) { fprintf( stderr, "Failed to get Gamescope interfaces\n" ); return false; @@ -119,100 +141,126 @@ namespace gamescope bool GamescopeCtl::Execute( std::span args ) { - if ( args.size() < 1 ) + if ( args.size( ) < 1 ) { fprintf( stderr, "No command to execute\n" ); return false; } - std::string szArg1 = std::string{ args[0] }; - std::string szArg2 = args.size() == 1 ? "" : std::string{ args[1] }; + std::string szArg1 = std::string{ args[ 0 ] }; + std::string szArg2 = args.size( ) == 1 ? "" : std::string{ args[ 1 ] }; - gamescope_private_execute( m_pGamescopePrivate, szArg1.c_str(), szArg2.c_str() ); + gamescope_private_execute( + m_pGamescopePrivate, szArg1.c_str( ), szArg2.c_str( ) ); wl_display_roundtrip( m_pDisplay ); return true; } - void GamescopeCtl::Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ) + void GamescopeCtl::Wayland_Registry_Global( + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ) { - if ( m_bInitControl && !strcmp( pInterface, gamescope_control_interface.name ) ) + if ( m_bInitControl && + !strcmp( pInterface, gamescope_control_interface.name ) ) { - m_pGamescopeControl = (decltype(m_pGamescopeControl)) wl_registry_bind( pRegistry, uName, &gamescope_control_interface, uVersion ); - gamescope_control_add_listener( m_pGamescopeControl, &s_GamescopeControlListener, this ); + m_pGamescopeControl = + ( decltype( m_pGamescopeControl ) )wl_registry_bind( + pRegistry, uName, &gamescope_control_interface, uVersion ); + gamescope_control_add_listener( + m_pGamescopeControl, &s_GamescopeControlListener, this ); } - else if ( m_bInitPrivate && !strcmp( pInterface, gamescope_private_interface.name ) ) + else if ( + m_bInitPrivate && + !strcmp( pInterface, gamescope_private_interface.name ) ) { - m_pGamescopePrivate = (decltype(m_pGamescopePrivate)) wl_registry_bind( pRegistry, uName, &gamescope_private_interface, uVersion ); - gamescope_private_add_listener( m_pGamescopePrivate, &s_GamescopePrivateListener, this ); + m_pGamescopePrivate = + ( decltype( m_pGamescopePrivate ) )wl_registry_bind( + pRegistry, uName, &gamescope_private_interface, uVersion ); + gamescope_private_add_listener( + m_pGamescopePrivate, &s_GamescopePrivateListener, this ); } } - const wl_registry_listener GamescopeCtl::s_RegistryListener = - { - .global = WAYLAND_USERDATA_TO_THIS( GamescopeCtl, Wayland_Registry_Global ), - .global_remove = WAYLAND_NULL(), + const wl_registry_listener GamescopeCtl::s_RegistryListener = { + .global = + WAYLAND_USERDATA_TO_THIS( GamescopeCtl, Wayland_Registry_Global ), + .global_remove = WAYLAND_NULL( ), }; - void GamescopeCtl::Wayland_GamescopeControl_FeatureSupport( gamescope_control *pGamescopeControl, uint32_t uFeature, uint32_t uVersion, uint32_t uFlags ) + void GamescopeCtl::Wayland_GamescopeControl_FeatureSupport( + gamescope_control *pGamescopeControl, + uint32_t uFeature, + uint32_t uVersion, + uint32_t uFlags ) { - gamescope_control_feature eFeature = static_cast( uFeature ); - if ( eFeature == GAMESCOPE_CONTROL_FEATURE_DONE ) - return; - m_Features.emplace_back( GamescopeFeature - { - .eFeature = eFeature, - .uVersion = uVersion, - .uFlags = uFlags - } ); + gamescope_control_feature eFeature = + static_cast( uFeature ); + if ( eFeature == GAMESCOPE_CONTROL_FEATURE_DONE ) return; + m_Features.emplace_back( + GamescopeFeature{ .eFeature = eFeature, + .uVersion = uVersion, + .uFlags = uFlags } ); } - void GamescopeCtl::Wayland_GamescopeControl_ActiveDisplayInfo( gamescope_control *pGamescopeControl, const char *pConnectorName, const char *pDisplayMake, const char *pDisplayModel, uint32_t uDisplayFlags, wl_array *pValidRefreshRatesArray ) + void GamescopeCtl::Wayland_GamescopeControl_ActiveDisplayInfo( + gamescope_control *pGamescopeControl, + const char *pConnectorName, + const char *pDisplayMake, + const char *pDisplayModel, + uint32_t uDisplayFlags, + wl_array *pValidRefreshRatesArray ) { - const uint32_t *pValidRefreshRates = reinterpret_cast( pValidRefreshRatesArray->data ); + const uint32_t *pValidRefreshRates = + reinterpret_cast( pValidRefreshRatesArray->data ); std::vector validRefreshRates; - for ( size_t i = 0; i < pValidRefreshRatesArray->size / sizeof( uint32_t ); i++ ) - validRefreshRates.push_back( pValidRefreshRates[i] ); - - m_ActiveDisplayInfo = GamescopeActiveDisplayInfo - { - .szConnectorName = pConnectorName, - .szDisplayMake = pDisplayMake, - .szDisplayModel = pDisplayModel, - .uDisplayFlags = uDisplayFlags, + for ( size_t i = 0; + i < pValidRefreshRatesArray->size / sizeof( uint32_t ); + i++ ) + validRefreshRates.push_back( pValidRefreshRates[ i ] ); + + m_ActiveDisplayInfo = GamescopeActiveDisplayInfo{ + .szConnectorName = pConnectorName, + .szDisplayMake = pDisplayMake, + .szDisplayModel = pDisplayModel, + .uDisplayFlags = uDisplayFlags, .ValidRefreshRates = std::move( validRefreshRates ), }; } - void GamescopeCtl::Wayland_GamescopeControl_ScreenshotTaken( gamescope_control *pGamescopeControl, const char *pPath ) - { - fprintf( stderr, "Screenshot taken to: %s\n", pPath ); - } - - const gamescope_control_listener GamescopeCtl::s_GamescopeControlListener = - { - .feature_support = WAYLAND_USERDATA_TO_THIS( GamescopeCtl, Wayland_GamescopeControl_FeatureSupport ), - .active_display_info = WAYLAND_USERDATA_TO_THIS( GamescopeCtl, Wayland_GamescopeControl_ActiveDisplayInfo ), - .screenshot_taken = WAYLAND_USERDATA_TO_THIS( GamescopeCtl, Wayland_GamescopeControl_ScreenshotTaken ), - }; + void GamescopeCtl::Wayland_GamescopeControl_ScreenshotTaken( + gamescope_control *pGamescopeControl, const char *pPath ) + { fprintf( stderr, "Screenshot taken to: %s\n", pPath ); } + + const gamescope_control_listener + GamescopeCtl::s_GamescopeControlListener = { + .feature_support = WAYLAND_USERDATA_TO_THIS( + GamescopeCtl, Wayland_GamescopeControl_FeatureSupport ), + .active_display_info = WAYLAND_USERDATA_TO_THIS( + GamescopeCtl, Wayland_GamescopeControl_ActiveDisplayInfo ), + .screenshot_taken = WAYLAND_USERDATA_TO_THIS( + GamescopeCtl, Wayland_GamescopeControl_ScreenshotTaken ), + }; - void GamescopeCtl::Wayland_GamescopePrivate_Log( gamescope_private *pGamescopePrivate, const char *pText ) - { - fprintf( stderr, "%s\n", pText ); - } + void GamescopeCtl::Wayland_GamescopePrivate_Log( + gamescope_private *pGamescopePrivate, const char *pText ) + { fprintf( stderr, "%s\n", pText ); } - void GamescopeCtl::Wayland_GamescopePrivate_CommandExecuted( gamescope_private *pGamescopePrivate ) - { - m_uCommandCount++; - } + void GamescopeCtl::Wayland_GamescopePrivate_CommandExecuted( + gamescope_private *pGamescopePrivate ) + { m_uCommandCount++; } - const gamescope_private_listener GamescopeCtl::s_GamescopePrivateListener = - { - .log = WAYLAND_USERDATA_TO_THIS( GamescopeCtl, Wayland_GamescopePrivate_Log ), - .command_executed = WAYLAND_USERDATA_TO_THIS( GamescopeCtl, Wayland_GamescopePrivate_CommandExecuted ), - }; + const gamescope_private_listener + GamescopeCtl::s_GamescopePrivateListener = { + .log = WAYLAND_USERDATA_TO_THIS( + GamescopeCtl, Wayland_GamescopePrivate_Log ), + .command_executed = WAYLAND_USERDATA_TO_THIS( + GamescopeCtl, Wayland_GamescopePrivate_CommandExecuted ), + }; static std::string_view GetFeatureName( gamescope_control_feature eFeature ) { - switch( eFeature ) + switch ( eFeature ) { case GAMESCOPE_CONTROL_FEATURE_DONE: return "Done (dummy)"; @@ -238,52 +286,82 @@ namespace gamescope bool bInfoOnly = argc < 2; gamescope::GamescopeCtl gamescopeCtl; - if ( !gamescopeCtl.Init( bInfoOnly, !bInfoOnly ) ) - return 1; + if ( !gamescopeCtl.Init( bInfoOnly, !bInfoOnly ) ) return 1; if ( bInfoOnly ) { - PrintVersion(); + PrintVersion( ); fprintf( stdout, "gamescope_control info:\n" ); - const auto &oActiveDisplayInfo = gamescopeCtl.GetActiveDisplayInfo(); + const auto &oActiveDisplayInfo = + gamescopeCtl.GetActiveDisplayInfo( ); if ( oActiveDisplayInfo ) { - fprintf( stdout, " - Connector Name: %.*s\n", (int)oActiveDisplayInfo->szConnectorName.length(), oActiveDisplayInfo->szConnectorName.data() ); - fprintf( stdout, " - Display Make: %.*s\n", (int)oActiveDisplayInfo->szDisplayMake.length(), oActiveDisplayInfo->szDisplayMake.data() ); - fprintf( stdout, " - Display Model: %.*s\n", (int)oActiveDisplayInfo->szDisplayModel.length(), oActiveDisplayInfo->szDisplayModel.data() ); - fprintf( stdout, " - Display Flags: 0x%x\n", oActiveDisplayInfo->uDisplayFlags ); + fprintf( + stdout, + " - Connector Name: %.*s\n", + ( int )oActiveDisplayInfo->szConnectorName.length( ), + oActiveDisplayInfo->szConnectorName.data( ) ); + fprintf( + stdout, + " - Display Make: %.*s\n", + ( int )oActiveDisplayInfo->szDisplayMake.length( ), + oActiveDisplayInfo->szDisplayMake.data( ) ); + fprintf( + stdout, + " - Display Model: %.*s\n", + ( int )oActiveDisplayInfo->szDisplayModel.length( ), + oActiveDisplayInfo->szDisplayModel.data( ) ); + fprintf( + stdout, + " - Display Flags: 0x%x\n", + oActiveDisplayInfo->uDisplayFlags ); fprintf( stdout, " - ValidRefreshRates: " ); - for ( size_t i = 0; i < oActiveDisplayInfo->ValidRefreshRates.size(); i++ ) + for ( size_t i = 0; + i < oActiveDisplayInfo->ValidRefreshRates.size( ); + i++ ) { - bool bLast = i == oActiveDisplayInfo->ValidRefreshRates.size() - 1; - uint32_t uRate = oActiveDisplayInfo->ValidRefreshRates[i]; + bool bLast = + i == oActiveDisplayInfo->ValidRefreshRates.size( ) - 1; + uint32_t uRate = oActiveDisplayInfo->ValidRefreshRates[ i ]; fprintf( stdout, bLast ? "%u" : "%u, ", uRate ); } fprintf( stdout, "\n" ); } fprintf( stdout, " Features:\n" ); - for ( const GamescopeFeature &feature : gamescopeCtl.GetFeatures() ) + for ( const GamescopeFeature &feature : + gamescopeCtl.GetFeatures( ) ) { - std::string_view szFeatureName = GetFeatureName( feature.eFeature ); - fprintf( stdout, " - %.*s (%u) - Version: %u - Flags: 0x%x\n", (int)szFeatureName.size(), szFeatureName.data(), uint32_t{ feature.eFeature }, feature.uVersion, feature.uFlags ); + std::string_view szFeatureName = + GetFeatureName( feature.eFeature ); + fprintf( + stdout, + " - %.*s (%u) - Version: %u - Flags: 0x%x\n", + ( int )szFeatureName.size( ), + szFeatureName.data( ), + uint32_t{ feature.eFeature }, + feature.uVersion, + feature.uFlags ); } - fprintf( stdout, "You can execute any debug command in Gamescope using this tool.\n" ); - fprintf( stdout, "For a list of commands and convars, use 'gamescopectl help'\n" ); + fprintf( + stdout, + "You can execute any debug command in Gamescope using this " + "tool.\n" ); + fprintf( + stdout, + "For a list of commands and convars, use 'gamescopectl " + "help'\n" ); return 0; } std::vector args; for ( int i = 1; i < argc; i++ ) - args.emplace_back( argv[i] ); + args.emplace_back( argv[ i ] ); - if ( !gamescopeCtl.Execute( std::span{ args } ) ) - return 1; + if ( !gamescopeCtl.Execute( std::span{ args } ) ) return 1; return 0; } -} +} // namespace gamescope int main( int argc, char *argv[] ) -{ - return gamescope::RunGamescopeCtl( argc, argv ); -} \ No newline at end of file +{ return gamescope::RunGamescopeCtl( argc, argv ); } diff --git a/src/Apps/gamescopereaper.cpp b/src/Apps/gamescopereaper.cpp index 0fd2c36906..3b199e4c69 100644 --- a/src/Apps/gamescopereaper.cpp +++ b/src/Apps/gamescopereaper.cpp @@ -5,9 +5,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -20,33 +20,31 @@ namespace gamescope // It sets itself up as a subreaper so any children get reparented ti oti. // If the primary process dies, it kills all the children. // - // Gamescope can have a lot of bad things happen to it, crashes, segfaults, whatever - // but we always want to make sure that we cleanly kill all of our children when we die. - // This child process attempts to stay alive as long as it can in order to fulfil Gamescope's - // dying wish -- to kill all of it's children. + // Gamescope can have a lot of bad things happen to it, crashes, segfaults, + // whatever but we always want to make sure that we cleanly kill all of our + // children when we die. This child process attempts to stay alive as long + // as it can in order to fulfil Gamescope's dying wish -- to kill all of + // it's children. int GamescopeReaperProcess( int argc, char **argv ) { - pthread_setname_np( pthread_self(), "gamescope-reaper" ); + pthread_setname_np( pthread_self( ), "gamescope-reaper" ); - static constexpr struct option k_ReaperOptions[] = - { + static constexpr struct option k_ReaperOptions[] = { { "label", required_argument, nullptr, 0 }, { "new-session-id", no_argument, nullptr, 0 }, { "respawn", no_argument, nullptr, 0 }, }; - bool bRespawn = false; - bool bNewSession = false; - static bool s_bRun = true; + bool bRespawn = false; + bool bNewSession = false; + static bool s_bRun = true; int nOptIndex = -1; - int nOption = -1; - while ( ( nOption = getopt_long(argc, argv, "", k_ReaperOptions, &nOptIndex ) ) != -1 ) + int nOption = -1; + while ( ( nOption = getopt_long( + argc, argv, "", k_ReaperOptions, &nOptIndex ) ) != -1 ) { - if ( nOption == '?' ) - { - s_ReaperLog.errorf( "Unknown option." ); - } + if ( nOption == '?' ) { s_ReaperLog.errorf( "Unknown option." ); } assert( nOption == 0 ); const char *pszOptionName = k_ReaperOptions[ nOptIndex ].name; @@ -55,10 +53,7 @@ namespace gamescope // Do nothing. continue; } - else if ( !strcmp( pszOptionName, "respawn" ) ) - { - bRespawn = true; - } + else if ( !strcmp( pszOptionName, "respawn" ) ) { bRespawn = true; } else if ( !strcmp( pszOptionName, "new-session-id" ) ) { bNewSession = true; @@ -83,19 +78,18 @@ namespace gamescope // Mirror some of the busy work we do in ProcessPreSpawn, // in case someone else wants to use this utility. - Process::ResetSignals(); - std::array nExcludedFds = - {{ + Process::ResetSignals( ); + std::array nExcludedFds = { { STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, - }}; + } }; Process::CloseAllFds( nExcludedFds ); - // We typically don't make a new sid, as we want to keep the same stdin/stdout - // Don't really care about it for pgroup reasons, as processes can leave those. - if ( bNewSession ) - setsid(); + // We typically don't make a new sid, as we want to keep the same + // stdin/stdout Don't really care about it for pgroup reasons, as + // processes can leave those. + if ( bNewSession ) setsid( ); // Set up a signal handler, so that SIGTERM, etc goes // and kills all the children. @@ -104,23 +98,25 @@ namespace gamescope { switch ( nSignal ) { - case SIGHUP: - case SIGINT: - case SIGQUIT: - case SIGTERM: - sigaction( SIGHUP, nullptr, nullptr ); - sigaction( SIGINT, nullptr, nullptr ); - sigaction( SIGQUIT, nullptr, nullptr ); - sigaction( SIGTERM, nullptr, nullptr ); - - if ( s_bRun ) - { - s_ReaperLog.infof( "Parent of gamescopereaper was killed. Killing children." ); - - s_bRun = false; - Process::KillAllChildren( getpid(), SIGTERM ); - } - break; + case SIGHUP: + case SIGINT: + case SIGQUIT: + case SIGTERM: + sigaction( SIGHUP, nullptr, nullptr ); + sigaction( SIGINT, nullptr, nullptr ); + sigaction( SIGQUIT, nullptr, nullptr ); + sigaction( SIGTERM, nullptr, nullptr ); + + if ( s_bRun ) + { + s_ReaperLog.infof( + "Parent of gamescopereaper was killed. Killing " + "children." ); + + s_bRun = false; + Process::KillAllChildren( getpid( ), SIGTERM ); + } + break; } }; sigaction( SIGHUP, &reaperSignalHandler, nullptr ); @@ -129,7 +125,7 @@ namespace gamescope sigaction( SIGTERM, &reaperSignalHandler, nullptr ); // (Don't Lose) The Children - Process::BecomeSubreaper(); + Process::BecomeSubreaper( ); Process::SetDeathSignal( SIGTERM ); pid_t nPrimaryChild = Process::SpawnProcess( &argv[ nSubCommandArgc ] ); @@ -137,42 +133,45 @@ namespace gamescope if ( nPrimaryChild > 0 ) { - // Wait for the primary child to die, then forward the death signal to - // all of the other children, if we aren't in a PID namespace. + // Wait for the primary child to die, then forward the death signal + // to all of the other children, if we aren't in a PID namespace. Process::WaitForAllChildren( nPrimaryChild ); if ( bRespawn ) { while ( s_bRun ) { - s_ReaperLog.infof( "\"%s\" process shut down. Restarting.", argv[ nSubCommandArgc ] ); + s_ReaperLog.infof( + "\"%s\" process shut down. Restarting.", + argv[ nSubCommandArgc ] ); - nPrimaryChild = Process::SpawnProcess( &argv[ nSubCommandArgc ] ); + nPrimaryChild = + Process::SpawnProcess( &argv[ nSubCommandArgc ] ); Process::WaitForAllChildren( nPrimaryChild ); } } s_bRun = false; - Process::KillAllChildren( getpid(), SIGTERM ); - Process::WaitForAllChildren(); + Process::KillAllChildren( getpid( ), SIGTERM ); + Process::WaitForAllChildren( ); return 0; } else { - s_ReaperLog.errorf_errno( "Failed to create child process \"%s\" in reaper.", argv[ nSubCommandArgc ] ); + s_ReaperLog.errorf_errno( + "Failed to create child process \"%s\" in reaper.", + argv[ nSubCommandArgc ] ); s_bRun = false; - Process::KillAllChildren( getpid(), SIGTERM ); - Process::WaitForAllChildren(); + Process::KillAllChildren( getpid( ), SIGTERM ); + Process::WaitForAllChildren( ); return 1; } } -} +} // namespace gamescope int main( int argc, char **argv ) -{ - return gamescope::GamescopeReaperProcess( argc, argv ); -} +{ return gamescope::GamescopeReaperProcess( argc, argv ); } diff --git a/src/Apps/gamescopestream.cpp b/src/Apps/gamescopestream.cpp index b5ae7d9d43..e765c2a875 100644 --- a/src/Apps/gamescopestream.cpp +++ b/src/Apps/gamescopestream.cpp @@ -1,44 +1,53 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////// -// Gracefully butchered from https://docs.pipewire.org/spa_2examples_2adapter-control_8c-example.html -// by Wim Taymans under MIT +// Gracefully butchered from +// https://docs.pipewire.org/spa_2examples_2adapter-control_8c-example.html by +// Wim Taymans under MIT /////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include -#include + #include +#include +#include +#include #include -#include #include -#include - -#include -#include -#include +#include + #include - +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnan-infinity-disabled" +#pragma clang diagnostic ignored "-Wsign-compare" +#endif #include - -#define DEFAULT_WIDTH 1280 +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#define DEFAULT_WIDTH 1280 #define DEFAULT_HEIGHT 720 - -#define MAX_BUFFERS 64 - -#include -#include + +#define MAX_BUFFERS 64 + #include +#include +#include -#define WAYLAND_NULL() [] ( void *pData, Args... args ) { } +#define WAYLAND_NULL( ) []( void *pData, Args... args ) {} #include #include -#include "pipewire_gamescope.hpp" #include "log.hpp" +#include "pipewire_gamescope.hpp" static LogScope s_StreamLog( "stream" ); - -void spa_gamescopestream_log( struct spa_debug_context *ctx, const char *fmt, ... ) + +void spa_gamescopestream_log( + struct spa_debug_context *ctx, const char *fmt, ... ) { va_list args; va_start( args, fmt ); @@ -46,66 +55,68 @@ void spa_gamescopestream_log( struct spa_debug_context *ctx, const char *fmt, .. va_end( args ); } -struct spa_debug_context s_SpaDebugContext = -{ +struct spa_debug_context s_SpaDebugContext = { .log = spa_gamescopestream_log, }; -struct pw_version { - int major; - int minor; - int micro; +struct pw_version +{ + int major; + int minor; + int micro; }; -static uint32_t spa_format_to_drm(uint32_t spa_format) +static uint32_t spa_format_to_drm( uint32_t spa_format ) { - switch (spa_format) - { - case SPA_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; - default: - case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888; - } + switch ( spa_format ) + { + case SPA_VIDEO_FORMAT_NV12: + return DRM_FORMAT_NV12; + default: + case SPA_VIDEO_FORMAT_BGR: + return DRM_FORMAT_XRGB8888; + } } - -struct data { + +struct data +{ const char *path; - - wl_display *pDisplay = nullptr; - wl_compositor *pCompositor = nullptr; - zwp_linux_dmabuf_v1 *pLinuxDmabuf = nullptr; - libdecor *pDecor = nullptr; - libdecor_frame *pFrame = nullptr; - wl_surface *pSurface = nullptr; - wl_buffer *pWaylandBuffer = nullptr; + + wl_display *pDisplay = nullptr; + wl_compositor *pCompositor = nullptr; + zwp_linux_dmabuf_v1 *pLinuxDmabuf = nullptr; + libdecor *pDecor = nullptr; + libdecor_frame *pFrame = nullptr; + wl_surface *pSurface = nullptr; + wl_buffer *pWaylandBuffer = nullptr; struct pw_main_loop *loop; - struct spa_source *reneg; - + struct spa_source *reneg; + struct pw_stream *stream; - struct spa_hook stream_listener; - + struct spa_hook stream_listener; + struct spa_video_info format; - int32_t stride; - struct spa_rectangle size; + int32_t stride; + struct spa_rectangle size; bool needs_decor_commit; uint32_t appid; - + std::unordered_map> m_FormatModifiers; - + int counter; }; - + static void handle_events( struct data *pData ) { wl_display_flush( pData->pDisplay ); if ( wl_display_prepare_read( pData->pDisplay ) == 0 ) { - int nRet = 0; - pollfd pollfd = - { + int nRet = 0; + pollfd pollfd = { .fd = wl_display_get_fd( pData->pDisplay ), .events = POLLIN, }; @@ -115,73 +126,94 @@ static void handle_events( struct data *pData ) nRet = poll( &pollfd, 1, 0 ); } while ( nRet < 0 && ( errno == EINTR || errno == EAGAIN ) ); - if ( nRet > 0 ) - wl_display_read_events( pData->pDisplay ); + if ( nRet > 0 ) wl_display_read_events( pData->pDisplay ); else wl_display_cancel_read( pData->pDisplay ); } wl_display_dispatch_pending( pData->pDisplay ); } - -static struct spa_pod *build_format(struct data *data, struct spa_pod_builder *b, enum spa_video_format format, uint64_t *modifiers, int modifier_count) + +static struct spa_pod *build_format( + struct data *data, + struct spa_pod_builder *b, + enum spa_video_format format, + uint64_t *modifiers, + int modifier_count ) { - struct spa_pod_frame f[3]; - int i, c; - - spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); - spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + struct spa_pod_frame f[ 3 ]; + int i, c; + + spa_pod_builder_push_object( + b, &f[ 0 ], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat ); + spa_pod_builder_add( + b, SPA_FORMAT_mediaType, SPA_POD_Id( SPA_MEDIA_TYPE_video ), 0 ); + spa_pod_builder_add( + b, SPA_FORMAT_mediaSubtype, SPA_POD_Id( SPA_MEDIA_SUBTYPE_raw ), 0 ); /* format */ - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + spa_pod_builder_add( b, SPA_FORMAT_VIDEO_format, SPA_POD_Id( format ), 0 ); /* modifiers */ - if (modifier_count == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) { - // we only support implicit modifiers, use shortpath to skip fixation phase - spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); - spa_pod_builder_long(b, modifiers[0]); - } else if (modifier_count > 0) { - // build an enumeration of modifiers - spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); - spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); - // modifiers from the array - for (i = 0, c = 0; i < modifier_count; i++) { - spa_pod_builder_long(b, modifiers[i]); - if (c++ == 0) - spa_pod_builder_long(b, modifiers[i]); - } - spa_pod_builder_pop(b, &f[1]); + if ( modifier_count == 1 && modifiers[ 0 ] == DRM_FORMAT_MOD_INVALID ) + { + // we only support implicit modifiers, use shortpath to skip fixation + // phase + spa_pod_builder_prop( + b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY ); + spa_pod_builder_long( b, modifiers[ 0 ] ); + } + else if ( modifier_count > 0 ) + { + // build an enumeration of modifiers + spa_pod_builder_prop( + b, + SPA_FORMAT_VIDEO_modifier, + SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE ); + spa_pod_builder_push_choice( b, &f[ 1 ], SPA_CHOICE_Enum, 0 ); + // modifiers from the array + for ( i = 0, c = 0; i < modifier_count; i++ ) + { + spa_pod_builder_long( b, modifiers[ i ] ); + if ( c++ == 0 ) spa_pod_builder_long( b, modifiers[ i ] ); + } + spa_pod_builder_pop( b, &f[ 1 ] ); } - spa_rectangle default_size = SPA_RECTANGLE(DEFAULT_WIDTH, DEFAULT_HEIGHT); - spa_rectangle min_size = SPA_RECTANGLE(1,1); - spa_rectangle max_size = SPA_RECTANGLE(65535, 65535); - - spa_fraction frac1 = SPA_FRACTION(25,1); - spa_fraction frac2 = SPA_FRACTION(0,1); - spa_fraction frac3 = SPA_FRACTION(30,1); - - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, - SPA_POD_CHOICE_RANGE_Rectangle( &default_size, &min_size, &max_size), - 0); - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, - SPA_POD_CHOICE_RANGE_Fraction( - &frac1, - &frac2, - &frac3), - 0); - - if (data->appid) - spa_pod_builder_add(b, SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_Long(uint64_t(data->appid)), 0); - - return (spa_pod *)spa_pod_builder_pop(b, &f[0]); + spa_rectangle default_size = SPA_RECTANGLE( DEFAULT_WIDTH, DEFAULT_HEIGHT ); + spa_rectangle min_size = SPA_RECTANGLE( 1, 1 ); + spa_rectangle max_size = SPA_RECTANGLE( 65535, 65535 ); + + spa_fraction frac1 = SPA_FRACTION( 25, 1 ); + spa_fraction frac2 = SPA_FRACTION( 0, 1 ); + spa_fraction frac3 = SPA_FRACTION( 30, 1 ); + + spa_pod_builder_add( + b, + SPA_FORMAT_VIDEO_size, + SPA_POD_CHOICE_RANGE_Rectangle( &default_size, &min_size, &max_size ), + 0 ); + spa_pod_builder_add( + b, + SPA_FORMAT_VIDEO_framerate, + SPA_POD_CHOICE_RANGE_Fraction( &frac1, &frac2, &frac3 ), + 0 ); + + if ( data->appid ) + spa_pod_builder_add( + b, + SPA_FORMAT_VIDEO_gamescope_focus_appid, + SPA_POD_Long( uint64_t( data->appid ) ), + 0 ); + + return ( spa_pod * )spa_pod_builder_pop( b, &f[ 0 ] ); } -void commit_libdecor( struct data *data, libdecor_configuration *pConfiguration ) +void commit_libdecor( + struct data *data, libdecor_configuration *pConfiguration ) { - uint32_t uWidth = data->format.info.raw.size.width; + uint32_t uWidth = data->format.info.raw.size.width; uint32_t uHeight = data->format.info.raw.size.height; - uWidth = uWidth ? uWidth : 1280; - uHeight = uHeight ? uHeight : 720; + uWidth = uWidth ? uWidth : 1280; + uHeight = uHeight ? uHeight : 720; libdecor_state *pState = libdecor_state_new( uWidth, uHeight ); libdecor_frame_commit( data->pFrame, pState, pConfiguration ); @@ -189,7 +221,7 @@ void commit_libdecor( struct data *data, libdecor_configuration *pConfiguration data->needs_decor_commit = false; } - + /* our data processing function is in general: * * struct pw_buffer *b; @@ -199,40 +231,40 @@ void commit_libdecor( struct data *data, libdecor_configuration *pConfiguration * * pw_stream_queue_buffer(stream, b); */ -static void -on_process(void *_data) +static void on_process( void *_data ) { - struct data *data = (struct data *)_data; - struct pw_stream *stream = data->stream; - struct pw_buffer *b; + struct data *data = ( struct data * )_data; + struct pw_stream *stream = data->stream; + struct pw_buffer *b; struct spa_buffer *buf; - + b = nullptr; /* dequeue and queue old buffers, use the last available * buffer */ - while (true) { + while ( true ) + { struct pw_buffer *t; - if ((t = pw_stream_dequeue_buffer(stream)) == nullptr) - break; - if (b) - pw_stream_queue_buffer(stream, b); + if ( ( t = pw_stream_dequeue_buffer( stream ) ) == nullptr ) break; + if ( b ) pw_stream_queue_buffer( stream, b ); b = t; } - if (b == nullptr) { - pw_log_warn("out of buffers: %m"); + if ( b == nullptr ) + { + pw_log_warn( "out of buffers: %m" ); return; } - + buf = b->buffer; - - pw_log_info("new buffer %p", buf); - - handle_events(data); - zwp_linux_buffer_params_v1 *pBufferParams = zwp_linux_dmabuf_v1_create_params( data->pLinuxDmabuf ); + pw_log_info( "new buffer %p", buf ); + + handle_events( data ); + + zwp_linux_buffer_params_v1 *pBufferParams = + zwp_linux_dmabuf_v1_create_params( data->pLinuxDmabuf ); if ( !pBufferParams ) { - pw_stream_queue_buffer(stream, b); + pw_stream_queue_buffer( stream, b ); return; } @@ -240,16 +272,16 @@ on_process(void *_data) { zwp_linux_buffer_params_v1_add( pBufferParams, - buf->datas[i].fd, + buf->datas[ i ].fd, i, - buf->datas[i].chunk->offset, - buf->datas[i].chunk->stride, + buf->datas[ i ].chunk->offset, + buf->datas[ i ].chunk->stride, data->format.info.raw.modifier >> 32, - data->format.info.raw.modifier & 0xffffffff); + data->format.info.raw.modifier & 0xffffffff ); } - uint32_t uDrmFormat = spa_format_to_drm(data->format.info.raw.format); - + uint32_t uDrmFormat = spa_format_to_drm( data->format.info.raw.format ); + wl_buffer *pImportedBuffer = zwp_linux_buffer_params_v1_create_immed( pBufferParams, data->format.info.raw.size.width, @@ -261,23 +293,23 @@ on_process(void *_data) struct StreamBuffer { - struct data *pData = nullptr; - wl_buffer *pWaylandBuffer = nullptr; - pw_buffer *pPipewireBuffer = nullptr; + struct data *pData = nullptr; + wl_buffer *pWaylandBuffer = nullptr; + pw_buffer *pPipewireBuffer = nullptr; }; - StreamBuffer *pStreamBuffer = new StreamBuffer - { - .pData = data, - .pWaylandBuffer = pImportedBuffer, + StreamBuffer *pStreamBuffer = new StreamBuffer{ + .pData = data, + .pWaylandBuffer = pImportedBuffer, .pPipewireBuffer = b, }; - static constexpr wl_buffer_listener s_BufferListener = - { - .release = []( void *pData, wl_buffer *pBuffer ) + static constexpr wl_buffer_listener s_BufferListener = { + .release = + []( void *pData, wl_buffer *pBuffer ) { StreamBuffer *pStreamBuffer = ( StreamBuffer * )pData; - pw_stream_queue_buffer( pStreamBuffer->pData->stream, pStreamBuffer->pPipewireBuffer ); + pw_stream_queue_buffer( + pStreamBuffer->pData->stream, pStreamBuffer->pPipewireBuffer ); wl_buffer_destroy( pStreamBuffer->pWaylandBuffer ); delete pStreamBuffer; }, @@ -288,32 +320,35 @@ on_process(void *_data) wl_surface_damage( data->pSurface, 0, 0, INT32_MAX, INT32_MAX ); wl_surface_set_buffer_scale( data->pSurface, 1 ); - if (data->needs_decor_commit) - commit_libdecor( data, nullptr ); + if ( data->needs_decor_commit ) commit_libdecor( data, nullptr ); wl_surface_commit( data->pSurface ); wl_display_flush( data->pDisplay ); } - -static void on_stream_state_changed(void *_data, enum pw_stream_state old, - enum pw_stream_state state, const char *error) + +static void on_stream_state_changed( + void *_data, + enum pw_stream_state old, + enum pw_stream_state state, + const char *error ) { - struct data *data = (struct data *)_data; - s_StreamLog.debugf( "stream state: \"%s\"", pw_stream_state_as_string(state) ); - if ( error ) - s_StreamLog.errorf( "error: \"%s\"", error ); - switch (state) { - case PW_STREAM_STATE_UNCONNECTED: - pw_main_loop_quit(data->loop); - break; - case PW_STREAM_STATE_PAUSED: - break; - case PW_STREAM_STATE_STREAMING: - default: - break; + struct data *data = ( struct data * )_data; + s_StreamLog.debugf( + "stream state: \"%s\"", pw_stream_state_as_string( state ) ); + if ( error ) s_StreamLog.errorf( "error: \"%s\"", error ); + switch ( state ) + { + case PW_STREAM_STATE_UNCONNECTED: + pw_main_loop_quit( data->loop ); + break; + case PW_STREAM_STATE_PAUSED: + break; + case PW_STREAM_STATE_STREAMING: + default: + break; } } - + /* Be notified when the stream param changes. We're only looking at the * format changes. * @@ -326,120 +361,144 @@ static void on_stream_state_changed(void *_data, enum pw_stream_state old, * that we would like on our buffer, the size, alignment, etc. */ static void -on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) +on_stream_param_changed( void *_data, uint32_t id, const struct spa_pod *param ) { - struct data *data = (struct data *)_data; - struct pw_stream *stream = data->stream; - uint8_t params_buffer[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); - const struct spa_pod *params[1]; - + struct data *data = ( struct data * )_data; + struct pw_stream *stream = data->stream; + uint8_t params_buffer[ 1024 ]; + struct spa_pod_builder b = + SPA_POD_BUILDER_INIT( params_buffer, sizeof( params_buffer ) ); + const struct spa_pod *params[ 1 ]; + /* nullptr means to clear the format */ - if (param == nullptr || id != SPA_PARAM_Format) - return; - + if ( param == nullptr || id != SPA_PARAM_Format ) return; + s_StreamLog.debugf( "got format:" ); - spa_debugc_format(&s_SpaDebugContext, 2, nullptr, param); - - if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) + spa_debugc_format( &s_SpaDebugContext, 2, nullptr, param ); + + if ( spa_format_parse( + param, &data->format.media_type, &data->format.media_subtype ) < + 0 ) return; - - if (data->format.media_type != SPA_MEDIA_TYPE_video || - data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) + + if ( data->format.media_type != SPA_MEDIA_TYPE_video || + data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw ) return; - + /* call a helper function to parse the format for us. */ - spa_format_video_raw_parse(param, &data->format.info.raw); + spa_format_video_raw_parse( param, &data->format.info.raw ); data->size = data->format.info.raw.size; - - uint32_t drm_format = spa_format_to_drm(data->format.info.raw.format); - if (drm_format == DRM_FORMAT_INVALID) { - pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); + + uint32_t drm_format = spa_format_to_drm( data->format.info.raw.format ); + if ( drm_format == DRM_FORMAT_INVALID ) + { + pw_stream_set_error( stream, -EINVAL, "unknown pixel format" ); return; } - if (data->size.width == 0 || data->size.height == 0) { - pw_stream_set_error(stream, -EINVAL, "invalid size"); + if ( data->size.width == 0 || data->size.height == 0 ) + { + pw_stream_set_error( stream, -EINVAL, "invalid size" ); return; } data->stride = SPA_ROUND_UP_N( data->size.width * 4, 4 ); - + /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ - params[0] = (const struct spa_pod *) spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), - SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stride * data->size.height ), + SPA_PARAM_BUFFERS_stride, + SPA_POD_Int( data->stride ), + SPA_PARAM_BUFFERS_dataType, + SPA_POD_CHOICE_FLAGS_Int(( 1 << SPA_DATA_DmaBuf )) ); + /* we are done */ - pw_stream_update_params(stream, params, 1); + pw_stream_update_params( stream, params, 1 ); } - + /* these are the stream events we listen for */ static const struct pw_stream_events stream_events = { - .version = PW_VERSION_STREAM_EVENTS, + .version = PW_VERSION_STREAM_EVENTS, .state_changed = on_stream_state_changed, .param_changed = on_stream_param_changed, - .process = on_process, + .process = on_process, }; - -static int build_formats(struct data *data, struct spa_pod_builder *b, const struct spa_pod **params) + +static int build_formats( + struct data *data, + struct spa_pod_builder *b, + const struct spa_pod **params ) { int n_params = 0; - - if (data->m_FormatModifiers.contains(DRM_FORMAT_XRGB8888)) - params[n_params++] = build_format( data, b, SPA_VIDEO_FORMAT_BGRx, data->m_FormatModifiers[DRM_FORMAT_XRGB8888].data(), uint32_t( data->m_FormatModifiers[DRM_FORMAT_XRGB8888].size() ) ); - params[n_params++] = build_format( data, b, SPA_VIDEO_FORMAT_BGRx, nullptr, 0 ); - - for (int i=0; i < n_params; i++) - spa_debugc_format(&s_SpaDebugContext, 2, NULL, params[i]); + + if ( data->m_FormatModifiers.contains( DRM_FORMAT_XRGB8888 ) ) + params[ n_params++ ] = build_format( + data, + b, + SPA_VIDEO_FORMAT_BGRx, + data->m_FormatModifiers[ DRM_FORMAT_XRGB8888 ].data( ), + uint32_t( + data->m_FormatModifiers[ DRM_FORMAT_XRGB8888 ].size( ) ) ); + params[ n_params++ ] = + build_format( data, b, SPA_VIDEO_FORMAT_BGRx, nullptr, 0 ); + + for ( int i = 0; i < n_params; i++ ) + spa_debugc_format( &s_SpaDebugContext, 2, NULL, params[ i ] ); return n_params; } - -static void reneg_format(void *_data, uint64_t expiration) + +static void reneg_format( void *_data, uint64_t expiration ) { - struct data *data = (struct data*) _data; - uint8_t buffer[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - const struct spa_pod *params[2]; - uint32_t n_params; - - if (data->format.info.raw.format == 0) - return; - + struct data *data = ( struct data * )_data; + uint8_t buffer[ 1024 ]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT( buffer, sizeof( buffer ) ); + const struct spa_pod *params[ 2 ]; + uint32_t n_params; + + if ( data->format.info.raw.format == 0 ) return; + s_StreamLog.debugf( "renegotiate formats:" ); - n_params = build_formats(data, &b, params); - - pw_stream_update_params(data->stream, params, n_params); + n_params = build_formats( data, &b, params ); + + pw_stream_update_params( data->stream, params, n_params ); } - -static void do_quit(void *userdata, int signal_number) + +static void do_quit( void *userdata, int signal_number ) { - struct data *data = (struct data *)userdata; - pw_main_loop_quit(data->loop); + struct data *data = ( struct data * )userdata; + pw_main_loop_quit( data->loop ); } - -int main(int argc, char *argv[]) + +int main( int argc, char *argv[] ) { - struct data data = { 0, }; - const struct spa_pod *params[2]; - uint8_t buffer[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - struct pw_properties *props; - int res, n_params; - - pw_init(&argc, &argv); - + struct data data = { + 0, + }; + const struct spa_pod *params[ 2 ]; + uint8_t buffer[ 1024 ]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT( buffer, sizeof( buffer ) ); + struct pw_properties *props; + int res, n_params; + + pw_init( &argc, &argv ); + /* create a main loop */ - data.loop = pw_main_loop_new(nullptr); - - pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); - pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); - + data.loop = pw_main_loop_new( nullptr ); + + pw_loop_add_signal( + pw_main_loop_get_loop( data.loop ), SIGINT, do_quit, &data ); + pw_loop_add_signal( + pw_main_loop_get_loop( data.loop ), SIGTERM, do_quit, &data ); + /* create a simple stream, the simple stream manages to core and remote * objects for you if you don't need to deal with them * @@ -451,107 +510,126 @@ int main(int argc, char *argv[]) * you need to listen to is the process event where you need to consume * the data provided to you. */ - props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - nullptr), - data.appid = argc > 1 ? atoi(argv[1]) : 0; - data.path = argc > 2 ? argv[2] : "gamescope"; - if (data.path) - /* Set stream target if given on command line */ - pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); - + props = pw_properties_new( + PW_KEY_MEDIA_TYPE, + "Video", + PW_KEY_MEDIA_CATEGORY, + "Capture", + PW_KEY_MEDIA_ROLE, + "Camera", + nullptr ), + data.appid = argc > 1 ? atoi( argv[ 1 ] ) : 0; + data.path = argc > 2 ? argv[ 2 ] : "gamescope"; + if ( data.path ) /* Set stream target if given on command line */ + pw_properties_set( props, PW_KEY_TARGET_OBJECT, data.path ); + data.stream = pw_stream_new_simple( - pw_main_loop_get_loop(data.loop), - "video-play-fixate", - props, - &stream_events, - &data); - + pw_main_loop_get_loop( data.loop ), + "video-play-fixate", + props, + &stream_events, + &data ); + // - if ( !( data.pDisplay = wl_display_connect( nullptr ) ) ) - return -1; + if ( !( data.pDisplay = wl_display_connect( nullptr ) ) ) return -1; wl_registry *pRegistry; - if ( !( pRegistry = wl_display_get_registry( data.pDisplay ) ) ) - return -1; - - static constexpr wl_registry_listener s_RegistryListener = - { - .global = [] ( void *pUserData, wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ) + if ( !( pRegistry = wl_display_get_registry( data.pDisplay ) ) ) return -1; + + static constexpr wl_registry_listener s_RegistryListener = { + .global = + []( void *pUserData, + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ) { - struct data *pData = (struct data *)pUserData; - if ( !strcmp( pInterface, wl_compositor_interface.name ) && uVersion >= 4u ) + struct data *pData = ( struct data * )pUserData; + if ( !strcmp( pInterface, wl_compositor_interface.name ) && + uVersion >= 4u ) { - pData->pCompositor = (wl_compositor *)wl_registry_bind( pRegistry, uName, &wl_compositor_interface, 4u ); + pData->pCompositor = ( wl_compositor * )wl_registry_bind( + pRegistry, uName, &wl_compositor_interface, 4u ); } - else if ( !strcmp( pInterface, zwp_linux_dmabuf_v1_interface.name ) && uVersion >= 3 ) + else if ( + !strcmp( pInterface, zwp_linux_dmabuf_v1_interface.name ) && + uVersion >= 3 ) { - pData->pLinuxDmabuf = (zwp_linux_dmabuf_v1 *)wl_registry_bind( pRegistry, uName, &zwp_linux_dmabuf_v1_interface, 3u ); - static constexpr zwp_linux_dmabuf_v1_listener s_Listener = - { - .format = WAYLAND_NULL(), // Formats are also advertised by the modifier event, ignore them here. - .modifier = [] ( void *pUserData, zwp_linux_dmabuf_v1 *pDmabuf, uint32_t uFormat, uint32_t uModifierHi, uint32_t uModifierLo ) + pData->pLinuxDmabuf = ( zwp_linux_dmabuf_v1 * )wl_registry_bind( + pRegistry, uName, &zwp_linux_dmabuf_v1_interface, 3u ); + static constexpr zwp_linux_dmabuf_v1_listener s_Listener = { + .format = + WAYLAND_NULL( ), // Formats are also advertised by the + // modifier event, ignore them here. + .modifier = + []( void *pUserData, + zwp_linux_dmabuf_v1 *pDmabuf, + uint32_t uFormat, + uint32_t uModifierHi, + uint32_t uModifierLo ) { - uint64_t ulModifier = ( uint64_t( uModifierHi ) << 32 ) | uModifierLo; + uint64_t ulModifier = + ( uint64_t( uModifierHi ) << 32 ) | uModifierLo; - struct data *pData = (struct data *)pUserData; + struct data *pData = ( struct data * )pUserData; if ( ulModifier != DRM_FORMAT_MOD_INVALID ) - pData->m_FormatModifiers[ uFormat ].emplace_back( ulModifier ); + pData->m_FormatModifiers[ uFormat ].emplace_back( + ulModifier ); }, }; - zwp_linux_dmabuf_v1_add_listener( pData->pLinuxDmabuf, &s_Listener, pData ); + zwp_linux_dmabuf_v1_add_listener( + pData->pLinuxDmabuf, &s_Listener, pData ); } }, - .global_remove = WAYLAND_NULL(), + .global_remove = WAYLAND_NULL( ), }; - wl_registry_add_listener( pRegistry, &s_RegistryListener, (void *)&data ); + wl_registry_add_listener( pRegistry, &s_RegistryListener, ( void * )&data ); wl_display_roundtrip( data.pDisplay ); - if ( !data.pCompositor || !data.pLinuxDmabuf ) - return -1; + if ( !data.pCompositor || !data.pLinuxDmabuf ) return -1; - // Grab stuff from any extra bindings/listeners we set up, eg. format/modifiers. + // Grab stuff from any extra bindings/listeners we set up, eg. + // format/modifiers. wl_display_roundtrip( data.pDisplay ); wl_registry_destroy( pRegistry ); pRegistry = nullptr; - static libdecor_interface s_LibDecorInterface = - { - .error = []( libdecor *pContext, libdecor_error eError, const char *pMessage ) - { - s_StreamLog.errorf( "libdecor: %s", pMessage ); - }, + static libdecor_interface s_LibDecorInterface = { + .error = []( libdecor *pContext, + libdecor_error eError, + const char *pMessage ) + { s_StreamLog.errorf( "libdecor: %s", pMessage ); }, }; data.pDecor = libdecor_new( data.pDisplay, &s_LibDecorInterface ); - if ( !data.pDecor ) - return -1; + if ( !data.pDecor ) return -1; - static libdecor_frame_interface s_LibDecorFrameInterface - { - .configure = []( libdecor_frame *pFrame, libdecor_configuration *pConfiguration, void *pUserData ) + static libdecor_frame_interface s_LibDecorFrameInterface{ + .configure = + []( libdecor_frame *pFrame, + libdecor_configuration *pConfiguration, + void *pUserData ) { - struct data *pData = (struct data *)pUserData; + struct data *pData = ( struct data * )pUserData; commit_libdecor( pData, pConfiguration ); }, - .close = []( libdecor_frame *pFrame, void *pUserData ) - { - raise( SIGTERM ); - }, - .commit = []( libdecor_frame *pFrame, void *pUserData ) + .close = []( libdecor_frame *pFrame, void *pUserData ) + { raise( SIGTERM ); }, + .commit = + []( libdecor_frame *pFrame, void *pUserData ) { - struct data *pData = (struct data *)pUserData; + struct data *pData = ( struct data * )pUserData; pData->needs_decor_commit = true; }, - .dismiss_popup = []( libdecor_frame *pFrame, const char *pSeatName, void *pUserData ) - { - }, + .dismiss_popup = []( libdecor_frame *pFrame, + const char *pSeatName, + void *pUserData ) {}, }; data.pSurface = wl_compositor_create_surface( data.pCompositor ); - data.pFrame = libdecor_decorate( data.pDecor, data.pSurface, &s_LibDecorFrameInterface, &data ); + data.pFrame = libdecor_decorate( + data.pDecor, data.pSurface, &s_LibDecorFrameInterface, &data ); libdecor_frame_set_title( data.pFrame, "Gamescope Pipewire Stream" ); libdecor_frame_set_app_id( data.pFrame, "gamescopestream" ); libdecor_frame_map( data.pFrame ); @@ -559,38 +637,45 @@ int main(int argc, char *argv[]) wl_display_roundtrip( data.pDisplay ); // - + /* build the extra parameters to connect with. To connect, we can provide * a list of supported formats. We use a builder that writes the param * object to the stack. */ s_StreamLog.debugf( "supported formats:" ); - n_params = build_formats(&data, &b, params); - + n_params = build_formats( &data, &b, params ); + /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters */ - if ((res = pw_stream_connect(data.stream, - PW_DIRECTION_INPUT, - PW_ID_ANY, - pw_stream_flags( - PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ - PW_STREAM_FLAG_MAP_BUFFERS), /* mmap the buffer data for us */ - params, n_params)) /* extra parameters, see above */ < 0) { - s_StreamLog.errorf( "can't connect: %s\n", spa_strerror(res) ); + if ( ( res = pw_stream_connect( + data.stream, + PW_DIRECTION_INPUT, + PW_ID_ANY, + pw_stream_flags( + PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect + this stream */ + PW_STREAM_FLAG_MAP_BUFFERS ), /* mmap the buffer data for us + */ + params, + n_params ) ) /* extra parameters, see above */ + < 0 ) + { + s_StreamLog.errorf( "can't connect: %s\n", spa_strerror( res ) ); return -1; } - - data.reneg = pw_loop_add_event(pw_main_loop_get_loop(data.loop), reneg_format, &data); - + + data.reneg = pw_loop_add_event( + pw_main_loop_get_loop( data.loop ), reneg_format, &data ); + /* do things until we quit the mainloop */ - pw_main_loop_run(data.loop); - - pw_stream_destroy(data.stream); - pw_main_loop_destroy(data.loop); - + pw_main_loop_run( data.loop ); + + pw_stream_destroy( data.stream ); + pw_main_loop_destroy( data.loop ); + // TODO: cleanup wayland - pw_deinit(); - + pw_deinit( ); + return 0; -} \ No newline at end of file +} diff --git a/src/Backends/DRMBackend.cpp b/src/Backends/DRMBackend.cpp index 96e1a3812c..867513ba08 100644 --- a/src/Backends/DRMBackend.cpp +++ b/src/Backends/DRMBackend.cpp @@ -2,13 +2,13 @@ #include "Script/Script.h" -#include -#include -#include -#include #include -#include +#include #include +#include +#include +#include +#include #include #include @@ -27,9 +27,10 @@ #include #include +#include +#include "Utils/Defer.h" #include "backend.h" #include "color_helpers.h" -#include "Utils/Defer.h" #include "drm_include.h" #include "edid.h" #include "gamescope_shared.h" @@ -37,19 +38,18 @@ #include "log.hpp" #include "main.hpp" #include "modegen.hpp" +#include "refresh_rate.h" #include "rendervulkan.hpp" #include "steamcompmgr.hpp" #include "vblankmanager.hpp" #include "wlserver.hpp" -#include "refresh_rate.h" -#include -#include "wlr_begin.hpp" #include #include -#include "libdisplay-info/info.h" -#include "libdisplay-info/edid.h" #include "libdisplay-info/cta.h" +#include "libdisplay-info/edid.h" +#include "libdisplay-info/info.h" +#include "wlr_begin.hpp" #include "wlr_end.hpp" #include "gamescope-control-protocol.h" @@ -59,3983 +59,4569 @@ static constexpr bool k_bUseCursorPlane = false; extern int g_nPreferredOutputWidth; extern int g_nPreferredOutputHeight; -gamescope::ConVar cv_drm_single_plane_optimizations( "drm_single_plane_optimizations", true, "Whether or not to enable optimizations for single plane usage." ); - -gamescope::ConVar cv_drm_debug_disable_shaper_and_3dlut( "drm_debug_disable_shaper_and_3dlut", false, "Shaper + 3DLUT chicken bit. (Force disable/DEFAULT, no logic change)" ); -gamescope::ConVar cv_drm_debug_disable_degamma_tf( "drm_debug_disable_degamma_tf", false, "Degamma chicken bit. (Forces DEGAMMA_TF to DEFAULT, does not affect other logic)" ); -gamescope::ConVar cv_drm_debug_disable_regamma_tf( "drm_debug_disable_regamma_tf", false, "Regamma chicken bit. (Forces REGAMMA_TF to DEFAULT, does not affect other logic)" ); -gamescope::ConVar cv_drm_debug_disable_output_tf( "drm_debug_disable_output_tf", false, "Force default (identity) output TF, affects other logic. Not a property directly." ); -gamescope::ConVar cv_drm_debug_disable_blend_tf( "drm_debug_disable_blend_tf", false, "Blending chicken bit. (Forces BLEND_TF to DEFAULT, does not affect other logic)" ); -gamescope::ConVar cv_drm_debug_disable_ctm( "drm_debug_disable_ctm", false, "CTM chicken bit. (Forces CTM off, does not affect other logic)" ); -gamescope::ConVar cv_drm_debug_disable_color_encoding( "drm_debug_disable_color_encoding", false, "YUV Color Encoding chicken bit. (Forces COLOR_ENCODING to DEFAULT, does not affect other logic)" ); -gamescope::ConVar cv_drm_debug_disable_color_range( "drm_debug_disable_color_range", false, "YUV Color Range chicken bit. (Forces COLOR_RANGE to DEFAULT, does not affect other logic)" ); -gamescope::ConVar cv_drm_debug_disable_explicit_sync( "drm_debug_disable_explicit_sync", false, "Force disable explicit sync on the DRM backend." ); -gamescope::ConVar cv_drm_debug_disable_in_fence_fd( "drm_debug_disable_in_fence_fd", false, "Force disable IN_FENCE_FD being set to avoid over-synchronization on the DRM backend." ); - -gamescope::ConVar cv_drm_allow_dynamic_modes_for_external_display( "drm_allow_dynamic_modes_for_external_display", false, "Allow dynamic mode/refresh rate switching for external displays." ); +gamescope::ConVar cv_drm_single_plane_optimizations( + "drm_single_plane_optimizations", + true, + "Whether or not to enable optimizations for single plane usage." ); + +gamescope::ConVar cv_drm_debug_disable_shaper_and_3dlut( + "drm_debug_disable_shaper_and_3dlut", + false, + "Shaper + 3DLUT chicken bit. (Force disable/DEFAULT, no logic change)" ); +gamescope::ConVar cv_drm_debug_disable_degamma_tf( + "drm_debug_disable_degamma_tf", + false, + "Degamma chicken bit. (Forces DEGAMMA_TF to DEFAULT, does not affect other " + "logic)" ); +gamescope::ConVar cv_drm_debug_disable_regamma_tf( + "drm_debug_disable_regamma_tf", + false, + "Regamma chicken bit. (Forces REGAMMA_TF to DEFAULT, does not affect other " + "logic)" ); +gamescope::ConVar cv_drm_debug_disable_output_tf( + "drm_debug_disable_output_tf", + false, + "Force default (identity) output TF, affects other logic. Not a property " + "directly." ); +gamescope::ConVar cv_drm_debug_disable_blend_tf( + "drm_debug_disable_blend_tf", + false, + "Blending chicken bit. (Forces BLEND_TF to DEFAULT, does not affect other " + "logic)" ); +gamescope::ConVar cv_drm_debug_disable_ctm( + "drm_debug_disable_ctm", + false, + "CTM chicken bit. (Forces CTM off, does not affect other logic)" ); +gamescope::ConVar cv_drm_debug_disable_color_encoding( + "drm_debug_disable_color_encoding", + false, + "YUV Color Encoding chicken bit. (Forces COLOR_ENCODING to DEFAULT, does " + "not affect other logic)" ); +gamescope::ConVar cv_drm_debug_disable_color_range( + "drm_debug_disable_color_range", + false, + "YUV Color Range chicken bit. (Forces COLOR_RANGE to DEFAULT, does not " + "affect other logic)" ); +gamescope::ConVar cv_drm_debug_disable_explicit_sync( + "drm_debug_disable_explicit_sync", + false, + "Force disable explicit sync on the DRM backend." ); +gamescope::ConVar cv_drm_debug_disable_in_fence_fd( + "drm_debug_disable_in_fence_fd", + false, + "Force disable IN_FENCE_FD being set to avoid over-synchronization on the " + "DRM backend." ); + +gamescope::ConVar cv_drm_allow_dynamic_modes_for_external_display( + "drm_allow_dynamic_modes_for_external_display", + false, + "Allow dynamic mode/refresh rate switching for external displays." ); int HackyDRMPresent( const FrameInfo_t *pFrameInfo, bool bAsync ); enum GamescopeBroadcastRGBMode_t : uint32_t { - GAMESCOPE_BROADCAST_RGB_MODE_AUTOMATIC = 0, - GAMESCOPE_BROADCAST_RGB_MODE_FULL = 1, - GAMESCOPE_BROADCAST_RGB_MODE_LIMITED = 2, + GAMESCOPE_BROADCAST_RGB_MODE_AUTOMATIC = 0, + GAMESCOPE_BROADCAST_RGB_MODE_FULL = 1, + GAMESCOPE_BROADCAST_RGB_MODE_LIMITED = 2, }; -struct saved_mode { - int width; - int height; - int refresh; - GamescopeBroadcastRGBMode_t broadcast_mode; +struct saved_mode +{ + int width; + int height; + int refresh; + GamescopeBroadcastRGBMode_t broadcast_mode; }; -gamescope::ConVar cv_drm_ignore_internal_connectors( "drm_ignore_internal_connectors", false, "Disable internal displays for good, for debugging." ); +gamescope::ConVar cv_drm_ignore_internal_connectors( + "drm_ignore_internal_connectors", + false, + "Disable internal displays for good, for debugging." ); namespace gamescope { - class CDRMPlane; - class CDRMCRTC; - class CDRMConnector; -} + class CDRMPlane; + class CDRMCRTC; + class CDRMConnector; +} // namespace gamescope -struct drm_t { - bool bUseLiftoff; +struct drm_t +{ + bool bUseLiftoff; - int fd = -1; + int fd = -1; - int preferred_width, preferred_height, preferred_refresh; + int preferred_width, preferred_height, preferred_refresh; - uint64_t cursor_width, cursor_height; - bool allow_modifiers; - struct wlr_drm_format_set formats; + uint64_t cursor_width, cursor_height; + bool allow_modifiers; + struct wlr_drm_format_set formats; - std::vector< std::unique_ptr< gamescope::CDRMPlane > > planes; - std::vector< std::unique_ptr< gamescope::CDRMCRTC > > crtcs; - std::unordered_map< uint32_t, gamescope::CDRMConnector > connectors; + std::vector> planes; + std::vector> crtcs; + std::unordered_map connectors; - gamescope::CDRMPlane *pPrimaryPlane; - gamescope::CDRMCRTC *pCRTC; - gamescope::CDRMConnector *pConnector; + gamescope::CDRMPlane *pPrimaryPlane; + gamescope::CDRMCRTC *pCRTC; + gamescope::CDRMConnector *pConnector; - struct wlr_drm_format_set primary_formats; + struct wlr_drm_format_set primary_formats; - drmModeAtomicReq *req; - uint32_t flags; + drmModeAtomicReq *req; + uint32_t flags; - struct liftoff_device *lo_device; - struct liftoff_output *lo_output; - struct liftoff_layer *lo_layers[ k_nMaxLayers ]; + struct liftoff_device *lo_device; + struct liftoff_output *lo_output; + struct liftoff_layer *lo_layers[ k_nMaxLayers ]; - std::shared_ptr sdr_static_metadata; + std::shared_ptr sdr_static_metadata; - struct drm_state_t { - std::shared_ptr mode_id; - uint32_t color_mgmt_serial; - std::shared_ptr lut3d_id[ EOTF_Count ]; - std::shared_ptr shaperlut_id[ EOTF_Count ]; - amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } current, pending; + struct drm_state_t + { + std::shared_ptr mode_id; + uint32_t color_mgmt_serial; + std::shared_ptr lut3d_id[ EOTF_Count ]; + std::shared_ptr shaperlut_id[ EOTF_Count ]; + amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } current, pending; - // FBs in the atomic request, but not yet submitted to KMS - // Accessed only on req thread - std::vector> m_FbIdsInRequest; + // FBs in the atomic request, but not yet submitted to KMS + // Accessed only on req thread + std::vector> m_FbIdsInRequest; - // FBs currently queued to go on screen. - // May be accessed by page flip handler thread and req thread, thus mutex. - std::mutex m_QueuedFbIdsMutex; - std::vector> m_QueuedFbIds; - // FBs currently on screen. - // Accessed only on page flip handler thread. - std::mutex m_mutVisibleFbIds; - std::vector> m_VisibleFbIds; + // FBs currently queued to go on screen. + // May be accessed by page flip handler thread and req thread, thus mutex. + std::mutex m_QueuedFbIdsMutex; + std::vector> m_QueuedFbIds; + // FBs currently on screen. + // Accessed only on page flip handler thread. + std::mutex m_mutVisibleFbIds; + std::vector> m_VisibleFbIds; - std::atomic < uint32_t > uPendingFlipCount = { 0 }; + std::atomic uPendingFlipCount = { 0 }; - std::atomic < bool > paused = { false }; - std::atomic < int > out_of_date = { false }; - std::atomic < bool > needs_modeset = { false }; + std::atomic paused = { false }; + std::atomic out_of_date = { false }; + std::atomic needs_modeset = { false }; - std::unordered_map< std::string, int > connector_priorities; + std::unordered_map connector_priorities; - char *device_name = nullptr; + char *device_name = nullptr; }; void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); - using namespace std::literals; struct drm_t g_DRM = {}; namespace gamescope { - class CDRMBackend; - - std::tuple GetKernelVersion() - { - utsname name; - if ( uname( &name ) != 0 ) - return std::make_tuple( 0, 0, 0 ); - - std::vector szVersionParts = Split( name.release, "." ); - - uint32_t uVersion[3] = { 0 }; - for ( size_t i = 0; i < szVersionParts.size() && i < 3; i++ ) - { - auto oPart = Parse( szVersionParts[i] ); - if ( !oPart ) - break; - - uVersion[i] = *oPart; - } - - return std::make_tuple( uVersion[0], uVersion[1], uVersion[2] ); - } - - // Get a DRM mode in mHz - // Taken from wlroots, but we can't access it as we don't - // use the drm backend. - static int32_t GetModeRefresh(const drmModeModeInfo *mode) - { - int32_t nRefresh = (mode->clock * 1'000'000ll / mode->htotal + mode->vtotal / 2) / mode->vtotal; - - if (mode->flags & DRM_MODE_FLAG_INTERLACE) - nRefresh *= 2; - - if (mode->flags & DRM_MODE_FLAG_DBLSCAN) - nRefresh /= 2; - - if (mode->vscan > 1) - nRefresh /= mode->vscan; - - return nRefresh; - } - - template - using CAutoDeletePtr = std::unique_ptr; - - //////////////////////////////////////// - // DRM Object Wrappers + State Trackers - //////////////////////////////////////// - struct DRMObjectRawProperty - { - uint32_t uPropertyId = 0ul; - uint64_t ulValue = 0ul; - }; - using DRMObjectRawProperties = std::unordered_map; - - class CDRMAtomicObject - { - public: - CDRMAtomicObject( uint32_t ulObjectId ); - uint32_t GetObjectId() const { return m_ulObjectId; } - - // No copy or move constructors. - CDRMAtomicObject( const CDRMAtomicObject& ) = delete; - CDRMAtomicObject& operator=( const CDRMAtomicObject& ) = delete; - - CDRMAtomicObject( CDRMAtomicObject&& ) = delete; - CDRMAtomicObject& operator=( CDRMAtomicObject&& ) = delete; - protected: - uint32_t m_ulObjectId = 0ul; - }; - - template < uint32_t DRMObjectType > - class CDRMAtomicTypedObject : public CDRMAtomicObject - { - public: - CDRMAtomicTypedObject( uint32_t ulObjectId ); - protected: - std::optional GetRawProperties(); - }; - - class CDRMAtomicProperty - { - public: - CDRMAtomicProperty( CDRMAtomicObject *pObject, DRMObjectRawProperty rawProperty ); - - static std::optional Instantiate( const char *pszName, CDRMAtomicObject *pObject, const DRMObjectRawProperties& rawProperties ); - - uint64_t GetPendingValue() const { return m_ulPendingValue; } - uint64_t GetCurrentValue() const { return m_ulCurrentValue; } - uint64_t GetInitialValue() const { return m_ulInitialValue; } - int SetPendingValue( drmModeAtomicReq *pRequest, uint64_t ulValue, bool bForce ); - - void OnCommit(); - void Rollback(); - private: - CDRMAtomicObject *m_pObject = nullptr; - uint32_t m_uPropertyId = 0u; - - uint64_t m_ulPendingValue = 0ul; - uint64_t m_ulCurrentValue = 0ul; - uint64_t m_ulInitialValue = 0ul; - }; - - class CDRMPlane final : public CDRMAtomicTypedObject - { - public: - // Takes ownership of pPlane. - CDRMPlane( drmModePlane *pPlane ); - - void RefreshState(); - - drmModePlane *GetModePlane() const { return m_pPlane.get(); } - - struct PlaneProperties - { - std::optional *begin() { return &FB_ID; } - std::optional *end() { return &DUMMY_END; } - - std::optional type; // Immutable - std::optional IN_FORMATS; // Immutable - - std::optional FB_ID; - std::optional IN_FENCE_FD; - std::optional CRTC_ID; - std::optional SRC_X; - std::optional SRC_Y; - std::optional SRC_W; - std::optional SRC_H; - std::optional CRTC_X; - std::optional CRTC_Y; - std::optional CRTC_W; - std::optional CRTC_H; - std::optional zpos; - std::optional alpha; - std::optional rotation; - std::optional COLOR_ENCODING; - std::optional COLOR_RANGE; - std::optional AMD_PLANE_DEGAMMA_TF; - std::optional AMD_PLANE_DEGAMMA_LUT; - std::optional AMD_PLANE_CTM; - std::optional AMD_PLANE_HDR_MULT; - std::optional AMD_PLANE_SHAPER_LUT; - std::optional AMD_PLANE_SHAPER_TF; - std::optional AMD_PLANE_LUT3D; - std::optional AMD_PLANE_BLEND_TF; - std::optional AMD_PLANE_BLEND_LUT; - std::optional DUMMY_END; - }; - PlaneProperties &GetProperties() { return m_Props; } - const PlaneProperties &GetProperties() const { return m_Props; } - private: - CAutoDeletePtr m_pPlane; - PlaneProperties m_Props; - }; - - class CDRMCRTC final : public CDRMAtomicTypedObject - { - public: - // Takes ownership of pCRTC. - CDRMCRTC( drmModeCrtc *pCRTC, uint32_t uCRTCMask ); - - void RefreshState(); - uint32_t GetCRTCMask() const { return m_uCRTCMask; } - - struct CRTCProperties - { - std::optional *begin() { return &ACTIVE; } - std::optional *end() { return &DUMMY_END; } - - std::optional ACTIVE; - std::optional MODE_ID; - std::optional GAMMA_LUT; - std::optional DEGAMMA_LUT; - std::optional CTM; - std::optional VRR_ENABLED; - std::optional OUT_FENCE_PTR; - std::optional AMD_CRTC_REGAMMA_TF; - std::optional DUMMY_END; - }; - CRTCProperties &GetProperties() { return m_Props; } - const CRTCProperties &GetProperties() const { return m_Props; } - private: - CAutoDeletePtr m_pCRTC; - uint32_t m_uCRTCMask = 0u; - CRTCProperties m_Props; - }; - - class CDRMConnector final : public CBaseBackendConnector, public CDRMAtomicTypedObject - { - public: - CDRMConnector( CDRMBackend *pBackend, drmModeConnector *pConnector ); - - void RefreshState(); - - struct ConnectorProperties - { - std::optional *begin() { return &CRTC_ID; } - std::optional *end() { return &DUMMY_END; } - - std::optional CRTC_ID; - std::optional Colorspace; - std::optional content_type; // "content type" with space! - std::optional panel_orientation; // "panel orientation" with space! - std::optional HDR_OUTPUT_METADATA; - std::optional vrr_capable; - std::optional EDID; - std::optional Broadcast_RGB; - std::optional DUMMY_END; - }; - ConnectorProperties &GetProperties() { return m_Props; } - const ConnectorProperties &GetProperties() const { return m_Props; } - - drmModeConnector *GetModeConnector() { return m_pConnector.get(); } - const char *GetName() const override { return m_Mutable.szName; } - const char *GetMake() const override { return m_Mutable.pszMake; } - const char *GetModel() const override { return m_Mutable.szModel; } - const char *GetDataString() const { return m_Mutable.szDataString; } - uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } - std::span GetValidDynamicRefreshRates() const override { return m_Mutable.ValidDynamicRefreshRates; } - const displaycolorimetry_t& GetDisplayColorimetry() const { return m_Mutable.DisplayColorimetry; } - - std::span GetRawEDID() const override { return std::span{ m_Mutable.EdidData.begin(), m_Mutable.EdidData.end() }; } - bool HandleEdidChange() - { - bool bChanged = m_Mutable.bEdidChanged; - m_Mutable.bEdidChanged = false; - return bChanged; - } - - bool SupportsHDR10() const - { - return !!GetProperties().Colorspace && !!GetProperties().HDR_OUTPUT_METADATA && GetHDRInfo().IsHDR10(); - } - - bool SupportsHDRG22() const - { - return GetHDRInfo().IsHDRG22(); - } - - ////////////////////////////////////// - // IBackendConnector implementation - ////////////////////////////////////// - - GamescopeScreenType GetScreenType() const override - { - if ( m_pConnector->connector_type == DRM_MODE_CONNECTOR_eDP || - m_pConnector->connector_type == DRM_MODE_CONNECTOR_LVDS || - m_pConnector->connector_type == DRM_MODE_CONNECTOR_DSI ) - return GAMESCOPE_SCREEN_TYPE_INTERNAL; - - return GAMESCOPE_SCREEN_TYPE_EXTERNAL; - } - - GamescopePanelOrientation GetCurrentOrientation() const override - { - return m_ChosenOrientation; - } - - bool SupportsHDR() const override - { - return SupportsHDR10() || SupportsHDRG22(); - } - - bool IsHDRActive() const override - { - if ( SupportsHDR10() ) - { - return GetProperties().Colorspace->GetCurrentValue() == DRM_MODE_COLORIMETRY_BT2020_RGB; - } - else if ( SupportsHDRG22() ) - { - return true; - } - - return false; - } - - const BackendConnectorHDRInfo &GetHDRInfo() const override { return m_Mutable.HDR; } - - virtual bool IsVRRActive() const override - { - if ( !g_DRM.pCRTC || !g_DRM.pCRTC->GetProperties().VRR_ENABLED ) - return false; - - return !!g_DRM.pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue(); - } - - virtual std::span GetModes() const override { return m_Mutable.BackendModes; } - - bool SupportsVRR() const override - { - return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); - } + class CDRMBackend; - void GetNativeColorimetry( - bool bHDR, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override - { - *displayColorimetry = GetDisplayColorimetry(); - *displayEOTF = EOTF_Gamma22; - - if ( bHDR && GetHDRInfo().IsHDR10() ) - { - // For HDR10 output, expected content colorspace != native colorspace. - *outputEncodingColorimetry = displaycolorimetry_2020; - *outputEncodingEOTF = GetHDRInfo().eOutputEncodingEOTF; - } - else - { - *outputEncodingColorimetry = GetDisplayColorimetry(); - *outputEncodingEOTF = EOTF_Gamma22; - } - } - - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; - - using DRMModeGenerator = std::function; - const DRMModeGenerator &GetModeGenerator() const - { - return m_Mutable.fnDynamicModeGenerator; - } - void UpdateEffectiveOrientation( const drmModeModeInfo *pMode ); - - private: - void ParseEDID(); - - - CDRMBackend *m_pBackend = nullptr; - CAutoDeletePtr m_pConnector; - - struct MutableConnectorState - { - int nDefaultRefresh = 0; - - uint32_t uPossibleCRTCMask = 0u; - char szName[32]{}; - char szMakePNP[4]{}; - char szModel[16]{}; - char szDataString[16]{}; - const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. - DRMModeGenerator fnDynamicModeGenerator; - std::vector ValidDynamicRefreshRates{}; - std::vector EdidData; // Raw, unmodified. - std::vector BackendModes; - - displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; - BackendConnectorHDRInfo HDR; - - bool bEdidChanged = false; - } m_Mutable; - - GamescopePanelOrientation m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_AUTO; - - ConnectorProperties m_Props; - }; - - class CDRMFb final : public CBaseBackendFb - { - public: - CDRMFb( uint32_t uFbId ); - ~CDRMFb(); - - uint32_t GetFbId() const { return m_uFbId; } - - private: - uint32_t m_uFbId = 0; - }; -} - -uint32_t g_nDRMFormat = DRM_FORMAT_INVALID; -uint32_t g_nDRMFormatOverlay = DRM_FORMAT_INVALID; // for partial composition, we may have more limited formats than base planes + alpha. -bool g_bRotated = false; -extern bool g_bDebugLayers; + std::tuple GetKernelVersion( ) + { + utsname name; + if ( uname( &name ) != 0 ) return std::make_tuple( 0, 0, 0 ); -struct DRMPresentCtx -{ - uint64_t ulPendingFlipCount = 0; -}; + std::vector szVersionParts = + Split( name.release, "." ); -extern gamescope::ConVar cv_composite_force; -extern bool g_bColorSliderInUse; -extern bool fadingOut; -extern std::string g_reshade_effect; + uint32_t uVersion[ 3 ] = { 0 }; + for ( size_t i = 0; i < szVersionParts.size( ) && i < 3; i++ ) + { + auto oPart = Parse( szVersionParts[ i ] ); + if ( !oPart ) break; -#ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP -#define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 -#endif + uVersion[ i ] = *oPart; + } -bool drm_update_color_mgmt(struct drm_t *drm); -bool drm_supports_color_mgmt(struct drm_t *drm); -bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ); + return std::make_tuple( uVersion[ 0 ], uVersion[ 1 ], uVersion[ 2 ] ); + } -struct drm_color_ctm2 { - /* - * Conversion matrix in S31.32 sign-magnitude - * (not two's complement!) format. - */ - __u64 matrix[12]; -}; + // Get a DRM mode in mHz + // Taken from wlroots, but we can't access it as we don't + // use the drm backend. + static int32_t GetModeRefresh( const drmModeModeInfo *mode ) + { + int32_t nRefresh = + ( mode->clock * 1'000'000ll / mode->htotal + mode->vtotal / 2 ) / + mode->vtotal; -bool g_bSupportsAsyncFlips = false; -bool g_bSupportsSyncObjs = false; + if ( mode->flags & DRM_MODE_FLAG_INTERLACE ) nRefresh *= 2; -extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; -extern GamescopePanelOrientation g_DesiredInternalOrientation; + if ( mode->flags & DRM_MODE_FLAG_DBLSCAN ) nRefresh /= 2; -extern bool g_bForceDisableColorMgmt; + if ( mode->vscan > 1 ) nRefresh /= mode->vscan; -static LogScope drm_log( "drm" ); -static LogScope liftoff_log_scope( "liftoff" ); + return nRefresh; + } -static std::unordered_map< std::string, std::string > pnps = {}; + template + using CAutoDeletePtr = std::unique_ptr; + + //////////////////////////////////////// + // DRM Object Wrappers + State Trackers + //////////////////////////////////////// + struct DRMObjectRawProperty + { + uint32_t uPropertyId = 0ul; + uint64_t ulValue = 0ul; + }; + using DRMObjectRawProperties = + std::unordered_map; + + class CDRMAtomicObject + { + public: + CDRMAtomicObject( uint32_t ulObjectId ); + uint32_t GetObjectId( ) const { return m_ulObjectId; } + + // No copy or move constructors. + CDRMAtomicObject( const CDRMAtomicObject & ) = delete; + CDRMAtomicObject &operator=( const CDRMAtomicObject & ) = delete; + + CDRMAtomicObject( CDRMAtomicObject && ) = delete; + CDRMAtomicObject &operator=( CDRMAtomicObject && ) = delete; + + protected: + uint32_t m_ulObjectId = 0ul; + }; + + template + class CDRMAtomicTypedObject : public CDRMAtomicObject + { + public: + CDRMAtomicTypedObject( uint32_t ulObjectId ); + + protected: + std::optional GetRawProperties( ); + }; + + class CDRMAtomicProperty + { + public: + CDRMAtomicProperty( + CDRMAtomicObject *pObject, DRMObjectRawProperty rawProperty ); + + static std::optional Instantiate( + const char *pszName, + CDRMAtomicObject *pObject, + const DRMObjectRawProperties &rawProperties ); + + uint64_t GetPendingValue( ) const { return m_ulPendingValue; } + uint64_t GetCurrentValue( ) const { return m_ulCurrentValue; } + uint64_t GetInitialValue( ) const { return m_ulInitialValue; } + int SetPendingValue( + drmModeAtomicReq *pRequest, uint64_t ulValue, bool bForce ); + + void OnCommit( ); + void Rollback( ); + + private: + CDRMAtomicObject *m_pObject = nullptr; + uint32_t m_uPropertyId = 0u; + + uint64_t m_ulPendingValue = 0ul; + uint64_t m_ulCurrentValue = 0ul; + uint64_t m_ulInitialValue = 0ul; + }; + + class CDRMPlane final : public CDRMAtomicTypedObject + { + public: + // Takes ownership of pPlane. + CDRMPlane( drmModePlane *pPlane ); + + void RefreshState( ); + + drmModePlane *GetModePlane( ) const { return m_pPlane.get( ); } + + struct PlaneProperties + { + std::optional *begin( ) { return &FB_ID; } + std::optional *end( ) { return &DUMMY_END; } + + std::optional type; // Immutable + std::optional IN_FORMATS; // Immutable + + std::optional FB_ID; + std::optional IN_FENCE_FD; + std::optional CRTC_ID; + std::optional SRC_X; + std::optional SRC_Y; + std::optional SRC_W; + std::optional SRC_H; + std::optional CRTC_X; + std::optional CRTC_Y; + std::optional CRTC_W; + std::optional CRTC_H; + std::optional zpos; + std::optional alpha; + std::optional rotation; + std::optional COLOR_ENCODING; + std::optional COLOR_RANGE; + std::optional AMD_PLANE_DEGAMMA_TF; + std::optional AMD_PLANE_DEGAMMA_LUT; + std::optional AMD_PLANE_CTM; + std::optional AMD_PLANE_HDR_MULT; + std::optional AMD_PLANE_SHAPER_LUT; + std::optional AMD_PLANE_SHAPER_TF; + std::optional AMD_PLANE_LUT3D; + std::optional AMD_PLANE_BLEND_TF; + std::optional AMD_PLANE_BLEND_LUT; + std::optional DUMMY_END; + }; + PlaneProperties &GetProperties( ) { return m_Props; } + const PlaneProperties &GetProperties( ) const { return m_Props; } + + private: + CAutoDeletePtr m_pPlane; + PlaneProperties m_Props; + }; + + class CDRMCRTC final : public CDRMAtomicTypedObject + { + public: + // Takes ownership of pCRTC. + CDRMCRTC( drmModeCrtc *pCRTC, uint32_t uCRTCMask ); + + void RefreshState( ); + uint32_t GetCRTCMask( ) const { return m_uCRTCMask; } + + struct CRTCProperties + { + std::optional *begin( ) { return &ACTIVE; } + std::optional *end( ) { return &DUMMY_END; } + + std::optional ACTIVE; + std::optional MODE_ID; + std::optional GAMMA_LUT; + std::optional DEGAMMA_LUT; + std::optional CTM; + std::optional VRR_ENABLED; + std::optional OUT_FENCE_PTR; + std::optional AMD_CRTC_REGAMMA_TF; + std::optional DUMMY_END; + }; + CRTCProperties &GetProperties( ) { return m_Props; } + const CRTCProperties &GetProperties( ) const { return m_Props; } + + private: + CAutoDeletePtr m_pCRTC; + uint32_t m_uCRTCMask = 0u; + CRTCProperties m_Props; + }; + + class CDRMConnector final + : public CBaseBackendConnector, + public CDRMAtomicTypedObject + { + public: + CDRMConnector( CDRMBackend *pBackend, drmModeConnector *pConnector ); + + void RefreshState( ); + + struct ConnectorProperties + { + std::optional *begin( ) { return &CRTC_ID; } + std::optional *end( ) { return &DUMMY_END; } + + std::optional CRTC_ID; + std::optional Colorspace; + std::optional + content_type; // "content type" with space! + std::optional + panel_orientation; // "panel orientation" with space! + std::optional HDR_OUTPUT_METADATA; + std::optional vrr_capable; + std::optional EDID; + std::optional Broadcast_RGB; + std::optional DUMMY_END; + }; + ConnectorProperties &GetProperties( ) { return m_Props; } + const ConnectorProperties &GetProperties( ) const { return m_Props; } + + drmModeConnector *GetModeConnector( ) { return m_pConnector.get( ); } + const char *GetName( ) const override { return m_Mutable.szName; } + const char *GetMake( ) const override { return m_Mutable.pszMake; } + const char *GetModel( ) const override { return m_Mutable.szModel; } + const char *GetDataString( ) const { return m_Mutable.szDataString; } + uint32_t GetPossibleCRTCMask( ) const + { return m_Mutable.uPossibleCRTCMask; } + std::span GetValidDynamicRefreshRates( ) const override + { return m_Mutable.ValidDynamicRefreshRates; } + const displaycolorimetry_t &GetDisplayColorimetry( ) const + { return m_Mutable.DisplayColorimetry; } + + std::span GetRawEDID( ) const override + { + return std::span{ m_Mutable.EdidData.begin( ), + m_Mutable.EdidData.end( ) }; + } + bool HandleEdidChange( ) + { + bool bChanged = m_Mutable.bEdidChanged; + m_Mutable.bEdidChanged = false; + return bChanged; + } -static void drm_unset_mode( struct drm_t *drm ); -static void drm_unset_connector( struct drm_t *drm ); + bool SupportsHDR10( ) const + { + return !!GetProperties( ).Colorspace && + !!GetProperties( ).HDR_OUTPUT_METADATA && + GetHDRInfo( ).IsHDR10( ); + } -static constexpr uint32_t s_kSteamDeckLCDRates[] = -{ - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, -}; + bool SupportsHDRG22( ) const { return GetHDRInfo( ).IsHDRG22( ); } -static constexpr uint32_t s_kSteamDeckOLEDRates[] = -{ - 45, 47, 48, 49, - 50, 51, 53, 55, 56, 59, - 60, 62, 64, 65, 66, 68, - 72, 73, 76, 77, 78, - 80, 81, 82, 84, 85, 86, 87, 88, - 90, -}; + ////////////////////////////////////// + // IBackendConnector implementation + ////////////////////////////////////// -void update_connector_display_info_wl(struct drm_t *drm) -{ - wlserver_lock(); - for ( const auto &control : wlserver.gamescope_controls ) - { - wlserver_send_gamescope_control( control ); - } - wlserver_unlock(); -} + GamescopeScreenType GetScreenType( ) const override + { + if ( m_pConnector->connector_type == DRM_MODE_CONNECTOR_eDP || + m_pConnector->connector_type == DRM_MODE_CONNECTOR_LVDS || + m_pConnector->connector_type == DRM_MODE_CONNECTOR_DSI ) + return GAMESCOPE_SCREEN_TYPE_INTERNAL; -inline uint64_t drm_calc_s31_32(float val) -{ - // S31.32 sign-magnitude - float integral = 0.0f; - float fractional = modf( fabsf( val ), &integral ); + return GAMESCOPE_SCREEN_TYPE_EXTERNAL; + } - union - { - struct - { - uint64_t fractional : 32; - uint64_t integral : 31; - uint64_t sign_part : 1; - } s31_32_bits; - uint64_t s31_32; - } color; + GamescopePanelOrientation GetCurrentOrientation( ) const override + { return m_ChosenOrientation; } - color.s31_32_bits.sign_part = val < 0 ? 1 : 0; - color.s31_32_bits.integral = uint64_t( integral ); - color.s31_32_bits.fractional = uint64_t( fractional * float( 1ull << 32 ) ); + bool SupportsHDR( ) const override + { return SupportsHDR10( ) || SupportsHDRG22( ); } - return color.s31_32; -} + bool IsHDRActive( ) const override + { + if ( SupportsHDR10( ) ) + { + return GetProperties( ).Colorspace->GetCurrentValue( ) == + DRM_MODE_COLORIMETRY_BT2020_RGB; + } + else if ( SupportsHDRG22( ) ) { return true; } + + return false; + } -static gamescope::CDRMCRTC *find_crtc_for_connector( struct drm_t *drm, gamescope::CDRMConnector *pConnector ) -{ - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - { - if ( pConnector->GetPossibleCRTCMask() & pCRTC->GetCRTCMask() ) - return pCRTC.get(); - } + const BackendConnectorHDRInfo &GetHDRInfo( ) const override + { return m_Mutable.HDR; } - return nullptr; -} + virtual bool IsVRRActive( ) const override + { + if ( !g_DRM.pCRTC || !g_DRM.pCRTC->GetProperties( ).VRR_ENABLED ) + return false; -static bool get_plane_formats( struct drm_t *drm, gamescope::CDRMPlane *pPlane, struct wlr_drm_format_set *pFormatSet ) -{ - for ( uint32_t i = 0; i < pPlane->GetModePlane()->count_formats; i++ ) - { - const uint32_t uFormat = pPlane->GetModePlane()->formats[ i ]; - wlr_drm_format_set_add( pFormatSet, uFormat, DRM_FORMAT_MOD_INVALID ); - } - - if ( pPlane->GetProperties().IN_FORMATS ) - { - const uint64_t ulBlobId = pPlane->GetProperties().IN_FORMATS->GetCurrentValue(); - - drmModePropertyBlobRes *pBlob = drmModeGetPropertyBlob( drm->fd, ulBlobId ); - if ( !pBlob ) - { - drm_log.errorf_errno("drmModeGetPropertyBlob(IN_FORMATS) failed"); - return false; - } - defer( drmModeFreePropertyBlob( pBlob ) ); - - drm_format_modifier_blob *pModifierBlob = reinterpret_cast( pBlob->data ); - - uint32_t *pFormats = reinterpret_cast( reinterpret_cast( pBlob->data ) + pModifierBlob->formats_offset ); - drm_format_modifier *pMods = reinterpret_cast( reinterpret_cast( pBlob->data ) + pModifierBlob->modifiers_offset ); - - for ( uint32_t i = 0; i < pModifierBlob->count_modifiers; i++ ) - { - for ( uint32_t j = 0; j < 64; j++ ) - { - if ( pMods[i].formats & ( uint64_t(1) << j ) ) - wlr_drm_format_set_add( pFormatSet, pFormats[j + pMods[i].offset], pMods[i].modifier ); - } - } - } - - return true; -} + return !!g_DRM.pCRTC->GetProperties( ) + .VRR_ENABLED->GetCurrentValue( ); + } -static uint32_t pick_plane_format( const struct wlr_drm_format_set *formats, uint32_t Xformat, uint32_t Aformat ) -{ - uint32_t result = DRM_FORMAT_INVALID; - for ( size_t i = 0; i < formats->len; i++ ) { - uint32_t fmt = formats->formats[i].format; - if ( fmt == Xformat ) { - // Prefer formats without alpha channel for main plane - result = fmt; - } else if ( result == DRM_FORMAT_INVALID && fmt == Aformat ) { - result = fmt; - } - } - return result; -} + virtual std::span GetModes( ) const override + { return m_Mutable.BackendModes; } -/* Pick a primary plane that can be connected to the chosen CRTC. */ -static gamescope::CDRMPlane *find_primary_plane(struct drm_t *drm) -{ - if ( !drm->pCRTC ) - return nullptr; + bool SupportsVRR( ) const override + { + return this->GetProperties( ).vrr_capable && + !!this->GetProperties( ).vrr_capable->GetCurrentValue( ); + } - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - { - if ( pPlane->GetModePlane()->possible_crtcs & drm->pCRTC->GetCRTCMask() ) - { - if ( pPlane->GetProperties().type->GetCurrentValue() == DRM_PLANE_TYPE_PRIMARY ) - return pPlane.get(); - } - } + void GetNativeColorimetry( + bool bHDR, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const override + { + *displayColorimetry = GetDisplayColorimetry( ); + *displayEOTF = EOTF_Gamma22; + + if ( bHDR && GetHDRInfo( ).IsHDR10( ) ) + { + // For HDR10 output, expected content colorspace != native + // colorspace. + *outputEncodingColorimetry = displaycolorimetry_2020; + *outputEncodingEOTF = GetHDRInfo( ).eOutputEncodingEOTF; + } + else + { + *outputEncodingColorimetry = GetDisplayColorimetry( ); + *outputEncodingEOTF = EOTF_Gamma22; + } + } - return nullptr; -} + virtual int + Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; -static bool have_overlay_planes(struct drm_t *drm) -{ - if ( !drm->pCRTC ) - return false; + using DRMModeGenerator = + std::function; + const DRMModeGenerator &GetModeGenerator( ) const + { return m_Mutable.fnDynamicModeGenerator; } + void UpdateEffectiveOrientation( const drmModeModeInfo *pMode ); - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - { - if ( pPlane->GetModePlane()->possible_crtcs & drm->pCRTC->GetCRTCMask() ) - { - if ( pPlane->GetProperties().type->GetCurrentValue() == DRM_PLANE_TYPE_OVERLAY ) - return true; - } - } + private: + void ParseEDID( ); - return false; -} + [[maybe_unused]] CDRMBackend *m_pBackend = nullptr; + CAutoDeletePtr m_pConnector; -extern void mangoapp_output_update( uint64_t vblanktime ); -static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *data) -{ - DRMPresentCtx *pCtx = reinterpret_cast( data ); + struct MutableConnectorState + { + int nDefaultRefresh = 0; - // Make this const when we move into CDRMBackend. - GetBackend()->GetCurrentConnector()->PresentationFeedback().m_uCompletedPresents = pCtx->ulPendingFlipCount; + uint32_t uPossibleCRTCMask = 0u; + char szName[ 32 ]{}; + char szMakePNP[ 4 ]{}; + char szModel[ 16 ]{}; + char szDataString[ 16 ]{}; + const char *pszMake = ""; // Not owned, no free. This is a pointer + // to pnp db or szMakePNP. + DRMModeGenerator fnDynamicModeGenerator; + std::vector ValidDynamicRefreshRates{}; + std::vector EdidData; // Raw, unmodified. + std::vector BackendModes; - if ( !g_DRM.pCRTC ) - return; + displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; + BackendConnectorHDRInfo HDR; - if ( g_DRM.pCRTC->GetObjectId() != crtc_id ) - return; + bool bEdidChanged = false; + } m_Mutable; - static uint64_t ulLastVBlankTime = 0; + GamescopePanelOrientation m_ChosenOrientation = + GAMESCOPE_PANEL_ORIENTATION_AUTO; - // This is the last vblank time - uint64_t vblanktime = sec * 1'000'000'000lu + usec * 1'000lu; - GetVBlankTimer().MarkVBlank( vblanktime, true ); + ConnectorProperties m_Props; + }; - // TODO: get the fbids_queued instance from data if we ever have more than one in flight + class CDRMFb final : public CBaseBackendFb + { + public: + CDRMFb( uint32_t uFbId ); + ~CDRMFb( ); - drm_log.debugf("page_flip_handler %" PRIu64 " delta: %" PRIu64, pCtx->ulPendingFlipCount, vblanktime - ulLastVBlankTime ); - gpuvis_trace_printf("page_flip_handler %" PRIu64, pCtx->ulPendingFlipCount); + uint32_t GetFbId( ) const { return m_uFbId; } - ulLastVBlankTime = vblanktime; + private: + uint32_t m_uFbId = 0; + }; +} // namespace gamescope - { - std::scoped_lock lock{ g_DRM.m_QueuedFbIdsMutex, g_DRM.m_mutVisibleFbIds }; - // Swap and clear from queue -> visible to avoid allocations. - g_DRM.m_VisibleFbIds.swap( g_DRM.m_QueuedFbIds ); - g_DRM.m_QueuedFbIds.clear(); - } +uint32_t g_nDRMFormat = DRM_FORMAT_INVALID; +uint32_t g_nDRMFormatOverlay = + DRM_FORMAT_INVALID; // for partial composition, we may have more limited + // formats than base planes + alpha. +bool g_bRotated = false; +extern bool g_bDebugLayers; - g_DRM.uPendingFlipCount--; - g_DRM.uPendingFlipCount.notify_all(); +struct DRMPresentCtx +{ + uint64_t ulPendingFlipCount = 0; +}; - mangoapp_output_update( vblanktime ); +extern gamescope::ConVar cv_composite_force; +extern bool g_bColorSliderInUse; +extern bool fadingOut; +extern std::string g_reshade_effect; - // Nudge so that steamcompmgr releases commits. - nudge_steamcompmgr(); -} +#ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP + #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 +#endif -void flip_handler_thread_run(void) -{ - pthread_setname_np( pthread_self(), "gamescope-kms" ); - - struct pollfd pollfd = { - .fd = g_DRM.fd, - .events = POLLIN, - }; - - while ( true ) - { - int ret = poll( &pollfd, 1, -1 ); - if ( ret < 0 ) { - drm_log.errorf_errno( "polling for DRM events failed" ); - break; - } - - drmEventContext evctx = { - .version = 3, - .page_flip_handler2 = page_flip_handler, - }; - drmHandleEvent(g_DRM.fd, &evctx); - } -} +bool drm_update_color_mgmt( struct drm_t *drm ); +bool drm_supports_color_mgmt( struct drm_t *drm ); +bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ); -static bool refresh_state( drm_t *drm ) +struct drm_color_ctm2 { - drmModeRes *pResources = drmModeGetResources( drm->fd ); - if ( pResources == nullptr ) - { - drm_log.errorf_errno( "drmModeGetResources failed" ); - return false; - } - defer( drmModeFreeResources( pResources ) ); - - // Add connectors which appeared - for ( int i = 0; i < pResources->count_connectors; i++ ) - { - uint32_t uConnectorId = pResources->connectors[i]; - - drmModeConnector *pConnector = drmModeGetConnector( drm->fd, uConnectorId ); - if ( !pConnector ) - continue; - - if ( cv_drm_ignore_internal_connectors ) - { - if ( pConnector->connector_type == DRM_MODE_CONNECTOR_eDP || - pConnector->connector_type == DRM_MODE_CONNECTOR_LVDS || - pConnector->connector_type == DRM_MODE_CONNECTOR_DSI ) - { - drmModeFreeConnector( pConnector ); - continue; - } - } - - if ( !drm->connectors.contains( uConnectorId ) ) - { - drm->connectors.emplace( - std::piecewise_construct, - std::forward_as_tuple( uConnectorId ), - std::forward_as_tuple( reinterpret_cast( GetBackend() ), pConnector ) ); - } - } - - // Remove connectors which disappeared - for ( auto iter = drm->connectors.begin(); iter != drm->connectors.end(); ) - { - gamescope::CDRMConnector *pConnector = &iter->second; - - const bool bFound = std::any_of( - pResources->connectors, - pResources->connectors + pResources->count_connectors, - std::bind_front( std::equal_to{}, pConnector->GetObjectId() ) ); - - if ( !bFound ) - { - drm_log.debugf( "Connector '%s' disappeared.", pConnector->GetName() ); - - if ( drm->pConnector == pConnector ) - { - drm_log.infof( "Current connector '%s' disappeared.", pConnector->GetName() ); - drm->pConnector = nullptr; - } - - iter = drm->connectors.erase( iter ); - } - else - iter++; - } - - // Re-probe connectors props and status) - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - pConnector->RefreshState(); - } - - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - pCRTC->RefreshState(); - - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - pPlane->RefreshState(); - - return true; -} + /* + * Conversion matrix in S31.32 sign-magnitude + * (not two's complement!) format. + */ + __u64 matrix[ 12 ]; +}; -static bool get_resources(struct drm_t *drm) -{ - { - drmModeRes *pResources = drmModeGetResources( drm->fd ); - if ( !pResources ) - { - drm_log.errorf_errno( "drmModeGetResources failed" ); - return false; - } - defer( drmModeFreeResources( pResources ) ); - - for ( int i = 0; i < pResources->count_crtcs; i++ ) - { - drmModeCrtc *pCRTC = drmModeGetCrtc( drm->fd, pResources->crtcs[ i ] ); - if ( pCRTC ) - drm->crtcs.emplace_back( std::make_unique( pCRTC, 1u << i ) ); - } - } - - { - drmModePlaneRes *pPlaneResources = drmModeGetPlaneResources( drm->fd ); - if ( !pPlaneResources ) - { - drm_log.errorf_errno( "drmModeGetPlaneResources failed" ); - return false; - } - defer( drmModeFreePlaneResources( pPlaneResources ) ); - - for ( uint32_t i = 0; i < pPlaneResources->count_planes; i++ ) - { - drmModePlane *pPlane = drmModeGetPlane( drm->fd, pPlaneResources->planes[ i ] ); - if ( pPlane ) - drm->planes.emplace_back( std::make_unique( pPlane ) ); - } - } - - return refresh_state( drm ); -} +bool g_bSupportsAsyncFlips = false; +bool g_bSupportsSyncObjs = false; -struct mode_blocklist_entry -{ - uint32_t width, height, refresh; -}; +extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; +extern GamescopePanelOrientation g_DesiredInternalOrientation; -// Filter out reporting some modes that are required for -// certain certifications, but are completely useless, -// and probably don't fit the display pixel size. -static mode_blocklist_entry g_badModes[] = -{ - { 4096, 2160, 0 }, -}; +extern bool g_bForceDisableColorMgmt; -static const drmModeModeInfo *find_mode( const drmModeConnector *connector, int hdisplay, int vdisplay, uint32_t vrefresh ) -{ - for (int i = 0; i < connector->count_modes; i++) { - const drmModeModeInfo *mode = &connector->modes[i]; +static LogScope drm_log( "drm" ); +static LogScope liftoff_log_scope( "liftoff" ); - bool bad = false; - for (const auto& badMode : g_badModes) { - bad |= (badMode.width == 0 || mode->hdisplay == badMode.width) - && (badMode.height == 0 || mode->vdisplay == badMode.height) - && (badMode.refresh == 0 || mode->vrefresh == badMode.refresh); - } +static std::unordered_map pnps = {}; - if (bad) - continue; +static void drm_unset_mode( struct drm_t *drm ); +static void drm_unset_connector( struct drm_t *drm ); - if (hdisplay != 0 && hdisplay != mode->hdisplay) - continue; - if (vdisplay != 0 && vdisplay != mode->vdisplay) - continue; - if (vrefresh != 0 && vrefresh != mode->vrefresh) - continue; +static constexpr uint32_t s_kSteamDeckLCDRates[] = { + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, +}; - return mode; - } +static constexpr uint32_t s_kSteamDeckOLEDRates[] = { + 45, 47, 48, 49, 50, 51, 53, 55, 56, 59, 60, 62, 64, 65, 66, + 68, 72, 73, 76, 77, 78, 80, 81, 82, 84, 85, 86, 87, 88, 90, +}; - return NULL; +void update_connector_display_info_wl( struct drm_t *drm ) +{ + wlserver_lock( ); + for ( const auto &control : wlserver.gamescope_controls ) + { + wlserver_send_gamescope_control( control ); + } + wlserver_unlock( ); } -static std::unordered_map parse_connector_priorities(const char *str) -{ - std::unordered_map priorities{}; - if (!str) { - return priorities; - } - int i = 0; - char *buf = strdup(str); - char *name = strtok(buf, ","); - while (name) { - priorities[name] = i; - i++; - name = strtok(nullptr, ","); - } - free(buf); - return priorities; +inline uint64_t drm_calc_s31_32( float val ) +{ + // S31.32 sign-magnitude + float integral = 0.0f; + float fractional = modf( fabsf( val ), &integral ); + + union + { + struct + { + uint64_t fractional : 32; + uint64_t integral : 31; + uint64_t sign_part : 1; + } s31_32_bits; + uint64_t s31_32; + } color; + + color.s31_32_bits.sign_part = val < 0 ? 1 : 0; + color.s31_32_bits.integral = uint64_t( integral ); + color.s31_32_bits.fractional = uint64_t( fractional * float( 1ull << 32 ) ); + + return color.s31_32; } -static int get_connector_priority(struct drm_t *drm, const char *name) +static gamescope::CDRMCRTC *find_crtc_for_connector( + struct drm_t *drm, gamescope::CDRMConnector *pConnector ) { - if (drm->connector_priorities.count(name) > 0) { - return drm->connector_priorities[name]; - } - if (drm->connector_priorities.count("*") > 0) { - return drm->connector_priorities["*"]; - } - return drm->connector_priorities.size(); + for ( std::unique_ptr &pCRTC : drm->crtcs ) + { + if ( pConnector->GetPossibleCRTCMask( ) & pCRTC->GetCRTCMask( ) ) + return pCRTC.get( ); + } + + return nullptr; } -static bool get_saved_mode(const char *description, saved_mode &mode_info) +static bool get_plane_formats( + struct drm_t *drm, + gamescope::CDRMPlane *pPlane, + struct wlr_drm_format_set *pFormatSet ) { - const char *mode_file = getenv("GAMESCOPE_MODE_SAVE_FILE"); - if (!mode_file || !*mode_file) - return false; + for ( uint32_t i = 0; i < pPlane->GetModePlane( )->count_formats; i++ ) + { + const uint32_t uFormat = pPlane->GetModePlane( )->formats[ i ]; + wlr_drm_format_set_add( pFormatSet, uFormat, DRM_FORMAT_MOD_INVALID ); + } + + if ( pPlane->GetProperties( ).IN_FORMATS ) + { + const uint64_t ulBlobId = + pPlane->GetProperties( ).IN_FORMATS->GetCurrentValue( ); - FILE *file = fopen(mode_file, "r"); - if (!file) - return false; + drmModePropertyBlobRes *pBlob = + drmModeGetPropertyBlob( drm->fd, ulBlobId ); + if ( !pBlob ) + { + drm_log.errorf_errno( "drmModeGetPropertyBlob(IN_FORMATS) failed" ); + return false; + } + defer( drmModeFreePropertyBlob( pBlob ) ); - char line[256]; - while (fgets(line, sizeof(line), file)) - { - char saved_description[256]; - uint32_t broadcast_mode = 0; - int ret = sscanf(line, "%255[^:]:%dx%d@%d %u", saved_description, &mode_info.width, &mode_info.height, &mode_info.refresh, &broadcast_mode); + drm_format_modifier_blob *pModifierBlob = + reinterpret_cast( pBlob->data ); - mode_info.broadcast_mode = (GamescopeBroadcastRGBMode_t) broadcast_mode; - - bool valid = ret == 4 || ret == 5; + uint32_t *pFormats = reinterpret_cast( + reinterpret_cast( pBlob->data ) + + pModifierBlob->formats_offset ); + drm_format_modifier *pMods = reinterpret_cast( + reinterpret_cast( pBlob->data ) + + pModifierBlob->modifiers_offset ); - if (valid && !strcmp(saved_description, description)) - { - fclose(file); - return true; - } + for ( uint32_t i = 0; i < pModifierBlob->count_modifiers; i++ ) + { + for ( uint32_t j = 0; j < 64; j++ ) + { + if ( pMods[ i ].formats & ( uint64_t( 1 ) << j ) ) + wlr_drm_format_set_add( + pFormatSet, + pFormats[ j + pMods[ i ].offset ], + pMods[ i ].modifier ); + } + } } - fclose(file); - return false; -} -static GamescopeBroadcastRGBMode_t s_ExternalBroadcastRGBMode = GAMESCOPE_BROADCAST_RGB_MODE_AUTOMATIC; - -static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) -{ - if (drm->pConnector && drm->pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED) { - drm_log.infof("current connector '%s' disconnected", drm->pConnector->GetName()); - drm->pConnector = nullptr; - } - - gamescope::CDRMConnector *best = nullptr; - int nBestPriority = INT_MAX; - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - - if ( pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED ) - continue; - - if ( g_bForceInternal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) - continue; - - int nPriority = get_connector_priority( drm, pConnector->GetName() ); - if ( nPriority < nBestPriority ) - { - best = pConnector; - nBestPriority = nPriority; - } - } - - if ( best && best == drm->pConnector ) - { - // If the device's EDID changed from user us, force a mode-change - // as we might - if ( best->HandleEdidChange() ) - { - force = true; - } - } - - if (!force) { - if ((!best && drm->pConnector) || (best && best == drm->pConnector)) { - // Let's keep our current connector - return true; - } - } - - if (best == nullptr) { - drm_log.infof("cannot find any connected connector!"); - drm_unset_connector(drm); - drm_unset_mode(drm); - const struct wlserver_output_info wlserver_output_info = { - .description = "Virtual screen", - }; - wlserver_lock(); - wlserver_set_output_info(&wlserver_output_info); - wlserver_unlock(); - return true; - } - - if (!drm_set_connector(drm, best)) { - return false; - } - - char description[256]; - if (best->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL) { - snprintf(description, sizeof(description), "Internal screen"); - } else if (best->GetMake() && best->GetModel()) { - snprintf(description, sizeof(description), "%s %s", best->GetMake(), best->GetModel()); - } else if (best->GetModel()) { - snprintf(description, sizeof(description), "%s", best->GetModel()); - } else { - snprintf(description, sizeof(description), "External screen"); - } - - s_ExternalBroadcastRGBMode = GAMESCOPE_BROADCAST_RGB_MODE_AUTOMATIC; - - const drmModeModeInfo *mode = nullptr; - if ( drm->preferred_width != 0 || drm->preferred_height != 0 || drm->preferred_refresh != 0 ) - { - mode = find_mode(best->GetModeConnector(), drm->preferred_width, drm->preferred_height, gamescope::ConvertmHzToHz( drm->preferred_refresh )); - } - - if (!mode && best->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL) { - saved_mode mode_info{}; - if (get_saved_mode(description, mode_info)) - { - s_ExternalBroadcastRGBMode = mode_info.broadcast_mode; - mode = find_mode(best->GetModeConnector(), mode_info.width, mode_info.height, mode_info.refresh); - } - } - - if (!mode) { - mode = find_mode(best->GetModeConnector(), 0, 0, 0); - } - - if (!mode) { - drm_log.errorf("could not find mode!"); - return false; - } - - if (!drm_set_mode(drm, mode)) { - return false; - } - - // Don't allow rollback of mode_id after connector change - drm->current.mode_id = drm->pending.mode_id; - - const struct wlserver_output_info wlserver_output_info = { - .description = description, - .phys_width = (int) best->GetModeConnector()->mmWidth, - .phys_height = (int) best->GetModeConnector()->mmHeight, - }; - wlserver_lock(); - wlserver_set_output_info(&wlserver_output_info); - wlserver_unlock(); - - if (!initial) - WritePatchedEdid( best->GetRawEDID(), best->GetHDRInfo(), g_bRotated ); - - update_connector_display_info_wl( drm ); - - return true; + return true; } -void load_pnps(void) +static uint32_t pick_plane_format( + const struct wlr_drm_format_set *formats, + uint32_t Xformat, + uint32_t Aformat ) { -#ifdef HWDATA_PNP_IDS - const char *filename = HWDATA_PNP_IDS; - FILE *f = fopen(filename, "r"); - if (!f) { - drm_log.infof("failed to open PNP IDs file at '%s'", filename); - return; - } - - char *line = NULL; - size_t line_size = 0; - while (getline(&line, &line_size, f) >= 0) { - char *nl = strchr(line, '\n'); - if (nl) { - *nl = '\0'; - } - - char *sep = strchr(line, '\t'); - if (!sep) { - continue; - } - *sep = '\0'; - - std::string id(line); - std::string name(sep + 1); - pnps[id] = name; - } - - free(line); - fclose(f); -#endif + uint32_t result = DRM_FORMAT_INVALID; + for ( size_t i = 0; i < formats->len; i++ ) + { + uint32_t fmt = formats->formats[ i ].format; + if ( fmt == Xformat ) + { + // Prefer formats without alpha channel for main plane + result = fmt; + } + else if ( result == DRM_FORMAT_INVALID && fmt == Aformat ) + { + result = fmt; + } + } + return result; } -extern bool env_to_bool(const char *env); - -uint32_t g_uAlwaysSignalledSyncobj = 0; -int g_nAlwaysSignalledSyncFile = -1; - -static void -gamescope_liftoff_log_handler(enum liftoff_log_priority liftoff_priority, const char *fmt, va_list args) -{ - enum LogPriority priority = LOG_DEBUG; - - switch ( liftoff_priority ) - { - case LIFTOFF_ERROR: - priority = LOG_ERROR; - break; - case LIFTOFF_DEBUG: - priority = LOG_DEBUG; - break; - case LIFTOFF_SILENT: - priority = LOG_SILENT; - break; - } - - liftoff_log_scope.vlogf(priority, fmt, args); -} +/* Pick a primary plane that can be connected to the chosen CRTC. */ +static gamescope::CDRMPlane *find_primary_plane( struct drm_t *drm ) +{ + if ( !drm->pCRTC ) return nullptr; -bool init_drm(struct drm_t *drm, int width, int height, int refresh) -{ - load_pnps(); - - drm->bUseLiftoff = true; - - drm->preferred_width = width; - drm->preferred_height = height; - drm->preferred_refresh = refresh; - - drm->device_name = nullptr; - dev_t dev_id = 0; - if (vulkan_primary_dev_id(&dev_id)) { - drmDevice *drm_dev = nullptr; - if (drmGetDeviceFromDevId(dev_id, 0, &drm_dev) != 0) { - drm_log.errorf("Failed to find DRM device with device ID %" PRIu64, (uint64_t)dev_id); - return false; - } - assert(drm_dev->available_nodes & (1 << DRM_NODE_PRIMARY)); - drm->device_name = strdup(drm_dev->nodes[DRM_NODE_PRIMARY]); - drm_log.infof("opening DRM node '%s'", drm->device_name); - } - else - { - drm_log.infof("warning: picking an arbitrary DRM device"); - } - - drm->fd = wlsession_open_kms( drm->device_name ); - if ( drm->fd < 0 ) - { - drm_log.errorf("Could not open KMS device"); - return false; - } - - if ( !drmIsKMS( drm->fd ) ) - { - drm_log.errorf( "'%s' is not a KMS device", drm->device_name ); - wlsession_close_kms(); - return -1; - } - - if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) { - drm_log.errorf("drmSetClientCap(ATOMIC) failed"); - return false; - } - - if (drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &drm->cursor_width) != 0) { - drm->cursor_width = 64; - } - if (drmGetCap(drm->fd, DRM_CAP_CURSOR_HEIGHT, &drm->cursor_height) != 0) { - drm->cursor_height = 64; - } - - uint64_t cap; - g_bSupportsSyncObjs = drmGetCap(drm->fd, DRM_CAP_SYNCOBJ, &cap) == 0 && cap != 0; - if ( g_bSupportsSyncObjs ) { - int err = drmSyncobjCreate(drm->fd, DRM_SYNCOBJ_CREATE_SIGNALED, &g_uAlwaysSignalledSyncobj); - if (err < 0) { - drm_log.errorf("Failed to create dummy signalled syncobj"); - return false; - } - err = drmSyncobjExportSyncFile(drm->fd, g_uAlwaysSignalledSyncobj, &g_nAlwaysSignalledSyncFile); - if (err < 0) { - drm_log.errorf("Failed to create dummy signalled sync file"); - return false; - } - } else { - drm_log.errorf("Syncobjs are not supported by the KMS driver"); - } - - if (drmGetCap(drm->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap) == 0 && cap != 0) { - drm->allow_modifiers = true; - } - - g_bSupportsAsyncFlips = drmGetCap(drm->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap) == 0 && cap != 0; - if (!g_bSupportsAsyncFlips) - drm_log.errorf("Immediate flips are not supported by the KMS driver"); - - static bool async_disabled = env_to_bool(getenv("GAMESCOPE_DISABLE_ASYNC_FLIPS")); - - if ( async_disabled ) - { - g_bSupportsAsyncFlips = false; - drm_log.errorf("Immediate flips disabled from environment"); - } - - if (!get_resources(drm)) { - return false; - } - - drm->lo_device = liftoff_device_create( drm->fd ); - if ( drm->lo_device == nullptr ) - return false; - if ( liftoff_device_register_all_planes( drm->lo_device ) < 0 ) - return false; - - drm_log.infof("Connectors:"); - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - - const char *status_str = "disconnected"; - if ( pConnector->GetModeConnector()->connection == DRM_MODE_CONNECTED ) - status_str = "connected"; - - drm_log.infof(" %s (%s)", pConnector->GetName(), status_str); - } - - drm->connector_priorities = parse_connector_priorities( g_sOutputName ); - - if (!setup_best_connector(drm, true, true)) { - return false; - } - - // Fetch formats which can be scanned out - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - { - if ( !get_plane_formats( drm, pPlane.get(), &drm->formats ) ) - return false; - } - - // TODO: intersect primary planes formats instead - if ( !drm->pPrimaryPlane ) - drm->pPrimaryPlane = find_primary_plane( drm ); - - if ( !drm->pPrimaryPlane ) - { - drm_log.errorf("Failed to find a primary plane"); - return false; - } - - if ( !get_plane_formats( drm, drm->pPrimaryPlane, &drm->primary_formats ) ) - { - return false; - } - - // Pick a 10-bit format at first for our composition buffer, for a couple of reasons: - // - // 1. Many game engines automatically render to 10-bit formats such as UE4 which means - // that when we have to composite, we can keep the same HW dithering that we would get if - // we just scanned them out directly. - // - // 2. When compositing HDR content as a fallback when we undock, it avoids introducing - // a bunch of horrible banding when going to G2.2 curve. - // It ensures that we can dither that. - g_nDRMFormat = pick_plane_format(&drm->primary_formats, DRM_FORMAT_XRGB2101010, DRM_FORMAT_ARGB2101010); - if ( g_nDRMFormat == DRM_FORMAT_INVALID ) { - g_nDRMFormat = pick_plane_format(&drm->primary_formats, DRM_FORMAT_XBGR2101010, DRM_FORMAT_ABGR2101010); - if ( g_nDRMFormat == DRM_FORMAT_INVALID ) { - g_nDRMFormat = pick_plane_format(&drm->primary_formats, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888); - if ( g_nDRMFormat == DRM_FORMAT_INVALID ) { - drm_log.errorf("Primary plane doesn't support any formats >= 8888"); - return false; - } - } - } - - if (have_overlay_planes(drm)) { - // ARGB8888 is the Xformat and AFormat here in this function as we want transparent overlay - g_nDRMFormatOverlay = pick_plane_format(&drm->formats, DRM_FORMAT_ARGB2101010, DRM_FORMAT_ARGB2101010); - if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) { - g_nDRMFormatOverlay = pick_plane_format(&drm->formats, DRM_FORMAT_ABGR2101010, DRM_FORMAT_ABGR2101010); - if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) { - g_nDRMFormatOverlay = pick_plane_format(&drm->formats, DRM_FORMAT_ARGB8888, DRM_FORMAT_ARGB8888); - if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) { - drm_log.errorf("Overlay plane doesn't support any formats >= 8888"); - return false; - } - } - } - } else { - switch (g_nDRMFormat) { - case DRM_FORMAT_XRGB2101010: - g_nDRMFormatOverlay = DRM_FORMAT_ARGB2101010; - break; - case DRM_FORMAT_ABGR2101010: - g_nDRMFormatOverlay = DRM_FORMAT_ABGR2101010; - break; - case DRM_FORMAT_XRGB8888: - g_nDRMFormatOverlay = DRM_FORMAT_ARGB8888; - break; - default: - return false; - } - } - - std::thread flip_handler_thread( flip_handler_thread_run ); - flip_handler_thread.detach(); - - // Set log priority to the max, liftoff_log_scope will filter for us. - liftoff_log_set_priority(LIFTOFF_DEBUG); - liftoff_log_set_handler(gamescope_liftoff_log_handler); - - hdr_output_metadata sdr_metadata; - memset(&sdr_metadata, 0, sizeof(sdr_metadata)); - drm->sdr_static_metadata = GetBackend()->CreateBackendBlob( sdr_metadata ); - - drm->needs_modeset = true; - - return true; -} + for ( std::unique_ptr &pPlane : drm->planes ) + { + if ( pPlane->GetModePlane( )->possible_crtcs & + drm->pCRTC->GetCRTCMask( ) ) + { + if ( pPlane->GetProperties( ).type->GetCurrentValue( ) == + DRM_PLANE_TYPE_PRIMARY ) + return pPlane.get( ); + } + } -void OnSleepScreenChanged( gamescope::ConVar & ) -{ - force_repaint(); + return nullptr; } -gamescope::ConVar cv_drm_sleep_screens[] = +static bool have_overlay_planes( struct drm_t *drm ) { - { "drm_sleep_internal_screen", false, "Force the internal screen to be asleep", OnSleepScreenChanged }, - { "drm_sleep_external_screen", false, "Force the external screen to be asleep", OnSleepScreenChanged }, -}; + if ( !drm->pCRTC ) return false; -void drm_sleep_screen( gamescope::GamescopeScreenType eType, bool bSleep ) -{ - if ( cv_drm_sleep_screens[ eType ] == bSleep ) - return; + for ( std::unique_ptr &pPlane : drm->planes ) + { + if ( pPlane->GetModePlane( )->possible_crtcs & + drm->pCRTC->GetCRTCMask( ) ) + { + if ( pPlane->GetProperties( ).type->GetCurrentValue( ) == + DRM_PLANE_TYPE_OVERLAY ) + return true; + } + } - cv_drm_sleep_screens[ eType ] = bSleep; + return false; } - - -void finish_drm(struct drm_t *drm) +extern void mangoapp_output_update( uint64_t vblanktime ); +static void page_flip_handler( + int fd, + unsigned int frame, + unsigned int sec, + unsigned int usec, + unsigned int crtc_id, + void *data ) { - // Disable all connectors, CRTCs and planes. This is necessary to leave a - // clean KMS state behind. Some other KMS clients might not support all of - // the properties we use, e.g. "rotation" and Xorg don't play well - // together. - - drmModeAtomicReq *req = drmModeAtomicAlloc(); + DRMPresentCtx *pCtx = reinterpret_cast( data ); - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; + // Make this const when we move into CDRMBackend. + GetBackend( ) + ->GetCurrentConnector( ) + ->PresentationFeedback( ) + .m_uCompletedPresents = pCtx->ulPendingFlipCount; - pConnector->GetProperties().CRTC_ID->SetPendingValue( req, 0, true ); + if ( !g_DRM.pCRTC ) return; - if ( pConnector->GetProperties().Colorspace ) - pConnector->GetProperties().Colorspace->SetPendingValue( req, 0, true ); + if ( g_DRM.pCRTC->GetObjectId( ) != crtc_id ) return; - if ( pConnector->GetProperties().HDR_OUTPUT_METADATA ) - { - if ( drm->sdr_static_metadata && pConnector->GetHDRInfo().IsHDR10() ) - pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, drm->sdr_static_metadata->GetBlobValue(), true ); - else - pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, 0, true ); - } + static uint64_t ulLastVBlankTime = 0; - if ( pConnector->GetProperties().content_type ) - pConnector->GetProperties().content_type->SetPendingValue( req, 0, true ); - } + // This is the last vblank time + uint64_t vblanktime = sec * 1'000'000'000lu + usec * 1'000lu; + GetVBlankTimer( ).MarkVBlank( vblanktime, true ); - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - { - pCRTC->GetProperties().ACTIVE->SetPendingValue( req, 0, true ); - pCRTC->GetProperties().MODE_ID->SetPendingValue( req, 0, true ); + // TODO: get the fbids_queued instance from data if we ever have more than + // one in flight - if ( pCRTC->GetProperties().GAMMA_LUT ) - pCRTC->GetProperties().GAMMA_LUT->SetPendingValue( req, 0, true ); + drm_log.debugf( + "page_flip_handler %" PRIu64 " delta: %" PRIu64, + pCtx->ulPendingFlipCount, + vblanktime - ulLastVBlankTime ); + gpuvis_trace_printf( + "page_flip_handler %" PRIu64, pCtx->ulPendingFlipCount ); - if ( pCRTC->GetProperties().DEGAMMA_LUT ) - pCRTC->GetProperties().DEGAMMA_LUT->SetPendingValue( req, 0, true ); + ulLastVBlankTime = vblanktime; - if ( pCRTC->GetProperties().CTM ) - pCRTC->GetProperties().CTM->SetPendingValue( req, 0, true ); + { + std::scoped_lock lock{ g_DRM.m_QueuedFbIdsMutex, + g_DRM.m_mutVisibleFbIds }; + // Swap and clear from queue -> visible to avoid allocations. + g_DRM.m_VisibleFbIds.swap( g_DRM.m_QueuedFbIds ); + g_DRM.m_QueuedFbIds.clear( ); + } - if ( pCRTC->GetProperties().VRR_ENABLED ) - pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( req, 0, true ); + g_DRM.uPendingFlipCount--; + g_DRM.uPendingFlipCount.notify_all( ); - if ( pCRTC->GetProperties().OUT_FENCE_PTR ) - pCRTC->GetProperties().OUT_FENCE_PTR->SetPendingValue( req, 0, true ); + mangoapp_output_update( vblanktime ); - if ( pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF ) - pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF->SetPendingValue( req, 0, true ); - } + // Nudge so that steamcompmgr releases commits. + nudge_steamcompmgr( ); +} - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - { - pPlane->GetProperties().FB_ID->SetPendingValue( req, 0, true ); - pPlane->GetProperties().IN_FENCE_FD->SetPendingValue( req, -1, true ); - pPlane->GetProperties().CRTC_ID->SetPendingValue( req, 0, true ); - pPlane->GetProperties().SRC_X->SetPendingValue( req, 0, true ); - pPlane->GetProperties().SRC_Y->SetPendingValue( req, 0, true ); - pPlane->GetProperties().SRC_W->SetPendingValue( req, 0, true ); - pPlane->GetProperties().SRC_H->SetPendingValue( req, 0, true ); - pPlane->GetProperties().CRTC_X->SetPendingValue( req, 0, true ); - pPlane->GetProperties().CRTC_Y->SetPendingValue( req, 0, true ); - pPlane->GetProperties().CRTC_W->SetPendingValue( req, 0, true ); - pPlane->GetProperties().CRTC_H->SetPendingValue( req, 0, true ); +void flip_handler_thread_run( void ) +{ + pthread_setname_np( pthread_self( ), "gamescope-kms" ); - if ( pPlane->GetProperties().rotation ) - pPlane->GetProperties().rotation->SetPendingValue( req, DRM_MODE_ROTATE_0, true ); + struct pollfd pollfd = { + .fd = g_DRM.fd, + .events = POLLIN, + }; - if ( pPlane->GetProperties().alpha ) - pPlane->GetProperties().alpha->SetPendingValue( req, 0xFFFF, true ); + while ( true ) + { + int ret = poll( &pollfd, 1, -1 ); + if ( ret < 0 ) + { + drm_log.errorf_errno( "polling for DRM events failed" ); + break; + } - //if ( pPlane->GetProperties().zpos ) - // pPlane->GetProperties().zpos->SetPendingValue( req, , true ); + drmEventContext evctx = { + .version = 3, + .page_flip_handler2 = page_flip_handler, + }; + drmHandleEvent( g_DRM.fd, &evctx ); + } +} - if ( pPlane->GetProperties().AMD_PLANE_DEGAMMA_TF ) - pPlane->GetProperties().AMD_PLANE_DEGAMMA_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); +static bool refresh_state( drm_t *drm ) +{ + drmModeRes *pResources = drmModeGetResources( drm->fd ); + if ( pResources == nullptr ) + { + drm_log.errorf_errno( "drmModeGetResources failed" ); + return false; + } + defer( drmModeFreeResources( pResources ) ); - if ( pPlane->GetProperties().AMD_PLANE_DEGAMMA_LUT ) - pPlane->GetProperties().AMD_PLANE_DEGAMMA_LUT->SetPendingValue( req, 0, true ); + // Add connectors which appeared + for ( int i = 0; i < pResources->count_connectors; i++ ) + { + uint32_t uConnectorId = pResources->connectors[ i ]; - if ( pPlane->GetProperties().AMD_PLANE_CTM ) - pPlane->GetProperties().AMD_PLANE_CTM->SetPendingValue( req, 0, true ); + drmModeConnector *pConnector = + drmModeGetConnector( drm->fd, uConnectorId ); + if ( !pConnector ) continue; - if ( pPlane->GetProperties().AMD_PLANE_HDR_MULT ) - pPlane->GetProperties().AMD_PLANE_HDR_MULT->SetPendingValue( req, 0x100000000ULL, true ); + if ( cv_drm_ignore_internal_connectors ) + { + if ( pConnector->connector_type == DRM_MODE_CONNECTOR_eDP || + pConnector->connector_type == DRM_MODE_CONNECTOR_LVDS || + pConnector->connector_type == DRM_MODE_CONNECTOR_DSI ) + { + drmModeFreeConnector( pConnector ); + continue; + } + } - if ( pPlane->GetProperties().AMD_PLANE_SHAPER_TF ) - pPlane->GetProperties().AMD_PLANE_SHAPER_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); + if ( !drm->connectors.contains( uConnectorId ) ) + { + drm->connectors.emplace( + std::piecewise_construct, + std::forward_as_tuple( uConnectorId ), + std::forward_as_tuple( + reinterpret_cast( GetBackend( ) ), + pConnector ) ); + } + } - if ( pPlane->GetProperties().AMD_PLANE_SHAPER_LUT ) - pPlane->GetProperties().AMD_PLANE_SHAPER_LUT->SetPendingValue( req, 0, true ); + // Remove connectors which disappeared + for ( auto iter = drm->connectors.begin( ); + iter != drm->connectors.end( ); ) + { + gamescope::CDRMConnector *pConnector = &iter->second; - if ( pPlane->GetProperties().AMD_PLANE_LUT3D ) - pPlane->GetProperties().AMD_PLANE_LUT3D->SetPendingValue( req, 0, true ); + const bool bFound = std::any_of( + pResources->connectors, + pResources->connectors + pResources->count_connectors, + std::bind_front( std::equal_to{}, pConnector->GetObjectId( ) ) ); - if ( pPlane->GetProperties().AMD_PLANE_BLEND_TF ) - pPlane->GetProperties().AMD_PLANE_BLEND_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); - - if ( pPlane->GetProperties().AMD_PLANE_BLEND_LUT ) - pPlane->GetProperties().AMD_PLANE_BLEND_LUT->SetPendingValue( req, 0, true ); - } - - // We can't do a non-blocking commit here or else risk EBUSY in case the - // previous page-flip is still in flight. - uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET; - int ret = drmModeAtomicCommit( drm->fd, req, flags, nullptr ); - if ( ret != 0 ) { - drm_log.errorf_errno( "finish_drm: drmModeAtomicCommit failed" ); - } - drmModeAtomicFree(req); - - free(drm->device_name); - - wlr_drm_format_set_finish( &drm->formats ); - wlr_drm_format_set_finish( &drm->primary_formats ); - drm->m_FbIdsInRequest.clear(); - { - std::unique_lock lock( drm->m_QueuedFbIdsMutex ); - drm->m_QueuedFbIds.clear(); - } - { - std::unique_lock lock( drm->m_mutVisibleFbIds ); - drm->m_VisibleFbIds.clear(); - } - drm->sdr_static_metadata = nullptr; - drm->current = drm_t::drm_state_t{}; - drm->pending = drm_t::drm_state_t{}; - drm->planes.clear(); - drm->crtcs.clear(); - drm->connectors.clear(); - - - - // We can't close the DRM FD here, it might still be in use by the - // page-flip handler thread. -} + if ( !bFound ) + { + drm_log.debugf( + "Connector '%s' disappeared.", pConnector->GetName( ) ); + + if ( drm->pConnector == pConnector ) + { + drm_log.infof( + "Current connector '%s' disappeared.", + pConnector->GetName( ) ); + drm->pConnector = nullptr; + } + + iter = drm->connectors.erase( iter ); + } + else + iter++; + } -gamescope::OwningRc drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_dmabuf_attributes *dma_buf ) -{ - gamescope::OwningRc pBackendFb; - uint32_t fb_id = 0; - - if ( !wlr_drm_format_set_has( &drm->formats, dma_buf->format, dma_buf->modifier ) ) - { - drm_log.errorf( "Cannot import FB to DRM: format 0x%" PRIX32 " and modifier 0x%" PRIX64 " not supported for scan-out", dma_buf->format, dma_buf->modifier ); - return nullptr; - } - - uint32_t handles[4] = {0}; - uint64_t modifiers[4] = {0}; - for ( int i = 0; i < dma_buf->n_planes; i++ ) { - if ( drmPrimeFDToHandle( drm->fd, dma_buf->fd[i], &handles[i] ) != 0 ) - { - drm_log.errorf_errno("drmPrimeFDToHandle failed"); - goto out; - } - - /* KMS requires all planes to have the same modifier */ - modifiers[i] = dma_buf->modifier; - } - - if ( dma_buf->modifier != DRM_FORMAT_MOD_INVALID ) - { - if ( !drm->allow_modifiers ) - { - drm_log.errorf("Cannot import DMA-BUF: has a modifier (0x%" PRIX64 "), but KMS doesn't support them", dma_buf->modifier); - goto out; - } - - if ( drmModeAddFB2WithModifiers( drm->fd, dma_buf->width, dma_buf->height, dma_buf->format, handles, dma_buf->stride, dma_buf->offset, modifiers, &fb_id, DRM_MODE_FB_MODIFIERS ) != 0 ) - { - drm_log.errorf_errno("drmModeAddFB2WithModifiers failed"); - goto out; - } - } - else - { - if ( drmModeAddFB2( drm->fd, dma_buf->width, dma_buf->height, dma_buf->format, handles, dma_buf->stride, dma_buf->offset, &fb_id, 0 ) != 0 ) - { - drm_log.errorf_errno("drmModeAddFB2 failed"); - goto out; - } - } - - drm_log.debugf("make fbid %u", fb_id); - - pBackendFb = new gamescope::CDRMFb( fb_id ); + // Re-probe connectors props and status) + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + pConnector->RefreshState( ); + } -out: - for ( int i = 0; i < dma_buf->n_planes; i++ ) { - if ( handles[i] == 0 ) - continue; - - // GEM handles aren't ref'counted by the kernel. Two DMA-BUFs may - // return the same GEM handle, we need to be careful not to - // double-close them. - bool already_closed = false; - for ( int j = 0; j < i; j++ ) { - if ( handles[i] == handles[j] ) - already_closed = true; - } - if ( already_closed ) - continue; - - struct drm_gem_close args = { .handle = handles[i] }; - if ( drmIoctl( drm->fd, DRM_IOCTL_GEM_CLOSE, &args ) != 0 ) { - drm_log.errorf_errno( "drmIoctl(GEM_CLOSE) failed" ); - } - } - - return pBackendFb; -} + for ( std::unique_ptr &pCRTC : drm->crtcs ) + pCRTC->RefreshState( ); -static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) -{ - gamescope::IBackendConnector *pInternalConnector = GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ); - if ( pInternalConnector ) - { - gamescope::CDRMConnector *pDRMInternalConnector = static_cast( pInternalConnector ); - const drmModeModeInfo *pInternalMode = pMode; - if ( pDRMInternalConnector != drm->pConnector ) - pInternalMode = find_mode( pDRMInternalConnector->GetModeConnector(), 0, 0, 0 ); - - pDRMInternalConnector->UpdateEffectiveOrientation( pInternalMode ); - } - - gamescope::IBackendConnector *pExternalConnector = GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ); - if ( pExternalConnector ) - { - gamescope::CDRMConnector *pDRMExternalConnector = static_cast( pExternalConnector ); - const drmModeModeInfo *pExternalMode = pMode; - if ( pDRMExternalConnector != drm->pConnector ) - pExternalMode = find_mode( pDRMExternalConnector->GetModeConnector(), 0, 0, 0 ); - - pDRMExternalConnector->UpdateEffectiveOrientation( pExternalMode ); - } -} + for ( std::unique_ptr &pPlane : drm->planes ) + pPlane->RefreshState( ); -// Only used for NV12 buffers -static drm_color_encoding drm_get_color_encoding(EStreamColorspace colorspace) -{ - switch (colorspace) - { - default: - case k_EStreamColorspace_Unknown: - return DRM_COLOR_YCBCR_BT709; - - case k_EStreamColorspace_BT601: - return DRM_COLOR_YCBCR_BT601; - case k_EStreamColorspace_BT601_Full: - return DRM_COLOR_YCBCR_BT601; - - case k_EStreamColorspace_BT709: - return DRM_COLOR_YCBCR_BT709; - case k_EStreamColorspace_BT709_Full: - return DRM_COLOR_YCBCR_BT709; - } + return true; } -static drm_color_range drm_get_color_range(EStreamColorspace colorspace) +static bool get_resources( struct drm_t *drm ) { - switch (colorspace) - { - default: - case k_EStreamColorspace_Unknown: - return DRM_COLOR_YCBCR_FULL_RANGE; + { + drmModeRes *pResources = drmModeGetResources( drm->fd ); + if ( !pResources ) + { + drm_log.errorf_errno( "drmModeGetResources failed" ); + return false; + } + defer( drmModeFreeResources( pResources ) ); + + for ( int i = 0; i < pResources->count_crtcs; i++ ) + { + drmModeCrtc *pCRTC = + drmModeGetCrtc( drm->fd, pResources->crtcs[ i ] ); + if ( pCRTC ) + drm->crtcs.emplace_back( + std::make_unique( pCRTC, 1u << i ) ); + } + } - case k_EStreamColorspace_BT601: - return DRM_COLOR_YCBCR_LIMITED_RANGE; - case k_EStreamColorspace_BT601_Full: - return DRM_COLOR_YCBCR_FULL_RANGE; + { + drmModePlaneRes *pPlaneResources = drmModeGetPlaneResources( drm->fd ); + if ( !pPlaneResources ) + { + drm_log.errorf_errno( "drmModeGetPlaneResources failed" ); + return false; + } + defer( drmModeFreePlaneResources( pPlaneResources ) ); - case k_EStreamColorspace_BT709: - return DRM_COLOR_YCBCR_LIMITED_RANGE; - case k_EStreamColorspace_BT709_Full: - return DRM_COLOR_YCBCR_FULL_RANGE; - } -} + for ( uint32_t i = 0; i < pPlaneResources->count_planes; i++ ) + { + drmModePlane *pPlane = + drmModeGetPlane( drm->fd, pPlaneResources->planes[ i ] ); + if ( pPlane ) + drm->planes.emplace_back( + std::make_unique( pPlane ) ); + } + } -template -void hash_combine(size_t& s, const T& v) -{ - std::hash h; - s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); + return refresh_state( drm ); } -struct LiftoffStateCacheEntry +struct mode_blocklist_entry { - LiftoffStateCacheEntry() - { - memset(this, 0, sizeof(LiftoffStateCacheEntry)); - } - - int nLayerCount; - - struct LiftoffLayerState_t - { - bool ycbcr; - uint32_t zpos; - uint32_t srcW, srcH; - uint32_t crtcX, crtcY, crtcW, crtcH; - uint16_t opacity; - drm_color_encoding colorEncoding; - drm_color_range colorRange; - GamescopeAppTextureColorspace colorspace; - AlphaBlendingMode_t eAlphaBlendingMode; - } layerState[ k_nMaxLayers ]; - - bool operator == (const LiftoffStateCacheEntry& entry) const - { - return !memcmp(this, &entry, sizeof(LiftoffStateCacheEntry)); - } + uint32_t width, height, refresh; }; -struct LiftoffStateCacheEntryKasher -{ - size_t operator()(const LiftoffStateCacheEntry& k) const - { - size_t hash = 0; - hash_combine(hash, k.nLayerCount); - for ( int i = 0; i < k.nLayerCount; i++ ) - { - hash_combine(hash, k.layerState[i].ycbcr); - hash_combine(hash, k.layerState[i].zpos); - hash_combine(hash, k.layerState[i].srcW); - hash_combine(hash, k.layerState[i].srcH); - hash_combine(hash, k.layerState[i].crtcX); - hash_combine(hash, k.layerState[i].crtcY); - hash_combine(hash, k.layerState[i].crtcW); - hash_combine(hash, k.layerState[i].crtcH); - hash_combine(hash, k.layerState[i].opacity); - hash_combine(hash, k.layerState[i].colorEncoding); - hash_combine(hash, k.layerState[i].colorRange); - hash_combine(hash, k.layerState[i].colorspace); - hash_combine(hash, k.layerState[i].eAlphaBlendingMode); - } - - return hash; - } +// Filter out reporting some modes that are required for +// certain certifications, but are completely useless, +// and probably don't fit the display pixel size. +static mode_blocklist_entry g_badModes[] = { + { 4096, 2160, 0 }, }; +static const drmModeModeInfo *find_mode( + const drmModeConnector *connector, + int hdisplay, + int vdisplay, + uint32_t vrefresh ) +{ + for ( int i = 0; i < connector->count_modes; i++ ) + { + const drmModeModeInfo *mode = &connector->modes[ i ]; -std::unordered_set g_LiftoffStateCache; - -static inline amdgpu_transfer_function colorspace_to_plane_degamma_tf(GamescopeAppTextureColorspace colorspace) -{ - switch ( colorspace ) - { - default: // Linear in this sense is SRGB. Linear = sRGB image view doing automatic sRGB -> Linear which doesn't happen on DRM side. - case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: - return AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; - case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: - case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: - // Use LINEAR TF for scRGB float format as 80 nit = 1.0 in scRGB, which matches - // what PQ TF decodes to/encodes from. - // AMD internal format is FP16, and generally expected for 1.0 -> 80 nit. - // which just so happens to match scRGB. - return AMDGPU_TRANSFER_FUNCTION_IDENTITY; - case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: - return AMDGPU_TRANSFER_FUNCTION_PQ_EOTF; - } -} - -static inline amdgpu_transfer_function colorspace_to_plane_shaper_tf(GamescopeAppTextureColorspace colorspace) -{ - switch ( colorspace ) - { - default: - case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: - return AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF; - case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: // scRGB Linear -> PQ for shaper + 3D LUT - case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: - return AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF; - case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: - return AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } -} + bool bad = false; + for ( const auto &badMode : g_badModes ) + { + bad |= + ( badMode.width == 0 || mode->hdisplay == badMode.width ) && + ( badMode.height == 0 || mode->vdisplay == badMode.height ) && + ( badMode.refresh == 0 || mode->vrefresh == badMode.refresh ); + } -static inline amdgpu_transfer_function inverse_tf(amdgpu_transfer_function tf) -{ - switch ( tf ) - { - default: - case AMDGPU_TRANSFER_FUNCTION_DEFAULT: - return AMDGPU_TRANSFER_FUNCTION_DEFAULT; - case AMDGPU_TRANSFER_FUNCTION_IDENTITY: - return AMDGPU_TRANSFER_FUNCTION_IDENTITY; - case AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF: - return AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF; - case AMDGPU_TRANSFER_FUNCTION_BT709_OETF: - return AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF; - case AMDGPU_TRANSFER_FUNCTION_PQ_EOTF: - return AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF; - case AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF: - return AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF; - case AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF: - return AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF; - case AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF: - return AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF; - case AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF: - return AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; - case AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF: - return AMDGPU_TRANSFER_FUNCTION_BT709_OETF; - case AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF: - return AMDGPU_TRANSFER_FUNCTION_PQ_EOTF; - case AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF: - return AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF; - case AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF: - return AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF; - case AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF: - return AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF; - } -} + if ( bad ) continue; -static inline uint32_t ColorSpaceToEOTFIndex( GamescopeAppTextureColorspace colorspace ) -{ - switch ( colorspace ) - { - default: - case GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR: // Not actually linear, just Linear vs sRGB image views in Vulkan. Still viewed as sRGB on the DRM side. - case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: - // SDR sRGB content treated as native Gamma 22 curve. No need to do sRGB -> 2.2 or whatever. - return EOTF_Gamma22; - case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: - // Okay, so this is WEIRD right? OKAY Let me explain it to you. - // The plan for scRGB content is to go from scRGB -> PQ in a SHAPER_TF - // before indexing into the shaper. (input from colorspace_to_plane_regamma_tf!) - return EOTF_PQ; - case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: - return EOTF_PQ; - } -} + if ( hdisplay != 0 && hdisplay != mode->hdisplay ) continue; + if ( vdisplay != 0 && vdisplay != mode->vdisplay ) continue; + if ( vrefresh != 0 && vrefresh != mode->vrefresh ) continue; + return mode; + } -LiftoffStateCacheEntry FrameInfoToLiftoffStateCacheEntry( struct drm_t *drm, const FrameInfo_t *frameInfo ) -{ - LiftoffStateCacheEntry entry{}; - - entry.nLayerCount = frameInfo->layerCount; - for ( int i = 0; i < entry.nLayerCount; i++ ) - { - const uint16_t srcWidth = frameInfo->layers[ i ].tex->width(); - const uint16_t srcHeight = frameInfo->layers[ i ].tex->height(); - - int32_t crtcX = -frameInfo->layers[ i ].offset.x; - int32_t crtcY = -frameInfo->layers[ i ].offset.y; - uint64_t crtcW = srcWidth / frameInfo->layers[ i ].scale.x; - uint64_t crtcH = srcHeight / frameInfo->layers[ i ].scale.y; - - if (g_bRotated) - { - int64_t imageH = frameInfo->layers[ i ].tex->contentHeight() / frameInfo->layers[ i ].scale.y; - - const int32_t x = crtcX; - const uint64_t w = crtcW; - crtcX = g_nOutputHeight - imageH - crtcY; - crtcY = x; - crtcW = crtcH; - crtcH = w; - } - - entry.layerState[i].zpos = frameInfo->layers[ i ].zpos; - entry.layerState[i].srcW = srcWidth << 16; - entry.layerState[i].srcH = srcHeight << 16; - entry.layerState[i].crtcX = crtcX; - entry.layerState[i].crtcY = crtcY; - entry.layerState[i].crtcW = crtcW; - entry.layerState[i].crtcH = crtcH; - entry.layerState[i].opacity = frameInfo->layers[i].opacity * 0xffff; - entry.layerState[i].ycbcr = frameInfo->layers[i].isYcbcr(); - if ( entry.layerState[i].ycbcr ) - { - entry.layerState[i].colorEncoding = drm_get_color_encoding( g_ForcedNV12ColorSpace ); - entry.layerState[i].colorRange = drm_get_color_range( g_ForcedNV12ColorSpace ); - entry.layerState[i].colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - } - else - { - entry.layerState[i].colorspace = frameInfo->layers[ i ].colorspace; - } - entry.layerState[i].eAlphaBlendingMode = frameInfo->layers[i].eAlphaBlendingMode; - } - - return entry; + return NULL; } -static bool is_liftoff_caching_enabled() +static std::unordered_map +parse_connector_priorities( const char *str ) { - static bool disabled = env_to_bool(getenv("GAMESCOPE_LIFTOFF_CACHE_DISABLE")); - return !disabled; + std::unordered_map priorities{}; + if ( !str ) { return priorities; } + int i = 0; + char *buf = strdup( str ); + char *name = strtok( buf, "," ); + while ( name ) + { + priorities[ name ] = i; + i++; + name = strtok( nullptr, "," ); + } + free( buf ); + return priorities; } -namespace gamescope +static int get_connector_priority( struct drm_t *drm, const char *name ) { - //////////////////// - // CDRMAtomicObject - //////////////////// - CDRMAtomicObject::CDRMAtomicObject( uint32_t ulObjectId ) - : m_ulObjectId{ ulObjectId } - { - } - - - ///////////////////////// - // CDRMAtomicTypedObject - ///////////////////////// - template < uint32_t DRMObjectType > - CDRMAtomicTypedObject::CDRMAtomicTypedObject( uint32_t ulObjectId ) - : CDRMAtomicObject{ ulObjectId } - { - } - - template < uint32_t DRMObjectType > - std::optional CDRMAtomicTypedObject::GetRawProperties() - { - drmModeObjectProperties *pProperties = drmModeObjectGetProperties( g_DRM.fd, m_ulObjectId, DRMObjectType ); - if ( !pProperties ) - { - drm_log.errorf_errno( "drmModeObjectGetProperties failed" ); - return std::nullopt; - } - defer( drmModeFreeObjectProperties( pProperties ) ); - - DRMObjectRawProperties rawProperties; - for ( uint32_t i = 0; i < pProperties->count_props; i++ ) - { - drmModePropertyRes *pProperty = drmModeGetProperty( g_DRM.fd, pProperties->props[ i ] ); - if ( !pProperty ) - continue; - defer( drmModeFreeProperty( pProperty ) ); - - rawProperties[ pProperty->name ] = DRMObjectRawProperty{ pProperty->prop_id, pProperties->prop_values[ i ] }; - } - - return rawProperties; - } - - - ///////////////////////// - // CDRMAtomicProperty - ///////////////////////// - CDRMAtomicProperty::CDRMAtomicProperty( CDRMAtomicObject *pObject, DRMObjectRawProperty rawProperty ) - : m_pObject{ pObject } - , m_uPropertyId{ rawProperty.uPropertyId } - , m_ulPendingValue{ rawProperty.ulValue } - , m_ulCurrentValue{ rawProperty.ulValue } - , m_ulInitialValue{ rawProperty.ulValue } - { - } - - /*static*/ std::optional CDRMAtomicProperty::Instantiate( const char *pszName, CDRMAtomicObject *pObject, const DRMObjectRawProperties& rawProperties ) - { - auto iter = rawProperties.find( pszName ); - if ( iter == rawProperties.end() ) - return std::nullopt; - - return CDRMAtomicProperty{ pObject, iter->second }; - } - - int CDRMAtomicProperty::SetPendingValue( drmModeAtomicReq *pRequest, uint64_t ulValue, bool bForce /*= false*/ ) - { - // In instances where we rolled back due to -EINVAL, or we want to ensure a value from an unclean state - // eg. from an unclean or other initial state, you can force an update in the request with bForce. - - if ( ulValue == m_ulPendingValue && !bForce ) - return 0; - - int ret = drmModeAtomicAddProperty( pRequest, m_pObject->GetObjectId(), m_uPropertyId, ulValue ); - if ( ret < 0 ) - return ret; - - m_ulPendingValue = ulValue; - return ret; - } - - void CDRMAtomicProperty::OnCommit() - { - m_ulCurrentValue = m_ulPendingValue; - } - - void CDRMAtomicProperty::Rollback() - { - m_ulPendingValue = m_ulCurrentValue; - } - - ///////////////////////// - // CDRMPlane - ///////////////////////// - CDRMPlane::CDRMPlane( drmModePlane *pPlane ) - : CDRMAtomicTypedObject( pPlane->plane_id ) - , m_pPlane{ pPlane, []( drmModePlane *pPlane ){ drmModeFreePlane( pPlane ); } } - { - RefreshState(); - } - - void CDRMPlane::RefreshState() - { - auto rawProperties = GetRawProperties(); - if ( rawProperties ) - { - m_Props.type = CDRMAtomicProperty::Instantiate( "type", this, *rawProperties ); - m_Props.IN_FORMATS = CDRMAtomicProperty::Instantiate( "IN_FORMATS", this, *rawProperties ); - - m_Props.FB_ID = CDRMAtomicProperty::Instantiate( "FB_ID", this, *rawProperties ); - m_Props.IN_FENCE_FD = CDRMAtomicProperty::Instantiate( "IN_FENCE_FD", this, *rawProperties ); - m_Props.CRTC_ID = CDRMAtomicProperty::Instantiate( "CRTC_ID", this, *rawProperties ); - m_Props.SRC_X = CDRMAtomicProperty::Instantiate( "SRC_X", this, *rawProperties ); - m_Props.SRC_Y = CDRMAtomicProperty::Instantiate( "SRC_Y", this, *rawProperties ); - m_Props.SRC_W = CDRMAtomicProperty::Instantiate( "SRC_W", this, *rawProperties ); - m_Props.SRC_H = CDRMAtomicProperty::Instantiate( "SRC_H", this, *rawProperties ); - m_Props.CRTC_X = CDRMAtomicProperty::Instantiate( "CRTC_X", this, *rawProperties ); - m_Props.CRTC_Y = CDRMAtomicProperty::Instantiate( "CRTC_Y", this, *rawProperties ); - m_Props.CRTC_W = CDRMAtomicProperty::Instantiate( "CRTC_W", this, *rawProperties ); - m_Props.CRTC_H = CDRMAtomicProperty::Instantiate( "CRTC_H", this, *rawProperties ); - m_Props.zpos = CDRMAtomicProperty::Instantiate( "zpos", this, *rawProperties ); - m_Props.alpha = CDRMAtomicProperty::Instantiate( "alpha", this, *rawProperties ); - m_Props.rotation = CDRMAtomicProperty::Instantiate( "rotation", this, *rawProperties ); - m_Props.COLOR_ENCODING = CDRMAtomicProperty::Instantiate( "COLOR_ENCODING", this, *rawProperties ); - m_Props.COLOR_RANGE = CDRMAtomicProperty::Instantiate( "COLOR_RANGE", this, *rawProperties ); - m_Props.AMD_PLANE_DEGAMMA_TF = CDRMAtomicProperty::Instantiate( "AMD_PLANE_DEGAMMA_TF", this, *rawProperties ); - m_Props.AMD_PLANE_DEGAMMA_LUT = CDRMAtomicProperty::Instantiate( "AMD_PLANE_DEGAMMA_LUT", this, *rawProperties ); - m_Props.AMD_PLANE_CTM = CDRMAtomicProperty::Instantiate( "AMD_PLANE_CTM", this, *rawProperties ); - m_Props.AMD_PLANE_HDR_MULT = CDRMAtomicProperty::Instantiate( "AMD_PLANE_HDR_MULT", this, *rawProperties ); - m_Props.AMD_PLANE_SHAPER_LUT = CDRMAtomicProperty::Instantiate( "AMD_PLANE_SHAPER_LUT", this, *rawProperties ); - m_Props.AMD_PLANE_SHAPER_TF = CDRMAtomicProperty::Instantiate( "AMD_PLANE_SHAPER_TF", this, *rawProperties ); - m_Props.AMD_PLANE_LUT3D = CDRMAtomicProperty::Instantiate( "AMD_PLANE_LUT3D", this, *rawProperties ); - m_Props.AMD_PLANE_BLEND_TF = CDRMAtomicProperty::Instantiate( "AMD_PLANE_BLEND_TF", this, *rawProperties ); - m_Props.AMD_PLANE_BLEND_LUT = CDRMAtomicProperty::Instantiate( "AMD_PLANE_BLEND_LUT", this, *rawProperties ); - } - } - - ///////////////////////// - // CDRMCRTC - ///////////////////////// - CDRMCRTC::CDRMCRTC( drmModeCrtc *pCRTC, uint32_t uCRTCMask ) - : CDRMAtomicTypedObject( pCRTC->crtc_id ) - , m_pCRTC{ pCRTC, []( drmModeCrtc *pCRTC ){ drmModeFreeCrtc( pCRTC ); } } - , m_uCRTCMask{ uCRTCMask } - { - RefreshState(); - } - - void CDRMCRTC::RefreshState() - { - auto rawProperties = GetRawProperties(); - if ( rawProperties ) - { - m_Props.ACTIVE = CDRMAtomicProperty::Instantiate( "ACTIVE", this, *rawProperties ); - m_Props.MODE_ID = CDRMAtomicProperty::Instantiate( "MODE_ID", this, *rawProperties ); - m_Props.GAMMA_LUT = CDRMAtomicProperty::Instantiate( "GAMMA_LUT", this, *rawProperties ); - m_Props.DEGAMMA_LUT = CDRMAtomicProperty::Instantiate( "DEGAMMA_LUT", this, *rawProperties ); - m_Props.CTM = CDRMAtomicProperty::Instantiate( "CTM", this, *rawProperties ); - m_Props.VRR_ENABLED = CDRMAtomicProperty::Instantiate( "VRR_ENABLED", this, *rawProperties ); - m_Props.OUT_FENCE_PTR = CDRMAtomicProperty::Instantiate( "OUT_FENCE_PTR", this, *rawProperties ); - m_Props.AMD_CRTC_REGAMMA_TF = CDRMAtomicProperty::Instantiate( "AMD_CRTC_REGAMMA_TF", this, *rawProperties ); - } - } - - ///////////////////////// - // CDRMConnector - ///////////////////////// - CDRMConnector::CDRMConnector( CDRMBackend *pBackend, drmModeConnector *pConnector ) - : CDRMAtomicTypedObject( pConnector->connector_id ) - , m_pBackend{ pBackend } - , m_pConnector{ pConnector, []( drmModeConnector *pConnector ){ drmModeFreeConnector( pConnector ); } } - { - RefreshState(); - } - - void CDRMConnector::RefreshState() - { - // For the connector re-poll the drmModeConnector to get new modes, etc. - // This isn't needed for CRTC/Planes in which the state is immutable for their lifetimes. - // Connectors can be re-plugged. - - // TODO: Clean this up. - m_pConnector = CAutoDeletePtr< drmModeConnector > - { - drmModeGetConnector( g_DRM.fd, m_pConnector->connector_id ), - []( drmModeConnector *pConnector ){ drmModeFreeConnector( pConnector ); } - }; - - // Sort the modes to our preference. - std::stable_sort( m_pConnector->modes, m_pConnector->modes + m_pConnector->count_modes, []( const drmModeModeInfo &a, const drmModeModeInfo &b ) - { - bool bGoodRefreshA = a.vrefresh >= 60; - bool bGoodRefreshB = b.vrefresh >= 60; - if (bGoodRefreshA != bGoodRefreshB) - return bGoodRefreshA; - - bool bPreferredA = a.type & DRM_MODE_TYPE_PREFERRED; - bool bPreferredB = b.type & DRM_MODE_TYPE_PREFERRED; - if (bPreferredA != bPreferredB) - return bPreferredA; - - int nAreaA = a.hdisplay * a.vdisplay; - int nAreaB = b.hdisplay * b.vdisplay; - if (nAreaA != nAreaB) - return nAreaA > nAreaB; - - return a.vrefresh > b.vrefresh; - } ); - - std::vector oldEdid = std::move( m_Mutable.EdidData ); - m_Mutable.EdidData.clear(); - - // Clear this information out. - m_Mutable = MutableConnectorState{}; - - m_Mutable.uPossibleCRTCMask = drmModeConnectorGetPossibleCrtcs( g_DRM.fd, GetModeConnector() ); - - // These are string constants from libdrm, no free. - const char *pszTypeStr = drmModeGetConnectorTypeName( GetModeConnector()->connector_type ); - if ( !pszTypeStr ) - pszTypeStr = "Unknown"; - - snprintf( m_Mutable.szName, sizeof( m_Mutable.szName ), "%s-%d", pszTypeStr, GetModeConnector()->connector_type_id ); - m_Mutable.szName[ sizeof( m_Mutable.szName ) - 1 ] = '\0'; - - for ( int i = 0; i < m_pConnector->count_modes; i++ ) - { - drmModeModeInfo *pMode = &m_pConnector->modes[i]; - m_Mutable.BackendModes.emplace_back( BackendMode - { - .uWidth = pMode->hdisplay, - .uHeight = pMode->vdisplay, - .uRefresh = pMode->vrefresh, - }); - } - - auto rawProperties = GetRawProperties(); - if ( rawProperties ) - { - m_Props.CRTC_ID = CDRMAtomicProperty::Instantiate( "CRTC_ID", this, *rawProperties ); - m_Props.Colorspace = CDRMAtomicProperty::Instantiate( "Colorspace", this, *rawProperties ); - m_Props.content_type = CDRMAtomicProperty::Instantiate( "content type", this, *rawProperties ); - m_Props.panel_orientation = CDRMAtomicProperty::Instantiate( "panel orientation", this, *rawProperties ); - m_Props.HDR_OUTPUT_METADATA = CDRMAtomicProperty::Instantiate( "HDR_OUTPUT_METADATA", this, *rawProperties ); - m_Props.vrr_capable = CDRMAtomicProperty::Instantiate( "vrr_capable", this, *rawProperties ); - m_Props.EDID = CDRMAtomicProperty::Instantiate( "EDID", this, *rawProperties ); - m_Props.Broadcast_RGB = CDRMAtomicProperty::Instantiate( "Broadcast RGB", this, *rawProperties ); - } - - ParseEDID(); - - if ( m_Mutable.EdidData != oldEdid ) - { - m_Mutable.bEdidChanged = true; - } - } - - int CDRMConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) - { - return HackyDRMPresent( pFrameInfo, bAsync ); - } - - void CDRMConnector::UpdateEffectiveOrientation( const drmModeModeInfo *pMode ) - { - if ( this->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL && g_DesiredInternalOrientation != GAMESCOPE_PANEL_ORIENTATION_AUTO ) - { - m_ChosenOrientation = g_DesiredInternalOrientation; - } - else - { - if ( this->GetProperties().panel_orientation ) - { - switch ( this->GetProperties().panel_orientation->GetCurrentValue() ) - { - case DRM_MODE_PANEL_ORIENTATION_NORMAL: - m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_0; - return; - case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: - m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_180; - return; - case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: - m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_90; - return; - case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: - m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_270; - return; - default: - break; - } - } - - if ( this->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && pMode ) - { - // Auto-detect portait mode for internal displays - m_ChosenOrientation = pMode->hdisplay < pMode->vdisplay - ? GAMESCOPE_PANEL_ORIENTATION_270 - : GAMESCOPE_PANEL_ORIENTATION_0; - } - else - { - m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_0; - } - } - } - - void CDRMConnector::ParseEDID() - { - if ( !GetProperties().EDID ) - return; - - uint64_t ulBlobId = GetProperties().EDID->GetCurrentValue(); - if ( !ulBlobId ) - return; - - drmModePropertyBlobRes *pBlob = drmModeGetPropertyBlob( g_DRM.fd, ulBlobId ); - if ( !pBlob ) - return; - defer( drmModeFreePropertyBlob( pBlob ) ); - - const uint8_t *pDataPointer = reinterpret_cast( pBlob->data ); - m_Mutable.EdidData = std::vector{ pDataPointer, pDataPointer + pBlob->length }; - - di_info *pInfo = di_info_parse_edid( m_Mutable.EdidData.data(), m_Mutable.EdidData.size() ); - if ( !pInfo ) - { - drm_log.errorf( "Failed to parse edid for connector: %s", m_Mutable.szName ); - return; - } - defer( di_info_destroy( pInfo ) ); - - const di_edid *pEdid = di_info_get_edid( pInfo ); - - const di_edid_vendor_product *pProduct = di_edid_get_vendor_product( pEdid ); - m_Mutable.szMakePNP[0] = pProduct->manufacturer[0]; - m_Mutable.szMakePNP[1] = pProduct->manufacturer[1]; - m_Mutable.szMakePNP[2] = pProduct->manufacturer[2]; - m_Mutable.szMakePNP[3] = '\0'; - - m_Mutable.pszMake = m_Mutable.szMakePNP; - auto pnpIter = pnps.find( m_Mutable.szMakePNP ); - if ( pnpIter != pnps.end() ) - m_Mutable.pszMake = pnpIter->second.c_str(); - - const di_edid_display_descriptor *const *pDescriptors = di_edid_get_display_descriptors( pEdid ); - for ( size_t i = 0; pDescriptors[i] != nullptr; i++ ) - { - const di_edid_display_descriptor *pDesc = pDescriptors[i]; - const di_edid_display_descriptor_tag eTag = di_edid_display_descriptor_get_tag( pDesc ); - if ( eTag == DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME ) - { - // Max length of di_edid_display_descriptor_get_string is 14 - // m_szModel is 16 bytes. - const char *pszModel = di_edid_display_descriptor_get_string( pDesc ); - strncpy( m_Mutable.szModel, pszModel, sizeof( m_Mutable.szModel ) ); - } - else if ( eTag == DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING ) - { - const char *pszDataString = di_edid_display_descriptor_get_string( pDesc ); - strncpy( m_Mutable.szDataString, pszDataString, sizeof( m_Mutable.szDataString ) ); - } - } - - drm_log.infof("Connector %s -> %s - %s", m_Mutable.szName, m_Mutable.szMakePNP, m_Mutable.szModel ); - - bool bHasKnownColorimetry = false; - bool bHasKnownHDRInfo = false; - - m_Mutable.ValidDynamicRefreshRates.clear(); - m_Mutable.fnDynamicModeGenerator = nullptr; - { - CScriptScopedLock script; - - auto oKnownDisplay = script.Manager().Gamescope().Config.LookupDisplay( script, m_Mutable.szMakePNP, pProduct->product, m_Mutable.szModel, m_Mutable.szDataString ); - if ( oKnownDisplay ) - { - sol::table tTable = oKnownDisplay->second; - - std::string_view psvPrettyName = tTable.get_or( "pretty_name", std::string_view{ "Untitled Display" } ); - drm_log.infof( "Got known display: %.*s (%.*s)", - (int)oKnownDisplay->first.size(), oKnownDisplay->first.data(), - (int)psvPrettyName.size(), psvPrettyName.data() ); - - - sol::optional otDynamicRefreshRates = tTable["dynamic_refresh_rates"]; - sol::optional ofnDynamicModegen = tTable["dynamic_modegen"]; - - if ( otDynamicRefreshRates && ofnDynamicModegen ) - { - m_Mutable.ValidDynamicRefreshRates = TableToVector( *otDynamicRefreshRates ); - - m_Mutable.fnDynamicModeGenerator = [ fnDynamicModegen = *ofnDynamicModegen ]( const drmModeModeInfo *pBaseMode, int nRefreshHz ) -> drmModeModeInfo - { - CScriptScopedLock script; - - sol::table tInMode = script->create_table(); - tInMode["clock"] = pBaseMode->clock; - - tInMode["hdisplay"] = pBaseMode->hdisplay; - tInMode["hsync_start"] = pBaseMode->hsync_start; - tInMode["hsync_end"] = pBaseMode->hsync_end; - tInMode["htotal"] = pBaseMode->htotal; - - tInMode["vdisplay"] = pBaseMode->vdisplay; - tInMode["vsync_start"] = pBaseMode->vsync_start; - tInMode["vsync_end"] = pBaseMode->vsync_end; - tInMode["vtotal"] = pBaseMode->vtotal; - - tInMode["vrefresh"] = pBaseMode->vrefresh; - - sol::function_result ret = fnDynamicModegen(tInMode, nRefreshHz); - if ( !ret.valid() || !ret.get() ) - return *pBaseMode; - - sol::table tOutMode = ret; - - drmModeModeInfo outMode = *pBaseMode; - outMode.clock = tOutMode["clock"]; - - outMode.hdisplay = tOutMode["hdisplay"]; - outMode.hsync_start = tOutMode["hsync_start"]; - outMode.hsync_end = tOutMode["hsync_end"]; - outMode.htotal = tOutMode["htotal"]; - - outMode.vdisplay = tOutMode["vdisplay"]; - outMode.vsync_start = tOutMode["vsync_start"]; - outMode.vsync_end = tOutMode["vsync_end"]; - outMode.vtotal = tOutMode["vtotal"]; - - outMode.vrefresh = tOutMode["vrefresh"]; - - snprintf( outMode.name, sizeof( outMode.name ), "%dx%d@%d.00", outMode.hdisplay, outMode.vdisplay, nRefreshHz ); - - return outMode; - }; - } - - if ( sol::optional otColorimetry = tTable["colorimetry"] ) - { - sol::table tColorimetry = *otColorimetry; - - // TODO: Add a vec2 + colorimetry type? - sol::optional otR = tColorimetry["r"]; - sol::optional otG = tColorimetry["g"]; - sol::optional otB = tColorimetry["b"]; - sol::optional otW = tColorimetry["w"]; - - if ( otR && otG && otB && otW ) - { - m_Mutable.DisplayColorimetry.primaries.r = TableToVec( *otR ); - m_Mutable.DisplayColorimetry.primaries.g = TableToVec( *otG ); - m_Mutable.DisplayColorimetry.primaries.b = TableToVec( *otB ); - m_Mutable.DisplayColorimetry.white = TableToVec( *otW ); - - bHasKnownColorimetry = true; - } - } - - if ( sol::optional otHDRInfo = tTable["hdr"] ) - { - m_Mutable.HDR.bExposeHDRSupport = otHDRInfo->get_or( "supported", false ); - m_Mutable.HDR.eOutputEncodingEOTF = otHDRInfo->get_or( "eotf", EOTF_Gamma22 ); - m_Mutable.HDR.uMaxContentLightLevel = nits_to_u16( otHDRInfo->get_or( "max_content_light_level", 400.0f ) ); - m_Mutable.HDR.uMaxFrameAverageLuminance = nits_to_u16( otHDRInfo->get_or( "max_frame_average_luminance", 400.0f ) ); - m_Mutable.HDR.uMinContentLightLevel = nits_to_u16_dark( otHDRInfo->get_or( "min_content_light_level", 0.1f ) ); - - bHasKnownHDRInfo = true; - } - } - else - { - // Unknown display, see if there are any other refresh rates in the EDID we can get. - if ( GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL || cv_drm_allow_dynamic_modes_for_external_display ) - { - const drmModeModeInfo *pPreferredMode = find_mode( m_pConnector.get(), 0, 0, 0 ); - - if ( pPreferredMode ) - { - // See if the EDID has any modes for us. - for (int i = 0; i < m_pConnector->count_modes; i++) - { - const drmModeModeInfo *pMode = &m_pConnector->modes[i]; - - if ( pMode->hdisplay != pPreferredMode->hdisplay || pMode->vdisplay != pPreferredMode->vdisplay ) - continue; - - - if ( !Algorithm::Contains( m_Mutable.ValidDynamicRefreshRates, pMode->vrefresh ) ) - { - m_Mutable.ValidDynamicRefreshRates.push_back( pMode->vrefresh ); - } - } - - std::sort( m_Mutable.ValidDynamicRefreshRates.begin(), m_Mutable.ValidDynamicRefreshRates.end() ); - } - } - } - } - - if ( !bHasKnownColorimetry ) - { - // Steam Deck OLED has calibrated chromaticity coordinates in the EDID - // for each unit. - // Other external displays probably have this too. - - const di_edid_chromaticity_coords *pChroma = di_edid_get_chromaticity_coords( pEdid ); - if ( pChroma && pChroma->red_x != 0.0f ) - { - drm_log.infof( "[colorimetry]: EDID with colorimetry detected. Using it" ); - m_Mutable.DisplayColorimetry = displaycolorimetry_t - { - .primaries = { { pChroma->red_x, pChroma->red_y }, { pChroma->green_x, pChroma->green_y }, { pChroma->blue_x, pChroma->blue_y } }, - .white = { pChroma->white_x, pChroma->white_y }, - }; - } - else - { - // Assume 709 if we have no data at all. - m_Mutable.DisplayColorimetry = displaycolorimetry_709; - } - } - - drm_log.infof( "[colorimetry]: r %f %f", m_Mutable.DisplayColorimetry.primaries.r.x, m_Mutable.DisplayColorimetry.primaries.r.y ); - drm_log.infof( "[colorimetry]: g %f %f", m_Mutable.DisplayColorimetry.primaries.g.x, m_Mutable.DisplayColorimetry.primaries.g.y ); - drm_log.infof( "[colorimetry]: b %f %f", m_Mutable.DisplayColorimetry.primaries.b.x, m_Mutable.DisplayColorimetry.primaries.b.y ); - drm_log.infof( "[colorimetry]: w %f %f", m_Mutable.DisplayColorimetry.white.x, m_Mutable.DisplayColorimetry.white.y ); - - ///////////////////// - // Parse HDR stuff. - ///////////////////// - if ( !bHasKnownHDRInfo ) - { - const di_cta_hdr_static_metadata_block *pHDRStaticMetadata = nullptr; - const di_cta_colorimetry_block *pColorimetry = nullptr; - - const di_edid_cta* pCTA = NULL; - const di_edid_ext *const *ppExts = di_edid_get_extensions( pEdid ); - for ( ; *ppExts != nullptr; ppExts++ ) - { - if ( ( pCTA = di_edid_ext_get_cta( *ppExts ) ) ) - break; - } - - if ( pCTA ) - { - const di_cta_data_block *const *ppBlocks = di_edid_cta_get_data_blocks( pCTA ); - for ( ; *ppBlocks != nullptr; ppBlocks++ ) - { - if ( di_cta_data_block_get_tag( *ppBlocks ) == DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA ) - { - pHDRStaticMetadata = di_cta_data_block_get_hdr_static_metadata( *ppBlocks ); - continue; - } - - if ( di_cta_data_block_get_tag( *ppBlocks ) == DI_CTA_DATA_BLOCK_COLORIMETRY ) - { - pColorimetry = di_cta_data_block_get_colorimetry( *ppBlocks ); - continue; - } - } - } - - if ( pColorimetry && pColorimetry->bt2020_rgb && - pHDRStaticMetadata && pHDRStaticMetadata->eotfs && pHDRStaticMetadata->eotfs->pq ) - { - m_Mutable.HDR.bExposeHDRSupport = true; - m_Mutable.HDR.eOutputEncodingEOTF = EOTF_PQ; - m_Mutable.HDR.uMaxContentLightLevel = - pHDRStaticMetadata->desired_content_max_luminance - ? nits_to_u16( pHDRStaticMetadata->desired_content_max_luminance ) - : nits_to_u16( 1499.0f ); - m_Mutable.HDR.uMaxFrameAverageLuminance = - pHDRStaticMetadata->desired_content_max_frame_avg_luminance - ? nits_to_u16( pHDRStaticMetadata->desired_content_max_frame_avg_luminance ) - : nits_to_u16( std::min( 799.f, nits_from_u16( m_Mutable.HDR.uMaxContentLightLevel ) ) ); - m_Mutable.HDR.uMinContentLightLevel = - pHDRStaticMetadata->desired_content_min_luminance - ? nits_to_u16_dark( pHDRStaticMetadata->desired_content_min_luminance ) - : nits_to_u16_dark( 0.0f ); - } - else - { - m_Mutable.HDR.bExposeHDRSupport = false; - } - } - - // Generate a default HDR10 infoframe. - if ( m_Mutable.HDR.IsHDR10() ) - { - hdr_output_metadata defaultHDRMetadata{}; - hdr_metadata_infoframe *pInfoframe = &defaultHDRMetadata.hdmi_metadata_type1; - - // To be filled in by the app based on the scene, default to desired_content_max_luminance - // - // Using display's max_fall for the default metadata max_cll to avoid displays - // overcompensating with tonemapping for SDR content. - uint16_t uDefaultInfoframeLuminances = m_Mutable.HDR.uMaxFrameAverageLuminance; - - pInfoframe->display_primaries[0].x = color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.r.x ); - pInfoframe->display_primaries[0].y = color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.r.y ); - pInfoframe->display_primaries[1].x = color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.g.x ); - pInfoframe->display_primaries[1].y = color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.g.y ); - pInfoframe->display_primaries[2].x = color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.b.x ); - pInfoframe->display_primaries[2].y = color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.b.y ); - pInfoframe->white_point.x = color_xy_to_u16( m_Mutable.DisplayColorimetry.white.x ); - pInfoframe->white_point.y = color_xy_to_u16( m_Mutable.DisplayColorimetry.white.y ); - pInfoframe->max_display_mastering_luminance = uDefaultInfoframeLuminances; - pInfoframe->min_display_mastering_luminance = m_Mutable.HDR.uMinContentLightLevel; - pInfoframe->max_cll = uDefaultInfoframeLuminances; - pInfoframe->max_fall = uDefaultInfoframeLuminances; - pInfoframe->eotf = HDMI_EOTF_ST2084; - - m_Mutable.HDR.pDefaultMetadataBlob = GetBackend()->CreateBackendBlob( defaultHDRMetadata ); - } - } - - ///////////////////////// - // CDRMFb - ///////////////////////// - CDRMFb::CDRMFb( uint32_t uFbId ) - : m_uFbId{ uFbId } - { - - } - CDRMFb::~CDRMFb() - { - // I own the fbid. - if ( drmModeRmFB( g_DRM.fd, m_uFbId ) != 0 ) - drm_log.errorf_errno( "drmModeRmFB failed" ); - m_uFbId = 0; - } -} - -static int -drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, bool needs_modeset ) -{ - auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); - - // If we are modesetting, reset the state cache, we might - // move to another CRTC or whatever which might have differing caps. - // (same with different modes) - if (needs_modeset) - g_LiftoffStateCache.clear(); - - if (is_liftoff_caching_enabled()) - { - if (g_LiftoffStateCache.count(entry) != 0) - return -EINVAL; - } - - bool bSinglePlane = frameInfo->layerCount < 2 && cv_drm_single_plane_optimizations; - - for ( int i = 0; i < k_nMaxLayers; i++ ) - { - if ( i < frameInfo->layerCount ) - { - const FrameInfo_t::Layer_t *pLayer = &frameInfo->layers[ i ]; - gamescope::CDRMFb *pDrmFb = static_cast( (pLayer->tex && pLayer->tex->GetBackendFb()) ? pLayer->tex->GetBackendFb()->EnsureImported() : nullptr ); - - if ( pDrmFb == nullptr ) - { - drm_log.debugf("drm_prepare_liftoff: layer %d has no FB", i ); - return -EINVAL; - } - - const int nFence = cv_drm_debug_disable_in_fence_fd ? -1 : g_nAlwaysSignalledSyncFile; - - - liftoff_layer_set_property( drm->lo_layers[ i ], "FB_ID", pDrmFb->GetFbId()); - liftoff_layer_set_property( drm->lo_layers[ i ], "IN_FENCE_FD", nFence ); - drm->m_FbIdsInRequest.emplace_back( pDrmFb ); - - liftoff_layer_set_property( drm->lo_layers[ i ], "zpos", entry.layerState[i].zpos ); - liftoff_layer_set_property( drm->lo_layers[ i ], "alpha", frameInfo->layers[ i ].opacity * 0xffff); - - if ( entry.layerState[i].zpos != g_zposBase ) - { - liftoff_layer_set_property( drm->lo_layers[ i ], "pixel blend mode", (uint64_t) frameInfo->layers[i].eAlphaBlendingMode ); - } - else - { - liftoff_layer_unset_property( drm->lo_layers[ i ], "pixel blend mode" ); - } - - liftoff_layer_set_property( drm->lo_layers[ i ], "SRC_X", 0); - liftoff_layer_set_property( drm->lo_layers[ i ], "SRC_Y", 0); - liftoff_layer_set_property( drm->lo_layers[ i ], "SRC_W", entry.layerState[i].srcW ); - liftoff_layer_set_property( drm->lo_layers[ i ], "SRC_H", entry.layerState[i].srcH ); - - uint64_t ulOrientation = DRM_MODE_ROTATE_0; - switch ( drm->pConnector->GetCurrentOrientation() ) - { - default: - case GAMESCOPE_PANEL_ORIENTATION_0: - ulOrientation = DRM_MODE_ROTATE_0; - break; - case GAMESCOPE_PANEL_ORIENTATION_270: - ulOrientation = DRM_MODE_ROTATE_270; - break; - case GAMESCOPE_PANEL_ORIENTATION_90: - ulOrientation = DRM_MODE_ROTATE_90; - break; - case GAMESCOPE_PANEL_ORIENTATION_180: - ulOrientation = DRM_MODE_ROTATE_180; - break; - } - liftoff_layer_set_property( drm->lo_layers[ i ], "rotation", ulOrientation ); - - liftoff_layer_set_property( drm->lo_layers[ i ], "CRTC_X", entry.layerState[i].crtcX); - liftoff_layer_set_property( drm->lo_layers[ i ], "CRTC_Y", entry.layerState[i].crtcY); - - liftoff_layer_set_property( drm->lo_layers[ i ], "CRTC_W", entry.layerState[i].crtcW); - liftoff_layer_set_property( drm->lo_layers[ i ], "CRTC_H", entry.layerState[i].crtcH); - - if ( frameInfo->layers[i].applyColorMgmt ) - { - bool bYCbCr = entry.layerState[i].ycbcr; - - if ( !cv_drm_debug_disable_color_encoding && bYCbCr ) - { - liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_ENCODING", entry.layerState[i].colorEncoding ); - } - else - { - liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_ENCODING" ); - } - - if ( !cv_drm_debug_disable_color_range && bYCbCr ) - { - liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_RANGE", entry.layerState[i].colorRange ); - } - else - { - liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" ); - } - - if ( drm_supports_color_mgmt( drm ) ) - { - amdgpu_transfer_function degamma_tf = colorspace_to_plane_degamma_tf( entry.layerState[i].colorspace ); - amdgpu_transfer_function shaper_tf = colorspace_to_plane_shaper_tf( entry.layerState[i].colorspace ); - - if ( bYCbCr ) - { - // JoshA: Based on the Steam In-Home Streaming Shader, - // it looks like Y is actually sRGB, not HDTV G2.4 - // - // Matching BT709 for degamma -> regamma on shaper TF here - // is identity and works on YUV NV12 planes to preserve this. - // - // Doing LINEAR/DEFAULT here introduces banding so... this is the best way. - // (sRGB DEGAMMA does NOT work on YUV planes!) - degamma_tf = AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF; - shaper_tf = AMDGPU_TRANSFER_FUNCTION_BT709_OETF; - } - - bool bUseDegamma = !cv_drm_debug_disable_degamma_tf; - if ( bUseDegamma ) - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", degamma_tf ); - else - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", 0 ); - - bool bUseShaperAnd3DLUT = !cv_drm_debug_disable_shaper_and_3dlut; - if ( bUseShaperAnd3DLUT ) - { - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_TF", shaper_tf ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); - // Josh: See shaders/colorimetry.h colorspace_blend_tf if you have questions as to why we start doing sRGB for BLEND_TF despite potentially working in Gamma 2.2 space prior. - } - else - { - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_LUT", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_TF", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_LUT3D", 0 ); - } - } - } - else - { - if ( drm_supports_color_mgmt( drm ) ) - { - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_LUT", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_TF", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_LUT3D", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 ); - } - } - - if ( drm_supports_color_mgmt( drm ) ) - { - if (!cv_drm_debug_disable_blend_tf && !bSinglePlane) - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", drm->pending.output_tf ); - else - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT ); - - if (!cv_drm_debug_disable_ctm && frameInfo->layers[i].ctm != nullptr) - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_CTM", frameInfo->layers[i].ctm->GetBlobValue() ); - else - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 ); - } - } - else - { - liftoff_layer_set_property( drm->lo_layers[ i ], "FB_ID", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "IN_FENCE_FD", -1 ); - - liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_ENCODING" ); - liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" ); - liftoff_layer_unset_property( drm->lo_layers[ i ], "pixel blend mode" ); - - if ( drm_supports_color_mgmt( drm ) ) - { - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_LUT", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_SHAPER_TF", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_LUT3D", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT ); - liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 ); - } - } - } - - struct liftoff_output_apply_options lo_options = { - .timeout_ns = std::numeric_limits::max() - }; - - int ret = liftoff_output_apply( drm->lo_output, drm->req, drm->flags, &lo_options); - - // The NVIDIA 555 series drivers started advertising DRM_CAP_SYNCOBJ, but do - // not support IN_FENCE_FD. However, there is no way to hide the IN_FENCE_FD - // property in a DRM-KMS driver, so the driver returns EPERM when an - // application sets IN_FENCE_FD. To work around this, the first time a - // commit fails with -EPERM, try it again with the IN_FENCE_FD property - // reset to its default value. If this succeeds, disable use of the - // IN_FENCE_FD property. - static bool attempted_in_fence_fallback = false; - if ( ret == -EPERM && !attempted_in_fence_fallback && !cv_drm_debug_disable_in_fence_fd ) - { - attempted_in_fence_fallback = true; - for ( int i = 0; i < frameInfo->layerCount; i++ ) - { - liftoff_layer_set_property( drm->lo_layers[ i ], "IN_FENCE_FD", -1 ); - } - - ret = liftoff_output_apply( drm->lo_output, drm->req, drm->flags, &lo_options ); - - if ( ret == 0 ) - { - // IN_FENCE_FD isn't actually supported. Avoid it in the future. - cv_drm_debug_disable_in_fence_fd = true; - } - } - - if ( ret == 0 ) - { - // We don't support partial composition yet - if ( liftoff_output_needs_composition( drm->lo_output ) ) - ret = -EINVAL; - } - - // If we aren't modesetting and we got -EINVAL, that means that we - // probably can't do this layout, so add it to our state cache so we don't - // try it again. - if (!needs_modeset) - { - if (ret == -EINVAL) - g_LiftoffStateCache.insert(entry); - } - - if ( ret == 0 ) - drm_log.debugf( "can drm present %i layers", frameInfo->layerCount ); - else - drm_log.debugf( "can NOT drm present %i layers", frameInfo->layerCount ); - - return ret; + if ( drm->connector_priorities.count( name ) > 0 ) + { + return drm->connector_priorities[ name ]; + } + if ( drm->connector_priorities.count( "*" ) > 0 ) + { + return drm->connector_priorities[ "*" ]; + } + return drm->connector_priorities.size( ); } -bool g_bForceAsyncFlips = false; - -void drm_rollback( struct drm_t *drm ) +static bool get_saved_mode( const char *description, saved_mode &mode_info ) { - drm->pending = drm->current; - - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - { - for ( std::optional &oProperty : pCRTC->GetProperties() ) - { - if ( oProperty ) - oProperty->Rollback(); - } - } - - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - { - for ( std::optional &oProperty : pPlane->GetProperties() ) - { - if ( oProperty ) - oProperty->Rollback(); - } - } - - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - for ( std::optional &oProperty : pConnector->GetProperties() ) - { - if ( oProperty ) - oProperty->Rollback(); - } - } + const char *mode_file = getenv( "GAMESCOPE_MODE_SAVE_FILE" ); + if ( !mode_file || !*mode_file ) return false; + + FILE *file = fopen( mode_file, "r" ); + if ( !file ) return false; + + char line[ 256 ]; + while ( fgets( line, sizeof( line ), file ) ) + { + char saved_description[ 256 ]; + uint32_t broadcast_mode = 0; + int ret = sscanf( + line, + "%255[^:]:%dx%d@%d %u", + saved_description, + &mode_info.width, + &mode_info.height, + &mode_info.refresh, + &broadcast_mode ); + + mode_info.broadcast_mode = + ( GamescopeBroadcastRGBMode_t )broadcast_mode; + + bool valid = ret == 4 || ret == 5; + + if ( valid && !strcmp( saved_description, description ) ) + { + fclose( file ); + return true; + } + } + fclose( file ); + return false; } -/* Prepares an atomic commit for the provided scene-graph. Returns 0 on success, - * negative errno on failure or if the scene-graph can't be presented directly. */ -int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ) -{ - if ( !drm->pConnector ) - return -EACCES; - - drm_update_color_mgmt(drm); - - const bool bIsVRRCapable = drm->pConnector && drm->pConnector->GetProperties().vrr_capable && !!drm->pConnector->GetProperties().vrr_capable->GetCurrentValue(); - const bool bHasVRREnable = drm->pCRTC && drm->pCRTC->GetProperties().VRR_ENABLED; - - const bool bVRREnabled = bIsVRRCapable && bHasVRREnable && frameInfo->allowVRR; - if ( bIsVRRCapable ) - { - if ( bVRREnabled != !!drm->pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue() ) - drm->needs_modeset = true; - } - - drm_colorspace uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; - - const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; - gamescope::BackendBlob *pHDRMetadata = nullptr; - if ( drm->pConnector && drm->pConnector->SupportsHDR10() ) - { - if ( bWantsHDR10 ) - { - pHDRMetadata = drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); - - wlserver_vk_swapchain_feedback* pFeedback = steamcompmgr_get_base_layer_swapchain_feedback(); - if ( pFeedback && pFeedback->hdr_metadata_blob != nullptr ) - pHDRMetadata = pFeedback->hdr_metadata_blob.get(); - uColorimetry = DRM_MODE_COLORIMETRY_BT2020_RGB; - } - else - { - pHDRMetadata = drm->sdr_static_metadata.get(); - uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; - } +static GamescopeBroadcastRGBMode_t s_ExternalBroadcastRGBMode = + GAMESCOPE_BROADCAST_RGB_MODE_AUTOMATIC; - if ( uColorimetry != drm->pConnector->GetProperties().Colorspace->GetCurrentValue() ) - drm->needs_modeset = true; - } +static bool setup_best_connector( struct drm_t *drm, bool force, bool initial ) +{ + if ( drm->pConnector && drm->pConnector->GetModeConnector( )->connection != + DRM_MODE_CONNECTED ) + { + drm_log.infof( + "current connector '%s' disconnected", + drm->pConnector->GetName( ) ); + drm->pConnector = nullptr; + } - drm->m_FbIdsInRequest.clear(); + gamescope::CDRMConnector *best = nullptr; + int nBestPriority = INT_MAX; + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; - bool needs_modeset = drm->needs_modeset.exchange(false); + if ( pConnector->GetModeConnector( )->connection != DRM_MODE_CONNECTED ) + continue; - assert( drm->req == nullptr ); - drm->req = drmModeAtomicAlloc(); + if ( g_bForceInternal && pConnector->GetScreenType( ) == + gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) + continue; - bool bSinglePlane = frameInfo->layerCount < 2 && cv_drm_single_plane_optimizations; + int nPriority = get_connector_priority( drm, pConnector->GetName( ) ); + if ( nPriority < nBestPriority ) + { + best = pConnector; + nBestPriority = nPriority; + } + } - if ( drm_supports_color_mgmt( &g_DRM ) && frameInfo->applyOutputColorMgmt ) - { - if ( !cv_drm_debug_disable_output_tf && !bSinglePlane ) - { - drm->pending.output_tf = g_bOutputHDREnabled - ? AMDGPU_TRANSFER_FUNCTION_PQ_EOTF - : AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; - } - else - { - drm->pending.output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } - } - else - { - drm->pending.output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } - - uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; - - // We do internal refcounting with these events - - bool bSleep = false; - if ( drm->pConnector ) - { - bSleep = cv_drm_sleep_screens[ drm->pConnector->GetScreenType() ]; - - bool bCurrentlyAsleep = drm->pConnector->GetProperties().CRTC_ID->GetCurrentValue() == 0; - - if ( bCurrentlyAsleep != bSleep ) - needs_modeset = true; - } - - if ( !bSleep ) - { - if ( drm->pCRTC != nullptr ) - flags |= DRM_MODE_PAGE_FLIP_EVENT; - - if ( async || g_bForceAsyncFlips ) - flags |= DRM_MODE_PAGE_FLIP_ASYNC; - } - - bool bForceInRequest = needs_modeset; - - if ( needs_modeset ) - { - flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - - // Disable all connectors and CRTCs - - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - if ( pConnector->GetProperties().CRTC_ID->GetCurrentValue() == 0 ) - continue; - - pConnector->GetProperties().CRTC_ID->SetPendingValue( drm->req, 0, bForceInRequest ); - - if ( pConnector->GetProperties().Colorspace ) - pConnector->GetProperties().Colorspace->SetPendingValue( drm->req, 0, bForceInRequest ); - - if ( pConnector->GetProperties().HDR_OUTPUT_METADATA ) - pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, 0, bForceInRequest ); - - if ( pConnector->GetProperties().content_type ) - pConnector->GetProperties().content_type->SetPendingValue( drm->req, 0, bForceInRequest ); - - if ( pConnector->GetProperties().Broadcast_RGB ) - pConnector->GetProperties().Broadcast_RGB->SetPendingValue( drm->req, 0, bForceInRequest ); - } - - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - { - // We can't disable a CRTC if it's already disabled, or else the - // kernel will error out with "requesting event but off". - if ( pCRTC->GetProperties().ACTIVE->GetCurrentValue() == 0 ) - continue; - - pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 0, bForceInRequest ); - pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, 0, bForceInRequest ); - - if ( pCRTC->GetProperties().GAMMA_LUT ) - pCRTC->GetProperties().GAMMA_LUT->SetPendingValue( drm->req, 0, bForceInRequest ); - - if ( pCRTC->GetProperties().DEGAMMA_LUT ) - pCRTC->GetProperties().DEGAMMA_LUT->SetPendingValue( drm->req, 0, bForceInRequest ); + if ( best && best == drm->pConnector ) + { + // If the device's EDID changed from user us, force a mode-change + // as we might + if ( best->HandleEdidChange( ) ) { force = true; } + } - if ( pCRTC->GetProperties().CTM ) - pCRTC->GetProperties().CTM->SetPendingValue( drm->req, 0, bForceInRequest ); + if ( !force ) + { + if ( ( !best && drm->pConnector ) || + ( best && best == drm->pConnector ) ) + { + // Let's keep our current connector + return true; + } + } - if ( pCRTC->GetProperties().VRR_ENABLED ) - pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, 0, bForceInRequest ); + if ( best == nullptr ) + { + drm_log.infof( "cannot find any connected connector!" ); + drm_unset_connector( drm ); + drm_unset_mode( drm ); + const struct wlserver_output_info wlserver_output_info = { + .description = "Virtual screen", + }; + wlserver_lock( ); + wlserver_set_output_info( &wlserver_output_info ); + wlserver_unlock( ); + return true; + } - if ( pCRTC->GetProperties().OUT_FENCE_PTR ) - pCRTC->GetProperties().OUT_FENCE_PTR->SetPendingValue( drm->req, 0, bForceInRequest ); - - if ( pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF ) - pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF->SetPendingValue( drm->req, 0, bForceInRequest ); - } - - if ( drm->pConnector && !bSleep ) - { - // Always set our CRTC_ID for the modeset, especially - // as we zero-ed it above. - drm->pConnector->GetProperties().CRTC_ID->SetPendingValue( drm->req, drm->pCRTC->GetObjectId(), bForceInRequest ); - - if ( drm->pConnector->GetProperties().Colorspace ) - drm->pConnector->GetProperties().Colorspace->SetPendingValue( drm->req, uColorimetry, bForceInRequest ); - } + if ( !drm_set_connector( drm, best ) ) { return false; } - if ( drm->pCRTC && !bSleep ) - { - drm->pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 1u, true ); - drm->pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, drm->pending.mode_id ? drm->pending.mode_id->GetBlobValue() : 0lu, true ); + char description[ 256 ]; + if ( best->GetScreenType( ) == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + { + snprintf( description, sizeof( description ), "Internal screen" ); + } + else if ( best->GetMake( ) && best->GetModel( ) ) + { + snprintf( + description, + sizeof( description ), + "%s %s", + best->GetMake( ), + best->GetModel( ) ); + } + else if ( best->GetModel( ) ) + { + snprintf( description, sizeof( description ), "%s", best->GetModel( ) ); + } + else + { + snprintf( description, sizeof( description ), "External screen" ); + } - if ( drm->pCRTC->GetProperties().VRR_ENABLED ) - drm->pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, bVRREnabled, true ); - } - } + s_ExternalBroadcastRGBMode = GAMESCOPE_BROADCAST_RGB_MODE_AUTOMATIC; + + const drmModeModeInfo *mode = nullptr; + if ( drm->preferred_width != 0 || drm->preferred_height != 0 || + drm->preferred_refresh != 0 ) + { + mode = find_mode( + best->GetModeConnector( ), + drm->preferred_width, + drm->preferred_height, + gamescope::ConvertmHzToHz( drm->preferred_refresh ) ); + } - if ( drm->pConnector && !bSleep ) - { - if ( drm->pConnector->GetProperties().HDR_OUTPUT_METADATA ) - drm->pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, pHDRMetadata ? pHDRMetadata->GetBlobValue() : 0lu, bForceInRequest ); + if ( !mode && + best->GetScreenType( ) == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) + { + saved_mode mode_info{}; + if ( get_saved_mode( description, mode_info ) ) + { + s_ExternalBroadcastRGBMode = mode_info.broadcast_mode; + mode = find_mode( + best->GetModeConnector( ), + mode_info.width, + mode_info.height, + mode_info.refresh ); + } + } - if ( drm->pConnector->GetProperties().content_type ) - drm->pConnector->GetProperties().content_type->SetPendingValue( drm->req, DRM_MODE_CONTENT_TYPE_GAME, bForceInRequest ); + if ( !mode ) { mode = find_mode( best->GetModeConnector( ), 0, 0, 0 ); } - GamescopeBroadcastRGBMode_t eBroadcastRGB = drm->pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL - ? s_ExternalBroadcastRGBMode - : GAMESCOPE_BROADCAST_RGB_MODE_AUTOMATIC; + if ( !mode ) + { + drm_log.errorf( "could not find mode!" ); + return false; + } - if ( drm->pConnector->GetProperties().Broadcast_RGB ) - drm->pConnector->GetProperties().Broadcast_RGB->SetPendingValue( drm->req, eBroadcastRGB, bForceInRequest ); - } + if ( !drm_set_mode( drm, mode ) ) { return false; } - if ( drm->pCRTC && !bSleep ) - { - if ( drm->pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF ) - { - if ( !cv_drm_debug_disable_regamma_tf ) - drm->pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF->SetPendingValue( drm->req, inverse_tf( drm->pending.output_tf ), bForceInRequest ); - else - drm->pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF->SetPendingValue( drm->req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, bForceInRequest ); - } - } + // Don't allow rollback of mode_id after connector change + drm->current.mode_id = drm->pending.mode_id; - drm->flags = flags; + const struct wlserver_output_info wlserver_output_info = { + .description = description, + .phys_width = ( int )best->GetModeConnector( )->mmWidth, + .phys_height = ( int )best->GetModeConnector( )->mmHeight, + }; + wlserver_lock( ); + wlserver_set_output_info( &wlserver_output_info ); + wlserver_unlock( ); - int ret; - if ( drm->pCRTC == nullptr || bSleep ) { - ret = 0; - } else if ( drm->bUseLiftoff ) { - ret = drm_prepare_liftoff( drm, frameInfo, needs_modeset ); - } else { - ret = 0; - } + if ( !initial ) + WritePatchedEdid( + best->GetRawEDID( ), best->GetHDRInfo( ), g_bRotated ); - if ( ret != 0 ) { - drm_rollback( drm ); + update_connector_display_info_wl( drm ); - drmModeAtomicFree( drm->req ); - drm->req = nullptr; - - drm->m_FbIdsInRequest.clear(); - - if ( needs_modeset ) - drm->needs_modeset = true; - } - - return ret; + return true; } -bool drm_poll_state( struct drm_t *drm ) +void load_pnps( void ) { - int out_of_date = drm->out_of_date.exchange(false); - if ( !out_of_date ) - return false; +#ifdef HWDATA_PNP_IDS + const char *filename = HWDATA_PNP_IDS; + FILE *f = fopen( filename, "r" ); + if ( !f ) + { + drm_log.infof( "failed to open PNP IDs file at '%s'", filename ); + return; + } - refresh_state( drm ); + char *line = NULL; + size_t line_size = 0; + while ( getline( &line, &line_size, f ) >= 0 ) + { + char *nl = strchr( line, '\n' ); + if ( nl ) { *nl = '\0'; } - setup_best_connector(drm, out_of_date >= 2, false); + char *sep = strchr( line, '\t' ); + if ( !sep ) { continue; } + *sep = '\0'; - return true; -} + std::string id( line ); + std::string name( sep + 1 ); + pnps[ id ] = name; + } -static bool drm_set_crtc( struct drm_t *drm, gamescope::CDRMCRTC *pCRTC ) + free( line ); + fclose( f ); +#endif +} + +extern bool env_to_bool( const char *env ); + +uint32_t g_uAlwaysSignalledSyncobj = 0; +int g_nAlwaysSignalledSyncFile = -1; + +static void gamescope_liftoff_log_handler( + enum liftoff_log_priority liftoff_priority, const char *fmt, va_list args ) +{ + enum LogPriority priority = LOG_DEBUG; + + switch ( liftoff_priority ) + { + case LIFTOFF_ERROR: + priority = LOG_ERROR; + break; + case LIFTOFF_DEBUG: + priority = LOG_DEBUG; + break; + case LIFTOFF_SILENT: + priority = LOG_SILENT; + break; + } + + liftoff_log_scope.vlogf( priority, fmt, args ); +} + +bool init_drm( struct drm_t *drm, int width, int height, int refresh ) +{ + load_pnps( ); + + drm->bUseLiftoff = true; + + drm->preferred_width = width; + drm->preferred_height = height; + drm->preferred_refresh = refresh; + + drm->device_name = nullptr; + dev_t dev_id = 0; + if ( vulkan_primary_dev_id( &dev_id ) ) + { + drmDevice *drm_dev = nullptr; + if ( drmGetDeviceFromDevId( dev_id, 0, &drm_dev ) != 0 ) + { + drm_log.errorf( + "Failed to find DRM device with device ID %" PRIu64, + ( uint64_t )dev_id ); + return false; + } + assert( drm_dev->available_nodes & ( 1 << DRM_NODE_PRIMARY ) ); + drm->device_name = strdup( drm_dev->nodes[ DRM_NODE_PRIMARY ] ); + drm_log.infof( "opening DRM node '%s'", drm->device_name ); + } + else + { + drm_log.infof( "warning: picking an arbitrary DRM device" ); + } + + drm->fd = wlsession_open_kms( drm->device_name ); + if ( drm->fd < 0 ) + { + drm_log.errorf( "Could not open KMS device" ); + return false; + } + + if ( !drmIsKMS( drm->fd ) ) + { + drm_log.errorf( "'%s' is not a KMS device", drm->device_name ); + wlsession_close_kms( ); + return -1; + } + + if ( drmSetClientCap( drm->fd, DRM_CLIENT_CAP_ATOMIC, 1 ) != 0 ) + { + drm_log.errorf( "drmSetClientCap(ATOMIC) failed" ); + return false; + } + + if ( drmGetCap( drm->fd, DRM_CAP_CURSOR_WIDTH, &drm->cursor_width ) != 0 ) + { + drm->cursor_width = 64; + } + if ( drmGetCap( drm->fd, DRM_CAP_CURSOR_HEIGHT, &drm->cursor_height ) != 0 ) + { + drm->cursor_height = 64; + } + + uint64_t cap; + g_bSupportsSyncObjs = + drmGetCap( drm->fd, DRM_CAP_SYNCOBJ, &cap ) == 0 && cap != 0; + if ( g_bSupportsSyncObjs ) + { + int err = drmSyncobjCreate( + drm->fd, DRM_SYNCOBJ_CREATE_SIGNALED, &g_uAlwaysSignalledSyncobj ); + if ( err < 0 ) + { + drm_log.errorf( "Failed to create dummy signalled syncobj" ); + return false; + } + err = drmSyncobjExportSyncFile( + drm->fd, g_uAlwaysSignalledSyncobj, &g_nAlwaysSignalledSyncFile ); + if ( err < 0 ) + { + drm_log.errorf( "Failed to create dummy signalled sync file" ); + return false; + } + } + else + { + drm_log.errorf( "Syncobjs are not supported by the KMS driver" ); + } + + if ( drmGetCap( drm->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap ) == 0 && cap != 0 ) + { + drm->allow_modifiers = true; + } + + g_bSupportsAsyncFlips = + drmGetCap( drm->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap ) == 0 && + cap != 0; + if ( !g_bSupportsAsyncFlips ) + drm_log.errorf( "Immediate flips are not supported by the KMS driver" ); + + static bool async_disabled = + env_to_bool( getenv( "GAMESCOPE_DISABLE_ASYNC_FLIPS" ) ); + + if ( async_disabled ) + { + g_bSupportsAsyncFlips = false; + drm_log.errorf( "Immediate flips disabled from environment" ); + } + + if ( !get_resources( drm ) ) { return false; } + + drm->lo_device = liftoff_device_create( drm->fd ); + if ( drm->lo_device == nullptr ) return false; + if ( liftoff_device_register_all_planes( drm->lo_device ) < 0 ) + return false; + + drm_log.infof( "Connectors:" ); + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + + const char *status_str = "disconnected"; + if ( pConnector->GetModeConnector( )->connection == DRM_MODE_CONNECTED ) + status_str = "connected"; + + drm_log.infof( " %s (%s)", pConnector->GetName( ), status_str ); + } + + drm->connector_priorities = parse_connector_priorities( g_sOutputName ); + + if ( !setup_best_connector( drm, true, true ) ) { return false; } + + // Fetch formats which can be scanned out + for ( std::unique_ptr &pPlane : drm->planes ) + { + if ( !get_plane_formats( drm, pPlane.get( ), &drm->formats ) ) + return false; + } + + // TODO: intersect primary planes formats instead + if ( !drm->pPrimaryPlane ) drm->pPrimaryPlane = find_primary_plane( drm ); + + if ( !drm->pPrimaryPlane ) + { + drm_log.errorf( "Failed to find a primary plane" ); + return false; + } + + if ( !get_plane_formats( drm, drm->pPrimaryPlane, &drm->primary_formats ) ) + { + return false; + } + + // Pick a 10-bit format at first for our composition buffer, for a couple of + // reasons: + // + // 1. Many game engines automatically render to 10-bit formats such as UE4 + // which means that when we have to composite, we can keep the same HW + // dithering that we would get if we just scanned them out directly. + // + // 2. When compositing HDR content as a fallback when we undock, it avoids + // introducing a bunch of horrible banding when going to G2.2 curve. It + // ensures that we can dither that. + g_nDRMFormat = pick_plane_format( + &drm->primary_formats, DRM_FORMAT_XRGB2101010, DRM_FORMAT_ARGB2101010 ); + if ( g_nDRMFormat == DRM_FORMAT_INVALID ) + { + g_nDRMFormat = pick_plane_format( + &drm->primary_formats, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_ABGR2101010 ); + if ( g_nDRMFormat == DRM_FORMAT_INVALID ) + { + g_nDRMFormat = pick_plane_format( + &drm->primary_formats, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888 ); + if ( g_nDRMFormat == DRM_FORMAT_INVALID ) + { + drm_log.errorf( + "Primary plane doesn't support any formats >= 8888" ); + return false; + } + } + } + + if ( have_overlay_planes( drm ) ) + { + // ARGB8888 is the Xformat and AFormat here in this function as we want + // transparent overlay + g_nDRMFormatOverlay = pick_plane_format( + &drm->formats, DRM_FORMAT_ARGB2101010, DRM_FORMAT_ARGB2101010 ); + if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) + { + g_nDRMFormatOverlay = pick_plane_format( + &drm->formats, DRM_FORMAT_ABGR2101010, DRM_FORMAT_ABGR2101010 ); + if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) + { + g_nDRMFormatOverlay = pick_plane_format( + &drm->formats, DRM_FORMAT_ARGB8888, DRM_FORMAT_ARGB8888 ); + if ( g_nDRMFormatOverlay == DRM_FORMAT_INVALID ) + { + drm_log.errorf( + "Overlay plane doesn't support any formats >= 8888" ); + return false; + } + } + } + } + else + { + switch ( g_nDRMFormat ) + { + case DRM_FORMAT_XRGB2101010: + g_nDRMFormatOverlay = DRM_FORMAT_ARGB2101010; + break; + case DRM_FORMAT_ABGR2101010: + g_nDRMFormatOverlay = DRM_FORMAT_ABGR2101010; + break; + case DRM_FORMAT_XRGB8888: + g_nDRMFormatOverlay = DRM_FORMAT_ARGB8888; + break; + default: + return false; + } + } + + std::thread flip_handler_thread( flip_handler_thread_run ); + flip_handler_thread.detach( ); + + // Set log priority to the max, liftoff_log_scope will filter for us. + liftoff_log_set_priority( LIFTOFF_DEBUG ); + liftoff_log_set_handler( gamescope_liftoff_log_handler ); + + hdr_output_metadata sdr_metadata; + memset( &sdr_metadata, 0, sizeof( sdr_metadata ) ); + drm->sdr_static_metadata = GetBackend( )->CreateBackendBlob( sdr_metadata ); + + drm->needs_modeset = true; + + return true; +} + +void OnSleepScreenChanged( gamescope::ConVar & ) { force_repaint( ); } + +gamescope::ConVar cv_drm_sleep_screens[] = { + { "drm_sleep_internal_screen", + false, + "Force the internal screen to be asleep", + OnSleepScreenChanged }, + { "drm_sleep_external_screen", + false, + "Force the external screen to be asleep", + OnSleepScreenChanged }, +}; + +void drm_sleep_screen( gamescope::GamescopeScreenType eType, bool bSleep ) +{ + if ( cv_drm_sleep_screens[ eType ] == bSleep ) return; + + cv_drm_sleep_screens[ eType ] = bSleep; +} + +void finish_drm( struct drm_t *drm ) +{ + // Disable all connectors, CRTCs and planes. This is necessary to leave a + // clean KMS state behind. Some other KMS clients might not support all of + // the properties we use, e.g. "rotation" and Xorg don't play well + // together. + + drmModeAtomicReq *req = drmModeAtomicAlloc( ); + + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + + pConnector->GetProperties( ).CRTC_ID->SetPendingValue( req, 0, true ); + + if ( pConnector->GetProperties( ).Colorspace ) + pConnector->GetProperties( ).Colorspace->SetPendingValue( + req, 0, true ); + + if ( pConnector->GetProperties( ).HDR_OUTPUT_METADATA ) + { + if ( drm->sdr_static_metadata && + pConnector->GetHDRInfo( ).IsHDR10( ) ) + pConnector->GetProperties( ) + .HDR_OUTPUT_METADATA->SetPendingValue( + req, drm->sdr_static_metadata->GetBlobValue( ), true ); + else + pConnector->GetProperties( ) + .HDR_OUTPUT_METADATA->SetPendingValue( req, 0, true ); + } + + if ( pConnector->GetProperties( ).content_type ) + pConnector->GetProperties( ).content_type->SetPendingValue( + req, 0, true ); + } + + for ( std::unique_ptr &pCRTC : drm->crtcs ) + { + pCRTC->GetProperties( ).ACTIVE->SetPendingValue( req, 0, true ); + pCRTC->GetProperties( ).MODE_ID->SetPendingValue( req, 0, true ); + + if ( pCRTC->GetProperties( ).GAMMA_LUT ) + pCRTC->GetProperties( ).GAMMA_LUT->SetPendingValue( req, 0, true ); + + if ( pCRTC->GetProperties( ).DEGAMMA_LUT ) + pCRTC->GetProperties( ).DEGAMMA_LUT->SetPendingValue( + req, 0, true ); + + if ( pCRTC->GetProperties( ).CTM ) + pCRTC->GetProperties( ).CTM->SetPendingValue( req, 0, true ); + + if ( pCRTC->GetProperties( ).VRR_ENABLED ) + pCRTC->GetProperties( ).VRR_ENABLED->SetPendingValue( + req, 0, true ); + + if ( pCRTC->GetProperties( ).OUT_FENCE_PTR ) + pCRTC->GetProperties( ).OUT_FENCE_PTR->SetPendingValue( + req, 0, true ); + + if ( pCRTC->GetProperties( ).AMD_CRTC_REGAMMA_TF ) + pCRTC->GetProperties( ).AMD_CRTC_REGAMMA_TF->SetPendingValue( + req, 0, true ); + } + + for ( std::unique_ptr &pPlane : drm->planes ) + { + pPlane->GetProperties( ).FB_ID->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).IN_FENCE_FD->SetPendingValue( req, -1, true ); + pPlane->GetProperties( ).CRTC_ID->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).SRC_X->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).SRC_Y->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).SRC_W->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).SRC_H->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).CRTC_X->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).CRTC_Y->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).CRTC_W->SetPendingValue( req, 0, true ); + pPlane->GetProperties( ).CRTC_H->SetPendingValue( req, 0, true ); + + if ( pPlane->GetProperties( ).rotation ) + pPlane->GetProperties( ).rotation->SetPendingValue( + req, DRM_MODE_ROTATE_0, true ); + + if ( pPlane->GetProperties( ).alpha ) + pPlane->GetProperties( ).alpha->SetPendingValue( + req, 0xFFFF, true ); + + // if ( pPlane->GetProperties().zpos ) + // pPlane->GetProperties().zpos->SetPendingValue( req, , true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_DEGAMMA_TF ) + pPlane->GetProperties( ).AMD_PLANE_DEGAMMA_TF->SetPendingValue( + req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_DEGAMMA_LUT ) + pPlane->GetProperties( ).AMD_PLANE_DEGAMMA_LUT->SetPendingValue( + req, 0, true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_CTM ) + pPlane->GetProperties( ).AMD_PLANE_CTM->SetPendingValue( + req, 0, true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_HDR_MULT ) + pPlane->GetProperties( ).AMD_PLANE_HDR_MULT->SetPendingValue( + req, 0x100000000ULL, true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_SHAPER_TF ) + pPlane->GetProperties( ).AMD_PLANE_SHAPER_TF->SetPendingValue( + req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_SHAPER_LUT ) + pPlane->GetProperties( ).AMD_PLANE_SHAPER_LUT->SetPendingValue( + req, 0, true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_LUT3D ) + pPlane->GetProperties( ).AMD_PLANE_LUT3D->SetPendingValue( + req, 0, true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_BLEND_TF ) + pPlane->GetProperties( ).AMD_PLANE_BLEND_TF->SetPendingValue( + req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); + + if ( pPlane->GetProperties( ).AMD_PLANE_BLEND_LUT ) + pPlane->GetProperties( ).AMD_PLANE_BLEND_LUT->SetPendingValue( + req, 0, true ); + } + + // We can't do a non-blocking commit here or else risk EBUSY in case the + // previous page-flip is still in flight. + uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET; + int ret = drmModeAtomicCommit( drm->fd, req, flags, nullptr ); + if ( ret != 0 ) + { + drm_log.errorf_errno( "finish_drm: drmModeAtomicCommit failed" ); + } + drmModeAtomicFree( req ); + + free( drm->device_name ); + + wlr_drm_format_set_finish( &drm->formats ); + wlr_drm_format_set_finish( &drm->primary_formats ); + drm->m_FbIdsInRequest.clear( ); + { + std::unique_lock lock( drm->m_QueuedFbIdsMutex ); + drm->m_QueuedFbIds.clear( ); + } + { + std::unique_lock lock( drm->m_mutVisibleFbIds ); + drm->m_VisibleFbIds.clear( ); + } + drm->sdr_static_metadata = nullptr; + drm->current = drm_t::drm_state_t{}; + drm->pending = drm_t::drm_state_t{}; + drm->planes.clear( ); + drm->crtcs.clear( ); + drm->connectors.clear( ); + + // We can't close the DRM FD here, it might still be in use by the + // page-flip handler thread. +} + +gamescope::OwningRc +drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_dmabuf_attributes *dma_buf ) +{ + gamescope::OwningRc pBackendFb; + uint32_t fb_id = 0; + + if ( !wlr_drm_format_set_has( + &drm->formats, dma_buf->format, dma_buf->modifier ) ) + { + drm_log.errorf( + "Cannot import FB to DRM: format 0x%" PRIX32 + " and modifier 0x%" PRIX64 " not supported for scan-out", + dma_buf->format, + dma_buf->modifier ); + return nullptr; + } + + uint32_t handles[ 4 ] = { 0 }; + uint64_t modifiers[ 4 ] = { 0 }; + for ( int i = 0; i < dma_buf->n_planes; i++ ) + { + if ( drmPrimeFDToHandle( drm->fd, dma_buf->fd[ i ], &handles[ i ] ) != + 0 ) + { + drm_log.errorf_errno( "drmPrimeFDToHandle failed" ); + goto out; + } + + /* KMS requires all planes to have the same modifier */ + modifiers[ i ] = dma_buf->modifier; + } + + if ( dma_buf->modifier != DRM_FORMAT_MOD_INVALID ) + { + if ( !drm->allow_modifiers ) + { + drm_log.errorf( + "Cannot import DMA-BUF: has a modifier (0x%" PRIX64 + "), but KMS doesn't support them", + dma_buf->modifier ); + goto out; + } + + if ( drmModeAddFB2WithModifiers( + drm->fd, + dma_buf->width, + dma_buf->height, + dma_buf->format, + handles, + dma_buf->stride, + dma_buf->offset, + modifiers, + &fb_id, + DRM_MODE_FB_MODIFIERS ) != 0 ) + { + drm_log.errorf_errno( "drmModeAddFB2WithModifiers failed" ); + goto out; + } + } + else + { + if ( drmModeAddFB2( + drm->fd, + dma_buf->width, + dma_buf->height, + dma_buf->format, + handles, + dma_buf->stride, + dma_buf->offset, + &fb_id, + 0 ) != 0 ) + { + drm_log.errorf_errno( "drmModeAddFB2 failed" ); + goto out; + } + } + + drm_log.debugf( "make fbid %u", fb_id ); + + pBackendFb = new gamescope::CDRMFb( fb_id ); + +out: + for ( int i = 0; i < dma_buf->n_planes; i++ ) + { + if ( handles[ i ] == 0 ) continue; + + // GEM handles aren't ref'counted by the kernel. Two DMA-BUFs may + // return the same GEM handle, we need to be careful not to + // double-close them. + bool already_closed = false; + for ( int j = 0; j < i; j++ ) + { + if ( handles[ i ] == handles[ j ] ) already_closed = true; + } + if ( already_closed ) continue; + + struct drm_gem_close args = { .handle = handles[ i ] }; + if ( drmIoctl( drm->fd, DRM_IOCTL_GEM_CLOSE, &args ) != 0 ) + { + drm_log.errorf_errno( "drmIoctl(GEM_CLOSE) failed" ); + } + } + + return pBackendFb; +} + +static void update_drm_effective_orientations( + struct drm_t *drm, const drmModeModeInfo *pMode ) +{ + gamescope::IBackendConnector *pInternalConnector = + GetBackend( )->GetConnector( + gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ); + if ( pInternalConnector ) + { + gamescope::CDRMConnector *pDRMInternalConnector = + static_cast( pInternalConnector ); + const drmModeModeInfo *pInternalMode = pMode; + if ( pDRMInternalConnector != drm->pConnector ) + pInternalMode = find_mode( + pDRMInternalConnector->GetModeConnector( ), 0, 0, 0 ); + + pDRMInternalConnector->UpdateEffectiveOrientation( pInternalMode ); + } + + gamescope::IBackendConnector *pExternalConnector = + GetBackend( )->GetConnector( + gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ); + if ( pExternalConnector ) + { + gamescope::CDRMConnector *pDRMExternalConnector = + static_cast( pExternalConnector ); + const drmModeModeInfo *pExternalMode = pMode; + if ( pDRMExternalConnector != drm->pConnector ) + pExternalMode = find_mode( + pDRMExternalConnector->GetModeConnector( ), 0, 0, 0 ); + + pDRMExternalConnector->UpdateEffectiveOrientation( pExternalMode ); + } +} + +// Only used for NV12 buffers +static drm_color_encoding drm_get_color_encoding( EStreamColorspace colorspace ) +{ + switch ( colorspace ) + { + default: + case k_EStreamColorspace_Unknown: + return DRM_COLOR_YCBCR_BT709; + + case k_EStreamColorspace_BT601: + return DRM_COLOR_YCBCR_BT601; + case k_EStreamColorspace_BT601_Full: + return DRM_COLOR_YCBCR_BT601; + + case k_EStreamColorspace_BT709: + return DRM_COLOR_YCBCR_BT709; + case k_EStreamColorspace_BT709_Full: + return DRM_COLOR_YCBCR_BT709; + } +} + +static drm_color_range drm_get_color_range( EStreamColorspace colorspace ) +{ + switch ( colorspace ) + { + default: + case k_EStreamColorspace_Unknown: + return DRM_COLOR_YCBCR_FULL_RANGE; + + case k_EStreamColorspace_BT601: + return DRM_COLOR_YCBCR_LIMITED_RANGE; + case k_EStreamColorspace_BT601_Full: + return DRM_COLOR_YCBCR_FULL_RANGE; + + case k_EStreamColorspace_BT709: + return DRM_COLOR_YCBCR_LIMITED_RANGE; + case k_EStreamColorspace_BT709_Full: + return DRM_COLOR_YCBCR_FULL_RANGE; + } +} + +template void hash_combine( size_t &s, const T &v ) +{ + std::hash h; + s ^= h( v ) + 0x9e3779b9 + ( s << 6 ) + ( s >> 2 ); +} + +struct LiftoffStateCacheEntry +{ + LiftoffStateCacheEntry( ) + { memset( this, 0, sizeof( LiftoffStateCacheEntry ) ); } + + int nLayerCount; + + struct LiftoffLayerState_t + { + bool ycbcr; + uint32_t zpos; + uint32_t srcW, srcH; + uint32_t crtcX, crtcY, crtcW, crtcH; + uint16_t opacity; + drm_color_encoding colorEncoding; + drm_color_range colorRange; + GamescopeAppTextureColorspace colorspace; + AlphaBlendingMode_t eAlphaBlendingMode; + } layerState[ k_nMaxLayers ]; + + bool operator==( const LiftoffStateCacheEntry &entry ) const + { return !memcmp( this, &entry, sizeof( LiftoffStateCacheEntry ) ); } +}; + +struct LiftoffStateCacheEntryKasher +{ + size_t operator( )( const LiftoffStateCacheEntry &k ) const + { + size_t hash = 0; + hash_combine( hash, k.nLayerCount ); + for ( int i = 0; i < k.nLayerCount; i++ ) + { + hash_combine( hash, k.layerState[ i ].ycbcr ); + hash_combine( hash, k.layerState[ i ].zpos ); + hash_combine( hash, k.layerState[ i ].srcW ); + hash_combine( hash, k.layerState[ i ].srcH ); + hash_combine( hash, k.layerState[ i ].crtcX ); + hash_combine( hash, k.layerState[ i ].crtcY ); + hash_combine( hash, k.layerState[ i ].crtcW ); + hash_combine( hash, k.layerState[ i ].crtcH ); + hash_combine( hash, k.layerState[ i ].opacity ); + hash_combine( hash, k.layerState[ i ].colorEncoding ); + hash_combine( hash, k.layerState[ i ].colorRange ); + hash_combine( hash, k.layerState[ i ].colorspace ); + hash_combine( hash, k.layerState[ i ].eAlphaBlendingMode ); + } + + return hash; + } +}; + +std::unordered_set + g_LiftoffStateCache; + +static inline amdgpu_transfer_function +colorspace_to_plane_degamma_tf( GamescopeAppTextureColorspace colorspace ) +{ + switch ( colorspace ) + { + default: // Linear in this sense is SRGB. Linear = sRGB image view doing + // automatic sRGB -> Linear which doesn't happen on DRM side. + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: + return AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: + // Use LINEAR TF for scRGB float format as 80 nit = 1.0 in scRGB, + // which matches what PQ TF decodes to/encodes from. AMD internal + // format is FP16, and generally expected for 1.0 -> 80 nit. which + // just so happens to match scRGB. + return AMDGPU_TRANSFER_FUNCTION_IDENTITY; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: + return AMDGPU_TRANSFER_FUNCTION_PQ_EOTF; + } +} + +static inline amdgpu_transfer_function +colorspace_to_plane_shaper_tf( GamescopeAppTextureColorspace colorspace ) +{ + switch ( colorspace ) + { + default: + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: + return AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: // scRGB Linear -> PQ for + // shaper + 3D LUT + case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: + return AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: + return AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } +} + +static inline amdgpu_transfer_function inverse_tf( amdgpu_transfer_function tf ) +{ + switch ( tf ) + { + default: + case AMDGPU_TRANSFER_FUNCTION_DEFAULT: + return AMDGPU_TRANSFER_FUNCTION_DEFAULT; + case AMDGPU_TRANSFER_FUNCTION_IDENTITY: + return AMDGPU_TRANSFER_FUNCTION_IDENTITY; + case AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF: + return AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF; + case AMDGPU_TRANSFER_FUNCTION_BT709_OETF: + return AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF; + case AMDGPU_TRANSFER_FUNCTION_PQ_EOTF: + return AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF; + case AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF: + return AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF; + case AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF: + return AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF; + case AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF: + return AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF; + case AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF: + return AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; + case AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF: + return AMDGPU_TRANSFER_FUNCTION_BT709_OETF; + case AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF: + return AMDGPU_TRANSFER_FUNCTION_PQ_EOTF; + case AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF: + return AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF; + case AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF: + return AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF; + case AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF: + return AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF; + } +} + +static inline uint32_t +ColorSpaceToEOTFIndex( GamescopeAppTextureColorspace colorspace ) +{ + switch ( colorspace ) + { + default: + case GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR: // Not actually linear, + // just Linear vs sRGB + // image views in Vulkan. + // Still viewed as sRGB on + // the DRM side. + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: + // SDR sRGB content treated as native Gamma 22 curve. No need to do + // sRGB -> 2.2 or whatever. + return EOTF_Gamma22; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: + // Okay, so this is WEIRD right? OKAY Let me explain it to you. + // The plan for scRGB content is to go from scRGB -> PQ in a + // SHAPER_TF before indexing into the shaper. (input from + // colorspace_to_plane_regamma_tf!) + return EOTF_PQ; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: + return EOTF_PQ; + } +} + +LiftoffStateCacheEntry FrameInfoToLiftoffStateCacheEntry( + struct drm_t *drm, const FrameInfo_t *frameInfo ) +{ + LiftoffStateCacheEntry entry{}; + + entry.nLayerCount = frameInfo->layerCount; + for ( int i = 0; i < entry.nLayerCount; i++ ) + { + const uint16_t srcWidth = frameInfo->layers[ i ].tex->width( ); + const uint16_t srcHeight = frameInfo->layers[ i ].tex->height( ); + + int32_t crtcX = -frameInfo->layers[ i ].offset.x; + int32_t crtcY = -frameInfo->layers[ i ].offset.y; + uint64_t crtcW = srcWidth / frameInfo->layers[ i ].scale.x; + uint64_t crtcH = srcHeight / frameInfo->layers[ i ].scale.y; + + if ( g_bRotated ) + { + int64_t imageH = frameInfo->layers[ i ].tex->contentHeight( ) / + frameInfo->layers[ i ].scale.y; + + const int32_t x = crtcX; + const uint64_t w = crtcW; + crtcX = g_nOutputHeight - imageH - crtcY; + crtcY = x; + crtcW = crtcH; + crtcH = w; + } + + entry.layerState[ i ].zpos = frameInfo->layers[ i ].zpos; + entry.layerState[ i ].srcW = srcWidth << 16; + entry.layerState[ i ].srcH = srcHeight << 16; + entry.layerState[ i ].crtcX = crtcX; + entry.layerState[ i ].crtcY = crtcY; + entry.layerState[ i ].crtcW = crtcW; + entry.layerState[ i ].crtcH = crtcH; + entry.layerState[ i ].opacity = frameInfo->layers[ i ].opacity * 0xffff; + entry.layerState[ i ].ycbcr = frameInfo->layers[ i ].isYcbcr( ); + if ( entry.layerState[ i ].ycbcr ) + { + entry.layerState[ i ].colorEncoding = + drm_get_color_encoding( g_ForcedNV12ColorSpace ); + entry.layerState[ i ].colorRange = + drm_get_color_range( g_ForcedNV12ColorSpace ); + entry.layerState[ i ].colorspace = + GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + } + else + { + entry.layerState[ i ].colorspace = + frameInfo->layers[ i ].colorspace; + } + entry.layerState[ i ].eAlphaBlendingMode = + frameInfo->layers[ i ].eAlphaBlendingMode; + } + + return entry; +} + +static bool is_liftoff_caching_enabled( ) +{ + static bool disabled = + env_to_bool( getenv( "GAMESCOPE_LIFTOFF_CACHE_DISABLE" ) ); + return !disabled; +} + +namespace gamescope +{ + //////////////////// + // CDRMAtomicObject + //////////////////// + CDRMAtomicObject::CDRMAtomicObject( uint32_t ulObjectId ) : + m_ulObjectId{ ulObjectId } + {} + + ///////////////////////// + // CDRMAtomicTypedObject + ///////////////////////// + template + CDRMAtomicTypedObject::CDRMAtomicTypedObject( + uint32_t ulObjectId ) : + CDRMAtomicObject{ ulObjectId } + {} + + template + std::optional + CDRMAtomicTypedObject::GetRawProperties( ) + { + drmModeObjectProperties *pProperties = + drmModeObjectGetProperties( g_DRM.fd, m_ulObjectId, DRMObjectType ); + if ( !pProperties ) + { + drm_log.errorf_errno( "drmModeObjectGetProperties failed" ); + return std::nullopt; + } + defer( drmModeFreeObjectProperties( pProperties ) ); + + DRMObjectRawProperties rawProperties; + for ( uint32_t i = 0; i < pProperties->count_props; i++ ) + { + drmModePropertyRes *pProperty = + drmModeGetProperty( g_DRM.fd, pProperties->props[ i ] ); + if ( !pProperty ) continue; + defer( drmModeFreeProperty( pProperty ) ); + + rawProperties[ pProperty->name ] = + DRMObjectRawProperty{ pProperty->prop_id, + pProperties->prop_values[ i ] }; + } + + return rawProperties; + } + + ///////////////////////// + // CDRMAtomicProperty + ///////////////////////// + CDRMAtomicProperty::CDRMAtomicProperty( + CDRMAtomicObject *pObject, DRMObjectRawProperty rawProperty ) : + m_pObject{ pObject }, + m_uPropertyId{ rawProperty.uPropertyId }, + m_ulPendingValue{ rawProperty.ulValue }, + m_ulCurrentValue{ rawProperty.ulValue }, + m_ulInitialValue{ rawProperty.ulValue } + {} + + /*static*/ std::optional + CDRMAtomicProperty::Instantiate( + const char *pszName, + CDRMAtomicObject *pObject, + const DRMObjectRawProperties &rawProperties ) + { + auto iter = rawProperties.find( pszName ); + if ( iter == rawProperties.end( ) ) return std::nullopt; + + return CDRMAtomicProperty{ pObject, iter->second }; + } + + int CDRMAtomicProperty::SetPendingValue( + drmModeAtomicReq *pRequest, uint64_t ulValue, bool bForce /*= false*/ ) + { + // In instances where we rolled back due to -EINVAL, or we want to + // ensure a value from an unclean state eg. from an unclean or other + // initial state, you can force an update in the request with bForce. + + if ( ulValue == m_ulPendingValue && !bForce ) return 0; + + int ret = drmModeAtomicAddProperty( + pRequest, m_pObject->GetObjectId( ), m_uPropertyId, ulValue ); + if ( ret < 0 ) return ret; + + m_ulPendingValue = ulValue; + return ret; + } + + void CDRMAtomicProperty::OnCommit( ) + { m_ulCurrentValue = m_ulPendingValue; } + + void CDRMAtomicProperty::Rollback( ) + { m_ulPendingValue = m_ulCurrentValue; } + + ///////////////////////// + // CDRMPlane + ///////////////////////// + CDRMPlane::CDRMPlane( drmModePlane *pPlane ) : + CDRMAtomicTypedObject( pPlane->plane_id ), + m_pPlane{ pPlane, + []( drmModePlane *pPlane ) { drmModeFreePlane( pPlane ); } } + { RefreshState( ); } + + void CDRMPlane::RefreshState( ) + { + auto rawProperties = GetRawProperties( ); + if ( rawProperties ) + { + m_Props.type = + CDRMAtomicProperty::Instantiate( "type", this, *rawProperties ); + m_Props.IN_FORMATS = CDRMAtomicProperty::Instantiate( + "IN_FORMATS", this, *rawProperties ); + + m_Props.FB_ID = CDRMAtomicProperty::Instantiate( + "FB_ID", this, *rawProperties ); + m_Props.IN_FENCE_FD = CDRMAtomicProperty::Instantiate( + "IN_FENCE_FD", this, *rawProperties ); + m_Props.CRTC_ID = CDRMAtomicProperty::Instantiate( + "CRTC_ID", this, *rawProperties ); + m_Props.SRC_X = CDRMAtomicProperty::Instantiate( + "SRC_X", this, *rawProperties ); + m_Props.SRC_Y = CDRMAtomicProperty::Instantiate( + "SRC_Y", this, *rawProperties ); + m_Props.SRC_W = CDRMAtomicProperty::Instantiate( + "SRC_W", this, *rawProperties ); + m_Props.SRC_H = CDRMAtomicProperty::Instantiate( + "SRC_H", this, *rawProperties ); + m_Props.CRTC_X = CDRMAtomicProperty::Instantiate( + "CRTC_X", this, *rawProperties ); + m_Props.CRTC_Y = CDRMAtomicProperty::Instantiate( + "CRTC_Y", this, *rawProperties ); + m_Props.CRTC_W = CDRMAtomicProperty::Instantiate( + "CRTC_W", this, *rawProperties ); + m_Props.CRTC_H = CDRMAtomicProperty::Instantiate( + "CRTC_H", this, *rawProperties ); + m_Props.zpos = + CDRMAtomicProperty::Instantiate( "zpos", this, *rawProperties ); + m_Props.alpha = CDRMAtomicProperty::Instantiate( + "alpha", this, *rawProperties ); + m_Props.rotation = CDRMAtomicProperty::Instantiate( + "rotation", this, *rawProperties ); + m_Props.COLOR_ENCODING = CDRMAtomicProperty::Instantiate( + "COLOR_ENCODING", this, *rawProperties ); + m_Props.COLOR_RANGE = CDRMAtomicProperty::Instantiate( + "COLOR_RANGE", this, *rawProperties ); + m_Props.AMD_PLANE_DEGAMMA_TF = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_DEGAMMA_TF", this, *rawProperties ); + m_Props.AMD_PLANE_DEGAMMA_LUT = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_DEGAMMA_LUT", this, *rawProperties ); + m_Props.AMD_PLANE_CTM = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_CTM", this, *rawProperties ); + m_Props.AMD_PLANE_HDR_MULT = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_HDR_MULT", this, *rawProperties ); + m_Props.AMD_PLANE_SHAPER_LUT = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_SHAPER_LUT", this, *rawProperties ); + m_Props.AMD_PLANE_SHAPER_TF = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_SHAPER_TF", this, *rawProperties ); + m_Props.AMD_PLANE_LUT3D = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_LUT3D", this, *rawProperties ); + m_Props.AMD_PLANE_BLEND_TF = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_BLEND_TF", this, *rawProperties ); + m_Props.AMD_PLANE_BLEND_LUT = CDRMAtomicProperty::Instantiate( + "AMD_PLANE_BLEND_LUT", this, *rawProperties ); + } + } + + ///////////////////////// + // CDRMCRTC + ///////////////////////// + CDRMCRTC::CDRMCRTC( drmModeCrtc *pCRTC, uint32_t uCRTCMask ) : + CDRMAtomicTypedObject( pCRTC->crtc_id ), + m_pCRTC{ pCRTC, + []( drmModeCrtc *pCRTC ) { drmModeFreeCrtc( pCRTC ); } }, + m_uCRTCMask{ uCRTCMask } + { RefreshState( ); } + + void CDRMCRTC::RefreshState( ) + { + auto rawProperties = GetRawProperties( ); + if ( rawProperties ) + { + m_Props.ACTIVE = CDRMAtomicProperty::Instantiate( + "ACTIVE", this, *rawProperties ); + m_Props.MODE_ID = CDRMAtomicProperty::Instantiate( + "MODE_ID", this, *rawProperties ); + m_Props.GAMMA_LUT = CDRMAtomicProperty::Instantiate( + "GAMMA_LUT", this, *rawProperties ); + m_Props.DEGAMMA_LUT = CDRMAtomicProperty::Instantiate( + "DEGAMMA_LUT", this, *rawProperties ); + m_Props.CTM = + CDRMAtomicProperty::Instantiate( "CTM", this, *rawProperties ); + m_Props.VRR_ENABLED = CDRMAtomicProperty::Instantiate( + "VRR_ENABLED", this, *rawProperties ); + m_Props.OUT_FENCE_PTR = CDRMAtomicProperty::Instantiate( + "OUT_FENCE_PTR", this, *rawProperties ); + m_Props.AMD_CRTC_REGAMMA_TF = CDRMAtomicProperty::Instantiate( + "AMD_CRTC_REGAMMA_TF", this, *rawProperties ); + } + } + + ///////////////////////// + // CDRMConnector + ///////////////////////// + CDRMConnector::CDRMConnector( + CDRMBackend *pBackend, drmModeConnector *pConnector ) : + CDRMAtomicTypedObject( + pConnector->connector_id ), + m_pBackend{ pBackend }, + m_pConnector{ pConnector, + []( drmModeConnector *pConnector ) + { drmModeFreeConnector( pConnector ); } } + { RefreshState( ); } + + void CDRMConnector::RefreshState( ) + { + // For the connector re-poll the drmModeConnector to get new modes, etc. + // This isn't needed for CRTC/Planes in which the state is immutable for + // their lifetimes. Connectors can be re-plugged. + + // TODO: Clean this up. + m_pConnector = CAutoDeletePtr{ + drmModeGetConnector( g_DRM.fd, m_pConnector->connector_id ), + []( drmModeConnector *pConnector ) + { drmModeFreeConnector( pConnector ); } + }; + + // Sort the modes to our preference. + std::stable_sort( + m_pConnector->modes, + m_pConnector->modes + m_pConnector->count_modes, + []( const drmModeModeInfo &a, const drmModeModeInfo &b ) + { + bool bGoodRefreshA = a.vrefresh >= 60; + bool bGoodRefreshB = b.vrefresh >= 60; + if ( bGoodRefreshA != bGoodRefreshB ) return bGoodRefreshA; + + bool bPreferredA = a.type & DRM_MODE_TYPE_PREFERRED; + bool bPreferredB = b.type & DRM_MODE_TYPE_PREFERRED; + if ( bPreferredA != bPreferredB ) return bPreferredA; + + int nAreaA = a.hdisplay * a.vdisplay; + int nAreaB = b.hdisplay * b.vdisplay; + if ( nAreaA != nAreaB ) return nAreaA > nAreaB; + + return a.vrefresh > b.vrefresh; + } ); + + std::vector oldEdid = std::move( m_Mutable.EdidData ); + m_Mutable.EdidData.clear( ); + + // Clear this information out. + m_Mutable = MutableConnectorState{}; + + m_Mutable.uPossibleCRTCMask = + drmModeConnectorGetPossibleCrtcs( g_DRM.fd, GetModeConnector( ) ); + + // These are string constants from libdrm, no free. + const char *pszTypeStr = + drmModeGetConnectorTypeName( GetModeConnector( )->connector_type ); + if ( !pszTypeStr ) pszTypeStr = "Unknown"; + + snprintf( + m_Mutable.szName, + sizeof( m_Mutable.szName ), + "%s-%d", + pszTypeStr, + GetModeConnector( )->connector_type_id ); + m_Mutable.szName[ sizeof( m_Mutable.szName ) - 1 ] = '\0'; + + for ( int i = 0; i < m_pConnector->count_modes; i++ ) + { + drmModeModeInfo *pMode = &m_pConnector->modes[ i ]; + m_Mutable.BackendModes.emplace_back( + BackendMode{ + .uWidth = pMode->hdisplay, + .uHeight = pMode->vdisplay, + .uRefresh = pMode->vrefresh, + } ); + } + + auto rawProperties = GetRawProperties( ); + if ( rawProperties ) + { + m_Props.CRTC_ID = CDRMAtomicProperty::Instantiate( + "CRTC_ID", this, *rawProperties ); + m_Props.Colorspace = CDRMAtomicProperty::Instantiate( + "Colorspace", this, *rawProperties ); + m_Props.content_type = CDRMAtomicProperty::Instantiate( + "content type", this, *rawProperties ); + m_Props.panel_orientation = CDRMAtomicProperty::Instantiate( + "panel orientation", this, *rawProperties ); + m_Props.HDR_OUTPUT_METADATA = CDRMAtomicProperty::Instantiate( + "HDR_OUTPUT_METADATA", this, *rawProperties ); + m_Props.vrr_capable = CDRMAtomicProperty::Instantiate( + "vrr_capable", this, *rawProperties ); + m_Props.EDID = + CDRMAtomicProperty::Instantiate( "EDID", this, *rawProperties ); + m_Props.Broadcast_RGB = CDRMAtomicProperty::Instantiate( + "Broadcast RGB", this, *rawProperties ); + } + + ParseEDID( ); + + if ( m_Mutable.EdidData != oldEdid ) { m_Mutable.bEdidChanged = true; } + } + + int CDRMConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) + { return HackyDRMPresent( pFrameInfo, bAsync ); } + + void + CDRMConnector::UpdateEffectiveOrientation( const drmModeModeInfo *pMode ) + { + if ( this->GetScreenType( ) == GAMESCOPE_SCREEN_TYPE_INTERNAL && + g_DesiredInternalOrientation != GAMESCOPE_PANEL_ORIENTATION_AUTO ) + { + m_ChosenOrientation = g_DesiredInternalOrientation; + } + else + { + if ( this->GetProperties( ).panel_orientation ) + { + switch ( this->GetProperties( ) + .panel_orientation->GetCurrentValue( ) ) + { + case DRM_MODE_PANEL_ORIENTATION_NORMAL: + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_0; + return; + case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_180; + return; + case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_90; + return; + case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_270; + return; + default: + break; + } + } + + if ( this->GetScreenType( ) == + gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && + pMode ) + { + // Auto-detect portait mode for internal displays + m_ChosenOrientation = pMode->hdisplay < pMode->vdisplay + ? GAMESCOPE_PANEL_ORIENTATION_270 + : GAMESCOPE_PANEL_ORIENTATION_0; + } + else + { + m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_0; + } + } + } + + void CDRMConnector::ParseEDID( ) + { + if ( !GetProperties( ).EDID ) return; + + uint64_t ulBlobId = GetProperties( ).EDID->GetCurrentValue( ); + if ( !ulBlobId ) return; + + drmModePropertyBlobRes *pBlob = + drmModeGetPropertyBlob( g_DRM.fd, ulBlobId ); + if ( !pBlob ) return; + defer( drmModeFreePropertyBlob( pBlob ) ); + + const uint8_t *pDataPointer = + reinterpret_cast( pBlob->data ); + m_Mutable.EdidData = + std::vector{ pDataPointer, pDataPointer + pBlob->length }; + + di_info *pInfo = di_info_parse_edid( + m_Mutable.EdidData.data( ), m_Mutable.EdidData.size( ) ); + if ( !pInfo ) + { + drm_log.errorf( + "Failed to parse edid for connector: %s", m_Mutable.szName ); + return; + } + defer( di_info_destroy( pInfo ) ); + + const di_edid *pEdid = di_info_get_edid( pInfo ); + + const di_edid_vendor_product *pProduct = + di_edid_get_vendor_product( pEdid ); + m_Mutable.szMakePNP[ 0 ] = pProduct->manufacturer[ 0 ]; + m_Mutable.szMakePNP[ 1 ] = pProduct->manufacturer[ 1 ]; + m_Mutable.szMakePNP[ 2 ] = pProduct->manufacturer[ 2 ]; + m_Mutable.szMakePNP[ 3 ] = '\0'; + + m_Mutable.pszMake = m_Mutable.szMakePNP; + auto pnpIter = pnps.find( m_Mutable.szMakePNP ); + if ( pnpIter != pnps.end( ) ) + m_Mutable.pszMake = pnpIter->second.c_str( ); + + const di_edid_display_descriptor *const *pDescriptors = + di_edid_get_display_descriptors( pEdid ); + for ( size_t i = 0; pDescriptors[ i ] != nullptr; i++ ) + { + const di_edid_display_descriptor *pDesc = pDescriptors[ i ]; + const di_edid_display_descriptor_tag eTag = + di_edid_display_descriptor_get_tag( pDesc ); + if ( eTag == DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME ) + { + // Max length of di_edid_display_descriptor_get_string is 14 + // m_szModel is 16 bytes. + const char *pszModel = + di_edid_display_descriptor_get_string( pDesc ); + strncpy( + m_Mutable.szModel, pszModel, sizeof( m_Mutable.szModel ) ); + } + else if ( eTag == DI_EDID_DISPLAY_DESCRIPTOR_DATA_STRING ) + { + const char *pszDataString = + di_edid_display_descriptor_get_string( pDesc ); + strncpy( + m_Mutable.szDataString, + pszDataString, + sizeof( m_Mutable.szDataString ) ); + } + } + + drm_log.infof( + "Connector %s -> %s - %s", + m_Mutable.szName, + m_Mutable.szMakePNP, + m_Mutable.szModel ); + + bool bHasKnownColorimetry = false; + bool bHasKnownHDRInfo = false; + + m_Mutable.ValidDynamicRefreshRates.clear( ); + m_Mutable.fnDynamicModeGenerator = nullptr; + { + CScriptScopedLock script; + + auto oKnownDisplay = + script.Manager( ).Gamescope( ).Config.LookupDisplay( + script, + m_Mutable.szMakePNP, + pProduct->product, + m_Mutable.szModel, + m_Mutable.szDataString ); + if ( oKnownDisplay ) + { + sol::table tTable = oKnownDisplay->second; + + std::string_view psvPrettyName = tTable.get_or( + "pretty_name", std::string_view{ "Untitled Display" } ); + drm_log.infof( + "Got known display: %.*s (%.*s)", + ( int )oKnownDisplay->first.size( ), + oKnownDisplay->first.data( ), + ( int )psvPrettyName.size( ), + psvPrettyName.data( ) ); + + sol::optional otDynamicRefreshRates = + tTable[ "dynamic_refresh_rates" ]; + sol::optional ofnDynamicModegen = + tTable[ "dynamic_modegen" ]; + + if ( otDynamicRefreshRates && ofnDynamicModegen ) + { + m_Mutable.ValidDynamicRefreshRates = + TableToVector( *otDynamicRefreshRates ); + + m_Mutable.fnDynamicModeGenerator = + [ fnDynamicModegen = *ofnDynamicModegen ]( + const drmModeModeInfo *pBaseMode, + int nRefreshHz ) -> drmModeModeInfo + { + CScriptScopedLock script; + + sol::table tInMode = script->create_table( ); + tInMode[ "clock" ] = pBaseMode->clock; + + tInMode[ "hdisplay" ] = pBaseMode->hdisplay; + tInMode[ "hsync_start" ] = pBaseMode->hsync_start; + tInMode[ "hsync_end" ] = pBaseMode->hsync_end; + tInMode[ "htotal" ] = pBaseMode->htotal; + + tInMode[ "vdisplay" ] = pBaseMode->vdisplay; + tInMode[ "vsync_start" ] = pBaseMode->vsync_start; + tInMode[ "vsync_end" ] = pBaseMode->vsync_end; + tInMode[ "vtotal" ] = pBaseMode->vtotal; + + tInMode[ "vrefresh" ] = pBaseMode->vrefresh; + + sol::function_result ret = + fnDynamicModegen( tInMode, nRefreshHz ); + if ( !ret.valid( ) || !ret.get( ) ) + return *pBaseMode; + + sol::table tOutMode = ret; + + drmModeModeInfo outMode = *pBaseMode; + outMode.clock = tOutMode[ "clock" ]; + + outMode.hdisplay = tOutMode[ "hdisplay" ]; + outMode.hsync_start = tOutMode[ "hsync_start" ]; + outMode.hsync_end = tOutMode[ "hsync_end" ]; + outMode.htotal = tOutMode[ "htotal" ]; + + outMode.vdisplay = tOutMode[ "vdisplay" ]; + outMode.vsync_start = tOutMode[ "vsync_start" ]; + outMode.vsync_end = tOutMode[ "vsync_end" ]; + outMode.vtotal = tOutMode[ "vtotal" ]; + + outMode.vrefresh = tOutMode[ "vrefresh" ]; + + snprintf( + outMode.name, + sizeof( outMode.name ), + "%dx%d@%d.00", + outMode.hdisplay, + outMode.vdisplay, + nRefreshHz ); + + return outMode; + }; + } + + if ( sol::optional otColorimetry = + tTable[ "colorimetry" ] ) + { + sol::table tColorimetry = *otColorimetry; + + // TODO: Add a vec2 + colorimetry type? + sol::optional otR = tColorimetry[ "r" ]; + sol::optional otG = tColorimetry[ "g" ]; + sol::optional otB = tColorimetry[ "b" ]; + sol::optional otW = tColorimetry[ "w" ]; + + if ( otR && otG && otB && otW ) + { + m_Mutable.DisplayColorimetry.primaries.r = + TableToVec( *otR ); + m_Mutable.DisplayColorimetry.primaries.g = + TableToVec( *otG ); + m_Mutable.DisplayColorimetry.primaries.b = + TableToVec( *otB ); + m_Mutable.DisplayColorimetry.white = + TableToVec( *otW ); + + bHasKnownColorimetry = true; + } + } + + if ( sol::optional otHDRInfo = tTable[ "hdr" ] ) + { + m_Mutable.HDR.bExposeHDRSupport = + otHDRInfo->get_or( "supported", false ); + m_Mutable.HDR.eOutputEncodingEOTF = + otHDRInfo->get_or( "eotf", EOTF_Gamma22 ); + m_Mutable.HDR.uMaxContentLightLevel = + nits_to_u16( otHDRInfo->get_or( + "max_content_light_level", 400.0f ) ); + m_Mutable.HDR.uMaxFrameAverageLuminance = + nits_to_u16( otHDRInfo->get_or( + "max_frame_average_luminance", 400.0f ) ); + m_Mutable.HDR.uMinContentLightLevel = nits_to_u16_dark( + otHDRInfo->get_or( "min_content_light_level", 0.1f ) ); + + bHasKnownHDRInfo = true; + } + } + else + { + // Unknown display, see if there are any other refresh rates in + // the EDID we can get. + if ( GetScreenType( ) == GAMESCOPE_SCREEN_TYPE_INTERNAL || + cv_drm_allow_dynamic_modes_for_external_display ) + { + const drmModeModeInfo *pPreferredMode = + find_mode( m_pConnector.get( ), 0, 0, 0 ); + + if ( pPreferredMode ) + { + // See if the EDID has any modes for us. + for ( int i = 0; i < m_pConnector->count_modes; i++ ) + { + const drmModeModeInfo *pMode = + &m_pConnector->modes[ i ]; + + if ( pMode->hdisplay != pPreferredMode->hdisplay || + pMode->vdisplay != pPreferredMode->vdisplay ) + continue; + + if ( !Algorithm::Contains( + m_Mutable.ValidDynamicRefreshRates, + pMode->vrefresh ) ) + { + m_Mutable.ValidDynamicRefreshRates.push_back( + pMode->vrefresh ); + } + } + + std::sort( + m_Mutable.ValidDynamicRefreshRates.begin( ), + m_Mutable.ValidDynamicRefreshRates.end( ) ); + } + } + } + } + + if ( !bHasKnownColorimetry ) + { + // Steam Deck OLED has calibrated chromaticity coordinates in the + // EDID for each unit. Other external displays probably have this + // too. + + const di_edid_chromaticity_coords *pChroma = + di_edid_get_chromaticity_coords( pEdid ); + if ( pChroma && pChroma->red_x != 0.0f ) + { + drm_log.infof( + "[colorimetry]: EDID with colorimetry detected. Using it" ); + m_Mutable.DisplayColorimetry = displaycolorimetry_t{ + .primaries = { { pChroma->red_x, pChroma->red_y }, + { pChroma->green_x, pChroma->green_y }, + { pChroma->blue_x, pChroma->blue_y } }, + .white = { pChroma->white_x, pChroma->white_y }, + }; + } + else + { + // Assume 709 if we have no data at all. + m_Mutable.DisplayColorimetry = displaycolorimetry_709; + } + } + + drm_log.infof( + "[colorimetry]: r %f %f", + m_Mutable.DisplayColorimetry.primaries.r.x, + m_Mutable.DisplayColorimetry.primaries.r.y ); + drm_log.infof( + "[colorimetry]: g %f %f", + m_Mutable.DisplayColorimetry.primaries.g.x, + m_Mutable.DisplayColorimetry.primaries.g.y ); + drm_log.infof( + "[colorimetry]: b %f %f", + m_Mutable.DisplayColorimetry.primaries.b.x, + m_Mutable.DisplayColorimetry.primaries.b.y ); + drm_log.infof( + "[colorimetry]: w %f %f", + m_Mutable.DisplayColorimetry.white.x, + m_Mutable.DisplayColorimetry.white.y ); + + ///////////////////// + // Parse HDR stuff. + ///////////////////// + if ( !bHasKnownHDRInfo ) + { + const di_cta_hdr_static_metadata_block *pHDRStaticMetadata = + nullptr; + const di_cta_colorimetry_block *pColorimetry = nullptr; + + const di_edid_cta *pCTA = NULL; + const di_edid_ext *const *ppExts = di_edid_get_extensions( pEdid ); + for ( ; *ppExts != nullptr; ppExts++ ) + { + if (( pCTA = di_edid_ext_get_cta( *ppExts ) )) break; + } + + if ( pCTA ) + { + const di_cta_data_block *const *ppBlocks = + di_edid_cta_get_data_blocks( pCTA ); + for ( ; *ppBlocks != nullptr; ppBlocks++ ) + { + if ( di_cta_data_block_get_tag( *ppBlocks ) == + DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA ) + { + pHDRStaticMetadata = + di_cta_data_block_get_hdr_static_metadata( + *ppBlocks ); + continue; + } + + if ( di_cta_data_block_get_tag( *ppBlocks ) == + DI_CTA_DATA_BLOCK_COLORIMETRY ) + { + pColorimetry = + di_cta_data_block_get_colorimetry( *ppBlocks ); + continue; + } + } + } + + if ( pColorimetry && pColorimetry->bt2020_rgb && + pHDRStaticMetadata && pHDRStaticMetadata->eotfs && + pHDRStaticMetadata->eotfs->pq ) + { + m_Mutable.HDR.bExposeHDRSupport = true; + m_Mutable.HDR.eOutputEncodingEOTF = EOTF_PQ; + m_Mutable.HDR.uMaxContentLightLevel = + pHDRStaticMetadata->desired_content_max_luminance + ? nits_to_u16( pHDRStaticMetadata + ->desired_content_max_luminance ) + : nits_to_u16( 1499.0f ); + m_Mutable.HDR.uMaxFrameAverageLuminance = + pHDRStaticMetadata->desired_content_max_frame_avg_luminance + ? nits_to_u16( + pHDRStaticMetadata + ->desired_content_max_frame_avg_luminance ) + : nits_to_u16( + std::min( + 799.f, + nits_from_u16( + m_Mutable.HDR.uMaxContentLightLevel ) ) ); + m_Mutable.HDR.uMinContentLightLevel = + pHDRStaticMetadata->desired_content_min_luminance + ? nits_to_u16_dark( + pHDRStaticMetadata + ->desired_content_min_luminance ) + : nits_to_u16_dark( 0.0f ); + } + else + { + m_Mutable.HDR.bExposeHDRSupport = false; + } + } + + // Generate a default HDR10 infoframe. + if ( m_Mutable.HDR.IsHDR10( ) ) + { + hdr_output_metadata defaultHDRMetadata{}; + hdr_metadata_infoframe *pInfoframe = + &defaultHDRMetadata.hdmi_metadata_type1; + + // To be filled in by the app based on the scene, default to + // desired_content_max_luminance + // + // Using display's max_fall for the default metadata max_cll to + // avoid displays overcompensating with tonemapping for SDR content. + uint16_t uDefaultInfoframeLuminances = + m_Mutable.HDR.uMaxFrameAverageLuminance; + + pInfoframe->display_primaries[ 0 ].x = + color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.r.x ); + pInfoframe->display_primaries[ 0 ].y = + color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.r.y ); + pInfoframe->display_primaries[ 1 ].x = + color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.g.x ); + pInfoframe->display_primaries[ 1 ].y = + color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.g.y ); + pInfoframe->display_primaries[ 2 ].x = + color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.b.x ); + pInfoframe->display_primaries[ 2 ].y = + color_xy_to_u16( m_Mutable.DisplayColorimetry.primaries.b.y ); + pInfoframe->white_point.x = + color_xy_to_u16( m_Mutable.DisplayColorimetry.white.x ); + pInfoframe->white_point.y = + color_xy_to_u16( m_Mutable.DisplayColorimetry.white.y ); + pInfoframe->max_display_mastering_luminance = + uDefaultInfoframeLuminances; + pInfoframe->min_display_mastering_luminance = + m_Mutable.HDR.uMinContentLightLevel; + pInfoframe->max_cll = uDefaultInfoframeLuminances; + pInfoframe->max_fall = uDefaultInfoframeLuminances; + pInfoframe->eotf = HDMI_EOTF_ST2084; + + m_Mutable.HDR.pDefaultMetadataBlob = + GetBackend( )->CreateBackendBlob( defaultHDRMetadata ); + } + } + + ///////////////////////// + // CDRMFb + ///////////////////////// + CDRMFb::CDRMFb( uint32_t uFbId ) : m_uFbId{ uFbId } {} + CDRMFb::~CDRMFb( ) + { + // I own the fbid. + if ( drmModeRmFB( g_DRM.fd, m_uFbId ) != 0 ) + drm_log.errorf_errno( "drmModeRmFB failed" ); + m_uFbId = 0; + } +} // namespace gamescope + +static int drm_prepare_liftoff( + struct drm_t *drm, const struct FrameInfo_t *frameInfo, bool needs_modeset ) { - drm->pCRTC = pCRTC; - drm->needs_modeset = true; + auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); + + // If we are modesetting, reset the state cache, we might + // move to another CRTC or whatever which might have differing caps. + // (same with different modes) + if ( needs_modeset ) g_LiftoffStateCache.clear( ); + + if ( is_liftoff_caching_enabled( ) ) + { + if ( g_LiftoffStateCache.count( entry ) != 0 ) return -EINVAL; + } + + bool bSinglePlane = + frameInfo->layerCount < 2 && cv_drm_single_plane_optimizations; + + for ( int i = 0; i < k_nMaxLayers; i++ ) + { + if ( i < frameInfo->layerCount ) + { + const FrameInfo_t::Layer_t *pLayer = &frameInfo->layers[ i ]; + gamescope::CDRMFb *pDrmFb = static_cast( + ( pLayer->tex && pLayer->tex->GetBackendFb( ) ) + ? pLayer->tex->GetBackendFb( )->EnsureImported( ) + : nullptr ); + + if ( pDrmFb == nullptr ) + { + drm_log.debugf( "drm_prepare_liftoff: layer %d has no FB", i ); + return -EINVAL; + } + + const int nFence = cv_drm_debug_disable_in_fence_fd + ? -1 + : g_nAlwaysSignalledSyncFile; + + liftoff_layer_set_property( + drm->lo_layers[ i ], "FB_ID", pDrmFb->GetFbId( ) ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "IN_FENCE_FD", nFence ); + drm->m_FbIdsInRequest.emplace_back( pDrmFb ); + + liftoff_layer_set_property( + drm->lo_layers[ i ], "zpos", entry.layerState[ i ].zpos ); + liftoff_layer_set_property( + drm->lo_layers[ i ], + "alpha", + frameInfo->layers[ i ].opacity * 0xffff ); + + if ( entry.layerState[ i ].zpos != g_zposBase ) + { + liftoff_layer_set_property( + drm->lo_layers[ i ], + "pixel blend mode", + ( uint64_t )frameInfo->layers[ i ].eAlphaBlendingMode ); + } + else + { + liftoff_layer_unset_property( + drm->lo_layers[ i ], "pixel blend mode" ); + } + + liftoff_layer_set_property( drm->lo_layers[ i ], "SRC_X", 0 ); + liftoff_layer_set_property( drm->lo_layers[ i ], "SRC_Y", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "SRC_W", entry.layerState[ i ].srcW ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "SRC_H", entry.layerState[ i ].srcH ); + + uint64_t ulOrientation = DRM_MODE_ROTATE_0; + switch ( drm->pConnector->GetCurrentOrientation( ) ) + { + default: + case GAMESCOPE_PANEL_ORIENTATION_0: + ulOrientation = DRM_MODE_ROTATE_0; + break; + case GAMESCOPE_PANEL_ORIENTATION_270: + ulOrientation = DRM_MODE_ROTATE_270; + break; + case GAMESCOPE_PANEL_ORIENTATION_90: + ulOrientation = DRM_MODE_ROTATE_90; + break; + case GAMESCOPE_PANEL_ORIENTATION_180: + ulOrientation = DRM_MODE_ROTATE_180; + break; + } + liftoff_layer_set_property( + drm->lo_layers[ i ], "rotation", ulOrientation ); + + liftoff_layer_set_property( + drm->lo_layers[ i ], "CRTC_X", entry.layerState[ i ].crtcX ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "CRTC_Y", entry.layerState[ i ].crtcY ); + + liftoff_layer_set_property( + drm->lo_layers[ i ], "CRTC_W", entry.layerState[ i ].crtcW ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "CRTC_H", entry.layerState[ i ].crtcH ); + + if ( frameInfo->layers[ i ].applyColorMgmt ) + { + bool bYCbCr = entry.layerState[ i ].ycbcr; + + if ( !cv_drm_debug_disable_color_encoding && bYCbCr ) + { + liftoff_layer_set_property( + drm->lo_layers[ i ], + "COLOR_ENCODING", + entry.layerState[ i ].colorEncoding ); + } + else + { + liftoff_layer_unset_property( + drm->lo_layers[ i ], "COLOR_ENCODING" ); + } + + if ( !cv_drm_debug_disable_color_range && bYCbCr ) + { + liftoff_layer_set_property( + drm->lo_layers[ i ], + "COLOR_RANGE", + entry.layerState[ i ].colorRange ); + } + else + { + liftoff_layer_unset_property( + drm->lo_layers[ i ], "COLOR_RANGE" ); + } + + if ( drm_supports_color_mgmt( drm ) ) + { + amdgpu_transfer_function degamma_tf = + colorspace_to_plane_degamma_tf( + entry.layerState[ i ].colorspace ); + amdgpu_transfer_function shaper_tf = + colorspace_to_plane_shaper_tf( + entry.layerState[ i ].colorspace ); + + if ( bYCbCr ) + { + // JoshA: Based on the Steam In-Home Streaming Shader, + // it looks like Y is actually sRGB, not HDTV G2.4 + // + // Matching BT709 for degamma -> regamma on shaper TF + // here is identity and works on YUV NV12 planes to + // preserve this. + // + // Doing LINEAR/DEFAULT here introduces banding so... + // this is the best way. (sRGB DEGAMMA does NOT work on + // YUV planes!) + degamma_tf = AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF; + shaper_tf = AMDGPU_TRANSFER_FUNCTION_BT709_OETF; + } + + bool bUseDegamma = !cv_drm_debug_disable_degamma_tf; + if ( bUseDegamma ) + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_DEGAMMA_TF", + degamma_tf ); + else + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", 0 ); + + bool bUseShaperAnd3DLUT = + !cv_drm_debug_disable_shaper_and_3dlut; + if ( bUseShaperAnd3DLUT ) + { + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_SHAPER_LUT", + drm->pending + .shaperlut_id[ ColorSpaceToEOTFIndex( + entry.layerState[ i ].colorspace ) ] + ->GetBlobValue( ) ); + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_SHAPER_TF", + shaper_tf ); + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_LUT3D", + drm->pending + .lut3d_id[ ColorSpaceToEOTFIndex( + entry.layerState[ i ].colorspace ) ] + ->GetBlobValue( ) ); + // Josh: See shaders/colorimetry.h colorspace_blend_tf + // if you have questions as to why we start doing sRGB + // for BLEND_TF despite potentially working in Gamma 2.2 + // space prior. + } + else + { + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_SHAPER_LUT", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_SHAPER_TF", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_LUT3D", 0 ); + } + } + } + else + { + if ( drm_supports_color_mgmt( drm ) ) + { + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_DEGAMMA_TF", + AMDGPU_TRANSFER_FUNCTION_DEFAULT ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_SHAPER_LUT", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_SHAPER_TF", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_LUT3D", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 ); + } + } + + if ( drm_supports_color_mgmt( drm ) ) + { + if ( !cv_drm_debug_disable_blend_tf && !bSinglePlane ) + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_BLEND_TF", + drm->pending.output_tf ); + else + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_BLEND_TF", + AMDGPU_TRANSFER_FUNCTION_DEFAULT ); + + if ( !cv_drm_debug_disable_ctm && + frameInfo->layers[ i ].ctm != nullptr ) + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_CTM", + frameInfo->layers[ i ].ctm->GetBlobValue( ) ); + else + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 ); + } + } + else + { + liftoff_layer_set_property( drm->lo_layers[ i ], "FB_ID", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "IN_FENCE_FD", -1 ); + + liftoff_layer_unset_property( + drm->lo_layers[ i ], "COLOR_ENCODING" ); + liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" ); + liftoff_layer_unset_property( + drm->lo_layers[ i ], "pixel blend mode" ); + + if ( drm_supports_color_mgmt( drm ) ) + { + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_DEGAMMA_TF", + AMDGPU_TRANSFER_FUNCTION_DEFAULT ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_SHAPER_LUT", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_SHAPER_TF", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_LUT3D", 0 ); + liftoff_layer_set_property( + drm->lo_layers[ i ], + "AMD_PLANE_BLEND_TF", + AMDGPU_TRANSFER_FUNCTION_DEFAULT ); + liftoff_layer_set_property( + drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 ); + } + } + } - drm->pPrimaryPlane = find_primary_plane( drm ); - if ( drm->pPrimaryPlane == nullptr ) { - drm_log.errorf("could not find a suitable primary plane"); - return false; - } + struct liftoff_output_apply_options lo_options = { + .timeout_ns = std::numeric_limits::max( ) + }; + + int ret = liftoff_output_apply( + drm->lo_output, drm->req, drm->flags, &lo_options ); + + // The NVIDIA 555 series drivers started advertising DRM_CAP_SYNCOBJ, but do + // not support IN_FENCE_FD. However, there is no way to hide the IN_FENCE_FD + // property in a DRM-KMS driver, so the driver returns EPERM when an + // application sets IN_FENCE_FD. To work around this, the first time a + // commit fails with -EPERM, try it again with the IN_FENCE_FD property + // reset to its default value. If this succeeds, disable use of the + // IN_FENCE_FD property. + static bool attempted_in_fence_fallback = false; + if ( ret == -EPERM && !attempted_in_fence_fallback && + !cv_drm_debug_disable_in_fence_fd ) + { + attempted_in_fence_fallback = true; + for ( int i = 0; i < frameInfo->layerCount; i++ ) + { + liftoff_layer_set_property( + drm->lo_layers[ i ], "IN_FENCE_FD", -1 ); + } - struct liftoff_output *lo_output = liftoff_output_create( drm->lo_device, pCRTC->GetObjectId() ); - if ( lo_output == nullptr ) - return false; + ret = liftoff_output_apply( + drm->lo_output, drm->req, drm->flags, &lo_options ); - for ( int i = 0; i < k_nMaxLayers; i++ ) - { - liftoff_layer_destroy( drm->lo_layers[ i ] ); - drm->lo_layers[ i ] = liftoff_layer_create( lo_output ); - if ( drm->lo_layers[ i ] == nullptr ) - return false; - } + if ( ret == 0 ) + { + // IN_FENCE_FD isn't actually supported. Avoid it in the future. + cv_drm_debug_disable_in_fence_fd = true; + } + } - liftoff_output_destroy( drm->lo_output ); - drm->lo_output = lo_output; + if ( ret == 0 ) + { + // We don't support partial composition yet + if ( liftoff_output_needs_composition( drm->lo_output ) ) ret = -EINVAL; + } - return true; + // If we aren't modesetting and we got -EINVAL, that means that we + // probably can't do this layout, so add it to our state cache so we don't + // try it again. + if ( !needs_modeset ) + { + if ( ret == -EINVAL ) g_LiftoffStateCache.insert( entry ); + } + + if ( ret == 0 ) + drm_log.debugf( "can drm present %i layers", frameInfo->layerCount ); + else + drm_log.debugf( + "can NOT drm present %i layers", frameInfo->layerCount ); + + return ret; +} + +bool g_bForceAsyncFlips = false; + +void drm_rollback( struct drm_t *drm ) +{ + drm->pending = drm->current; + + for ( std::unique_ptr &pCRTC : drm->crtcs ) + { + for ( std::optional &oProperty : + pCRTC->GetProperties( ) ) + { + if ( oProperty ) oProperty->Rollback( ); + } + } + + for ( std::unique_ptr &pPlane : drm->planes ) + { + for ( std::optional &oProperty : + pPlane->GetProperties( ) ) + { + if ( oProperty ) oProperty->Rollback( ); + } + } + + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + for ( std::optional &oProperty : + pConnector->GetProperties( ) ) + { + if ( oProperty ) oProperty->Rollback( ); + } + } +} + +/* Prepares an atomic commit for the provided scene-graph. Returns 0 on success, + * negative errno on failure or if the scene-graph can't be presented directly. + */ +int drm_prepare( + struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ) +{ + if ( !drm->pConnector ) return -EACCES; + + drm_update_color_mgmt( drm ); + + const bool bIsVRRCapable = + drm->pConnector && drm->pConnector->GetProperties( ).vrr_capable && + !!drm->pConnector->GetProperties( ).vrr_capable->GetCurrentValue( ); + const bool bHasVRREnable = + drm->pCRTC && drm->pCRTC->GetProperties( ).VRR_ENABLED; + + const bool bVRREnabled = + bIsVRRCapable && bHasVRREnable && frameInfo->allowVRR; + if ( bIsVRRCapable ) + { + if ( bVRREnabled != + !!drm->pCRTC->GetProperties( ).VRR_ENABLED->GetCurrentValue( ) ) + drm->needs_modeset = true; + } + + drm_colorspace uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; + + const bool bWantsHDR10 = + g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; + gamescope::BackendBlob *pHDRMetadata = nullptr; + if ( drm->pConnector && drm->pConnector->SupportsHDR10( ) ) + { + if ( bWantsHDR10 ) + { + pHDRMetadata = + drm->pConnector->GetHDRInfo( ).pDefaultMetadataBlob.get( ); + + wlserver_vk_swapchain_feedback *pFeedback = + steamcompmgr_get_base_layer_swapchain_feedback( ); + if ( pFeedback && pFeedback->hdr_metadata_blob != nullptr ) + pHDRMetadata = pFeedback->hdr_metadata_blob.get( ); + uColorimetry = DRM_MODE_COLORIMETRY_BT2020_RGB; + } + else + { + pHDRMetadata = drm->sdr_static_metadata.get( ); + uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; + } + + if ( uColorimetry != + drm->pConnector->GetProperties( ).Colorspace->GetCurrentValue( ) ) + drm->needs_modeset = true; + } + + drm->m_FbIdsInRequest.clear( ); + + bool needs_modeset = drm->needs_modeset.exchange( false ); + + assert( drm->req == nullptr ); + drm->req = drmModeAtomicAlloc( ); + + bool bSinglePlane = + frameInfo->layerCount < 2 && cv_drm_single_plane_optimizations; + + if ( drm_supports_color_mgmt( &g_DRM ) && frameInfo->applyOutputColorMgmt ) + { + if ( !cv_drm_debug_disable_output_tf && !bSinglePlane ) + { + drm->pending.output_tf = g_bOutputHDREnabled + ? AMDGPU_TRANSFER_FUNCTION_PQ_EOTF + : AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; + } + else + { + drm->pending.output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } + } + else + { + drm->pending.output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } + + uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; + + // We do internal refcounting with these events + + bool bSleep = false; + if ( drm->pConnector ) + { + bSleep = cv_drm_sleep_screens[ drm->pConnector->GetScreenType( ) ]; + + bool bCurrentlyAsleep = + drm->pConnector->GetProperties( ).CRTC_ID->GetCurrentValue( ) == 0; + + if ( bCurrentlyAsleep != bSleep ) needs_modeset = true; + } + + if ( !bSleep ) + { + if ( drm->pCRTC != nullptr ) flags |= DRM_MODE_PAGE_FLIP_EVENT; + + if ( async || g_bForceAsyncFlips ) flags |= DRM_MODE_PAGE_FLIP_ASYNC; + } + + bool bForceInRequest = needs_modeset; + + if ( needs_modeset ) + { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + + // Disable all connectors and CRTCs + + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + if ( pConnector->GetProperties( ).CRTC_ID->GetCurrentValue( ) == 0 ) + continue; + + pConnector->GetProperties( ).CRTC_ID->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pConnector->GetProperties( ).Colorspace ) + pConnector->GetProperties( ).Colorspace->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pConnector->GetProperties( ).HDR_OUTPUT_METADATA ) + pConnector->GetProperties( ) + .HDR_OUTPUT_METADATA->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pConnector->GetProperties( ).content_type ) + pConnector->GetProperties( ).content_type->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pConnector->GetProperties( ).Broadcast_RGB ) + pConnector->GetProperties( ).Broadcast_RGB->SetPendingValue( + drm->req, 0, bForceInRequest ); + } + + for ( std::unique_ptr &pCRTC : drm->crtcs ) + { + // We can't disable a CRTC if it's already disabled, or else the + // kernel will error out with "requesting event but off". + if ( pCRTC->GetProperties( ).ACTIVE->GetCurrentValue( ) == 0 ) + continue; + + pCRTC->GetProperties( ).ACTIVE->SetPendingValue( + drm->req, 0, bForceInRequest ); + pCRTC->GetProperties( ).MODE_ID->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pCRTC->GetProperties( ).GAMMA_LUT ) + pCRTC->GetProperties( ).GAMMA_LUT->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pCRTC->GetProperties( ).DEGAMMA_LUT ) + pCRTC->GetProperties( ).DEGAMMA_LUT->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pCRTC->GetProperties( ).CTM ) + pCRTC->GetProperties( ).CTM->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pCRTC->GetProperties( ).VRR_ENABLED ) + pCRTC->GetProperties( ).VRR_ENABLED->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pCRTC->GetProperties( ).OUT_FENCE_PTR ) + pCRTC->GetProperties( ).OUT_FENCE_PTR->SetPendingValue( + drm->req, 0, bForceInRequest ); + + if ( pCRTC->GetProperties( ).AMD_CRTC_REGAMMA_TF ) + pCRTC->GetProperties( ).AMD_CRTC_REGAMMA_TF->SetPendingValue( + drm->req, 0, bForceInRequest ); + } + + if ( drm->pConnector && !bSleep ) + { + // Always set our CRTC_ID for the modeset, especially + // as we zero-ed it above. + drm->pConnector->GetProperties( ).CRTC_ID->SetPendingValue( + drm->req, drm->pCRTC->GetObjectId( ), bForceInRequest ); + + if ( drm->pConnector->GetProperties( ).Colorspace ) + drm->pConnector->GetProperties( ).Colorspace->SetPendingValue( + drm->req, uColorimetry, bForceInRequest ); + } + + if ( drm->pCRTC && !bSleep ) + { + drm->pCRTC->GetProperties( ).ACTIVE->SetPendingValue( + drm->req, 1u, true ); + drm->pCRTC->GetProperties( ).MODE_ID->SetPendingValue( + drm->req, + drm->pending.mode_id ? drm->pending.mode_id->GetBlobValue( ) + : 0lu, + true ); + + if ( drm->pCRTC->GetProperties( ).VRR_ENABLED ) + drm->pCRTC->GetProperties( ).VRR_ENABLED->SetPendingValue( + drm->req, bVRREnabled, true ); + } + } + + if ( drm->pConnector && !bSleep ) + { + if ( drm->pConnector->GetProperties( ).HDR_OUTPUT_METADATA ) + drm->pConnector->GetProperties( ) + .HDR_OUTPUT_METADATA->SetPendingValue( + drm->req, + pHDRMetadata ? pHDRMetadata->GetBlobValue( ) : 0lu, + bForceInRequest ); + + if ( drm->pConnector->GetProperties( ).content_type ) + drm->pConnector->GetProperties( ).content_type->SetPendingValue( + drm->req, DRM_MODE_CONTENT_TYPE_GAME, bForceInRequest ); + + GamescopeBroadcastRGBMode_t eBroadcastRGB = + drm->pConnector->GetScreenType( ) == + gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL + ? s_ExternalBroadcastRGBMode + : GAMESCOPE_BROADCAST_RGB_MODE_AUTOMATIC; + + if ( drm->pConnector->GetProperties( ).Broadcast_RGB ) + drm->pConnector->GetProperties( ).Broadcast_RGB->SetPendingValue( + drm->req, eBroadcastRGB, bForceInRequest ); + } + + if ( drm->pCRTC && !bSleep ) + { + if ( drm->pCRTC->GetProperties( ).AMD_CRTC_REGAMMA_TF ) + { + if ( !cv_drm_debug_disable_regamma_tf ) + drm->pCRTC->GetProperties( ) + .AMD_CRTC_REGAMMA_TF->SetPendingValue( + drm->req, + inverse_tf( drm->pending.output_tf ), + bForceInRequest ); + else + drm->pCRTC->GetProperties( ) + .AMD_CRTC_REGAMMA_TF->SetPendingValue( + drm->req, + AMDGPU_TRANSFER_FUNCTION_DEFAULT, + bForceInRequest ); + } + } + + drm->flags = flags; + + int ret; + if ( drm->pCRTC == nullptr || bSleep ) { ret = 0; } + else if ( drm->bUseLiftoff ) + { + ret = drm_prepare_liftoff( drm, frameInfo, needs_modeset ); + } + else + { + ret = 0; + } + + if ( ret != 0 ) + { + drm_rollback( drm ); + + drmModeAtomicFree( drm->req ); + drm->req = nullptr; + + drm->m_FbIdsInRequest.clear( ); + + if ( needs_modeset ) drm->needs_modeset = true; + } + + return ret; +} + +bool drm_poll_state( struct drm_t *drm ) +{ + int out_of_date = drm->out_of_date.exchange( false ); + if ( !out_of_date ) return false; + + refresh_state( drm ); + + setup_best_connector( drm, out_of_date >= 2, false ); + + return true; +} + +static bool drm_set_crtc( struct drm_t *drm, gamescope::CDRMCRTC *pCRTC ) +{ + drm->pCRTC = pCRTC; + drm->needs_modeset = true; + + drm->pPrimaryPlane = find_primary_plane( drm ); + if ( drm->pPrimaryPlane == nullptr ) + { + drm_log.errorf( "could not find a suitable primary plane" ); + return false; + } + + struct liftoff_output *lo_output = + liftoff_output_create( drm->lo_device, pCRTC->GetObjectId( ) ); + if ( lo_output == nullptr ) return false; + + for ( int i = 0; i < k_nMaxLayers; i++ ) + { + liftoff_layer_destroy( drm->lo_layers[ i ] ); + drm->lo_layers[ i ] = liftoff_layer_create( lo_output ); + if ( drm->lo_layers[ i ] == nullptr ) return false; + } + + liftoff_output_destroy( drm->lo_output ); + drm->lo_output = lo_output; + + return true; } bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ) { - drm_log.infof("selecting connector %s", conn->GetName()); + drm_log.infof( "selecting connector %s", conn->GetName( ) ); - gamescope::CDRMCRTC *pCRTC = find_crtc_for_connector(drm, conn); - if (pCRTC == nullptr) - { - drm_log.errorf("no CRTC found!"); - return false; - } + gamescope::CDRMCRTC *pCRTC = find_crtc_for_connector( drm, conn ); + if ( pCRTC == nullptr ) + { + drm_log.errorf( "no CRTC found!" ); + return false; + } - if (!drm_set_crtc(drm, pCRTC)) { - return false; - } + if ( !drm_set_crtc( drm, pCRTC ) ) { return false; } - // If we are changing connector, zero out the current and pending mode IDs. - // So we don't try to use one mode from the old connector on the new one if we roll back. - drm->pending.mode_id = nullptr; - drm->current.mode_id = nullptr; + // If we are changing connector, zero out the current and pending mode IDs. + // So we don't try to use one mode from the old connector on the new one if + // we roll back. + drm->pending.mode_id = nullptr; + drm->current.mode_id = nullptr; - drm->pConnector = conn; - drm->needs_modeset = true; + drm->pConnector = conn; + drm->needs_modeset = true; - return true; + return true; } static void drm_unset_connector( struct drm_t *drm ) { - drm->pCRTC = nullptr; - drm->pPrimaryPlane = nullptr; + drm->pCRTC = nullptr; + drm->pPrimaryPlane = nullptr; - for ( int i = 0; i < k_nMaxLayers; i++ ) - { - liftoff_layer_destroy( drm->lo_layers[ i ] ); - drm->lo_layers[ i ] = nullptr; - } + for ( int i = 0; i < k_nMaxLayers; i++ ) + { + liftoff_layer_destroy( drm->lo_layers[ i ] ); + drm->lo_layers[ i ] = nullptr; + } - liftoff_output_destroy(drm->lo_output); - drm->lo_output = nullptr; + liftoff_output_destroy( drm->lo_output ); + drm->lo_output = nullptr; - drm->pConnector = nullptr; - drm->needs_modeset = true; + drm->pConnector = nullptr; + drm->needs_modeset = true; } -bool drm_get_vrr_in_use(struct drm_t *drm) +bool drm_get_vrr_in_use( struct drm_t *drm ) { - if ( !drm->pCRTC || !drm->pCRTC->GetProperties().VRR_ENABLED ) - return false; + if ( !drm->pCRTC || !drm->pCRTC->GetProperties( ).VRR_ENABLED ) + return false; - return !!drm->pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue(); + return !!drm->pCRTC->GetProperties( ).VRR_ENABLED->GetCurrentValue( ); } -gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm) +gamescope::GamescopeScreenType drm_get_screen_type( struct drm_t *drm ) { - if ( !drm->pConnector ) - return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; + if ( !drm->pConnector ) return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; - return drm->pConnector->GetScreenType(); + return drm->pConnector->GetScreenType( ); } -bool drm_update_color_mgmt(struct drm_t *drm) +bool drm_update_color_mgmt( struct drm_t *drm ) { - if ( !drm_supports_color_mgmt( drm ) ) - return true; + if ( !drm_supports_color_mgmt( drm ) ) return true; - if ( g_ColorMgmt.serial == drm->current.color_mgmt_serial ) - return true; + if ( g_ColorMgmt.serial == drm->current.color_mgmt_serial ) return true; - drm->pending.color_mgmt_serial = g_ColorMgmt.serial; + drm->pending.color_mgmt_serial = g_ColorMgmt.serial; - for ( uint32_t i = 0; i < EOTF_Count; i++ ) - { - drm->pending.shaperlut_id[ i ] = 0; - drm->pending.lut3d_id[ i ] = 0; - } + for ( uint32_t i = 0; i < EOTF_Count; i++ ) + { + drm->pending.shaperlut_id[ i ] = 0; + drm->pending.lut3d_id[ i ] = 0; + } - for ( uint32_t i = 0; i < EOTF_Count; i++ ) - { - if ( !g_ColorMgmtLuts[i].HasLuts() ) - continue; + for ( uint32_t i = 0; i < EOTF_Count; i++ ) + { + if ( !g_ColorMgmtLuts[ i ].HasLuts( ) ) continue; - drm->pending.shaperlut_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut1d ); - drm->pending.lut3d_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut3d ); - } + drm->pending.shaperlut_id[ i ] = + GetBackend( )->CreateBackendBlob( g_ColorMgmtLuts[ i ].lut1d ); + drm->pending.lut3d_id[ i ] = + GetBackend( )->CreateBackendBlob( g_ColorMgmtLuts[ i ].lut3d ); + } - return true; + return true; } int g_nDynamicRefreshHz = 0; static void drm_unset_mode( struct drm_t *drm ) { - drm->pending.mode_id = 0; - drm->needs_modeset = true; + drm->pending.mode_id = 0; + drm->needs_modeset = true; - g_nOutputWidth = drm->preferred_width; - g_nOutputHeight = drm->preferred_height; - if (g_nOutputHeight == 0) - g_nOutputHeight = 720; - if (g_nOutputWidth == 0) - g_nOutputWidth = g_nOutputHeight * 16 / 9; + g_nOutputWidth = drm->preferred_width; + g_nOutputHeight = drm->preferred_height; + if ( g_nOutputHeight == 0 ) g_nOutputHeight = 720; + if ( g_nOutputWidth == 0 ) g_nOutputWidth = g_nOutputHeight * 16 / 9; - g_nOutputRefresh = drm->preferred_refresh; - if (g_nOutputRefresh == 0) - g_nOutputRefresh = gamescope::ConvertHztomHz( 60 ); - g_nDynamicRefreshHz = 0; + g_nOutputRefresh = drm->preferred_refresh; + if ( g_nOutputRefresh == 0 ) + g_nOutputRefresh = gamescope::ConvertHztomHz( 60 ); + g_nDynamicRefreshHz = 0; - g_bRotated = false; + g_bRotated = false; } bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) { - if (!drm->pConnector || !drm->pConnector->GetModeConnector()) - return false; - - drm_log.infof("selecting mode %dx%d@%uHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); - - drm->pending.mode_id = GetBackend()->CreateBackendBlob( *mode ); - drm->needs_modeset = true; - - g_nOutputRefresh = gamescope::GetModeRefresh( mode ); - g_nDynamicRefreshHz = 0; - - update_drm_effective_orientations(drm, mode); - - switch ( drm->pConnector->GetCurrentOrientation() ) - { - default: - case GAMESCOPE_PANEL_ORIENTATION_0: - case GAMESCOPE_PANEL_ORIENTATION_180: - g_bRotated = false; - g_nOutputWidth = mode->hdisplay; - g_nOutputHeight = mode->vdisplay; - break; - case GAMESCOPE_PANEL_ORIENTATION_90: - case GAMESCOPE_PANEL_ORIENTATION_270: - g_bRotated = true; - g_nOutputWidth = mode->vdisplay; - g_nOutputHeight = mode->hdisplay; - break; - } - - return true; + if ( !drm->pConnector || !drm->pConnector->GetModeConnector( ) ) + return false; + + drm_log.infof( + "selecting mode %dx%d@%uHz", + mode->hdisplay, + mode->vdisplay, + mode->vrefresh ); + + drm->pending.mode_id = GetBackend( )->CreateBackendBlob( *mode ); + drm->needs_modeset = true; + + g_nOutputRefresh = gamescope::GetModeRefresh( mode ); + g_nDynamicRefreshHz = 0; + + update_drm_effective_orientations( drm, mode ); + + switch ( drm->pConnector->GetCurrentOrientation( ) ) + { + default: + case GAMESCOPE_PANEL_ORIENTATION_0: + case GAMESCOPE_PANEL_ORIENTATION_180: + g_bRotated = false; + g_nOutputWidth = mode->hdisplay; + g_nOutputHeight = mode->vdisplay; + break; + case GAMESCOPE_PANEL_ORIENTATION_90: + case GAMESCOPE_PANEL_ORIENTATION_270: + g_bRotated = true; + g_nOutputWidth = mode->vdisplay; + g_nOutputHeight = mode->hdisplay; + break; + } + + return true; } bool drm_set_refresh( struct drm_t *drm, int refresh ) { - int width = g_nOutputWidth; - int height = g_nOutputHeight; - - if ( g_bRotated ) { - int tmp = width; - width = height; - height = tmp; - } - if (!drm->pConnector || !drm->pConnector->GetModeConnector()) - return false; - - drmModeConnector *connector = drm->pConnector->GetModeConnector(); - const drmModeModeInfo *existing_mode = find_mode(connector, width, height, refresh); - drmModeModeInfo mode = {0}; - if ( existing_mode ) - { - mode = *existing_mode; - } - else - { - if ( g_DRM.pConnector && g_DRM.pConnector->GetModeGenerator() ) - { - const drmModeModeInfo *preferred_mode = find_mode(connector, 0, 0, 0); - mode = g_DRM.pConnector->GetModeGenerator()( preferred_mode, refresh ); - } - else - { - /* TODO: check refresh is within the EDID limits */ - switch ( g_eGamescopeModeGeneration ) - { - case gamescope::GAMESCOPE_MODE_GENERATE_CVT: - generate_cvt_mode( &mode, width, height, refresh, true, false ); - break; - case gamescope::GAMESCOPE_MODE_GENERATE_FIXED: - { - const drmModeModeInfo *preferred_mode = find_mode(connector, 0, 0, 0); - generate_fixed_mode( &mode, preferred_mode, refresh ); - break; - } - } - } - } - - mode.type = DRM_MODE_TYPE_USERDEF; - - bool bSuccess = drm_set_mode(drm, &mode); - if ( !bSuccess ) - return false; - - g_nDynamicRefreshHz = refresh; - - return true; + int width = g_nOutputWidth; + int height = g_nOutputHeight; + + if ( g_bRotated ) + { + int tmp = width; + width = height; + height = tmp; + } + if ( !drm->pConnector || !drm->pConnector->GetModeConnector( ) ) + return false; + + drmModeConnector *connector = drm->pConnector->GetModeConnector( ); + const drmModeModeInfo *existing_mode = + find_mode( connector, width, height, refresh ); + drmModeModeInfo mode = { 0 }; + if ( existing_mode ) { mode = *existing_mode; } + else + { + if ( g_DRM.pConnector && g_DRM.pConnector->GetModeGenerator( ) ) + { + const drmModeModeInfo *preferred_mode = + find_mode( connector, 0, 0, 0 ); + mode = g_DRM.pConnector->GetModeGenerator( )( + preferred_mode, refresh ); + } + else + { + /* TODO: check refresh is within the EDID limits */ + switch ( g_eGamescopeModeGeneration ) + { + case gamescope::GAMESCOPE_MODE_GENERATE_CVT: + generate_cvt_mode( + &mode, width, height, refresh, true, false ); + break; + case gamescope::GAMESCOPE_MODE_GENERATE_FIXED: + { + const drmModeModeInfo *preferred_mode = + find_mode( connector, 0, 0, 0 ); + generate_fixed_mode( &mode, preferred_mode, refresh ); + break; + } + } + } + } + + mode.type = DRM_MODE_TYPE_USERDEF; + + bool bSuccess = drm_set_mode( drm, &mode ); + if ( !bSuccess ) return false; + + g_nDynamicRefreshHz = refresh; + + return true; } bool drm_set_resolution( struct drm_t *drm, int width, int height ) { - if (!drm->pConnector || !drm->pConnector->GetModeConnector()) - return false; + if ( !drm->pConnector || !drm->pConnector->GetModeConnector( ) ) + return false; - drmModeConnector *connector = drm->pConnector->GetModeConnector(); - const drmModeModeInfo *mode = find_mode(connector, width, height, 0); - if ( !mode ) - { - return false; - } + drmModeConnector *connector = drm->pConnector->GetModeConnector( ); + const drmModeModeInfo *mode = find_mode( connector, width, height, 0 ); + if ( !mode ) { return false; } - return drm_set_mode(drm, mode); + return drm_set_mode( drm, mode ); } -bool drm_get_vrr_capable(struct drm_t *drm) +bool drm_get_vrr_capable( struct drm_t *drm ) { - if ( drm->pConnector ) - return drm->pConnector->SupportsVRR(); + if ( drm->pConnector ) return drm->pConnector->SupportsVRR( ); - return false; + return false; } bool drm_supports_hdr( struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL ) { - if ( drm->pConnector && drm->pConnector->SupportsHDR() ) - { - if ( maxCLL ) - *maxCLL = drm->pConnector->GetHDRInfo().uMaxContentLightLevel; - if ( maxFALL ) - *maxFALL = drm->pConnector->GetHDRInfo().uMaxFrameAverageLuminance; - return true; - } + if ( drm->pConnector && drm->pConnector->SupportsHDR( ) ) + { + if ( maxCLL ) + *maxCLL = drm->pConnector->GetHDRInfo( ).uMaxContentLightLevel; + if ( maxFALL ) + *maxFALL = drm->pConnector->GetHDRInfo( ).uMaxFrameAverageLuminance; + return true; + } - return false; + return false; } -const char *drm_get_connector_name(struct drm_t *drm) +const char *drm_get_connector_name( struct drm_t *drm ) { - if ( !drm->pConnector ) - return nullptr; + if ( !drm->pConnector ) return nullptr; - return drm->pConnector->GetName(); + return drm->pConnector->GetName( ); } -const char *drm_get_device_name(struct drm_t *drm) -{ - return drm->device_name; -} +const char *drm_get_device_name( struct drm_t *drm ) +{ return drm->device_name; } -std::pair drm_get_connector_identifier(struct drm_t *drm) +std::pair drm_get_connector_identifier( struct drm_t *drm ) { - if ( !drm->pConnector ) - return { 0u, 0u }; + if ( !drm->pConnector ) return { 0u, 0u }; - return std::make_pair(drm->pConnector->GetModeConnector()->connector_type, drm->pConnector->GetModeConnector()->connector_type_id); + return std::make_pair( + drm->pConnector->GetModeConnector( )->connector_type, + drm->pConnector->GetModeConnector( )->connector_type_id ); } -bool drm_supports_color_mgmt(struct drm_t *drm) +bool drm_supports_color_mgmt( struct drm_t *drm ) { - if ( g_bForceDisableColorMgmt ) - return false; + if ( g_bForceDisableColorMgmt ) return false; - if ( !drm->pPrimaryPlane ) - return false; + if ( !drm->pPrimaryPlane ) return false; - return drm->pPrimaryPlane->GetProperties().AMD_PLANE_CTM.has_value() && drm->pPrimaryPlane->GetProperties().AMD_PLANE_BLEND_TF.has_value(); + return drm->pPrimaryPlane->GetProperties( ).AMD_PLANE_CTM.has_value( ) && + drm->pPrimaryPlane->GetProperties( ).AMD_PLANE_BLEND_TF.has_value( ); } std::span drm_get_valid_refresh_rates( struct drm_t *drm ) { - if ( drm && drm->pConnector ) - return drm->pConnector->GetValidDynamicRefreshRates(); + if ( drm && drm->pConnector ) + return drm->pConnector->GetValidDynamicRefreshRates( ); - return std::span{}; + return std::span{}; } namespace gamescope { - class CDRMBackend; - - class CDRMBackend final : public CBaseBackend - { - public: - CDRMBackend() - { - } - - virtual ~CDRMBackend() - { - if ( g_DRM.fd != -1 ) - finish_drm( &g_DRM ); - } - - virtual bool Init() override - { - if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) - { - fprintf( stderr, "Failed to initialize Vulkan\n" ); - return false; - } - - if ( !wlsession_init() ) - { - fprintf( stderr, "Failed to initialize Wayland session\n" ); - return false; - } - - return init_drm( &g_DRM, g_nPreferredOutputWidth, g_nPreferredOutputHeight, g_nNestedRefresh ); - } - - virtual bool PostInit() override - { - if ( g_DRM.pConnector ) - WritePatchedEdid( g_DRM.pConnector->GetRawEDID(), g_DRM.pConnector->GetHDRInfo(), g_bRotated ); - return true; - } - - virtual std::span GetInstanceExtensions() const override - { - return std::span{}; - } - virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override - { - return std::span{}; - } - virtual VkImageLayout GetPresentLayout() const override - { - // Does not matter, as this has a queue family transition - // to VK_QUEUE_FAMILY_FOREIGN_EXT queue, - // thus: newLayout is ignored. - return VK_IMAGE_LAYOUT_GENERAL; - } - virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override - { - *pPrimaryPlaneFormat = g_nDRMFormat; - *pOverlayPlaneFormat = g_nDRMFormatOverlay; - } - virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override - { - return vulkan_has_drm_props(); - } - - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) - { - static uint64_t s_ulLastTime = get_time_in_nanos(); - uint64_t ulNow = get_time_in_nanos(); - drm_log.debugf( "CDRMBackend::Present Begin: %lu -> delta: %lu", ulNow, ulNow - s_ulLastTime ); - s_ulLastTime = ulNow; - - bool bWantsPartialComposite = pFrameInfo->layerCount >= 3 && !kDisablePartialComposition; - - static bool s_bWasFirstFrame = true; - bool bWasFirstFrame = s_bWasFirstFrame; - s_bWasFirstFrame = false; - - bool bDrewCursor = false; - for ( uint32_t i = 0; i < k_nMaxLayers; i++ ) - { - if ( pFrameInfo->layers[i].zpos == g_zposCursor ) - { - bDrewCursor = true; - break; - } - } - - bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f); - - bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; - - bool bNeedsFullComposite = false; - bNeedsFullComposite |= cv_composite_force; - bNeedsFullComposite |= bWasFirstFrame; - bNeedsFullComposite |= pFrameInfo->useFSRLayer0; - bNeedsFullComposite |= pFrameInfo->useNISLayer0; - bNeedsFullComposite |= pFrameInfo->blurLayer0; - bNeedsFullComposite |= bNeedsCompositeFromFilter; - bNeedsFullComposite |= !k_bUseCursorPlane && bDrewCursor; - bNeedsFullComposite |= g_bColorSliderInUse; - bNeedsFullComposite |= pFrameInfo->bFadingOut; - bNeedsFullComposite |= !g_reshade_effect.empty(); - - if ( g_bOutputHDREnabled ) - { - bNeedsFullComposite |= g_bHDRItmEnable; - if ( !SupportsColorManagement() ) - bNeedsFullComposite |= ( pFrameInfo->layerCount > 1 || pFrameInfo->layers[0].colorspace != GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ); - } - else - { - if ( !SupportsColorManagement() ) - bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace ); - } - - bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); - - bool bDoComposite = true; - if ( !bNeedsFullComposite && !bWantsPartialComposite ) - { - int ret = drm_prepare( &g_DRM, bAsync, pFrameInfo ); - if ( ret == 0 ) - bDoComposite = false; - else if ( ret == -EACCES ) - return 0; - } - - // Update to let the vblank manager know we are currently compositing. - GetVBlankTimer().UpdateWasCompositing( bDoComposite ); - - if ( !bDoComposite ) - { - // Scanout + Planes Path - m_bWasPartialCompositing = false; - m_bWasCompositing = false; - if ( pFrameInfo->layerCount == 2 ) - m_nLastSingleOverlayZPos = pFrameInfo->layers[1].zpos; - - return Commit( pFrameInfo ); - } - - // Composition Path - if ( kDisablePartialComposition ) - bNeedsFullComposite = true; - - FrameInfo_t compositeFrameInfo = *pFrameInfo; - - if ( compositeFrameInfo.layerCount == 1 ) - { - // If we failed to flip a single plane then - // we definitely need to composite for some reason... - bNeedsFullComposite = true; - } - - if ( !bNeedsFullComposite ) - { - // If we want to partial composite, fallback to full - // composite if we have mismatching colorspaces in our overlays. - // This is 2, and we do i-1 so 1...layerCount. So AFTER we have removed baseplane. - // Overlays only. - // - // Josh: - // We could handle mismatching colorspaces for partial composition - // but I want to keep overlay -> partial composition promotion as simple - // as possible, using the same 3D + SHAPER LUTs + BLEND in DRM - // as changing them is incredibly expensive!! It takes forever. - // We can't just point it to random BDA or whatever, it has to be uploaded slowly - // thru registers which is SUPER SLOW. - // This avoids stutter. - for ( int i = 2; i < compositeFrameInfo.layerCount; i++ ) - { - if ( pFrameInfo->layers[i - 1].colorspace != pFrameInfo->layers[i].colorspace ) - { - bNeedsFullComposite = true; - break; - } - } - } - - // If we ever promoted from partial -> full, for the first frame - // do NOT defer this partial composition. - // We were already stalling for the full composition before, so it's not an issue - // for latency, we just need to make sure we get 1 partial frame that isn't deferred - // in time so we don't lose layers. - bool bDefer = !bNeedsFullComposite && ( !m_bWasCompositing || m_bWasPartialCompositing ); - - // If doing a partial composition then remove the baseplane - // from our frameinfo to composite. - if ( !bNeedsFullComposite ) - { - for ( int i = 1; i < compositeFrameInfo.layerCount; i++ ) - compositeFrameInfo.layers[i - 1] = compositeFrameInfo.layers[i]; - compositeFrameInfo.layerCount -= 1; - - // When doing partial composition, apply the shaper + 3D LUT stuff - // at scanout. - for ( uint32_t nEOTF = 0; nEOTF < EOTF_Count; nEOTF++ ) { - compositeFrameInfo.shaperLut[ nEOTF ] = nullptr; - compositeFrameInfo.lut3D[ nEOTF ] = nullptr; - } - } - - // If using composite debug markers, make sure we mark them as partial - // so we know! - if ( bDefer && !!( g_uCompositeDebug & CompositeDebugFlag::Markers ) ) - g_uCompositeDebug |= CompositeDebugFlag::Markers_Partial; - - std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite ); - - m_bWasCompositing = true; - - g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; - - if ( !oCompositeResult ) - { - xwm_log.errorf("vulkan_composite failed"); - return -EINVAL; - } - - vulkan_wait( *oCompositeResult, true ); - - FrameInfo_t presentCompFrameInfo = {}; - presentCompFrameInfo.allowVRR = pFrameInfo->allowVRR; - presentCompFrameInfo.outputEncodingEOTF = pFrameInfo->outputEncodingEOTF; - - if ( bNeedsFullComposite ) - { - presentCompFrameInfo.applyOutputColorMgmt = false; - presentCompFrameInfo.layerCount = 1; - - FrameInfo_t::Layer_t *baseLayer = &presentCompFrameInfo.layers[ 0 ]; - baseLayer->scale.x = 1.0; - baseLayer->scale.y = 1.0; - baseLayer->opacity = 1.0; - baseLayer->zpos = g_zposBase; - - baseLayer->tex = vulkan_get_last_output_image( false, false ); - baseLayer->applyColorMgmt = false; - - baseLayer->filter = GamescopeUpscaleFilter::NEAREST; - baseLayer->ctm = nullptr; - baseLayer->colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - - m_bWasPartialCompositing = false; - } - else - { - if ( m_bWasPartialCompositing || !bDefer ) - { - presentCompFrameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; - presentCompFrameInfo.layerCount = 2; - - presentCompFrameInfo.layers[ 0 ] = pFrameInfo->layers[ 0 ]; - presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; - - FrameInfo_t::Layer_t *overlayLayer = &presentCompFrameInfo.layers[ 1 ]; - overlayLayer->scale.x = 1.0; - overlayLayer->scale.y = 1.0; - overlayLayer->opacity = 1.0; - overlayLayer->zpos = g_zposOverlay; - - overlayLayer->tex = vulkan_get_last_output_image( true, bDefer ); - overlayLayer->applyColorMgmt = g_ColorMgmt.pending.enabled; - - overlayLayer->filter = GamescopeUpscaleFilter::NEAREST; - // Partial composition stuff has the same colorspace. - // So read that from the composite frame info - overlayLayer->ctm = nullptr; - overlayLayer->colorspace = compositeFrameInfo.layers[0].colorspace; - } - else - { - // Use whatever overlay we had last while waiting for the - // partial composition to have anything queued. - presentCompFrameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; - presentCompFrameInfo.layerCount = 1; - - presentCompFrameInfo.layers[ 0 ] = pFrameInfo->layers[ 0 ]; - presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; - - const FrameInfo_t::Layer_t *lastPresentedOverlayLayer = nullptr; - for (int i = 0; i < pFrameInfo->layerCount; i++) - { - if ( pFrameInfo->layers[i].zpos == m_nLastSingleOverlayZPos ) - { - lastPresentedOverlayLayer = &pFrameInfo->layers[i]; - break; - } - } - - if ( lastPresentedOverlayLayer ) - { - FrameInfo_t::Layer_t *overlayLayer = &presentCompFrameInfo.layers[ 1 ]; - *overlayLayer = *lastPresentedOverlayLayer; - overlayLayer->zpos = g_zposOverlay; - - presentCompFrameInfo.layerCount = 2; - } - } - - m_bWasPartialCompositing = true; - } - - int ret = drm_prepare( &g_DRM, bAsync, &presentCompFrameInfo ); - - // Happens when we're VT-switched away - if ( ret == -EACCES ) - return 0; - - if ( ret != 0 ) - { - if ( g_DRM.current.mode_id == 0 ) - { - xwm_log.errorf("We failed our modeset and have no mode to fall back to! (Initial modeset failed?): %s", strerror(-ret)); - return 0; - } - - xwm_log.errorf("Failed to prepare 1-layer flip (%s), trying again with previous mode if modeset needed", strerror( -ret )); - - // Try once again to in case we need to fall back to another mode. - ret = drm_prepare( &g_DRM, bAsync, &compositeFrameInfo ); - - // Happens when we're VT-switched away - if ( ret == -EACCES ) - return 0; - - if ( ret != 0 ) - { - xwm_log.errorf("Failed to prepare 1-layer flip entirely: %s", strerror( -ret )); - // We should always handle a 1-layer flip, this used to abort, - // but lets be more friendly and just avoid a commit and try again later. - // Let's re-poll our state, and force grab the best connector again. - // - // Some intense connector hotplugging could be occuring and the - // connector could become destroyed before we had a chance to use it - // as we hadn't reffed it in a commit yet. - this->DirtyState( true, false ); - this->PollState(); - return ret; - } - } - - return Commit( &compositeFrameInfo ); - } - - virtual void DirtyState( bool bForce, bool bForceModeset ) override - { - if ( bForceModeset ) - g_DRM.needs_modeset = true; - g_DRM.out_of_date = std::max( g_DRM.out_of_date, bForce ? 2 : 1 ); - g_DRM.paused = !wlsession_active(); - } - - virtual bool PollState() override - { - return drm_poll_state( &g_DRM ); - } - - virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override - { - uint32_t uBlob = 0; - if ( type == typeid( glm::mat3x4 ) ) - { - assert( data.size() == sizeof( glm::mat3x4 ) ); - - drm_color_ctm2 ctm2; - const float *pData = reinterpret_cast( data.data() ); - for ( uint32_t i = 0; i < 12; i++ ) - ctm2.matrix[i] = drm_calc_s31_32( pData[i] ); - - if ( drmModeCreatePropertyBlob( g_DRM.fd, reinterpret_cast( &ctm2 ), sizeof( ctm2 ), &uBlob ) != 0 ) - return nullptr; - } - else - { - if ( drmModeCreatePropertyBlob( g_DRM.fd, data.data(), data.size(), &uBlob ) != 0 ) - return nullptr; - } - - return std::make_shared( data, uBlob, true ); - } - - virtual OwningRc ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override - { - return drm_fbid_from_dmabuf( &g_DRM, pDmaBuf ); - } - - virtual bool UsesModifiers() const override - { - return g_DRM.allow_modifiers; - } - virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override - { - const wlr_drm_format *pFormat = wlr_drm_format_set_get( &g_DRM.formats, uDrmFormat ); - if ( !pFormat ) - return std::span{}; - - return std::span{ pFormat->modifiers, pFormat->modifiers + pFormat->len }; - } - - virtual IBackendConnector *GetCurrentConnector() override - { - return g_DRM.pConnector; - } - - virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override - { - if ( GetCurrentConnector() && GetCurrentConnector()->GetScreenType() == eScreenType ) - return GetCurrentConnector(); - - if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - { - for ( auto &iter : g_DRM.connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - if ( pConnector->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - return pConnector; - } - } - - return nullptr; - } - - virtual bool SupportsPlaneHardwareCursor() const override - { - return true; - } - - virtual bool SupportsTearing() const override - { - return g_bSupportsAsyncFlips; - } - - virtual bool UsesVulkanSwapchain() const override - { - return false; - } - - virtual bool IsSessionBased() const override - { - return true; - } - - virtual bool SupportsExplicitSync() const override - { + class CDRMBackend; + + class CDRMBackend final : public CBaseBackend + { + public: + CDRMBackend( ) {} + + virtual ~CDRMBackend( ) + { + if ( g_DRM.fd != -1 ) finish_drm( &g_DRM ); + } + + virtual bool Init( ) override + { + if ( !vulkan_init( vulkan_get_instance( ), VK_NULL_HANDLE ) ) + { + fprintf( stderr, "Failed to initialize Vulkan\n" ); + return false; + } + + if ( !wlsession_init( ) ) + { + fprintf( stderr, "Failed to initialize Wayland session\n" ); + return false; + } + + return init_drm( + &g_DRM, + g_nPreferredOutputWidth, + g_nPreferredOutputHeight, + g_nNestedRefresh ); + } + + virtual bool PostInit( ) override + { + if ( g_DRM.pConnector ) + WritePatchedEdid( + g_DRM.pConnector->GetRawEDID( ), + g_DRM.pConnector->GetHDRInfo( ), + g_bRotated ); + return true; + } + + virtual std::span + GetInstanceExtensions( ) const override + { return std::span{}; } + virtual std::span + GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override + { return std::span{}; } + virtual VkImageLayout GetPresentLayout( ) const override + { + // Does not matter, as this has a queue family transition + // to VK_QUEUE_FAMILY_FOREIGN_EXT queue, + // thus: newLayout is ignored. + return VK_IMAGE_LAYOUT_GENERAL; + } + virtual void GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, + uint32_t *pOverlayPlaneFormat ) const override + { + *pPrimaryPlaneFormat = g_nDRMFormat; + *pOverlayPlaneFormat = g_nDRMFormatOverlay; + } + virtual bool + ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override + { return vulkan_has_drm_props( ); } + + int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) + { + static uint64_t s_ulLastTime = get_time_in_nanos( ); + uint64_t ulNow = get_time_in_nanos( ); + drm_log.debugf( + "CDRMBackend::Present Begin: %lu -> delta: %lu", + ulNow, + ulNow - s_ulLastTime ); + s_ulLastTime = ulNow; + + bool bWantsPartialComposite = + pFrameInfo->layerCount >= 3 && !kDisablePartialComposition; + + static bool s_bWasFirstFrame = true; + bool bWasFirstFrame = s_bWasFirstFrame; + s_bWasFirstFrame = false; + + bool bDrewCursor = false; + for ( uint32_t i = 0; i < k_nMaxLayers; i++ ) + { + if ( pFrameInfo->layers[ i ].zpos == g_zposCursor ) + { + bDrewCursor = true; + break; + } + } + + bool bLayer0ScreenSize = + close_enough( pFrameInfo->layers[ 0 ].scale.x, 1.0f ) && + close_enough( pFrameInfo->layers[ 0 ].scale.y, 1.0f ); + + bool bNeedsCompositeFromFilter = + ( g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || + g_upscaleFilter == GamescopeUpscaleFilter::PIXEL ) && + !bLayer0ScreenSize; + + bool bNeedsFullComposite = false; + bNeedsFullComposite |= cv_composite_force; + bNeedsFullComposite |= bWasFirstFrame; + bNeedsFullComposite |= pFrameInfo->useFSRLayer0; + bNeedsFullComposite |= pFrameInfo->useNISLayer0; + bNeedsFullComposite |= pFrameInfo->blurLayer0; + bNeedsFullComposite |= bNeedsCompositeFromFilter; + bNeedsFullComposite |= !k_bUseCursorPlane && bDrewCursor; + bNeedsFullComposite |= g_bColorSliderInUse; + bNeedsFullComposite |= pFrameInfo->bFadingOut; + bNeedsFullComposite |= !g_reshade_effect.empty( ); + + if ( g_bOutputHDREnabled ) + { + bNeedsFullComposite |= g_bHDRItmEnable; + if ( !SupportsColorManagement( ) ) + bNeedsFullComposite |= + ( pFrameInfo->layerCount > 1 || + pFrameInfo->layers[ 0 ].colorspace != + GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ); + } + else + { + if ( !SupportsColorManagement( ) ) + bNeedsFullComposite |= + ColorspaceIsHDR( pFrameInfo->layers[ 0 ].colorspace ); + } + + bNeedsFullComposite |= + !!( g_uCompositeDebug & CompositeDebugFlag::Heatmap ); + + bool bDoComposite = true; + if ( !bNeedsFullComposite && !bWantsPartialComposite ) + { + int ret = drm_prepare( &g_DRM, bAsync, pFrameInfo ); + if ( ret == 0 ) bDoComposite = false; + else if ( ret == -EACCES ) + return 0; + } + + // Update to let the vblank manager know we are currently + // compositing. + GetVBlankTimer( ).UpdateWasCompositing( bDoComposite ); + + if ( !bDoComposite ) + { + // Scanout + Planes Path + m_bWasPartialCompositing = false; + m_bWasCompositing = false; + if ( pFrameInfo->layerCount == 2 ) + m_nLastSingleOverlayZPos = pFrameInfo->layers[ 1 ].zpos; + + return Commit( pFrameInfo ); + } + + // Composition Path + if ( kDisablePartialComposition ) bNeedsFullComposite = true; + + FrameInfo_t compositeFrameInfo = *pFrameInfo; + + if ( compositeFrameInfo.layerCount == 1 ) + { + // If we failed to flip a single plane then + // we definitely need to composite for some reason... + bNeedsFullComposite = true; + } + + if ( !bNeedsFullComposite ) + { + // If we want to partial composite, fallback to full + // composite if we have mismatching colorspaces in our overlays. + // This is 2, and we do i-1 so 1...layerCount. So AFTER we have + // removed baseplane. Overlays only. + // + // Josh: + // We could handle mismatching colorspaces for partial + // composition but I want to keep overlay -> partial composition + // promotion as simple as possible, using the same 3D + SHAPER + // LUTs + BLEND in DRM as changing them is incredibly + // expensive!! It takes forever. We can't just point it to + // random BDA or whatever, it has to be uploaded slowly thru + // registers which is SUPER SLOW. This avoids stutter. + for ( int i = 2; i < compositeFrameInfo.layerCount; i++ ) + { + if ( pFrameInfo->layers[ i - 1 ].colorspace != + pFrameInfo->layers[ i ].colorspace ) + { + bNeedsFullComposite = true; + break; + } + } + } + + // If we ever promoted from partial -> full, for the first frame + // do NOT defer this partial composition. + // We were already stalling for the full composition before, so it's + // not an issue for latency, we just need to make sure we get 1 + // partial frame that isn't deferred in time so we don't lose + // layers. + bool bDefer = !bNeedsFullComposite && + ( !m_bWasCompositing || m_bWasPartialCompositing ); + + // If doing a partial composition then remove the baseplane + // from our frameinfo to composite. + if ( !bNeedsFullComposite ) + { + for ( int i = 1; i < compositeFrameInfo.layerCount; i++ ) + compositeFrameInfo.layers[ i - 1 ] = + compositeFrameInfo.layers[ i ]; + compositeFrameInfo.layerCount -= 1; + + // When doing partial composition, apply the shaper + 3D LUT + // stuff at scanout. + for ( uint32_t nEOTF = 0; nEOTF < EOTF_Count; nEOTF++ ) + { + compositeFrameInfo.shaperLut[ nEOTF ] = nullptr; + compositeFrameInfo.lut3D[ nEOTF ] = nullptr; + } + } + + // If using composite debug markers, make sure we mark them as + // partial so we know! + if ( bDefer && + !!( g_uCompositeDebug & CompositeDebugFlag::Markers ) ) + g_uCompositeDebug |= CompositeDebugFlag::Markers_Partial; + + std::optional oCompositeResult = vulkan_composite( + &compositeFrameInfo, nullptr, !bNeedsFullComposite ); + + m_bWasCompositing = true; + + g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; + + if ( !oCompositeResult ) + { + xwm_log.errorf( "vulkan_composite failed" ); + return -EINVAL; + } + + vulkan_wait( *oCompositeResult, true ); + + FrameInfo_t presentCompFrameInfo = {}; + presentCompFrameInfo.allowVRR = pFrameInfo->allowVRR; + presentCompFrameInfo.outputEncodingEOTF = + pFrameInfo->outputEncodingEOTF; + + if ( bNeedsFullComposite ) + { + presentCompFrameInfo.applyOutputColorMgmt = false; + presentCompFrameInfo.layerCount = 1; + + FrameInfo_t::Layer_t *baseLayer = + &presentCompFrameInfo.layers[ 0 ]; + baseLayer->scale.x = 1.0; + baseLayer->scale.y = 1.0; + baseLayer->opacity = 1.0; + baseLayer->zpos = g_zposBase; + + baseLayer->tex = vulkan_get_last_output_image( false, false ); + baseLayer->applyColorMgmt = false; + + baseLayer->filter = GamescopeUpscaleFilter::NEAREST; + baseLayer->ctm = nullptr; + baseLayer->colorspace = + pFrameInfo->outputEncodingEOTF == EOTF_PQ + ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ + : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + + m_bWasPartialCompositing = false; + } + else + { + if ( m_bWasPartialCompositing || !bDefer ) + { + presentCompFrameInfo.applyOutputColorMgmt = + g_ColorMgmt.pending.enabled; + presentCompFrameInfo.layerCount = 2; + + presentCompFrameInfo.layers[ 0 ] = pFrameInfo->layers[ 0 ]; + presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; + + FrameInfo_t::Layer_t *overlayLayer = + &presentCompFrameInfo.layers[ 1 ]; + overlayLayer->scale.x = 1.0; + overlayLayer->scale.y = 1.0; + overlayLayer->opacity = 1.0; + overlayLayer->zpos = g_zposOverlay; + + overlayLayer->tex = + vulkan_get_last_output_image( true, bDefer ); + overlayLayer->applyColorMgmt = g_ColorMgmt.pending.enabled; + + overlayLayer->filter = GamescopeUpscaleFilter::NEAREST; + // Partial composition stuff has the same colorspace. + // So read that from the composite frame info + overlayLayer->ctm = nullptr; + overlayLayer->colorspace = + compositeFrameInfo.layers[ 0 ].colorspace; + } + else + { + // Use whatever overlay we had last while waiting for the + // partial composition to have anything queued. + presentCompFrameInfo.applyOutputColorMgmt = + g_ColorMgmt.pending.enabled; + presentCompFrameInfo.layerCount = 1; + + presentCompFrameInfo.layers[ 0 ] = pFrameInfo->layers[ 0 ]; + presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; + + const FrameInfo_t::Layer_t *lastPresentedOverlayLayer = + nullptr; + for ( int i = 0; i < pFrameInfo->layerCount; i++ ) + { + if ( pFrameInfo->layers[ i ].zpos == + m_nLastSingleOverlayZPos ) + { + lastPresentedOverlayLayer = + &pFrameInfo->layers[ i ]; + break; + } + } + + if ( lastPresentedOverlayLayer ) + { + FrameInfo_t::Layer_t *overlayLayer = + &presentCompFrameInfo.layers[ 1 ]; + *overlayLayer = *lastPresentedOverlayLayer; + overlayLayer->zpos = g_zposOverlay; + + presentCompFrameInfo.layerCount = 2; + } + } + + m_bWasPartialCompositing = true; + } + + int ret = drm_prepare( &g_DRM, bAsync, &presentCompFrameInfo ); + + // Happens when we're VT-switched away + if ( ret == -EACCES ) return 0; + + if ( ret != 0 ) + { + if ( g_DRM.current.mode_id == 0 ) + { + xwm_log.errorf( + "We failed our modeset and have no mode to fall back " + "to! (Initial modeset failed?): %s", + strerror( -ret ) ); + return 0; + } + + xwm_log.errorf( + "Failed to prepare 1-layer flip (%s), trying again with " + "previous mode if modeset needed", + strerror( -ret ) ); + + // Try once again to in case we need to fall back to another + // mode. + ret = drm_prepare( &g_DRM, bAsync, &compositeFrameInfo ); + + // Happens when we're VT-switched away + if ( ret == -EACCES ) return 0; + + if ( ret != 0 ) + { + xwm_log.errorf( + "Failed to prepare 1-layer flip entirely: %s", + strerror( -ret ) ); + // We should always handle a 1-layer flip, this used to + // abort, but lets be more friendly and just avoid a commit + // and try again later. Let's re-poll our state, and force + // grab the best connector again. + // + // Some intense connector hotplugging could be occuring and + // the connector could become destroyed before we had a + // chance to use it as we hadn't reffed it in a commit yet. + this->DirtyState( true, false ); + this->PollState( ); + return ret; + } + } + + return Commit( &compositeFrameInfo ); + } + + virtual void DirtyState( bool bForce, bool bForceModeset ) override + { + if ( bForceModeset ) g_DRM.needs_modeset = true; + g_DRM.out_of_date = + std::max( g_DRM.out_of_date, bForce ? 2 : 1 ); + g_DRM.paused = !wlsession_active( ); + } + + virtual bool PollState( ) override { return drm_poll_state( &g_DRM ); } + + virtual std::shared_ptr CreateBackendBlob( + const std::type_info &type, std::span data ) override + { + uint32_t uBlob = 0; + if ( type == typeid( glm::mat3x4 ) ) + { + assert( data.size( ) == sizeof( glm::mat3x4 ) ); + + drm_color_ctm2 ctm2; + const float *pData = + reinterpret_cast( data.data( ) ); + for ( uint32_t i = 0; i < 12; i++ ) + ctm2.matrix[ i ] = drm_calc_s31_32( pData[ i ] ); + + if ( drmModeCreatePropertyBlob( + g_DRM.fd, + reinterpret_cast( &ctm2 ), + sizeof( ctm2 ), + &uBlob ) != 0 ) + return nullptr; + } + else + { + if ( drmModeCreatePropertyBlob( + g_DRM.fd, data.data( ), data.size( ), &uBlob ) != 0 ) + return nullptr; + } + + return std::make_shared( data, uBlob, true ); + } + + virtual OwningRc + ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override + { return drm_fbid_from_dmabuf( &g_DRM, pDmaBuf ); } + + virtual bool UsesModifiers( ) const override + { return g_DRM.allow_modifiers; } + virtual std::span + GetSupportedModifiers( uint32_t uDrmFormat ) const override + { + const wlr_drm_format *pFormat = + wlr_drm_format_set_get( &g_DRM.formats, uDrmFormat ); + if ( !pFormat ) return std::span{}; + + return std::span{ + pFormat->modifiers, pFormat->modifiers + pFormat->len + }; + } + + virtual IBackendConnector *GetCurrentConnector( ) override + { return g_DRM.pConnector; } + + virtual IBackendConnector * + GetConnector( GamescopeScreenType eScreenType ) override + { + if ( GetCurrentConnector( ) && + GetCurrentConnector( )->GetScreenType( ) == eScreenType ) + return GetCurrentConnector( ); + + if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) + { + for ( auto &iter : g_DRM.connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + if ( pConnector->GetScreenType( ) == + GAMESCOPE_SCREEN_TYPE_INTERNAL ) + return pConnector; + } + } + + return nullptr; + } + + virtual bool SupportsPlaneHardwareCursor( ) const override + { return true; } + + virtual bool SupportsTearing( ) const override + { return g_bSupportsAsyncFlips; } + + virtual bool UsesVulkanSwapchain( ) const override { return false; } + + virtual bool IsSessionBased( ) const override { return true; } + + virtual bool SupportsExplicitSync( ) const override + { #if __linux__ - auto [nMajor, nMinor, nPatch] = GetKernelVersion(); - - // Only expose support on 6.8+ for eventfd fixes. - if ( nMajor < 6 ) - return false; - - if ( nMajor == 6 && nMinor < 8 ) - return false; + auto [ nMajor, nMinor, nPatch ] = GetKernelVersion( ); + + // Only expose support on 6.8+ for eventfd fixes. + if ( nMajor < 6 ) return false; + + if ( nMajor == 6 && nMinor < 8 ) return false; #else - // I don't know about this for FreeBSD, etc. - return false; + // I don't know about this for FreeBSD, etc. + return false; #endif - return g_bSupportsSyncObjs && !cv_drm_debug_disable_explicit_sync; - } + return g_bSupportsSyncObjs && !cv_drm_debug_disable_explicit_sync; + } + + virtual bool IsPaused( ) const override { return g_DRM.paused; } + + virtual bool IsVisible( ) const override { return !this->IsPaused( ); } - virtual bool IsPaused() const override - { - return g_DRM.paused; - } + virtual glm::uvec2 + CursorSurfaceSize( glm::uvec2 uvecSize ) const override + { + if ( !k_bUseCursorPlane ) return uvecSize; + + return glm::uvec2{ g_DRM.cursor_width, g_DRM.cursor_height }; + } - virtual bool IsVisible() const override - { - return !this->IsPaused(); - } + virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override + { return drm_set_refresh( &g_DRM, nRefresh ); } - virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override - { - if ( !k_bUseCursorPlane ) - return uvecSize; + virtual void HackUpdatePatchedEdid( ) override + { + if ( !GetCurrentConnector( ) ) return; - return glm::uvec2{ g_DRM.cursor_width, g_DRM.cursor_height }; - } + WritePatchedEdid( + GetCurrentConnector( )->GetRawEDID( ), + GetCurrentConnector( )->GetHDRInfo( ), + g_bRotated ); + } - virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override - { - return drm_set_refresh( &g_DRM, nRefresh ); - } + protected: + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override + { + if ( pBlob->GetBlobValue( ) ) + drmModeDestroyPropertyBlob( g_DRM.fd, pBlob->GetBlobValue( ) ); + } - virtual void HackUpdatePatchedEdid() override - { - if ( !GetCurrentConnector() ) - return; - - WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), g_bRotated ); - } + private: + bool m_bWasCompositing = false; + bool m_bWasPartialCompositing = false; + int m_nLastSingleOverlayZPos = 0; - protected: - - virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override - { - if ( pBlob->GetBlobValue() ) - drmModeDestroyPropertyBlob( g_DRM.fd, pBlob->GetBlobValue() ); - } - - private: - bool m_bWasCompositing = false; - bool m_bWasPartialCompositing = false; - int m_nLastSingleOverlayZPos = 0; - - uint32_t m_uNextPresentCtx = 0; - DRMPresentCtx m_PresentCtxs[3]; - - bool SupportsColorManagement() const - { - return drm_supports_color_mgmt( &g_DRM ); - } - - int Commit( const FrameInfo_t *pFrameInfo ) - { - drm_t *drm = &g_DRM; - int ret = 0; - - assert( drm->req != nullptr ); - - defer( if ( drm->req != nullptr ) { drmModeAtomicFree( drm->req ); drm->req = nullptr; } ); - - bool isPageFlip = drm->flags & DRM_MODE_PAGE_FLIP_EVENT; - uint32_t uNewPendingFlipCount = 0; - - if ( isPageFlip ) - { - uNewPendingFlipCount = ++drm->uPendingFlipCount; - - // Do it before the commit, as otherwise the pageflip handler could - // potentially beat us to the refcount checks. - - // Swap over request FDs -> Queue - std::unique_lock lock( drm->m_QueuedFbIdsMutex ); - drm->m_QueuedFbIds.swap( drm->m_FbIdsInRequest ); - } - - GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents++; - - uint32_t uCurrentPresentCtx = m_uNextPresentCtx; - m_uNextPresentCtx = ( m_uNextPresentCtx + 1 ) % 3; - m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents; - - drm_log.debugf("flip commit %" PRIu64, (uint64_t)GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents); - gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents ); - - ret = drmModeAtomicCommit(drm->fd, drm->req, drm->flags, &m_PresentCtxs[uCurrentPresentCtx] ); - if ( ret != 0 ) - { - drm_log.errorf_errno( "flip error" ); - - if ( ret != -EBUSY && ret != -EACCES ) - { - drm_log.errorf( "fatal flip error, aborting" ); - if ( isPageFlip ) - drm->uPendingFlipCount--; - abort(); - } - - drm_rollback( drm ); - - // Swap back over to what was previously queued (probably nothing) - // if this commit failed. - { - std::unique_lock lock( drm->m_QueuedFbIdsMutex ); - drm->m_QueuedFbIds.swap( drm->m_FbIdsInRequest ); - } - // Clear our refs. - drm->m_FbIdsInRequest.clear(); - - GetCurrentConnector()->PresentationFeedback().m_uQueuedPresents--; - - if ( isPageFlip ) - drm->uPendingFlipCount--; - - return ret; - } else { - // Our request went through! - // Clear what we swapped with (what was previously queued) - drm->m_FbIdsInRequest.clear(); - - drm->current = drm->pending; - - for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - { - for ( std::optional &oProperty : pCRTC->GetProperties() ) - { - if ( oProperty ) - oProperty->OnCommit(); - } - } - - for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) - { - for ( std::optional &oProperty : pPlane->GetProperties() ) - { - if ( oProperty ) - oProperty->OnCommit(); - } - } - - for ( auto &iter : drm->connectors ) - { - gamescope::CDRMConnector *pConnector = &iter.second; - for ( std::optional &oProperty : pConnector->GetProperties() ) - { - if ( oProperty ) - oProperty->OnCommit(); - } - } - } - - // Update the draw time - // Ideally this would be updated by something right before the page flip - // is queued and would end up being the new page flip, rather than here. - // However, the page flip handler is called when the page flip occurs, - // not when it is successfully queued. - GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - - if ( isPageFlip ) - { - // Wait for bPendingFlip to change from true -> false. - drm->uPendingFlipCount.wait( uNewPendingFlipCount ); - assert( drm->uPendingFlipCount == 0 ); - } - - return ret; - } - - }; - - ///////////////////////// - // Backend Instantiator - ///////////////////////// - - template <> - bool IBackend::Set() - { - return Set( new CDRMBackend{} ); - } -} + uint32_t m_uNextPresentCtx = 0; + DRMPresentCtx m_PresentCtxs[ 3 ]; + + bool SupportsColorManagement( ) const + { return drm_supports_color_mgmt( &g_DRM ); } + + int Commit( const FrameInfo_t *pFrameInfo ) + { + drm_t *drm = &g_DRM; + int ret = 0; + + assert( drm->req != nullptr ); + + defer( if ( drm->req != nullptr ) { + drmModeAtomicFree( drm->req ); + drm->req = nullptr; + } ); + + bool isPageFlip = drm->flags & DRM_MODE_PAGE_FLIP_EVENT; + uint32_t uNewPendingFlipCount = 0; + + if ( isPageFlip ) + { + uNewPendingFlipCount = ++drm->uPendingFlipCount; + + // Do it before the commit, as otherwise the pageflip handler + // could potentially beat us to the refcount checks. + + // Swap over request FDs -> Queue + std::unique_lock lock( drm->m_QueuedFbIdsMutex ); + drm->m_QueuedFbIds.swap( drm->m_FbIdsInRequest ); + } + + GetCurrentConnector( )->PresentationFeedback( ).m_uQueuedPresents++; + + uint32_t uCurrentPresentCtx = m_uNextPresentCtx; + m_uNextPresentCtx = ( m_uNextPresentCtx + 1 ) % 3; + m_PresentCtxs[ uCurrentPresentCtx ].ulPendingFlipCount = + GetCurrentConnector( ) + ->PresentationFeedback( ) + .m_uQueuedPresents; + + drm_log.debugf( + "flip commit %" PRIu64, + ( uint64_t )GetCurrentConnector( ) + ->PresentationFeedback( ) + .m_uQueuedPresents ); + gpuvis_trace_printf( + "flip commit %" PRIu64, + ( uint64_t )GetCurrentConnector( ) + ->PresentationFeedback( ) + .m_uQueuedPresents ); + + ret = drmModeAtomicCommit( + drm->fd, + drm->req, + drm->flags, + &m_PresentCtxs[ uCurrentPresentCtx ] ); + if ( ret != 0 ) + { + drm_log.errorf_errno( "flip error" ); + + if ( ret != -EBUSY && ret != -EACCES ) + { + drm_log.errorf( "fatal flip error, aborting" ); + if ( isPageFlip ) drm->uPendingFlipCount--; + abort( ); + } + + drm_rollback( drm ); + + // Swap back over to what was previously queued (probably + // nothing) if this commit failed. + { + std::unique_lock lock( drm->m_QueuedFbIdsMutex ); + drm->m_QueuedFbIds.swap( drm->m_FbIdsInRequest ); + } + // Clear our refs. + drm->m_FbIdsInRequest.clear( ); + + GetCurrentConnector( ) + ->PresentationFeedback( ) + .m_uQueuedPresents--; + + if ( isPageFlip ) drm->uPendingFlipCount--; + + return ret; + } + else + { + // Our request went through! + // Clear what we swapped with (what was previously queued) + drm->m_FbIdsInRequest.clear( ); + + drm->current = drm->pending; + + for ( std::unique_ptr &pCRTC : drm->crtcs ) + { + for ( std::optional + &oProperty : pCRTC->GetProperties( ) ) + { + if ( oProperty ) oProperty->OnCommit( ); + } + } + + for ( std::unique_ptr &pPlane : + drm->planes ) + { + for ( std::optional + &oProperty : pPlane->GetProperties( ) ) + { + if ( oProperty ) oProperty->OnCommit( ); + } + } + + for ( auto &iter : drm->connectors ) + { + gamescope::CDRMConnector *pConnector = &iter.second; + for ( std::optional + &oProperty : pConnector->GetProperties( ) ) + { + if ( oProperty ) oProperty->OnCommit( ); + } + } + } + + // Update the draw time + // Ideally this would be updated by something right before the page + // flip is queued and would end up being the new page flip, rather + // than here. However, the page flip handler is called when the page + // flip occurs, not when it is successfully queued. + GetVBlankTimer( ).UpdateLastDrawTime( + get_time_in_nanos( ) - g_SteamCompMgrVBlankTime.ulWakeupTime ); + + if ( isPageFlip ) + { + // Wait for bPendingFlip to change from true -> false. + drm->uPendingFlipCount.wait( uNewPendingFlipCount ); + assert( drm->uPendingFlipCount == 0 ); + } + + return ret; + } + }; + + ///////////////////////// + // Backend Instantiator + ///////////////////////// + + template<> bool IBackend::Set( ) + { return Set( new CDRMBackend{} ); } +} // namespace gamescope int HackyDRMPresent( const FrameInfo_t *pFrameInfo, bool bAsync ) { - return static_cast( GetBackend() )->Present( pFrameInfo, bAsync ); + return static_cast( GetBackend( ) ) + ->Present( pFrameInfo, bAsync ); } - diff --git a/src/Backends/DeferredBackend.h b/src/Backends/DeferredBackend.h index 79d1f0aefe..1052686628 100644 --- a/src/Backends/DeferredBackend.h +++ b/src/Backends/DeferredBackend.h @@ -14,77 +14,73 @@ namespace gamescope { class CDeferredBackend; - class CDeferredBackend final : public CBaseBackend - { - public: - CDeferredBackend( IBackend *pChild ) - : m_pChild{ pChild } - { - } - - virtual ~CDeferredBackend() - { + class CDeferredBackend final : public CBaseBackend + { + public: + CDeferredBackend( IBackend *pChild ) : m_pChild{ pChild } {} + + virtual ~CDeferredBackend( ) + { if ( m_pChild ) { delete m_pChild; m_pChild = nullptr; } - } - - virtual bool Init() override - { - g_nOutputWidth = g_nPreferredOutputWidth; - g_nOutputHeight = g_nPreferredOutputHeight; - g_nOutputRefresh = g_nNestedRefresh; - - if ( g_nOutputHeight == 0 ) - { - if ( g_nOutputWidth != 0 ) - { - fprintf( stderr, "Cannot specify -W without -H\n" ); - return false; - } - g_nOutputHeight = 720; - } - if ( g_nOutputWidth == 0 ) - g_nOutputWidth = g_nOutputHeight * 16 / 9; - if ( g_nOutputRefresh == 0 ) - g_nOutputRefresh = ConvertHztomHz( 60 ); - - if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) + } + + virtual bool Init( ) override + { + g_nOutputWidth = g_nPreferredOutputWidth; + g_nOutputHeight = g_nPreferredOutputHeight; + g_nOutputRefresh = g_nNestedRefresh; + + if ( g_nOutputHeight == 0 ) + { + if ( g_nOutputWidth != 0 ) + { + fprintf( stderr, "Cannot specify -W without -H\n" ); + return false; + } + g_nOutputHeight = 720; + } + if ( g_nOutputWidth == 0 ) + g_nOutputWidth = g_nOutputHeight * 16 / 9; + if ( g_nOutputRefresh == 0 ) + g_nOutputRefresh = ConvertHztomHz( 60 ); + + if ( !vulkan_init( vulkan_get_instance( ), VK_NULL_HANDLE ) ) { return false; } - if ( !wlsession_init() ) + if ( !wlsession_init( ) ) { fprintf( stderr, "Failed to initialize deferred backend\n" ); return false; } - TryInittingChild(); + TryInittingChild( ); - return true; - } + return true; + } - virtual bool PostInit() override - { + virtual bool PostInit( ) override + { { std::shared_lock lock{ m_mutInit }; m_bDonePostInit = true; - if ( m_bInittedChild ) - return m_pChild->PostInit(); + if ( m_bInittedChild ) return m_pChild->PostInit( ); } return true; - } + } - virtual std::span GetInstanceExtensions() const override - { + virtual std::span + GetInstanceExtensions( ) const override + { // Basically what's needed to support SDL + OpenVR. - static const std::array requiredInstanceExts - { + static const std::array requiredInstanceExts{ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, @@ -94,13 +90,13 @@ namespace gamescope "VK_KHR_wayland_surface", VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, }; - return std::span{ requiredInstanceExts }; - } - virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override - { + return std::span{ requiredInstanceExts }; + } + virtual std::span + GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override + { // Basically what's needed to support OpenVR. - static const std::array requiredDeviceExts - { + static const std::array requiredDeviceExts{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, @@ -110,219 +106,218 @@ namespace gamescope VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, }; - return std::span{ requiredDeviceExts }; - } - virtual VkImageLayout GetPresentLayout() const override - { + return std::span{ requiredDeviceExts }; + } + virtual VkImageLayout GetPresentLayout( ) const override + { { std::shared_lock lock{ m_mutInit }; - if ( m_bInittedChild ) - return m_pChild->GetPresentLayout(); + if ( m_bInittedChild ) return m_pChild->GetPresentLayout( ); } - return VK_IMAGE_LAYOUT_GENERAL; - } - virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override - { - *pPrimaryPlaneFormat = VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); - *pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); - } - virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override - { - return true; - } - - virtual void DirtyState( bool bForce, bool bForceModeset ) override - { + return VK_IMAGE_LAYOUT_GENERAL; + } + virtual void GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, + uint32_t *pOverlayPlaneFormat ) const override + { + *pPrimaryPlaneFormat = + VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); + *pOverlayPlaneFormat = + VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); + } + virtual bool + ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override + { return true; } + + virtual void DirtyState( bool bForce, bool bForceModeset ) override + { { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) return m_pChild->DirtyState( bForce, bForceModeset ); } - } + } - virtual bool PollState() override - { - TryInittingChild(); + virtual bool PollState( ) override + { + TryInittingChild( ); { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) - return m_pChild->PollState() || m_bJustInittedPoll.exchange( false ); + return m_pChild->PollState( ) || + m_bJustInittedPoll.exchange( false ); } - return false; - } + return false; + } - virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override - { + virtual std::shared_ptr CreateBackendBlob( + const std::type_info &type, std::span data ) override + { // Only dummy backend blobs supported. - return std::make_shared( data ); - } + return std::make_shared( data ); + } - virtual OwningRc ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override - { - return m_pChild->ImportDmabufToBackend( pDmaBuf ); - } + virtual OwningRc + ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override + { return m_pChild->ImportDmabufToBackend( pDmaBuf ); } - virtual bool UsesModifiers() const override - { - return true; - } - virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override - { - return GetSupportedSampleModifiers( uDrmFormat ); - } - - virtual IBackendConnector *GetCurrentConnector() override - { + virtual bool UsesModifiers( ) const override { return true; } + virtual std::span + GetSupportedModifiers( uint32_t uDrmFormat ) const override + { return GetSupportedSampleModifiers( uDrmFormat ); } + + virtual IBackendConnector *GetCurrentConnector( ) override + { { std::shared_lock lock{ m_mutInit }; - if ( m_bInittedChild ) - return m_pChild->GetCurrentConnector(); + if ( m_bInittedChild ) return m_pChild->GetCurrentConnector( ); } return nullptr; - } - virtual IBackendConnector *GetCurrentMouseConnector() override - { + } + virtual IBackendConnector *GetCurrentMouseConnector( ) override + { { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) - return m_pChild->GetCurrentMouseConnector(); + return m_pChild->GetCurrentMouseConnector( ); } return nullptr; - } + } - virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override - { + virtual IBackendConnector * + GetConnector( GamescopeScreenType eScreenType ) override + { { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) return m_pChild->GetConnector( eScreenType ); } - - return nullptr; - } - virtual bool SupportsPlaneHardwareCursor() const override - { + return nullptr; + } + + virtual bool SupportsPlaneHardwareCursor( ) const override + { // Doesn't need to be 'initted' for this check. - return m_pChild->SupportsPlaneHardwareCursor(); - } + return m_pChild->SupportsPlaneHardwareCursor( ); + } - virtual bool SupportsTearing() const override - { + virtual bool SupportsTearing( ) const override + { { std::shared_lock lock{ m_mutInit }; - if ( m_bInittedChild ) - return m_pChild->SupportsTearing(); + if ( m_bInittedChild ) return m_pChild->SupportsTearing( ); } - - return false; - } - virtual bool UsesVulkanSwapchain() const override - { + return false; + } + + virtual bool UsesVulkanSwapchain( ) const override + { // Doesn't need to be 'initted' for this check. - return m_pChild->UsesVulkanSwapchain(); - } + return m_pChild->UsesVulkanSwapchain( ); + } - virtual bool IsSessionBased() const override - { + virtual bool IsSessionBased( ) const override + { // Doesn't need to be 'initted' for this check. - return m_pChild->IsSessionBased(); - } + return m_pChild->IsSessionBased( ); + } - virtual bool SupportsExplicitSync() const override - { + virtual bool SupportsExplicitSync( ) const override + { // Doesn't need to be 'initted' for this check. - return m_pChild->SupportsExplicitSync(); - } + return m_pChild->SupportsExplicitSync( ); + } - virtual bool IsPaused() const override - { + virtual bool IsPaused( ) const override + { { std::shared_lock lock{ m_mutInit }; - if ( m_bInittedChild ) - return m_pChild->IsPaused(); + if ( m_bInittedChild ) return m_pChild->IsPaused( ); } // We are always "paused" when not initted. // Don't do any commits! - return true; - } + return true; + } - virtual bool IsVisible() const override - { + virtual bool IsVisible( ) const override + { { std::shared_lock lock{ m_mutInit }; - if ( m_bInittedChild ) - return m_pChild->IsVisible(); + if ( m_bInittedChild ) return m_pChild->IsVisible( ); } - return true; - } + return true; + } - virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override - { + virtual glm::uvec2 + CursorSurfaceSize( glm::uvec2 uvecSize ) const override + { { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) return m_pChild->CursorSurfaceSize( uvecSize ); } - return uvecSize; - } + return uvecSize; + } - virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override - { + virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override + { { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) return m_pChild->HackTemporarySetDynamicRefresh( nRefresh ); } - return false; - } + return false; + } - virtual void HackUpdatePatchedEdid() override - { + virtual void HackUpdatePatchedEdid( ) override + { { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) - return m_pChild->HackUpdatePatchedEdid(); + return m_pChild->HackUpdatePatchedEdid( ); } - } + } - virtual bool NeedsFrameSync() const override + virtual bool NeedsFrameSync( ) const override { // Deferred backends do not support frame sync. return false; } - virtual TouchClickMode GetTouchClickMode() override + virtual TouchClickMode GetTouchClickMode( ) override { // Doesn't need to be 'initted' for this check. - return m_pChild->GetTouchClickMode(); + return m_pChild->GetTouchClickMode( ); } - virtual void DumpDebugInfo() override + virtual void DumpDebugInfo( ) override { // Doesn't need to be 'initted' for this check. - return m_pChild->DumpDebugInfo(); + return m_pChild->DumpDebugInfo( ); } - virtual bool UsesVirtualConnectors() override + virtual bool UsesVirtualConnectors( ) override { // Doesn't need to be 'initted' for this check. - return m_pChild->UsesVirtualConnectors(); + return m_pChild->UsesVirtualConnectors( ); } - virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override + virtual std::shared_ptr + CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override { { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) - return m_pChild->CreateVirtualConnector( ulVirtualConnectorKey ); + return m_pChild->CreateVirtualConnector( + ulVirtualConnectorKey ); } return nullptr; @@ -337,88 +332,77 @@ namespace gamescope } } - virtual bool SupportsVROverlayForwarding() override + virtual bool SupportsVROverlayForwarding( ) override { // Doesn't need to be 'initted' for this check. - return m_pChild->SupportsVROverlayForwarding(); + return m_pChild->SupportsVROverlayForwarding( ); } - virtual void ForwardFramebuffer( std::shared_ptr &pPlane, IBackendFb *pFramebuffer, const void *pData ) override + virtual void ForwardFramebuffer( + std::shared_ptr &pPlane, + IBackendFb *pFramebuffer, + const void *pData ) override { { std::shared_lock lock{ m_mutInit }; if ( m_bInittedChild ) - return m_pChild->ForwardFramebuffer( pPlane, pFramebuffer, pData ); + return m_pChild->ForwardFramebuffer( + pPlane, pFramebuffer, pData ); } } - bool IsChildInitted() - { - return m_bInittedChild; - } + bool IsChildInitted( ) { return m_bInittedChild; } - IBackend *GetChild() - { - return m_pChild; - } + IBackend *GetChild( ) { return m_pChild; } - bool NewlyInitted() override - { - return m_bJustInittedClient.exchange( false ); - } + bool NewlyInitted( ) override + { return m_bJustInittedClient.exchange( false ); } - bool ShouldFitWindows() override - { - return m_pChild->ShouldFitWindows(); - } + bool ShouldFitWindows( ) override + { return m_pChild->ShouldFitWindows( ); } - void OnEndFrame() override + void OnEndFrame( ) override { { std::shared_lock lock{ m_mutInit }; - if ( m_bInittedChild ) - m_pChild->OnEndFrame(); + if ( m_bInittedChild ) m_pChild->OnEndFrame( ); } } - protected: - - virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override - { - } - - private: + protected: + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override {} - void TryInittingChild() + private: + void TryInittingChild( ) { if ( !m_bInittedChild ) { std::unique_lock lock{ m_mutInit }; if ( !m_bInittedChild ) { - if ( m_pChild->Init() ) + if ( m_pChild->Init( ) ) { m_bInittedChild = true; if ( m_bDonePostInit ) { - bool bRet = m_pChild->PostInit(); + bool bRet = m_pChild->PostInit( ); assert( bRet && "PostInit failed!" ); } m_bJustInittedClient = true; - m_bJustInittedPoll = true; + m_bJustInittedPoll = true; } } } } - IBackend *m_pChild = nullptr; + IBackend *m_pChild = nullptr; mutable std::shared_mutex m_mutInit; - bool m_bDonePostInit = false; + bool m_bDonePostInit = false; - std::atomic m_bInittedChild = { false }; + std::atomic m_bInittedChild = { false }; std::atomic m_bJustInittedClient = { false }; - std::atomic m_bJustInittedPoll = { false }; - }; + std::atomic m_bJustInittedPoll = { false }; + }; -} +} // namespace gamescope diff --git a/src/Backends/HeadlessBackend.cpp b/src/Backends/HeadlessBackend.cpp index 8e4540066e..4e5da1ccaa 100644 --- a/src/Backends/HeadlessBackend.cpp +++ b/src/Backends/HeadlessBackend.cpp @@ -1,7 +1,7 @@ #include "backend.h" +#include "refresh_rate.h" #include "rendervulkan.hpp" #include "wlserver.hpp" -#include "refresh_rate.h" extern int g_nPreferredOutputWidth; extern int g_nPreferredOutputHeight; @@ -11,269 +11,184 @@ namespace gamescope class CHeadlessConnector final : public CBaseBackendConnector { public: - CHeadlessConnector() - { - } - virtual ~CHeadlessConnector() - { - } + CHeadlessConnector( ) {} + virtual ~CHeadlessConnector( ) {} + + virtual gamescope::GamescopeScreenType GetScreenType( ) const override + { return GAMESCOPE_SCREEN_TYPE_INTERNAL; } + virtual GamescopePanelOrientation + GetCurrentOrientation( ) const override + { return GAMESCOPE_PANEL_ORIENTATION_0; } + virtual bool SupportsHDR( ) const override { return false; } + virtual bool IsHDRActive( ) const override { return false; } + virtual const BackendConnectorHDRInfo &GetHDRInfo( ) const override + { return m_HDRInfo; } + virtual bool IsVRRActive( ) const override { return false; } + virtual std::span GetModes( ) const override + { return std::span{}; } + + virtual bool SupportsVRR( ) const override { return false; } + + virtual std::span GetRawEDID( ) const override + { return std::span{}; } + virtual std::span + GetValidDynamicRefreshRates( ) const override + { return std::span{}; } - virtual gamescope::GamescopeScreenType GetScreenType() const override - { - return GAMESCOPE_SCREEN_TYPE_INTERNAL; - } - virtual GamescopePanelOrientation GetCurrentOrientation() const override - { - return GAMESCOPE_PANEL_ORIENTATION_0; - } - virtual bool SupportsHDR() const override - { - return false; - } - virtual bool IsHDRActive() const override - { - return false; - } - virtual const BackendConnectorHDRInfo &GetHDRInfo() const override - { - return m_HDRInfo; - } - virtual bool IsVRRActive() const override - { - return false; - } - virtual std::span GetModes() const override + virtual void GetNativeColorimetry( + bool bHDR10, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const override { - return std::span{}; + *displayColorimetry = displaycolorimetry_709; + *displayEOTF = EOTF_Gamma22; + *outputEncodingColorimetry = displaycolorimetry_709; + *outputEncodingEOTF = EOTF_Gamma22; } - virtual bool SupportsVRR() const override - { - return false; - } + virtual const char *GetName( ) const override { return "Headless"; } + virtual const char *GetMake( ) const override { return "Gamescope"; } + virtual const char *GetModel( ) const override + { return "Virtual Display"; } - virtual std::span GetRawEDID() const override - { - return std::span{}; - } - virtual std::span GetValidDynamicRefreshRates() const override - { - return std::span{}; - } + virtual int + Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override + { return 0; } - virtual void GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override - { - *displayColorimetry = displaycolorimetry_709; - *displayEOTF = EOTF_Gamma22; - *outputEncodingColorimetry = displaycolorimetry_709; - *outputEncodingEOTF = EOTF_Gamma22; - } + private: + BackendConnectorHDRInfo m_HDRInfo{}; + }; - virtual const char *GetName() const override + class CHeadlessBackend final : public CBaseBackend + { + public: + CHeadlessBackend( ) {} + + virtual ~CHeadlessBackend( ) {} + + virtual bool Init( ) override { - return "Headless"; + g_nOutputWidth = g_nPreferredOutputWidth; + g_nOutputHeight = g_nPreferredOutputHeight; + g_nOutputRefresh = g_nNestedRefresh; + + if ( g_nOutputHeight == 0 ) + { + if ( g_nOutputWidth != 0 ) + { + fprintf( stderr, "Cannot specify -W without -H\n" ); + return false; + } + g_nOutputHeight = 720; + } + if ( g_nOutputWidth == 0 ) + g_nOutputWidth = g_nOutputHeight * 16 / 9; + if ( g_nOutputRefresh == 0 ) + g_nOutputRefresh = ConvertHztomHz( 60 ); + + if ( !vulkan_init( vulkan_get_instance( ), VK_NULL_HANDLE ) ) + { + return false; + } + + if ( !wlsession_init( ) ) + { + fprintf( stderr, "Failed to initialize Wayland session\n" ); + return false; + } + + return true; } - virtual const char *GetMake() const override + + virtual bool PostInit( ) override { return true; } + + virtual std::span + GetInstanceExtensions( ) const override + { return std::span{}; } + virtual std::span + GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override + { return std::span{}; } + virtual VkImageLayout GetPresentLayout( ) const override + { return VK_IMAGE_LAYOUT_GENERAL; } + virtual void GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, + uint32_t *pOverlayPlaneFormat ) const override { - return "Gamescope"; + *pPrimaryPlaneFormat = + VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); + *pOverlayPlaneFormat = + VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); } - virtual const char *GetModel() const override + virtual bool + ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override + { return true; } + + virtual void DirtyState( bool bForce, bool bForceModeset ) override {} + + virtual bool PollState( ) override { return false; } + + virtual std::shared_ptr CreateBackendBlob( + const std::type_info &type, std::span data ) override + { return std::make_shared( data ); } + + virtual OwningRc + ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override + { return new CBaseBackendFb( ); } + + virtual bool UsesModifiers( ) const override { return false; } + virtual std::span + GetSupportedModifiers( uint32_t uDrmFormat ) const override + { return std::span{}; } + + virtual IBackendConnector *GetCurrentConnector( ) override + { return &m_Connector; } + virtual IBackendConnector * + GetConnector( GamescopeScreenType eScreenType ) override { - return "Virtual Display"; + if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) + return &m_Connector; + + return nullptr; } - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override - { - return 0; - } + virtual bool SupportsPlaneHardwareCursor( ) const override + { return false; } - private: - BackendConnectorHDRInfo m_HDRInfo{}; - }; + virtual bool SupportsTearing( ) const override { return false; } + + virtual bool UsesVulkanSwapchain( ) const override { return false; } + + virtual bool IsSessionBased( ) const override { return false; } + + virtual bool SupportsExplicitSync( ) const override { return true; } + + virtual bool IsPaused( ) const override { return false; } - class CHeadlessBackend final : public CBaseBackend - { - public: - CHeadlessBackend() - { - } - - virtual ~CHeadlessBackend() - { - } - - virtual bool Init() override - { - g_nOutputWidth = g_nPreferredOutputWidth; - g_nOutputHeight = g_nPreferredOutputHeight; - g_nOutputRefresh = g_nNestedRefresh; - - if ( g_nOutputHeight == 0 ) - { - if ( g_nOutputWidth != 0 ) - { - fprintf( stderr, "Cannot specify -W without -H\n" ); - return false; - } - g_nOutputHeight = 720; - } - if ( g_nOutputWidth == 0 ) - g_nOutputWidth = g_nOutputHeight * 16 / 9; - if ( g_nOutputRefresh == 0 ) - g_nOutputRefresh = ConvertHztomHz( 60 ); - - if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) - { - return false; - } - - if ( !wlsession_init() ) - { - fprintf( stderr, "Failed to initialize Wayland session\n" ); - return false; - } - - return true; - } - - virtual bool PostInit() override - { - return true; - } - - virtual std::span GetInstanceExtensions() const override - { - return std::span{}; - } - virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override - { - return std::span{}; - } - virtual VkImageLayout GetPresentLayout() const override - { - return VK_IMAGE_LAYOUT_GENERAL; - } - virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override - { - *pPrimaryPlaneFormat = VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); - *pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); - } - virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override - { - return true; - } - - virtual void DirtyState( bool bForce, bool bForceModeset ) override - { - } - - virtual bool PollState() override - { - return false; - } - - virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override - { - return std::make_shared( data ); - } - - virtual OwningRc ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override - { - return new CBaseBackendFb(); - } - - virtual bool UsesModifiers() const override - { - return false; - } - virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override - { - return std::span{}; - } - - virtual IBackendConnector *GetCurrentConnector() override - { - return &m_Connector; - } - virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override - { - if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - return &m_Connector; - - return nullptr; - } - - virtual bool SupportsPlaneHardwareCursor() const override - { - return false; - } - - virtual bool SupportsTearing() const override - { - return false; - } - - virtual bool UsesVulkanSwapchain() const override - { - return false; - } - - virtual bool IsSessionBased() const override - { - return false; - } - - virtual bool SupportsExplicitSync() const override - { - return true; - } - - virtual bool IsPaused() const override - { - return false; - } - - virtual bool IsVisible() const override - { - return true; - } - - virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override - { - return uvecSize; - } - - virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override - { - return false; - } - - virtual void HackUpdatePatchedEdid() override - { - } - - protected: - - virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override - { - } - - private: + virtual bool IsVisible( ) const override { return true; } + virtual glm::uvec2 + CursorSurfaceSize( glm::uvec2 uvecSize ) const override + { return uvecSize; } + + virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override + { return false; } + + virtual void HackUpdatePatchedEdid( ) override {} + + protected: + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override {} + + private: CHeadlessConnector m_Connector; - }; + }; - ///////////////////////// - // Backend Instantiator - ///////////////////////// + ///////////////////////// + // Backend Instantiator + ///////////////////////// - template <> - bool IBackend::Set() - { - return Set( new CHeadlessBackend{} ); - } + template<> bool IBackend::Set( ) + { return Set( new CHeadlessBackend{} ); } -} \ No newline at end of file +} // namespace gamescope diff --git a/src/Backends/OpenVRBackend.cpp b/src/Backends/OpenVRBackend.cpp index efdc779ce2..b3c8c233e7 100644 --- a/src/Backends/OpenVRBackend.cpp +++ b/src/Backends/OpenVRBackend.cpp @@ -1,81 +1,110 @@ -#include #include +#include #define VK_NO_PROTOTYPES -#include #include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" #include #pragma GCC diagnostic pop +#include "LibInputHandler.h" +#include "Ratio.h" #include "backend.h" +#include "edid.h" +#include "ime.hpp" +#include "log.hpp" #include "main.hpp" #include "openvr.h" +#include "refresh_rate.h" #include "steamcompmgr.hpp" #include "wlserver.hpp" -#include "log.hpp" -#include "ime.hpp" -#include "refresh_rate.h" -#include "edid.h" -#include "Ratio.h" -#include "LibInputHandler.h" +#include +#include #include #include #include -#include -#include struct wlserver_input_method; -extern bool steamMode; -extern int g_argc; +extern bool steamMode; +extern int g_argc; extern char **g_argv; extern int g_nPreferredOutputWidth; extern int g_nPreferredOutputHeight; -extern int g_nPreferredOutputWidth; -extern int g_nPreferredOutputHeight; +extern int g_nPreferredOutputWidth; +extern int g_nPreferredOutputHeight; extern bool g_bForceHDR10OutputDebug; extern bool g_bBorderlessOutputWindow; extern std::string *g_pVROverlayKey; extern gamescope::ConVar cv_composite_force; -extern bool g_bColorSliderInUse; -extern bool fadingOut; -extern std::string g_reshade_effect; +extern bool g_bColorSliderInUse; +extern bool fadingOut; +extern std::string g_reshade_effect; extern gamescope::ConVar cv_hdr_enabled; extern uint64_t g_SteamCompMgrLimitedAppRefreshCycle; -extern bool g_bAllowDeferredBackend; - -void MakeFocusDirty(); -void update_connector_display_info_wl(struct drm_t *drm); -void close_virtual_connector_key(gamescope::VirtualConnectorKey_t eKey); - -static LogScope openvr_log("openvr"); - -static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList ); -static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, std::vector< std::string > &outDeviceExtensionList ); - -gamescope::ConVar cv_vr_always_warp_cursor( "vr_always_warp_cursor", true, "Whether or not we should always warp the cursor, even if it is invisible so we get hover events." ); -gamescope::ConVar cv_vr_use_modifiers( "vr_use_modifiers", true, "Use DMA-BUF modifiers?" ); -gamescope::ConVar cv_vr_transparent_backing( "vr_transparent_backing", false, "Should backing be transparent or not?" ); -gamescope::ConVar cv_vr_use_window_icons( "vr_use_window_icons", true, "Should we use window icons if they are available?" ); -gamescope::ConVar cv_vr_trackpad_hide_laser( "vr_trackpad_hide_laser", false, "Hide laser mouse when we are in trackpad mode." ); -gamescope::ConVar cv_vr_trackpad_relative_mouse_mode( "vr_trackpad_relative_mouse_mode", true, "If we are in relative mouse mode, treat the screen like a big trackpad?" ); -gamescope::ConVar cv_vr_trackpad_sensitivity( "vr_trackpad_sensitivity", 1500.f, "Sensitivity for VR Trackpad Mode" ); -gamescope::ConVar cv_vr_trackpad_click_time( "vr_trackpad_click_time", 250'000'000ul, "Time to consider a 'click' vs a 'drag' when using trackpad mode. In nanoseconds." ); -gamescope::ConVar cv_vr_trackpad_click_max_delta( "vr_trackpad_click_max_delta", 0.14f, "Max amount the cursor can move before not clicking." ); -gamescope::ConVar cv_vr_debug_force_opaque( "vr_debug_force_opaque", false, "Force textures to be treated as opaque." ); -gamescope::ConVar cv_vr_nudge_to_visible_per_connector( "vr_nudge_to_visible_per_connector", false, "" ); +extern bool g_bAllowDeferredBackend; + +void MakeFocusDirty( ); +void update_connector_display_info_wl( struct drm_t *drm ); +void close_virtual_connector_key( gamescope::VirtualConnectorKey_t eKey ); + +static LogScope openvr_log( "openvr" ); + +static bool GetVulkanInstanceExtensionsRequired( + std::vector &outInstanceExtensionList ); +static bool GetVulkanDeviceExtensionsRequired( + VkPhysicalDevice pPhysicalDevice, + std::vector &outDeviceExtensionList ); + +gamescope::ConVar cv_vr_always_warp_cursor( + "vr_always_warp_cursor", + true, + "Whether or not we should always warp the cursor, even if it is invisible " + "so we get hover events." ); +gamescope::ConVar + cv_vr_use_modifiers( "vr_use_modifiers", true, "Use DMA-BUF modifiers?" ); +gamescope::ConVar cv_vr_transparent_backing( + "vr_transparent_backing", false, "Should backing be transparent or not?" ); +gamescope::ConVar cv_vr_use_window_icons( + "vr_use_window_icons", + true, + "Should we use window icons if they are available?" ); +gamescope::ConVar cv_vr_trackpad_hide_laser( + "vr_trackpad_hide_laser", + false, + "Hide laser mouse when we are in trackpad mode." ); +gamescope::ConVar cv_vr_trackpad_relative_mouse_mode( + "vr_trackpad_relative_mouse_mode", + true, + "If we are in relative mouse mode, treat the screen like a big trackpad?" ); +gamescope::ConVar cv_vr_trackpad_sensitivity( + "vr_trackpad_sensitivity", 1500.f, "Sensitivity for VR Trackpad Mode" ); +gamescope::ConVar cv_vr_trackpad_click_time( + "vr_trackpad_click_time", + 250'000'000ul, + "Time to consider a 'click' vs a 'drag' when using trackpad mode. In " + "nanoseconds." ); +gamescope::ConVar cv_vr_trackpad_click_max_delta( + "vr_trackpad_click_max_delta", + 0.14f, + "Max amount the cursor can move before not clicking." ); +gamescope::ConVar cv_vr_debug_force_opaque( + "vr_debug_force_opaque", false, "Force textures to be treated as opaque." ); +gamescope::ConVar cv_vr_nudge_to_visible_per_connector( + "vr_nudge_to_visible_per_connector", false, "" ); // Maximum interval between polling for VR events (normally paced by frame sync) -gamescope::ConVar cv_vr_poll_rate( "vr_poll_rate", 50ul, "Max time between input polls. In milliseconds." ); +gamescope::ConVar cv_vr_poll_rate( + "vr_poll_rate", 50ul, "Max time between input polls. In milliseconds." ); extern std::atomic g_FocusedVROverlayMouse; extern std::atomic g_FocusedVROverlayKeyboard; @@ -83,27 +112,31 @@ extern std::atomic g_FocusedVROverlayKeyboard; // Not in public headers yet. namespace vr { - const EVRButtonId k_EButton_Steam = (EVRButtonId)(50); - const EVRButtonId k_EButton_QAM = (EVRButtonId)(51); - - constexpr EVREventType VREvent_OverlayInputFocusChanged = ( EVREventType )(564); // data is overlayInputFocus + const EVRButtonId k_EButton_Steam = ( EVRButtonId )( 50 ); + const EVRButtonId k_EButton_QAM = ( EVRButtonId )( 51 ); + constexpr EVREventType VREvent_OverlayInputFocusChanged = + ( EVREventType )( 564 ); // data is overlayInputFocus /** Used in VREvent_OverlayInputFocus_t */ enum EVRInputFocusEventFlags { // Indicates the overlay should actively have keyboard focus. - k_EVRInputFocusEventFlags_OverlayShouldShowAffordanceForKeyboardInput = 1 << 0, + k_EVRInputFocusEventFlags_OverlayShouldShowAffordanceForKeyboardInput = + 1 << 0, // Indicates the overlay should actively have gamepad focus. - k_EVRInputFocusEventFlags_OverlayShouldShowAffordanceForGamepadInput = 1 << 1, + k_EVRInputFocusEventFlags_OverlayShouldShowAffordanceForGamepadInput = + 1 << 1, }; /** Used for a few events about overlay input focus */ struct VREvent_OverlayInputFocus_t { - // Overlay that has keyboard and/or gamepad focus. If this is your overlay and its contents is a desktop or a desktop window, - // this event is a great oppportunity to move that window to the foreground so it receives input. + // Overlay that has keyboard and/or gamepad focus. If this is your + // overlay and its contents is a desktop or a desktop window, this event + // is a great oppportunity to move that window to the foreground so it + // receives input. uint64_t overlayHandle; // VROverlayHandle_t - uint32_t flags; // EVRInputFocusEventFlags + uint32_t flags; // EVRInputFocusEventFlags }; typedef union @@ -111,15 +144,16 @@ namespace vr VREvent_OverlayInputFocus_t overlayInputFocus; } VREvent_Data_Gamescope_t; - static inline const VREvent_Data_Gamescope_t &CastToGamescopeEventData( const VREvent_Data_t &eventData ) - { - return reinterpret_cast< const vr::VREvent_Data_Gamescope_t & >( eventData ); - } - static inline VREvent_Data_Gamescope_t &CastToGamescopeEventData( VREvent_Data_t &eventData ) + [[maybe_unused]] static inline const VREvent_Data_Gamescope_t & + CastToGamescopeEventData( const VREvent_Data_t &eventData ) { - return reinterpret_cast< vr::VREvent_Data_Gamescope_t & >( eventData ); + return reinterpret_cast( + eventData ); } -} + static inline VREvent_Data_Gamescope_t & + CastToGamescopeEventData( VREvent_Data_t &eventData ) + { return reinterpret_cast( eventData ); } +} // namespace vr // extern std::atomic g_unCurrentVRSceneAppId; @@ -128,36 +162,40 @@ uint32_t get_appid_from_pid( pid_t pid ); /////////////////////////////////////////////// // Josh: -// GetVulkanInstanceExtensionsRequired and GetVulkanDeviceExtensionsRequired return *space separated* exts :( -// I am too lazy to write that myself. -// This is stolen verbatim from hellovr_vulkan with the .clear removed. -// If it is broken, blame the samples. +// GetVulkanInstanceExtensionsRequired and GetVulkanDeviceExtensionsRequired +// return *space separated* exts :( I am too lazy to write that myself. This is +// stolen verbatim from hellovr_vulkan with the .clear removed. If it is broken, +// blame the samples. -static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList ) +static bool GetVulkanInstanceExtensionsRequired( + std::vector &outInstanceExtensionList ) { - if ( !vr::VRCompositor() ) + if ( !vr::VRCompositor( ) ) { - openvr_log.errorf( "GetVulkanInstanceExtensionsRequired: Failed to get VRCompositor" ); + openvr_log.errorf( + "GetVulkanInstanceExtensionsRequired: Failed to get VRCompositor" ); return false; } - uint32_t nBufferSize = vr::VRCompositor()->GetVulkanInstanceExtensionsRequired( nullptr, 0 ); + uint32_t nBufferSize = + vr::VRCompositor( )->GetVulkanInstanceExtensionsRequired( nullptr, 0 ); if ( nBufferSize > 0 ) { // Allocate memory for the space separated list and query for it char *pExtensionStr = new char[ nBufferSize ]; - pExtensionStr[0] = 0; - vr::VRCompositor()->GetVulkanInstanceExtensionsRequired( pExtensionStr, nBufferSize ); + pExtensionStr[ 0 ] = 0; + vr::VRCompositor( )->GetVulkanInstanceExtensionsRequired( + pExtensionStr, nBufferSize ); // Break up the space separated list into entries on the CUtlStringList std::string curExtStr; - uint32_t nIndex = 0; + uint32_t nIndex = 0; while ( pExtensionStr[ nIndex ] != 0 && ( nIndex < nBufferSize ) ) { if ( pExtensionStr[ nIndex ] == ' ' ) { outInstanceExtensionList.push_back( curExtStr ); - curExtStr.clear(); + curExtStr.clear( ); } else { @@ -165,42 +203,50 @@ static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &out } nIndex++; } - if ( curExtStr.size() > 0 ) + if ( curExtStr.size( ) > 0 ) { outInstanceExtensionList.push_back( curExtStr ); } - delete [] pExtensionStr; + delete[] pExtensionStr; } return true; } -static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, std::vector< std::string > &outDeviceExtensionList ) +static bool GetVulkanDeviceExtensionsRequired( + VkPhysicalDevice pPhysicalDevice, + std::vector &outDeviceExtensionList ) { - if ( !vr::VRCompositor() ) + if ( !vr::VRCompositor( ) ) { - openvr_log.errorf( "GetVulkanDeviceExtensionsRequired: Failed to get VRCompositor" ); + openvr_log.errorf( + "GetVulkanDeviceExtensionsRequired: Failed to get VRCompositor" ); return false; } - uint32_t nBufferSize = vr::VRCompositor()->GetVulkanDeviceExtensionsRequired( ( VkPhysicalDevice_T * ) pPhysicalDevice, nullptr, 0 ); + uint32_t nBufferSize = + vr::VRCompositor( )->GetVulkanDeviceExtensionsRequired( + ( VkPhysicalDevice_T * )pPhysicalDevice, nullptr, 0 ); if ( nBufferSize > 0 ) { // Allocate memory for the space separated list and query for it char *pExtensionStr = new char[ nBufferSize ]; - pExtensionStr[0] = 0; - vr::VRCompositor()->GetVulkanDeviceExtensionsRequired( ( VkPhysicalDevice_T * ) pPhysicalDevice, pExtensionStr, nBufferSize ); + pExtensionStr[ 0 ] = 0; + vr::VRCompositor( )->GetVulkanDeviceExtensionsRequired( + ( VkPhysicalDevice_T * )pPhysicalDevice, + pExtensionStr, + nBufferSize ); // Break up the space separated list into entries on the CUtlStringList std::string curExtStr; - uint32_t nIndex = 0; + uint32_t nIndex = 0; while ( pExtensionStr[ nIndex ] != 0 && ( nIndex < nBufferSize ) ) { if ( pExtensionStr[ nIndex ] == ' ' ) { outDeviceExtensionList.push_back( curExtStr ); - curExtStr.clear(); + curExtStr.clear( ); } else { @@ -208,12 +254,12 @@ static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, } nIndex++; } - if ( curExtStr.size() > 0 ) + if ( curExtStr.size( ) > 0 ) { outDeviceExtensionList.push_back( curExtStr ); } - delete [] pExtensionStr; + delete[] pExtensionStr; } return true; @@ -229,128 +275,137 @@ namespace gamescope class COpenVRFb final : public CBaseBackendFb { public: - COpenVRFb( COpenVRBackend *pBackend, struct wlr_dmabuf_attributes *attributes ); - ~COpenVRFb(); + COpenVRFb( + COpenVRBackend *pBackend, + struct wlr_dmabuf_attributes *attributes ); + ~COpenVRFb( ); + + IBackendFb *EnsureImported( ) override; - IBackendFb *EnsureImported() override; + vr::SharedTextureHandle_t GetSharedTextureHandle( ) const + { return m_ulHandle; } - vr::SharedTextureHandle_t GetSharedTextureHandle() const { return m_ulHandle; } private: bool Import( struct wlr_dmabuf_attributes *attributes ); - void ClearAttributes(); + void ClearAttributes( ); - COpenVRBackend *m_pBackend = nullptr; + COpenVRBackend *m_pBackend = nullptr; struct wlr_dmabuf_attributes m_attributes{}; - bool m_bHasDmabufAttributes = false; - vr::SharedTextureHandle_t m_ulHandle = 0; + bool m_bHasDmabufAttributes = false; + vr::SharedTextureHandle_t m_ulHandle = 0; }; // TODO: Merge with WaylandPlaneState struct OpenVRPlaneState { - CVulkanTexture *pTexture; - int32_t nDestX; - int32_t nDestY; - double flSrcX; - double flSrcY; - double flSrcWidth; - double flSrcHeight; - int32_t nDstWidth; - int32_t nDstHeight; + CVulkanTexture *pTexture; + int32_t nDestX; + int32_t nDestY; + double flSrcX; + double flSrcY; + double flSrcWidth; + double flSrcHeight; + int32_t nDstWidth; + int32_t nDstHeight; GamescopeAppTextureColorspace eColorspace; - bool bOpaque; - float flAlpha = 1.0f; + bool bOpaque; + float flAlpha = 1.0f; }; class COpenVRPlane final : public IBackendPlane { public: COpenVRPlane( COpenVRConnector *pConnector ); - ~COpenVRPlane(); + ~COpenVRPlane( ); bool Init( COpenVRPlane *pParent, COpenVRPlane *pSiblingBelow ); - void InitAsForwarder( vr::VROverlayHandle_t hOverlay ) { m_hOverlay = hOverlay; } + void InitAsForwarder( vr::VROverlayHandle_t hOverlay ) + { m_hOverlay = hOverlay; } void Present( std::optional oState ); void Present( const FrameInfo_t::Layer_t *pLayer ); void ForwardFramebuffer( COpenVRFb *pFb ); - vr::VROverlayHandle_t GetOverlay() const { return m_hOverlay; } - vr::VROverlayHandle_t GetOverlayThumbnail() const { return m_hOverlayThumbnail; } + vr::VROverlayHandle_t GetOverlay( ) const { return m_hOverlay; } + vr::VROverlayHandle_t GetOverlayThumbnail( ) const + { return m_hOverlayThumbnail; } - uint32_t GetSortOrder() const { return m_uSortOrder; } - bool IsSubview() const { return m_bIsSubview; } + uint32_t GetSortOrder( ) const { return m_uSortOrder; } + bool IsSubview( ) const { return m_bIsSubview; } - COpenVRBackend *GetBackend() const { return m_pBackend; } - COpenVRConnector *GetConnector() const { return m_pConnector; } + COpenVRBackend *GetBackend( ) const { return m_pBackend; } + COpenVRConnector *GetConnector( ) const { return m_pConnector; } - void OnPageFlip(); + void OnPageFlip( ); - bool operator==( const vr::VROverlayHandle_t& hOverlay ) const - { - return this->m_hOverlay == hOverlay; - } + bool operator==( const vr::VROverlayHandle_t &hOverlay ) const + { return this->m_hOverlay == hOverlay; } private: COpenVRConnector *m_pConnector = nullptr; - COpenVRBackend *m_pBackend = nullptr; + COpenVRBackend *m_pBackend = nullptr; std::string m_sDashboardOverlayKey; - bool m_bIsSubview = false; - uint32_t m_uSortOrder = 0; - vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid; - vr::VROverlayHandle_t m_hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; + bool m_bIsSubview = false; + uint32_t m_uSortOrder = 0; + vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid; + vr::VROverlayHandle_t m_hOverlayThumbnail = + vr::k_ulOverlayHandleInvalid; - std::mutex m_mutFbIds; + std::mutex m_mutFbIds; Rc m_pQueuedFbId; Rc m_pVisibleFbId; }; - class COpenVRConnector final : public CBaseBackendConnector, public INestedHints + class COpenVRConnector final : public CBaseBackendConnector, + public INestedHints { public: - - COpenVRConnector( COpenVRBackend *pBackend, uint64_t ulVirtualConnectorKey ); + COpenVRConnector( + COpenVRBackend *pBackend, uint64_t ulVirtualConnectorKey ); ////////////////////// // IBackendConnector ////////////////////// - ~COpenVRConnector(); - virtual GamescopeScreenType GetScreenType() const override; - virtual GamescopePanelOrientation GetCurrentOrientation() const override; - virtual bool SupportsHDR() const override; - virtual bool IsHDRActive() const override; - virtual const BackendConnectorHDRInfo &GetHDRInfo() const override; - virtual bool IsVRRActive() const override; - virtual std::span GetModes() const override; + ~COpenVRConnector( ); + virtual GamescopeScreenType GetScreenType( ) const override; + virtual GamescopePanelOrientation + GetCurrentOrientation( ) const override; + virtual bool SupportsHDR( ) const override; + virtual bool IsHDRActive( ) const override; + virtual const BackendConnectorHDRInfo &GetHDRInfo( ) const override; + virtual bool IsVRRActive( ) const override; + virtual std::span GetModes( ) const override; - virtual bool SupportsVRR() const override; + virtual bool SupportsVRR( ) const override; - virtual std::span GetRawEDID() const override; - virtual std::span GetValidDynamicRefreshRates() const override; + virtual std::span GetRawEDID( ) const override; + virtual std::span + GetValidDynamicRefreshRates( ) const override; virtual void GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override; + bool bHDR10, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const override; - virtual const char *GetName() const override; - virtual const char *GetMake() const override; - virtual const char *GetModel() const override; + virtual const char *GetName( ) const override; + virtual const char *GetMake( ) const override; + virtual const char *GetModel( ) const override; - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; + virtual int + Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; - virtual INestedHints *GetNestedHints() override - { - return this; - } + virtual INestedHints *GetNestedHints( ) override { return this; } - bool IsTouchForbidden() const { return m_bForbidTouchMode; } + bool IsTouchForbidden( ) const { return m_bForbidTouchMode; } - virtual void SetProperty( ConnectorProperty eProperty, std::any value ) + virtual void + SetProperty( ConnectorProperty eProperty, std::any value ) override { if ( eProperty == ConnectorProperty::IsFileBrowser ) { @@ -362,44 +417,48 @@ namespace gamescope // INestedHints /////////////////// - virtual void SetCursorImage( std::shared_ptr info ) override; + virtual void SetCursorImage( + std::shared_ptr info ) override; virtual void SetRelativeMouseMode( bool bRelative ) override; virtual void SetVisible( bool bVisible ) override; virtual void SetTitle( std::shared_ptr szTitle ) override; - virtual void SetIcon( std::shared_ptr> uIconPixels ) override; - virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override; - virtual bool ShouldPaintCursor() override { return true; } + virtual void + SetIcon( std::shared_ptr> uIconPixels ) override; + virtual void SetSelection( + std::shared_ptr szContents, + GamescopeSelection eSelection ) override; + virtual bool ShouldPaintCursor( ) override { return true; } + + bool UpdateEdid( ); - bool UpdateEdid(); + bool Init( ); - bool Init(); + COpenVRBackend *GetBackend( ) const { return m_pBackend; } - COpenVRBackend *GetBackend() const { return m_pBackend; } + COpenVRPlane *GetPrimaryPlane( ) { return &m_Planes[ 0 ]; } - COpenVRPlane *GetPrimaryPlane() + std::span GetPlanes( ) { - return &m_Planes[0]; + return std::span( + &m_Planes[ 0 ], std::size( m_Planes ) ); } - std::span GetPlanes() { return std::span( &m_Planes[0], std::size( m_Planes ) ); } - COpenVRPlane *GetPlaneByOverlayHandle( vr::VROverlayHandle_t hOverlay ) { - auto iter = std::find( std::begin( m_Planes ), std::end( m_Planes ), hOverlay ); - if ( iter == std::end( m_Planes ) ) - return nullptr; + auto iter = std::find( + std::begin( m_Planes ), std::end( m_Planes ), hOverlay ); + if ( iter == std::end( m_Planes ) ) return nullptr; else - return &(*iter); + return &( *iter ); } - bool ConsumeNudgeToVisible() { return std::exchange( m_bNudgeToVisible, false ); } - bool IsRelativeMouse() const { return m_bRelativeMouse; } + bool ConsumeNudgeToVisible( ) + { return std::exchange( m_bNudgeToVisible, false ); } + bool IsRelativeMouse( ) const { return m_bRelativeMouse; } // Thread safe. - bool IsVisible() const - { - return m_bOverlayShown || m_bSceneAppVisible; - } + bool IsVisible( ) const + { return m_bOverlayShown || m_bSceneAppVisible; } // Only called from event thread void MarkOverlayShown( bool bShown ) @@ -418,60 +477,61 @@ namespace gamescope void UpdateVisibility( const char *pszReason ); // XXX - std::atomic m_bUsingVRMouse = { true }; - bool m_bCurrentlyOverridingPosition = false; + std::atomic m_bUsingVRMouse = { true }; + bool m_bCurrentlyOverridingPosition = false; private: COpenVRBackend *m_pBackend = nullptr; - COpenVRPlane m_Planes[8]; + COpenVRPlane m_Planes[ 8 ]; BackendConnectorHDRInfo m_HDRInfo{}; - std::vector m_FakeEdid; + std::vector m_FakeEdid; - bool m_bNudgeToVisible = false; - std::atomic m_bRelativeMouse = false; + bool m_bNudgeToVisible = false; + std::atomic m_bRelativeMouse = false; - bool m_bWasVisible = false; // Event thread only - std::atomic m_bOverlayShown = { false }; + bool m_bWasVisible = false; // Event thread only + std::atomic m_bOverlayShown = { false }; std::atomic m_bSceneAppVisible = { false }; bool m_bForbidTouchMode = false; }; - class COpenVRBackend final : public CBaseBackend - { - public: - COpenVRBackend() - : m_LibInputWaiter{ "gamescope-libinput" } - , m_FlipHandlerThread{ [this](){ this->FlipHandlerThread(); } } - { - } - - virtual ~COpenVRBackend() - { + class COpenVRBackend final : public CBaseBackend + { + public: + COpenVRBackend( ) : + m_LibInputWaiter{ "gamescope-libinput" }, + m_FlipHandlerThread{ [ this ]( ) { this->FlipHandlerThread( ); } } + {} + + virtual ~COpenVRBackend( ) + { m_bRunning = false; m_bInitted = true; - m_bInitted.notify_all(); + m_bInitted.notify_all( ); - m_FlipHandlerThread.join(); - } + m_FlipHandlerThread.join( ); + } - void FlipHandlerThread() + void FlipHandlerThread( ) { - pthread_setname_np( pthread_self(), "gamescope-vrflip" ); + pthread_setname_np( pthread_self( ), "gamescope-vrflip" ); m_bInitted.wait( false ); while ( m_bRunning ) { - if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None ) + if ( vr::VROverlay( )->WaitFrameSync( ~0u ) != + vr::VROverlayError_None ) openvr_log.errorf( "WaitFrameSync failed!" ); - static constexpr uint64_t k_ulSchedulingFudge = 100'000; // 0.1ms - uint64_t ulNow = get_time_in_nanos() - k_ulSchedulingFudge; + static constexpr uint64_t k_ulSchedulingFudge = + 100'000; // 0.1ms + uint64_t ulNow = get_time_in_nanos( ) - k_ulSchedulingFudge; - GetVBlankTimer().MarkVBlank( ulNow, true ); + GetVBlankTimer( ).MarkVBlank( ulNow, true ); // Flush out any pending commits -> visible // and any visible commits -> release. @@ -480,9 +540,9 @@ namespace gamescope for ( COpenVRConnector *pConnector : m_pActiveConnectors ) { - for ( COpenVRPlane &plane : pConnector->GetPlanes() ) + for ( COpenVRPlane &plane : pConnector->GetPlanes( ) ) { - plane.OnPageFlip(); + plane.OnPageFlip( ); } } } @@ -490,108 +550,179 @@ namespace gamescope { std::scoped_lock lock{ m_mutForwarderPlanes }; - for ( std::shared_ptr &pPlane : m_pForwarderPlanesInFlight ) + for ( std::shared_ptr &pPlane : + m_pForwarderPlanesInFlight ) { - pPlane->OnPageFlip(); + pPlane->OnPageFlip( ); } - m_pForwarderPlanesInFlight.clear(); + m_pForwarderPlanesInFlight.clear( ); } // Nudge so that steamcompmgr releases commits. - nudge_steamcompmgr(); + nudge_steamcompmgr( ); - ProcessVRInput(); + ProcessVRInput( ); } } - bool IsInitted() const { return m_bInitted; } + bool IsInitted( ) const { return m_bInitted; } - ///////////// - // IBackend - ///////////// + ///////////// + // IBackend + ///////////// - virtual bool Init() override - { + virtual bool Init( ) override + { // Setup nested stuff. - g_nOutputWidth = g_nPreferredOutputWidth; - g_nOutputHeight = g_nPreferredOutputHeight; - - if ( g_nOutputHeight == 0 ) - { - if ( g_nOutputWidth != 0 ) - { - fprintf( stderr, "Cannot specify -W without -H\n" ); - return false; - } - g_nOutputHeight = 720; - } - if ( g_nOutputWidth == 0 ) - g_nOutputWidth = g_nOutputHeight * 16 / 9; + g_nOutputWidth = g_nPreferredOutputWidth; + g_nOutputHeight = g_nPreferredOutputHeight; + + if ( g_nOutputHeight == 0 ) + { + if ( g_nOutputWidth != 0 ) + { + fprintf( stderr, "Cannot specify -W without -H\n" ); + return false; + } + g_nOutputHeight = 720; + } + if ( g_nOutputWidth == 0 ) + g_nOutputWidth = g_nOutputHeight * 16 / 9; // Reset getopt() state optind = 1; int o; int opt_index = -1; - while ((o = getopt_long(g_argc, g_argv, gamescope_optstring, gamescope_options, &opt_index)) != -1) + while ( ( o = getopt_long( + g_argc, + g_argv, + gamescope_optstring, + gamescope_options, + &opt_index ) ) != -1 ) { const char *opt_name; - switch (o) { + switch ( o ) + { case 0: // long options without a short option - opt_name = gamescope_options[opt_index].name; - if (strcmp(opt_name, "vr-overlay-key") == 0) { - m_szOverlayKey = optarg; + opt_name = gamescope_options[ opt_index ].name; + if ( strcmp( opt_name, "vr-overlay-key" ) == 0 ) + { + m_szOverlayKey = optarg; g_pVROverlayKey = &m_szOverlayKey; - } else if (strcmp(opt_name, "vr-app-overlay-key") == 0) { + } + else if ( + strcmp( opt_name, "vr-app-overlay-key" ) == 0 ) + { m_szAppOverlayKey = optarg; - } else if (strcmp(opt_name, "vr-overlay-explicit-name") == 0) { - m_pchOverlayName = optarg; + } + else if ( + strcmp( opt_name, "vr-overlay-explicit-name" ) == + 0 ) + { + m_pchOverlayName = optarg; m_bExplicitOverlayName = true; - } else if (strcmp(opt_name, "vr-overlay-default-name") == 0) { + } + else if ( + strcmp( opt_name, "vr-overlay-default-name" ) == 0 ) + { m_pchOverlayName = optarg; - } else if (strcmp(opt_name, "vr-overlay-icon") == 0) { + } + else if ( strcmp( opt_name, "vr-overlay-icon" ) == 0 ) + { m_pchOverlayIcon = optarg; - } else if (strcmp(opt_name, "vr-overlay-show-immediately") == 0) { + } + else if ( + strcmp( opt_name, "vr-overlay-show-immediately" ) == + 0 ) + { m_bNudgeToVisible = true; - } else if (strcmp(opt_name, "vr-overlay-enable-control-bar") == 0) { + } + else if ( + strcmp( + opt_name, "vr-overlay-enable-control-bar" ) == + 0 ) + { m_bEnableControlBar = true; - } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-keyboard") == 0) { + } + else if ( + strcmp( + opt_name, + "vr-overlay-enable-control-bar-keyboard" ) == + 0 ) + { m_bEnableControlBarKeyboard = true; - } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-close") == 0) { + } + else if ( + strcmp( + opt_name, + "vr-overlay-enable-control-bar-close" ) == 0 ) + { m_bEnableControlBarClose = true; - } else if (strcmp(opt_name, "vr-overlay-enable-click-stabilization") == 0) { + } + else if ( + strcmp( + opt_name, + "vr-overlay-enable-click-stabilization" ) == 0 ) + { m_bEnableClickStabilization = true; - } else if (strcmp(opt_name, "vr-overlay-modal") == 0) { + } + else if ( strcmp( opt_name, "vr-overlay-modal" ) == 0 ) + { m_bModal = true; - } else if (strcmp(opt_name, "vr-overlay-physical-width") == 0) { + } + else if ( + strcmp( opt_name, "vr-overlay-physical-width" ) == + 0 ) + { m_flPhysicalWidth = atof( optarg ); if ( m_flPhysicalWidth <= 0.0f ) m_flPhysicalWidth = 2.0f; - } else if (strcmp(opt_name, "vr-overlay-physical-curvature") == 0) { + } + else if ( + strcmp( + opt_name, "vr-overlay-physical-curvature" ) == + 0 ) + { m_flPhysicalCurvature = atof( optarg ); - } else if (strcmp(opt_name, "vr-overlay-physical-pre-curve-pitch") == 0) { + } + else if ( + strcmp( + opt_name, + "vr-overlay-physical-pre-curve-pitch" ) == 0 ) + { m_flPhysicalPreCurvePitch = atof( optarg ); - } else if (strcmp(opt_name, "vr-scroll-speed") == 0) { + } + else if ( strcmp( opt_name, "vr-scroll-speed" ) == 0 ) + { m_flScrollSpeed = atof( optarg ); - } else if (strcmp(opt_name, "vr-session-manager") == 0) { - openvr_log.infof( "Becoming the VR session manager." ); + } + else if ( + strcmp( opt_name, "vr-session-manager" ) == 0 ) + { + openvr_log.infof( + "Becoming the VR session manager." ); - std::unique_ptr pLibInput = std::make_unique(); - if ( pLibInput->Init() ) + std::unique_ptr pLibInput = + std::make_unique( ); + if ( pLibInput->Init( ) ) { m_pLibInput = std::move( pLibInput ); - m_LibInputWaiter.AddWaitable( m_pLibInput.get() ); + m_LibInputWaiter.AddWaitable( + m_pLibInput.get( ) ); } else { - openvr_log.errorf( "Could not start libinput for being the vr session manager" ); + openvr_log.errorf( + "Could not start libinput for being the vr " + "session manager" ); } } break; case '?': - assert(false); // unreachable + assert( false ); // unreachable } } @@ -600,55 +731,67 @@ namespace gamescope if ( error != vr::VRInitError_None ) { - openvr_log.errorf("Unable to init VR runtime: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription( error )); + openvr_log.errorf( + "Unable to init VR runtime: %s\n", + vr::VR_GetVRInitErrorAsEnglishDescription( error ) ); return false; } - if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) - { - return false; - } + if ( !vulkan_init( vulkan_get_instance( ), VK_NULL_HANDLE ) ) + { + return false; + } - if ( !wlsession_init() ) - { - fprintf( stderr, "Failed to initialize Wayland session\n" ); - return false; - } + if ( !wlsession_init( ) ) + { + fprintf( stderr, "Failed to initialize Wayland session\n" ); + return false; + } - if ( !m_pchOverlayName ) - m_pchOverlayName = "Gamescope"; + if ( !m_pchOverlayName ) m_pchOverlayName = "Gamescope"; - m_pIPCResourceManager = vr::VRIPCResourceManager(); + m_pIPCResourceManager = vr::VRIPCResourceManager( ); if ( !g_bAllowDeferredBackend ) { if ( m_pIPCResourceManager ) { uint32_t uFormatCount = 0; - m_pIPCResourceManager->GetDmabufFormats( &uFormatCount, nullptr ); + m_pIPCResourceManager->GetDmabufFormats( + &uFormatCount, nullptr ); if ( uFormatCount ) { std::vector uFormats; uFormats.resize( uFormatCount ); - m_pIPCResourceManager->GetDmabufFormats( &uFormatCount, uFormats.data() ); + m_pIPCResourceManager->GetDmabufFormats( + &uFormatCount, uFormats.data( ) ); for ( uint32_t i = 0; i < uFormatCount; i++ ) { - uint32_t uFormat = uFormats[i]; + uint32_t uFormat = uFormats[ i ]; uint32_t uModifierCount = 0; - m_pIPCResourceManager->GetDmabufModifiers( vr::VRApplication_Overlay, uFormat, &uModifierCount, nullptr ); + m_pIPCResourceManager->GetDmabufModifiers( + vr::VRApplication_Overlay, + uFormat, + &uModifierCount, + nullptr ); if ( uModifierCount ) { std::vector ulModifiers; ulModifiers.resize( uModifierCount ); - m_pIPCResourceManager->GetDmabufModifiers( vr::VRApplication_Overlay, uFormat, &uModifierCount, ulModifiers.data() ); + m_pIPCResourceManager->GetDmabufModifiers( + vr::VRApplication_Overlay, + uFormat, + &uModifierCount, + ulModifiers.data( ) ); for ( uint64_t ulModifier : ulModifiers ) { if ( ulModifier != DRM_FORMAT_MOD_INVALID ) - m_FormatModifiers[uFormat].emplace_back( ulModifier ); + m_FormatModifiers[ uFormat ] + .emplace_back( ulModifier ); } } } @@ -656,42 +799,50 @@ namespace gamescope } } - if ( UsesModifiers() ) - { - openvr_log.infof( "Using modifiers!" ); - } + if ( UsesModifiers( ) ) { openvr_log.infof( "Using modifiers!" ); } - if ( !vr::VROverlay() ) + if ( !vr::VROverlay( ) ) { openvr_log.errorf( "SteamVR runtime version mismatch!\n" ); return false; } // Setup misc. stuff - g_nOutputRefresh = (int32_t) ConvertHztomHz( roundf( vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ) ) ); + g_nOutputRefresh = ( int32_t )ConvertHztomHz( roundf( + vr::VRSystem( )->GetFloatTrackedDeviceProperty( + vr::k_unTrackedDeviceIndex_Hmd, + vr::Prop_DisplayFrequency_Float ) ) ); m_bRunning = true; m_bInitted = true; - m_bInitted.notify_all(); + m_bInitted.notify_all( ); return true; - } + } - virtual bool PostInit() override - { - if ( m_szOverlayKey.empty() ) - m_szOverlayKey = std::string( "gamescope." ) + wlserver_get_wl_display_name(); + virtual bool PostInit( ) override + { + if ( m_szOverlayKey.empty( ) ) + m_szOverlayKey = std::string( "gamescope." ) + + wlserver_get_wl_display_name( ); - m_pIME = create_local_ime(); - if ( !m_pIME ) - return false; + m_pIME = create_local_ime( ); + if ( !m_pIME ) return false; // This breaks cursor intersection right now. // Come back to me later. - //Ratio aspectRatio{ g_nOutputWidth, g_nOutputHeight }; - //m_pBlackTexture = vulkan_create_flat_texture( aspectRatio.Num(), aspectRatio.Denom(), 0, 0, 0, cv_vr_transparent_backing ? 0 : 255 ); - m_pBlackTexture = vulkan_create_flat_texture( g_nOutputWidth, g_nOutputHeight, 0, 0, 0, cv_vr_transparent_backing ? 0 : 255 ); + // Ratio aspectRatio{ g_nOutputWidth, g_nOutputHeight }; + // m_pBlackTexture = vulkan_create_flat_texture( aspectRatio.Num(), + // aspectRatio.Denom(), 0, 0, 0, cv_vr_transparent_backing ? 0 : 255 + // ); + m_pBlackTexture = vulkan_create_flat_texture( + g_nOutputWidth, + g_nOutputHeight, + 0, + 0, + 0, + cv_vr_transparent_backing ? 0 : 255 ); if ( !m_pBlackTexture ) { openvr_log.errorf( "Failed to create dummy black texture." ); @@ -699,158 +850,133 @@ namespace gamescope } return true; - } + } - virtual std::span GetInstanceExtensions() const override - { + virtual std::span + GetInstanceExtensions( ) const override + { static std::vector s_exts; GetVulkanInstanceExtensionsRequired( s_exts ); static std::vector s_extPtrs; for ( const std::string &ext : s_exts ) - s_extPtrs.emplace_back( ext.c_str() ); - return std::span{ s_extPtrs.begin(), s_extPtrs.end() }; - } - virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override - { + s_extPtrs.emplace_back( ext.c_str( ) ); + return std::span{ s_extPtrs.begin( ), + s_extPtrs.end( ) }; + } + virtual std::span + GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override + { static std::vector s_exts; GetVulkanDeviceExtensionsRequired( pVkPhysicalDevice, s_exts ); static std::vector s_extPtrs; for ( const std::string &ext : s_exts ) - s_extPtrs.emplace_back( ext.c_str() ); - return std::span{ s_extPtrs.begin(), s_extPtrs.end() }; - } - virtual VkImageLayout GetPresentLayout() const override - { - return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - } - virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override + s_extPtrs.emplace_back( ext.c_str( ) ); + return std::span{ s_extPtrs.begin( ), + s_extPtrs.end( ) }; + } + virtual VkImageLayout GetPresentLayout( ) const override + { return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; } + virtual void GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, + uint32_t *pOverlayPlaneFormat ) const override { - *pPrimaryPlaneFormat = VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); - *pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); + *pPrimaryPlaneFormat = + VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); + *pOverlayPlaneFormat = + VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); } - virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override - { - return true; - } - - virtual void DirtyState( bool bForce, bool bForceModeset ) override - { - } - - virtual bool PollState() override - { - return false; - } - - virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override - { - return std::make_shared( data ); - } - - virtual OwningRc ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override - { - return new COpenVRFb{ this, pDmaBuf }; - } - - virtual bool UsesModifiers() const override - { - if ( !cv_vr_use_modifiers ) - return false; + virtual bool + ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override + { return true; } - if ( g_bAllowDeferredBackend ) - return true; + virtual void DirtyState( bool bForce, bool bForceModeset ) override {} - if ( !m_pIPCResourceManager ) - return false; + virtual bool PollState( ) override { return false; } - return !m_FormatModifiers.empty(); - } - virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override - { - if ( !UsesModifiers() ) - return std::span{}; + virtual std::shared_ptr CreateBackendBlob( + const std::type_info &type, std::span data ) override + { return std::make_shared( data ); } + + virtual OwningRc + ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override + { return new COpenVRFb{ this, pDmaBuf }; } + + virtual bool UsesModifiers( ) const override + { + if ( !cv_vr_use_modifiers ) return false; + + if ( g_bAllowDeferredBackend ) return true; + + if ( !m_pIPCResourceManager ) return false; + + return !m_FormatModifiers.empty( ); + } + virtual std::span + GetSupportedModifiers( uint32_t uDrmFormat ) const override + { + if ( !UsesModifiers( ) ) return std::span{}; auto iter = m_FormatModifiers.find( uDrmFormat ); - if ( iter == m_FormatModifiers.end() ) + if ( iter == m_FormatModifiers.end( ) ) return std::span{}; - return std::span{ iter->second.begin(), iter->second.end() }; - } + return std::span{ iter->second.begin( ), + iter->second.end( ) }; + } - virtual IBackendConnector *GetCurrentConnector() override - { - return m_pKeyboardFocusConnector; - } - virtual IBackendConnector *GetCurrentMouseConnector() override + virtual IBackendConnector *GetCurrentConnector( ) override + { return m_pKeyboardFocusConnector; } + virtual IBackendConnector *GetCurrentMouseConnector( ) override + { return m_pMouseFocusConnector; } + virtual IBackendConnector * + GetConnector( GamescopeScreenType eScreenType ) override { - return m_pMouseFocusConnector; + if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) + return GetCurrentConnector( ); + + return nullptr; } - virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override - { - if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - return GetCurrentConnector(); - - return nullptr; - } - - virtual bool SupportsPlaneHardwareCursor() const override - { - return false; - } - - virtual bool SupportsTearing() const override - { - return false; - } - - virtual bool UsesVulkanSwapchain() const override - { - return false; - } - - virtual bool IsSessionBased() const override - { - return false; - } - - virtual bool SupportsExplicitSync() const override + + virtual bool SupportsPlaneHardwareCursor( ) const override + { return false; } + + virtual bool SupportsTearing( ) const override { return false; } + + virtual bool UsesVulkanSwapchain( ) const override { return false; } + + virtual bool IsSessionBased( ) const override { return false; } + + virtual bool SupportsExplicitSync( ) const override { // We only forward done DMA-BUFs, so this should be fine. // SteamVR does not do any wait/poll/sync on these. return true; } - virtual bool IsPaused() const override - { - return false; - } + virtual bool IsPaused( ) const override { return false; } - virtual bool IsVisible() const override - { - if ( ShouldNudgeToVisible() ) - return true; + virtual bool IsVisible( ) const override + { + if ( ShouldNudgeToVisible( ) ) return true; - return m_nOverlaysVisible.load() != 0; - } + return m_nOverlaysVisible.load( ) != 0; + } - virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override - { - return uvecSize; - } + virtual glm::uvec2 + CursorSurfaceSize( glm::uvec2 uvecSize ) const override + { return uvecSize; } - virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override - { - return false; - } + virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override + { return false; } - virtual void HackUpdatePatchedEdid() override - { - if ( !GetCurrentConnector() ) - return; + virtual void HackUpdatePatchedEdid( ) override + { + if ( !GetCurrentConnector( ) ) return; - // XXX: We should do this a better way that handles per-window and appid stuff - // down the line - if ( cv_hdr_enabled && GetCurrentConnector()->GetHDRInfo().bExposeHDRSupport ) + // XXX: We should do this a better way that handles per-window and + // appid stuff down the line + if ( cv_hdr_enabled && + GetCurrentConnector( )->GetHDRInfo( ).bExposeHDRSupport ) { setenv( "DXVK_HDR", "1", true ); } @@ -859,73 +985,78 @@ namespace gamescope setenv( "DXVK_HDR", "0", true ); } - WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), false ); - } - - virtual bool NeedsFrameSync() const override - { - return false; + WritePatchedEdid( + GetCurrentConnector( )->GetRawEDID( ), + GetCurrentConnector( )->GetHDRInfo( ), + false ); } - virtual TouchClickMode GetTouchClickMode() override + virtual bool NeedsFrameSync( ) const override { return false; } + + virtual TouchClickMode GetTouchClickMode( ) override { - COpenVRConnector *pConnector = static_cast( GetCurrentConnector() ); - if ( cv_vr_trackpad_relative_mouse_mode && pConnector && pConnector->IsRelativeMouse() ) + COpenVRConnector *pConnector = + static_cast( GetCurrentConnector( ) ); + if ( cv_vr_trackpad_relative_mouse_mode && pConnector && + pConnector->IsRelativeMouse( ) ) { return TouchClickModes::Trackpad; } if ( pConnector ) { - if ( pConnector->IsTouchForbidden() ) + if ( pConnector->IsTouchForbidden( ) ) { return TouchClickModes::Left; } - if ( VirtualConnectorKeyIsNonSteamWindow( pConnector->GetVirtualConnectorKey() ) ) + if ( VirtualConnectorKeyIsNonSteamWindow( + pConnector->GetVirtualConnectorKey( ) ) ) { return TouchClickModes::Passthrough; } - if ( VirtualConnectorInSteamPerAppState() ) + if ( VirtualConnectorInSteamPerAppState( ) ) { - if ( !VirtualConnectorKeyIsSteam( pConnector->GetVirtualConnectorKey() ) ) + if ( !VirtualConnectorKeyIsSteam( + pConnector->GetVirtualConnectorKey( ) ) ) return TouchClickModes::Left; } } - return CBaseBackend::GetTouchClickMode(); + return CBaseBackend::GetTouchClickMode( ); } - bool UsesVirtualConnectors() override + bool UsesVirtualConnectors( ) override { return true; } + std::shared_ptr + CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override { - return true; - } - std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override - { - std::shared_ptr pConnector = std::make_shared( this, ulVirtualConnectorKey ); + std::shared_ptr pConnector = + std::make_shared( + this, ulVirtualConnectorKey ); - if ( !pConnector->Init() ) - { - return nullptr; - } + if ( !pConnector->Init( ) ) { return nullptr; } { COpenVRConnector *pExpected = nullptr; - m_pMouseFocusConnector.compare_exchange_strong( pExpected, pConnector.get() ); + m_pMouseFocusConnector.compare_exchange_strong( + pExpected, pConnector.get( ) ); } { COpenVRConnector *pExpected = nullptr; - if ( m_pKeyboardFocusConnector.compare_exchange_strong( pExpected, pConnector.get() ) ) + if ( m_pKeyboardFocusConnector.compare_exchange_strong( + pExpected, pConnector.get( ) ) ) { - openvr_log.debugf( "Changed keyboard focus connector to %p ", pConnector.get() ); + openvr_log.debugf( + "Changed keyboard focus connector to %p ", + pConnector.get( ) ); update_connector_display_info_wl( NULL ); } } std::scoped_lock lock{ m_mutActiveConnectors }; - m_pActiveConnectors.push_back( pConnector.get() ); + m_pActiveConnectors.push_back( pConnector.get( ) ); return pConnector; } @@ -938,109 +1069,111 @@ namespace gamescope std::scoped_lock lock{ m_mutActiveConnectors }; - COpenVRConnector *pConnector = static_cast( GetCurrentConnector() ); - if ( pConnector ) - { - pConnector->m_bUsingVRMouse = false; - } + COpenVRConnector *pConnector = + static_cast( GetCurrentConnector( ) ); + if ( pConnector ) { pConnector->m_bUsingVRMouse = false; } } } - - bool SupportsVROverlayForwarding() override - { - return true; - } - - void ForwardFramebuffer( std::shared_ptr &pPlane, IBackendFb *pFramebuffer, const void *pData ) override + + bool SupportsVROverlayForwarding( ) override { return true; } + + void ForwardFramebuffer( + std::shared_ptr &pPlane, + IBackendFb *pFramebuffer, + const void *pData ) override { - COpenVRFb *pVRFB = static_cast( pFramebuffer->EnsureImported() ); + COpenVRFb *pVRFB = + static_cast( pFramebuffer->EnsureImported( ) ); if ( !pVRFB ) { openvr_log.debugf( "ForwardFramebuffer: No FB!" ); return; } - vr::VROverlayHandle_t hOverlay = static_cast( *reinterpret_cast( pData ) ); - openvr_log.debugf( "Forwarding for overlay: %lx", (unsigned long)hOverlay ); + vr::VROverlayHandle_t hOverlay = static_cast( + *reinterpret_cast( pData ) ); + openvr_log.debugf( + "Forwarding for overlay: %lx", ( unsigned long )hOverlay ); if ( !pPlane ) { - std::shared_ptr pOpenVRPlane = std::make_shared( nullptr ); + std::shared_ptr pOpenVRPlane = + std::make_shared( nullptr ); pOpenVRPlane->InitAsForwarder( hOverlay ); pPlane = pOpenVRPlane; } - std::shared_ptr pOpenVRPlane = std::static_pointer_cast( pPlane ); - assert( pOpenVRPlane->GetOverlay() == hOverlay ); + std::shared_ptr pOpenVRPlane = + std::static_pointer_cast( pPlane ); + assert( pOpenVRPlane->GetOverlay( ) == hOverlay ); pOpenVRPlane->ForwardFramebuffer( pVRFB ); { std::scoped_lock lock{ m_mutForwarderPlanes }; - if ( !Algorithm::Contains( m_pForwarderPlanesInFlight, pOpenVRPlane ) ) + if ( !Algorithm::Contains( + m_pForwarderPlanesInFlight, pOpenVRPlane ) ) { - m_pForwarderPlanesInFlight.emplace_back( std::move( pOpenVRPlane ) ); + m_pForwarderPlanesInFlight.emplace_back( + std::move( pOpenVRPlane ) ); } } } - bool ShouldFitWindows() override - { - return false; - } + bool ShouldFitWindows( ) override { return false; } - vr::IVRIPCResourceManagerClient *GetIPCResourceManager() - { - return m_pIPCResourceManager; - } + vr::IVRIPCResourceManagerClient *GetIPCResourceManager( ) + { return m_pIPCResourceManager; } - bool SupportsColorManagement() const - { - return false; - } + bool SupportsColorManagement( ) const { return false; } - void OnEndFrame() override + void OnEndFrame( ) override { - GetVBlankTimer().UpdateWasCompositing( true ); - GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - - int32_t nNewRefreshRate = (int32_t) ConvertHztomHz( roundf( vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ) ) ); + GetVBlankTimer( ).UpdateWasCompositing( true ); + GetVBlankTimer( ).UpdateLastDrawTime( + get_time_in_nanos( ) - g_SteamCompMgrVBlankTime.ulWakeupTime ); + + int32_t nNewRefreshRate = ( int32_t )ConvertHztomHz( roundf( + vr::VRSystem( )->GetFloatTrackedDeviceProperty( + vr::k_unTrackedDeviceIndex_Hmd, + vr::Prop_DisplayFrequency_Float ) ) ); if ( g_nOutputRefresh != nNewRefreshRate ) g_nOutputRefresh = nNewRefreshRate; - PollState(); + PollState( ); } - const char *GetOverlayKey() const { return m_szOverlayKey.c_str(); } - const char *GetAppOverlayKey() const { return m_szAppOverlayKey.c_str(); } - const char *GetOverlayName() const { return m_pchOverlayName; } - const char *GetOverlayIcon() const { return m_pchOverlayIcon; } - bool ShouldEnableControlBar() const { return m_bEnableControlBar; } - bool ShouldEnableControlBarKeyboard() const { return m_bEnableControlBarKeyboard; } - bool ShouldEnableControlBarClose() const { return m_bEnableControlBarClose; } - bool ShouldEnableClickStabilization() const { return m_bEnableClickStabilization; } - bool IsModal() const { return m_bModal; } - float GetPhysicalWidth() const { return m_flPhysicalWidth; } - float GetPhysicalCurvature() const { return m_flPhysicalCurvature; } - float GetPhysicalPreCurvePitch() const { return m_flPhysicalPreCurvePitch; } - float GetScrollSpeed() const { return m_flScrollSpeed; } - - bool ConsumeNudgeToVisible() { return std::exchange( m_bNudgeToVisible, false ); } - bool ShouldNudgeToVisible() const { return m_bNudgeToVisible; } - - CVulkanTexture *GetBlackTexture() { return m_pBlackTexture.get(); } - - protected: + const char *GetOverlayKey( ) const { return m_szOverlayKey.c_str( ); } + const char *GetAppOverlayKey( ) const + { return m_szAppOverlayKey.c_str( ); } + const char *GetOverlayName( ) const { return m_pchOverlayName; } + const char *GetOverlayIcon( ) const { return m_pchOverlayIcon; } + bool ShouldEnableControlBar( ) const { return m_bEnableControlBar; } + bool ShouldEnableControlBarKeyboard( ) const + { return m_bEnableControlBarKeyboard; } + bool ShouldEnableControlBarClose( ) const + { return m_bEnableControlBarClose; } + bool ShouldEnableClickStabilization( ) const + { return m_bEnableClickStabilization; } + bool IsModal( ) const { return m_bModal; } + float GetPhysicalWidth( ) const { return m_flPhysicalWidth; } + float GetPhysicalCurvature( ) const { return m_flPhysicalCurvature; } + float GetPhysicalPreCurvePitch( ) const + { return m_flPhysicalPreCurvePitch; } + float GetScrollSpeed( ) const { return m_flScrollSpeed; } + + bool ConsumeNudgeToVisible( ) + { return std::exchange( m_bNudgeToVisible, false ); } + bool ShouldNudgeToVisible( ) const { return m_bNudgeToVisible; } + + CVulkanTexture *GetBlackTexture( ) { return m_pBlackTexture.get( ); } + + protected: + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override {} - virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override - { - } - - private: - - void WaitUntilVisible() + private: + void WaitUntilVisible( ) { - if ( ShouldNudgeToVisible() ) - return; + if ( ShouldNudgeToVisible( ) ) return; m_nOverlaysVisible.wait( 0 ); } @@ -1051,28 +1184,31 @@ namespace gamescope for ( COpenVRConnector *pConnector : m_pActiveConnectors ) { pPlane = pConnector->GetPlaneByOverlayHandle( hOverlay ); - if ( pPlane ) - break; + if ( pPlane ) break; } return pPlane; } void SetMouseFocus( uint64_t ulFocusOverlay ) { - uint64_t oldOverlayHandle = g_FocusedVROverlayMouse.exchange( ulFocusOverlay ); + uint64_t oldOverlayHandle = + g_FocusedVROverlayMouse.exchange( ulFocusOverlay ); - if ( oldOverlayHandle == ulFocusOverlay ) - return; + if ( oldOverlayHandle == ulFocusOverlay ) return; - openvr_log.debugf( "Changing mouse focus from %lx to %lx", oldOverlayHandle, ulFocusOverlay ); + openvr_log.debugf( + "Changing mouse focus from %lx to %lx", + oldOverlayHandle, + ulFocusOverlay ); COpenVRConnector *pInputConnector = nullptr; if ( ulFocusOverlay != vr::k_ulOverlayHandleInvalid ) { - COpenVRPlane *pInputPlane = GetPlaneByOverlayHandle( ulFocusOverlay ); + COpenVRPlane *pInputPlane = + GetPlaneByOverlayHandle( ulFocusOverlay ); if ( pInputPlane ) { - pInputConnector = pInputPlane->GetConnector(); + pInputConnector = pInputPlane->GetConnector( ); } } @@ -1080,232 +1216,304 @@ namespace gamescope { pInputConnector->m_bUsingVRMouse = true; - COpenVRConnector *pOldConnector = m_pMouseFocusConnector.exchange( pInputConnector ); + COpenVRConnector *pOldConnector = + m_pMouseFocusConnector.exchange( pInputConnector ); if ( pOldConnector != pInputConnector ) { - openvr_log.debugf( "Changing mouse focus connector to %p", pInputConnector ); + openvr_log.debugf( + "Changing mouse focus connector to %p", + pInputConnector ); // We don't do anything with mouse focus that isn't local, // so only dirty focus if the focus connector changed. // - // Unlike with Keyboard where we need to focus for forwarders too! + // Unlike with Keyboard where we need to focus for + // forwarders too! - MakeFocusDirty(); - nudge_steamcompmgr(); + MakeFocusDirty( ); + nudge_steamcompmgr( ); } } } void SetKeyboardFocus( uint64_t ulFocusOverlay ) { - uint64_t oldOverlayHandle = g_FocusedVROverlayKeyboard.exchange( ulFocusOverlay ); + uint64_t oldOverlayHandle = + g_FocusedVROverlayKeyboard.exchange( ulFocusOverlay ); - if ( oldOverlayHandle == ulFocusOverlay ) - return; + if ( oldOverlayHandle == ulFocusOverlay ) return; - openvr_log.debugf( "Changing keyboard focus from %lx to %lx", oldOverlayHandle, ulFocusOverlay ); + openvr_log.debugf( + "Changing keyboard focus from %lx to %lx", + oldOverlayHandle, + ulFocusOverlay ); COpenVRConnector *pInputConnector = nullptr; if ( ulFocusOverlay != vr::k_ulOverlayHandleInvalid ) { - COpenVRPlane *pInputPlane = GetPlaneByOverlayHandle( ulFocusOverlay ); + COpenVRPlane *pInputPlane = + GetPlaneByOverlayHandle( ulFocusOverlay ); if ( pInputPlane ) { - pInputConnector = pInputPlane->GetConnector(); + pInputConnector = pInputPlane->GetConnector( ); } } if ( pInputConnector ) { - openvr_log.debugf( "Changing keyboard focus connector to %p", pInputConnector ); + openvr_log.debugf( + "Changing keyboard focus connector to %p", + pInputConnector ); m_pKeyboardFocusConnector.exchange( pInputConnector ); } - MakeFocusDirty(); - nudge_steamcompmgr(); + MakeFocusDirty( ); + nudge_steamcompmgr( ); } - void ProcessVRInput() + void ProcessVRInput( ) { - std::scoped_lock lock{m_mutActiveConnectors}; + std::scoped_lock lock{ m_mutActiveConnectors }; - vr::VREvent_t vrEvent; - bool bDidScrollThisFrame = false; - bool bPendingTouchMove = false; - float flTouchMoveX, flTouchMoveY; + vr::VREvent_t vrEvent; + bool bDidScrollThisFrame = false; + bool bPendingTouchMove = false; + float flTouchMoveX, flTouchMoveY; vr::VROverlayHandle_t hOverlay; - while (vr::VRSystem()->PollNextEventWithPoseAndOverlays(vr::TrackingUniverseSeated, &vrEvent, sizeof(vrEvent), nullptr, &hOverlay)) + while ( vr::VRSystem( )->PollNextEventWithPoseAndOverlays( + vr::TrackingUniverseSeated, + &vrEvent, + sizeof( vrEvent ), + nullptr, + &hOverlay ) ) { - COpenVRPlane *pPlane = nullptr; + COpenVRPlane *pPlane = nullptr; COpenVRConnector *pConnector = nullptr; - bool bIsSteam = false; - if (hOverlay != vr::k_ulOverlayHandleInvalid) + bool bIsSteam = false; + if ( hOverlay != vr::k_ulOverlayHandleInvalid ) { - pPlane = GetPlaneByOverlayHandle(hOverlay); - if (pPlane) + pPlane = GetPlaneByOverlayHandle( hOverlay ); + if ( pPlane ) { - pConnector = pPlane->GetConnector(); - bIsSteam = VirtualConnectorKeyIsSteam(pConnector->GetVirtualConnectorKey()); + pConnector = pPlane->GetConnector( ); + bIsSteam = VirtualConnectorKeyIsSteam( + pConnector->GetVirtualConnectorKey( ) ); } } - switch (vrEvent.eventType) - { - case vr::VREvent_Quit: - { - raise(SIGTERM); - } - break; - - case vr::VREvent_OverlayClosed: + switch ( vrEvent.eventType ) { - if (!steamMode || bIsSteam) - { - if (!pPlane || !pPlane->IsSubview()) - { - raise(SIGTERM); - } - } - else + case vr::VREvent_Quit: { - close_virtual_connector_key( pConnector->GetVirtualConnectorKey() ); + raise( SIGTERM ); } break; - } - case vr::VREvent_SceneApplicationChanged: - { - if (m_uCurrentScenePid != vrEvent.data.process.pid) + case vr::VREvent_OverlayClosed: { - m_uCurrentScenePid = vrEvent.data.process.pid; - m_uCurrentSceneAppId = get_appid_from_pid(m_uCurrentScenePid); - g_unCurrentVRSceneAppId = m_uCurrentSceneAppId; - - openvr_log.debugf("SceneApplicationChanged -> pid: %u appid: %u", m_uCurrentScenePid, m_uCurrentSceneAppId); - - std::optional oulNewSceneAppVirtualConnectorKey; - if (cv_backend_virtual_connector_strategy == VirtualConnectorStrategies::PerAppId) + if ( !steamMode || bIsSteam ) + { + if ( !pPlane || !pPlane->IsSubview( ) ) + { + raise( SIGTERM ); + } + } + else { - oulNewSceneAppVirtualConnectorKey = m_uCurrentSceneAppId; + close_virtual_connector_key( + pConnector->GetVirtualConnectorKey( ) ); } + break; + } - if ((oulNewSceneAppVirtualConnectorKey || m_oulCurrentSceneVirtualConnectorKey) && - (oulNewSceneAppVirtualConnectorKey != m_oulCurrentSceneVirtualConnectorKey)) + case vr::VREvent_SceneApplicationChanged: + { + if ( m_uCurrentScenePid != vrEvent.data.process.pid ) { - for (COpenVRConnector *pOtherConnector : m_pActiveConnectors) + m_uCurrentScenePid = vrEvent.data.process.pid; + m_uCurrentSceneAppId = + get_appid_from_pid( m_uCurrentScenePid ); + g_unCurrentVRSceneAppId = m_uCurrentSceneAppId; + + openvr_log.debugf( + "SceneApplicationChanged -> pid: %u appid: %u", + m_uCurrentScenePid, + m_uCurrentSceneAppId ); + + std::optional + oulNewSceneAppVirtualConnectorKey; + if ( cv_backend_virtual_connector_strategy == + VirtualConnectorStrategies::PerAppId ) { - if (oulNewSceneAppVirtualConnectorKey) - { - if (pOtherConnector->GetVirtualConnectorKey() == *oulNewSceneAppVirtualConnectorKey) - pOtherConnector->MarkSceneAppShown(true); - } + oulNewSceneAppVirtualConnectorKey = + m_uCurrentSceneAppId; + } - if (m_oulCurrentSceneVirtualConnectorKey) + if ( ( oulNewSceneAppVirtualConnectorKey || + m_oulCurrentSceneVirtualConnectorKey ) && + ( oulNewSceneAppVirtualConnectorKey != + m_oulCurrentSceneVirtualConnectorKey ) ) + { + for ( COpenVRConnector *pOtherConnector : + m_pActiveConnectors ) { - if (pOtherConnector->GetVirtualConnectorKey() == *m_oulCurrentSceneVirtualConnectorKey) - pOtherConnector->MarkSceneAppShown(false); + if ( oulNewSceneAppVirtualConnectorKey ) + { + if ( + pOtherConnector + ->GetVirtualConnectorKey( ) == + *oulNewSceneAppVirtualConnectorKey ) + pOtherConnector->MarkSceneAppShown( + true ); + } + + if ( m_oulCurrentSceneVirtualConnectorKey ) + { + if ( + pOtherConnector + ->GetVirtualConnectorKey( ) == + *m_oulCurrentSceneVirtualConnectorKey ) + pOtherConnector->MarkSceneAppShown( + false ); + } } } + + m_oulCurrentSceneVirtualConnectorKey = + oulNewSceneAppVirtualConnectorKey; } - m_oulCurrentSceneVirtualConnectorKey = oulNewSceneAppVirtualConnectorKey; + break; } - break; - } - - case vr::VREvent_KeyboardCharInput: - { - if (m_pIME) + case vr::VREvent_KeyboardCharInput: { - const char *pchText = vrEvent.data.keyboard.cNewInput; - size_t unStrLen = strlen( pchText ); - bool bEscaped = false; - if ( unStrLen > 2 && pchText[0] == '\x1b' && pchText[1] == '[' ) + if ( m_pIME ) { - bEscaped = true; - } - - if ( bEscaped ) - { - const char chControlChar = pchText[2]; - gamescope_input_method_action eAction = GAMESCOPE_INPUT_METHOD_ACTION_NONE; - switch (chControlChar) + const char *pchText = + vrEvent.data.keyboard.cNewInput; + size_t unStrLen = strlen( pchText ); + bool bEscaped = false; + if ( unStrLen > 2 && pchText[ 0 ] == '\x1b' && + pchText[ 1 ] == '[' ) { - case 'D': eAction = GAMESCOPE_INPUT_METHOD_ACTION_MOVE_LEFT; break; - case 'C': eAction = GAMESCOPE_INPUT_METHOD_ACTION_MOVE_RIGHT; break; - case 'A': eAction = GAMESCOPE_INPUT_METHOD_ACTION_MOVE_UP; break; - case 'B': eAction = GAMESCOPE_INPUT_METHOD_ACTION_MOVE_DOWN; break; + bEscaped = true; } - if ( eAction != GAMESCOPE_INPUT_METHOD_ACTION_NONE ) + if ( bEscaped ) { - perform_action( m_pIME, eAction ); - } - } - else - { - if ( unStrLen == 1 && pchText[0] == '\n' ) - { - perform_action( m_pIME, GAMESCOPE_INPUT_METHOD_ACTION_SUBMIT ); - } - else if ( unStrLen == 1 && pchText[0] == '\b' ) - { - perform_action( m_pIME, GAMESCOPE_INPUT_METHOD_ACTION_DELETE_LEFT ); + const char chControlChar = pchText[ 2 ]; + gamescope_input_method_action eAction = + GAMESCOPE_INPUT_METHOD_ACTION_NONE; + switch ( chControlChar ) + { + case 'D': + eAction = + GAMESCOPE_INPUT_METHOD_ACTION_MOVE_LEFT; + break; + case 'C': + eAction = + GAMESCOPE_INPUT_METHOD_ACTION_MOVE_RIGHT; + break; + case 'A': + eAction = + GAMESCOPE_INPUT_METHOD_ACTION_MOVE_UP; + break; + case 'B': + eAction = + GAMESCOPE_INPUT_METHOD_ACTION_MOVE_DOWN; + break; + } + + if ( eAction != + GAMESCOPE_INPUT_METHOD_ACTION_NONE ) + { + perform_action( m_pIME, eAction ); + } } else { - type_text( m_pIME, pchText ); + if ( unStrLen == 1 && pchText[ 0 ] == '\n' ) + { + perform_action( + m_pIME, + GAMESCOPE_INPUT_METHOD_ACTION_SUBMIT ); + } + else if ( + unStrLen == 1 && pchText[ 0 ] == '\b' ) + { + perform_action( + m_pIME, + GAMESCOPE_INPUT_METHOD_ACTION_DELETE_LEFT ); + } + else + { + type_text( m_pIME, pchText ); + } } } + break; } - break; - } - case vr::VREvent_MouseMove: - { - if (pConnector && pConnector->m_bUsingVRMouse) + case vr::VREvent_MouseMove: { - SetMouseFocus( hOverlay ); - - float flX = vrEvent.data.mouse.x / float(g_nOutputWidth); - float flY = (g_nOutputHeight - vrEvent.data.mouse.y) / float(g_nOutputHeight); + if ( pConnector && pConnector->m_bUsingVRMouse ) + { + SetMouseFocus( hOverlay ); - TouchClickMode eMode = GetTouchClickMode(); + float flX = + vrEvent.data.mouse.x / float( g_nOutputWidth ); + float flY = + ( g_nOutputHeight - vrEvent.data.mouse.y ) / + float( g_nOutputHeight ); - if (eMode == TouchClickModes::Trackpad) - { - glm::vec2 vOldTrackpadPos = m_vScreenTrackpadPos; - m_vScreenTrackpadPos = glm::vec2{flX, flY}; + TouchClickMode eMode = GetTouchClickMode( ); - if (m_bMouseDown) + if ( eMode == TouchClickModes::Trackpad ) { - glm::vec2 vDelta = (m_vScreenTrackpadPos - vOldTrackpadPos); - // We are based off normalized coords, so we need to fix the aspect ratio - // or we get different sensitivities on X and Y. - vDelta.y *= ((float)g_nOutputHeight / (float)g_nOutputWidth); + glm::vec2 vOldTrackpadPos = + m_vScreenTrackpadPos; + m_vScreenTrackpadPos = glm::vec2{ flX, flY }; - vDelta *= float(cv_vr_trackpad_sensitivity); - - wlserver_lock(); - wlserver_mousemotion(vDelta.x, vDelta.y, ++m_uFakeTimestamp); - wlserver_unlock(); + if ( m_bMouseDown ) + { + glm::vec2 vDelta = + ( m_vScreenTrackpadPos - + vOldTrackpadPos ); + // We are based off normalized coords, so we + // need to fix the aspect ratio or we get + // different sensitivities on X and Y. + vDelta.y *= + ( ( float )g_nOutputHeight / + ( float )g_nOutputWidth ); + + vDelta *= + float( cv_vr_trackpad_sensitivity ); + + wlserver_lock( ); + wlserver_mousemotion( + vDelta.x, + vDelta.y, + ++m_uFakeTimestamp ); + wlserver_unlock( ); + } + } + else + { + // Hold the call to wlserver_touchmotion until + // we're sure there are no VREvent_ScrollSmooth + // events this frame + bPendingTouchMove = true; + flTouchMoveX = flX; + flTouchMoveY = flY; } } - else - { - // Hold the call to wlserver_touchmotion until we're sure there are no VREvent_ScrollSmooth events this frame - bPendingTouchMove = true; - flTouchMoveX = flX; - flTouchMoveY = flY; - } + break; } - break; - } - case vr::VREvent_FocusEnter: + case vr::VREvent_FocusEnter: { - if (pConnector) + if ( pConnector ) { pConnector->m_bUsingVRMouse = true; SetMouseFocus( hOverlay ); @@ -1313,160 +1521,219 @@ namespace gamescope } break; - case vr::VREvent_OverlayFocusChanged: + case vr::VREvent_OverlayFocusChanged: { SetMouseFocus( vrEvent.data.overlay.overlayHandle ); } break; - case vr::VREvent_OverlayInputFocusChanged: + case vr::VREvent_OverlayInputFocusChanged: { - const vr::VREvent_Data_Gamescope_t &data = CastToGamescopeEventData( vrEvent.data ); - SetKeyboardFocus( data.overlayInputFocus.overlayHandle ); + const vr::VREvent_Data_Gamescope_t &data = + CastToGamescopeEventData( vrEvent.data ); + SetKeyboardFocus( + data.overlayInputFocus.overlayHandle ); } break; - case vr::VREvent_MouseButtonUp: - case vr::VREvent_MouseButtonDown: - if (pConnector) - { - SetMouseFocus( hOverlay ); - - if (!pConnector->m_bUsingVRMouse) + case vr::VREvent_MouseButtonUp: + case vr::VREvent_MouseButtonDown: + if ( pConnector ) { - pConnector->m_bUsingVRMouse = true; - } - else - { - - float flX = vrEvent.data.mouse.x / float(g_nOutputWidth); - float flY = (g_nOutputHeight - vrEvent.data.mouse.y) / float(g_nOutputHeight); - - uint64_t ulNow = get_time_in_nanos(); + SetMouseFocus( hOverlay ); - if (vrEvent.eventType == vr::VREvent_MouseButtonDown) + if ( !pConnector->m_bUsingVRMouse ) { - m_ulMouseDownTime = ulNow; - m_bMouseDown = true; + pConnector->m_bUsingVRMouse = true; } else { - m_bMouseDown = false; - } - TouchClickMode eMode = GetTouchClickMode(); - if (eMode == TouchClickModes::Trackpad) - { - m_vScreenTrackpadPos = glm::vec2{flX, flY}; + float flX = vrEvent.data.mouse.x / + float( g_nOutputWidth ); + float flY = + ( g_nOutputHeight - vrEvent.data.mouse.y ) / + float( g_nOutputHeight ); - if (vrEvent.eventType == vr::VREvent_MouseButtonUp) - { - glm::vec2 vTotalDelta = (m_vScreenTrackpadPos - m_vScreenStartTrackpadPos); - vTotalDelta.y *= ((float)g_nOutputHeight / (float)g_nOutputWidth); - float flMaxAbsTotalDelta = std::max(std::abs(vTotalDelta.x), std::abs(vTotalDelta.y)); - - uint64_t ulClickTime = ulNow - m_ulMouseDownTime; - if (ulClickTime <= cv_vr_trackpad_click_time && flMaxAbsTotalDelta <= cv_vr_trackpad_click_max_delta) - { - wlserver_lock(); - wlserver_mousebutton(BTN_LEFT, true, ++m_uFakeTimestamp); - wlserver_unlock(); + uint64_t ulNow = get_time_in_nanos( ); - sleep_for_nanos(g_SteamCompMgrLimitedAppRefreshCycle + 1'000'000); - - wlserver_lock(); - wlserver_mousebutton(BTN_LEFT, false, ++m_uFakeTimestamp); - wlserver_unlock(); - } - else - { - m_vScreenStartTrackpadPos = m_vScreenTrackpadPos; - } + if ( vrEvent.eventType == + vr::VREvent_MouseButtonDown ) + { + m_ulMouseDownTime = ulNow; + m_bMouseDown = true; } - } - else - { - bool bDown = vrEvent.eventType == vr::VREvent_MouseButtonDown; - wlserver_lock(); - if (vrEvent.data.mouse.button == vr::VRMouseButton_Left) + else { - if (bDown) - wlserver_touchdown(flX, flY, 0, ++m_uFakeTimestamp); - else - wlserver_touchup(0, ++m_uFakeTimestamp); + m_bMouseDown = false; } - else if (vrEvent.data.mouse.button == vr::VRMouseButton_Right) + + TouchClickMode eMode = GetTouchClickMode( ); + if ( eMode == TouchClickModes::Trackpad ) { - wlserver_mousebutton(BTN_RIGHT, bDown, ++m_uFakeTimestamp); + m_vScreenTrackpadPos = + glm::vec2{ flX, flY }; + + if ( vrEvent.eventType == + vr::VREvent_MouseButtonUp ) + { + glm::vec2 vTotalDelta = + ( m_vScreenTrackpadPos - + m_vScreenStartTrackpadPos ); + vTotalDelta.y *= + ( ( float )g_nOutputHeight / + ( float )g_nOutputWidth ); + float flMaxAbsTotalDelta = + std::max( + std::abs( vTotalDelta.x ), + std::abs( vTotalDelta.y ) ); + + uint64_t ulClickTime = + ulNow - m_ulMouseDownTime; + if ( + ulClickTime <= + cv_vr_trackpad_click_time && + flMaxAbsTotalDelta <= + cv_vr_trackpad_click_max_delta ) + { + wlserver_lock( ); + wlserver_mousebutton( + BTN_LEFT, + true, + ++m_uFakeTimestamp ); + wlserver_unlock( ); + + sleep_for_nanos( + g_SteamCompMgrLimitedAppRefreshCycle + + 1'000'000 ); + + wlserver_lock( ); + wlserver_mousebutton( + BTN_LEFT, + false, + ++m_uFakeTimestamp ); + wlserver_unlock( ); + } + else + { + m_vScreenStartTrackpadPos = + m_vScreenTrackpadPos; + } + } } - else if (vrEvent.data.mouse.button == vr::VRMouseButton_Middle) + else { - wlserver_mousebutton(BTN_MIDDLE, bDown, ++m_uFakeTimestamp); + bool bDown = vrEvent.eventType == + vr::VREvent_MouseButtonDown; + wlserver_lock( ); + if ( vrEvent.data.mouse.button == + vr::VRMouseButton_Left ) + { + if ( bDown ) + wlserver_touchdown( + flX, + flY, + 0, + ++m_uFakeTimestamp ); + else + wlserver_touchup( + 0, ++m_uFakeTimestamp ); + } + else if ( + vrEvent.data.mouse.button == + vr::VRMouseButton_Right ) + { + wlserver_mousebutton( + BTN_RIGHT, + bDown, + ++m_uFakeTimestamp ); + } + else if ( + vrEvent.data.mouse.button == + vr::VRMouseButton_Middle ) + { + wlserver_mousebutton( + BTN_MIDDLE, + bDown, + ++m_uFakeTimestamp ); + } + wlserver_unlock( ); } - wlserver_unlock(); } } - } - break; + break; - case vr::VREvent_ScrollSmooth: - if (pConnector) - { - SetMouseFocus( hOverlay ); - - float flX = -vrEvent.data.scroll.xdelta * m_flScrollSpeed; - float flY = -vrEvent.data.scroll.ydelta * m_flScrollSpeed; - wlserver_lock(); - wlserver_mousewheel(flX, flY, ++m_uFakeTimestamp); - wlserver_unlock(); - bDidScrollThisFrame = true; - } - break; + case vr::VREvent_ScrollSmooth: + if ( pConnector ) + { + SetMouseFocus( hOverlay ); - case vr::VREvent_OverlayShown: - case vr::VREvent_OverlayHidden: - if (pConnector) - { - // Only handle this for the base plane. - // Subviews can be hidden if we hide them ourselves, - // or for other reasons. - if (!pPlane->IsSubview()) + float flX = + -vrEvent.data.scroll.xdelta * m_flScrollSpeed; + float flY = + -vrEvent.data.scroll.ydelta * m_flScrollSpeed; + wlserver_lock( ); + wlserver_mousewheel( flX, flY, ++m_uFakeTimestamp ); + wlserver_unlock( ); + bDidScrollThisFrame = true; + } + break; + + case vr::VREvent_OverlayShown: + case vr::VREvent_OverlayHidden: + if ( pConnector ) { - pConnector->MarkOverlayShown(vrEvent.eventType == vr::VREvent_OverlayShown); + // Only handle this for the base plane. + // Subviews can be hidden if we hide them ourselves, + // or for other reasons. + if ( !pPlane->IsSubview( ) ) + { + pConnector->MarkOverlayShown( + vrEvent.eventType == + vr::VREvent_OverlayShown ); + } } - } - break; + break; - default: - break; + default: + break; } } - if (bPendingTouchMove) + if ( bPendingTouchMove ) { - // Always warp a cursor, even if it's invisible, so we get hover events. - // Unless a scroll happened this frame. Then, skip the cursor move because it causes the scroll to be dropped. - bool bAlwaysMoveCursor = !bDidScrollThisFrame && (GetTouchClickMode() == TouchClickModes::Passthrough) && cv_vr_always_warp_cursor; - - wlserver_lock(); - wlserver_touchmotion(flTouchMoveX, flTouchMoveY, 0, ++m_uFakeTimestamp, bAlwaysMoveCursor); - wlserver_unlock(); + // Always warp a cursor, even if it's invisible, so we get hover + // events. Unless a scroll happened this frame. Then, skip the + // cursor move because it causes the scroll to be dropped. + bool bAlwaysMoveCursor = + !bDidScrollThisFrame && + ( GetTouchClickMode( ) == TouchClickModes::Passthrough ) && + cv_vr_always_warp_cursor; + + wlserver_lock( ); + wlserver_touchmotion( + flTouchMoveX, + flTouchMoveY, + 0, + ++m_uFakeTimestamp, + bAlwaysMoveCursor ); + wlserver_unlock( ); } } std::string m_szOverlayKey; std::string m_szAppOverlayKey; - const char *m_pchOverlayName = nullptr; - const char *m_pchOverlayIcon = nullptr; - bool m_bExplicitOverlayName = false; - bool m_bNudgeToVisible = false; - bool m_bEnableControlBar = false; - bool m_bEnableControlBarKeyboard = false; - bool m_bEnableControlBarClose = false; - bool m_bEnableClickStabilization = false; - bool m_bModal = false; - float m_flPhysicalWidth = 2.0f; - float m_flPhysicalCurvature = 0.0f; - float m_flPhysicalPreCurvePitch = 0.0f; - float m_flScrollSpeed = 1.0f; + const char *m_pchOverlayName = nullptr; + const char *m_pchOverlayIcon = nullptr; + bool m_bExplicitOverlayName = false; + bool m_bNudgeToVisible = false; + bool m_bEnableControlBar = false; + bool m_bEnableControlBarKeyboard = false; + bool m_bEnableControlBarClose = false; + bool m_bEnableClickStabilization = false; + bool m_bModal = false; + float m_flPhysicalWidth = 2.0f; + float m_flPhysicalCurvature = 0.0f; + float m_flPhysicalPreCurvePitch = 0.0f; + float m_flScrollSpeed = 1.0f; // TODO: Restructure and remove the need for this. @@ -1481,142 +1748,123 @@ namespace gamescope std::atomic m_uFakeTimestamp = { 0 }; - bool m_bMouseDown = false; + bool m_bMouseDown = false; uint64_t m_ulMouseDownTime = 0; // Fake "trackpad" tracking for the whole overlay panel. glm::vec2 m_vScreenTrackpadPos{}; glm::vec2 m_vScreenStartTrackpadPos{}; - uint32_t m_uCurrentScenePid = -1; - uint32_t m_uCurrentSceneAppId = 0; + uint32_t m_uCurrentScenePid = -1; + uint32_t m_uCurrentSceneAppId = 0; std::optional m_oulCurrentSceneVirtualConnectorKey; friend COpenVRConnector; - std::vector m_pActiveConnectors; - std::mutex m_mutActiveConnectors; + std::vector m_pActiveConnectors; + std::mutex m_mutActiveConnectors; std::atomic m_pMouseFocusConnector; std::atomic m_pKeyboardFocusConnector; std::atomic m_bInitted = { false }; std::atomic m_bRunning = { false }; - std::shared_ptr m_pLibInput; + std::shared_ptr m_pLibInput; CAsyncWaiter, 16> m_LibInputWaiter; - std::mutex m_mutForwarderPlanes; + std::mutex m_mutForwarderPlanes; std::vector> m_pForwarderPlanesInFlight; - // Threads need to go last, as they rely on the other things in the class being constructed before their code is run. + // Threads need to go last, as they rely on the other things in the + // class being constructed before their code is run. std::thread m_FlipHandlerThread; - }; + }; //////////////////// // COpenVRConnector //////////////////// - COpenVRConnector::COpenVRConnector( COpenVRBackend *pBackend, uint64_t ulVirtualConnectorKey ) - : CBaseBackendConnector{ ulVirtualConnectorKey } - , m_pBackend{ pBackend } - , m_Planes{ this, this, this, this, this, this, this, this } - { - } + COpenVRConnector::COpenVRConnector( + COpenVRBackend *pBackend, uint64_t ulVirtualConnectorKey ) : + CBaseBackendConnector{ ulVirtualConnectorKey }, + m_pBackend{ pBackend }, + m_Planes{ this, this, this, this, this, this, this, this } + {} - COpenVRConnector::~COpenVRConnector() + COpenVRConnector::~COpenVRConnector( ) { std::scoped_lock lock{ m_pBackend->m_mutActiveConnectors }; MarkSceneAppShown( false ); MarkOverlayShown( false ); - auto iter = m_pBackend->m_pActiveConnectors.begin(); - for ( ; iter != m_pBackend->m_pActiveConnectors.end(); iter++ ) + auto iter = m_pBackend->m_pActiveConnectors.begin( ); + for ( ; iter != m_pBackend->m_pActiveConnectors.end( ); iter++ ) { - if ( *iter == this ) - break; + if ( *iter == this ) break; } - if ( iter != m_pBackend->m_pActiveConnectors.end() ) + if ( iter != m_pBackend->m_pActiveConnectors.end( ) ) m_pBackend->m_pActiveConnectors.erase( iter ); COpenVRConnector *pThis = this; - m_pBackend->m_pMouseFocusConnector.compare_exchange_strong( pThis, nullptr ); + m_pBackend->m_pMouseFocusConnector.compare_exchange_strong( + pThis, nullptr ); pThis = this; - m_pBackend->m_pKeyboardFocusConnector.compare_exchange_strong( pThis, nullptr ); - } - GamescopeScreenType COpenVRConnector::GetScreenType() const - { - return GAMESCOPE_SCREEN_TYPE_INTERNAL; - } - GamescopePanelOrientation COpenVRConnector::GetCurrentOrientation() const - { - return GAMESCOPE_PANEL_ORIENTATION_0; - } - bool COpenVRConnector::SupportsHDR() const - { - return false; - } - bool COpenVRConnector::IsHDRActive() const - { - return false; + m_pBackend->m_pKeyboardFocusConnector.compare_exchange_strong( + pThis, nullptr ); } - const BackendConnectorHDRInfo &COpenVRConnector::GetHDRInfo() const + GamescopeScreenType COpenVRConnector::GetScreenType( ) const + { return GAMESCOPE_SCREEN_TYPE_INTERNAL; } + GamescopePanelOrientation COpenVRConnector::GetCurrentOrientation( ) const + { return GAMESCOPE_PANEL_ORIENTATION_0; } + bool COpenVRConnector::SupportsHDR( ) const { return false; } + bool COpenVRConnector::IsHDRActive( ) const { return false; } + const BackendConnectorHDRInfo &COpenVRConnector::GetHDRInfo( ) const + { return m_HDRInfo; } + bool COpenVRConnector::IsVRRActive( ) const { return false; } + std::span COpenVRConnector::GetModes( ) const + { return std::span{}; } + + bool COpenVRConnector::SupportsVRR( ) const { return false; } + + std::span COpenVRConnector::GetRawEDID( ) const { - return m_HDRInfo; - } - bool COpenVRConnector::IsVRRActive() const - { - return false; - } - std::span COpenVRConnector::GetModes() const - { - return std::span{}; - } - - bool COpenVRConnector::SupportsVRR() const - { - return false; - } - - std::span COpenVRConnector::GetRawEDID() const - { - return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() }; - } - std::span COpenVRConnector::GetValidDynamicRefreshRates() const - { - return std::span{}; + return std::span{ m_FakeEdid.begin( ), + m_FakeEdid.end( ) }; } + std::span + COpenVRConnector::GetValidDynamicRefreshRates( ) const + { return std::span{}; } void COpenVRConnector::GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const + bool bHDR10, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const { - *displayColorimetry = displaycolorimetry_709; - *displayEOTF = EOTF_Gamma22; + *displayColorimetry = displaycolorimetry_709; + *displayEOTF = EOTF_Gamma22; *outputEncodingColorimetry = displaycolorimetry_709; - *outputEncodingEOTF = EOTF_Gamma22; + *outputEncodingEOTF = EOTF_Gamma22; } - const char *COpenVRConnector::GetName() const - { - return "OpenVR"; - } - const char *COpenVRConnector::GetMake() const - { - return "Gamescope"; - } - const char *COpenVRConnector::GetModel() const - { - return "Virtual Display"; - } + const char *COpenVRConnector::GetName( ) const { return "OpenVR"; } + const char *COpenVRConnector::GetMake( ) const { return "Gamescope"; } + const char *COpenVRConnector::GetModel( ) const + { return "Virtual Display"; } int COpenVRConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) { bool bNeedsFullComposite = false; // TODO: Dedupe some of this composite check code between us and drm.cpp - bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f); + bool bLayer0ScreenSize = + close_enough( pFrameInfo->layers[ 0 ].scale.x, 1.0f ) && + close_enough( pFrameInfo->layers[ 0 ].scale.y, 1.0f ); - bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; + bool bNeedsCompositeFromFilter = + ( g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || + g_upscaleFilter == GamescopeUpscaleFilter::PIXEL ) && + !bLayer0ScreenSize; bNeedsFullComposite |= cv_composite_force; bNeedsFullComposite |= pFrameInfo->useFSRLayer0; @@ -1625,46 +1873,49 @@ namespace gamescope bNeedsFullComposite |= bNeedsCompositeFromFilter; bNeedsFullComposite |= g_bColorSliderInUse; bNeedsFullComposite |= pFrameInfo->bFadingOut; - bNeedsFullComposite |= !g_reshade_effect.empty(); - bNeedsFullComposite |= !m_pBackend->UsesModifiers(); + bNeedsFullComposite |= !g_reshade_effect.empty( ); + bNeedsFullComposite |= !m_pBackend->UsesModifiers( ); - if ( g_bOutputHDREnabled ) - bNeedsFullComposite |= g_bHDRItmEnable; + if ( g_bOutputHDREnabled ) bNeedsFullComposite |= g_bHDRItmEnable; - if ( !m_pBackend->SupportsColorManagement() ) - bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace ); + if ( !m_pBackend->SupportsColorManagement( ) ) + bNeedsFullComposite |= + ColorspaceIsHDR( pFrameInfo->layers[ 0 ].colorspace ); - bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); + bNeedsFullComposite |= + !!( g_uCompositeDebug & CompositeDebugFlag::Heatmap ); // Non-Steam windows NEVER use composition! - bool bExplicitNonSteam = VirtualConnectorKeyIsNonSteamWindow( GetVirtualConnectorKey() ); - if ( bExplicitNonSteam ) - bNeedsFullComposite = false; + bool bExplicitNonSteam = + VirtualConnectorKeyIsNonSteamWindow( GetVirtualConnectorKey( ) ); + if ( bExplicitNonSteam ) bNeedsFullComposite = false; if ( !bNeedsFullComposite ) { bool bNeedsBacking = true; if ( pFrameInfo->layerCount >= 1 ) { - if ( pFrameInfo->layers[0].isScreenSize() && ( !pFrameInfo->layers[0].hasAlpha() || cv_vr_transparent_backing ) ) + if ( pFrameInfo->layers[ 0 ].isScreenSize( ) && + ( !pFrameInfo->layers[ 0 ].hasAlpha( ) || + cv_vr_transparent_backing ) ) bNeedsBacking = false; } uint32_t uCurrentPlane = 0; if ( bNeedsBacking ) { - COpenVRPlane *pPlane = &m_Planes[uCurrentPlane++]; + COpenVRPlane *pPlane = &m_Planes[ uCurrentPlane++ ]; pPlane->Present( - OpenVRPlaneState - { - .pTexture = m_pBackend->GetBlackTexture(), + OpenVRPlaneState{ + .pTexture = m_pBackend->GetBlackTexture( ), .flSrcWidth = double( g_nOutputWidth ), .flSrcHeight = double( g_nOutputHeight ), .nDstWidth = int32_t( g_nOutputWidth ), .nDstHeight = int32_t( g_nOutputHeight ), - .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, - .bOpaque = !cv_vr_transparent_backing, - .flAlpha = cv_vr_transparent_backing ? 0.0f : 1.0f, + .eColorspace = + GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, + .bOpaque = !cv_vr_transparent_backing, + .flAlpha = cv_vr_transparent_backing ? 0.0f : 1.0f, } ); } @@ -1672,22 +1923,28 @@ namespace gamescope for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) { - const FrameInfo_t::Layer_t *pLayer = i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr; + const FrameInfo_t::Layer_t *pLayer = + i < pFrameInfo->layerCount ? &pFrameInfo->layers[ i ] + : nullptr; if ( pLayer && pLayer->zpos == g_zposCursor ) { - bool bUsingPhysicalMouse = m_pBackend->GetCurrentMouseConnector() == this && !m_bUsingVRMouse; + bool bUsingPhysicalMouse = + m_pBackend->GetCurrentMouseConnector( ) == this && + !m_bUsingVRMouse; - bool bShowCursor = !IsRelativeMouse(); + bool bShowCursor = !IsRelativeMouse( ); - if (bUsingPhysicalMouse && bShowCursor) + if ( bUsingPhysicalMouse && bShowCursor ) { - vr::HmdVector2_t vMousePos = - { + vr::HmdVector2_t vMousePos = { static_cast( -pLayer->offset.x ), - static_cast( static_cast( g_nOutputHeight ) + pLayer->offset.y ), + static_cast( + static_cast( g_nOutputHeight ) + + pLayer->offset.y ), }; - vr::VROverlay()->SetOverlayCursorPositionOverride( GetPrimaryPlane()->GetOverlay(), &vMousePos ); + vr::VROverlay( )->SetOverlayCursorPositionOverride( + GetPrimaryPlane( )->GetOverlay( ), &vMousePos ); m_bCurrentlyOverridingPosition = true; bShouldHideCursor = false; @@ -1695,14 +1952,15 @@ namespace gamescope pLayer = nullptr; // Handled here. } - m_Planes[uCurrentPlane++].Present( pLayer ); + m_Planes[ uCurrentPlane++ ].Present( pLayer ); } if ( bShouldHideCursor ) { if ( m_bCurrentlyOverridingPosition ) { - vr::VROverlay()->ClearOverlayCursorPositionOverride( GetPrimaryPlane()->GetOverlay() ); + vr::VROverlay( )->ClearOverlayCursorPositionOverride( + GetPrimaryPlane( )->GetOverlay( ) ); m_bCurrentlyOverridingPosition = false; } @@ -1710,7 +1968,8 @@ namespace gamescope } else { - std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); + std::optional oCompositeResult = + vulkan_composite( ( FrameInfo_t * )pFrameInfo, nullptr, false ); if ( !oCompositeResult ) { openvr_log.errorf( "vulkan_composite failed" ); @@ -1723,19 +1982,22 @@ namespace gamescope compositeLayer.scale.x = 1.0; compositeLayer.scale.y = 1.0; compositeLayer.opacity = 1.0; - compositeLayer.zpos = g_zposBase; + compositeLayer.zpos = g_zposBase; compositeLayer.tex = vulkan_get_last_output_image( false, false ); compositeLayer.applyColorMgmt = false; compositeLayer.filter = GamescopeUpscaleFilter::NEAREST; - compositeLayer.ctm = nullptr; - compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + compositeLayer.ctm = nullptr; + compositeLayer.colorspace = + pFrameInfo->outputEncodingEOTF == EOTF_PQ + ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ + : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - GetPrimaryPlane()->Present( &compositeLayer ); + GetPrimaryPlane( )->Present( &compositeLayer ); for ( int i = 1; i < 8; i++ ) - m_Planes[i].Present( nullptr ); + m_Planes[ i ].Present( nullptr ); } return 0; @@ -1745,109 +2007,132 @@ namespace gamescope // INestedHints /////////////////// - void COpenVRConnector::SetCursorImage( std::shared_ptr info ) - { - } + void COpenVRConnector::SetCursorImage( + std::shared_ptr info ) + {} void COpenVRConnector::SetRelativeMouseMode( bool bRelative ) { if ( bRelative != m_bRelativeMouse ) { for ( COpenVRPlane &plane : m_Planes ) { - vr::VROverlay()->SetOverlayFlag( plane.GetOverlay(), vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && bRelative ); + vr::VROverlay( )->SetOverlayFlag( + plane.GetOverlay( ), + vr::VROverlayFlags_HideLaserIntersection, + cv_vr_trackpad_hide_laser && bRelative ); } m_bRelativeMouse = bRelative; } } void COpenVRConnector::SetVisible( bool bVisible ) { - vr::VROverlay()->SetOverlayFlag( GetPrimaryPlane()->GetOverlay(), vr::VROverlayFlags_VisibleInDashboard, bVisible ); + vr::VROverlay( )->SetOverlayFlag( + GetPrimaryPlane( )->GetOverlay( ), + vr::VROverlayFlags_VisibleInDashboard, + bVisible ); } void COpenVRConnector::SetTitle( std::shared_ptr szTitle ) { if ( !m_pBackend->m_bExplicitOverlayName ) - vr::VROverlay()->SetOverlayName( GetPrimaryPlane()->GetOverlay(), szTitle ? szTitle->c_str() : m_pBackend->GetOverlayName() ); + vr::VROverlay( )->SetOverlayName( + GetPrimaryPlane( )->GetOverlay( ), + szTitle ? szTitle->c_str( ) : m_pBackend->GetOverlayName( ) ); } - void COpenVRConnector::SetIcon( std::shared_ptr> uIconPixels ) + void COpenVRConnector::SetIcon( + std::shared_ptr> uIconPixels ) { - bool bExplicitNonSteam = VirtualConnectorKeyIsNonSteamWindow( GetVirtualConnectorKey() ); + bool bExplicitNonSteam = + VirtualConnectorKeyIsNonSteamWindow( GetVirtualConnectorKey( ) ); - if ( cv_vr_use_window_icons && uIconPixels && uIconPixels->size() >= 3 ) + if ( cv_vr_use_window_icons && uIconPixels && + uIconPixels->size( ) >= 3 ) { - const uint32_t uWidth = (*uIconPixels)[0]; - const uint32_t uHeight = (*uIconPixels)[1]; + const uint32_t uWidth = ( *uIconPixels )[ 0 ]; + const uint32_t uHeight = ( *uIconPixels )[ 1 ]; struct rgba_t { - uint8_t r,g,b,a; + uint8_t r, g, b, a; }; - for ( uint32_t& val : *uIconPixels ) + for ( uint32_t &val : *uIconPixels ) { - rgba_t rgb = *((rgba_t*)&val); - std::swap(rgb.r, rgb.b); - val = *((uint32_t*)&rgb); + rgba_t rgb = *( ( rgba_t * )&val ); + std::swap( rgb.r, rgb.b ); + val = *( ( uint32_t * )&rgb ); } - vr::VROverlay()->SetOverlayRaw( GetPrimaryPlane()->GetOverlayThumbnail(), &(*uIconPixels)[2], uWidth, uHeight, sizeof(uint32_t) ); + vr::VROverlay( )->SetOverlayRaw( + GetPrimaryPlane( )->GetOverlayThumbnail( ), + &( *uIconPixels )[ 2 ], + uWidth, + uHeight, + sizeof( uint32_t ) ); } - else if ( m_pBackend->GetOverlayIcon() && !bExplicitNonSteam ) + else if ( m_pBackend->GetOverlayIcon( ) && !bExplicitNonSteam ) { - vr::VROverlay()->SetOverlayFromFile( GetPrimaryPlane()->GetOverlayThumbnail(), m_pBackend->GetOverlayIcon() ); + vr::VROverlay( )->SetOverlayFromFile( + GetPrimaryPlane( )->GetOverlayThumbnail( ), + m_pBackend->GetOverlayIcon( ) ); } else { - vr::VROverlay()->ClearOverlayTexture( GetPrimaryPlane()->GetOverlayThumbnail() ); + vr::VROverlay( )->ClearOverlayTexture( + GetPrimaryPlane( )->GetOverlayThumbnail( ) ); } } - void COpenVRConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) + void COpenVRConnector::SetSelection( + std::shared_ptr szContents, GamescopeSelection eSelection ) { // Do nothing } - bool COpenVRConnector::UpdateEdid() + bool COpenVRConnector::UpdateEdid( ) { m_FakeEdid = GenerateSimpleEdid( g_nNestedWidth, g_nNestedHeight ); return true; } - - bool COpenVRConnector::Init() + bool COpenVRConnector::Init( ) { - openvr_log.debugf( "New connector! -> ulKey: %lu", GetVirtualConnectorKey() ); + openvr_log.debugf( + "New connector! -> ulKey: %lu", GetVirtualConnectorKey( ) ); - m_bNudgeToVisible = m_pBackend->ShouldNudgeToVisible(); + m_bNudgeToVisible = m_pBackend->ShouldNudgeToVisible( ); for ( uint32_t i = 0; i < 8; i++ ) { - bool bSuccess = m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] ); - if ( !bSuccess ) - return false; + bool bSuccess = m_Planes[ i ].Init( + i == 0 ? nullptr : &m_Planes[ 0 ], + i == 0 ? nullptr : &m_Planes[ i - 1 ] ); + if ( !bSuccess ) return false; } - UpdateEdid(); - m_pBackend->HackUpdatePatchedEdid(); + UpdateEdid( ); + m_pBackend->HackUpdatePatchedEdid( ); + + if ( g_bForceRelativeMouse ) this->SetRelativeMouseMode( true ); - if ( g_bForceRelativeMouse ) - this->SetRelativeMouseMode( true ); - if ( m_pBackend->m_oulCurrentSceneVirtualConnectorKey && - GetVirtualConnectorKey() == *m_pBackend->m_oulCurrentSceneVirtualConnectorKey ) + GetVirtualConnectorKey( ) == + *m_pBackend->m_oulCurrentSceneVirtualConnectorKey ) { MarkSceneAppShown( true ); } // Set the initial overlay visibility - MarkOverlayShown( vr::VROverlay()->IsOverlayVisible( GetPrimaryPlane()->GetOverlay() ) ); + MarkOverlayShown( + vr::VROverlay( )->IsOverlayVisible( + GetPrimaryPlane( )->GetOverlay( ) ) ); return true; } void COpenVRConnector::UpdateVisibility( const char *pszReason ) { - bool bVisible = IsVisible(); + bool bVisible = IsVisible( ); if ( m_bWasVisible != bVisible ) { int nNewOverlayVisibleCount; @@ -1856,25 +2141,27 @@ namespace gamescope else nNewOverlayVisibleCount = --m_pBackend->m_nOverlaysVisible; - m_pBackend->m_nOverlaysVisible.notify_all(); + m_pBackend->m_nOverlaysVisible.notify_all( ); m_bWasVisible = bVisible; - openvr_log.debugf( "[%s] ulKey: %lu nNewOverlayVisibleCount: %d -> m_bOverlayShown: %s m_bSceneAppVisible: %s", + openvr_log.debugf( + "[%s] ulKey: %lu nNewOverlayVisibleCount: %d -> " + "m_bOverlayShown: %s m_bSceneAppVisible: %s", pszReason, - GetVirtualConnectorKey(), + GetVirtualConnectorKey( ), nNewOverlayVisibleCount, - m_bOverlayShown ? "true" : "false", + m_bOverlayShown ? "true" : "false", m_bSceneAppVisible ? "true" : "false" ); } } - ///////////////////////// - // COpenVRFb - ///////////////////////// + ///////////////////////// + // COpenVRFb + ///////////////////////// - COpenVRFb::COpenVRFb( COpenVRBackend *pBackend, struct wlr_dmabuf_attributes *attributes ) - : CBaseBackendFb{} - , m_pBackend{ pBackend } + COpenVRFb::COpenVRFb( + COpenVRBackend *pBackend, struct wlr_dmabuf_attributes *attributes ) : + CBaseBackendFb{}, m_pBackend{ pBackend } { if ( !Import( attributes ) ) { @@ -1884,16 +2171,16 @@ namespace gamescope } } - COpenVRFb::~COpenVRFb() + COpenVRFb::~COpenVRFb( ) { - ClearAttributes(); + ClearAttributes( ); if ( m_ulHandle != 0 ) - m_pBackend->GetIPCResourceManager()->UnrefResource( m_ulHandle ); + m_pBackend->GetIPCResourceManager( )->UnrefResource( m_ulHandle ); m_ulHandle = 0; } - IBackendFb *COpenVRFb::EnsureImported() + IBackendFb *COpenVRFb::EnsureImported( ) { if ( m_ulHandle != 0 ) { @@ -1905,7 +2192,7 @@ namespace gamescope if ( Import( &m_attributes ) ) { // We managed to import now. - ClearAttributes(); + ClearAttributes( ); return this; } @@ -1916,62 +2203,62 @@ namespace gamescope bool COpenVRFb::Import( struct wlr_dmabuf_attributes *attributes ) { - if ( !m_pBackend->IsInitted() ) - return false; - - if ( !m_pBackend->UsesModifiers() ) - return false; - - vr::DmabufAttributes_t dmabufAttributes = - { - .unWidth = uint32_t( attributes->width ), - .unHeight = uint32_t( attributes->height ), - .unDepth = 1, - .unMipLevels = 1, - .unArrayLayers = 1, - .unSampleCount = 1, - .unFormat = attributes->format, - .ulModifier = attributes->modifier, - .unPlaneCount = uint32_t( attributes->n_planes ), - .plane = - { - { - .unOffset = attributes->offset[0], - .unStride = attributes->stride[0], - .nFd = attributes->fd[0], - }, - { - .unOffset = attributes->offset[1], - .unStride = attributes->stride[1], - .nFd = attributes->fd[1], - }, - { - .unOffset = attributes->offset[2], - .unStride = attributes->stride[2], - .nFd = attributes->fd[2], - }, - { - .unOffset = attributes->offset[3], - .unStride = attributes->stride[3], - .nFd = attributes->fd[3], - }, - } - }; + if ( !m_pBackend->IsInitted( ) ) return false; + + if ( !m_pBackend->UsesModifiers( ) ) return false; + + vr::DmabufAttributes_t + dmabufAttributes = { .unWidth = uint32_t( attributes->width ), + .unHeight = uint32_t( attributes->height ), + .unDepth = 1, + .unMipLevels = 1, + .unArrayLayers = 1, + .unSampleCount = 1, + .unFormat = attributes->format, + .ulModifier = attributes->modifier, + .unPlaneCount = + uint32_t( attributes->n_planes ), + .plane = { + { + .unOffset = attributes->offset[ 0 ], + .unStride = attributes->stride[ 0 ], + .nFd = attributes->fd[ 0 ], + }, + { + .unOffset = attributes->offset[ 1 ], + .unStride = attributes->stride[ 1 ], + .nFd = attributes->fd[ 1 ], + }, + { + .unOffset = attributes->offset[ 2 ], + .unStride = attributes->stride[ 2 ], + .nFd = attributes->fd[ 2 ], + }, + { + .unOffset = attributes->offset[ 3 ], + .unStride = attributes->stride[ 3 ], + .nFd = attributes->fd[ 3 ], + }, + } }; vr::SharedTextureHandle_t ulSharedHandle = 0; - if ( !m_pBackend->GetIPCResourceManager()->ImportDmabuf( vr::VRApplication_Overlay, &dmabufAttributes, &ulSharedHandle ) ) + if ( !m_pBackend->GetIPCResourceManager( )->ImportDmabuf( + vr::VRApplication_Overlay, + &dmabufAttributes, + &ulSharedHandle ) ) return false; assert( ulSharedHandle != 0 ); // Take the first reference! - if ( !m_pBackend->GetIPCResourceManager()->RefResource( ulSharedHandle, nullptr ) ) + if ( !m_pBackend->GetIPCResourceManager( )->RefResource( + ulSharedHandle, nullptr ) ) return false; m_ulHandle = ulSharedHandle; return true; } - void COpenVRFb::ClearAttributes() + void COpenVRFb::ClearAttributes( ) { if ( m_bHasDmabufAttributes ) { @@ -1980,42 +2267,43 @@ namespace gamescope } } - ///////////////////////// - // COpenVRPlane - ///////////////////////// + ///////////////////////// + // COpenVRPlane + ///////////////////////// - COpenVRPlane::COpenVRPlane( COpenVRConnector *pConnector ) - : m_pConnector{ pConnector } - , m_pBackend{ pConnector ? pConnector->GetBackend() : nullptr } - { - } - COpenVRPlane::~COpenVRPlane() + COpenVRPlane::COpenVRPlane( COpenVRConnector *pConnector ) : + m_pConnector{ pConnector }, + m_pBackend{ pConnector ? pConnector->GetBackend( ) : nullptr } + {} + COpenVRPlane::~COpenVRPlane( ) { if ( m_hOverlayThumbnail != vr::k_ulOverlayHandleInvalid ) - vr::VROverlay()->DestroyOverlay( m_hOverlayThumbnail ); + vr::VROverlay( )->DestroyOverlay( m_hOverlayThumbnail ); if ( m_hOverlay != vr::k_ulOverlayHandleInvalid ) - vr::VROverlay()->DestroyOverlay( m_hOverlay ); + vr::VROverlay( )->DestroyOverlay( m_hOverlay ); } - bool COpenVRPlane::Init( COpenVRPlane *pParent, COpenVRPlane *pSiblingBelow ) + bool + COpenVRPlane::Init( COpenVRPlane *pParent, COpenVRPlane *pSiblingBelow ) { m_bIsSubview = pParent != nullptr; if ( pSiblingBelow ) { - m_uSortOrder = pSiblingBelow->GetSortOrder() + 1; + m_uSortOrder = pSiblingBelow->GetSortOrder( ) + 1; } - std::string sOverlayKey = m_pBackend->GetOverlayKey(); + std::string sOverlayKey = m_pBackend->GetOverlayKey( ); bool bExplicitNonSteam = false; - VirtualConnectorStrategy eStrategy = cv_backend_virtual_connector_strategy; + VirtualConnectorStrategy eStrategy = + cv_backend_virtual_connector_strategy; if ( !VirtualConnectorStrategyIsSingleOutput( eStrategy ) ) { - uint64_t ulKey = m_pConnector->GetVirtualConnectorKey(); - bool bIsSteam = VirtualConnectorKeyIsSteam( ulKey ); + uint64_t ulKey = m_pConnector->GetVirtualConnectorKey( ); + bool bIsSteam = VirtualConnectorKeyIsSteam( ulKey ); if ( !bIsSteam ) { if ( ulKey == k_ulSteamBootstrapperKey ) @@ -2024,14 +2312,19 @@ namespace gamescope } else { - bExplicitNonSteam = VirtualConnectorKeyIsNonSteamWindow( ulKey ); + bExplicitNonSteam = + VirtualConnectorKeyIsNonSteamWindow( ulKey ); if ( bExplicitNonSteam ) { - sOverlayKey = std::format( "gamescope.{}.window.{}", wlserver_get_wl_display_name(), ulKey & ~gamescope::k_ulNonSteamWindowBit ); + sOverlayKey = std::format( + "gamescope.{}.window.{}", + wlserver_get_wl_display_name( ), + ulKey & ~gamescope::k_ulNonSteamWindowBit ); } else { - const char *pszAppOverlayKey = m_pBackend->GetAppOverlayKey(); + const char *pszAppOverlayKey = + m_pBackend->GetAppOverlayKey( ); if ( pszAppOverlayKey && *pszAppOverlayKey ) { sOverlayKey = pszAppOverlayKey; @@ -2041,7 +2334,8 @@ namespace gamescope { sOverlayKey += ".app."; } - sOverlayKey += std::to_string( m_pConnector->GetVirtualConnectorKey() ); + sOverlayKey += std::to_string( + m_pConnector->GetVirtualConnectorKey( ) ); } } } @@ -2050,89 +2344,148 @@ namespace gamescope if ( !m_bIsSubview ) { m_sDashboardOverlayKey = sOverlayKey; - openvr_log.debugf( "Creating new dashboard overlay: %s", m_sDashboardOverlayKey.c_str() ); - - vr::VROverlay()->CreateDashboardOverlay( - sOverlayKey.c_str(), - m_pBackend->GetOverlayName(), - &m_hOverlay, &m_hOverlayThumbnail ); - - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBar, m_pBackend->ShouldEnableControlBar() || bExplicitNonSteam ); - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarKeyboard, m_pBackend->ShouldEnableControlBarKeyboard() || bExplicitNonSteam ); - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarClose, m_pBackend->ShouldEnableControlBarClose() || bExplicitNonSteam ); - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_WantsModalBehavior, m_pBackend->IsModal() ); - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_HideLaserIntersection, cv_vr_trackpad_hide_laser && m_pConnector->IsRelativeMouse() ); - - vr::VROverlay()->SetOverlayWidthInMeters( m_hOverlay, m_pBackend->GetPhysicalWidth() ); - vr::VROverlay()->SetOverlayCurvature ( m_hOverlay, m_pBackend->GetPhysicalCurvature() ); - vr::VROverlay()->SetOverlayPreCurvePitch( m_hOverlay, m_pBackend->GetPhysicalPreCurvePitch() ); - - if ( m_pBackend->GetOverlayIcon() && !bExplicitNonSteam ) + openvr_log.debugf( + "Creating new dashboard overlay: %s", + m_sDashboardOverlayKey.c_str( ) ); + + vr::VROverlay( )->CreateDashboardOverlay( + sOverlayKey.c_str( ), + m_pBackend->GetOverlayName( ), + &m_hOverlay, + &m_hOverlayThumbnail ); + + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, + vr::VROverlayFlags_EnableControlBar, + m_pBackend->ShouldEnableControlBar( ) || bExplicitNonSteam ); + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, + vr::VROverlayFlags_EnableControlBarKeyboard, + m_pBackend->ShouldEnableControlBarKeyboard( ) || + bExplicitNonSteam ); + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, + vr::VROverlayFlags_EnableControlBarClose, + m_pBackend->ShouldEnableControlBarClose( ) || + bExplicitNonSteam ); + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, + vr::VROverlayFlags_WantsModalBehavior, + m_pBackend->IsModal( ) ); + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, + vr::VROverlayFlags_HideLaserIntersection, + cv_vr_trackpad_hide_laser && m_pConnector->IsRelativeMouse( ) ); + + vr::VROverlay( )->SetOverlayWidthInMeters( + m_hOverlay, m_pBackend->GetPhysicalWidth( ) ); + vr::VROverlay( )->SetOverlayCurvature( + m_hOverlay, m_pBackend->GetPhysicalCurvature( ) ); + vr::VROverlay( )->SetOverlayPreCurvePitch( + m_hOverlay, m_pBackend->GetPhysicalPreCurvePitch( ) ); + + if ( m_pBackend->GetOverlayIcon( ) && !bExplicitNonSteam ) { - vr::EVROverlayError err = vr::VROverlay()->SetOverlayFromFile( m_hOverlayThumbnail, m_pBackend->GetOverlayIcon() ); - if( err != vr::VROverlayError_None ) + vr::EVROverlayError err = vr::VROverlay( )->SetOverlayFromFile( + m_hOverlayThumbnail, m_pBackend->GetOverlayIcon( ) ); + if ( err != vr::VROverlayError_None ) { - openvr_log.errorf( "Unable to set thumbnail to %s: %s\n", m_pBackend->GetOverlayIcon(), vr::VROverlay()->GetOverlayErrorNameFromEnum( err ) ); + openvr_log.errorf( + "Unable to set thumbnail to %s: %s\n", + m_pBackend->GetOverlayIcon( ), + vr::VROverlay( )->GetOverlayErrorNameFromEnum( err ) ); } } } else { - std::string szSubviewName = sOverlayKey + std::string(".layer") + std::to_string( m_uSortOrder ); - vr::VROverlay()->CreateSubviewOverlay( pParent->GetOverlay(), szSubviewName.c_str(), "Gamescope Layer", &m_hOverlay ); + std::string szSubviewName = sOverlayKey + std::string( ".layer" ) + + std::to_string( m_uSortOrder ); + vr::VROverlay( )->CreateSubviewOverlay( + pParent->GetOverlay( ), + szSubviewName.c_str( ), + "Gamescope Layer", + &m_hOverlay ); } - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableClickStabilization, m_pBackend->ShouldEnableClickStabilization() ); - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IsPremultiplied, true ); - vr::VROverlay()->SetOverlayInputMethod( m_hOverlay, m_bIsSubview ? vr::VROverlayInputMethod_None : vr::VROverlayInputMethod_Mouse ); - vr::VROverlay()->SetOverlaySortOrder( m_hOverlay, m_uSortOrder ); + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, + vr::VROverlayFlags_EnableClickStabilization, + m_pBackend->ShouldEnableClickStabilization( ) ); + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, vr::VROverlayFlags_IsPremultiplied, true ); + vr::VROverlay( )->SetOverlayInputMethod( + m_hOverlay, + m_bIsSubview ? vr::VROverlayInputMethod_None + : vr::VROverlayInputMethod_Mouse ); + vr::VROverlay( )->SetOverlaySortOrder( m_hOverlay, m_uSortOrder ); return true; } void COpenVRPlane::Present( std::optional oState ) { - + if ( oState ) { - vr::VROverlay()->SetOverlayAlpha( m_hOverlay, oState->flAlpha ); - - if ( m_pBackend->UsesModifiers() ) - { - vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, oState->bOpaque || !DRMFormatHasAlpha( oState->pTexture->drmFormat() ) || cv_vr_debug_force_opaque ); + vr::VROverlay( )->SetOverlayAlpha( m_hOverlay, oState->flAlpha ); - vr::HmdVector2_t vMouseScale = - { + if ( m_pBackend->UsesModifiers( ) ) + { + vr::VROverlay( )->SetOverlayFlag( + m_hOverlay, + vr::VROverlayFlags_IgnoreTextureAlpha, + oState->bOpaque || + !DRMFormatHasAlpha( oState->pTexture->drmFormat( ) ) || + cv_vr_debug_force_opaque ); + + vr::HmdVector2_t vMouseScale = { float( oState->nDstWidth ), float( oState->nDstHeight ), }; - vr::VROverlay()->SetOverlayMouseScale( m_hOverlay, &vMouseScale ); - vr::VRTextureBounds_t vTextureBounds = - { - float( ( oState->flSrcX ) / double( oState->pTexture->width() ) ), - float( ( oState->flSrcY ) / double( oState->pTexture->height() ) ), - float( ( oState->flSrcX + oState->flSrcWidth ) / double( oState->pTexture->width() ) ), - float( ( oState->flSrcY + oState->flSrcHeight ) / double( oState->pTexture->height() ) ), + vr::VROverlay( )->SetOverlayMouseScale( + m_hOverlay, &vMouseScale ); + vr::VRTextureBounds_t vTextureBounds = { + float( + ( oState->flSrcX ) / + double( oState->pTexture->width( ) ) ), + float( + ( oState->flSrcY ) / + double( oState->pTexture->height( ) ) ), + float( + ( oState->flSrcX + oState->flSrcWidth ) / + double( oState->pTexture->width( ) ) ), + float( + ( oState->flSrcY + oState->flSrcHeight ) / + double( oState->pTexture->height( ) ) ), }; - vr::VROverlay()->SetOverlayTextureBounds( m_hOverlay, &vTextureBounds ); + vr::VROverlay( )->SetOverlayTextureBounds( + m_hOverlay, &vTextureBounds ); if ( m_bIsSubview ) { - vr::VROverlay()->SetSubviewPosition( m_hOverlay, oState->nDestX, oState->nDestY ); - vr::VROverlay()->ShowOverlay( m_hOverlay ); + vr::VROverlay( )->SetSubviewPosition( + m_hOverlay, oState->nDestX, oState->nDestY ); + vr::VROverlay( )->ShowOverlay( m_hOverlay ); } - COpenVRFb *pFb = static_cast( oState->pTexture->GetBackendFb()->EnsureImported() ); + COpenVRFb *pFb = static_cast( + oState->pTexture->GetBackendFb( )->EnsureImported( ) ); if ( !pFb ) { openvr_log.debugf( "OpenVRPlane::Present: No FB!" ); return; } - vr::SharedTextureHandle_t ulHandle = pFb->GetSharedTextureHandle(); + vr::SharedTextureHandle_t ulHandle = + pFb->GetSharedTextureHandle( ); - vr::Texture_t texture = { (void *)&ulHandle, vr::TextureType_SharedTextureHandle, vr::ColorSpace_Gamma }; - vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture ); + vr::Texture_t texture = { ( void * )&ulHandle, + vr::TextureType_SharedTextureHandle, + vr::ColorSpace_Gamma }; + vr::VROverlay( )->SetOverlayTexture( m_hOverlay, &texture ); { std::scoped_lock lock{ m_mutFbIds }; @@ -2142,48 +2495,49 @@ namespace gamescope else { assert( !m_bIsSubview ); - - vr::VRVulkanTextureData_t data = - { - .m_nImage = (uint64_t)(uintptr_t)oState->pTexture->vkImage(), - .m_pDevice = g_device.device(), - .m_pPhysicalDevice = g_device.physDev(), - .m_pInstance = g_device.instance(), - .m_pQueue = g_device.queue(), - .m_nQueueFamilyIndex = g_device.queueFamily(), - .m_nWidth = oState->pTexture->width(), - .m_nHeight = oState->pTexture->height(), - .m_nFormat = oState->pTexture->format(), + + vr::VRVulkanTextureData_t data = { + .m_nImage = + ( uint64_t )( uintptr_t )oState->pTexture->vkImage( ), + .m_pDevice = g_device.device( ), + .m_pPhysicalDevice = g_device.physDev( ), + .m_pInstance = g_device.instance( ), + .m_pQueue = g_device.queue( ), + .m_nQueueFamilyIndex = g_device.queueFamily( ), + .m_nWidth = oState->pTexture->width( ), + .m_nHeight = oState->pTexture->height( ), + .m_nFormat = oState->pTexture->format( ), .m_nSampleCount = 1, }; - vr::Texture_t texture = { &data, vr::TextureType_Vulkan, vr::ColorSpace_Gamma }; - vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture ); + vr::Texture_t texture = { &data, + vr::TextureType_Vulkan, + vr::ColorSpace_Gamma }; + vr::VROverlay( )->SetOverlayTexture( m_hOverlay, &texture ); } if ( !m_bIsSubview ) { - bool bNudgeToVisible = cv_vr_nudge_to_visible_per_connector - ? m_pConnector->ConsumeNudgeToVisible() - : m_pBackend->ConsumeNudgeToVisible(); + bool bNudgeToVisible = + cv_vr_nudge_to_visible_per_connector + ? m_pConnector->ConsumeNudgeToVisible( ) + : m_pBackend->ConsumeNudgeToVisible( ); if ( bNudgeToVisible ) { - vr::VROverlay()->ShowDashboard( m_sDashboardOverlayKey.c_str() ); + vr::VROverlay( )->ShowDashboard( + m_sDashboardOverlayKey.c_str( ) ); // Make sure we don't leave any nudges either side. - m_pConnector->ConsumeNudgeToVisible(); + m_pConnector->ConsumeNudgeToVisible( ); if ( !cv_vr_nudge_to_visible_per_connector ) - m_pBackend->ConsumeNudgeToVisible(); + m_pBackend->ConsumeNudgeToVisible( ); } } } else { - if ( m_bIsSubview ) - { - vr::VROverlay()->HideOverlay( m_hOverlay ); - } + if ( m_bIsSubview ) { vr::VROverlay( )->HideOverlay( m_hOverlay ); } } } @@ -2192,19 +2546,21 @@ namespace gamescope if ( pLayer && pLayer->tex ) { Present( - OpenVRPlaneState - { - .pTexture = pLayer->tex.get(), + OpenVRPlaneState{ + .pTexture = pLayer->tex.get( ), .nDestX = int32_t( -pLayer->offset.x ), .nDestY = int32_t( -pLayer->offset.y ), .flSrcX = 0.0, .flSrcY = 0.0, - .flSrcWidth = double( pLayer->tex->width() ), - .flSrcHeight = double( pLayer->tex->height() ), - .nDstWidth = int32_t( pLayer->tex->width() / double( pLayer->scale.x ) ), - .nDstHeight = int32_t( pLayer->tex->height() / double( pLayer->scale.y ) ), + .flSrcWidth = double( pLayer->tex->width( ) ), + .flSrcHeight = double( pLayer->tex->height( ) ), + .nDstWidth = int32_t( + pLayer->tex->width( ) / double( pLayer->scale.x ) ), + .nDstHeight = int32_t( + pLayer->tex->height( ) / double( pLayer->scale.y ) ), .eColorspace = pLayer->colorspace, - .bOpaque = pLayer->zpos == g_zposBase && !cv_vr_transparent_backing, + .bOpaque = pLayer->zpos == g_zposBase && + !cv_vr_transparent_backing, .flAlpha = pLayer->opacity, } ); } @@ -2216,10 +2572,12 @@ namespace gamescope void COpenVRPlane::ForwardFramebuffer( COpenVRFb *pFb ) { - vr::SharedTextureHandle_t ulHandle = pFb->GetSharedTextureHandle(); + vr::SharedTextureHandle_t ulHandle = pFb->GetSharedTextureHandle( ); - vr::Texture_t texture = { (void *)&ulHandle, vr::TextureType_SharedTextureHandle, vr::ColorSpace_Gamma }; - vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture ); + vr::Texture_t texture = { ( void * )&ulHandle, + vr::TextureType_SharedTextureHandle, + vr::ColorSpace_Gamma }; + vr::VROverlay( )->SetOverlayTexture( m_hOverlay, &texture ); { std::scoped_lock lock{ m_mutFbIds }; @@ -2227,28 +2585,25 @@ namespace gamescope } } - void COpenVRPlane::OnPageFlip() + void COpenVRPlane::OnPageFlip( ) { { std::scoped_lock lock{ m_mutFbIds }; - // XXX: We have no guarantee for WHAT the sequence is here. This could be total crap. - // but this is probably good enough for now? + // XXX: We have no guarantee for WHAT the sequence is here. This + // could be total crap. but this is probably good enough for now? if ( m_pQueuedFbId ) { m_pVisibleFbId = std::move( m_pQueuedFbId ); - m_pQueuedFbId = nullptr; + m_pQueuedFbId = nullptr; } } } - ///////////////////////// - // Backend Instantiator - ///////////////////////// + ///////////////////////// + // Backend Instantiator + ///////////////////////// - template <> - bool IBackend::Set() - { - return Set( new COpenVRBackend{} ); - } -} + template<> bool IBackend::Set( ) + { return Set( new COpenVRBackend{} ); } +} // namespace gamescope diff --git a/src/Backends/SDLBackend.cpp b/src/Backends/SDLBackend.cpp index 87bde7e2f1..68777c914d 100644 --- a/src/Backends/SDLBackend.cpp +++ b/src/Backends/SDLBackend.cpp @@ -1,992 +1,1031 @@ // For the nested case, reads input from the SDL window and send to wayland #include -#include #include -#include #include +#include +#include #include #include +#include +#include #include "SDL_clipboard.h" #include "SDL_events.h" +#include "Utils/Defer.h" #include "gamescope_shared.h" #include "main.hpp" -#include "wlserver.hpp" -#include -#include +#include "refresh_rate.h" #include "rendervulkan.hpp" #include "steamcompmgr.hpp" -#include "Utils/Defer.h" -#include "refresh_rate.h" +#include "wlserver.hpp" #include "sdlscancodetable.hpp" -static int g_nOldNestedRefresh = 0; -static bool g_bWindowFocused = true; +static int g_nOldNestedRefresh = 0; +static bool g_bWindowFocused = true; -static int g_nOutputWidthPts = 0; +static int g_nOutputWidthPts = 0; static int g_nOutputHeightPts = 0; extern bool g_bForceHDR10OutputDebug; extern bool steamMode; extern bool g_bFirstFrame; -extern int g_nPreferredOutputWidth; -extern int g_nPreferredOutputHeight; +extern int g_nPreferredOutputWidth; +extern int g_nPreferredOutputHeight; namespace gamescope { - enum class SDLInitState - { - SDLInit_Waiting, - SDLInit_Success, - SDLInit_Failure, - }; - - enum SDLCustomEvents - { - GAMESCOPE_SDL_EVENT_TITLE, - GAMESCOPE_SDL_EVENT_ICON, - GAMESCOPE_SDL_EVENT_VISIBLE, - GAMESCOPE_SDL_EVENT_GRAB, - GAMESCOPE_SDL_EVENT_CURSOR, - - GAMESCOPE_SDL_EVENT_COUNT, - }; - - class CSDLBackend; - - class CSDLConnector final : public CBaseBackendConnector, public INestedHints - { - public: - CSDLConnector( CSDLBackend *pBackend ); - virtual bool Init(); - - virtual ~CSDLConnector(); - - ///////////////////// - // IBackendConnector - ///////////////////// - - virtual gamescope::GamescopeScreenType GetScreenType() const override; - virtual GamescopePanelOrientation GetCurrentOrientation() const override; - virtual bool SupportsHDR() const override; - virtual bool IsHDRActive() const override; - virtual const BackendConnectorHDRInfo &GetHDRInfo() const override; - virtual bool IsVRRActive() const override; - virtual std::span GetModes() const override; - - virtual bool SupportsVRR() const override; - - virtual std::span GetRawEDID() const override; - virtual std::span GetValidDynamicRefreshRates() const override; + enum class SDLInitState + { + SDLInit_Waiting, + SDLInit_Success, + SDLInit_Failure, + }; + + enum SDLCustomEvents + { + GAMESCOPE_SDL_EVENT_TITLE, + GAMESCOPE_SDL_EVENT_ICON, + GAMESCOPE_SDL_EVENT_VISIBLE, + GAMESCOPE_SDL_EVENT_GRAB, + GAMESCOPE_SDL_EVENT_CURSOR, + + GAMESCOPE_SDL_EVENT_COUNT, + }; + + class CSDLBackend; + + class CSDLConnector final : public CBaseBackendConnector, + public INestedHints + { + public: + CSDLConnector( CSDLBackend *pBackend ); + bool Init( ); + + virtual ~CSDLConnector( ); + + ///////////////////// + // IBackendConnector + ///////////////////// + + virtual gamescope::GamescopeScreenType GetScreenType( ) const override; + virtual GamescopePanelOrientation + GetCurrentOrientation( ) const override; + virtual bool SupportsHDR( ) const override; + virtual bool IsHDRActive( ) const override; + virtual const BackendConnectorHDRInfo &GetHDRInfo( ) const override; + virtual bool IsVRRActive( ) const override; + virtual std::span GetModes( ) const override; + + virtual bool SupportsVRR( ) const override; + + virtual std::span GetRawEDID( ) const override; + virtual std::span + GetValidDynamicRefreshRates( ) const override; virtual void GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override; + bool bHDR10, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const override; - virtual const char *GetName() const override - { - return "SDLWindow"; - } - virtual const char *GetMake() const override - { - return "Gamescope"; - } - virtual const char *GetModel() const override - { - return "Virtual Display"; - } + virtual const char *GetName( ) const override { return "SDLWindow"; } + virtual const char *GetMake( ) const override { return "Gamescope"; } + virtual const char *GetModel( ) const override + { return "Virtual Display"; } - virtual INestedHints *GetNestedHints() override - { - return this; - } + virtual INestedHints *GetNestedHints( ) override { return this; } - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; + virtual int + Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; - /////////////////// - // INestedHints - /////////////////// + /////////////////// + // INestedHints + /////////////////// - virtual void SetCursorImage( std::shared_ptr info ) override; + virtual void SetCursorImage( + std::shared_ptr info ) override; virtual void SetRelativeMouseMode( bool bRelative ) override; virtual void SetVisible( bool bVisible ) override; virtual void SetTitle( std::shared_ptr szTitle ) override; - virtual void SetIcon( std::shared_ptr> uIconPixels ) override; - virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override; + virtual void + SetIcon( std::shared_ptr> uIconPixels ) override; + virtual void SetSelection( + std::shared_ptr szContents, + GamescopeSelection eSelection ) override; - //-- + //-- - SDL_Window *GetSDLWindow() const { return m_pWindow; } - VkSurfaceKHR GetVulkanSurface() const { return m_pVkSurface; } - private: - CSDLBackend *m_pBackend = nullptr; - SDL_Window *m_pWindow = nullptr; - VkSurfaceKHR m_pVkSurface = VK_NULL_HANDLE; - BackendConnectorHDRInfo m_HDRInfo{}; - }; + SDL_Window *GetSDLWindow( ) const { return m_pWindow; } + VkSurfaceKHR GetVulkanSurface( ) const { return m_pVkSurface; } - class CSDLBackend : public CBaseBackend - { - public: - CSDLBackend(); + private: + CSDLBackend *m_pBackend = nullptr; + SDL_Window *m_pWindow = nullptr; + VkSurfaceKHR m_pVkSurface = VK_NULL_HANDLE; + BackendConnectorHDRInfo m_HDRInfo{}; + }; - ///////////// - // IBackend - ///////////// + class CSDLBackend : public CBaseBackend + { + public: + CSDLBackend( ); + + ///////////// + // IBackend + ///////////// + + virtual bool Init( ) override; + virtual bool PostInit( ) override; + virtual std::span + GetInstanceExtensions( ) const override; + virtual std::span GetDeviceExtensions( + VkPhysicalDevice pVkPhysicalDevice ) const override; + virtual VkImageLayout GetPresentLayout( ) const override; + virtual void GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, + uint32_t *pOverlayPlaneFormat ) const override; + virtual bool ValidPhysicalDevice( + VkPhysicalDevice pVkPhysicalDevice ) const override; + + virtual void + DirtyState( bool bForce = false, bool bForceModeset = false ) override; + virtual bool PollState( ) override; + + virtual std::shared_ptr CreateBackendBlob( + const std::type_info &type, + std::span data ) override; + + virtual OwningRc + ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override; + virtual bool UsesModifiers( ) const override; + virtual std::span + GetSupportedModifiers( uint32_t uDrmFormat ) const override; + + virtual IBackendConnector *GetCurrentConnector( ) override; + virtual IBackendConnector * + GetConnector( GamescopeScreenType eScreenType ) override; + + virtual bool SupportsPlaneHardwareCursor( ) const override; + + virtual bool SupportsTearing( ) const override; + virtual bool UsesVulkanSwapchain( ) const override; + + virtual bool IsSessionBased( ) const override; + virtual bool SupportsExplicitSync( ) const override; + + virtual bool IsPaused( ) const override; + virtual bool IsVisible( ) const override; + + virtual glm::uvec2 + CursorSurfaceSize( glm::uvec2 uvecSize ) const override; + + //////////////////////// + // INestedHints Compat + /////////////////////// - virtual bool Init() override; - virtual bool PostInit() override; - virtual std::span GetInstanceExtensions() const override; - virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override; - virtual VkImageLayout GetPresentLayout() const override; - virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override; - virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override; + void SetCursorImage( std::shared_ptr info ); + void SetRelativeMouseMode( bool bRelative ); + void SetVisible( bool bVisible ); + void SetTitle( std::shared_ptr szTitle ); + void SetIcon( std::shared_ptr> uIconPixels ); + void SetSelection( + std::shared_ptr szContents, + GamescopeSelection eSelection ); + + protected: + virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override; + + private: + void SDLThreadFunc( ); + + uint32_t GetUserEventIndex( SDLCustomEvents eEvent ) const; + void PushUserEvent( SDLCustomEvents eEvent ); + + bool m_bShown = false; + CSDLConnector m_Connector; // Window. + uint32_t m_uUserEventIdBase = 0u; + std::vector m_pszInstanceExtensions; + + std::thread m_SDLThread; + std::atomic m_eSDLInit = { + SDLInitState::SDLInit_Waiting + }; + + std::atomic m_bApplicationGrabbed = { false }; + std::atomic m_bApplicationVisible = { false }; + std::atomic> + m_pApplicationCursor; + std::atomic> m_pApplicationTitle; + std::atomic>> m_pApplicationIcon; + SDL_Surface *m_pIconSurface = nullptr; + SDL_Surface *m_pCursorSurface = nullptr; + SDL_Cursor *m_pCursor = nullptr; + }; + + ////////////////// + // CSDLConnector + ////////////////// + + CSDLConnector::CSDLConnector( CSDLBackend *pBackend ) : + m_pBackend{ pBackend } + {} + + CSDLConnector::~CSDLConnector( ) + { + if ( m_pWindow ) SDL_DestroyWindow( m_pWindow ); + } - virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override; - virtual bool PollState() override; + bool CSDLConnector::Init( ) + { + g_nOutputWidth = g_nPreferredOutputWidth; + g_nOutputHeight = g_nPreferredOutputHeight; + g_nOutputRefresh = g_nNestedRefresh; - virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override; + if ( g_nOutputHeight == 0 ) + { + if ( g_nOutputWidth != 0 ) + { + fprintf( stderr, "Cannot specify -W without -H\n" ); + return false; + } + g_nOutputHeight = 720; + } + if ( g_nOutputWidth == 0 ) g_nOutputWidth = g_nOutputHeight * 16 / 9; + if ( g_nOutputRefresh == 0 ) + g_nOutputRefresh = gamescope::ConvertHztomHz( 60 ); - virtual OwningRc ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override; - virtual bool UsesModifiers() const override; - virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override; + uint32_t uSDLWindowFlags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | + SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI; - virtual IBackendConnector *GetCurrentConnector() override; - virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override; + if ( g_bBorderlessOutputWindow == true ) + uSDLWindowFlags |= SDL_WINDOW_BORDERLESS; - virtual bool SupportsPlaneHardwareCursor() const override; + if ( g_bFullscreen == true ) + uSDLWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; - virtual bool SupportsTearing() const override; - virtual bool UsesVulkanSwapchain() const override; + if ( g_bGrabbed == true ) + uSDLWindowFlags |= SDL_WINDOW_KEYBOARD_GRABBED; - virtual bool IsSessionBased() const override; - virtual bool SupportsExplicitSync() const override; + m_pWindow = SDL_CreateWindow( + "gamescope", + SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), + SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), + g_nOutputWidth, + g_nOutputHeight, + uSDLWindowFlags ); - virtual bool IsPaused() const override; - virtual bool IsVisible() const override; + if ( m_pWindow == nullptr ) return false; - virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override; + if ( !SDL_Vulkan_CreateSurface( + m_pWindow, vulkan_get_instance( ), &m_pVkSurface ) ) + { + fprintf( + stderr, + "SDL_Vulkan_CreateSurface failed: %s", + SDL_GetError( ) ); + return false; + } - //////////////////////// - // INestedHints Compat - /////////////////////// + return true; + } - void SetCursorImage( std::shared_ptr info ); - void SetRelativeMouseMode( bool bRelative ); - void SetVisible( bool bVisible ); - void SetTitle( std::shared_ptr szTitle ); - void SetIcon( std::shared_ptr> uIconPixels ); - void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ); - protected: - virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override; - private: - void SDLThreadFunc(); - - uint32_t GetUserEventIndex( SDLCustomEvents eEvent ) const; - void PushUserEvent( SDLCustomEvents eEvent ); - - bool m_bShown = false; - CSDLConnector m_Connector; // Window. - uint32_t m_uUserEventIdBase = 0u; - std::vector m_pszInstanceExtensions; - - std::thread m_SDLThread; - std::atomic m_eSDLInit = { SDLInitState::SDLInit_Waiting }; - - std::atomic m_bApplicationGrabbed = { false }; - std::atomic m_bApplicationVisible = { false }; - std::atomic> m_pApplicationCursor; - std::atomic> m_pApplicationTitle; - std::atomic>> m_pApplicationIcon; - SDL_Surface *m_pIconSurface = nullptr; - SDL_Surface *m_pCursorSurface = nullptr; - SDL_Cursor *m_pCursor = nullptr; - }; - - ////////////////// - // CSDLConnector - ////////////////// - - CSDLConnector::CSDLConnector( CSDLBackend *pBackend ) - : m_pBackend{ pBackend } - { - } - - CSDLConnector::~CSDLConnector() - { - if ( m_pWindow ) - SDL_DestroyWindow( m_pWindow ); - } - - bool CSDLConnector::Init() - { - g_nOutputWidth = g_nPreferredOutputWidth; - g_nOutputHeight = g_nPreferredOutputHeight; - g_nOutputRefresh = g_nNestedRefresh; - - if ( g_nOutputHeight == 0 ) - { - if ( g_nOutputWidth != 0 ) - { - fprintf( stderr, "Cannot specify -W without -H\n" ); - return false; - } - g_nOutputHeight = 720; - } - if ( g_nOutputWidth == 0 ) - g_nOutputWidth = g_nOutputHeight * 16 / 9; - if ( g_nOutputRefresh == 0 ) - g_nOutputRefresh = gamescope::ConvertHztomHz( 60 ); - - uint32_t uSDLWindowFlags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI; - - if ( g_bBorderlessOutputWindow == true ) - uSDLWindowFlags |= SDL_WINDOW_BORDERLESS; - - if ( g_bFullscreen == true ) - uSDLWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; - - if ( g_bGrabbed == true ) - uSDLWindowFlags |= SDL_WINDOW_KEYBOARD_GRABBED; - - m_pWindow = SDL_CreateWindow( - "gamescope", - SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), - SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), - g_nOutputWidth, - g_nOutputHeight, - uSDLWindowFlags ); - - if ( m_pWindow == nullptr ) - return false; - - if ( !SDL_Vulkan_CreateSurface( m_pWindow, vulkan_get_instance(), &m_pVkSurface ) ) - { - fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError () ); - return false; - } - - return true; - } - - GamescopeScreenType CSDLConnector::GetScreenType() const - { - return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; - } - GamescopePanelOrientation CSDLConnector::GetCurrentOrientation() const - { - return GAMESCOPE_PANEL_ORIENTATION_0; - } - bool CSDLConnector::SupportsHDR() const - { - return GetHDRInfo().IsHDR10(); - } - bool CSDLConnector::IsHDRActive() const - { - // XXX: blah - return false; - } - const BackendConnectorHDRInfo &CSDLConnector::GetHDRInfo() const - { - return m_HDRInfo; - } - bool CSDLConnector::IsVRRActive() const - { - return false; - } - std::span CSDLConnector::GetModes() const - { - return std::span{}; - } - - bool CSDLConnector::SupportsVRR() const - { - return false; - } - - std::span CSDLConnector::GetRawEDID() const - { - return std::span{}; - } - std::span CSDLConnector::GetValidDynamicRefreshRates() const - { - return std::span{}; - } - - void CSDLConnector::GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const - { - if ( g_bForceHDR10OutputDebug ) - { - *displayColorimetry = displaycolorimetry_2020; - *displayEOTF = EOTF_PQ; - *outputEncodingColorimetry = displaycolorimetry_2020; - *outputEncodingEOTF = EOTF_PQ; - } - else - { - *displayColorimetry = displaycolorimetry_709; - *displayEOTF = EOTF_Gamma22; - *outputEncodingColorimetry = displaycolorimetry_709; - *outputEncodingEOTF = EOTF_Gamma22; - } - } - - int CSDLConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) - { - // TODO: Resolve const crap - std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); - if ( !oCompositeResult ) - return -EINVAL; - - vulkan_present_to_window(); - - // TODO: Hook up PresentationFeedback. - - // Wait for the composite result on our side *after* we - // commit the buffer to the compositor to avoid a bubble. - vulkan_wait( *oCompositeResult, true ); - - GetVBlankTimer().UpdateWasCompositing( true ); - GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - - return 0; - } - - void CSDLConnector::SetCursorImage( std::shared_ptr info ) - { - m_pBackend->SetCursorImage( std::move( info ) ); - } - void CSDLConnector::SetRelativeMouseMode( bool bRelative ) - { - m_pBackend->SetRelativeMouseMode( bRelative ); - } - void CSDLConnector::SetVisible( bool bVisible ) - { - m_pBackend->SetVisible( bVisible ); - } - void CSDLConnector::SetTitle( std::shared_ptr szTitle ) - { - m_pBackend->SetTitle( std::move( szTitle ) ); - } - void CSDLConnector::SetIcon( std::shared_ptr> uIconPixels ) - { - m_pBackend->SetIcon( std::move( uIconPixels ) ); - } - - void CSDLConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) + GamescopeScreenType CSDLConnector::GetScreenType( ) const + { return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; } + GamescopePanelOrientation CSDLConnector::GetCurrentOrientation( ) const + { return GAMESCOPE_PANEL_ORIENTATION_0; } + bool CSDLConnector::SupportsHDR( ) const + { return GetHDRInfo( ).IsHDR10( ); } + bool CSDLConnector::IsHDRActive( ) const + { + // XXX: blah + return false; + } + const BackendConnectorHDRInfo &CSDLConnector::GetHDRInfo( ) const + { return m_HDRInfo; } + bool CSDLConnector::IsVRRActive( ) const { return false; } + std::span CSDLConnector::GetModes( ) const + { return std::span{}; } + + bool CSDLConnector::SupportsVRR( ) const { return false; } + + std::span CSDLConnector::GetRawEDID( ) const + { return std::span{}; } + std::span + CSDLConnector::GetValidDynamicRefreshRates( ) const + { return std::span{}; } + + void CSDLConnector::GetNativeColorimetry( + bool bHDR10, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const { - if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD) - SDL_SetClipboardText(szContents->c_str()); - else if (eSelection == GAMESCOPE_SELECTION_PRIMARY) - SDL_SetPrimarySelectionText(szContents->c_str()); + if ( g_bForceHDR10OutputDebug ) + { + *displayColorimetry = displaycolorimetry_2020; + *displayEOTF = EOTF_PQ; + *outputEncodingColorimetry = displaycolorimetry_2020; + *outputEncodingEOTF = EOTF_PQ; + } + else + { + *displayColorimetry = displaycolorimetry_709; + *displayEOTF = EOTF_Gamma22; + *outputEncodingColorimetry = displaycolorimetry_709; + *outputEncodingEOTF = EOTF_Gamma22; + } } - //////////////// - // CSDLBackend - //////////////// - - CSDLBackend::CSDLBackend() - : m_Connector{ this } - , m_SDLThread{ [this](){ this->SDLThreadFunc(); } } - { - } - - bool CSDLBackend::Init() - { - m_eSDLInit.wait( SDLInitState::SDLInit_Waiting ); - - return m_eSDLInit == SDLInitState::SDLInit_Success; - } - - bool CSDLBackend::PostInit() - { - return true; - } - - std::span CSDLBackend::GetInstanceExtensions() const - { - return std::span{ m_pszInstanceExtensions.begin(), m_pszInstanceExtensions.end() }; - } - - std::span CSDLBackend::GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const - { - return std::span{}; - } - - VkImageLayout CSDLBackend::GetPresentLayout() const - { - return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - } - - void CSDLBackend::GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const - { - *pPrimaryPlaneFormat = VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); - *pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); - } - - bool CSDLBackend::ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const - { - return true; - } - - void CSDLBackend::DirtyState( bool bForce, bool bForceModeset ) - { - } - bool CSDLBackend::PollState() - { - return false; - } - - std::shared_ptr CSDLBackend::CreateBackendBlob( const std::type_info &type, std::span data ) - { - return std::make_shared( data ); - } - - OwningRc CSDLBackend::ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) - { - return new CBaseBackendFb(); - } - - bool CSDLBackend::UsesModifiers() const - { - return false; - } - std::span CSDLBackend::GetSupportedModifiers( uint32_t uDrmFormat ) const - { - return std::span{}; - } - - IBackendConnector *CSDLBackend::GetCurrentConnector() - { - return &m_Connector; - } - IBackendConnector *CSDLBackend::GetConnector( GamescopeScreenType eScreenType ) - { - if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - return &m_Connector; - - return nullptr; - } - - bool CSDLBackend::SupportsPlaneHardwareCursor() const - { - // We use the nested hints cursor stuff. - // Not our own plane. - return false; - } - - bool CSDLBackend::SupportsTearing() const - { - return false; - } - bool CSDLBackend::UsesVulkanSwapchain() const - { - return true; - } - - bool CSDLBackend::IsSessionBased() const - { - return false; - } - - bool CSDLBackend::SupportsExplicitSync() const - { - // We use a Vulkan swapchain, so yes. - return true; - } - - bool CSDLBackend::IsPaused() const - { - return false; - } - - bool CSDLBackend::IsVisible() const - { - return true; - } - - glm::uvec2 CSDLBackend::CursorSurfaceSize( glm::uvec2 uvecSize ) const - { - return uvecSize; - } - - /////////////////// - // INestedHints - /////////////////// - - void CSDLBackend::SetCursorImage( std::shared_ptr info ) - { - m_pApplicationCursor = info; - PushUserEvent( GAMESCOPE_SDL_EVENT_CURSOR ); - } - void CSDLBackend::SetRelativeMouseMode( bool bRelative ) - { - m_bApplicationGrabbed = bRelative; - PushUserEvent( GAMESCOPE_SDL_EVENT_GRAB ); - } - void CSDLBackend::SetVisible( bool bVisible ) - { - m_bApplicationVisible = bVisible; - PushUserEvent( GAMESCOPE_SDL_EVENT_VISIBLE ); - } - void CSDLBackend::SetTitle( std::shared_ptr szTitle ) - { - m_pApplicationTitle = szTitle; - PushUserEvent( GAMESCOPE_SDL_EVENT_TITLE ); - } - void CSDLBackend::SetIcon( std::shared_ptr> uIconPixels ) - { - m_pApplicationIcon = uIconPixels; - PushUserEvent( GAMESCOPE_SDL_EVENT_ICON ); - } - - void CSDLBackend::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) - { - if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD) - SDL_SetClipboardText(szContents->c_str()); - else if (eSelection == GAMESCOPE_SELECTION_PRIMARY) - SDL_SetPrimarySelectionText(szContents->c_str()); - } - - void CSDLBackend::OnBackendBlobDestroyed( BackendBlob *pBlob ) - { - // Do nothing. - } - - void CSDLBackend::SDLThreadFunc() - { - pthread_setname_np( pthread_self(), "gamescope-sdl" ); - - m_uUserEventIdBase = SDL_RegisterEvents( GAMESCOPE_SDL_EVENT_COUNT ); - - SDL_SetHint( SDL_HINT_APP_NAME, "Gamescope" ); - SDL_SetHint( SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1" ); - - if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS ) != 0 ) - { - m_eSDLInit = SDLInitState::SDLInit_Failure; - m_eSDLInit.notify_all(); - return; - } + int CSDLConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) + { + // TODO: Resolve const crap + std::optional oCompositeResult = + vulkan_composite( ( FrameInfo_t * )pFrameInfo, nullptr, false ); + if ( !oCompositeResult ) return -EINVAL; - if ( SDL_Vulkan_LoadLibrary( nullptr ) != 0 ) - { - fprintf(stderr, "SDL_Vulkan_LoadLibrary failed: %s\n", SDL_GetError()); - m_eSDLInit = SDLInitState::SDLInit_Failure; - m_eSDLInit.notify_all(); - return; - } - - unsigned int uExtCount = 0; - SDL_Vulkan_GetInstanceExtensions( nullptr, &uExtCount, nullptr ); - m_pszInstanceExtensions.resize( uExtCount ); - SDL_Vulkan_GetInstanceExtensions( nullptr, &uExtCount, m_pszInstanceExtensions.data() ); - - if ( !m_Connector.Init() ) - { - m_eSDLInit = SDLInitState::SDLInit_Failure; - m_eSDLInit.notify_all(); - return; - } + vulkan_present_to_window( ); - if ( !vulkan_init( vulkan_get_instance(), m_Connector.GetVulkanSurface() ) ) - { - m_eSDLInit = SDLInitState::SDLInit_Failure; - m_eSDLInit.notify_all(); - return; - } + // TODO: Hook up PresentationFeedback. - if ( !wlsession_init() ) - { - fprintf( stderr, "Failed to initialize Wayland session\n" ); - m_eSDLInit = SDLInitState::SDLInit_Failure; - m_eSDLInit.notify_all(); - return; - } + // Wait for the composite result on our side *after* we + // commit the buffer to the compositor to avoid a bubble. + vulkan_wait( *oCompositeResult, true ); - // Update g_nOutputWidthPts. - { - int width, height; - SDL_GetWindowSize( m_Connector.GetSDLWindow(), &width, &height ); - g_nOutputWidthPts = width; - g_nOutputHeightPts = height; - - #if SDL_VERSION_ATLEAST(2, 26, 0) - SDL_GetWindowSizeInPixels( m_Connector.GetSDLWindow(), &width, &height ); - #endif - g_nOutputWidth = width; - g_nOutputHeight = height; - } - - if ( g_bForceRelativeMouse ) - { - SDL_SetRelativeMouseMode( SDL_TRUE ); - m_bApplicationGrabbed = true; - } + GetVBlankTimer( ).UpdateWasCompositing( true ); + GetVBlankTimer( ).UpdateLastDrawTime( + get_time_in_nanos( ) - g_SteamCompMgrVBlankTime.ulWakeupTime ); + + return 0; + } + + void CSDLConnector::SetCursorImage( + std::shared_ptr info ) + { m_pBackend->SetCursorImage( std::move( info ) ); } + void CSDLConnector::SetRelativeMouseMode( bool bRelative ) + { m_pBackend->SetRelativeMouseMode( bRelative ); } + void CSDLConnector::SetVisible( bool bVisible ) + { m_pBackend->SetVisible( bVisible ); } + void CSDLConnector::SetTitle( std::shared_ptr szTitle ) + { m_pBackend->SetTitle( std::move( szTitle ) ); } + void + CSDLConnector::SetIcon( std::shared_ptr> uIconPixels ) + { m_pBackend->SetIcon( std::move( uIconPixels ) ); } + + void CSDLConnector::SetSelection( + std::shared_ptr szContents, GamescopeSelection eSelection ) + { + if ( eSelection == GAMESCOPE_SELECTION_CLIPBOARD ) + SDL_SetClipboardText( szContents->c_str( ) ); + else if ( eSelection == GAMESCOPE_SELECTION_PRIMARY ) + SDL_SetPrimarySelectionText( szContents->c_str( ) ); + } - SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" ); + //////////////// + // CSDLBackend + //////////////// - g_nOldNestedRefresh = g_nNestedRefresh; + CSDLBackend::CSDLBackend( ) : + m_Connector{ this }, + m_SDLThread{ [ this ]( ) { this->SDLThreadFunc( ); } } + {} - m_eSDLInit = SDLInitState::SDLInit_Success; - m_eSDLInit.notify_all(); + bool CSDLBackend::Init( ) + { + m_eSDLInit.wait( SDLInitState::SDLInit_Waiting ); - static uint32_t fake_timestamp = 0; + return m_eSDLInit == SDLInitState::SDLInit_Success; + } - wlserver.bWaylandServerRunning.wait( false ); + bool CSDLBackend::PostInit( ) { return true; } - SDL_Event event; - while( SDL_WaitEvent( &event ) ) - { - fake_timestamp++; + std::span CSDLBackend::GetInstanceExtensions( ) const + { + return std::span{ m_pszInstanceExtensions.begin( ), + m_pszInstanceExtensions.end( ) }; + } - switch( event.type ) - { - case SDL_CLIPBOARDUPDATE: - { - char *pClipBoard = SDL_GetClipboardText(); - char *pPrimarySelection = SDL_GetPrimarySelectionText(); - - gamescope_set_selection(pClipBoard, GAMESCOPE_SELECTION_CLIPBOARD); - gamescope_set_selection(pPrimarySelection, GAMESCOPE_SELECTION_PRIMARY); - - SDL_free(pClipBoard); - SDL_free(pPrimarySelection); - } - break; - - case SDL_MOUSEMOTION: - { - if ( m_bApplicationGrabbed ) - { - if ( g_bWindowFocused ) - { - wlserver_lock(); - wlserver_mousemotion( event.motion.xrel, event.motion.yrel, fake_timestamp ); - wlserver_unlock(); - } - } - else - { - wlserver_lock(); - wlserver_touchmotion( - event.motion.x / float(g_nOutputWidthPts), - event.motion.y / float(g_nOutputHeightPts), - 0, - fake_timestamp ); - wlserver_unlock(); - } - } - break; - - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - { - wlserver_lock(); - wlserver_mousebutton( SDLButtonToLinuxButton( event.button.button ), - event.button.state == SDL_PRESSED, - fake_timestamp ); - wlserver_unlock(); - } - break; - - case SDL_MOUSEWHEEL: - { - wlserver_lock(); - wlserver_mousewheel( -event.wheel.x, -event.wheel.y, fake_timestamp ); - wlserver_unlock(); - } - break; - - case SDL_FINGERMOTION: - { - wlserver_lock(); - wlserver_touchmotion( event.tfinger.x, event.tfinger.y, event.tfinger.fingerId, fake_timestamp ); - wlserver_unlock(); - } - break; - - case SDL_FINGERDOWN: - { - wlserver_lock(); - wlserver_touchdown( event.tfinger.x, event.tfinger.y, event.tfinger.fingerId, fake_timestamp ); - wlserver_unlock(); - } - break; - - case SDL_FINGERUP: - { - wlserver_lock(); - wlserver_touchup( event.tfinger.fingerId, fake_timestamp ); - wlserver_unlock(); - } - break; - - case SDL_KEYDOWN: - { - // If this keydown event is super + one of the shortcut keys, consume the keydown event, since the corresponding keyup - // event will be consumed by the next case statement when the user releases the key - if ( event.key.keysym.mod & KMOD_LGUI ) - { - uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); - const uint32_t shortcutKeys[] = {KEY_F, KEY_N, KEY_B, KEY_U, KEY_Y, KEY_I, KEY_O, KEY_S, KEY_G}; - const bool isShortcutKey = std::find(std::begin(shortcutKeys), std::end(shortcutKeys), key) != std::end(shortcutKeys); - if ( isShortcutKey ) - { - break; - } - } - } - [[fallthrough]]; - case SDL_KEYUP: - { - uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); - - if ( event.type == SDL_KEYUP && ( event.key.keysym.mod & KMOD_LGUI ) ) - { - bool handled = true; - switch ( key ) - { - case KEY_F: - g_bFullscreen = !g_bFullscreen; - SDL_SetWindowFullscreen( m_Connector.GetSDLWindow(), g_bFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 ); - break; - case KEY_N: - g_wantedUpscaleFilter = GamescopeUpscaleFilter::PIXEL; - break; - case KEY_B: - g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; - break; - case KEY_U: - g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::FSR) ? - GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::FSR; - break; - case KEY_Y: - g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::NIS) ? - GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NIS; - break; - case KEY_I: - g_upscaleFilterSharpness = std::min(20, g_upscaleFilterSharpness + 1); - break; - case KEY_O: - g_upscaleFilterSharpness = std::max(0, g_upscaleFilterSharpness - 1); - break; - case KEY_S: - gamescope::CScreenshotManager::Get().TakeScreenshot( true ); - break; - case KEY_G: - g_bGrabbed = !g_bGrabbed; - SDL_SetWindowKeyboardGrab( m_Connector.GetSDLWindow(), g_bGrabbed ? SDL_TRUE : SDL_FALSE ); - - SDL_Event event; - event.type = GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE ); - SDL_PushEvent( &event ); - break; - default: - handled = false; - } - if ( handled ) - { - break; - } - } - - // On Wayland, clients handle key repetition - if ( event.key.repeat ) - break; - - wlserver_lock(); - wlserver_key( key, event.type == SDL_KEYDOWN, fake_timestamp ); - wlserver_unlock(); - } - break; - - case SDL_WINDOWEVENT: - { - switch( event.window.event ) - { - case SDL_WINDOWEVENT_CLOSE: - raise( SIGTERM ); - break; - default: - break; - case SDL_WINDOWEVENT_SIZE_CHANGED: - int width, height; - SDL_GetWindowSize( m_Connector.GetSDLWindow(), &width, &height ); - g_nOutputWidthPts = width; - g_nOutputHeightPts = height; - -#if SDL_VERSION_ATLEAST(2, 26, 0) - SDL_GetWindowSizeInPixels( m_Connector.GetSDLWindow(), &width, &height ); + std::span + CSDLBackend::GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const + { return std::span{}; } + + VkImageLayout CSDLBackend::GetPresentLayout( ) const + { return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; } + + void CSDLBackend::GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const + { + *pPrimaryPlaneFormat = + VulkanFormatToDRM( VK_FORMAT_A2B10G10R10_UNORM_PACK32 ); + *pOverlayPlaneFormat = VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ); + } + + bool + CSDLBackend::ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const + { return true; } + + void CSDLBackend::DirtyState( bool bForce, bool bForceModeset ) {} + bool CSDLBackend::PollState( ) { return false; } + + std::shared_ptr CSDLBackend::CreateBackendBlob( + const std::type_info &type, std::span data ) + { return std::make_shared( data ); } + + OwningRc + CSDLBackend::ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) + { return new CBaseBackendFb( ); } + + bool CSDLBackend::UsesModifiers( ) const { return false; } + std::span + CSDLBackend::GetSupportedModifiers( uint32_t uDrmFormat ) const + { return std::span{}; } + + IBackendConnector *CSDLBackend::GetCurrentConnector( ) + { return &m_Connector; } + IBackendConnector * + CSDLBackend::GetConnector( GamescopeScreenType eScreenType ) + { + if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) + return &m_Connector; + + return nullptr; + } + + bool CSDLBackend::SupportsPlaneHardwareCursor( ) const + { + // We use the nested hints cursor stuff. + // Not our own plane. + return false; + } + + bool CSDLBackend::SupportsTearing( ) const { return false; } + bool CSDLBackend::UsesVulkanSwapchain( ) const { return true; } + + bool CSDLBackend::IsSessionBased( ) const { return false; } + + bool CSDLBackend::SupportsExplicitSync( ) const + { + // We use a Vulkan swapchain, so yes. + return true; + } + + bool CSDLBackend::IsPaused( ) const { return false; } + + bool CSDLBackend::IsVisible( ) const { return true; } + + glm::uvec2 CSDLBackend::CursorSurfaceSize( glm::uvec2 uvecSize ) const + { return uvecSize; } + + /////////////////// + // INestedHints + /////////////////// + + void CSDLBackend::SetCursorImage( + std::shared_ptr info ) + { + m_pApplicationCursor = info; + PushUserEvent( GAMESCOPE_SDL_EVENT_CURSOR ); + } + void CSDLBackend::SetRelativeMouseMode( bool bRelative ) + { + m_bApplicationGrabbed = bRelative; + PushUserEvent( GAMESCOPE_SDL_EVENT_GRAB ); + } + void CSDLBackend::SetVisible( bool bVisible ) + { + m_bApplicationVisible = bVisible; + PushUserEvent( GAMESCOPE_SDL_EVENT_VISIBLE ); + } + void CSDLBackend::SetTitle( std::shared_ptr szTitle ) + { + m_pApplicationTitle = szTitle; + PushUserEvent( GAMESCOPE_SDL_EVENT_TITLE ); + } + void + CSDLBackend::SetIcon( std::shared_ptr> uIconPixels ) + { + m_pApplicationIcon = uIconPixels; + PushUserEvent( GAMESCOPE_SDL_EVENT_ICON ); + } + + void CSDLBackend::SetSelection( + std::shared_ptr szContents, GamescopeSelection eSelection ) + { + if ( eSelection == GAMESCOPE_SELECTION_CLIPBOARD ) + SDL_SetClipboardText( szContents->c_str( ) ); + else if ( eSelection == GAMESCOPE_SELECTION_PRIMARY ) + SDL_SetPrimarySelectionText( szContents->c_str( ) ); + } + + void CSDLBackend::OnBackendBlobDestroyed( BackendBlob *pBlob ) + { + // Do nothing. + } + + void CSDLBackend::SDLThreadFunc( ) + { + pthread_setname_np( pthread_self( ), "gamescope-sdl" ); + + m_uUserEventIdBase = SDL_RegisterEvents( GAMESCOPE_SDL_EVENT_COUNT ); + + SDL_SetHint( SDL_HINT_APP_NAME, "Gamescope" ); + SDL_SetHint( SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1" ); + + if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS ) != 0 ) + { + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all( ); + return; + } + + if ( SDL_Vulkan_LoadLibrary( nullptr ) != 0 ) + { + fprintf( + stderr, + "SDL_Vulkan_LoadLibrary failed: %s\n", + SDL_GetError( ) ); + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all( ); + return; + } + + unsigned int uExtCount = 0; + SDL_Vulkan_GetInstanceExtensions( nullptr, &uExtCount, nullptr ); + m_pszInstanceExtensions.resize( uExtCount ); + SDL_Vulkan_GetInstanceExtensions( + nullptr, &uExtCount, m_pszInstanceExtensions.data( ) ); + + if ( !m_Connector.Init( ) ) + { + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all( ); + return; + } + + if ( !vulkan_init( + vulkan_get_instance( ), m_Connector.GetVulkanSurface( ) ) ) + { + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all( ); + return; + } + + if ( !wlsession_init( ) ) + { + fprintf( stderr, "Failed to initialize Wayland session\n" ); + m_eSDLInit = SDLInitState::SDLInit_Failure; + m_eSDLInit.notify_all( ); + return; + } + + // Update g_nOutputWidthPts. + { + int width, height; + SDL_GetWindowSize( m_Connector.GetSDLWindow( ), &width, &height ); + g_nOutputWidthPts = width; + g_nOutputHeightPts = height; + +#if SDL_VERSION_ATLEAST( 2, 26, 0 ) + SDL_GetWindowSizeInPixels( + m_Connector.GetSDLWindow( ), &width, &height ); #endif - g_nOutputWidth = width; - g_nOutputHeight = height; - - [[fallthrough]]; - case SDL_WINDOWEVENT_MOVED: - case SDL_WINDOWEVENT_SHOWN: - { - int display_index = 0; - SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 }; - - display_index = SDL_GetWindowDisplayIndex( m_Connector.GetSDLWindow() ); - if ( SDL_GetDesktopDisplayMode( display_index, &mode ) == 0 ) - { - g_nOutputRefresh = ConvertHztomHz( mode.refresh_rate ); - } - } - break; - case SDL_WINDOWEVENT_FOCUS_LOST: - g_nNestedRefresh = g_nNestedUnfocusedRefresh; - g_bWindowFocused = false; - break; - case SDL_WINDOWEVENT_FOCUS_GAINED: - g_nNestedRefresh = g_nOldNestedRefresh; - g_bWindowFocused = true; - break; - case SDL_WINDOWEVENT_EXPOSED: - force_repaint(); - break; - } - } - break; - - default: - { - if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_VISIBLE ) ) - { - bool bVisible = m_bApplicationVisible; - - // If we are Steam Mode in nested, show the window - // whenever we have had a first frame to match - // what we do in embedded with Steam for testing - // held commits, etc. - if ( steamMode ) - bVisible |= !g_bFirstFrame; - - if ( m_bShown != bVisible ) - { - m_bShown = bVisible; - - if ( m_bShown ) - { - SDL_ShowWindow( m_Connector.GetSDLWindow() ); - } - else - { - SDL_HideWindow( m_Connector.GetSDLWindow() ); - } - } - } - else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE ) ) - { - std::shared_ptr pAppTitle = m_pApplicationTitle; - - std::string szTitle = pAppTitle ? *pAppTitle : "gamescope"; - if ( g_bGrabbed ) - szTitle += " (grabbed)"; - SDL_SetWindowTitle( m_Connector.GetSDLWindow(), szTitle.c_str() ); - - szTitle = "Title: " + szTitle; - SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, szTitle.c_str() ); - SDL_DisableScreenSaver(); - } - else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_ICON ) ) - { - std::shared_ptr> pIcon = m_pApplicationIcon; - - if ( m_pIconSurface ) - { - SDL_FreeSurface( m_pIconSurface ); - m_pIconSurface = nullptr; - } - - if ( pIcon && pIcon->size() >= 3 ) - { - const uint32_t uWidth = (*pIcon)[0]; - const uint32_t uHeight = (*pIcon)[1]; - - m_pIconSurface = SDL_CreateRGBSurfaceFrom( - &(*pIcon)[2], - uWidth, uHeight, - 32, uWidth * sizeof(uint32_t), - 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); - } - - SDL_SetWindowIcon( m_Connector.GetSDLWindow(), m_pIconSurface ); - } - else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_GRAB ) ) - { - SDL_SetRelativeMouseMode( m_bApplicationGrabbed ? SDL_TRUE : SDL_FALSE ); - } - else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_CURSOR ) ) - { - std::shared_ptr pCursorInfo = m_pApplicationCursor; - - if ( m_pCursorSurface ) - { - SDL_FreeSurface( m_pCursorSurface ); - m_pCursorSurface = nullptr; - } - - if ( m_pCursor ) - { - SDL_FreeCursor( m_pCursor ); - m_pCursor = nullptr; - } - - if ( pCursorInfo ) - { - m_pCursorSurface = SDL_CreateRGBSurfaceFrom( - pCursorInfo->pPixels.data(), - pCursorInfo->uWidth, - pCursorInfo->uHeight, - 32, - pCursorInfo->uWidth * sizeof(uint32_t), - 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); - - m_pCursor = SDL_CreateColorCursor( m_pCursorSurface, pCursorInfo->uXHotspot, pCursorInfo->uYHotspot ); - } - - SDL_SetCursor( m_pCursor ); - } - } - break; - } - } - } - - uint32_t CSDLBackend::GetUserEventIndex( SDLCustomEvents eEvent ) const - { - return m_uUserEventIdBase + uint32_t( eEvent ); - } - - void CSDLBackend::PushUserEvent( SDLCustomEvents eEvent ) - { - SDL_Event event = + g_nOutputWidth = width; + g_nOutputHeight = height; + } + + if ( g_bForceRelativeMouse ) + { + SDL_SetRelativeMouseMode( SDL_TRUE ); + m_bApplicationGrabbed = true; + } + + SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" ); + + g_nOldNestedRefresh = g_nNestedRefresh; + + m_eSDLInit = SDLInitState::SDLInit_Success; + m_eSDLInit.notify_all( ); + + static uint32_t fake_timestamp = 0; + + wlserver.bWaylandServerRunning.wait( false ); + + SDL_Event event; + while ( SDL_WaitEvent( &event ) ) + { + fake_timestamp++; + + switch ( event.type ) + { + case SDL_CLIPBOARDUPDATE: + { + char *pClipBoard = SDL_GetClipboardText( ); + char *pPrimarySelection = SDL_GetPrimarySelectionText( ); + + gamescope_set_selection( + pClipBoard, GAMESCOPE_SELECTION_CLIPBOARD ); + gamescope_set_selection( + pPrimarySelection, GAMESCOPE_SELECTION_PRIMARY ); + + SDL_free( pClipBoard ); + SDL_free( pPrimarySelection ); + } + break; + + case SDL_MOUSEMOTION: + { + if ( m_bApplicationGrabbed ) + { + if ( g_bWindowFocused ) + { + wlserver_lock( ); + wlserver_mousemotion( + event.motion.xrel, + event.motion.yrel, + fake_timestamp ); + wlserver_unlock( ); + } + } + else + { + wlserver_lock( ); + wlserver_touchmotion( + event.motion.x / float( g_nOutputWidthPts ), + event.motion.y / float( g_nOutputHeightPts ), + 0, + fake_timestamp ); + wlserver_unlock( ); + } + } + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + wlserver_lock( ); + wlserver_mousebutton( + SDLButtonToLinuxButton( event.button.button ), + event.button.state == SDL_PRESSED, + fake_timestamp ); + wlserver_unlock( ); + } + break; + + case SDL_MOUSEWHEEL: + { + wlserver_lock( ); + wlserver_mousewheel( + -event.wheel.x, -event.wheel.y, fake_timestamp ); + wlserver_unlock( ); + } + break; + + case SDL_FINGERMOTION: + { + wlserver_lock( ); + wlserver_touchmotion( + event.tfinger.x, + event.tfinger.y, + event.tfinger.fingerId, + fake_timestamp ); + wlserver_unlock( ); + } + break; + + case SDL_FINGERDOWN: + { + wlserver_lock( ); + wlserver_touchdown( + event.tfinger.x, + event.tfinger.y, + event.tfinger.fingerId, + fake_timestamp ); + wlserver_unlock( ); + } + break; + + case SDL_FINGERUP: + { + wlserver_lock( ); + wlserver_touchup( event.tfinger.fingerId, fake_timestamp ); + wlserver_unlock( ); + } + break; + + case SDL_KEYDOWN: + { + // If this keydown event is super + one of the shortcut + // keys, consume the keydown event, since the corresponding + // keyup event will be consumed by the next case statement + // when the user releases the key + if ( event.key.keysym.mod & KMOD_LGUI ) + { + uint32_t key = + SDLScancodeToLinuxKey( event.key.keysym.scancode ); + const uint32_t shortcutKeys[] = { KEY_F, KEY_N, KEY_B, + KEY_U, KEY_Y, KEY_I, + KEY_O, KEY_S, KEY_G }; + const bool isShortcutKey = + std::find( + std::begin( shortcutKeys ), + std::end( shortcutKeys ), + key ) != std::end( shortcutKeys ); + if ( isShortcutKey ) { break; } + } + } + [[fallthrough]]; + case SDL_KEYUP: + { + uint32_t key = + SDLScancodeToLinuxKey( event.key.keysym.scancode ); + + if ( event.type == SDL_KEYUP && + ( event.key.keysym.mod & KMOD_LGUI ) ) + { + bool handled = true; + switch ( key ) + { + case KEY_F: + g_bFullscreen = !g_bFullscreen; + SDL_SetWindowFullscreen( + m_Connector.GetSDLWindow( ), + g_bFullscreen + ? SDL_WINDOW_FULLSCREEN_DESKTOP + : 0 ); + break; + case KEY_N: + g_wantedUpscaleFilter = + GamescopeUpscaleFilter::PIXEL; + break; + case KEY_B: + g_wantedUpscaleFilter = + GamescopeUpscaleFilter::LINEAR; + break; + case KEY_U: + g_wantedUpscaleFilter = + ( g_wantedUpscaleFilter == + GamescopeUpscaleFilter::FSR ) + ? GamescopeUpscaleFilter::LINEAR + : GamescopeUpscaleFilter::FSR; + break; + case KEY_Y: + g_wantedUpscaleFilter = + ( g_wantedUpscaleFilter == + GamescopeUpscaleFilter::NIS ) + ? GamescopeUpscaleFilter::LINEAR + : GamescopeUpscaleFilter::NIS; + break; + case KEY_I: + g_upscaleFilterSharpness = std::min( + 20, g_upscaleFilterSharpness + 1 ); + break; + case KEY_O: + g_upscaleFilterSharpness = + std::max( 0, g_upscaleFilterSharpness - 1 ); + break; + case KEY_S: + gamescope::CScreenshotManager::Get( ) + .TakeScreenshot( true ); + break; + case KEY_G: + g_bGrabbed = !g_bGrabbed; + SDL_SetWindowKeyboardGrab( + m_Connector.GetSDLWindow( ), + g_bGrabbed ? SDL_TRUE : SDL_FALSE ); + + SDL_Event event; + event.type = GetUserEventIndex( + GAMESCOPE_SDL_EVENT_TITLE ); + SDL_PushEvent( &event ); + break; + default: + handled = false; + } + if ( handled ) { break; } + } + + // On Wayland, clients handle key repetition + if ( event.key.repeat ) break; + + wlserver_lock( ); + wlserver_key( + key, event.type == SDL_KEYDOWN, fake_timestamp ); + wlserver_unlock( ); + } + break; + + case SDL_WINDOWEVENT: + { + switch ( event.window.event ) + { + case SDL_WINDOWEVENT_CLOSE: + raise( SIGTERM ); + break; + default: + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + int width, height; + SDL_GetWindowSize( + m_Connector.GetSDLWindow( ), &width, &height ); + g_nOutputWidthPts = width; + g_nOutputHeightPts = height; + +#if SDL_VERSION_ATLEAST( 2, 26, 0 ) + SDL_GetWindowSizeInPixels( + m_Connector.GetSDLWindow( ), &width, &height ); +#endif + g_nOutputWidth = width; + g_nOutputHeight = height; + + [[fallthrough]]; + case SDL_WINDOWEVENT_MOVED: + case SDL_WINDOWEVENT_SHOWN: + { + int display_index = 0; + SDL_DisplayMode mode = { + SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 + }; + + display_index = SDL_GetWindowDisplayIndex( + m_Connector.GetSDLWindow( ) ); + if ( SDL_GetDesktopDisplayMode( + display_index, &mode ) == 0 ) + { + g_nOutputRefresh = + ConvertHztomHz( mode.refresh_rate ); + } + } + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + g_nNestedRefresh = g_nNestedUnfocusedRefresh; + g_bWindowFocused = false; + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + g_nNestedRefresh = g_nOldNestedRefresh; + g_bWindowFocused = true; + break; + case SDL_WINDOWEVENT_EXPOSED: + force_repaint( ); + break; + } + } + break; + + default: + { + if ( event.type == + GetUserEventIndex( GAMESCOPE_SDL_EVENT_VISIBLE ) ) + { + bool bVisible = m_bApplicationVisible; + + // If we are Steam Mode in nested, show the window + // whenever we have had a first frame to match + // what we do in embedded with Steam for testing + // held commits, etc. + if ( steamMode ) bVisible |= !g_bFirstFrame; + + if ( m_bShown != bVisible ) + { + m_bShown = bVisible; + + if ( m_bShown ) + { + SDL_ShowWindow( m_Connector.GetSDLWindow( ) ); + } + else + { + SDL_HideWindow( m_Connector.GetSDLWindow( ) ); + } + } + } + else if ( + event.type == + GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE ) ) + { + std::shared_ptr pAppTitle = + m_pApplicationTitle; + + std::string szTitle = + pAppTitle ? *pAppTitle : "gamescope"; + if ( g_bGrabbed ) szTitle += " (grabbed)"; + SDL_SetWindowTitle( + m_Connector.GetSDLWindow( ), szTitle.c_str( ) ); + + szTitle = "Title: " + szTitle; + SDL_SetHint( + SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, + szTitle.c_str( ) ); + SDL_DisableScreenSaver( ); + } + else if ( + event.type == + GetUserEventIndex( GAMESCOPE_SDL_EVENT_ICON ) ) + { + std::shared_ptr> pIcon = + m_pApplicationIcon; + + if ( m_pIconSurface ) + { + SDL_FreeSurface( m_pIconSurface ); + m_pIconSurface = nullptr; + } + + if ( pIcon && pIcon->size( ) >= 3 ) + { + const uint32_t uWidth = ( *pIcon )[ 0 ]; + const uint32_t uHeight = ( *pIcon )[ 1 ]; + + m_pIconSurface = SDL_CreateRGBSurfaceFrom( + &( *pIcon )[ 2 ], + uWidth, + uHeight, + 32, + uWidth * sizeof( uint32_t ), + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000 ); + } + + SDL_SetWindowIcon( + m_Connector.GetSDLWindow( ), m_pIconSurface ); + } + else if ( + event.type == + GetUserEventIndex( GAMESCOPE_SDL_EVENT_GRAB ) ) + { + SDL_SetRelativeMouseMode( + m_bApplicationGrabbed ? SDL_TRUE : SDL_FALSE ); + } + else if ( + event.type == + GetUserEventIndex( GAMESCOPE_SDL_EVENT_CURSOR ) ) + { + std::shared_ptr pCursorInfo = + m_pApplicationCursor; + + if ( m_pCursorSurface ) + { + SDL_FreeSurface( m_pCursorSurface ); + m_pCursorSurface = nullptr; + } + + if ( m_pCursor ) + { + SDL_FreeCursor( m_pCursor ); + m_pCursor = nullptr; + } + + if ( pCursorInfo ) + { + m_pCursorSurface = SDL_CreateRGBSurfaceFrom( + pCursorInfo->pPixels.data( ), + pCursorInfo->uWidth, + pCursorInfo->uHeight, + 32, + pCursorInfo->uWidth * sizeof( uint32_t ), + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000 ); + + m_pCursor = SDL_CreateColorCursor( + m_pCursorSurface, + pCursorInfo->uXHotspot, + pCursorInfo->uYHotspot ); + } + + SDL_SetCursor( m_pCursor ); + } + } + break; + } + } + } + + uint32_t CSDLBackend::GetUserEventIndex( SDLCustomEvents eEvent ) const + { return m_uUserEventIdBase + uint32_t( eEvent ); } + + void CSDLBackend::PushUserEvent( SDLCustomEvents eEvent ) + { + SDL_Event event = { .user = { .type = GetUserEventIndex( eEvent ), }, }; - SDL_PushEvent( &event ); - } - - ///////////////////////// - // Backend Instantiator - ///////////////////////// - - template <> - bool IBackend::Set() - { - return Set( new CSDLBackend{} ); - } -} + SDL_PushEvent( &event ); + } + + ///////////////////////// + // Backend Instantiator + ///////////////////////// + + template<> bool IBackend::Set( ) + { return Set( new CSDLBackend{} ); } +} // namespace gamescope diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index abdf7820b0..7f443cdb32 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -1,129 +1,158 @@ -#include "backend.h" -#include "rendervulkan.hpp" -#include "wlserver.hpp" -#include "vblankmanager.hpp" -#include "steamcompmgr.hpp" -#include "edid.h" -#include "Utils/Defer.h" #include "Utils/Algorithm.h" +#include "Utils/Defer.h" +#include "Utils/TempFiles.h" +#include "backend.h" #include "convar.h" +#include "edid.h" #include "refresh_rate.h" +#include "rendervulkan.hpp" +#include "steamcompmgr.hpp" +#include "vblankmanager.hpp" #include "waitable.h" -#include "Utils/TempFiles.h" +#include "wlserver.hpp" +#include #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include -#include -#include "wlr_begin.hpp" -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include #include -#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include "wlr_begin.hpp" #include "wlr_end.hpp" #include "drm_include.h" #define WL_FRACTIONAL_SCALE_DENOMINATOR 120 -extern int g_nPreferredOutputWidth; -extern int g_nPreferredOutputHeight; -extern bool g_bForceHDR10OutputDebug; -extern bool g_bBorderlessOutputWindow; +extern int g_nPreferredOutputWidth; +extern int g_nPreferredOutputHeight; +extern bool g_bForceHDR10OutputDebug; +extern bool g_bBorderlessOutputWindow; extern gamescope::ConVar cv_adaptive_sync; extern gamescope::ConVar cv_composite_force; -extern bool g_bColorSliderInUse; -extern bool fadingOut; -extern std::string g_reshade_effect; +extern bool g_bColorSliderInUse; +extern bool fadingOut; +extern std::string g_reshade_effect; using namespace std::literals; static LogScope xdg_log( "xdg_backend" ); -static const char *GAMESCOPE_proxy_tag = "gamescope-proxy"; -static const char *GAMESCOPE_plane_tag = "gamescope-plane"; +static const char *GAMESCOPE_proxy_tag = "gamescope-proxy"; +static const char *GAMESCOPE_plane_tag = "gamescope-plane"; static const char *GAMESCOPE_toplevel_tag = "gamescope-toplevel"; -template -auto CallWithAllButLast(Func pFunc, Args&&... args) +template +auto CallWithAllButLast( Func pFunc, Args &&...args ) { - auto Forwarder = [&] (Tuple&& tuple, std::index_sequence) - { - return pFunc(std::get(std::forward(tuple))...); - }; - return Forwarder(std::forward_as_tuple(args...), std::make_index_sequence()); + auto Forwarder = [ & ]( + Tuple &&tuple, std::index_sequence ) + { return pFunc( std::get( std::forward( tuple ) )... ); }; + return Forwarder( + std::forward_as_tuple( args... ), + std::make_index_sequence( ) ); } -static inline uint32_t WaylandScaleToPhysical( uint32_t pValue, uint32_t pFactor ) { - return pValue * pFactor / WL_FRACTIONAL_SCALE_DENOMINATOR; -} -static inline uint32_t WaylandScaleToLogical( uint32_t pValue, uint32_t pFactor ) { - return div_roundup( pValue * WL_FRACTIONAL_SCALE_DENOMINATOR, pFactor ); -} +static inline uint32_t +WaylandScaleToPhysical( uint32_t pValue, uint32_t pFactor ) +{ return pValue * pFactor / WL_FRACTIONAL_SCALE_DENOMINATOR; } +static inline uint32_t +WaylandScaleToLogical( uint32_t pValue, uint32_t pFactor ) +{ return div_roundup( pValue * WL_FRACTIONAL_SCALE_DENOMINATOR, pFactor ); } -[[maybe_unused]] static bool IsGamescopeProxy( void *pProxy ) { - // HACK: this probably should never be called with a null pointer, but it - // was happening after a window was closed. - if ( !pProxy ) - return false; +[[maybe_unused]] static bool IsGamescopeProxy( void *pProxy ) +{ + // HACK: this probably should never be called with a null pointer, but it + // was happening after a window was closed. + if ( !pProxy ) return false; - const char* const* pTag = wl_proxy_get_tag( (wl_proxy *)pProxy ); + const char *const *pTag = wl_proxy_get_tag( ( wl_proxy * )pProxy ); - return pTag == &GAMESCOPE_proxy_tag || - pTag == &GAMESCOPE_plane_tag || - pTag == &GAMESCOPE_toplevel_tag; + return pTag == &GAMESCOPE_proxy_tag || pTag == &GAMESCOPE_plane_tag || + pTag == &GAMESCOPE_toplevel_tag; } -[[maybe_unused]] static bool IsGamescopePlane( wl_surface *pSurface ) { - // HACK: this probably should never be called with a null pointer, but it - // was happening after a window was closed. - if ( !pSurface ) - return false; - const char* const* pTag = wl_proxy_get_tag( (wl_proxy *)pSurface ); +[[maybe_unused]] static bool IsGamescopePlane( wl_surface *pSurface ) +{ + // HACK: this probably should never be called with a null pointer, but it + // was happening after a window was closed. + if ( !pSurface ) return false; + const char *const *pTag = wl_proxy_get_tag( ( wl_proxy * )pSurface ); - return pTag == &GAMESCOPE_plane_tag || - pTag == &GAMESCOPE_toplevel_tag; + return pTag == &GAMESCOPE_plane_tag || pTag == &GAMESCOPE_toplevel_tag; } -static bool IsGamescopeToplevel( wl_surface *pSurface ) { - // HACK: this probably should never be called with a null pointer, but it - // was happening after a window was closed. - return pSurface && (wl_proxy_get_tag( (wl_proxy *)pSurface ) == &GAMESCOPE_toplevel_tag); +static bool IsGamescopeToplevel( wl_surface *pSurface ) +{ + // HACK: this probably should never be called with a null pointer, but it + // was happening after a window was closed. + return pSurface && ( wl_proxy_get_tag( ( wl_proxy * )pSurface ) == + &GAMESCOPE_toplevel_tag ); } -#define WAYLAND_NULL() [] ( void *pData, Args... args ) { } -#define WAYLAND_USERDATA_TO_THIS(type, name) [] ( void *pData, Args... args ) { type *pThing = (type *)pData; pThing->name( std::forward(args)... ); } +#define WAYLAND_NULL( ) []( void *pData, Args... args ) {} +#define WAYLAND_USERDATA_TO_THIS( type, name ) \ + []( void *pData, Args... args ) \ + { \ + type *pThing = ( type * )pData; \ + pThing->name( std::forward( args )... ); \ + } -// Libdecor puts its userdata ptr at the end... how fun! I shouldn't have spent so long writing this total atrocity to mankind. -#define LIBDECOR_USERDATA_TO_THIS(type, name) [] ( Args... args ) { type *pThing = (type *)std::get(std::forward_as_tuple(args...)); CallWithAllButLast([&](Args2... args2){ pThing->name(std::forward(args2)...); }, std::forward(args)...); } +// Libdecor puts its userdata ptr at the end... how fun! I shouldn't have spent +// so long writing this total atrocity to mankind. +#define LIBDECOR_USERDATA_TO_THIS( type, name ) \ + []( Args... args ) \ + { \ + type *pThing = ( type * )std::get( \ + std::forward_as_tuple( args... ) ); \ + CallWithAllButLast( \ + [ & ]( Args2... args2 ) \ + { pThing->name( std::forward( args2 )... ); }, \ + std::forward( args )... ); \ + } extern gamescope::ConVar cv_hdr_enabled; namespace gamescope { - extern std::shared_ptr GetX11HostCursor(); - - gamescope::ConVar cv_wayland_mouse_warp_without_keyboard_focus( "wayland_mouse_warp_without_keyboard_focus", true, "Should we only forward mouse warps to the app when we have keyboard focus?" ); - gamescope::ConVar cv_wayland_mouse_relmotion_without_keyboard_focus( "wayland_mouse_relmotion_without_keyboard_focus", false, "Should we only forward mouse relative motion to the app when we have keyboard focus?" ); - gamescope::ConVar cv_wayland_use_modifiers( "wayland_use_modifiers", true, "Use DMA-BUF modifiers?" ); - - gamescope::ConVar cv_wayland_hdr10_saturation_scale( "wayland_hdr10_saturation_scale", 1.0, "Saturation scale for HDR10 content by gamut expansion. 1.0 - 1.2 is a good range to play with." ); + extern std::shared_ptr GetX11HostCursor( ); + + gamescope::ConVar cv_wayland_mouse_warp_without_keyboard_focus( + "wayland_mouse_warp_without_keyboard_focus", + true, + "Should we only forward mouse warps to the app when we have keyboard " + "focus?" ); + gamescope::ConVar cv_wayland_mouse_relmotion_without_keyboard_focus( + "wayland_mouse_relmotion_without_keyboard_focus", + false, + "Should we only forward mouse relative motion to the app when we have " + "keyboard focus?" ); + gamescope::ConVar cv_wayland_use_modifiers( + "wayland_use_modifiers", true, "Use DMA-BUF modifiers?" ); + + gamescope::ConVar cv_wayland_hdr10_saturation_scale( + "wayland_hdr10_saturation_scale", + 1.0, + "Saturation scale for HDR10 content by gamut expansion. 1.0 - 1.2 is a " + "good range to play with." ); class CWaylandConnector; class CWaylandPlane; @@ -132,33 +161,42 @@ namespace gamescope struct WaylandPlaneState { - wl_buffer *pBuffer; - int32_t nDestX; - int32_t nDestY; - double flSrcX; - double flSrcY; - double flSrcWidth; - double flSrcHeight; - int32_t nDstWidth; - int32_t nDstHeight; - GamescopeAppTextureColorspace eColorspace; + wl_buffer *pBuffer; + int32_t nDestX; + int32_t nDestY; + double flSrcX; + double flSrcY; + double flSrcWidth; + double flSrcHeight; + int32_t nDstWidth; + int32_t nDstHeight; + GamescopeAppTextureColorspace eColorspace; std::shared_ptr pHDRMetadata; - bool bOpaque; - uint32_t uFractionalScale; + bool bOpaque; + uint32_t uFractionalScale; }; inline WaylandPlaneState ClipPlane( const WaylandPlaneState &state ) { - int32_t nClippedDstWidth = std::min( g_nOutputWidth, state.nDstWidth + state.nDestX ) - state.nDestX; - int32_t nClippedDstHeight = std::min( g_nOutputHeight, state.nDstHeight + state.nDestY ) - state.nDestY; - double flClippedSrcWidth = state.flSrcWidth * ( nClippedDstWidth / double( state.nDstWidth ) ); - double flClippedSrcHeight = state.flSrcHeight * ( nClippedDstHeight / double( state.nDstHeight ) ); + int32_t nClippedDstWidth = + std::min( + g_nOutputWidth, state.nDstWidth + state.nDestX ) - + state.nDestX; + int32_t nClippedDstHeight = + std::min( + g_nOutputHeight, state.nDstHeight + state.nDestY ) - + state.nDestY; + double flClippedSrcWidth = + state.flSrcWidth * ( nClippedDstWidth / double( state.nDstWidth ) ); + double flClippedSrcHeight = + state.flSrcHeight * + ( nClippedDstHeight / double( state.nDstHeight ) ); WaylandPlaneState outState = state; - outState.nDstWidth = nClippedDstWidth; - outState.nDstHeight = nClippedDstHeight; - outState.flSrcWidth = flClippedSrcWidth; - outState.flSrcHeight = flClippedSrcHeight; + outState.nDstWidth = nClippedDstWidth; + outState.nDstHeight = nClippedDstHeight; + outState.flSrcWidth = flClippedSrcWidth; + outState.flSrcHeight = flClippedSrcHeight; return outState; } @@ -166,8 +204,7 @@ namespace gamescope { char szShmBufferPath[ PATH_MAX ]; int nFd = MakeTempFile( szShmBufferPath, k_szGamescopeTempShmTemplate ); - if ( nFd < 0 ) - return -1; + if ( nFd < 0 ) return -1; if ( ftruncate( nFd, uSize ) < 0 ) { @@ -177,9 +214,9 @@ namespace gamescope if ( pData ) { - void *pMappedData = mmap( nullptr, uSize, PROT_READ | PROT_WRITE, MAP_SHARED, nFd, 0 ); - if ( pMappedData == MAP_FAILED ) - return -1; + void *pMappedData = mmap( + nullptr, uSize, PROT_READ | PROT_WRITE, MAP_SHARED, nFd, 0 ); + if ( pMappedData == MAP_FAILED ) return -1; defer( munmap( pMappedData, uSize ) ); memcpy( pMappedData, pData, uSize ); @@ -190,162 +227,242 @@ namespace gamescope struct WaylandPlaneColorState { - GamescopeAppTextureColorspace eColorspace; + GamescopeAppTextureColorspace eColorspace; std::shared_ptr pHDRMetadata; - bool operator ==( const WaylandPlaneColorState &other ) const = default; - bool operator !=( const WaylandPlaneColorState &other ) const = default; + bool operator==( const WaylandPlaneColorState &other ) const = default; + bool operator!=( const WaylandPlaneColorState &other ) const = default; }; class CWaylandPlane { public: CWaylandPlane( CWaylandConnector *pBackend ); - ~CWaylandPlane(); + ~CWaylandPlane( ); bool Init( CWaylandPlane *pParent, CWaylandPlane *pSiblingBelow ); - uint32_t GetScale() const; + uint32_t GetScale( ) const; void Present( std::optional oState ); void Present( const FrameInfo_t::Layer_t *pLayer ); void CommitLibDecor( libdecor_configuration *pConfiguration ); - void Commit(); + void Commit( ); - wl_surface *GetSurface() const { return m_pSurface; } - libdecor_frame *GetFrame() const { return m_pFrame; } - xdg_toplevel *GetXdgToplevel() const; + wl_surface *GetSurface( ) const { return m_pSurface; } + libdecor_frame *GetFrame( ) const { return m_pFrame; } + xdg_toplevel *GetXdgToplevel( ) const; - std::optional GetCurrentState() { std::unique_lock lock( m_PlaneStateLock ); return m_oCurrentPlaneState; } + std::optional GetCurrentState( ) + { + std::unique_lock lock( m_PlaneStateLock ); + return m_oCurrentPlaneState; + } - void UpdateVRRRefreshRate(); + void UpdateVRRRefreshRate( ); private: - void Wayland_Surface_Enter( wl_surface *pSurface, wl_output *pOutput ); void Wayland_Surface_Leave( wl_surface *pSurface, wl_output *pOutput ); static const wl_surface_listener s_SurfaceListener; - void LibDecor_Frame_Configure( libdecor_frame *pFrame, libdecor_configuration *pConfiguration ); + void LibDecor_Frame_Configure( + libdecor_frame *pFrame, libdecor_configuration *pConfiguration ); void LibDecor_Frame_Close( libdecor_frame *pFrame ); void LibDecor_Frame_Commit( libdecor_frame *pFrame ); - void LibDecor_Frame_DismissPopup( libdecor_frame *pFrame, const char *pSeatName ); + void LibDecor_Frame_DismissPopup( + libdecor_frame *pFrame, const char *pSeatName ); static libdecor_frame_interface s_LibDecorFrameInterface; - void Wayland_PresentationFeedback_SyncOutput( struct wp_presentation_feedback *pFeedback, wl_output *pOutput ); - void Wayland_PresentationFeedback_Presented( struct wp_presentation_feedback *pFeedback, uint32_t uTVSecHi, uint32_t uTVSecLo, uint32_t uTVNSec, uint32_t uRefresh, uint32_t uSeqHi, uint32_t uSeqLo, uint32_t uFlags ); - void Wayland_PresentationFeedback_Discarded( struct wp_presentation_feedback *pFeedback ); - static const wp_presentation_feedback_listener s_PresentationFeedbackListener; + void Wayland_PresentationFeedback_SyncOutput( + struct wp_presentation_feedback *pFeedback, wl_output *pOutput ); + void Wayland_PresentationFeedback_Presented( + struct wp_presentation_feedback *pFeedback, + uint32_t uTVSecHi, + uint32_t uTVSecLo, + uint32_t uTVNSec, + uint32_t uRefresh, + uint32_t uSeqHi, + uint32_t uSeqLo, + uint32_t uFlags ); + void Wayland_PresentationFeedback_Discarded( + struct wp_presentation_feedback *pFeedback ); + static const wp_presentation_feedback_listener + s_PresentationFeedbackListener; void Wayland_FrogColorManagedSurface_PreferredMetadata( frog_color_managed_surface *pFrogSurface, - uint32_t uTransferFunction, - uint32_t uOutputDisplayPrimaryRedX, - uint32_t uOutputDisplayPrimaryRedY, - uint32_t uOutputDisplayPrimaryGreenX, - uint32_t uOutputDisplayPrimaryGreenY, - uint32_t uOutputDisplayPrimaryBlueX, - uint32_t uOutputDisplayPrimaryBlueY, - uint32_t uOutputWhitePointX, - uint32_t uOutputWhitePointY, - uint32_t uMaxLuminance, - uint32_t uMinLuminance, - uint32_t uMaxFullFrameLuminance ); - static const frog_color_managed_surface_listener s_FrogColorManagedSurfaceListener; - - void Wayland_WPColorManagementSurfaceFeedback_PreferredChanged( wp_color_management_surface_feedback_v1 *pColorManagementSurface, unsigned int data ); - static const wp_color_management_surface_feedback_v1_listener s_WPColorManagementSurfaceListener; - void UpdateWPPreferredColorManagement(); - - void Wayland_WPImageDescriptionInfo_Done( wp_image_description_info_v1 *pImageDescInfo ); - void Wayland_WPImageDescriptionInfo_ICCFile( wp_image_description_info_v1 *pImageDescInfo, int32_t nICCFd, uint32_t uICCSize ); - void Wayland_WPImageDescriptionInfo_Primaries( wp_image_description_info_v1 *pImageDescInfo, int32_t nRedX, int32_t nRedY, int32_t nGreenX, int32_t nGreenY, int32_t nBlueX, int32_t nBlueY, int32_t nWhiteX, int32_t nWhiteY ); - void Wayland_WPImageDescriptionInfo_PrimariesNamed( wp_image_description_info_v1 *pImageDescInfo, uint32_t uPrimaries ); - void Wayland_WPImageDescriptionInfo_TFPower( wp_image_description_info_v1 *pImageDescInfo, uint32_t uExp); - void Wayland_WPImageDescriptionInfo_TFNamed( wp_image_description_info_v1 *pImageDescInfo, uint32_t uTF); - void Wayland_WPImageDescriptionInfo_Luminances( wp_image_description_info_v1 *pImageDescInfo, uint32_t uMinLum, uint32_t uMaxLum, uint32_t uRefLum ); - void Wayland_WPImageDescriptionInfo_TargetPrimaries( wp_image_description_info_v1 *pImageDescInfo, int32_t nRedX, int32_t nRedY, int32_t nGreenX, int32_t nGreenY, int32_t nBlueX, int32_t nBlueY, int32_t nWhiteX, int32_t nWhiteY ); - void Wayland_WPImageDescriptionInfo_TargetLuminance( wp_image_description_info_v1 *pImageDescInfo, uint32_t uMinLum, uint32_t uMaxLum ); - void Wayland_WPImageDescriptionInfo_Target_MaxCLL( wp_image_description_info_v1 *pImageDescInfo, uint32_t uMaxCLL ); - void Wayland_WPImageDescriptionInfo_Target_MaxFALL( wp_image_description_info_v1 *pImageDescInfo, uint32_t uMaxFALL ); - static const wp_image_description_info_v1_listener s_ImageDescriptionInfoListener; - - void Wayland_FractionalScale_PreferredScale( wp_fractional_scale_v1 *pFractionalScale, uint32_t uScale ); + uint32_t uTransferFunction, + uint32_t uOutputDisplayPrimaryRedX, + uint32_t uOutputDisplayPrimaryRedY, + uint32_t uOutputDisplayPrimaryGreenX, + uint32_t uOutputDisplayPrimaryGreenY, + uint32_t uOutputDisplayPrimaryBlueX, + uint32_t uOutputDisplayPrimaryBlueY, + uint32_t uOutputWhitePointX, + uint32_t uOutputWhitePointY, + uint32_t uMaxLuminance, + uint32_t uMinLuminance, + uint32_t uMaxFullFrameLuminance ); + static const frog_color_managed_surface_listener + s_FrogColorManagedSurfaceListener; + + void Wayland_WPColorManagementSurfaceFeedback_PreferredChanged( + wp_color_management_surface_feedback_v1 *pColorManagementSurface, + unsigned int data ); + static const wp_color_management_surface_feedback_v1_listener + s_WPColorManagementSurfaceListener; + void UpdateWPPreferredColorManagement( ); + + void Wayland_WPImageDescriptionInfo_Done( + wp_image_description_info_v1 *pImageDescInfo ); + void Wayland_WPImageDescriptionInfo_ICCFile( + wp_image_description_info_v1 *pImageDescInfo, + int32_t nICCFd, + uint32_t uICCSize ); + void Wayland_WPImageDescriptionInfo_Primaries( + wp_image_description_info_v1 *pImageDescInfo, + int32_t nRedX, + int32_t nRedY, + int32_t nGreenX, + int32_t nGreenY, + int32_t nBlueX, + int32_t nBlueY, + int32_t nWhiteX, + int32_t nWhiteY ); + void Wayland_WPImageDescriptionInfo_PrimariesNamed( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uPrimaries ); + void Wayland_WPImageDescriptionInfo_TFPower( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uExp ); + void Wayland_WPImageDescriptionInfo_TFNamed( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uTF ); + void Wayland_WPImageDescriptionInfo_Luminances( + wp_image_description_info_v1 *pImageDescInfo, + uint32_t uMinLum, + uint32_t uMaxLum, + uint32_t uRefLum ); + void Wayland_WPImageDescriptionInfo_TargetPrimaries( + wp_image_description_info_v1 *pImageDescInfo, + int32_t nRedX, + int32_t nRedY, + int32_t nGreenX, + int32_t nGreenY, + int32_t nBlueX, + int32_t nBlueY, + int32_t nWhiteX, + int32_t nWhiteY ); + void Wayland_WPImageDescriptionInfo_TargetLuminance( + wp_image_description_info_v1 *pImageDescInfo, + uint32_t uMinLum, + uint32_t uMaxLum ); + void Wayland_WPImageDescriptionInfo_Target_MaxCLL( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uMaxCLL ); + void Wayland_WPImageDescriptionInfo_Target_MaxFALL( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uMaxFALL ); + static const wp_image_description_info_v1_listener + s_ImageDescriptionInfoListener; + + void Wayland_FractionalScale_PreferredScale( + wp_fractional_scale_v1 *pFractionalScale, uint32_t uScale ); static const wp_fractional_scale_v1_listener s_FractionalScaleListener; CWaylandConnector *m_pConnector = nullptr; - CWaylandBackend *m_pBackend = nullptr; - - CWaylandPlane *m_pParent = nullptr; - wl_surface *m_pSurface = nullptr; - wp_viewport *m_pViewport = nullptr; - frog_color_managed_surface *m_pFrogColorManagedSurface = nullptr; - wp_color_management_surface_v1 *m_pWPColorManagedSurface = nullptr; - wp_color_management_surface_feedback_v1 *m_pWPColorManagedSurfaceFeedback = nullptr; - wp_fractional_scale_v1 *m_pFractionalScale = nullptr; - wl_subsurface *m_pSubsurface = nullptr; - libdecor_frame *m_pFrame = nullptr; - libdecor_window_state m_eWindowState = LIBDECOR_WINDOW_STATE_NONE; + CWaylandBackend *m_pBackend = nullptr; + + CWaylandPlane *m_pParent = nullptr; + wl_surface *m_pSurface = nullptr; + wp_viewport *m_pViewport = nullptr; + frog_color_managed_surface *m_pFrogColorManagedSurface = nullptr; + wp_color_management_surface_v1 *m_pWPColorManagedSurface = nullptr; + wp_color_management_surface_feedback_v1 + *m_pWPColorManagedSurfaceFeedback = nullptr; + wp_fractional_scale_v1 *m_pFractionalScale = nullptr; + wl_subsurface *m_pSubsurface = nullptr; + libdecor_frame *m_pFrame = nullptr; + libdecor_window_state m_eWindowState = LIBDECOR_WINDOW_STATE_NONE; std::vector m_pOutputs; - bool m_bNeedsDecorCommit = false; - uint32_t m_uFractionalScale = 120; - bool m_bHasRecievedScale = false; + bool m_bNeedsDecorCommit = false; + uint32_t m_uFractionalScale = 120; + bool m_bHasRecievedScale = false; std::optional m_ColorState{}; - float m_flPreviousSaturationScale = 1.0f; - wp_image_description_v1 *m_pCurrentImageDescription = nullptr; + float m_flPreviousSaturationScale = 1.0f; + wp_image_description_v1 *m_pCurrentImageDescription = nullptr; - std::mutex m_PlaneStateLock; + std::mutex m_PlaneStateLock; std::optional m_oCurrentPlaneState; }; - const wl_surface_listener CWaylandPlane::s_SurfaceListener = - { - .enter = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_Surface_Enter ), - .leave = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_Surface_Leave ), - .preferred_buffer_scale = WAYLAND_NULL(), - .preferred_buffer_transform = WAYLAND_NULL(), + const wl_surface_listener CWaylandPlane::s_SurfaceListener = { + .enter = + WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_Surface_Enter ), + .leave = + WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_Surface_Leave ), + .preferred_buffer_scale = WAYLAND_NULL( ), + .preferred_buffer_transform = WAYLAND_NULL( ), }; // Can't be const, libdecor api bad... - libdecor_frame_interface CWaylandPlane::s_LibDecorFrameInterface = - { - .configure = LIBDECOR_USERDATA_TO_THIS( CWaylandPlane, LibDecor_Frame_Configure ), - .close = LIBDECOR_USERDATA_TO_THIS( CWaylandPlane, LibDecor_Frame_Close ), - .commit = LIBDECOR_USERDATA_TO_THIS( CWaylandPlane, LibDecor_Frame_Commit ), - .dismiss_popup = LIBDECOR_USERDATA_TO_THIS( CWaylandPlane, LibDecor_Frame_DismissPopup ), - }; - const wp_presentation_feedback_listener CWaylandPlane::s_PresentationFeedbackListener = - { - .sync_output = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_PresentationFeedback_SyncOutput ), - .presented = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_PresentationFeedback_Presented ), - .discarded = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_PresentationFeedback_Discarded ), - }; - const frog_color_managed_surface_listener CWaylandPlane::s_FrogColorManagedSurfaceListener = - { - .preferred_metadata = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_FrogColorManagedSurface_PreferredMetadata ), - }; - const wp_color_management_surface_feedback_v1_listener CWaylandPlane::s_WPColorManagementSurfaceListener = - { - .preferred_changed = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPColorManagementSurfaceFeedback_PreferredChanged ), - }; - const wp_image_description_info_v1_listener CWaylandPlane::s_ImageDescriptionInfoListener = - { - .done = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_Done ), - .icc_file = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_ICCFile ), - .primaries = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_Primaries ), - .primaries_named = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_PrimariesNamed ), - .tf_power = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_TFPower ), - .tf_named = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_TFNamed ), - .luminances = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_Luminances ), - .target_primaries = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_TargetPrimaries ), - .target_luminance = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_TargetLuminance ), - .target_max_cll = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_Target_MaxCLL ), - .target_max_fall = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_WPImageDescriptionInfo_Target_MaxFALL ), - }; - const wp_fractional_scale_v1_listener CWaylandPlane::s_FractionalScaleListener = - { - .preferred_scale = WAYLAND_USERDATA_TO_THIS( CWaylandPlane, Wayland_FractionalScale_PreferredScale ), + libdecor_frame_interface CWaylandPlane::s_LibDecorFrameInterface = { + .configure = LIBDECOR_USERDATA_TO_THIS( + CWaylandPlane, LibDecor_Frame_Configure ), + .close = + LIBDECOR_USERDATA_TO_THIS( CWaylandPlane, LibDecor_Frame_Close ), + .commit = + LIBDECOR_USERDATA_TO_THIS( CWaylandPlane, LibDecor_Frame_Commit ), + .dismiss_popup = LIBDECOR_USERDATA_TO_THIS( + CWaylandPlane, LibDecor_Frame_DismissPopup ), }; + const wp_presentation_feedback_listener + CWaylandPlane::s_PresentationFeedbackListener = { + .sync_output = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_PresentationFeedback_SyncOutput ), + .presented = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_PresentationFeedback_Presented ), + .discarded = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_PresentationFeedback_Discarded ), + }; + const frog_color_managed_surface_listener + CWaylandPlane::s_FrogColorManagedSurfaceListener = { + .preferred_metadata = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, + Wayland_FrogColorManagedSurface_PreferredMetadata ), + }; + const wp_color_management_surface_feedback_v1_listener + CWaylandPlane::s_WPColorManagementSurfaceListener = { + .preferred_changed = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, + Wayland_WPColorManagementSurfaceFeedback_PreferredChanged ), + }; + const wp_image_description_info_v1_listener + CWaylandPlane::s_ImageDescriptionInfoListener = { + .done = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_Done ), + .icc_file = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_ICCFile ), + .primaries = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_Primaries ), + .primaries_named = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_PrimariesNamed ), + .tf_power = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_TFPower ), + .tf_named = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_TFNamed ), + .luminances = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_Luminances ), + .target_primaries = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_TargetPrimaries ), + .target_luminance = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_TargetLuminance ), + .target_max_cll = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_Target_MaxCLL ), + .target_max_fall = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_WPImageDescriptionInfo_Target_MaxFALL ), + }; + const wp_fractional_scale_v1_listener + CWaylandPlane::s_FractionalScaleListener = { + .preferred_scale = WAYLAND_USERDATA_TO_THIS( + CWaylandPlane, Wayland_FractionalScale_PreferredScale ), + }; enum WaylandModifierIndex { @@ -359,7 +476,8 @@ namespace gamescope GAMESCOPE_WAYLAND_MOD_COUNT, }; - constexpr const char *WaylandModifierToXkbModifierName( WaylandModifierIndex eIndex ) + constexpr const char * + WaylandModifierToXkbModifierName( WaylandModifierIndex eIndex ) { switch ( eIndex ) { @@ -383,93 +501,95 @@ namespace gamescope struct WaylandOutputInfo { int32_t nRefresh = 60; - int32_t nScale = 1; + int32_t nScale = 1; }; - - class CWaylandConnector final : public CBaseBackendConnector, public INestedHints + class CWaylandConnector final : public CBaseBackendConnector, + public INestedHints { public: - CWaylandConnector( CWaylandBackend *pBackend, uint64_t ulVirtualConnectorKey ); - virtual ~CWaylandConnector(); - - bool UpdateEdid(); - bool Init(); - void SetFullscreen( bool bFullscreen ); // Thread safe, can be called from the input thread. - void UpdateFullscreenState(); - - - bool HostCompositorIsCurrentlyVRR() const { return m_bHostCompositorIsCurrentlyVRR; } - void SetHostCompositorIsCurrentlyVRR( bool bActive ) { m_bHostCompositorIsCurrentlyVRR = bActive; } - bool CurrentDisplaySupportsVRR() const { return HostCompositorIsCurrentlyVRR(); } - CWaylandBackend *GetBackend() const { return m_pBackend; } + CWaylandConnector( + CWaylandBackend *pBackend, uint64_t ulVirtualConnectorKey ); + virtual ~CWaylandConnector( ); + + bool UpdateEdid( ); + bool Init( ); + void SetFullscreen( bool bFullscreen ); // Thread safe, can be called + // from the input thread. + void UpdateFullscreenState( ); + + bool HostCompositorIsCurrentlyVRR( ) const + { return m_bHostCompositorIsCurrentlyVRR; } + void SetHostCompositorIsCurrentlyVRR( bool bActive ) + { m_bHostCompositorIsCurrentlyVRR = bActive; } + bool CurrentDisplaySupportsVRR( ) const + { return HostCompositorIsCurrentlyVRR( ); } + CWaylandBackend *GetBackend( ) const { return m_pBackend; } ///////////////////// // IBackendConnector ///////////////////// - virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; + virtual int + Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; - virtual gamescope::GamescopeScreenType GetScreenType() const override; - virtual GamescopePanelOrientation GetCurrentOrientation() const override; - virtual bool SupportsHDR() const override; - virtual bool IsHDRActive() const override; - virtual const BackendConnectorHDRInfo &GetHDRInfo() const override; - virtual bool IsVRRActive() const override; - virtual std::span GetModes() const override; + virtual gamescope::GamescopeScreenType GetScreenType( ) const override; + virtual GamescopePanelOrientation + GetCurrentOrientation( ) const override; + virtual bool SupportsHDR( ) const override; + virtual bool IsHDRActive( ) const override; + virtual const BackendConnectorHDRInfo &GetHDRInfo( ) const override; + virtual bool IsVRRActive( ) const override; + virtual std::span GetModes( ) const override; - virtual bool SupportsVRR() const override; + virtual bool SupportsVRR( ) const override; - virtual std::span GetRawEDID() const override; - virtual std::span GetValidDynamicRefreshRates() const override; + virtual std::span GetRawEDID( ) const override; + virtual std::span + GetValidDynamicRefreshRates( ) const override; virtual void GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const override; + bool bHDR10, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const override; - virtual const char *GetName() const override - { - return "Wayland"; - } - virtual const char *GetMake() const override - { - return "Gamescope"; - } - virtual const char *GetModel() const override - { - return "Virtual Display"; - } + virtual const char *GetName( ) const override { return "Wayland"; } + virtual const char *GetMake( ) const override { return "Gamescope"; } + virtual const char *GetModel( ) const override + { return "Virtual Display"; } - virtual INestedHints *GetNestedHints() override - { - return this; - } + virtual INestedHints *GetNestedHints( ) override { return this; } /////////////////// // INestedHints /////////////////// - virtual void SetCursorImage( std::shared_ptr info ) override; + virtual void SetCursorImage( + std::shared_ptr info ) override; virtual void SetRelativeMouseMode( bool bRelative ) override; virtual void SetVisible( bool bVisible ) override; virtual void SetTitle( std::shared_ptr szTitle ) override; - virtual void SetIcon( std::shared_ptr> uIconPixels ) override; - virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) override; - private: + virtual void + SetIcon( std::shared_ptr> uIconPixels ) override; + virtual void SetSelection( + std::shared_ptr szContents, + GamescopeSelection eSelection ) override; + private: friend CWaylandPlane; BackendConnectorHDRInfo m_HDRInfo{}; - uint32_t m_uReferenceLuminance = 203; - uint32_t m_uMaxTargetLuminance = 203; - displaycolorimetry_t m_DisplayColorimetry = displaycolorimetry_709; - std::vector m_FakeEdid; + uint32_t m_uReferenceLuminance = 203; + uint32_t m_uMaxTargetLuminance = 203; + displaycolorimetry_t m_DisplayColorimetry = displaycolorimetry_709; + std::vector m_FakeEdid; CWaylandBackend *m_pBackend = nullptr; - CWaylandPlane m_Planes[8]; - bool m_bVisible = true; + CWaylandPlane m_Planes[ 8 ]; + bool m_bVisible = true; std::atomic m_bDesiredFullscreenState = { false }; bool m_bHostCompositorIsCurrentlyVRR = false; @@ -479,485 +599,643 @@ namespace gamescope { public: CWaylandFb( CWaylandBackend *pBackend, wl_buffer *pHostBuffer ); - ~CWaylandFb(); + ~CWaylandFb( ); - void OnCompositorAcquire(); - void OnCompositorRelease(); + void OnCompositorAcquire( ); + void OnCompositorRelease( ); - wl_buffer *GetHostBuffer() const { return m_pHostBuffer; } - wlr_buffer *GetClientBuffer() const { return m_pClientBuffer; } + wl_buffer *GetHostBuffer( ) const { return m_pHostBuffer; } + wlr_buffer *GetClientBuffer( ) const { return m_pClientBuffer; } void Wayland_Buffer_Release( wl_buffer *pBuffer ); static const wl_buffer_listener s_BufferListener; private: - CWaylandBackend *m_pBackend = nullptr; - wl_buffer *m_pHostBuffer = nullptr; - wlr_buffer *m_pClientBuffer = nullptr; - bool m_bCompositorAcquired = false; + CWaylandBackend *m_pBackend = nullptr; + wl_buffer *m_pHostBuffer = nullptr; + wlr_buffer *m_pClientBuffer = nullptr; + bool m_bCompositorAcquired = false; }; - const wl_buffer_listener CWaylandFb::s_BufferListener = - { - .release = WAYLAND_USERDATA_TO_THIS( CWaylandFb, Wayland_Buffer_Release ), + const wl_buffer_listener CWaylandFb::s_BufferListener = { + .release = + WAYLAND_USERDATA_TO_THIS( CWaylandFb, Wayland_Buffer_Release ), }; class CWaylandInputThread { public: - CWaylandInputThread(); - ~CWaylandInputThread(); + CWaylandInputThread( ); + ~CWaylandInputThread( ); bool Init( CWaylandBackend *pBackend ); - void ThreadFunc(); + void ThreadFunc( ); // This is only shared_ptr because it works nicely // with std::atomic, std::any and such and makes it very easy. // // It could be a std::unique_ptr if you added a mutex, // but I didn't seem it worth it. - template - std::shared_ptr QueueLaunder( T* pObject ); + template std::shared_ptr QueueLaunder( T *pObject ); void SetRelativePointer( bool bRelative ); private: - void HandleKey( uint32_t uKey, bool bPressed ); CWaylandBackend *m_pBackend = nullptr; CWaiter<4> m_Waiter; - std::thread m_Thread; + std::thread m_Thread; std::atomic m_bInitted = { false }; uint32_t m_uPointerEnterSerial = 0; - bool m_bMouseEntered = false; - bool m_bKeyboardEntered = false; + bool m_bMouseEntered = false; + bool m_bKeyboardEntered = false; - wl_event_queue *m_pQueue = nullptr; + wl_event_queue *m_pQueue = nullptr; std::shared_ptr m_pDisplayWrapper; - wl_seat *m_pSeat = nullptr; - wl_keyboard *m_pKeyboard = nullptr; - wl_pointer *m_pPointer = nullptr; - wl_touch *m_pTouch = nullptr; + wl_seat *m_pSeat = nullptr; + wl_keyboard *m_pKeyboard = nullptr; + wl_pointer *m_pPointer = nullptr; + wl_touch *m_pTouch = nullptr; zwp_relative_pointer_manager_v1 *m_pRelativePointerManager = nullptr; uint32_t m_uFakeTimestamp = 0; xkb_context *m_pXkbContext = nullptr; - xkb_keymap *m_pXkbKeymap = nullptr; + xkb_keymap *m_pXkbKeymap = nullptr; uint32_t m_uKeyModifiers = 0; uint32_t m_uModMask[ GAMESCOPE_WAYLAND_MOD_COUNT ]; - double m_flScrollAccum[2] = { 0.0, 0.0 }; - uint32_t m_uAxisSource = WL_POINTER_AXIS_SOURCE_WHEEL; + double m_flScrollAccum[ 2 ] = { 0.0, 0.0 }; + uint32_t m_uAxisSource = WL_POINTER_AXIS_SOURCE_WHEEL; - wl_surface *m_pCurrentCursorSurface = nullptr; + wl_surface *m_pCurrentCursorSurface = nullptr; std::optional m_ofPendingCursorX; std::optional m_ofPendingCursorY; - std::atomic> m_pRelativePointer = nullptr; + std::atomic> + m_pRelativePointer = nullptr; std::unordered_set m_uScancodesHeld; - void Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ); + void Wayland_Registry_Global( + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ); static const wl_registry_listener s_RegistryListener; - void Wayland_Seat_Capabilities( wl_seat *pSeat, uint32_t uCapabilities ); + void + Wayland_Seat_Capabilities( wl_seat *pSeat, uint32_t uCapabilities ); void Wayland_Seat_Name( wl_seat *pSeat, const char *pName ); static const wl_seat_listener s_SeatListener; - void Wayland_Pointer_Enter( wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface, wl_fixed_t fSurfaceX, wl_fixed_t fSurfaceY ); - void Wayland_Pointer_Leave( wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface ); - void Wayland_Pointer_Motion( wl_pointer *pPointer, uint32_t uTime, wl_fixed_t fSurfaceX, wl_fixed_t fSurfaceY ); - void Wayland_Pointer_Button( wl_pointer *pPointer, uint32_t uSerial, uint32_t uTime, uint32_t uButton, uint32_t uState ); - void Wayland_Pointer_Axis( wl_pointer *pPointer, uint32_t uTime, uint32_t uAxis, wl_fixed_t fValue ); - void Wayland_Pointer_Axis_Source( wl_pointer *pPointer, uint32_t uAxisSource ); - void Wayland_Pointer_Axis_Stop( wl_pointer *pPointer, uint32_t uTime, uint32_t uAxis ); - void Wayland_Pointer_Axis_Discrete( wl_pointer *pPointer, uint32_t uAxis, int32_t nDiscrete ); - void Wayland_Pointer_Axis_Value120( wl_pointer *pPointer, uint32_t uAxis, int32_t nValue120 ); + void Wayland_Pointer_Enter( + wl_pointer *pPointer, + uint32_t uSerial, + wl_surface *pSurface, + wl_fixed_t fSurfaceX, + wl_fixed_t fSurfaceY ); + void Wayland_Pointer_Leave( + wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface ); + void Wayland_Pointer_Motion( + wl_pointer *pPointer, + uint32_t uTime, + wl_fixed_t fSurfaceX, + wl_fixed_t fSurfaceY ); + void Wayland_Pointer_Button( + wl_pointer *pPointer, + uint32_t uSerial, + uint32_t uTime, + uint32_t uButton, + uint32_t uState ); + void Wayland_Pointer_Axis( + wl_pointer *pPointer, + uint32_t uTime, + uint32_t uAxis, + wl_fixed_t fValue ); + void Wayland_Pointer_Axis_Source( + wl_pointer *pPointer, uint32_t uAxisSource ); + void Wayland_Pointer_Axis_Stop( + wl_pointer *pPointer, uint32_t uTime, uint32_t uAxis ); + void Wayland_Pointer_Axis_Discrete( + wl_pointer *pPointer, uint32_t uAxis, int32_t nDiscrete ); + void Wayland_Pointer_Axis_Value120( + wl_pointer *pPointer, uint32_t uAxis, int32_t nValue120 ); void Wayland_Pointer_Frame( wl_pointer *pPointer ); static const wl_pointer_listener s_PointerListener; - void Wayland_Keyboard_Keymap( wl_keyboard *pKeyboard, uint32_t uFormat, int32_t nFd, uint32_t uSize ); - void Wayland_Keyboard_Enter( wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface, wl_array *pKeys ); - void Wayland_Keyboard_Leave( wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface ); - void Wayland_Keyboard_Key( wl_keyboard *pKeyboard, uint32_t uSerial, uint32_t uTime, uint32_t uKey, uint32_t uState ); - void Wayland_Keyboard_Modifiers( wl_keyboard *pKeyboard, uint32_t uSerial, uint32_t uModsDepressed, uint32_t uModsLatched, uint32_t uModsLocked, uint32_t uGroup ); - void Wayland_Keyboard_RepeatInfo( wl_keyboard *pKeyboard, int32_t nRate, int32_t nDelay ); + void Wayland_Keyboard_Keymap( + wl_keyboard *pKeyboard, + uint32_t uFormat, + int32_t nFd, + uint32_t uSize ); + void Wayland_Keyboard_Enter( + wl_keyboard *pKeyboard, + uint32_t uSerial, + wl_surface *pSurface, + wl_array *pKeys ); + void Wayland_Keyboard_Leave( + wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface ); + void Wayland_Keyboard_Key( + wl_keyboard *pKeyboard, + uint32_t uSerial, + uint32_t uTime, + uint32_t uKey, + uint32_t uState ); + void Wayland_Keyboard_Modifiers( + wl_keyboard *pKeyboard, + uint32_t uSerial, + uint32_t uModsDepressed, + uint32_t uModsLatched, + uint32_t uModsLocked, + uint32_t uGroup ); + void Wayland_Keyboard_RepeatInfo( + wl_keyboard *pKeyboard, int32_t nRate, int32_t nDelay ); static const wl_keyboard_listener s_KeyboardListener; - void Wayland_RelativePointer_RelativeMotion( zwp_relative_pointer_v1 *pRelativePointer, uint32_t uTimeHi, uint32_t uTimeLo, wl_fixed_t fDx, wl_fixed_t fDy, wl_fixed_t fDxUnaccel, wl_fixed_t fDyUnaccel ); + void Wayland_RelativePointer_RelativeMotion( + zwp_relative_pointer_v1 *pRelativePointer, + uint32_t uTimeHi, + uint32_t uTimeLo, + wl_fixed_t fDx, + wl_fixed_t fDy, + wl_fixed_t fDxUnaccel, + wl_fixed_t fDyUnaccel ); static const zwp_relative_pointer_v1_listener s_RelativePointerListener; }; - const wl_registry_listener CWaylandInputThread::s_RegistryListener = - { - .global = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Registry_Global ), - .global_remove = WAYLAND_NULL(), + const wl_registry_listener CWaylandInputThread::s_RegistryListener = { + .global = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Registry_Global ), + .global_remove = WAYLAND_NULL( ), }; - const wl_seat_listener CWaylandInputThread::s_SeatListener = - { - .capabilities = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Seat_Capabilities ), - .name = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Seat_Name ), + const wl_seat_listener CWaylandInputThread::s_SeatListener = { + .capabilities = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Seat_Capabilities ), + .name = + WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Seat_Name ), }; - const wl_pointer_listener CWaylandInputThread::s_PointerListener = - { - .enter = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Enter ), - .leave = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Leave ), - .motion = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Motion ), - .button = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Button ), - .axis = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Axis ), - .frame = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Frame ), - .axis_source = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Axis_Source ), - .axis_stop = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Axis_Stop ), - .axis_discrete = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Axis_Discrete ), - .axis_value120 = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Pointer_Axis_Value120 ), + const wl_pointer_listener CWaylandInputThread::s_PointerListener = { + .enter = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Enter ), + .leave = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Leave ), + .motion = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Motion ), + .button = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Button ), + .axis = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Axis ), + .frame = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Frame ), + .axis_source = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Axis_Source ), + .axis_stop = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Axis_Stop ), + .axis_discrete = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Axis_Discrete ), + .axis_value120 = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Pointer_Axis_Value120 ), }; - const wl_keyboard_listener CWaylandInputThread::s_KeyboardListener = - { - .keymap = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_Keymap ), - .enter = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_Enter ), - .leave = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_Leave ), - .key = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_Key ), - .modifiers = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_Modifiers ), - .repeat_info = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_RepeatInfo ), - }; - const zwp_relative_pointer_v1_listener CWaylandInputThread::s_RelativePointerListener = - { - .relative_motion = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_RelativePointer_RelativeMotion ), + const wl_keyboard_listener CWaylandInputThread::s_KeyboardListener = { + .keymap = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Keyboard_Keymap ), + .enter = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Keyboard_Enter ), + .leave = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Keyboard_Leave ), + .key = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Keyboard_Key ), + .modifiers = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Keyboard_Modifiers ), + .repeat_info = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_Keyboard_RepeatInfo ), }; + const zwp_relative_pointer_v1_listener + CWaylandInputThread::s_RelativePointerListener = { + .relative_motion = WAYLAND_USERDATA_TO_THIS( + CWaylandInputThread, Wayland_RelativePointer_RelativeMotion ), + }; class CWaylandBackend : public CBaseBackend { public: - CWaylandBackend(); + CWaylandBackend( ); ///////////// // IBackend ///////////// - virtual bool Init() override; - virtual bool PostInit() override; - virtual std::span GetInstanceExtensions() const override; - virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const override; - virtual VkImageLayout GetPresentLayout() const override; - virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const override; - virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override; + virtual bool Init( ) override; + virtual bool PostInit( ) override; + virtual std::span + GetInstanceExtensions( ) const override; + virtual std::span GetDeviceExtensions( + VkPhysicalDevice pVkPhysicalDevice ) const override; + virtual VkImageLayout GetPresentLayout( ) const override; + virtual void GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, + uint32_t *pOverlayPlaneFormat ) const override; + virtual bool ValidPhysicalDevice( + VkPhysicalDevice pVkPhysicalDevice ) const override; + + virtual void + DirtyState( bool bForce = false, bool bForceModeset = false ) override; + virtual bool PollState( ) override; - virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override; - virtual bool PollState() override; + virtual std::shared_ptr CreateBackendBlob( + const std::type_info &type, + std::span data ) override; - virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override; + virtual OwningRc + ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override; + virtual bool UsesModifiers( ) const override; + virtual std::span + GetSupportedModifiers( uint32_t uDrmFormat ) const override; - virtual OwningRc ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) override; - virtual bool UsesModifiers() const override; - virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const override; + virtual IBackendConnector *GetCurrentConnector( ) override; + virtual IBackendConnector * + GetConnector( GamescopeScreenType eScreenType ) override; - virtual IBackendConnector *GetCurrentConnector() override; - virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) override; + virtual bool SupportsPlaneHardwareCursor( ) const override; - virtual bool SupportsPlaneHardwareCursor() const override; + virtual bool SupportsTearing( ) const override; + virtual bool UsesVulkanSwapchain( ) const override; - virtual bool SupportsTearing() const override; - virtual bool UsesVulkanSwapchain() const override; + virtual bool IsSessionBased( ) const override; + virtual bool SupportsExplicitSync( ) const override; - virtual bool IsSessionBased() const override; - virtual bool SupportsExplicitSync() const override; + virtual bool IsPaused( ) const override; + virtual bool IsVisible( ) const override; - virtual bool IsPaused() const override; - virtual bool IsVisible() const override; + virtual glm::uvec2 + CursorSurfaceSize( glm::uvec2 uvecSize ) const override; + virtual void HackUpdatePatchedEdid( ) override; - virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override; - virtual void HackUpdatePatchedEdid() override; + virtual bool UsesVirtualConnectors( ) override; + virtual std::shared_ptr + CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override; - virtual bool UsesVirtualConnectors() override; - virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override; protected: virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override; - wl_surface *CursorInfoToSurface( const std::shared_ptr &info ); + wl_surface *CursorInfoToSurface( + const std::shared_ptr &info ); - bool SupportsColorManagement() const; + bool SupportsColorManagement( ) const; void SetCursorImage( std::shared_ptr info ); void SetRelativeMouseMode( wl_surface *pSurface, bool bRelative ); - void UpdateCursor(); + void UpdateCursor( ); friend CWaylandConnector; friend CWaylandPlane; friend CWaylandInputThread; friend CWaylandFb; - wl_display *GetDisplay() const { return m_pDisplay; } - wl_shm *GetShm() const { return m_pShm; } - wl_compositor *GetCompositor() const { return m_pCompositor; } - wp_single_pixel_buffer_manager_v1 *GetSinglePixelBufferManager() const { return m_pSinglePixelBufferManager; } - wl_subcompositor *GetSubcompositor() const { return m_pSubcompositor; } - zwp_linux_dmabuf_v1 *GetLinuxDmabuf() const { return m_pLinuxDmabuf; } - xdg_wm_base *GetXDGWMBase() const { return m_pXdgWmBase; } - wp_viewporter *GetViewporter() const { return m_pViewporter; } - wp_presentation *GetPresentation() const { return m_pPresentation; } - frog_color_management_factory_v1 *GetFrogColorManagementFactory() const { return m_pFrogColorMgmtFactory; } - wp_color_manager_v1 *GetWPColorManager() const { return m_pWPColorManager; } - wp_image_description_v1 *GetWPImageDescription( GamescopeAppTextureColorspace eColorspace ) const { return m_pWPImageDescriptions[ (uint32_t)eColorspace ]; } - wp_fractional_scale_manager_v1 *GetFractionalScaleManager() const { return m_pFractionalScaleManager; } - xdg_toplevel_icon_manager_v1 *GetToplevelIconManager() const { return m_pToplevelIconManager; } - libdecor *GetLibDecor() const { return m_pLibDecor; } - - void UpdateFullscreenState(); + wl_display *GetDisplay( ) const { return m_pDisplay; } + wl_shm *GetShm( ) const { return m_pShm; } + wl_compositor *GetCompositor( ) const { return m_pCompositor; } + wp_single_pixel_buffer_manager_v1 *GetSinglePixelBufferManager( ) const + { return m_pSinglePixelBufferManager; } + wl_subcompositor *GetSubcompositor( ) const { return m_pSubcompositor; } + zwp_linux_dmabuf_v1 *GetLinuxDmabuf( ) const { return m_pLinuxDmabuf; } + xdg_wm_base *GetXDGWMBase( ) const { return m_pXdgWmBase; } + wp_viewporter *GetViewporter( ) const { return m_pViewporter; } + wp_presentation *GetPresentation( ) const { return m_pPresentation; } + frog_color_management_factory_v1 *GetFrogColorManagementFactory( ) const + { return m_pFrogColorMgmtFactory; } + wp_color_manager_v1 *GetWPColorManager( ) const + { return m_pWPColorManager; } + wp_image_description_v1 * + GetWPImageDescription( GamescopeAppTextureColorspace eColorspace ) const + { return m_pWPImageDescriptions[ ( uint32_t )eColorspace ]; } + wp_fractional_scale_manager_v1 *GetFractionalScaleManager( ) const + { return m_pFractionalScaleManager; } + xdg_toplevel_icon_manager_v1 *GetToplevelIconManager( ) const + { return m_pToplevelIconManager; } + libdecor *GetLibDecor( ) const { return m_pLibDecor; } + + void UpdateFullscreenState( ); WaylandOutputInfo *GetOutputInfo( wl_output *pOutput ) { auto iter = m_pOutputs.find( pOutput ); - if ( iter == m_pOutputs.end() ) - return nullptr; + if ( iter == m_pOutputs.end( ) ) return nullptr; return &iter->second; } - wl_region *GetEmptyRegion() const { return m_pEmptyRegion; } - wl_region *GetFullRegion() const { return m_pFullRegion; } - CWaylandFb *GetBlackFb() const { return m_BlackFb.get(); } + wl_region *GetEmptyRegion( ) const { return m_pEmptyRegion; } + wl_region *GetFullRegion( ) const { return m_pFullRegion; } + CWaylandFb *GetBlackFb( ) const { return m_BlackFb.get( ); } void OnConnectorDestroyed( CWaylandConnector *pConnector ) - { - m_pFocusConnector.compare_exchange_strong( pConnector, nullptr ); - } + { m_pFocusConnector.compare_exchange_strong( pConnector, nullptr ); } private: - - void Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ); + void Wayland_Registry_Global( + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ); static const wl_registry_listener s_RegistryListener; - void Wayland_Modifier( zwp_linux_dmabuf_v1 *pDmabuf, uint32_t uFormat, uint32_t uModifierHi, uint32_t uModifierLo ); - - void Wayland_Output_Geometry( wl_output *pOutput, int32_t nX, int32_t nY, int32_t nPhysicalWidth, int32_t nPhysicalHeight, int32_t nSubpixel, const char *pMake, const char *pModel, int32_t nTransform ); - void Wayland_Output_Mode( wl_output *pOutput, uint32_t uFlags, int32_t nWidth, int32_t nHeight, int32_t nRefresh ); + void Wayland_Modifier( + zwp_linux_dmabuf_v1 *pDmabuf, + uint32_t uFormat, + uint32_t uModifierHi, + uint32_t uModifierLo ); + + void Wayland_Output_Geometry( + wl_output *pOutput, + int32_t nX, + int32_t nY, + int32_t nPhysicalWidth, + int32_t nPhysicalHeight, + int32_t nSubpixel, + const char *pMake, + const char *pModel, + int32_t nTransform ); + void Wayland_Output_Mode( + wl_output *pOutput, + uint32_t uFlags, + int32_t nWidth, + int32_t nHeight, + int32_t nRefresh ); void Wayland_Output_Done( wl_output *pOutput ); void Wayland_Output_Scale( wl_output *pOutput, int32_t nFactor ); void Wayland_Output_Name( wl_output *pOutput, const char *pName ); - void Wayland_Output_Description( wl_output *pOutput, const char *pDescription ); + void Wayland_Output_Description( + wl_output *pOutput, const char *pDescription ); static const wl_output_listener s_OutputListener; - void Wayland_Seat_Capabilities( wl_seat *pSeat, uint32_t uCapabilities ); + void + Wayland_Seat_Capabilities( wl_seat *pSeat, uint32_t uCapabilities ); static const wl_seat_listener s_SeatListener; - void Wayland_Pointer_Enter( wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface, wl_fixed_t fSurfaceX, wl_fixed_t fSurfaceY ); - void Wayland_Pointer_Leave( wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface ); + void Wayland_Pointer_Enter( + wl_pointer *pPointer, + uint32_t uSerial, + wl_surface *pSurface, + wl_fixed_t fSurfaceX, + wl_fixed_t fSurfaceY ); + void Wayland_Pointer_Leave( + wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface ); static const wl_pointer_listener s_PointerListener; - void Wayland_Keyboard_Enter( wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface, wl_array *pKeys ); - void Wayland_Keyboard_Leave( wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface ); + void Wayland_Keyboard_Enter( + wl_keyboard *pKeyboard, + uint32_t uSerial, + wl_surface *pSurface, + wl_array *pKeys ); + void Wayland_Keyboard_Leave( + wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface ); static const wl_keyboard_listener s_KeyboardListener; - void Wayland_LockedPointer_Locked( zwp_locked_pointer_v1 *pLockedPointer ); - void Wayland_LockedPointer_Unlocked( zwp_locked_pointer_v1 *pLockedPointer ); - static const zwp_locked_pointer_v1_listener s_LockedPointerListener; - - void Wayland_WPColorManager_SupportedIntent( wp_color_manager_v1 *pWPColorManager, uint32_t uRenderIntent ); - void Wayland_WPColorManager_SupportedFeature( wp_color_manager_v1 *pWPColorManager, uint32_t uFeature ); - void Wayland_WPColorManager_SupportedTFNamed( wp_color_manager_v1 *pWPColorManager, uint32_t uTF ); - void Wayland_WPColorManager_SupportedPrimariesNamed( wp_color_manager_v1 *pWPColorManager, uint32_t uPrimaries ); - void Wayland_WPColorManager_ColorManagerDone( wp_color_manager_v1 *pWPColorManager ); + void + Wayland_LockedPointer_Locked( zwp_locked_pointer_v1 *pLockedPointer ); + void + Wayland_LockedPointer_Unlocked( zwp_locked_pointer_v1 *pLockedPointer ); + static const zwp_locked_pointer_v1_listener s_LockedPointerListener; + + void Wayland_WPColorManager_SupportedIntent( + wp_color_manager_v1 *pWPColorManager, uint32_t uRenderIntent ); + void Wayland_WPColorManager_SupportedFeature( + wp_color_manager_v1 *pWPColorManager, uint32_t uFeature ); + void Wayland_WPColorManager_SupportedTFNamed( + wp_color_manager_v1 *pWPColorManager, uint32_t uTF ); + void Wayland_WPColorManager_SupportedPrimariesNamed( + wp_color_manager_v1 *pWPColorManager, uint32_t uPrimaries ); + void Wayland_WPColorManager_ColorManagerDone( + wp_color_manager_v1 *pWPColorManager ); static const wp_color_manager_v1_listener s_WPColorManagerListener; - void Wayland_DataSource_Send( struct wl_data_source *pSource, const char *pMime, int nFd ); + void Wayland_DataSource_Send( + struct wl_data_source *pSource, const char *pMime, int nFd ); void Wayland_DataSource_Cancelled( struct wl_data_source *pSource ); static const wl_data_source_listener s_DataSourceListener; - void Wayland_PrimarySelectionSource_Send( struct zwp_primary_selection_source_v1 *pSource, const char *pMime, int nFd ); - void Wayland_PrimarySelectionSource_Cancelled( struct zwp_primary_selection_source_v1 *pSource ); - static const zwp_primary_selection_source_v1_listener s_PrimarySelectionSourceListener; + void Wayland_PrimarySelectionSource_Send( + struct zwp_primary_selection_source_v1 *pSource, + const char *pMime, + int nFd ); + void Wayland_PrimarySelectionSource_Cancelled( + struct zwp_primary_selection_source_v1 *pSource ); + static const zwp_primary_selection_source_v1_listener + s_PrimarySelectionSourceListener; CWaylandInputThread m_InputThread; - wl_display *m_pDisplay = nullptr; - wl_shm *m_pShm = nullptr; - wl_compositor *m_pCompositor = nullptr; - wp_single_pixel_buffer_manager_v1 *m_pSinglePixelBufferManager = nullptr; - wl_subcompositor *m_pSubcompositor = nullptr; - zwp_linux_dmabuf_v1 *m_pLinuxDmabuf = nullptr; - xdg_wm_base *m_pXdgWmBase = nullptr; - wp_viewporter *m_pViewporter = nullptr; - wl_region *m_pEmptyRegion = nullptr; - wl_region *m_pFullRegion = nullptr; - Rc m_BlackFb; - OwningRc m_pOwnedBlackFb; - OwningRc m_pBlackTexture; - wp_presentation *m_pPresentation = nullptr; + wl_display *m_pDisplay = nullptr; + wl_shm *m_pShm = nullptr; + wl_compositor *m_pCompositor = nullptr; + wp_single_pixel_buffer_manager_v1 *m_pSinglePixelBufferManager = + nullptr; + wl_subcompositor *m_pSubcompositor = nullptr; + zwp_linux_dmabuf_v1 *m_pLinuxDmabuf = nullptr; + xdg_wm_base *m_pXdgWmBase = nullptr; + wp_viewporter *m_pViewporter = nullptr; + wl_region *m_pEmptyRegion = nullptr; + wl_region *m_pFullRegion = nullptr; + Rc m_BlackFb; + OwningRc m_pOwnedBlackFb; + OwningRc m_pBlackTexture; + wp_presentation *m_pPresentation = nullptr; frog_color_management_factory_v1 *m_pFrogColorMgmtFactory = nullptr; - wp_color_manager_v1 *m_pWPColorManager = nullptr; - wp_image_description_v1 *m_pWPImageDescriptions[ GamescopeAppTextureColorspace_Count ]{}; - zwp_pointer_constraints_v1 *m_pPointerConstraints = nullptr; + wp_color_manager_v1 *m_pWPColorManager = nullptr; + wp_image_description_v1 + *m_pWPImageDescriptions[ GamescopeAppTextureColorspace_Count ]{}; + zwp_pointer_constraints_v1 *m_pPointerConstraints = nullptr; zwp_relative_pointer_manager_v1 *m_pRelativePointerManager = nullptr; - wp_fractional_scale_manager_v1 *m_pFractionalScaleManager = nullptr; - xdg_toplevel_icon_manager_v1 *m_pToplevelIconManager = nullptr; + wp_fractional_scale_manager_v1 *m_pFractionalScaleManager = nullptr; + xdg_toplevel_icon_manager_v1 *m_pToplevelIconManager = nullptr; // TODO: Restructure and remove the need for this. std::atomic m_pFocusConnector; - wl_data_device_manager *m_pDataDeviceManager = nullptr; - wl_data_device *m_pDataDevice = nullptr; - std::shared_ptr m_pClipboard = nullptr; + wl_data_device_manager *m_pDataDeviceManager = nullptr; + wl_data_device *m_pDataDevice = nullptr; + std::shared_ptr m_pClipboard = nullptr; - zwp_primary_selection_device_manager_v1 *m_pPrimarySelectionDeviceManager = nullptr; + zwp_primary_selection_device_manager_v1 + *m_pPrimarySelectionDeviceManager = nullptr; zwp_primary_selection_device_v1 *m_pPrimarySelectionDevice = nullptr; - std::shared_ptr m_pPrimarySelection = nullptr; + std::shared_ptr m_pPrimarySelection = nullptr; struct { std::vector ePrimaries; - std::vector eTransferFunctions; + std::vector + eTransferFunctions; std::vector eRenderIntents; - std::vector eFeatures; + std::vector eFeatures; - bool bSupportsGamescopeColorManagement = false; // Has everything we want and need? + bool bSupportsGamescopeColorManagement = + false; // Has everything we want and need? } m_WPColorManagerFeatures; std::unordered_map m_pOutputs; libdecor *m_pLibDecor = nullptr; - wl_seat *m_pSeat = nullptr; - wl_keyboard *m_pKeyboard = nullptr; - wl_pointer *m_pPointer = nullptr; - wl_touch *m_pTouch = nullptr; - zwp_locked_pointer_v1 *m_pLockedPointer = nullptr; - bool m_bPointerLocked = false; - wl_surface *m_pLockedSurface = nullptr; + wl_seat *m_pSeat = nullptr; + wl_keyboard *m_pKeyboard = nullptr; + wl_pointer *m_pPointer = nullptr; + wl_touch *m_pTouch = nullptr; + zwp_locked_pointer_v1 *m_pLockedPointer = nullptr; + bool m_bPointerLocked = false; + wl_surface *m_pLockedSurface = nullptr; zwp_relative_pointer_v1 *m_pRelativePointer = nullptr; bool m_bCanUseModifiers = false; std::unordered_map> m_FormatModifiers; - std::unordered_map m_ImportedFbs; + std::unordered_map m_ImportedFbs; - uint32_t m_uPointerEnterSerial = 0; - bool m_bMouseEntered = false; + uint32_t m_uPointerEnterSerial = 0; + bool m_bMouseEntered = false; uint32_t m_uKeyboardEnterSerial = 0; - bool m_bKeyboardEntered = false; + bool m_bKeyboardEntered = false; std::shared_ptr m_pCursorInfo; - wl_surface *m_pCursorSurface = nullptr; + wl_surface *m_pCursorSurface = nullptr; std::shared_ptr m_pDefaultCursorInfo; wl_surface *m_pDefaultCursorSurface = nullptr; }; - const wl_registry_listener CWaylandBackend::s_RegistryListener = - { - .global = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Registry_Global ), - .global_remove = WAYLAND_NULL(), - }; - const wl_output_listener CWaylandBackend::s_OutputListener = - { - .geometry = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Geometry ), - .mode = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Mode ), - .done = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Done ), - .scale = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Scale ), - .name = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Name ), - .description = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Description ), - }; - const wl_seat_listener CWaylandBackend::s_SeatListener = - { - .capabilities = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Seat_Capabilities ), - .name = WAYLAND_NULL(), + const wl_registry_listener CWaylandBackend::s_RegistryListener = { + .global = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_Registry_Global ), + .global_remove = WAYLAND_NULL( ), }; - const wl_pointer_listener CWaylandBackend::s_PointerListener = - { - .enter = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Pointer_Enter ), - .leave = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Pointer_Leave ), - .motion = WAYLAND_NULL(), - .button = WAYLAND_NULL(), - .axis = WAYLAND_NULL(), - .frame = WAYLAND_NULL(), - .axis_source = WAYLAND_NULL(), - .axis_stop = WAYLAND_NULL(), - .axis_discrete = WAYLAND_NULL(), - .axis_value120 = WAYLAND_NULL(), + const wl_output_listener CWaylandBackend::s_OutputListener = { + .geometry = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_Output_Geometry ), + .mode = + WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Mode ), + .done = + WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Done ), + .scale = + WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Scale ), + .name = + WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Output_Name ), + .description = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_Output_Description ), }; - const wl_keyboard_listener CWaylandBackend::s_KeyboardListener = - { - .keymap = WAYLAND_NULL(), - .enter = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Keyboard_Enter ), - .leave = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Keyboard_Leave ), - .key = WAYLAND_NULL(), - .modifiers = WAYLAND_NULL(), - .repeat_info = WAYLAND_NULL(), + const wl_seat_listener CWaylandBackend::s_SeatListener = { + .capabilities = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_Seat_Capabilities ), + .name = WAYLAND_NULL( ), }; - const zwp_locked_pointer_v1_listener CWaylandBackend::s_LockedPointerListener = - { - .locked = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_LockedPointer_Locked ), - .unlocked = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_LockedPointer_Unlocked ), - }; - - const wp_color_manager_v1_listener CWaylandBackend::s_WPColorManagerListener - { - .supported_intent = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_WPColorManager_SupportedIntent ), - .supported_feature = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_WPColorManager_SupportedFeature ), - .supported_tf_named = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_WPColorManager_SupportedTFNamed ), - .supported_primaries_named = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_WPColorManager_SupportedPrimariesNamed ), - .done = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_WPColorManager_ColorManagerDone ), + const wl_pointer_listener CWaylandBackend::s_PointerListener = { + .enter = + WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Pointer_Enter ), + .leave = + WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Pointer_Leave ), + .motion = WAYLAND_NULL( ), + .button = WAYLAND_NULL( ), + .axis = WAYLAND_NULL( ), + .frame = WAYLAND_NULL( ), + .axis_source = WAYLAND_NULL( ), + .axis_stop = WAYLAND_NULL( ), + .axis_discrete = WAYLAND_NULL( ), + .axis_value120 = WAYLAND_NULL( ), }; - const wl_data_source_listener CWaylandBackend::s_DataSourceListener = - { - .target = WAYLAND_NULL(), - .send = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataSource_Send ), - .cancelled = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_DataSource_Cancelled ), - .dnd_drop_performed = WAYLAND_NULL(), - .dnd_finished = WAYLAND_NULL(), - .action = WAYLAND_NULL(), + const wl_keyboard_listener CWaylandBackend::s_KeyboardListener = { + .keymap = WAYLAND_NULL( ), + .enter = + WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Keyboard_Enter ), + .leave = + WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Keyboard_Leave ), + .key = WAYLAND_NULL( ), + .modifiers = WAYLAND_NULL( ), + .repeat_info = WAYLAND_NULL( ), }; - const zwp_primary_selection_source_v1_listener CWaylandBackend::s_PrimarySelectionSourceListener = - { - .send = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_PrimarySelectionSource_Send ), - .cancelled = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_PrimarySelectionSource_Cancelled ), + const zwp_locked_pointer_v1_listener + CWaylandBackend::s_LockedPointerListener = { + .locked = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_LockedPointer_Locked ), + .unlocked = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_LockedPointer_Unlocked ), + }; + + const wp_color_manager_v1_listener + CWaylandBackend::s_WPColorManagerListener{ + .supported_intent = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_WPColorManager_SupportedIntent ), + .supported_feature = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_WPColorManager_SupportedFeature ), + .supported_tf_named = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_WPColorManager_SupportedTFNamed ), + .supported_primaries_named = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, + Wayland_WPColorManager_SupportedPrimariesNamed ), + .done = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_WPColorManager_ColorManagerDone ), + }; + const wl_data_source_listener CWaylandBackend::s_DataSourceListener = { + .target = WAYLAND_NULL( ), + .send = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_DataSource_Send ), + .cancelled = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_DataSource_Cancelled ), + .dnd_drop_performed = WAYLAND_NULL( ), + .dnd_finished = WAYLAND_NULL( ), + .action = WAYLAND_NULL( ), }; + const zwp_primary_selection_source_v1_listener + CWaylandBackend::s_PrimarySelectionSourceListener = { + .send = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_PrimarySelectionSource_Send ), + .cancelled = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_PrimarySelectionSource_Cancelled ), + }; ////////////////// // CWaylandFb ////////////////// - CWaylandFb::CWaylandFb( CWaylandBackend *pBackend, wl_buffer *pHostBuffer ) - : CBaseBackendFb() - , m_pBackend { pBackend } - , m_pHostBuffer { pHostBuffer } - { - wl_buffer_add_listener( pHostBuffer, &s_BufferListener, this ); - } + CWaylandFb::CWaylandFb( + CWaylandBackend *pBackend, wl_buffer *pHostBuffer ) : + CBaseBackendFb( ), m_pBackend{ pBackend }, m_pHostBuffer{ pHostBuffer } + { wl_buffer_add_listener( pHostBuffer, &s_BufferListener, this ); } - CWaylandFb::~CWaylandFb() + CWaylandFb::~CWaylandFb( ) { // I own the pHostBuffer. wl_buffer_destroy( m_pHostBuffer ); m_pHostBuffer = nullptr; } - void CWaylandFb::OnCompositorAcquire() + void CWaylandFb::OnCompositorAcquire( ) { // If the compositor has acquired us, track that // and increment the ref count. if ( !m_bCompositorAcquired ) { m_bCompositorAcquired = true; - IncRef(); + IncRef( ); } } - void CWaylandFb::OnCompositorRelease() + void CWaylandFb::OnCompositorRelease( ) { // Compositor has released us, decrement rc. - //assert( m_bCompositorAcquired ); + // assert( m_bCompositorAcquired ); if ( m_bCompositorAcquired ) { m_bCompositorAcquired = false; - DecRef(); + DecRef( ); } else { - xdg_log.errorf( "Compositor released us but we were not acquired. Oh no." ); + xdg_log.errorf( + "Compositor released us but we were not acquired. Oh no." ); } } @@ -968,74 +1246,69 @@ namespace gamescope xdg_log.debugf( "buffer_release: %p", pBuffer ); - OnCompositorRelease(); + OnCompositorRelease( ); } ////////////////// // CWaylandConnector ////////////////// - CWaylandConnector::CWaylandConnector( CWaylandBackend *pBackend, uint64_t ulVirtualConnectorKey ) - : CBaseBackendConnector{ ulVirtualConnectorKey } - , m_pBackend( pBackend ) - , m_Planes{ this, this, this, this, this, this, this, this } - { - m_HDRInfo.bAlwaysPatchEdid = true; - } + CWaylandConnector::CWaylandConnector( + CWaylandBackend *pBackend, uint64_t ulVirtualConnectorKey ) : + CBaseBackendConnector{ ulVirtualConnectorKey }, + m_pBackend( pBackend ), + m_Planes{ this, this, this, this, this, this, this, this } + { m_HDRInfo.bAlwaysPatchEdid = true; } - CWaylandConnector::~CWaylandConnector() - { - m_pBackend->OnConnectorDestroyed( this ); - } + CWaylandConnector::~CWaylandConnector( ) + { m_pBackend->OnConnectorDestroyed( this ); } - bool CWaylandConnector::UpdateEdid() + bool CWaylandConnector::UpdateEdid( ) { m_FakeEdid = GenerateSimpleEdid( g_nNestedWidth, g_nNestedHeight ); return true; } - bool CWaylandConnector::Init() + bool CWaylandConnector::Init( ) { for ( uint32_t i = 0; i < 8; i++ ) { - bool bSuccess = m_Planes[i].Init( i == 0 ? nullptr : &m_Planes[0], i == 0 ? nullptr : &m_Planes[ i - 1 ] ); - if ( !bSuccess ) - return false; + bool bSuccess = m_Planes[ i ].Init( + i == 0 ? nullptr : &m_Planes[ 0 ], + i == 0 ? nullptr : &m_Planes[ i - 1 ] ); + if ( !bSuccess ) return false; } if ( g_bFullscreen ) { m_bDesiredFullscreenState = true; - g_bFullscreen = false; - UpdateFullscreenState(); + g_bFullscreen = false; + UpdateFullscreenState( ); } - UpdateEdid(); - m_pBackend->HackUpdatePatchedEdid(); + UpdateEdid( ); + m_pBackend->HackUpdatePatchedEdid( ); - if ( g_bForceRelativeMouse ) - this->SetRelativeMouseMode( true ); + if ( g_bForceRelativeMouse ) this->SetRelativeMouseMode( true ); return true; } void CWaylandConnector::SetFullscreen( bool bFullscreen ) - { - m_bDesiredFullscreenState = bFullscreen; - } + { m_bDesiredFullscreenState = bFullscreen; } - void CWaylandConnector::UpdateFullscreenState() + void CWaylandConnector::UpdateFullscreenState( ) { - if ( !m_bVisible ) - g_bFullscreen = false; + if ( !m_bVisible ) g_bFullscreen = false; if ( m_bDesiredFullscreenState != g_bFullscreen && m_bVisible ) { if ( m_bDesiredFullscreenState ) - libdecor_frame_set_fullscreen( m_Planes[0].GetFrame(), nullptr ); + libdecor_frame_set_fullscreen( + m_Planes[ 0 ].GetFrame( ), nullptr ); else - libdecor_frame_unset_fullscreen( m_Planes[0].GetFrame() ); + libdecor_frame_unset_fullscreen( m_Planes[ 0 ].GetFrame( ) ); g_bFullscreen = m_bDesiredFullscreenState; } @@ -1043,7 +1316,7 @@ namespace gamescope int CWaylandConnector::Present( const FrameInfo_t *pFrameInfo, bool bAsync ) { - UpdateFullscreenState(); + UpdateFullscreenState( ); bool bNeedsFullComposite = false; @@ -1051,14 +1324,20 @@ namespace gamescope { uint32_t uCurrentPlane = 0; for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) - m_Planes[uCurrentPlane++].Present( nullptr ); + m_Planes[ uCurrentPlane++ ].Present( nullptr ); } else { - // TODO: Dedupe some of this composite check code between us and drm.cpp - bool bLayer0ScreenSize = close_enough(pFrameInfo->layers[0].scale.x, 1.0f) && close_enough(pFrameInfo->layers[0].scale.y, 1.0f); + // TODO: Dedupe some of this composite check code between us and + // drm.cpp + bool bLayer0ScreenSize = + close_enough( pFrameInfo->layers[ 0 ].scale.x, 1.0f ) && + close_enough( pFrameInfo->layers[ 0 ].scale.y, 1.0f ); - bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; + bool bNeedsCompositeFromFilter = + ( g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || + g_upscaleFilter == GamescopeUpscaleFilter::PIXEL ) && + !bLayer0ScreenSize; bNeedsFullComposite |= cv_composite_force; bNeedsFullComposite |= pFrameInfo->useFSRLayer0; @@ -1067,51 +1346,57 @@ namespace gamescope bNeedsFullComposite |= bNeedsCompositeFromFilter; bNeedsFullComposite |= g_bColorSliderInUse; bNeedsFullComposite |= pFrameInfo->bFadingOut; - bNeedsFullComposite |= !g_reshade_effect.empty(); + bNeedsFullComposite |= !g_reshade_effect.empty( ); - if ( g_bOutputHDREnabled ) - bNeedsFullComposite |= g_bHDRItmEnable; + if ( g_bOutputHDREnabled ) bNeedsFullComposite |= g_bHDRItmEnable; - if ( !m_pBackend->SupportsColorManagement() ) - bNeedsFullComposite |= ColorspaceIsHDR( pFrameInfo->layers[0].colorspace ); + if ( !m_pBackend->SupportsColorManagement( ) ) + bNeedsFullComposite |= + ColorspaceIsHDR( pFrameInfo->layers[ 0 ].colorspace ); - bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); + bNeedsFullComposite |= + !!( g_uCompositeDebug & CompositeDebugFlag::Heatmap ); if ( !bNeedsFullComposite ) { bool bNeedsBacking = true; if ( pFrameInfo->layerCount >= 1 ) { - if ( pFrameInfo->layers[0].isScreenSize() && !pFrameInfo->layers[0].hasAlpha() ) + if ( pFrameInfo->layers[ 0 ].isScreenSize( ) && + !pFrameInfo->layers[ 0 ].hasAlpha( ) ) bNeedsBacking = false; } uint32_t uCurrentPlane = 0; if ( bNeedsBacking ) { - m_pBackend->GetBlackFb()->OnCompositorAcquire(); + m_pBackend->GetBlackFb( )->OnCompositorAcquire( ); - CWaylandPlane *pPlane = &m_Planes[uCurrentPlane++]; + CWaylandPlane *pPlane = &m_Planes[ uCurrentPlane++ ]; pPlane->Present( - WaylandPlaneState - { - .pBuffer = m_pBackend->GetBlackFb()->GetHostBuffer(), + WaylandPlaneState{ + .pBuffer = + m_pBackend->GetBlackFb( )->GetHostBuffer( ), .flSrcWidth = 1.0, .flSrcHeight = 1.0, .nDstWidth = int32_t( g_nOutputWidth ), .nDstHeight = int32_t( g_nOutputHeight ), - .eColorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, - .bOpaque = true, - .uFractionalScale = pPlane->GetScale(), + .eColorspace = + GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, + .bOpaque = true, + .uFractionalScale = pPlane->GetScale( ), } ); } for ( int i = 0; i < 8 && uCurrentPlane < 8; i++ ) - m_Planes[uCurrentPlane++].Present( i < pFrameInfo->layerCount ? &pFrameInfo->layers[i] : nullptr ); + m_Planes[ uCurrentPlane++ ].Present( + i < pFrameInfo->layerCount ? &pFrameInfo->layers[ i ] + : nullptr ); } else { - std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); + std::optional oCompositeResult = vulkan_composite( + ( FrameInfo_t * )pFrameInfo, nullptr, false ); if ( !oCompositeResult ) { @@ -1125,135 +1410,129 @@ namespace gamescope compositeLayer.scale.x = 1.0; compositeLayer.scale.y = 1.0; compositeLayer.opacity = 1.0; - compositeLayer.zpos = g_zposBase; + compositeLayer.zpos = g_zposBase; - compositeLayer.tex = vulkan_get_last_output_image( false, false ); + compositeLayer.tex = + vulkan_get_last_output_image( false, false ); compositeLayer.applyColorMgmt = false; compositeLayer.filter = GamescopeUpscaleFilter::NEAREST; - compositeLayer.ctm = nullptr; - compositeLayer.colorspace = pFrameInfo->outputEncodingEOTF == EOTF_PQ ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + compositeLayer.ctm = nullptr; + compositeLayer.colorspace = + pFrameInfo->outputEncodingEOTF == EOTF_PQ + ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ + : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - m_Planes[0].Present( &compositeLayer ); + m_Planes[ 0 ].Present( &compositeLayer ); for ( int i = 1; i < 8; i++ ) - m_Planes[i].Present( nullptr ); + m_Planes[ i ].Present( nullptr ); } } for ( int i = 7; i >= 0; i-- ) - m_Planes[i].Commit(); + m_Planes[ i ].Commit( ); - wl_display_flush( m_pBackend->GetDisplay() ); + wl_display_flush( m_pBackend->GetDisplay( ) ); - GetVBlankTimer().UpdateWasCompositing( bNeedsFullComposite ); - GetVBlankTimer().UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); + GetVBlankTimer( ).UpdateWasCompositing( bNeedsFullComposite ); + GetVBlankTimer( ).UpdateLastDrawTime( + get_time_in_nanos( ) - g_SteamCompMgrVBlankTime.ulWakeupTime ); - m_pBackend->PollState(); + m_pBackend->PollState( ); return 0; } - GamescopeScreenType CWaylandConnector::GetScreenType() const - { - return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; - } - GamescopePanelOrientation CWaylandConnector::GetCurrentOrientation() const - { - return GAMESCOPE_PANEL_ORIENTATION_0; - } - bool CWaylandConnector::SupportsHDR() const - { - return GetHDRInfo().IsHDR10(); - } - bool CWaylandConnector::IsHDRActive() const + GamescopeScreenType CWaylandConnector::GetScreenType( ) const + { return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; } + GamescopePanelOrientation CWaylandConnector::GetCurrentOrientation( ) const + { return GAMESCOPE_PANEL_ORIENTATION_0; } + bool CWaylandConnector::SupportsHDR( ) const + { return GetHDRInfo( ).IsHDR10( ); } + bool CWaylandConnector::IsHDRActive( ) const { // XXX: blah return false; } - const BackendConnectorHDRInfo &CWaylandConnector::GetHDRInfo() const - { - return m_HDRInfo; - } - bool CWaylandConnector::IsVRRActive() const - { - return cv_adaptive_sync && m_bHostCompositorIsCurrentlyVRR; - } - std::span CWaylandConnector::GetModes() const - { - return std::span{}; - } + const BackendConnectorHDRInfo &CWaylandConnector::GetHDRInfo( ) const + { return m_HDRInfo; } + bool CWaylandConnector::IsVRRActive( ) const + { return cv_adaptive_sync && m_bHostCompositorIsCurrentlyVRR; } + std::span CWaylandConnector::GetModes( ) const + { return std::span{}; } - bool CWaylandConnector::SupportsVRR() const - { - return CurrentDisplaySupportsVRR(); - } + bool CWaylandConnector::SupportsVRR( ) const + { return CurrentDisplaySupportsVRR( ); } - std::span CWaylandConnector::GetRawEDID() const - { - return std::span{ m_FakeEdid.begin(), m_FakeEdid.end() }; - } - std::span CWaylandConnector::GetValidDynamicRefreshRates() const + std::span CWaylandConnector::GetRawEDID( ) const { - return std::span{}; + return std::span{ m_FakeEdid.begin( ), + m_FakeEdid.end( ) }; } + std::span + CWaylandConnector::GetValidDynamicRefreshRates( ) const + { return std::span{}; } void CWaylandConnector::GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const + bool bHDR10, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const { *displayColorimetry = m_DisplayColorimetry; - *displayEOTF = EOTF_Gamma22; + *displayEOTF = EOTF_Gamma22; - if ( bHDR10 && GetHDRInfo().IsHDR10() ) + if ( bHDR10 && GetHDRInfo( ).IsHDR10( ) ) { - // For HDR10 output, expected content colorspace != native colorspace. + // For HDR10 output, expected content colorspace != native + // colorspace. *outputEncodingColorimetry = displaycolorimetry_2020; - *outputEncodingEOTF = GetHDRInfo().eOutputEncodingEOTF; + *outputEncodingEOTF = GetHDRInfo( ).eOutputEncodingEOTF; } else { // We always use default 'perceptual' intent, so // this should be correct for SDR content. *outputEncodingColorimetry = m_DisplayColorimetry; - *outputEncodingEOTF = EOTF_Gamma22; + *outputEncodingEOTF = EOTF_Gamma22; } } - - void CWaylandConnector::SetCursorImage( std::shared_ptr info ) - { - m_pBackend->SetCursorImage( std::move( info ) ); - } + void CWaylandConnector::SetCursorImage( + std::shared_ptr info ) + { m_pBackend->SetCursorImage( std::move( info ) ); } void CWaylandConnector::SetRelativeMouseMode( bool bRelative ) { - // TODO: Do more tracking across multiple connectors, and activity here if we ever want to use this. - m_pBackend->SetRelativeMouseMode( m_Planes[0].GetSurface(), bRelative ); + // TODO: Do more tracking across multiple connectors, and activity here + // if we ever want to use this. + m_pBackend->SetRelativeMouseMode( + m_Planes[ 0 ].GetSurface( ), bRelative ); } void CWaylandConnector::SetVisible( bool bVisible ) { - if ( m_bVisible == bVisible ) - return; + if ( m_bVisible == bVisible ) return; m_bVisible = bVisible; - force_repaint(); + force_repaint( ); } void CWaylandConnector::SetTitle( std::shared_ptr pAppTitle ) { std::string szTitle = pAppTitle ? *pAppTitle : "gamescope"; - if ( g_bGrabbed ) - szTitle += " (grabbed)"; - libdecor_frame_set_title( m_Planes[0].GetFrame(), szTitle.c_str() ); + if ( g_bGrabbed ) szTitle += " (grabbed)"; + libdecor_frame_set_title( m_Planes[ 0 ].GetFrame( ), szTitle.c_str( ) ); } - void CWaylandConnector::SetIcon( std::shared_ptr> uIconPixels ) + void CWaylandConnector::SetIcon( + std::shared_ptr> uIconPixels ) { - if ( !m_pBackend->GetToplevelIconManager() ) - return; + if ( !m_pBackend->GetToplevelIconManager( ) ) return; - if ( uIconPixels && uIconPixels->size() >= 3 ) + if ( uIconPixels && uIconPixels->size( ) >= 3 ) { - xdg_toplevel_icon_v1 *pIcon = xdg_toplevel_icon_manager_v1_create_icon( m_pBackend->GetToplevelIconManager() ); + xdg_toplevel_icon_v1 *pIcon = + xdg_toplevel_icon_manager_v1_create_icon( + m_pBackend->GetToplevelIconManager( ) ); if ( !pIcon ) { xdg_log.errorf( "Failed to create xdg_toplevel_icon_v1" ); @@ -1261,12 +1540,12 @@ namespace gamescope } defer( xdg_toplevel_icon_v1_destroy( pIcon ) ); - const uint32_t uWidth = ( *uIconPixels )[0]; - const uint32_t uHeight = ( *uIconPixels )[1]; + const uint32_t uWidth = ( *uIconPixels )[ 0 ]; + const uint32_t uHeight = ( *uIconPixels )[ 1 ]; const uint32_t uStride = uWidth * 4; const uint32_t uSize = uStride * uHeight; - int32_t nFd = CreateShmBuffer( uSize, &( *uIconPixels )[2] ); + int32_t nFd = CreateShmBuffer( uSize, &( *uIconPixels )[ 2 ] ); if ( nFd < 0 ) { xdg_log.errorf( "Failed to create/map shm buffer" ); @@ -1274,53 +1553,84 @@ namespace gamescope } defer( close( nFd ) ); - wl_shm_pool *pPool = wl_shm_create_pool( m_pBackend->GetShm(), nFd, uSize ); + wl_shm_pool *pPool = + wl_shm_create_pool( m_pBackend->GetShm( ), nFd, uSize ); defer( wl_shm_pool_destroy( pPool ) ); - wl_buffer *pBuffer = wl_shm_pool_create_buffer( pPool, 0, uWidth, uHeight, uStride, WL_SHM_FORMAT_ARGB8888 ); + wl_buffer *pBuffer = wl_shm_pool_create_buffer( + pPool, 0, uWidth, uHeight, uStride, WL_SHM_FORMAT_ARGB8888 ); defer( wl_buffer_destroy( pBuffer ) ); xdg_toplevel_icon_v1_add_buffer( pIcon, pBuffer, 1 ); - xdg_toplevel_icon_manager_v1_set_icon( m_pBackend->GetToplevelIconManager(), m_Planes[0].GetXdgToplevel(), pIcon ); + xdg_toplevel_icon_manager_v1_set_icon( + m_pBackend->GetToplevelIconManager( ), + m_Planes[ 0 ].GetXdgToplevel( ), + pIcon ); } else { - xdg_toplevel_icon_manager_v1_set_icon( m_pBackend->GetToplevelIconManager(), m_Planes[0].GetXdgToplevel(), nullptr ); + xdg_toplevel_icon_manager_v1_set_icon( + m_pBackend->GetToplevelIconManager( ), + m_Planes[ 0 ].GetXdgToplevel( ), + nullptr ); } } - void CWaylandConnector::SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) + void CWaylandConnector::SetSelection( + std::shared_ptr szContents, GamescopeSelection eSelection ) { if ( m_pBackend->m_pDataDeviceManager && !m_pBackend->m_pDataDevice ) - m_pBackend->m_pDataDevice = wl_data_device_manager_get_data_device( m_pBackend->m_pDataDeviceManager, m_pBackend->m_pSeat ); + m_pBackend->m_pDataDevice = wl_data_device_manager_get_data_device( + m_pBackend->m_pDataDeviceManager, m_pBackend->m_pSeat ); - if ( m_pBackend->m_pPrimarySelectionDeviceManager && !m_pBackend->m_pPrimarySelectionDevice ) - m_pBackend->m_pPrimarySelectionDevice = zwp_primary_selection_device_manager_v1_get_device( m_pBackend->m_pPrimarySelectionDeviceManager, m_pBackend->m_pSeat ); + if ( m_pBackend->m_pPrimarySelectionDeviceManager && + !m_pBackend->m_pPrimarySelectionDevice ) + m_pBackend->m_pPrimarySelectionDevice = + zwp_primary_selection_device_manager_v1_get_device( + m_pBackend->m_pPrimarySelectionDeviceManager, + m_pBackend->m_pSeat ); - if ( eSelection == GAMESCOPE_SELECTION_CLIPBOARD && m_pBackend->m_pDataDevice ) + if ( eSelection == GAMESCOPE_SELECTION_CLIPBOARD && + m_pBackend->m_pDataDevice ) { m_pBackend->m_pClipboard = szContents; - wl_data_source *source = wl_data_device_manager_create_data_source( m_pBackend->m_pDataDeviceManager ); - wl_data_source_add_listener( source, &m_pBackend->s_DataSourceListener, m_pBackend ); + wl_data_source *source = wl_data_device_manager_create_data_source( + m_pBackend->m_pDataDeviceManager ); + wl_data_source_add_listener( + source, &m_pBackend->s_DataSourceListener, m_pBackend ); wl_data_source_offer( source, "text/plain" ); wl_data_source_offer( source, "text/plain;charset=utf-8" ); wl_data_source_offer( source, "TEXT" ); wl_data_source_offer( source, "STRING" ); wl_data_source_offer( source, "UTF8_STRING" ); - wl_data_device_set_selection( m_pBackend->m_pDataDevice, source, m_pBackend->m_uKeyboardEnterSerial ); + wl_data_device_set_selection( + m_pBackend->m_pDataDevice, + source, + m_pBackend->m_uKeyboardEnterSerial ); } - else if ( eSelection == GAMESCOPE_SELECTION_PRIMARY && m_pBackend->m_pPrimarySelectionDevice ) + else if ( + eSelection == GAMESCOPE_SELECTION_PRIMARY && + m_pBackend->m_pPrimarySelectionDevice ) { m_pBackend->m_pPrimarySelection = szContents; - zwp_primary_selection_source_v1 *source = zwp_primary_selection_device_manager_v1_create_source( m_pBackend->m_pPrimarySelectionDeviceManager ); - zwp_primary_selection_source_v1_add_listener( source, &m_pBackend->s_PrimarySelectionSourceListener, m_pBackend ); + zwp_primary_selection_source_v1 *source = + zwp_primary_selection_device_manager_v1_create_source( + m_pBackend->m_pPrimarySelectionDeviceManager ); + zwp_primary_selection_source_v1_add_listener( + source, + &m_pBackend->s_PrimarySelectionSourceListener, + m_pBackend ); zwp_primary_selection_source_v1_offer( source, "text/plain" ); - zwp_primary_selection_source_v1_offer( source, "text/plain;charset=utf-8" ); + zwp_primary_selection_source_v1_offer( + source, "text/plain;charset=utf-8" ); zwp_primary_selection_source_v1_offer( source, "TEXT" ); zwp_primary_selection_source_v1_offer( source, "STRING" ); zwp_primary_selection_source_v1_offer( source, "UTF8_STRING" ); - zwp_primary_selection_device_v1_set_selection( m_pBackend->m_pPrimarySelectionDevice, source, m_pBackend->m_uPointerEnterSerial ); + zwp_primary_selection_device_v1_set_selection( + m_pBackend->m_pPrimarySelectionDevice, + source, + m_pBackend->m_uPointerEnterSerial ); } } @@ -1328,109 +1638,131 @@ namespace gamescope // CWaylandPlane ////////////////// - CWaylandPlane::CWaylandPlane( CWaylandConnector *pConnector ) - : m_pConnector{ pConnector } - , m_pBackend{ pConnector->GetBackend() } - { - } + CWaylandPlane::CWaylandPlane( CWaylandConnector *pConnector ) : + m_pConnector{ pConnector }, m_pBackend{ pConnector->GetBackend( ) } + {} - CWaylandPlane::~CWaylandPlane() + CWaylandPlane::~CWaylandPlane( ) { std::scoped_lock lock{ m_PlaneStateLock }; m_eWindowState = LIBDECOR_WINDOW_STATE_NONE; - m_pOutputs.clear(); + m_pOutputs.clear( ); m_bNeedsDecorCommit = false; m_oCurrentPlaneState = std::nullopt; - if ( m_pFrame ) - libdecor_frame_unref( m_pFrame ); // Ew. + if ( m_pFrame ) libdecor_frame_unref( m_pFrame ); // Ew. - if ( m_pSubsurface ) - wl_subsurface_destroy( m_pSubsurface ); + if ( m_pSubsurface ) wl_subsurface_destroy( m_pSubsurface ); if ( m_pFractionalScale ) wp_fractional_scale_v1_destroy( m_pFractionalScale ); if ( m_pWPColorManagedSurface ) wp_color_management_surface_v1_destroy( m_pWPColorManagedSurface ); if ( m_pWPColorManagedSurfaceFeedback ) - wp_color_management_surface_feedback_v1_destroy( m_pWPColorManagedSurfaceFeedback ); + wp_color_management_surface_feedback_v1_destroy( + m_pWPColorManagedSurfaceFeedback ); if ( m_pFrogColorManagedSurface ) frog_color_managed_surface_destroy( m_pFrogColorManagedSurface ); - if ( m_pViewport ) - wp_viewport_destroy( m_pViewport ); - if ( m_pSurface ) - wl_surface_destroy( m_pSurface ); + if ( m_pViewport ) wp_viewport_destroy( m_pViewport ); + if ( m_pSurface ) wl_surface_destroy( m_pSurface ); } - bool CWaylandPlane::Init( CWaylandPlane *pParent, CWaylandPlane *pSiblingBelow ) + bool + CWaylandPlane::Init( CWaylandPlane *pParent, CWaylandPlane *pSiblingBelow ) { m_pParent = pParent; - m_pSurface = wl_compositor_create_surface( m_pBackend->GetCompositor() ); + m_pSurface = + wl_compositor_create_surface( m_pBackend->GetCompositor( ) ); wl_surface_set_user_data( m_pSurface, this ); wl_surface_add_listener( m_pSurface, &s_SurfaceListener, this ); - m_pViewport = wp_viewporter_get_viewport( m_pBackend->GetViewporter(), m_pSurface ); + m_pViewport = wp_viewporter_get_viewport( + m_pBackend->GetViewporter( ), m_pSurface ); - if ( m_pBackend->GetWPColorManager() ) + if ( m_pBackend->GetWPColorManager( ) ) { - m_pWPColorManagedSurface = wp_color_manager_v1_get_surface( m_pBackend->GetWPColorManager(), m_pSurface ); - m_pWPColorManagedSurfaceFeedback = wp_color_manager_v1_get_surface_feedback( m_pBackend->GetWPColorManager(), m_pSurface ); + m_pWPColorManagedSurface = wp_color_manager_v1_get_surface( + m_pBackend->GetWPColorManager( ), m_pSurface ); + m_pWPColorManagedSurfaceFeedback = + wp_color_manager_v1_get_surface_feedback( + m_pBackend->GetWPColorManager( ), m_pSurface ); // Only add the listener for the toplevel to avoid useless spam. if ( !pParent ) - wp_color_management_surface_feedback_v1_add_listener( m_pWPColorManagedSurfaceFeedback, &s_WPColorManagementSurfaceListener, this ); + wp_color_management_surface_feedback_v1_add_listener( + m_pWPColorManagedSurfaceFeedback, + &s_WPColorManagementSurfaceListener, + this ); - UpdateWPPreferredColorManagement(); + UpdateWPPreferredColorManagement( ); } - else if ( m_pBackend->GetFrogColorManagementFactory() ) + else if ( m_pBackend->GetFrogColorManagementFactory( ) ) { - m_pFrogColorManagedSurface = frog_color_management_factory_v1_get_color_managed_surface( m_pBackend->GetFrogColorManagementFactory(), m_pSurface ); + m_pFrogColorManagedSurface = + frog_color_management_factory_v1_get_color_managed_surface( + m_pBackend->GetFrogColorManagementFactory( ), m_pSurface ); // Only add the listener for the toplevel to avoid useless spam. if ( !pParent ) - frog_color_managed_surface_add_listener( m_pFrogColorManagedSurface, &s_FrogColorManagedSurfaceListener, this ); + frog_color_managed_surface_add_listener( + m_pFrogColorManagedSurface, + &s_FrogColorManagedSurfaceListener, + this ); } - if ( m_pBackend->GetFractionalScaleManager() ) + if ( m_pBackend->GetFractionalScaleManager( ) ) { - m_pFractionalScale = wp_fractional_scale_manager_v1_get_fractional_scale( m_pBackend->GetFractionalScaleManager(), m_pSurface ); + m_pFractionalScale = + wp_fractional_scale_manager_v1_get_fractional_scale( + m_pBackend->GetFractionalScaleManager( ), m_pSurface ); if ( !pParent ) - wp_fractional_scale_v1_add_listener( m_pFractionalScale, &s_FractionalScaleListener, this ); + wp_fractional_scale_v1_add_listener( + m_pFractionalScale, &s_FractionalScaleListener, this ); } if ( !pParent ) { - wl_proxy_set_tag( (wl_proxy *)m_pSurface, &GAMESCOPE_toplevel_tag ); - m_pFrame = libdecor_decorate( m_pBackend->GetLibDecor(), m_pSurface, &s_LibDecorFrameInterface, this ); + wl_proxy_set_tag( + ( wl_proxy * )m_pSurface, &GAMESCOPE_toplevel_tag ); + m_pFrame = libdecor_decorate( + m_pBackend->GetLibDecor( ), + m_pSurface, + &s_LibDecorFrameInterface, + this ); libdecor_frame_set_title( m_pFrame, "Gamescope" ); libdecor_frame_set_app_id( m_pFrame, "gamescope" ); libdecor_frame_map( m_pFrame ); } else { - wl_proxy_set_tag( (wl_proxy *)m_pSurface, &GAMESCOPE_plane_tag ); - m_pSubsurface = wl_subcompositor_get_subsurface( m_pBackend->GetSubcompositor(), m_pSurface, pParent->GetSurface() ); - wl_subsurface_place_above( m_pSubsurface, pSiblingBelow->GetSurface() ); + wl_proxy_set_tag( ( wl_proxy * )m_pSurface, &GAMESCOPE_plane_tag ); + m_pSubsurface = wl_subcompositor_get_subsurface( + m_pBackend->GetSubcompositor( ), + m_pSurface, + pParent->GetSurface( ) ); + wl_subsurface_place_above( + m_pSubsurface, pSiblingBelow->GetSurface( ) ); wl_subsurface_set_sync( m_pSubsurface ); - // Allow pParent to receive input while covered by subsurface planes - wl_surface_set_input_region( m_pSurface, m_pBackend->GetEmptyRegion() ); + // Allow pParent to receive input while covered by subsurface planes + wl_surface_set_input_region( + m_pSurface, m_pBackend->GetEmptyRegion( ) ); } wl_surface_commit( m_pSurface ); - wl_display_roundtrip( m_pBackend->GetDisplay() ); + wl_display_roundtrip( m_pBackend->GetDisplay( ) ); if ( m_pFrame ) - libdecor_frame_set_visibility( m_pFrame, !g_bBorderlessOutputWindow ); + libdecor_frame_set_visibility( + m_pFrame, !g_bBorderlessOutputWindow ); return true; } - uint32_t CWaylandPlane::GetScale() const + uint32_t CWaylandPlane::GetScale( ) const { - if ( m_pParent ) - return m_pParent->GetScale(); + if ( m_pParent ) return m_pParent->GetScale( ); return m_uFractionalScale; } @@ -1448,116 +1780,188 @@ namespace gamescope if ( m_pFrame ) { - struct wp_presentation_feedback *pFeedback = wp_presentation_feedback( m_pBackend->GetPresentation(), m_pSurface ); - wp_presentation_feedback_add_listener( pFeedback, &s_PresentationFeedbackListener, this ); + struct wp_presentation_feedback *pFeedback = + wp_presentation_feedback( + m_pBackend->GetPresentation( ), m_pSurface ); + wp_presentation_feedback_add_listener( + pFeedback, &s_PresentationFeedbackListener, this ); } if ( m_pWPColorManagedSurface ) { - WaylandPlaneColorState colorState = - { + WaylandPlaneColorState colorState = { .eColorspace = oState->eColorspace, .pHDRMetadata = oState->pHDRMetadata, }; float flScale = cv_wayland_hdr10_saturation_scale; - if ( !m_ColorState || *m_ColorState != colorState || m_flPreviousSaturationScale != flScale ) + if ( !m_ColorState || *m_ColorState != colorState || + m_flPreviousSaturationScale != flScale ) { - m_ColorState = colorState; + m_ColorState = colorState; m_flPreviousSaturationScale = flScale; if ( m_pCurrentImageDescription ) { - wp_image_description_v1_destroy( m_pCurrentImageDescription ); + wp_image_description_v1_destroy( + m_pCurrentImageDescription ); m_pCurrentImageDescription = nullptr; } - if ( oState->eColorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ) + if ( oState->eColorspace == + GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ) { - m_pCurrentImageDescription = wp_color_manager_v1_create_windows_scrgb( m_pBackend->GetWPColorManager() ); + m_pCurrentImageDescription = + wp_color_manager_v1_create_windows_scrgb( + m_pBackend->GetWPColorManager( ) ); } - else if ( oState->eColorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ) + else if ( + oState->eColorspace == + GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ) { - wp_image_description_creator_params_v1 *pParams = wp_color_manager_v1_create_parametric_creator( m_pBackend->GetWPColorManager() ); + wp_image_description_creator_params_v1 *pParams = + wp_color_manager_v1_create_parametric_creator( + m_pBackend->GetWPColorManager( ) ); if ( close_enough( flScale, 1.0f ) ) { - wp_image_description_creator_params_v1_set_primaries_named( pParams, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 ); + wp_image_description_creator_params_v1_set_primaries_named( + pParams, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 ); } else { - wp_image_description_creator_params_v1_set_primaries( pParams, - (int32_t)(0.708 * flScale * 1'000'000.0), - (int32_t)(0.292 / flScale * 1'000'000.0), - (int32_t)(0.170 / flScale * 1'000'000.0), - (int32_t)(0.797 * flScale * 1'000'000.0), - (int32_t)(0.131 / flScale * 1'000'000.0), - (int32_t)(0.046 / flScale * 1'000'000.0), - (int32_t)(0.3127 * 1'000'000.0), - (int32_t)(0.3290 * 1'000'000.0) ); + wp_image_description_creator_params_v1_set_primaries( + pParams, + ( int32_t )( 0.708 * flScale * 1'000'000.0 ), + ( int32_t )( 0.292 / flScale * 1'000'000.0 ), + ( int32_t )( 0.170 / flScale * 1'000'000.0 ), + ( int32_t )( 0.797 * flScale * 1'000'000.0 ), + ( int32_t )( 0.131 / flScale * 1'000'000.0 ), + ( int32_t )( 0.046 / flScale * 1'000'000.0 ), + ( int32_t )( 0.3127 * 1'000'000.0 ), + ( int32_t )( 0.3290 * 1'000'000.0 ) ); } - wp_image_description_creator_params_v1_set_tf_named( pParams, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ ); + wp_image_description_creator_params_v1_set_tf_named( + pParams, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ ); if ( m_ColorState->pHDRMetadata ) { - const hdr_metadata_infoframe *pInfoframe = &m_ColorState->pHDRMetadata->View().hdmi_metadata_type1; + const hdr_metadata_infoframe *pInfoframe = + &m_ColorState->pHDRMetadata + ->View( ) + .hdmi_metadata_type1; - wp_image_description_creator_params_v1_set_mastering_display_primaries( pParams, + wp_image_description_creator_params_v1_set_mastering_display_primaries( + pParams, // Rescale... - (((int32_t)pInfoframe->display_primaries[0].x) * 1'000'000) / 0xC350, - (((int32_t)pInfoframe->display_primaries[0].y) * 1'000'000) / 0xC350, - (((int32_t)pInfoframe->display_primaries[1].x) * 1'000'000) / 0xC350, - (((int32_t)pInfoframe->display_primaries[1].y) * 1'000'000) / 0xC350, - (((int32_t)pInfoframe->display_primaries[2].x) * 1'000'000) / 0xC350, - (((int32_t)pInfoframe->display_primaries[2].y) * 1'000'000) / 0xC350, - (((int32_t)pInfoframe->white_point.x) * 1'000'000) / 0xC350, - (((int32_t)pInfoframe->white_point.y) * 1'000'000) / 0xC350); - - wp_image_description_creator_params_v1_set_mastering_luminance( pParams, + ( ( ( int32_t )pInfoframe + ->display_primaries[ 0 ] + .x ) * + 1'000'000 ) / + 0xC350, + ( ( ( int32_t )pInfoframe + ->display_primaries[ 0 ] + .y ) * + 1'000'000 ) / + 0xC350, + ( ( ( int32_t )pInfoframe + ->display_primaries[ 1 ] + .x ) * + 1'000'000 ) / + 0xC350, + ( ( ( int32_t )pInfoframe + ->display_primaries[ 1 ] + .y ) * + 1'000'000 ) / + 0xC350, + ( ( ( int32_t )pInfoframe + ->display_primaries[ 2 ] + .x ) * + 1'000'000 ) / + 0xC350, + ( ( ( int32_t )pInfoframe + ->display_primaries[ 2 ] + .y ) * + 1'000'000 ) / + 0xC350, + ( ( ( int32_t )pInfoframe->white_point.x ) * + 1'000'000 ) / + 0xC350, + ( ( ( int32_t )pInfoframe->white_point.y ) * + 1'000'000 ) / + 0xC350 ); + + wp_image_description_creator_params_v1_set_mastering_luminance( + pParams, pInfoframe->min_display_mastering_luminance, pInfoframe->max_display_mastering_luminance ); - wp_image_description_creator_params_v1_set_max_cll( pParams, - pInfoframe->max_cll ); + wp_image_description_creator_params_v1_set_max_cll( + pParams, pInfoframe->max_cll ); - wp_image_description_creator_params_v1_set_max_fall( pParams, - pInfoframe->max_fall ); + wp_image_description_creator_params_v1_set_max_fall( + pParams, pInfoframe->max_fall ); } - m_pCurrentImageDescription = wp_image_description_creator_params_v1_create( pParams ); + m_pCurrentImageDescription = + wp_image_description_creator_params_v1_create( + pParams ); } } if ( m_pCurrentImageDescription ) { - wp_color_management_surface_v1_set_image_description( m_pWPColorManagedSurface, m_pCurrentImageDescription, WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL ); + wp_color_management_surface_v1_set_image_description( + m_pWPColorManagedSurface, + m_pCurrentImageDescription, + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL ); } else { - wp_color_management_surface_v1_unset_image_description( m_pWPColorManagedSurface ); + wp_color_management_surface_v1_unset_image_description( + m_pWPColorManagedSurface ); } } else if ( m_pFrogColorManagedSurface ) { - frog_color_managed_surface_set_render_intent( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_RENDER_INTENT_PERCEPTUAL ); + frog_color_managed_surface_set_render_intent( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_RENDER_INTENT_PERCEPTUAL ); switch ( oState->eColorspace ) { default: case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: - frog_color_managed_surface_set_known_container_color_volume( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_PRIMARIES_UNDEFINED ); - frog_color_managed_surface_set_known_container_color_volume( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_UNDEFINED ); + frog_color_managed_surface_set_known_container_color_volume( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_PRIMARIES_UNDEFINED ); + frog_color_managed_surface_set_known_container_color_volume( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_UNDEFINED ); break; case GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR: case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: - frog_color_managed_surface_set_known_container_color_volume( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC709 ); - frog_color_managed_surface_set_known_transfer_function( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22 ); + frog_color_managed_surface_set_known_container_color_volume( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC709 ); + frog_color_managed_surface_set_known_transfer_function( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22 ); break; case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: - frog_color_managed_surface_set_known_container_color_volume( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC2020 ); - frog_color_managed_surface_set_known_transfer_function( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ); + frog_color_managed_surface_set_known_container_color_volume( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC2020 ); + frog_color_managed_surface_set_known_transfer_function( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ); break; case GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB: - frog_color_managed_surface_set_known_container_color_volume( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC709 ); - frog_color_managed_surface_set_known_transfer_function( m_pFrogColorManagedSurface, FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SCRGB_LINEAR ); + frog_color_managed_surface_set_known_container_color_volume( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC709 ); + frog_color_managed_surface_set_known_transfer_function( + m_pFrogColorManagedSurface, + FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SCRGB_LINEAR ); break; } } @@ -1587,7 +1991,9 @@ namespace gamescope // Use the subsurface set_position thing instead. wl_surface_attach( m_pSurface, oState->pBuffer, 0, 0 ); wl_surface_damage( m_pSurface, 0, 0, INT32_MAX, INT32_MAX ); - wl_surface_set_opaque_region( m_pSurface, oState->bOpaque ? m_pBackend->GetFullRegion() : nullptr ); + wl_surface_set_opaque_region( + m_pSurface, + oState->bOpaque ? m_pBackend->GetFullRegion( ) : nullptr ); wl_surface_set_buffer_scale( m_pSurface, 1 ); } else @@ -1599,7 +2005,7 @@ namespace gamescope void CWaylandPlane::CommitLibDecor( libdecor_configuration *pConfiguration ) { - int32_t uScale = GetScale(); + int32_t uScale = GetScale( ); libdecor_state *pState = libdecor_state_new( WaylandScaleToLogical( g_nOutputWidth, uScale ), WaylandScaleToLogical( g_nOutputHeight, uScale ) ); @@ -1607,7 +2013,7 @@ namespace gamescope libdecor_state_free( pState ); } - void CWaylandPlane::Commit() + void CWaylandPlane::Commit( ) { if ( m_bNeedsDecorCommit ) { @@ -1618,39 +2024,44 @@ namespace gamescope wl_surface_commit( m_pSurface ); } - xdg_toplevel *CWaylandPlane::GetXdgToplevel() const + xdg_toplevel *CWaylandPlane::GetXdgToplevel( ) const { - if ( !m_pFrame ) - return nullptr; + if ( !m_pFrame ) return nullptr; return libdecor_frame_get_xdg_toplevel( m_pFrame ); } void CWaylandPlane::Present( const FrameInfo_t::Layer_t *pLayer ) { - CWaylandFb *pWaylandFb = pLayer && pLayer->tex != nullptr ? static_cast( pLayer->tex->GetBackendFb()->EnsureImported() ) : nullptr; - wl_buffer *pBuffer = pWaylandFb ? pWaylandFb->GetHostBuffer() : nullptr; + CWaylandFb *pWaylandFb = + pLayer && pLayer->tex != nullptr + ? static_cast( + pLayer->tex->GetBackendFb( )->EnsureImported( ) ) + : nullptr; + wl_buffer *pBuffer = + pWaylandFb ? pWaylandFb->GetHostBuffer( ) : nullptr; if ( pBuffer ) { - pWaylandFb->OnCompositorAcquire(); - - Present( - ClipPlane( WaylandPlaneState - { - .pBuffer = pBuffer, - .nDestX = int32_t( -pLayer->offset.x ), - .nDestY = int32_t( -pLayer->offset.y ), - .flSrcX = 0.0, - .flSrcY = 0.0, - .flSrcWidth = double( pLayer->tex->width() ), - .flSrcHeight = double( pLayer->tex->height() ), - .nDstWidth = int32_t( ceil( pLayer->tex->width() / double( pLayer->scale.x ) ) ), - .nDstHeight = int32_t( ceil( pLayer->tex->height() / double( pLayer->scale.y ) ) ), - .eColorspace = pLayer->colorspace, - .pHDRMetadata = pLayer->hdr_metadata_blob, - .bOpaque = pLayer->zpos == g_zposBase, - .uFractionalScale = GetScale(), + pWaylandFb->OnCompositorAcquire( ); + + Present( ClipPlane( + WaylandPlaneState{ + .pBuffer = pBuffer, + .nDestX = int32_t( -pLayer->offset.x ), + .nDestY = int32_t( -pLayer->offset.y ), + .flSrcX = 0.0, + .flSrcY = 0.0, + .flSrcWidth = double( pLayer->tex->width( ) ), + .flSrcHeight = double( pLayer->tex->height( ) ), + .nDstWidth = int32_t( ceil( + pLayer->tex->width( ) / double( pLayer->scale.x ) ) ), + .nDstHeight = int32_t( ceil( + pLayer->tex->height( ) / double( pLayer->scale.y ) ) ), + .eColorspace = pLayer->colorspace, + .pHDRMetadata = pLayer->hdr_metadata_blob, + .bOpaque = pLayer->zpos == g_zposBase, + .uFractionalScale = GetScale( ), } ) ); } else @@ -1659,63 +2070,67 @@ namespace gamescope } } - void CWaylandPlane::UpdateVRRRefreshRate() + void CWaylandPlane::UpdateVRRRefreshRate( ) { - if ( m_pParent ) - return; + if ( m_pParent ) return; - if ( !m_pConnector->HostCompositorIsCurrentlyVRR() ) - return; + if ( !m_pConnector->HostCompositorIsCurrentlyVRR( ) ) return; - if ( m_pOutputs.empty() ) - return; + if ( m_pOutputs.empty( ) ) return; int32_t nLargestRefreshRateMhz = 0; for ( wl_output *pOutput : m_pOutputs ) { - WaylandOutputInfo *pOutputInfo = m_pBackend->GetOutputInfo( pOutput ); - if ( !pOutputInfo ) - continue; + WaylandOutputInfo *pOutputInfo = + m_pBackend->GetOutputInfo( pOutput ); + if ( !pOutputInfo ) continue; - nLargestRefreshRateMhz = std::max( nLargestRefreshRateMhz, pOutputInfo->nRefresh ); + nLargestRefreshRateMhz = + std::max( nLargestRefreshRateMhz, pOutputInfo->nRefresh ); } - if ( nLargestRefreshRateMhz && nLargestRefreshRateMhz != g_nOutputRefresh ) + if ( nLargestRefreshRateMhz && + nLargestRefreshRateMhz != g_nOutputRefresh ) { // TODO(strategy): We should pick the largest refresh rate. - xdg_log.infof( "Changed refresh to: %.3fhz", ConvertmHzToHz( (float) nLargestRefreshRateMhz ) ); + xdg_log.infof( + "Changed refresh to: %.3fhz", + ConvertmHzToHz( ( float )nLargestRefreshRateMhz ) ); g_nOutputRefresh = nLargestRefreshRateMhz; } } - void CWaylandPlane::Wayland_Surface_Enter( wl_surface *pSurface, wl_output *pOutput ) + void CWaylandPlane::Wayland_Surface_Enter( + wl_surface *pSurface, wl_output *pOutput ) { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + if ( !IsGamescopeToplevel( pSurface ) ) return; m_pOutputs.emplace_back( pOutput ); - UpdateVRRRefreshRate(); + UpdateVRRRefreshRate( ); } - void CWaylandPlane::Wayland_Surface_Leave( wl_surface *pSurface, wl_output *pOutput ) + void CWaylandPlane::Wayland_Surface_Leave( + wl_surface *pSurface, wl_output *pOutput ) { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + if ( !IsGamescopeToplevel( pSurface ) ) return; std::erase( m_pOutputs, pOutput ); - UpdateVRRRefreshRate(); + UpdateVRRRefreshRate( ); } - void CWaylandPlane::LibDecor_Frame_Configure( libdecor_frame *pFrame, libdecor_configuration *pConfiguration ) + void CWaylandPlane::LibDecor_Frame_Configure( + libdecor_frame *pFrame, libdecor_configuration *pConfiguration ) { - if ( !libdecor_configuration_get_window_state( pConfiguration, &m_eWindowState ) ) - m_eWindowState = LIBDECOR_WINDOW_STATE_NONE; + if ( !libdecor_configuration_get_window_state( + pConfiguration, &m_eWindowState ) ) + m_eWindowState = LIBDECOR_WINDOW_STATE_NONE; - int32_t uScale = GetScale(); + int32_t uScale = GetScale( ); int nWidth, nHeight; - if ( !libdecor_configuration_get_content_size( pConfiguration, m_pFrame, &nWidth, &nHeight ) ) + if ( !libdecor_configuration_get_content_size( + pConfiguration, m_pFrame, &nWidth, &nHeight ) ) { // XXX(virtual connector): Move g_nOutputWidth etc to connector. // Right now we are doubling this up when we should not be. @@ -1729,27 +2144,34 @@ namespace gamescope CommitLibDecor( pConfiguration ); - force_repaint(); - } - void CWaylandPlane::LibDecor_Frame_Close( libdecor_frame *pFrame ) - { - raise( SIGTERM ); + force_repaint( ); } + void CWaylandPlane::LibDecor_Frame_Close( libdecor_frame *pFrame ) + { raise( SIGTERM ); } void CWaylandPlane::LibDecor_Frame_Commit( libdecor_frame *pFrame ) { m_bNeedsDecorCommit = true; - force_repaint(); - } - void CWaylandPlane::LibDecor_Frame_DismissPopup( libdecor_frame *pFrame, const char *pSeatName ) - { - } - - void CWaylandPlane::Wayland_PresentationFeedback_SyncOutput( struct wp_presentation_feedback *pFeedback, wl_output *pOutput ) - { - } - void CWaylandPlane::Wayland_PresentationFeedback_Presented( struct wp_presentation_feedback *pFeedback, uint32_t uTVSecHi, uint32_t uTVSecLo, uint32_t uTVNSec, uint32_t uRefreshCycle, uint32_t uSeqHi, uint32_t uSeqLo, uint32_t uFlags ) - { - uint64_t ulTime = ( ( ( uint64_t( uTVSecHi ) << 32ul ) | uTVSecLo ) * 1'000'000'000lu ) + + force_repaint( ); + } + void CWaylandPlane::LibDecor_Frame_DismissPopup( + libdecor_frame *pFrame, const char *pSeatName ) + {} + + void CWaylandPlane::Wayland_PresentationFeedback_SyncOutput( + struct wp_presentation_feedback *pFeedback, wl_output *pOutput ) + {} + void CWaylandPlane::Wayland_PresentationFeedback_Presented( + struct wp_presentation_feedback *pFeedback, + uint32_t uTVSecHi, + uint32_t uTVSecLo, + uint32_t uTVNSec, + uint32_t uRefreshCycle, + uint32_t uSeqHi, + uint32_t uSeqLo, + uint32_t uFlags ) + { + uint64_t ulTime = ( ( ( uint64_t( uTVSecHi ) << 32ul ) | uTVSecLo ) * + 1'000'000'000lu ) + ( uint64_t( uTVNSec ) ); if ( uRefreshCycle ) @@ -1757,7 +2179,9 @@ namespace gamescope int32_t nRefresh = RefreshCycleTomHz( uRefreshCycle ); if ( nRefresh && nRefresh != g_nOutputRefresh ) { - xdg_log.infof( "Changed refresh to: %.3fhz", ConvertmHzToHz( (float) nRefresh ) ); + xdg_log.infof( + "Changed refresh to: %.3fhz", + ConvertmHzToHz( ( float )nRefresh ) ); g_nOutputRefresh = nRefresh; } @@ -1767,56 +2191,79 @@ namespace gamescope { m_pConnector->SetHostCompositorIsCurrentlyVRR( true ); - UpdateVRRRefreshRate(); + UpdateVRRRefreshRate( ); } - GetVBlankTimer().MarkVBlank( ulTime, true ); + GetVBlankTimer( ).MarkVBlank( ulTime, true ); wp_presentation_feedback_destroy( pFeedback ); // Nudge so that steamcompmgr releases commits. - nudge_steamcompmgr(); + nudge_steamcompmgr( ); } - void CWaylandPlane::Wayland_PresentationFeedback_Discarded( struct wp_presentation_feedback *pFeedback ) + void CWaylandPlane::Wayland_PresentationFeedback_Discarded( + struct wp_presentation_feedback *pFeedback ) { wp_presentation_feedback_destroy( pFeedback ); // Nudge so that steamcompmgr releases commits. - nudge_steamcompmgr(); + nudge_steamcompmgr( ); } void CWaylandPlane::Wayland_FrogColorManagedSurface_PreferredMetadata( frog_color_managed_surface *pFrogSurface, - uint32_t uTransferFunction, - uint32_t uOutputDisplayPrimaryRedX, - uint32_t uOutputDisplayPrimaryRedY, - uint32_t uOutputDisplayPrimaryGreenX, - uint32_t uOutputDisplayPrimaryGreenY, - uint32_t uOutputDisplayPrimaryBlueX, - uint32_t uOutputDisplayPrimaryBlueY, - uint32_t uOutputWhitePointX, - uint32_t uOutputWhitePointY, - uint32_t uMaxLuminance, - uint32_t uMinLuminance, - uint32_t uMaxFullFrameLuminance ) + uint32_t uTransferFunction, + uint32_t uOutputDisplayPrimaryRedX, + uint32_t uOutputDisplayPrimaryRedY, + uint32_t uOutputDisplayPrimaryGreenX, + uint32_t uOutputDisplayPrimaryGreenY, + uint32_t uOutputDisplayPrimaryBlueX, + uint32_t uOutputDisplayPrimaryBlueY, + uint32_t uOutputWhitePointX, + uint32_t uOutputWhitePointY, + uint32_t uMaxLuminance, + uint32_t uMinLuminance, + uint32_t uMaxFullFrameLuminance ) { auto *pHDRInfo = &m_pConnector->m_HDRInfo; - pHDRInfo->bExposeHDRSupport = ( cv_hdr_enabled && uTransferFunction == FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ); - pHDRInfo->eOutputEncodingEOTF = ( cv_hdr_enabled && uTransferFunction == FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ) ? EOTF_PQ : EOTF_Gamma22; + pHDRInfo->bExposeHDRSupport = + ( cv_hdr_enabled && + uTransferFunction == + FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ); + pHDRInfo->eOutputEncodingEOTF = + ( cv_hdr_enabled && + uTransferFunction == + FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ ) + ? EOTF_PQ + : EOTF_Gamma22; pHDRInfo->uMaxContentLightLevel = uMaxLuminance; pHDRInfo->uMaxFrameAverageLuminance = uMaxFullFrameLuminance; pHDRInfo->uMinContentLightLevel = uMinLuminance; auto *pDisplayColorimetry = &m_pConnector->m_DisplayColorimetry; - pDisplayColorimetry->primaries.r = glm::vec2{ uOutputDisplayPrimaryRedX * 0.00002f, uOutputDisplayPrimaryRedY * 0.00002f }; - pDisplayColorimetry->primaries.g = glm::vec2{ uOutputDisplayPrimaryGreenX * 0.00002f, uOutputDisplayPrimaryGreenY * 0.00002f }; - pDisplayColorimetry->primaries.b = glm::vec2{ uOutputDisplayPrimaryBlueX * 0.00002f, uOutputDisplayPrimaryBlueY * 0.00002f }; - pDisplayColorimetry->white = glm::vec2{ uOutputWhitePointX * 0.00002f, uOutputWhitePointY * 0.00002f }; - - xdg_log.infof( "PreferredMetadata: Red: %g %g, Green: %g %g, Blue: %g %g, White: %g %g, Max Luminance: %u nits, Min Luminance: %g nits, Max Full Frame Luminance: %u nits", - uOutputDisplayPrimaryRedX * 0.00002, uOutputDisplayPrimaryRedY * 0.00002, - uOutputDisplayPrimaryGreenX * 0.00002, uOutputDisplayPrimaryGreenY * 0.00002, - uOutputDisplayPrimaryBlueX * 0.00002, uOutputDisplayPrimaryBlueY * 0.00002, - uOutputWhitePointX * 0.00002, uOutputWhitePointY * 0.00002, + pDisplayColorimetry->primaries.r = + glm::vec2{ uOutputDisplayPrimaryRedX * 0.00002f, + uOutputDisplayPrimaryRedY * 0.00002f }; + pDisplayColorimetry->primaries.g = + glm::vec2{ uOutputDisplayPrimaryGreenX * 0.00002f, + uOutputDisplayPrimaryGreenY * 0.00002f }; + pDisplayColorimetry->primaries.b = + glm::vec2{ uOutputDisplayPrimaryBlueX * 0.00002f, + uOutputDisplayPrimaryBlueY * 0.00002f }; + pDisplayColorimetry->white = glm::vec2{ uOutputWhitePointX * 0.00002f, + uOutputWhitePointY * 0.00002f }; + + xdg_log.infof( + "PreferredMetadata: Red: %g %g, Green: %g %g, Blue: %g %g, White: " + "%g %g, Max Luminance: %u nits, Min Luminance: %g nits, Max Full " + "Frame Luminance: %u nits", + uOutputDisplayPrimaryRedX * 0.00002, + uOutputDisplayPrimaryRedY * 0.00002, + uOutputDisplayPrimaryGreenX * 0.00002, + uOutputDisplayPrimaryGreenY * 0.00002, + uOutputDisplayPrimaryBlueX * 0.00002, + uOutputDisplayPrimaryBlueY * 0.00002, + uOutputWhitePointX * 0.00002, + uOutputWhitePointY * 0.00002, uint32_t( uMaxLuminance ), uMinLuminance * 0.0001, uint32_t( uMaxFullFrameLuminance ) ); @@ -1824,89 +2271,130 @@ namespace gamescope // - void CWaylandPlane::Wayland_WPColorManagementSurfaceFeedback_PreferredChanged( wp_color_management_surface_feedback_v1 *pColorManagementSurface, unsigned int data) - { - UpdateWPPreferredColorManagement(); - } + void + CWaylandPlane::Wayland_WPColorManagementSurfaceFeedback_PreferredChanged( + wp_color_management_surface_feedback_v1 *pColorManagementSurface, + unsigned int data ) + { UpdateWPPreferredColorManagement( ); } - void CWaylandPlane::UpdateWPPreferredColorManagement() + void CWaylandPlane::UpdateWPPreferredColorManagement( ) { - if ( m_pParent ) - return; + if ( m_pParent ) return; - wp_image_description_v1 *pImageDescription = wp_color_management_surface_feedback_v1_get_preferred( m_pWPColorManagedSurfaceFeedback ); - wp_image_description_info_v1 *pImageDescInfo = wp_image_description_v1_get_information( pImageDescription ); - wp_image_description_info_v1_add_listener( pImageDescInfo, &s_ImageDescriptionInfoListener, this ); - wl_display_roundtrip( m_pBackend->GetDisplay() ); + wp_image_description_v1 *pImageDescription = + wp_color_management_surface_feedback_v1_get_preferred( + m_pWPColorManagedSurfaceFeedback ); + wp_image_description_info_v1 *pImageDescInfo = + wp_image_description_v1_get_information( pImageDescription ); + wp_image_description_info_v1_add_listener( + pImageDescInfo, &s_ImageDescriptionInfoListener, this ); + wl_display_roundtrip( m_pBackend->GetDisplay( ) ); wp_image_description_info_v1_destroy( pImageDescInfo ); wp_image_description_v1_destroy( pImageDescription ); } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_Done( wp_image_description_info_v1 *pImageDescInfo ) + void CWaylandPlane::Wayland_WPImageDescriptionInfo_Done( + wp_image_description_info_v1 *pImageDescInfo ) { auto *pHDRInfo = &m_pConnector->m_HDRInfo; - if (m_pBackend->SupportsColorManagement()) { - pHDRInfo->bExposeHDRSupport = ( cv_hdr_enabled && m_pConnector->m_uMaxTargetLuminance > m_pConnector->m_uReferenceLuminance ); - pHDRInfo->eOutputEncodingEOTF = pHDRInfo->bExposeHDRSupport ? EOTF_PQ : EOTF_Gamma22; + if ( m_pBackend->SupportsColorManagement( ) ) + { + pHDRInfo->bExposeHDRSupport = + ( cv_hdr_enabled && m_pConnector->m_uMaxTargetLuminance > + m_pConnector->m_uReferenceLuminance ); + pHDRInfo->eOutputEncodingEOTF = + pHDRInfo->bExposeHDRSupport ? EOTF_PQ : EOTF_Gamma22; } xdg_log.infof( "HDR INFO" ); - xdg_log.infof( " cv_hdr_enabled: %s", cv_hdr_enabled ? "true" : "false" ); - xdg_log.infof( " uMaxLum: %u, uRefLum: %u", m_pConnector->m_uMaxTargetLuminance, m_pConnector->m_uReferenceLuminance); - xdg_log.infof( " bExposeHDRSupport: %s", pHDRInfo->bExposeHDRSupport ? "true" : "false" ); - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_ICCFile( wp_image_description_info_v1 *pImageDescInfo, int32_t nICCFd, uint32_t uICCSize ) - { - if ( nICCFd >= 0 ) - close( nICCFd ); - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_Primaries( wp_image_description_info_v1 *pImageDescInfo, int32_t nRedX, int32_t nRedY, int32_t nGreenX, int32_t nGreenY, int32_t nBlueX, int32_t nBlueY, int32_t nWhiteX, int32_t nWhiteY ) - { - - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_PrimariesNamed( wp_image_description_info_v1 *pImageDescInfo, uint32_t uPrimaries ) - { - - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_TFPower( wp_image_description_info_v1 *pImageDescInfo, uint32_t uExp) - { - - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_TFNamed( wp_image_description_info_v1 *pImageDescInfo, uint32_t uTF) - { - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_Luminances( wp_image_description_info_v1 *pImageDescInfo, uint32_t uMinLum, uint32_t uMaxLum, uint32_t uRefLum ) - { - m_pConnector->m_uReferenceLuminance = uRefLum; - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_TargetPrimaries( wp_image_description_info_v1 *pImageDescInfo, int32_t nRedX, int32_t nRedY, int32_t nGreenX, int32_t nGreenY, int32_t nBlueX, int32_t nBlueY, int32_t nWhiteX, int32_t nWhiteY ) + xdg_log.infof( + " cv_hdr_enabled: %s", cv_hdr_enabled ? "true" : "false" ); + xdg_log.infof( + " uMaxLum: %u, uRefLum: %u", + m_pConnector->m_uMaxTargetLuminance, + m_pConnector->m_uReferenceLuminance ); + xdg_log.infof( + " bExposeHDRSupport: %s", + pHDRInfo->bExposeHDRSupport ? "true" : "false" ); + } + void CWaylandPlane::Wayland_WPImageDescriptionInfo_ICCFile( + wp_image_description_info_v1 *pImageDescInfo, + int32_t nICCFd, + uint32_t uICCSize ) + { + if ( nICCFd >= 0 ) close( nICCFd ); + } + void CWaylandPlane::Wayland_WPImageDescriptionInfo_Primaries( + wp_image_description_info_v1 *pImageDescInfo, + int32_t nRedX, + int32_t nRedY, + int32_t nGreenX, + int32_t nGreenY, + int32_t nBlueX, + int32_t nBlueY, + int32_t nWhiteX, + int32_t nWhiteY ) + {} + void CWaylandPlane::Wayland_WPImageDescriptionInfo_PrimariesNamed( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uPrimaries ) + {} + void CWaylandPlane::Wayland_WPImageDescriptionInfo_TFPower( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uExp ) + {} + void CWaylandPlane::Wayland_WPImageDescriptionInfo_TFNamed( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uTF ) + {} + void CWaylandPlane::Wayland_WPImageDescriptionInfo_Luminances( + wp_image_description_info_v1 *pImageDescInfo, + uint32_t uMinLum, + uint32_t uMaxLum, + uint32_t uRefLum ) + { m_pConnector->m_uReferenceLuminance = uRefLum; } + void CWaylandPlane::Wayland_WPImageDescriptionInfo_TargetPrimaries( + wp_image_description_info_v1 *pImageDescInfo, + int32_t nRedX, + int32_t nRedY, + int32_t nGreenX, + int32_t nGreenY, + int32_t nBlueX, + int32_t nBlueY, + int32_t nWhiteX, + int32_t nWhiteY ) { auto *pDisplayColorimetry = &m_pConnector->m_DisplayColorimetry; - pDisplayColorimetry->primaries.r = glm::vec2{ nRedX / 1000000.0f, nRedY / 1000000.0f }; - pDisplayColorimetry->primaries.g = glm::vec2{ nGreenX / 1000000.0f, nGreenY / 1000000.0f }; - pDisplayColorimetry->primaries.b = glm::vec2{ nBlueX / 1000000.0f, nBlueY / 1000000.0f }; - pDisplayColorimetry->white = glm::vec2{ nWhiteX / 1000000.0f, nWhiteY / 1000000.0f }; - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_TargetLuminance( wp_image_description_info_v1 *pImageDescInfo, uint32_t uMinLum, uint32_t uMaxLum ) - { - m_pConnector->m_uMaxTargetLuminance = uMaxLum; - } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_Target_MaxCLL( wp_image_description_info_v1 *pImageDescInfo, uint32_t uMaxCLL ) - { - auto *pHDRInfo = &m_pConnector->m_HDRInfo; + pDisplayColorimetry->primaries.r = + glm::vec2{ nRedX / 1000000.0f, nRedY / 1000000.0f }; + pDisplayColorimetry->primaries.g = + glm::vec2{ nGreenX / 1000000.0f, nGreenY / 1000000.0f }; + pDisplayColorimetry->primaries.b = + glm::vec2{ nBlueX / 1000000.0f, nBlueY / 1000000.0f }; + pDisplayColorimetry->white = + glm::vec2{ nWhiteX / 1000000.0f, nWhiteY / 1000000.0f }; + } + void CWaylandPlane::Wayland_WPImageDescriptionInfo_TargetLuminance( + wp_image_description_info_v1 *pImageDescInfo, + uint32_t uMinLum, + uint32_t uMaxLum ) + { m_pConnector->m_uMaxTargetLuminance = uMaxLum; } + void CWaylandPlane::Wayland_WPImageDescriptionInfo_Target_MaxCLL( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uMaxCLL ) + { + auto *pHDRInfo = &m_pConnector->m_HDRInfo; pHDRInfo->uMaxContentLightLevel = uMaxCLL; xdg_log.infof( "uMaxContentLightLevel: %u", uMaxCLL ); } - void CWaylandPlane::Wayland_WPImageDescriptionInfo_Target_MaxFALL( wp_image_description_info_v1 *pImageDescInfo, uint32_t uMaxFALL ) + void CWaylandPlane::Wayland_WPImageDescriptionInfo_Target_MaxFALL( + wp_image_description_info_v1 *pImageDescInfo, uint32_t uMaxFALL ) { - auto *pHDRInfo = &m_pConnector->m_HDRInfo; + auto *pHDRInfo = &m_pConnector->m_HDRInfo; pHDRInfo->uMaxFrameAverageLuminance = uMaxFALL; } // - void CWaylandPlane::Wayland_FractionalScale_PreferredScale( wp_fractional_scale_v1 *pFractionalScale, uint32_t uScale ) + void CWaylandPlane::Wayland_FractionalScale_PreferredScale( + wp_fractional_scale_v1 *pFractionalScale, uint32_t uScale ) { bool bDirty = false; @@ -1915,24 +2403,25 @@ namespace gamescope { if ( m_bHasRecievedScale ) { - g_nOutputWidth = ( g_nOutputWidth * uScale ) / m_uFractionalScale; - g_nOutputHeight = ( g_nOutputHeight * uScale ) / m_uFractionalScale; + g_nOutputWidth = + ( g_nOutputWidth * uScale ) / m_uFractionalScale; + g_nOutputHeight = + ( g_nOutputHeight * uScale ) / m_uFractionalScale; } s_uGlobalFractionalScale = uScale; - bDirty = true; + bDirty = true; } if ( m_uFractionalScale != uScale ) { m_uFractionalScale = uScale; - bDirty = true; + bDirty = true; } m_bHasRecievedScale = true; - if ( bDirty ) - force_repaint(); + if ( bDirty ) force_repaint( ); } //////////////// @@ -1940,22 +2429,19 @@ namespace gamescope //////////////// // Not const... weird. - static libdecor_interface s_LibDecorInterface = - { - .error = []( libdecor *pContext, libdecor_error eError, const char *pMessage ) - { - xdg_log.errorf( "libdecor: %s", pMessage ); - }, + static libdecor_interface s_LibDecorInterface = { + .error = []( libdecor *pContext, + libdecor_error eError, + const char *pMessage ) + { xdg_log.errorf( "libdecor: %s", pMessage ); }, }; - CWaylandBackend::CWaylandBackend() - { - } + CWaylandBackend::CWaylandBackend( ) {} - bool CWaylandBackend::Init() + bool CWaylandBackend::Init( ) { - g_nOutputWidth = g_nPreferredOutputWidth; - g_nOutputHeight = g_nPreferredOutputHeight; + g_nOutputWidth = g_nPreferredOutputWidth; + g_nOutputHeight = g_nPreferredOutputHeight; g_nOutputRefresh = g_nNestedRefresh; // TODO: Dedupe the init of this stuff, @@ -1969,10 +2455,8 @@ namespace gamescope } g_nOutputHeight = 720; } - if ( g_nOutputWidth == 0 ) - g_nOutputWidth = g_nOutputHeight * 16 / 9; - if ( g_nOutputRefresh == 0 ) - g_nOutputRefresh = ConvertHztomHz( 60 ); + if ( g_nOutputWidth == 0 ) g_nOutputWidth = g_nOutputHeight * 16 / 9; + if ( g_nOutputRefresh == 0 ) g_nOutputRefresh = ConvertHztomHz( 60 ); if ( !( m_pDisplay = wl_display_connect( nullptr ) ) ) { @@ -1990,17 +2474,20 @@ namespace gamescope wl_registry_add_listener( pRegistry, &s_RegistryListener, this ); wl_display_roundtrip( m_pDisplay ); - if ( !m_pCompositor || !m_pSubcompositor || !m_pXdgWmBase || !m_pLinuxDmabuf || !m_pViewporter || !m_pPresentation || !m_pRelativePointerManager || !m_pPointerConstraints || !m_pShm ) + if ( !m_pCompositor || !m_pSubcompositor || !m_pXdgWmBase || + !m_pLinuxDmabuf || !m_pViewporter || !m_pPresentation || + !m_pRelativePointerManager || !m_pPointerConstraints || !m_pShm ) { xdg_log.errorf( "Couldn't create Wayland objects." ); return false; } - m_pEmptyRegion = wl_compositor_create_region( m_pCompositor ); - m_pFullRegion = wl_compositor_create_region( m_pCompositor ); - wl_region_add( m_pFullRegion, 0, 0, INT32_MAX, INT32_MAX ); + m_pEmptyRegion = wl_compositor_create_region( m_pCompositor ); + m_pFullRegion = wl_compositor_create_region( m_pCompositor ); + wl_region_add( m_pFullRegion, 0, 0, INT32_MAX, INT32_MAX ); - // Grab stuff from any extra bindings/listeners we set up, eg. format/modifiers. + // Grab stuff from any extra bindings/listeners we set up, eg. + // format/modifiers. wl_display_roundtrip( m_pDisplay ); wl_registry_destroy( pRegistry ); @@ -2008,50 +2495,83 @@ namespace gamescope if ( m_pWPColorManager ) { - m_WPColorManagerFeatures.bSupportsGamescopeColorManagement = [this]() -> bool + m_WPColorManagerFeatures.bSupportsGamescopeColorManagement = + [ this ]( ) -> bool { // Features - if ( !Algorithm::Contains( m_WPColorManagerFeatures.eFeatures, WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.eFeatures, + WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC ) ) return false; - if ( !Algorithm::Contains( m_WPColorManagerFeatures.eFeatures, WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.eFeatures, + WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES ) ) return false; - if ( !Algorithm::Contains( m_WPColorManagerFeatures.eFeatures, WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES ) ) + if ( + !Algorithm::Contains( + m_WPColorManagerFeatures.eFeatures, + WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES ) ) return false; - if ( !Algorithm::Contains( m_WPColorManagerFeatures.eFeatures, WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.eFeatures, + WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME ) ) return false; - if ( !Algorithm::Contains( m_WPColorManagerFeatures.eFeatures, WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.eFeatures, + WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES ) ) return false; - if ( !Algorithm::Contains( m_WPColorManagerFeatures.eFeatures, WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.eFeatures, + WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB ) ) return false; // Transfer Functions - if ( !Algorithm::Contains( m_WPColorManagerFeatures.eTransferFunctions, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.eTransferFunctions, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB ) ) return false; - if ( !Algorithm::Contains( m_WPColorManagerFeatures.eTransferFunctions, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.eTransferFunctions, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ ) ) return false; // Primaries - if ( !Algorithm::Contains( m_WPColorManagerFeatures.ePrimaries, WP_COLOR_MANAGER_V1_PRIMARIES_SRGB ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.ePrimaries, + WP_COLOR_MANAGER_V1_PRIMARIES_SRGB ) ) return false; - if ( !Algorithm::Contains( m_WPColorManagerFeatures.ePrimaries, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 ) ) + if ( !Algorithm::Contains( + m_WPColorManagerFeatures.ePrimaries, + WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 ) ) return false; return true; - }(); + }( ); if ( m_WPColorManagerFeatures.bSupportsGamescopeColorManagement ) { // HDR10. { - wp_image_description_creator_params_v1 *pParams = wp_color_manager_v1_create_parametric_creator( m_pWPColorManager ); - wp_image_description_creator_params_v1_set_primaries_named( pParams, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 ); - wp_image_description_creator_params_v1_set_tf_named( pParams, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ ); - m_pWPImageDescriptions[ GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ] = wp_image_description_creator_params_v1_create( pParams ); + wp_image_description_creator_params_v1 *pParams = + wp_color_manager_v1_create_parametric_creator( + m_pWPColorManager ); + wp_image_description_creator_params_v1_set_primaries_named( + pParams, WP_COLOR_MANAGER_V1_PRIMARIES_BT2020 ); + wp_image_description_creator_params_v1_set_tf_named( + pParams, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ ); + m_pWPImageDescriptions + [ GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ] = + wp_image_description_creator_params_v1_create( + pParams ); } // scRGB { - m_pWPImageDescriptions[ GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ] = wp_color_manager_v1_create_windows_scrgb( m_pWPColorManager ); + m_pWPImageDescriptions + [ GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ] = + wp_color_manager_v1_create_windows_scrgb( + m_pWPColorManager ); } } } @@ -2063,12 +2583,12 @@ namespace gamescope return false; } - if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) + if ( !vulkan_init( vulkan_get_instance( ), VK_NULL_HANDLE ) ) { return false; } - - if ( !wlsession_init() ) + + if ( !wlsession_init( ) ) { xdg_log.errorf( "Failed to initialize Wayland session" ); return false; @@ -2085,13 +2605,15 @@ namespace gamescope return true; } - bool CWaylandBackend::PostInit() + bool CWaylandBackend::PostInit( ) { if ( m_pSinglePixelBufferManager ) { - wl_buffer *pBlackBuffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( m_pSinglePixelBufferManager, 0, 0, 0, ~0u ); + wl_buffer *pBlackBuffer = + wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( + m_pSinglePixelBufferManager, 0, 0, 0, ~0u ); m_pOwnedBlackFb = new CWaylandFb( this, pBlackBuffer ); - m_BlackFb = m_pOwnedBlackFb.get(); + m_BlackFb = m_pOwnedBlackFb.get( ); } else { @@ -2101,7 +2623,8 @@ namespace gamescope xdg_log.errorf( "Failed to create dummy black texture." ); return false; } - m_BlackFb = static_cast( m_pBlackTexture->GetBackendFb() ); + m_BlackFb = + static_cast( m_pBlackTexture->GetBackendFb( ) ); } if ( m_BlackFb == nullptr ) @@ -2110,7 +2633,7 @@ namespace gamescope return false; } - m_pDefaultCursorInfo = GetX11HostCursor(); + m_pDefaultCursorInfo = GetX11HostCursor( ); m_pDefaultCursorSurface = CursorInfoToSurface( m_pDefaultCursorInfo ); xdg_log.infof( "Post-Initted Wayland backend" ); @@ -2118,22 +2641,18 @@ namespace gamescope return true; } - std::span CWaylandBackend::GetInstanceExtensions() const - { - return std::span{}; - } + std::span CWaylandBackend::GetInstanceExtensions( ) const + { return std::span{}; } - std::span CWaylandBackend::GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const - { - return std::span{}; - } + std::span CWaylandBackend::GetDeviceExtensions( + VkPhysicalDevice pVkPhysicalDevice ) const + { return std::span{}; } - VkImageLayout CWaylandBackend::GetPresentLayout() const - { - return VK_IMAGE_LAYOUT_GENERAL; - } + VkImageLayout CWaylandBackend::GetPresentLayout( ) const + { return VK_IMAGE_LAYOUT_GENERAL; } - void CWaylandBackend::GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const + void CWaylandBackend::GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const { // Prefer opaque for composition on the Wayland backend. @@ -2159,27 +2678,24 @@ namespace gamescope assert( u8BitFormat != DRM_FORMAT_INVALID ); - *pPrimaryPlaneFormat = u10BitFormat != DRM_FORMAT_INVALID ? u10BitFormat : u8BitFormat; + *pPrimaryPlaneFormat = + u10BitFormat != DRM_FORMAT_INVALID ? u10BitFormat : u8BitFormat; *pOverlayPlaneFormat = u8BitFormat; } - bool CWaylandBackend::ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const - { - return true; - } + bool CWaylandBackend::ValidPhysicalDevice( + VkPhysicalDevice pVkPhysicalDevice ) const + { return true; } - void CWaylandBackend::DirtyState( bool bForce, bool bForceModeset ) - { - } - bool CWaylandBackend::PollState() + void CWaylandBackend::DirtyState( bool bForce, bool bForceModeset ) {} + bool CWaylandBackend::PollState( ) { wl_display_flush( m_pDisplay ); if ( wl_display_prepare_read( m_pDisplay ) == 0 ) { - int nRet = 0; - pollfd pollfd = - { + int nRet = 0; + pollfd pollfd = { .fd = wl_display_get_fd( m_pDisplay ), .events = POLLIN, }; @@ -2189,8 +2705,7 @@ namespace gamescope nRet = poll( &pollfd, 1, 0 ); } while ( nRet < 0 && ( errno == EINTR || errno == EAGAIN ) ); - if ( nRet > 0 ) - wl_display_read_events( m_pDisplay ); + if ( nRet > 0 ) wl_display_read_events( m_pDisplay ); else wl_display_cancel_read( m_pDisplay ); } @@ -2200,14 +2715,15 @@ namespace gamescope return false; } - std::shared_ptr CWaylandBackend::CreateBackendBlob( const std::type_info &type, std::span data ) - { - return std::make_shared( data ); - } + std::shared_ptr CWaylandBackend::CreateBackendBlob( + const std::type_info &type, std::span data ) + { return std::make_shared( data ); } - OwningRc CWaylandBackend::ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) + OwningRc + CWaylandBackend::ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) { - zwp_linux_buffer_params_v1 *pBufferParams = zwp_linux_dmabuf_v1_create_params( m_pLinuxDmabuf ); + zwp_linux_buffer_params_v1 *pBufferParams = + zwp_linux_dmabuf_v1_create_params( m_pLinuxDmabuf ); if ( !pBufferParams ) { xdg_log.errorf( "Failed to create imported dmabuf params" ); @@ -2218,12 +2734,12 @@ namespace gamescope { zwp_linux_buffer_params_v1_add( pBufferParams, - pDmaBuf->fd[i], + pDmaBuf->fd[ i ], i, - pDmaBuf->offset[i], - pDmaBuf->stride[i], + pDmaBuf->offset[ i ], + pDmaBuf->stride[ i ], pDmaBuf->modifier >> 32, - pDmaBuf->modifier & 0xffffffff); + pDmaBuf->modifier & 0xffffffff ); } wl_buffer *pImportedBuffer = zwp_linux_buffer_params_v1_create_immed( @@ -2244,83 +2760,63 @@ namespace gamescope return new CWaylandFb{ this, pImportedBuffer }; } - bool CWaylandBackend::UsesModifiers() const + bool CWaylandBackend::UsesModifiers( ) const { - if ( !cv_wayland_use_modifiers ) - return false; + if ( !cv_wayland_use_modifiers ) return false; return m_bCanUseModifiers; } - std::span CWaylandBackend::GetSupportedModifiers( uint32_t uDrmFormat ) const + std::span + CWaylandBackend::GetSupportedModifiers( uint32_t uDrmFormat ) const { auto iter = m_FormatModifiers.find( uDrmFormat ); - if ( iter == m_FormatModifiers.end() ) + if ( iter == m_FormatModifiers.end( ) ) return std::span{}; - return std::span{ iter->second.begin(), iter->second.end() }; + return std::span{ iter->second.begin( ), + iter->second.end( ) }; } - IBackendConnector *CWaylandBackend::GetCurrentConnector() - { - return m_pFocusConnector; - } - IBackendConnector *CWaylandBackend::GetConnector( GamescopeScreenType eScreenType ) + IBackendConnector *CWaylandBackend::GetCurrentConnector( ) + { return m_pFocusConnector; } + IBackendConnector * + CWaylandBackend::GetConnector( GamescopeScreenType eScreenType ) { if ( eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL ) - return GetCurrentConnector(); + return GetCurrentConnector( ); return nullptr; } - bool CWaylandBackend::SupportsPlaneHardwareCursor() const + bool CWaylandBackend::SupportsPlaneHardwareCursor( ) const { // We use the nested hints cursor stuff. // Not our own plane. return false; } - bool CWaylandBackend::SupportsTearing() const - { - return false; - } - bool CWaylandBackend::UsesVulkanSwapchain() const - { - return false; - } + bool CWaylandBackend::SupportsTearing( ) const { return false; } + bool CWaylandBackend::UsesVulkanSwapchain( ) const { return false; } - bool CWaylandBackend::IsSessionBased() const - { - return false; - } + bool CWaylandBackend::IsSessionBased( ) const { return false; } - bool CWaylandBackend::SupportsExplicitSync() const - { - return true; - } + bool CWaylandBackend::SupportsExplicitSync( ) const { return true; } - bool CWaylandBackend::IsPaused() const - { - return false; - } + bool CWaylandBackend::IsPaused( ) const { return false; } - bool CWaylandBackend::IsVisible() const - { - return true; - } + bool CWaylandBackend::IsVisible( ) const { return true; } glm::uvec2 CWaylandBackend::CursorSurfaceSize( glm::uvec2 uvecSize ) const - { - return uvecSize; - } + { return uvecSize; } - void CWaylandBackend::HackUpdatePatchedEdid() + void CWaylandBackend::HackUpdatePatchedEdid( ) { - if ( !GetCurrentConnector() ) - return; + if ( !GetCurrentConnector( ) ) return; - // XXX: We should do this a better way that handles per-window and appid stuff - // down the line - if ( cv_hdr_enabled && GetCurrentConnector()->GetHDRInfo().bExposeHDRSupport ) + // XXX: We should do this a better way that handles per-window and appid + // stuff down the line + if ( cv_hdr_enabled && + GetCurrentConnector( )->GetHDRInfo( ).bExposeHDRSupport ) { setenv( "DXVK_HDR", "1", true ); } @@ -2329,22 +2825,21 @@ namespace gamescope setenv( "DXVK_HDR", "0", true ); } - WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), false ); + WritePatchedEdid( + GetCurrentConnector( )->GetRawEDID( ), + GetCurrentConnector( )->GetHDRInfo( ), + false ); } - bool CWaylandBackend::UsesVirtualConnectors() - { - return true; - } - std::shared_ptr CWaylandBackend::CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) + bool CWaylandBackend::UsesVirtualConnectors( ) { return true; } + std::shared_ptr + CWaylandBackend::CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) { - std::shared_ptr pConnector = std::make_shared( this, ulVirtualConnectorKey ); - m_pFocusConnector = pConnector.get(); + std::shared_ptr pConnector = + std::make_shared( this, ulVirtualConnectorKey ); + m_pFocusConnector = pConnector.get( ); - if ( !pConnector->Init() ) - { - return nullptr; - } + if ( !pConnector->Init( ) ) { return nullptr; } return pConnector; } @@ -2358,26 +2853,32 @@ namespace gamescope // Do nothing. } - wl_surface *CWaylandBackend::CursorInfoToSurface( const std::shared_ptr &info ) + wl_surface *CWaylandBackend::CursorInfoToSurface( + const std::shared_ptr &info ) { - if ( !info ) - return nullptr; + if ( !info ) return nullptr; uint32_t uStride = info->uWidth * 4; - uint32_t uSize = uStride * info->uHeight; + uint32_t uSize = uStride * info->uHeight; - int32_t nFd = CreateShmBuffer( uSize, info->pPixels.data() ); - if ( nFd < 0 ) - return nullptr; + int32_t nFd = CreateShmBuffer( uSize, info->pPixels.data( ) ); + if ( nFd < 0 ) return nullptr; defer( close( nFd ) ); wl_shm_pool *pPool = wl_shm_create_pool( m_pShm, nFd, uSize ); defer( wl_shm_pool_destroy( pPool ) ); - wl_buffer *pBuffer = wl_shm_pool_create_buffer( pPool, 0, info->uWidth, info->uHeight, uStride, WL_SHM_FORMAT_ARGB8888 ); + wl_buffer *pBuffer = wl_shm_pool_create_buffer( + pPool, + 0, + info->uWidth, + info->uHeight, + uStride, + WL_SHM_FORMAT_ARGB8888 ); defer( wl_buffer_destroy( pBuffer ) ); - wl_surface *pCursorSurface = wl_compositor_create_surface( m_pCompositor ); + wl_surface *pCursorSurface = + wl_compositor_create_surface( m_pCompositor ); wl_surface_attach( pCursorSurface, pBuffer, 0, 0 ); wl_surface_damage( pCursorSurface, 0, 0, INT32_MAX, INT32_MAX ); wl_surface_commit( pCursorSurface ); @@ -2385,12 +2886,15 @@ namespace gamescope return pCursorSurface; } - bool CWaylandBackend::SupportsColorManagement() const + bool CWaylandBackend::SupportsColorManagement( ) const { - return m_pFrogColorMgmtFactory != nullptr || ( m_pWPColorManager != nullptr && m_WPColorManagerFeatures.bSupportsGamescopeColorManagement ); + return m_pFrogColorMgmtFactory != nullptr || + ( m_pWPColorManager != nullptr && + m_WPColorManagerFeatures.bSupportsGamescopeColorManagement ); } - void CWaylandBackend::SetCursorImage( std::shared_ptr info ) + void CWaylandBackend::SetCursorImage( + std::shared_ptr info ) { m_pCursorInfo = info; @@ -2402,14 +2906,15 @@ namespace gamescope m_pCursorSurface = CursorInfoToSurface( info ); - UpdateCursor(); + UpdateCursor( ); } - void CWaylandBackend::SetRelativeMouseMode( wl_surface *pSurface, bool bRelative ) + void CWaylandBackend::SetRelativeMouseMode( + wl_surface *pSurface, bool bRelative ) { - if ( !m_pPointer ) - return; + if ( !m_pPointer ) return; - if ( !!bRelative != !!m_pLockedPointer || ( pSurface != m_pLockedSurface && bRelative ) ) + if ( !!bRelative != !!m_pLockedPointer || + ( pSurface != m_pLockedSurface && bRelative ) ) { if ( m_pLockedPointer ) { @@ -2425,46 +2930,65 @@ namespace gamescope m_pLockedSurface = nullptr; } - if ( bRelative ) - { - m_pLockedPointer = zwp_pointer_constraints_v1_lock_pointer( m_pPointerConstraints, pSurface, m_pPointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT ); - zwp_locked_pointer_v1_add_listener( m_pLockedPointer, &s_LockedPointerListener, this ); - - m_pRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( m_pRelativePointerManager, m_pPointer ); - - m_pLockedSurface = pSurface; - } + if ( bRelative ) + { + m_pLockedPointer = zwp_pointer_constraints_v1_lock_pointer( + m_pPointerConstraints, + pSurface, + m_pPointer, + nullptr, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT ); + zwp_locked_pointer_v1_add_listener( + m_pLockedPointer, &s_LockedPointerListener, this ); + + m_pRelativePointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + m_pRelativePointerManager, m_pPointer ); + + m_pLockedSurface = pSurface; + } m_InputThread.SetRelativePointer( bRelative ); - UpdateCursor(); + UpdateCursor( ); } } - void CWaylandBackend::UpdateCursor() + void CWaylandBackend::UpdateCursor( ) { bool bUseHostCursor = false; - if ( !m_pPointer ) - return; + if ( !m_pPointer ) return; - if ( cv_wayland_mouse_warp_without_keyboard_focus ) - bUseHostCursor = m_bPointerLocked && !m_bKeyboardEntered && m_pDefaultCursorSurface; - else - bUseHostCursor = !m_bKeyboardEntered && m_pDefaultCursorSurface; + if ( cv_wayland_mouse_warp_without_keyboard_focus ) + bUseHostCursor = m_bPointerLocked && !m_bKeyboardEntered && + m_pDefaultCursorSurface; + else + bUseHostCursor = !m_bKeyboardEntered && m_pDefaultCursorSurface; if ( bUseHostCursor ) { - wl_pointer_set_cursor( m_pPointer, m_uPointerEnterSerial, m_pDefaultCursorSurface, m_pDefaultCursorInfo->uXHotspot, m_pDefaultCursorInfo->uYHotspot ); + wl_pointer_set_cursor( + m_pPointer, + m_uPointerEnterSerial, + m_pDefaultCursorSurface, + m_pDefaultCursorInfo->uXHotspot, + m_pDefaultCursorInfo->uYHotspot ); } else { - bool bHideCursor = m_bPointerLocked || !m_pCursorSurface; + bool bHideCursor = m_bPointerLocked || !m_pCursorSurface; if ( bHideCursor ) - wl_pointer_set_cursor( m_pPointer, m_uPointerEnterSerial, nullptr, 0, 0 ); + wl_pointer_set_cursor( + m_pPointer, m_uPointerEnterSerial, nullptr, 0, 0 ); else - wl_pointer_set_cursor( m_pPointer, m_uPointerEnterSerial, m_pCursorSurface, m_pCursorInfo->uXHotspot, m_pCursorInfo->uYHotspot ); + wl_pointer_set_cursor( + m_pPointer, + m_uPointerEnterSerial, + m_pCursorSurface, + m_pCursorInfo->uXHotspot, + m_pCursorInfo->uYHotspot ); } } @@ -2472,101 +2996,177 @@ namespace gamescope // Wayland Callbacks ///////////////////// - void CWaylandBackend::Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ) + void CWaylandBackend::Wayland_Registry_Global( + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ) { - if ( !strcmp( pInterface, wl_compositor_interface.name ) && uVersion >= 4u ) + if ( !strcmp( pInterface, wl_compositor_interface.name ) && + uVersion >= 4u ) { - m_pCompositor = (wl_compositor *)wl_registry_bind( pRegistry, uName, &wl_compositor_interface, 4u ); + m_pCompositor = ( wl_compositor * )wl_registry_bind( + pRegistry, uName, &wl_compositor_interface, 4u ); } - if ( !strcmp( pInterface, wp_single_pixel_buffer_manager_v1_interface.name ) ) + if ( !strcmp( + pInterface, + wp_single_pixel_buffer_manager_v1_interface.name ) ) { - m_pSinglePixelBufferManager = (wp_single_pixel_buffer_manager_v1 *)wl_registry_bind( pRegistry, uName, &wp_single_pixel_buffer_manager_v1_interface, 1u ); + m_pSinglePixelBufferManager = + ( wp_single_pixel_buffer_manager_v1 * )wl_registry_bind( + pRegistry, + uName, + &wp_single_pixel_buffer_manager_v1_interface, + 1u ); } else if ( !strcmp( pInterface, wl_subcompositor_interface.name ) ) { - m_pSubcompositor = (wl_subcompositor *)wl_registry_bind( pRegistry, uName, &wl_subcompositor_interface, 1u ); + m_pSubcompositor = ( wl_subcompositor * )wl_registry_bind( + pRegistry, uName, &wl_subcompositor_interface, 1u ); } - else if ( !strcmp( pInterface, xdg_wm_base_interface.name ) && uVersion >= 1u ) + else if ( + !strcmp( pInterface, xdg_wm_base_interface.name ) && + uVersion >= 1u ) { - static constexpr xdg_wm_base_listener s_Listener = - { - .ping = []( void *pData, xdg_wm_base *pXdgWmBase, uint32_t uSerial ) - { - xdg_wm_base_pong( pXdgWmBase, uSerial ); - } + static constexpr xdg_wm_base_listener s_Listener = { + .ping = + []( void *pData, xdg_wm_base *pXdgWmBase, uint32_t uSerial ) + { xdg_wm_base_pong( pXdgWmBase, uSerial ); } }; - m_pXdgWmBase = (xdg_wm_base *)wl_registry_bind( pRegistry, uName, &xdg_wm_base_interface, 1u ); + m_pXdgWmBase = ( xdg_wm_base * )wl_registry_bind( + pRegistry, uName, &xdg_wm_base_interface, 1u ); xdg_wm_base_add_listener( m_pXdgWmBase, &s_Listener, this ); } - else if ( !strcmp( pInterface, zwp_linux_dmabuf_v1_interface.name ) && uVersion >= 3 ) - { - m_pLinuxDmabuf = (zwp_linux_dmabuf_v1 *)wl_registry_bind( pRegistry, uName, &zwp_linux_dmabuf_v1_interface, 3u ); - static constexpr zwp_linux_dmabuf_v1_listener s_Listener = - { - .format = WAYLAND_NULL(), // Formats are also advertised by the modifier event, ignore them here. - .modifier = WAYLAND_USERDATA_TO_THIS( CWaylandBackend, Wayland_Modifier ), + else if ( + !strcmp( pInterface, zwp_linux_dmabuf_v1_interface.name ) && + uVersion >= 3 ) + { + m_pLinuxDmabuf = ( zwp_linux_dmabuf_v1 * )wl_registry_bind( + pRegistry, uName, &zwp_linux_dmabuf_v1_interface, 3u ); + static constexpr zwp_linux_dmabuf_v1_listener s_Listener = { + .format = WAYLAND_NULL( ), // Formats are also advertised by the + // modifier event, ignore them here. + .modifier = WAYLAND_USERDATA_TO_THIS( + CWaylandBackend, Wayland_Modifier ), }; - zwp_linux_dmabuf_v1_add_listener( m_pLinuxDmabuf, &s_Listener, this ); + zwp_linux_dmabuf_v1_add_listener( + m_pLinuxDmabuf, &s_Listener, this ); } else if ( !strcmp( pInterface, wp_viewporter_interface.name ) ) { - m_pViewporter = (wp_viewporter *)wl_registry_bind( pRegistry, uName, &wp_viewporter_interface, 1u ); + m_pViewporter = ( wp_viewporter * )wl_registry_bind( + pRegistry, uName, &wp_viewporter_interface, 1u ); } - else if ( !strcmp( pInterface, wl_seat_interface.name ) && uVersion >= 8u ) + else if ( + !strcmp( pInterface, wl_seat_interface.name ) && uVersion >= 8u ) { - m_pSeat = (wl_seat *)wl_registry_bind( pRegistry, uName, &wl_seat_interface, 8u ); + m_pSeat = ( wl_seat * )wl_registry_bind( + pRegistry, uName, &wl_seat_interface, 8u ); wl_seat_add_listener( m_pSeat, &s_SeatListener, this ); } else if ( !strcmp( pInterface, wp_presentation_interface.name ) ) { - m_pPresentation = (wp_presentation *)wl_registry_bind( pRegistry, uName, &wp_presentation_interface, 1u ); + m_pPresentation = ( wp_presentation * )wl_registry_bind( + pRegistry, uName, &wp_presentation_interface, 1u ); } else if ( !strcmp( pInterface, wl_output_interface.name ) ) { - wl_output *pOutput = (wl_output *)wl_registry_bind( pRegistry, uName, &wl_output_interface, 4u ); - wl_output_add_listener( pOutput , &s_OutputListener, this ); - m_pOutputs.emplace( std::make_pair( std::move( pOutput ), WaylandOutputInfo{} ) ); + wl_output *pOutput = ( wl_output * )wl_registry_bind( + pRegistry, uName, &wl_output_interface, 4u ); + wl_output_add_listener( pOutput, &s_OutputListener, this ); + m_pOutputs.emplace( + std::make_pair( + std::move( pOutput ), WaylandOutputInfo{} ) ); } - else if ( !strcmp( pInterface, frog_color_management_factory_v1_interface.name ) ) + else if ( !strcmp( + pInterface, + frog_color_management_factory_v1_interface.name ) ) { - m_pFrogColorMgmtFactory = (frog_color_management_factory_v1 *)wl_registry_bind( pRegistry, uName, &frog_color_management_factory_v1_interface, 1u ); + m_pFrogColorMgmtFactory = + ( frog_color_management_factory_v1 * )wl_registry_bind( + pRegistry, + uName, + &frog_color_management_factory_v1_interface, + 1u ); } else if ( !strcmp( pInterface, wp_color_manager_v1_interface.name ) ) { - m_pWPColorManager = (wp_color_manager_v1 *)wl_registry_bind( pRegistry, uName, &wp_color_manager_v1_interface, 1u ); - wp_color_manager_v1_add_listener( m_pWPColorManager, &s_WPColorManagerListener, this ); - } - else if ( !strcmp( pInterface, zwp_pointer_constraints_v1_interface.name ) ) - { - m_pPointerConstraints = (zwp_pointer_constraints_v1 *)wl_registry_bind( pRegistry, uName, &zwp_pointer_constraints_v1_interface, 1u ); - } - else if ( !strcmp( pInterface, zwp_relative_pointer_manager_v1_interface.name ) ) - { - m_pRelativePointerManager = (zwp_relative_pointer_manager_v1 *)wl_registry_bind( pRegistry, uName, &zwp_relative_pointer_manager_v1_interface, 1u ); - } - else if ( !strcmp( pInterface, wp_fractional_scale_manager_v1_interface.name ) ) - { - m_pFractionalScaleManager = (wp_fractional_scale_manager_v1 *)wl_registry_bind( pRegistry, uName, &wp_fractional_scale_manager_v1_interface, 1u ); + m_pWPColorManager = ( wp_color_manager_v1 * )wl_registry_bind( + pRegistry, uName, &wp_color_manager_v1_interface, 1u ); + wp_color_manager_v1_add_listener( + m_pWPColorManager, &s_WPColorManagerListener, this ); + } + else if ( !strcmp( + pInterface, zwp_pointer_constraints_v1_interface.name ) ) + { + m_pPointerConstraints = + ( zwp_pointer_constraints_v1 * )wl_registry_bind( + pRegistry, + uName, + &zwp_pointer_constraints_v1_interface, + 1u ); + } + else if ( !strcmp( + pInterface, + zwp_relative_pointer_manager_v1_interface.name ) ) + { + m_pRelativePointerManager = + ( zwp_relative_pointer_manager_v1 * )wl_registry_bind( + pRegistry, + uName, + &zwp_relative_pointer_manager_v1_interface, + 1u ); + } + else if ( !strcmp( + pInterface, + wp_fractional_scale_manager_v1_interface.name ) ) + { + m_pFractionalScaleManager = + ( wp_fractional_scale_manager_v1 * )wl_registry_bind( + pRegistry, + uName, + &wp_fractional_scale_manager_v1_interface, + 1u ); } else if ( !strcmp( pInterface, wl_shm_interface.name ) ) { - m_pShm = (wl_shm *)wl_registry_bind( pRegistry, uName, &wl_shm_interface, 1u ); + m_pShm = ( wl_shm * )wl_registry_bind( + pRegistry, uName, &wl_shm_interface, 1u ); } - else if ( !strcmp( pInterface, xdg_toplevel_icon_manager_v1_interface.name ) ) + else if ( !strcmp( + pInterface, + xdg_toplevel_icon_manager_v1_interface.name ) ) { - m_pToplevelIconManager = (xdg_toplevel_icon_manager_v1 *)wl_registry_bind( pRegistry, uName, &xdg_toplevel_icon_manager_v1_interface, 1u ); + m_pToplevelIconManager = + ( xdg_toplevel_icon_manager_v1 * )wl_registry_bind( + pRegistry, + uName, + &xdg_toplevel_icon_manager_v1_interface, + 1u ); } else if ( !strcmp( pInterface, wl_data_device_manager_interface.name ) ) { - m_pDataDeviceManager = (wl_data_device_manager *)wl_registry_bind( pRegistry, uName, &wl_data_device_manager_interface, 3u ); + m_pDataDeviceManager = ( wl_data_device_manager * )wl_registry_bind( + pRegistry, uName, &wl_data_device_manager_interface, 3u ); } - else if ( !strcmp( pInterface, zwp_primary_selection_device_manager_v1_interface.name ) ) + else if ( !strcmp( + pInterface, + zwp_primary_selection_device_manager_v1_interface.name ) ) { - m_pPrimarySelectionDeviceManager = (zwp_primary_selection_device_manager_v1 *)wl_registry_bind( pRegistry, uName, &zwp_primary_selection_device_manager_v1_interface, 1u ); + m_pPrimarySelectionDeviceManager = + ( zwp_primary_selection_device_manager_v1 * )wl_registry_bind( + pRegistry, + uName, + &zwp_primary_selection_device_manager_v1_interface, + 1u ); } } - void CWaylandBackend::Wayland_Modifier( zwp_linux_dmabuf_v1 *pDmabuf, uint32_t uFormat, uint32_t uModifierHi, uint32_t uModifierLo ) + void CWaylandBackend::Wayland_Modifier( + zwp_linux_dmabuf_v1 *pDmabuf, + uint32_t uFormat, + uint32_t uModifierHi, + uint32_t uModifierLo ) { uint64_t ulModifier = ( uint64_t( uModifierHi ) << 32 ) | uModifierLo; @@ -2580,38 +3180,46 @@ namespace gamescope xdg_log.infof( "Modifier: %s (0x%" PRIX32 ") %lx%s", drmGetFormatName( uFormat ), uFormat, ulModifier, pszExtraModifierName ); #endif - if ( ulModifier != DRM_FORMAT_MOD_INVALID ) - m_bCanUseModifiers = true; + if ( ulModifier != DRM_FORMAT_MOD_INVALID ) m_bCanUseModifiers = true; - m_FormatModifiers[uFormat].emplace_back( ulModifier ); + m_FormatModifiers[ uFormat ].emplace_back( ulModifier ); } // Output - void CWaylandBackend::Wayland_Output_Geometry( wl_output *pOutput, int32_t nX, int32_t nY, int32_t nPhysicalWidth, int32_t nPhysicalHeight, int32_t nSubpixel, const char *pMake, const char *pModel, int32_t nTransform ) - { - } - void CWaylandBackend::Wayland_Output_Mode( wl_output *pOutput, uint32_t uFlags, int32_t nWidth, int32_t nHeight, int32_t nRefresh ) - { - m_pOutputs[ pOutput ].nRefresh = nRefresh; - } - void CWaylandBackend::Wayland_Output_Done( wl_output *pOutput ) - { - } - void CWaylandBackend::Wayland_Output_Scale( wl_output *pOutput, int32_t nFactor ) - { - m_pOutputs[ pOutput ].nScale = nFactor; - } - void CWaylandBackend::Wayland_Output_Name( wl_output *pOutput, const char *pName ) - { - } - void CWaylandBackend::Wayland_Output_Description( wl_output *pOutput, const char *pDescription ) - { - } + void CWaylandBackend::Wayland_Output_Geometry( + wl_output *pOutput, + int32_t nX, + int32_t nY, + int32_t nPhysicalWidth, + int32_t nPhysicalHeight, + int32_t nSubpixel, + const char *pMake, + const char *pModel, + int32_t nTransform ) + {} + void CWaylandBackend::Wayland_Output_Mode( + wl_output *pOutput, + uint32_t uFlags, + int32_t nWidth, + int32_t nHeight, + int32_t nRefresh ) + { m_pOutputs[ pOutput ].nRefresh = nRefresh; } + void CWaylandBackend::Wayland_Output_Done( wl_output *pOutput ) {} + void + CWaylandBackend::Wayland_Output_Scale( wl_output *pOutput, int32_t nFactor ) + { m_pOutputs[ pOutput ].nScale = nFactor; } + void CWaylandBackend::Wayland_Output_Name( + wl_output *pOutput, const char *pName ) + {} + void CWaylandBackend::Wayland_Output_Description( + wl_output *pOutput, const char *pDescription ) + {} // Seat - void CWaylandBackend::Wayland_Seat_Capabilities( wl_seat *pSeat, uint32_t uCapabilities ) + void CWaylandBackend::Wayland_Seat_Capabilities( + wl_seat *pSeat, uint32_t uCapabilities ) { if ( !!( uCapabilities & WL_SEAT_CAPABILITY_POINTER ) != !!m_pPointer ) { @@ -2627,7 +3235,8 @@ namespace gamescope } } - if ( !!( uCapabilities & WL_SEAT_CAPABILITY_KEYBOARD ) != !!m_pKeyboard ) + if ( !!( uCapabilities & WL_SEAT_CAPABILITY_KEYBOARD ) != + !!m_pKeyboard ) { if ( m_pKeyboard ) { @@ -2637,131 +3246,149 @@ namespace gamescope else { m_pKeyboard = wl_seat_get_keyboard( m_pSeat ); - wl_keyboard_add_listener( m_pKeyboard, &s_KeyboardListener, this ); + wl_keyboard_add_listener( + m_pKeyboard, &s_KeyboardListener, this ); } } } // Pointer - void CWaylandBackend::Wayland_Pointer_Enter( wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface, wl_fixed_t fSurfaceX, wl_fixed_t fSurfaceY ) + void CWaylandBackend::Wayland_Pointer_Enter( + wl_pointer *pPointer, + uint32_t uSerial, + wl_surface *pSurface, + wl_fixed_t fSurfaceX, + wl_fixed_t fSurfaceY ) { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + if ( !IsGamescopeToplevel( pSurface ) ) return; m_uPointerEnterSerial = uSerial; - m_bMouseEntered = true; + m_bMouseEntered = true; - UpdateCursor(); + UpdateCursor( ); } - void CWaylandBackend::Wayland_Pointer_Leave( wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface ) + void CWaylandBackend::Wayland_Pointer_Leave( + wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface ) { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + if ( !IsGamescopeToplevel( pSurface ) ) return; m_bMouseEntered = false; } // Keyboard - void CWaylandBackend::Wayland_Keyboard_Enter( wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface, wl_array *pKeys ) + void CWaylandBackend::Wayland_Keyboard_Enter( + wl_keyboard *pKeyboard, + uint32_t uSerial, + wl_surface *pSurface, + wl_array *pKeys ) { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + if ( !IsGamescopeToplevel( pSurface ) ) return; m_uKeyboardEnterSerial = uSerial; - m_bKeyboardEntered = true; + m_bKeyboardEntered = true; - UpdateCursor(); + UpdateCursor( ); } - void CWaylandBackend::Wayland_Keyboard_Leave( wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface ) + void CWaylandBackend::Wayland_Keyboard_Leave( + wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface ) { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + if ( !IsGamescopeToplevel( pSurface ) ) return; m_bKeyboardEntered = false; - UpdateCursor(); + UpdateCursor( ); } - void CWaylandBackend::Wayland_LockedPointer_Locked( zwp_locked_pointer_v1 *pLockedPointer ) - { - m_bPointerLocked = true; - UpdateCursor(); - } - void CWaylandBackend::Wayland_LockedPointer_Unlocked( zwp_locked_pointer_v1 *pLockedPointer ) - { - m_bPointerLocked = false; - UpdateCursor(); - } + void CWaylandBackend::Wayland_LockedPointer_Locked( + zwp_locked_pointer_v1 *pLockedPointer ) + { + m_bPointerLocked = true; + UpdateCursor( ); + } + void CWaylandBackend::Wayland_LockedPointer_Unlocked( + zwp_locked_pointer_v1 *pLockedPointer ) + { + m_bPointerLocked = false; + UpdateCursor( ); + } // WP Color Manager - void CWaylandBackend::Wayland_WPColorManager_SupportedIntent( wp_color_manager_v1 *pWPColorManager, uint32_t uRenderIntent ) - { - m_WPColorManagerFeatures.eRenderIntents.push_back( static_cast( uRenderIntent ) ); - } - void CWaylandBackend::Wayland_WPColorManager_SupportedFeature( wp_color_manager_v1 *pWPColorManager, uint32_t uFeature ) + void CWaylandBackend::Wayland_WPColorManager_SupportedIntent( + wp_color_manager_v1 *pWPColorManager, uint32_t uRenderIntent ) { - m_WPColorManagerFeatures.eFeatures.push_back( static_cast( uFeature ) ); + m_WPColorManagerFeatures.eRenderIntents.push_back( + static_cast( uRenderIntent ) ); } - void CWaylandBackend::Wayland_WPColorManager_SupportedTFNamed( wp_color_manager_v1 *pWPColorManager, uint32_t uTF ) + void CWaylandBackend::Wayland_WPColorManager_SupportedFeature( + wp_color_manager_v1 *pWPColorManager, uint32_t uFeature ) { - m_WPColorManagerFeatures.eTransferFunctions.push_back( static_cast( uTF ) ); + m_WPColorManagerFeatures.eFeatures.push_back( + static_cast( uFeature ) ); } - void CWaylandBackend::Wayland_WPColorManager_SupportedPrimariesNamed( wp_color_manager_v1 *pWPColorManager, uint32_t uPrimaries ) + void CWaylandBackend::Wayland_WPColorManager_SupportedTFNamed( + wp_color_manager_v1 *pWPColorManager, uint32_t uTF ) { - m_WPColorManagerFeatures.ePrimaries.push_back( static_cast( uPrimaries ) ); + m_WPColorManagerFeatures.eTransferFunctions.push_back( + static_cast( uTF ) ); } - void CWaylandBackend::Wayland_WPColorManager_ColorManagerDone( wp_color_manager_v1 *pWPColorManager ) + void CWaylandBackend::Wayland_WPColorManager_SupportedPrimariesNamed( + wp_color_manager_v1 *pWPColorManager, uint32_t uPrimaries ) { - + m_WPColorManagerFeatures.ePrimaries.push_back( + static_cast( uPrimaries ) ); } + void CWaylandBackend::Wayland_WPColorManager_ColorManagerDone( + wp_color_manager_v1 *pWPColorManager ) + {} // Data Source - void CWaylandBackend::Wayland_DataSource_Send( struct wl_data_source *pSource, const char *pMime, int nFd ) + void CWaylandBackend::Wayland_DataSource_Send( + struct wl_data_source *pSource, const char *pMime, int nFd ) { - ssize_t len = m_pClipboard->length(); - if ( write( nFd, m_pClipboard->c_str(), len ) != len ) + ssize_t len = m_pClipboard->length( ); + if ( write( nFd, m_pClipboard->c_str( ), len ) != len ) xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); close( nFd ); } - void CWaylandBackend::Wayland_DataSource_Cancelled( struct wl_data_source *pSource ) - { - wl_data_source_destroy( pSource ); - } + void CWaylandBackend::Wayland_DataSource_Cancelled( + struct wl_data_source *pSource ) + { wl_data_source_destroy( pSource ); } // Primary Selection Source - void CWaylandBackend::Wayland_PrimarySelectionSource_Send( struct zwp_primary_selection_source_v1 *pSource, const char *pMime, int nFd ) + void CWaylandBackend::Wayland_PrimarySelectionSource_Send( + struct zwp_primary_selection_source_v1 *pSource, + const char *pMime, + int nFd ) { - ssize_t len = m_pPrimarySelection->length(); - if ( write( nFd, m_pPrimarySelection->c_str(), len ) != len ) - xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); + ssize_t len = m_pPrimarySelection->length( ); + if ( write( nFd, m_pPrimarySelection->c_str( ), len ) != len ) + xdg_log.infof( "Failed to write all %zd bytes to clipboard", len ); close( nFd ); } - void CWaylandBackend::Wayland_PrimarySelectionSource_Cancelled( struct zwp_primary_selection_source_v1 *pSource) - { - zwp_primary_selection_source_v1_destroy( pSource ); - } + void CWaylandBackend::Wayland_PrimarySelectionSource_Cancelled( + struct zwp_primary_selection_source_v1 *pSource ) + { zwp_primary_selection_source_v1_destroy( pSource ); } /////////////////////// // CWaylandInputThread /////////////////////// - CWaylandInputThread::CWaylandInputThread() - : m_Thread{ [this](){ this->ThreadFunc(); } } - { - } + CWaylandInputThread::CWaylandInputThread( ) : + m_Thread{ [ this ]( ) { this->ThreadFunc( ); } } + {} - CWaylandInputThread::~CWaylandInputThread() + CWaylandInputThread::~CWaylandInputThread( ) { m_bInitted = true; - m_bInitted.notify_all(); + m_bInitted.notify_all( ); - m_Waiter.Shutdown(); - m_Thread.join(); + m_Waiter.Shutdown( ); + m_Thread.join( ); } bool CWaylandInputThread::Init( CWaylandBackend *pBackend ) @@ -2774,28 +3401,31 @@ namespace gamescope return false; } - if ( !( m_pQueue = wl_display_create_queue( m_pBackend->GetDisplay() ) ) ) + if ( !( m_pQueue = + wl_display_create_queue( m_pBackend->GetDisplay( ) ) ) ) { xdg_log.errorf( "Couldn't create input thread queue." ); return false; } - if ( !( m_pDisplayWrapper = QueueLaunder( m_pBackend->GetDisplay() ) ) ) + if ( !( m_pDisplayWrapper = + QueueLaunder( m_pBackend->GetDisplay( ) ) ) ) { xdg_log.errorf( "Couldn't create display proxy for input thread" ); return false; } wl_registry *pRegistry; - if ( !( pRegistry = wl_display_get_registry( m_pDisplayWrapper.get() ) ) ) + if ( !( pRegistry = + wl_display_get_registry( m_pDisplayWrapper.get( ) ) ) ) { xdg_log.errorf( "Couldn't create registry for input thread" ); return false; } wl_registry_add_listener( pRegistry, &s_RegistryListener, this ); - wl_display_roundtrip_queue( pBackend->GetDisplay(), m_pQueue ); - wl_display_roundtrip_queue( pBackend->GetDisplay(), m_pQueue ); + wl_display_roundtrip_queue( pBackend->GetDisplay( ), m_pQueue ); + wl_display_roundtrip_queue( pBackend->GetDisplay( ), m_pQueue ); wl_registry_destroy( pRegistry ); pRegistry = nullptr; @@ -2807,87 +3437,89 @@ namespace gamescope } m_bInitted = true; - m_bInitted.notify_all(); + m_bInitted.notify_all( ); return true; } - void CWaylandInputThread::ThreadFunc() + void CWaylandInputThread::ThreadFunc( ) { m_bInitted.wait( false ); - if ( !m_Waiter.IsRunning() ) - return; + if ( !m_Waiter.IsRunning( ) ) return; - int nFD = wl_display_get_fd( m_pBackend->GetDisplay() ); - if ( nFD < 0 ) - { - abort(); - } + int nFD = wl_display_get_fd( m_pBackend->GetDisplay( ) ); + if ( nFD < 0 ) { abort( ); } CFunctionWaitable waitable( nFD ); m_Waiter.AddWaitable( &waitable ); int nRet = 0; - while ( m_Waiter.IsRunning() ) + while ( m_Waiter.IsRunning( ) ) { - if ( ( nRet = wl_display_dispatch_queue_pending( m_pBackend->GetDisplay(), m_pQueue ) ) < 0 ) + if ( ( nRet = wl_display_dispatch_queue_pending( + m_pBackend->GetDisplay( ), m_pQueue ) ) < 0 ) { - abort(); + abort( ); } - if ( ( nRet = wl_display_prepare_read_queue( m_pBackend->GetDisplay(), m_pQueue ) ) < 0 ) + if ( ( nRet = wl_display_prepare_read_queue( + m_pBackend->GetDisplay( ), m_pQueue ) ) < 0 ) { - if ( errno == EAGAIN || errno == EINTR ) - continue; + if ( errno == EAGAIN || errno == EINTR ) continue; - abort(); + abort( ); } - if ( ( nRet = m_Waiter.PollEvents() ) <= 0 ) + if ( ( nRet = m_Waiter.PollEvents( ) ) <= 0 ) { - wl_display_cancel_read( m_pBackend->GetDisplay() ); - if ( nRet < 0 ) - abort(); + wl_display_cancel_read( m_pBackend->GetDisplay( ) ); + if ( nRet < 0 ) abort( ); assert( nRet == 0 ); continue; } - if ( ( nRet = wl_display_read_events( m_pBackend->GetDisplay() ) ) < 0 ) + if ( ( nRet = wl_display_read_events( + m_pBackend->GetDisplay( ) ) ) < 0 ) { - abort(); + abort( ); } } } - template - std::shared_ptr CWaylandInputThread::QueueLaunder( T* pObject ) + template + std::shared_ptr CWaylandInputThread::QueueLaunder( T *pObject ) { - if ( !pObject ) - return nullptr; + if ( !pObject ) return nullptr; - T *pObjectWrapper = (T*)wl_proxy_create_wrapper( (void *)pObject ); - if ( !pObjectWrapper ) - return nullptr; - wl_proxy_set_queue( (wl_proxy *)pObjectWrapper, m_pQueue ); + T *pObjectWrapper = ( T * )wl_proxy_create_wrapper( ( void * )pObject ); + if ( !pObjectWrapper ) return nullptr; + wl_proxy_set_queue( ( wl_proxy * )pObjectWrapper, m_pQueue ); - return std::shared_ptr{ pObjectWrapper, []( T *pThing ){ wl_proxy_wrapper_destroy( (void *)pThing ); } }; + return std::shared_ptr{ + pObjectWrapper, + []( T *pThing ) { wl_proxy_wrapper_destroy( ( void * )pThing ); } + }; } void CWaylandInputThread::SetRelativePointer( bool bRelative ) { - if ( bRelative == !!m_pRelativePointer.load() ) - return; - // This constructors/destructors the display's mutex, so should be safe to do across threads. - if ( !bRelative ) - { - m_pRelativePointer = nullptr; - } + if ( bRelative == !!m_pRelativePointer.load( ) ) return; + // This constructors/destructors the display's mutex, so should be safe + // to do across threads. + if ( !bRelative ) { m_pRelativePointer = nullptr; } else { - zwp_relative_pointer_v1 *pRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( m_pRelativePointerManager, m_pPointer ); - m_pRelativePointer = std::shared_ptr{ pRelativePointer, []( zwp_relative_pointer_v1 *pObject ){ zwp_relative_pointer_v1_destroy( pObject ); } }; - zwp_relative_pointer_v1_add_listener( pRelativePointer, &s_RelativePointerListener, this ); + zwp_relative_pointer_v1 *pRelativePointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + m_pRelativePointerManager, m_pPointer ); + m_pRelativePointer = std::shared_ptr{ + pRelativePointer, + []( zwp_relative_pointer_v1 *pObject ) + { zwp_relative_pointer_v1_destroy( pObject ); } + }; + zwp_relative_pointer_v1_add_listener( + pRelativePointer, &s_RelativePointerListener, this ); } } @@ -2901,7 +3533,9 @@ namespace gamescope { if ( !bPressed ) { - static_cast< CWaylandConnector * >( m_pBackend->GetCurrentConnector() )->SetFullscreen( !g_bFullscreen ); + static_cast( + m_pBackend->GetCurrentConnector( ) ) + ->SetFullscreen( !g_bFullscreen ); } return; } @@ -2928,8 +3562,11 @@ namespace gamescope { if ( !bPressed ) { - g_wantedUpscaleFilter = ( g_wantedUpscaleFilter == GamescopeUpscaleFilter::FSR ) ? - GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::FSR; + g_wantedUpscaleFilter = + ( g_wantedUpscaleFilter == + GamescopeUpscaleFilter::FSR ) + ? GamescopeUpscaleFilter::LINEAR + : GamescopeUpscaleFilter::FSR; } return; } @@ -2938,8 +3575,11 @@ namespace gamescope { if ( !bPressed ) { - g_wantedUpscaleFilter = ( g_wantedUpscaleFilter == GamescopeUpscaleFilter::NIS ) ? - GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NIS; + g_wantedUpscaleFilter = + ( g_wantedUpscaleFilter == + GamescopeUpscaleFilter::NIS ) + ? GamescopeUpscaleFilter::LINEAR + : GamescopeUpscaleFilter::NIS; } return; } @@ -2948,7 +3588,8 @@ namespace gamescope { if ( !bPressed ) { - g_upscaleFilterSharpness = std::min( 20, g_upscaleFilterSharpness + 1 ); + g_upscaleFilterSharpness = + std::min( 20, g_upscaleFilterSharpness + 1 ); } return; } @@ -2957,7 +3598,8 @@ namespace gamescope { if ( !bPressed ) { - g_upscaleFilterSharpness = std::max( 0, g_upscaleFilterSharpness - 1 ); + g_upscaleFilterSharpness = + std::max( 0, g_upscaleFilterSharpness - 1 ); } return; } @@ -2966,7 +3608,8 @@ namespace gamescope { if ( !bPressed ) { - gamescope::CScreenshotManager::Get().TakeScreenshot( true ); + gamescope::CScreenshotManager::Get( ).TakeScreenshot( + true ); } return; } @@ -2976,29 +3619,42 @@ namespace gamescope } } - wlserver_lock(); + wlserver_lock( ); wlserver_key( uKey, bPressed, ++m_uFakeTimestamp ); - wlserver_unlock(); + wlserver_unlock( ); } // Registry - void CWaylandInputThread::Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion ) + void CWaylandInputThread::Wayland_Registry_Global( + wl_registry *pRegistry, + uint32_t uName, + const char *pInterface, + uint32_t uVersion ) { if ( !strcmp( pInterface, wl_seat_interface.name ) && uVersion >= 8u ) { - m_pSeat = (wl_seat *)wl_registry_bind( pRegistry, uName, &wl_seat_interface, 8u ); + m_pSeat = ( wl_seat * )wl_registry_bind( + pRegistry, uName, &wl_seat_interface, 8u ); wl_seat_add_listener( m_pSeat, &s_SeatListener, this ); } - else if ( !strcmp( pInterface, zwp_relative_pointer_manager_v1_interface.name ) ) + else if ( !strcmp( + pInterface, + zwp_relative_pointer_manager_v1_interface.name ) ) { - m_pRelativePointerManager = (zwp_relative_pointer_manager_v1 *)wl_registry_bind( pRegistry, uName, &zwp_relative_pointer_manager_v1_interface, 1u ); + m_pRelativePointerManager = + ( zwp_relative_pointer_manager_v1 * )wl_registry_bind( + pRegistry, + uName, + &zwp_relative_pointer_manager_v1_interface, + 1u ); } } // Seat - void CWaylandInputThread::Wayland_Seat_Capabilities( wl_seat *pSeat, uint32_t uCapabilities ) + void CWaylandInputThread::Wayland_Seat_Capabilities( + wl_seat *pSeat, uint32_t uCapabilities ) { if ( !!( uCapabilities & WL_SEAT_CAPABILITY_POINTER ) != !!m_pPointer ) { @@ -3014,7 +3670,8 @@ namespace gamescope } } - if ( !!( uCapabilities & WL_SEAT_CAPABILITY_KEYBOARD ) != !!m_pKeyboard ) + if ( !!( uCapabilities & WL_SEAT_CAPABILITY_KEYBOARD ) != + !!m_pKeyboard ) { if ( m_pKeyboard ) { @@ -3024,51 +3681,58 @@ namespace gamescope else { m_pKeyboard = wl_seat_get_keyboard( m_pSeat ); - wl_keyboard_add_listener( m_pKeyboard, &s_KeyboardListener, this ); + wl_keyboard_add_listener( + m_pKeyboard, &s_KeyboardListener, this ); } } } - void CWaylandInputThread::Wayland_Seat_Name( wl_seat *pSeat, const char *pName ) - { - xdg_log.infof( "Seat name: %s", pName ); - } + void + CWaylandInputThread::Wayland_Seat_Name( wl_seat *pSeat, const char *pName ) + { xdg_log.infof( "Seat name: %s", pName ); } // Pointer - void CWaylandInputThread::Wayland_Pointer_Enter( wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface, wl_fixed_t fSurfaceX, wl_fixed_t fSurfaceY ) - { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + void CWaylandInputThread::Wayland_Pointer_Enter( + wl_pointer *pPointer, + uint32_t uSerial, + wl_surface *pSurface, + wl_fixed_t fSurfaceX, + wl_fixed_t fSurfaceY ) + { + if ( !IsGamescopeToplevel( pSurface ) ) return; - m_pCurrentCursorSurface = pSurface; - m_bMouseEntered = true; - m_uPointerEnterSerial = uSerial; + m_pCurrentCursorSurface = pSurface; + m_bMouseEntered = true; + m_uPointerEnterSerial = uSerial; - Wayland_Pointer_Motion( pPointer, 0, fSurfaceX, fSurfaceY ); - } - void CWaylandInputThread::Wayland_Pointer_Leave( wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface ) - { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + Wayland_Pointer_Motion( pPointer, 0, fSurfaceX, fSurfaceY ); + } + void CWaylandInputThread::Wayland_Pointer_Leave( + wl_pointer *pPointer, uint32_t uSerial, wl_surface *pSurface ) + { + if ( !IsGamescopeToplevel( pSurface ) ) return; - m_pCurrentCursorSurface = nullptr; - m_bMouseEntered = false; - } - void CWaylandInputThread::Wayland_Pointer_Motion( wl_pointer *pPointer, uint32_t uTime, wl_fixed_t fSurfaceX, wl_fixed_t fSurfaceY ) - { - if ( !m_bMouseEntered ) - return; + m_pCurrentCursorSurface = nullptr; + m_bMouseEntered = false; + } + void CWaylandInputThread::Wayland_Pointer_Motion( + wl_pointer *pPointer, + uint32_t uTime, + wl_fixed_t fSurfaceX, + wl_fixed_t fSurfaceY ) + { + if ( !m_bMouseEntered ) return; - CWaylandPlane *pPlane = (CWaylandPlane *)wl_surface_get_user_data( m_pCurrentCursorSurface ); + CWaylandPlane *pPlane = ( CWaylandPlane * )wl_surface_get_user_data( + m_pCurrentCursorSurface ); - if ( !pPlane ) - return; + if ( !pPlane ) return; - if ( m_pRelativePointer.load() != nullptr ) - return; + if ( m_pRelativePointer.load( ) != nullptr ) return; - if ( !cv_wayland_mouse_warp_without_keyboard_focus && !m_bKeyboardEntered ) + if ( !cv_wayland_mouse_warp_without_keyboard_focus && + !m_bKeyboardEntered ) { // Don't do any motion/movement stuff if we don't have kb focus m_ofPendingCursorX = fSurfaceX; @@ -3076,48 +3740,66 @@ namespace gamescope return; } - auto oState = pPlane->GetCurrentState(); - if ( !oState ) - return; + auto oState = pPlane->GetCurrentState( ); + if ( !oState ) return; uint32_t uScale = oState->uFractionalScale; - double flX = ( wl_fixed_to_double( fSurfaceX ) * uScale / 120.0 + oState->nDestX ) / g_nOutputWidth; - double flY = ( wl_fixed_to_double( fSurfaceY ) * uScale / 120.0 + oState->nDestY ) / g_nOutputHeight; + double flX = ( wl_fixed_to_double( fSurfaceX ) * uScale / 120.0 + + oState->nDestX ) / + g_nOutputWidth; + double flY = ( wl_fixed_to_double( fSurfaceY ) * uScale / 120.0 + + oState->nDestY ) / + g_nOutputHeight; - wlserver_lock(); + wlserver_lock( ); wlserver_touchmotion( flX, flY, 0, ++m_uFakeTimestamp ); - wlserver_unlock(); + wlserver_unlock( ); } - void CWaylandInputThread::Wayland_Pointer_Button( wl_pointer *pPointer, uint32_t uSerial, uint32_t uTime, uint32_t uButton, uint32_t uState ) + void CWaylandInputThread::Wayland_Pointer_Button( + wl_pointer *pPointer, + uint32_t uSerial, + uint32_t uTime, + uint32_t uButton, + uint32_t uState ) { // Don't do any motion/movement stuff if we don't have kb focus - if ( !cv_wayland_mouse_warp_without_keyboard_focus && !m_bKeyboardEntered ) + if ( !cv_wayland_mouse_warp_without_keyboard_focus && + !m_bKeyboardEntered ) return; - wlserver_lock(); - wlserver_mousebutton( uButton, uState == WL_POINTER_BUTTON_STATE_PRESSED, ++m_uFakeTimestamp ); - wlserver_unlock(); - } - void CWaylandInputThread::Wayland_Pointer_Axis( wl_pointer *pPointer, uint32_t uTime, uint32_t uAxis, wl_fixed_t fValue ) - { - } - void CWaylandInputThread::Wayland_Pointer_Axis_Source( wl_pointer *pPointer, uint32_t uAxisSource ) - { - m_uAxisSource = uAxisSource; - } - void CWaylandInputThread::Wayland_Pointer_Axis_Stop( wl_pointer *pPointer, uint32_t uTime, uint32_t uAxis ) - { - } - void CWaylandInputThread::Wayland_Pointer_Axis_Discrete( wl_pointer *pPointer, uint32_t uAxis, int32_t nDiscrete ) - { - } - void CWaylandInputThread::Wayland_Pointer_Axis_Value120( wl_pointer *pPointer, uint32_t uAxis, int32_t nValue120 ) - { - if ( !cv_wayland_mouse_warp_without_keyboard_focus && !m_bKeyboardEntered ) + wlserver_lock( ); + wlserver_mousebutton( + uButton, + uState == WL_POINTER_BUTTON_STATE_PRESSED, + ++m_uFakeTimestamp ); + wlserver_unlock( ); + } + void CWaylandInputThread::Wayland_Pointer_Axis( + wl_pointer *pPointer, + uint32_t uTime, + uint32_t uAxis, + wl_fixed_t fValue ) + {} + void CWaylandInputThread::Wayland_Pointer_Axis_Source( + wl_pointer *pPointer, uint32_t uAxisSource ) + { m_uAxisSource = uAxisSource; } + void CWaylandInputThread::Wayland_Pointer_Axis_Stop( + wl_pointer *pPointer, uint32_t uTime, uint32_t uAxis ) + {} + void CWaylandInputThread::Wayland_Pointer_Axis_Discrete( + wl_pointer *pPointer, uint32_t uAxis, int32_t nDiscrete ) + {} + void CWaylandInputThread::Wayland_Pointer_Axis_Value120( + wl_pointer *pPointer, uint32_t uAxis, int32_t nValue120 ) + { + if ( !cv_wayland_mouse_warp_without_keyboard_focus && + !m_bKeyboardEntered ) return; - assert( uAxis == WL_POINTER_AXIS_VERTICAL_SCROLL || uAxis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ); + assert( + uAxis == WL_POINTER_AXIS_VERTICAL_SCROLL || + uAxis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ); // Vertical is first in the wl_pointer_axis enum, flip y,x -> x,y m_flScrollAccum[ !uAxis ] += nValue120 / 120.0; @@ -3125,37 +3807,38 @@ namespace gamescope void CWaylandInputThread::Wayland_Pointer_Frame( wl_pointer *pPointer ) { defer( m_uAxisSource = WL_POINTER_AXIS_SOURCE_WHEEL ); - double flX = m_flScrollAccum[0]; - double flY = m_flScrollAccum[1]; - m_flScrollAccum[0] = 0.0; - m_flScrollAccum[1] = 0.0; + double flX = m_flScrollAccum[ 0 ]; + double flY = m_flScrollAccum[ 1 ]; + m_flScrollAccum[ 0 ] = 0.0; + m_flScrollAccum[ 1 ] = 0.0; - if ( !cv_wayland_mouse_warp_without_keyboard_focus && !m_bKeyboardEntered ) + if ( !cv_wayland_mouse_warp_without_keyboard_focus && + !m_bKeyboardEntered ) return; - if ( m_uAxisSource != WL_POINTER_AXIS_SOURCE_WHEEL ) - return; + if ( m_uAxisSource != WL_POINTER_AXIS_SOURCE_WHEEL ) return; - if ( flX == 0.0 && flY == 0.0 ) - return; + if ( flX == 0.0 && flY == 0.0 ) return; - wlserver_lock(); + wlserver_lock( ); wlserver_mousewheel( flX, flY, ++m_uFakeTimestamp ); - wlserver_unlock(); + wlserver_unlock( ); } // Keyboard - void CWaylandInputThread::Wayland_Keyboard_Keymap( wl_keyboard *pKeyboard, uint32_t uFormat, int32_t nFd, uint32_t uSize ) + void CWaylandInputThread::Wayland_Keyboard_Keymap( + wl_keyboard *pKeyboard, uint32_t uFormat, int32_t nFd, uint32_t uSize ) { // We are not doing much with the keymap, we pass keycodes thru. - // Ideally we'd use this to influence our keymap to clients, eg. x server. + // Ideally we'd use this to influence our keymap to clients, eg. x + // server. defer( close( nFd ) ); - if ( uFormat != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 ) - return; + if ( uFormat != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 ) return; - char *pMap = (char *)mmap( nullptr, uSize, PROT_READ, MAP_PRIVATE, nFd, 0 ); + char *pMap = + ( char * )mmap( nullptr, uSize, PROT_READ, MAP_PRIVATE, nFd, 0 ); if ( !pMap || pMap == MAP_FAILED ) { xdg_log.errorf( "Failed to map keymap fd." ); @@ -3163,7 +3846,11 @@ namespace gamescope } defer( munmap( pMap, uSize ) ); - xkb_keymap *pKeymap = xkb_keymap_new_from_string( m_pXkbContext, pMap, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS ); + xkb_keymap *pKeymap = xkb_keymap_new_from_string( + m_pXkbContext, + pMap, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS ); if ( !pKeymap ) { xdg_log.errorf( "Failed to create xkb_keymap" ); @@ -3174,18 +3861,24 @@ namespace gamescope m_pXkbKeymap = pKeymap; for ( uint32_t i = 0; i < GAMESCOPE_WAYLAND_MOD_COUNT; i++ ) - m_uModMask[ i ] = 1u << xkb_keymap_mod_get_index( m_pXkbKeymap, WaylandModifierToXkbModifierName( ( WaylandModifierIndex ) i ) ); + m_uModMask[ i ] = 1u << xkb_keymap_mod_get_index( + m_pXkbKeymap, + WaylandModifierToXkbModifierName( + ( WaylandModifierIndex )i ) ); } - void CWaylandInputThread::Wayland_Keyboard_Enter( wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface, wl_array *pKeys ) + void CWaylandInputThread::Wayland_Keyboard_Enter( + wl_keyboard *pKeyboard, + uint32_t uSerial, + wl_surface *pSurface, + wl_array *pKeys ) { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + if ( !IsGamescopeToplevel( pSurface ) ) return; m_bKeyboardEntered = true; - m_uScancodesHeld.clear(); + m_uScancodesHeld.clear( ); - const uint32_t *pBegin = (uint32_t *)pKeys->data; - const uint32_t *pEnd = pBegin + ( pKeys->size / sizeof(uint32_t) ); + const uint32_t *pBegin = ( uint32_t * )pKeys->data; + const uint32_t *pEnd = pBegin + ( pKeys->size / sizeof( uint32_t ) ); std::span keys{ pBegin, pEnd }; for ( uint32_t uKey : keys ) { @@ -3195,71 +3888,87 @@ namespace gamescope if ( m_ofPendingCursorX ) { - assert( m_ofPendingCursorY.has_value() ); + assert( m_ofPendingCursorY.has_value( ) ); - Wayland_Pointer_Motion( m_pPointer, 0, *m_ofPendingCursorX, *m_ofPendingCursorY ); + Wayland_Pointer_Motion( + m_pPointer, 0, *m_ofPendingCursorX, *m_ofPendingCursorY ); m_ofPendingCursorX = std::nullopt; m_ofPendingCursorY = std::nullopt; } } - void CWaylandInputThread::Wayland_Keyboard_Leave( wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface ) + void CWaylandInputThread::Wayland_Keyboard_Leave( + wl_keyboard *pKeyboard, uint32_t uSerial, wl_surface *pSurface ) { - if ( !IsGamescopeToplevel( pSurface ) ) - return; + if ( !IsGamescopeToplevel( pSurface ) ) return; m_bKeyboardEntered = false; - m_uKeyModifiers = 0; + m_uKeyModifiers = 0; for ( uint32_t uKey : m_uScancodesHeld ) HandleKey( uKey, false ); - m_uScancodesHeld.clear(); + m_uScancodesHeld.clear( ); } - void CWaylandInputThread::Wayland_Keyboard_Key( wl_keyboard *pKeyboard, uint32_t uSerial, uint32_t uTime, uint32_t uKey, uint32_t uState ) + void CWaylandInputThread::Wayland_Keyboard_Key( + wl_keyboard *pKeyboard, + uint32_t uSerial, + uint32_t uTime, + uint32_t uKey, + uint32_t uState ) { - if ( !m_bKeyboardEntered ) - return; + if ( !m_bKeyboardEntered ) return; - const bool bPressed = uState == WL_KEYBOARD_KEY_STATE_PRESSED; + const bool bPressed = uState == WL_KEYBOARD_KEY_STATE_PRESSED; const bool bWasPressed = m_uScancodesHeld.contains( uKey ); - if ( bWasPressed == bPressed ) - return; + if ( bWasPressed == bPressed ) return; HandleKey( uKey, bPressed ); - if ( bWasPressed ) - m_uScancodesHeld.erase( uKey ); + if ( bWasPressed ) m_uScancodesHeld.erase( uKey ); else m_uScancodesHeld.emplace( uKey ); } - void CWaylandInputThread::Wayland_Keyboard_Modifiers( wl_keyboard *pKeyboard, uint32_t uSerial, uint32_t uModsDepressed, uint32_t uModsLatched, uint32_t uModsLocked, uint32_t uGroup ) - { - m_uKeyModifiers = uModsDepressed | uModsLatched | uModsLocked; - } - void CWaylandInputThread::Wayland_Keyboard_RepeatInfo( wl_keyboard *pKeyboard, int32_t nRate, int32_t nDelay ) - { - } + void CWaylandInputThread::Wayland_Keyboard_Modifiers( + wl_keyboard *pKeyboard, + uint32_t uSerial, + uint32_t uModsDepressed, + uint32_t uModsLatched, + uint32_t uModsLocked, + uint32_t uGroup ) + { m_uKeyModifiers = uModsDepressed | uModsLatched | uModsLocked; } + void CWaylandInputThread::Wayland_Keyboard_RepeatInfo( + wl_keyboard *pKeyboard, int32_t nRate, int32_t nDelay ) + {} // Relative Pointer - void CWaylandInputThread::Wayland_RelativePointer_RelativeMotion( zwp_relative_pointer_v1 *pRelativePointer, uint32_t uTimeHi, uint32_t uTimeLo, wl_fixed_t fDx, wl_fixed_t fDy, wl_fixed_t fDxUnaccel, wl_fixed_t fDyUnaccel ) + void CWaylandInputThread::Wayland_RelativePointer_RelativeMotion( + zwp_relative_pointer_v1 *pRelativePointer, + uint32_t uTimeHi, + uint32_t uTimeLo, + wl_fixed_t fDx, + wl_fixed_t fDy, + wl_fixed_t fDxUnaccel, + wl_fixed_t fDyUnaccel ) { - // Don't do any motion/movement stuff if we don't have kb focus - if ( !m_pBackend->m_bPointerLocked || ( !cv_wayland_mouse_relmotion_without_keyboard_focus && !m_bKeyboardEntered ) ) - return; + // Don't do any motion/movement stuff if we don't have kb focus + if ( !m_pBackend->m_bPointerLocked || + ( !cv_wayland_mouse_relmotion_without_keyboard_focus && + !m_bKeyboardEntered ) ) + return; - wlserver_lock(); - wlserver_mousemotion( wl_fixed_to_double( fDxUnaccel ), wl_fixed_to_double( fDyUnaccel ), ++m_uFakeTimestamp ); - wlserver_unlock(); + wlserver_lock( ); + wlserver_mousemotion( + wl_fixed_to_double( fDxUnaccel ), + wl_fixed_to_double( fDyUnaccel ), + ++m_uFakeTimestamp ); + wlserver_unlock( ); } ///////////////////////// // Backend Instantiator ///////////////////////// - template <> - bool IBackend::Set() - { - return Set( new CWaylandBackend{} ); - } -} + template<> bool IBackend::Set( ) + { return Set( new CWaylandBackend{} ); } +} // namespace gamescope diff --git a/src/BufferMemo.cpp b/src/BufferMemo.cpp index bd892c484a..8fd8098004 100644 --- a/src/BufferMemo.cpp +++ b/src/BufferMemo.cpp @@ -10,28 +10,27 @@ namespace gamescope // CBufferMemo ///////////////// - CBufferMemo::CBufferMemo( CBufferMemoizer *pMemoizer, wlr_buffer *pBuffer, OwningRc pTexture ) - : m_pMemoizer{ pMemoizer } - , m_pBuffer{ pBuffer } - , m_pVulkanTexture{ std::move( pTexture ) } - { - } + CBufferMemo::CBufferMemo( + CBufferMemoizer *pMemoizer, + wlr_buffer *pBuffer, + OwningRc pTexture ) : + m_pMemoizer{ pMemoizer }, + m_pBuffer{ pBuffer }, + m_pVulkanTexture{ std::move( pTexture ) } + {} - CBufferMemo::~CBufferMemo() - { - wl_list_remove( &m_DeleteListener.link ); - } + CBufferMemo::~CBufferMemo( ) { wl_list_remove( &m_DeleteListener.link ); } - void CBufferMemo::Finalize() + void CBufferMemo::Finalize( ) { - wlserver_lock(); - wl_signal_add( &m_pBuffer->events.destroy, &m_DeleteListener ); - wlserver_unlock(); + wlserver_lock( ); + wl_signal_add( &m_pBuffer->events.destroy, &m_DeleteListener ); + wlserver_unlock( ); } void CBufferMemo::OnBufferDestroyed( void *pUserData ) { - assert( m_pVulkanTexture->GetRefCount() == 0 ); + assert( m_pVulkanTexture->GetRefCount( ) == 0 ); m_pMemoizer->UnmemoizeBuffer( m_pBuffer ); } @@ -39,37 +38,42 @@ namespace gamescope // CBufferMemoizer /////////////////// - OwningRc CBufferMemoizer::LookupVulkanTexture( wlr_buffer *pBuffer ) const + OwningRc + CBufferMemoizer::LookupVulkanTexture( wlr_buffer *pBuffer ) const { std::scoped_lock lock{ m_mutBufferMemos }; - auto iter = m_BufferMemos.find( pBuffer ); - if ( iter == m_BufferMemos.end() ) - return nullptr; + auto iter = m_BufferMemos.find( pBuffer ); + if ( iter == m_BufferMemos.end( ) ) return nullptr; - return iter->second.GetVulkanTexture(); + return iter->second.GetVulkanTexture( ); } - void CBufferMemoizer::MemoizeBuffer( wlr_buffer *pBuffer, OwningRc pTexture ) + void CBufferMemoizer::MemoizeBuffer( + wlr_buffer *pBuffer, OwningRc pTexture ) { - memo_log.debugf( "Memoizing new buffer: wlr_buffer %p -> texture: %p", pBuffer, pTexture.get() ); + memo_log.debugf( + "Memoizing new buffer: wlr_buffer %p -> texture: %p", + pBuffer, + pTexture.get( ) ); - // Can't hold m_mutBufferMemos while we finalize link from pMemo to buffer - // as we can't have wlserver_lock held otherwise we can deadlock when - // adding the wl_signal. + // Can't hold m_mutBufferMemos while we finalize link from pMemo to + // buffer as we can't have wlserver_lock held otherwise we can deadlock + // when adding the wl_signal. // - // This is fine as the lookups only happen on one thread, that calls this - // or LookupVulkanTexture. + // This is fine as the lookups only happen on one thread, that calls + // this or LookupVulkanTexture. CBufferMemo *pMemo = nullptr; { std::scoped_lock lock{ m_mutBufferMemos }; - auto [ iter, bSuccess ] = m_BufferMemos.emplace( std::piecewise_construct, + auto [ iter, bSuccess ] = m_BufferMemos.emplace( + std::piecewise_construct, std::forward_as_tuple( pBuffer ), std::forward_as_tuple( this, pBuffer, std::move( pTexture ) ) ); assert( bSuccess ); pMemo = &iter->second; } - pMemo->Finalize(); + pMemo->Finalize( ); } void CBufferMemoizer::UnmemoizeBuffer( wlr_buffer *pBuffer ) @@ -77,8 +81,8 @@ namespace gamescope memo_log.debugf( "Unmemoizing buffer: wlr_buffer %p", pBuffer ); std::scoped_lock lock{ m_mutBufferMemos }; - auto iter = m_BufferMemos.find( pBuffer ); - assert( iter != m_BufferMemos.end() ); + auto iter = m_BufferMemos.find( pBuffer ); + assert( iter != m_BufferMemos.end( ) ); m_BufferMemos.erase( iter ); } -} \ No newline at end of file +} // namespace gamescope diff --git a/src/BufferMemo.h b/src/BufferMemo.h index 5b905c57ff..af908ea3ea 100644 --- a/src/BufferMemo.h +++ b/src/BufferMemo.h @@ -3,15 +3,23 @@ #include "rc.h" #include "rendervulkan.hpp" -#include #include +#include struct wl_listener; struct wlr_buffer; // TODO: Move to common code when we want it more. -#define WAYLAND_LISTENER( member_listener, func ) \ - wl_listener { .notify = []( wl_listener *pListener, void *pUserData ) { decltype( this ) pObject = wl_container_of( pListener, pObject, member_listener ); pObject->func( pUserData ); } } +#define WAYLAND_LISTENER( member_listener, func ) \ + wl_listener \ + { \ + .notify = []( wl_listener *pListener, void *pUserData ) \ + { \ + decltype( this ) pObject = \ + wl_container_of( pListener, pObject, member_listener ); \ + pObject->func( pUserData ); \ + } \ + } namespace gamescope { @@ -21,20 +29,26 @@ namespace gamescope class CBufferMemo { public: - CBufferMemo( CBufferMemoizer *pMemoizer, wlr_buffer *pBuffer, OwningRc pTexture ); - ~CBufferMemo(); + CBufferMemo( + CBufferMemoizer *pMemoizer, + wlr_buffer *pBuffer, + OwningRc pTexture ); + ~CBufferMemo( ); - void Finalize(); + void Finalize( ); - CBufferMemoizer *GetMemoizer() const { return m_pMemoizer; } + CBufferMemoizer *GetMemoizer( ) const { return m_pMemoizer; } - const OwningRc &GetVulkanTexture() const { return m_pVulkanTexture; } + const OwningRc &GetVulkanTexture( ) const + { return m_pVulkanTexture; } void OnBufferDestroyed( void *pUserData ); + private: CBufferMemoizer *m_pMemoizer = nullptr; - wlr_buffer *m_pBuffer = nullptr; - wl_listener m_DeleteListener = WAYLAND_LISTENER( m_DeleteListener, OnBufferDestroyed ); + wlr_buffer *m_pBuffer = nullptr; + wl_listener m_DeleteListener = + WAYLAND_LISTENER( m_DeleteListener, OnBufferDestroyed ); // OwningRc to have a private reference: // So we can keep the CVulkanTexture, as public references @@ -45,14 +59,18 @@ namespace gamescope class CBufferMemoizer { public: - // Must return an OwningRc for the locking to make sense and not deadlock. - OwningRc LookupVulkanTexture( wlr_buffer *pBuffer ) const; + // Must return an OwningRc for the locking to make sense and not + // deadlock. + OwningRc + LookupVulkanTexture( wlr_buffer *pBuffer ) const; - void MemoizeBuffer( wlr_buffer *pBuffer, OwningRc pTexture ); + void + MemoizeBuffer( wlr_buffer *pBuffer, OwningRc pTexture ); void UnmemoizeBuffer( wlr_buffer *pBuffer ); + private: - mutable std::mutex m_mutBufferMemos; + mutable std::mutex m_mutBufferMemos; std::unordered_map m_BufferMemos; }; -} +} // namespace gamescope diff --git a/src/GamescopeVersion.h.in b/src/GamescopeVersion.h.in index bdf051533a..3c73f41cca 100644 --- a/src/GamescopeVersion.h.in +++ b/src/GamescopeVersion.h.in @@ -3,4 +3,4 @@ namespace gamescope { static constexpr const char k_szGamescopeVersion[] = "@VCS_TAG@"; -} \ No newline at end of file +} diff --git a/src/InputEmulation.cpp b/src/InputEmulation.cpp index 5412b01b93..6e14349954 100644 --- a/src/InputEmulation.cpp +++ b/src/InputEmulation.cpp @@ -1,23 +1,21 @@ #if HAVE_LIBEIS -#include + #include -#include -#include + #include + #include -#include "backend.h" -#include "InputEmulation.h" -#include "wlserver.hpp" + #include "InputEmulation.h" + #include "backend.h" + #include "wlserver.hpp" -static LogScope gamescope_ei("gamescope_ei"); +static LogScope gamescope_ei( "gamescope_ei" ); namespace gamescope { - GamescopeInputServer::GamescopeInputServer() - { - } + GamescopeInputServer::GamescopeInputServer( ) {} - GamescopeInputServer::~GamescopeInputServer() + GamescopeInputServer::~GamescopeInputServer( ) { eis_unref( m_pEis ); m_pEis = nullptr; @@ -56,12 +54,9 @@ namespace gamescope return true; } - int GamescopeInputServer::GetFD() - { - return m_nFd; - } + int GamescopeInputServer::GetFD( ) { return m_nFd; } - void GamescopeInputServer::OnPollIn() + void GamescopeInputServer::OnPollIn( ) { static uint32_t s_uSequence = 0; @@ -77,14 +72,21 @@ namespace gamescope eis_client_connect( pClient ); eis_seat *pSeat = eis_client_new_seat( pClient, "chair" ); - eis_seat_configure_capability( pSeat, EIS_DEVICE_CAP_POINTER ); - eis_seat_configure_capability( pSeat, EIS_DEVICE_CAP_POINTER_ABSOLUTE ); - eis_seat_configure_capability( pSeat, EIS_DEVICE_CAP_KEYBOARD ); - //eis_seat_configure_capability( pSeat, EIS_DEVICE_CAP_TOUCH ); - eis_seat_configure_capability( pSeat, EIS_DEVICE_CAP_BUTTON ); - eis_seat_configure_capability( pSeat, EIS_DEVICE_CAP_SCROLL ); + eis_seat_configure_capability( + pSeat, EIS_DEVICE_CAP_POINTER ); + eis_seat_configure_capability( + pSeat, EIS_DEVICE_CAP_POINTER_ABSOLUTE ); + eis_seat_configure_capability( + pSeat, EIS_DEVICE_CAP_KEYBOARD ); + // eis_seat_configure_capability( pSeat, + // EIS_DEVICE_CAP_TOUCH ); + eis_seat_configure_capability( + pSeat, EIS_DEVICE_CAP_BUTTON ); + eis_seat_configure_capability( + pSeat, EIS_DEVICE_CAP_SCROLL ); eis_seat_add( pSeat ); - // Unref it now that we no longer need it, and gave it over to eis. + // Unref it now that we no longer need it, and gave it over + // to eis. eis_seat_unref( pSeat ); } break; @@ -99,47 +101,70 @@ namespace gamescope case EIS_EVENT_SEAT_BIND: { eis_client *pClient = eis_event_get_client( pEisEvent ); - eis_seat *pSeat = eis_event_get_seat( pEisEvent ); - - bool bWantsDevice = eis_event_seat_has_capability( pEisEvent, EIS_DEVICE_CAP_POINTER ) || - eis_event_seat_has_capability( pEisEvent, EIS_DEVICE_CAP_POINTER_ABSOLUTE ) || - eis_event_seat_has_capability( pEisEvent, EIS_DEVICE_CAP_BUTTON ) || - eis_event_seat_has_capability( pEisEvent, EIS_DEVICE_CAP_SCROLL ) || - eis_event_seat_has_capability( pEisEvent, EIS_DEVICE_CAP_KEYBOARD ); - - bool bHasDevice = eis_client_get_user_data( pClient ) != nullptr; + eis_seat *pSeat = eis_event_get_seat( pEisEvent ); + + bool bWantsDevice = + eis_event_seat_has_capability( + pEisEvent, EIS_DEVICE_CAP_POINTER ) || + eis_event_seat_has_capability( + pEisEvent, EIS_DEVICE_CAP_POINTER_ABSOLUTE ) || + eis_event_seat_has_capability( + pEisEvent, EIS_DEVICE_CAP_BUTTON ) || + eis_event_seat_has_capability( + pEisEvent, EIS_DEVICE_CAP_SCROLL ) || + eis_event_seat_has_capability( + pEisEvent, EIS_DEVICE_CAP_KEYBOARD ); + + bool bHasDevice = + eis_client_get_user_data( pClient ) != nullptr; if ( bWantsDevice && !bHasDevice ) { - eis_device *pVirtualInput = eis_seat_new_device( pSeat ); - eis_device_configure_name( pVirtualInput, "Gamescope Virtual Input" ); - eis_device_configure_capability( pVirtualInput, EIS_DEVICE_CAP_POINTER ); - eis_device_configure_capability( pVirtualInput, EIS_DEVICE_CAP_POINTER_ABSOLUTE ); - eis_device_configure_capability( pVirtualInput, EIS_DEVICE_CAP_BUTTON ); - eis_device_configure_capability( pVirtualInput, EIS_DEVICE_CAP_SCROLL ); - eis_device_configure_capability( pVirtualInput, EIS_DEVICE_CAP_KEYBOARD ); + eis_device *pVirtualInput = + eis_seat_new_device( pSeat ); + eis_device_configure_name( + pVirtualInput, "Gamescope Virtual Input" ); + eis_device_configure_capability( + pVirtualInput, EIS_DEVICE_CAP_POINTER ); + eis_device_configure_capability( + pVirtualInput, EIS_DEVICE_CAP_POINTER_ABSOLUTE ); + eis_device_configure_capability( + pVirtualInput, EIS_DEVICE_CAP_BUTTON ); + eis_device_configure_capability( + pVirtualInput, EIS_DEVICE_CAP_SCROLL ); + eis_device_configure_capability( + pVirtualInput, EIS_DEVICE_CAP_KEYBOARD ); // Can add this someday if we want it. - //eis_device_configure_capability( pVirtualInput, EIS_DEVICE_CAP_TOUCH ); - - eis_region *pVirtualInputRegion = eis_device_new_region( pVirtualInput ); - eis_region_set_mapping_id( pVirtualInputRegion, "Mr. Worldwide" ); - eis_region_set_size( pVirtualInputRegion, INT32_MAX, INT32_MAX ); + // eis_device_configure_capability( pVirtualInput, + // EIS_DEVICE_CAP_TOUCH ); + + eis_region *pVirtualInputRegion = + eis_device_new_region( pVirtualInput ); + eis_region_set_mapping_id( + pVirtualInputRegion, "Mr. Worldwide" ); + eis_region_set_size( + pVirtualInputRegion, INT32_MAX, INT32_MAX ); eis_region_set_offset( pVirtualInputRegion, 0, 0 ); eis_region_add( pVirtualInputRegion ); - // We don't want this anymore, but pVirtualInput can own it + // We don't want this anymore, but pVirtualInput can own + // it eis_region_unref( pVirtualInputRegion ); eis_device_add( pVirtualInput ); eis_device_resume( pVirtualInput ); if ( !eis_client_is_sender( pClient ) ) - eis_device_start_emulating( pVirtualInput, ++s_uSequence ); + eis_device_start_emulating( + pVirtualInput, ++s_uSequence ); - // We have a ref on pVirtualInput, store that in pClient's userdata so we can remove device later. - eis_client_set_user_data( pClient, (void *) pVirtualInput ); + // We have a ref on pVirtualInput, store that in + // pClient's userdata so we can remove device later. + eis_client_set_user_data( + pClient, ( void * )pVirtualInput ); } else if ( !bWantsDevice && bHasDevice ) { - eis_device *pDevice = (eis_device *) eis_client_get_user_data( pClient ); + eis_device *pDevice = + ( eis_device * )eis_client_get_user_data( pClient ); eis_device_remove( pDevice ); eis_device_unref( pDevice ); eis_client_set_user_data( pClient, nullptr ); @@ -168,52 +193,70 @@ namespace gamescope case EIS_EVENT_POINTER_MOTION: { - GetBackend()->NotifyPhysicalInput( InputType::Mouse ); - - wlserver_lock(); - wlserver_mousemotion( eis_event_pointer_get_dx( pEisEvent ), eis_event_pointer_get_dy( pEisEvent ), ++s_uSequence ); - wlserver_unlock(); + GetBackend( )->NotifyPhysicalInput( InputType::Mouse ); + + wlserver_lock( ); + wlserver_mousemotion( + eis_event_pointer_get_dx( pEisEvent ), + eis_event_pointer_get_dy( pEisEvent ), + ++s_uSequence ); + wlserver_unlock( ); } break; case EIS_EVENT_POINTER_MOTION_ABSOLUTE: { - GetBackend()->NotifyPhysicalInput( InputType::Mouse ); - - wlserver_lock(); - wlserver_mousewarp( eis_event_pointer_get_absolute_x( pEisEvent ), eis_event_pointer_get_absolute_y( pEisEvent ), ++s_uSequence, true ); - wlserver_unlock(); + GetBackend( )->NotifyPhysicalInput( InputType::Mouse ); + + wlserver_lock( ); + wlserver_mousewarp( + eis_event_pointer_get_absolute_x( pEisEvent ), + eis_event_pointer_get_absolute_y( pEisEvent ), + ++s_uSequence, + true ); + wlserver_unlock( ); } break; case EIS_EVENT_BUTTON_BUTTON: { - wlserver_lock(); - wlserver_mousebutton( eis_event_button_get_button( pEisEvent ), eis_event_button_get_is_press( pEisEvent ), ++s_uSequence ); - wlserver_unlock(); + wlserver_lock( ); + wlserver_mousebutton( + eis_event_button_get_button( pEisEvent ), + eis_event_button_get_is_press( pEisEvent ), + ++s_uSequence ); + wlserver_unlock( ); } break; case EIS_EVENT_SCROLL_DELTA: { - wlserver_lock(); - wlserver_mousewheel( eis_event_scroll_get_dx( pEisEvent ), eis_event_scroll_get_dy( pEisEvent ), ++s_uSequence ); - wlserver_unlock(); + wlserver_lock( ); + wlserver_mousewheel( + eis_event_scroll_get_dx( pEisEvent ), + eis_event_scroll_get_dy( pEisEvent ), + ++s_uSequence ); + wlserver_unlock( ); } break; case EIS_EVENT_SCROLL_DISCRETE: { - m_flScrollAccum[0] += eis_event_scroll_get_discrete_dx( pEisEvent ) / 120.0; - m_flScrollAccum[1] += eis_event_scroll_get_discrete_dy( pEisEvent ) / 120.0; + m_flScrollAccum[ 0 ] += + eis_event_scroll_get_discrete_dx( pEisEvent ) / 120.0; + m_flScrollAccum[ 1 ] += + eis_event_scroll_get_discrete_dy( pEisEvent ) / 120.0; } break; case EIS_EVENT_KEYBOARD_KEY: { - wlserver_lock(); - wlserver_key( eis_event_keyboard_get_key( pEisEvent ), eis_event_keyboard_get_key_is_press( pEisEvent ), ++s_uSequence ); - wlserver_unlock(); + wlserver_lock( ); + wlserver_key( + eis_event_keyboard_get_key( pEisEvent ), + eis_event_keyboard_get_key_is_press( pEisEvent ), + ++s_uSequence ); + wlserver_unlock( ); } break; @@ -222,23 +265,23 @@ namespace gamescope case EIS_EVENT_TOUCH_UP: { // Touch not implemented yet. - gamescope_ei.errorf( "No touch support yet! How did you get here?" ); + gamescope_ei.errorf( + "No touch support yet! How did you get here?" ); } break; case EIS_EVENT_FRAME: { - double flScrollX = m_flScrollAccum[0]; - double flScrollY = m_flScrollAccum[1]; - m_flScrollAccum[0] = 0.0; - m_flScrollAccum[1] = 0.0; + double flScrollX = m_flScrollAccum[ 0 ]; + double flScrollY = m_flScrollAccum[ 1 ]; + m_flScrollAccum[ 0 ] = 0.0; + m_flScrollAccum[ 1 ] = 0.0; - if ( flScrollX == 0.0 && flScrollY == 0.0 ) - break; + if ( flScrollX == 0.0 && flScrollY == 0.0 ) break; - wlserver_lock(); + wlserver_lock( ); wlserver_mousewheel( flScrollX, flScrollY, ++s_uSequence ); - wlserver_unlock(); + wlserver_unlock( ); } break; @@ -252,5 +295,5 @@ namespace gamescope eis_event_unref( pEisEvent ); } } -} +} // namespace gamescope #endif diff --git a/src/InputEmulation.h b/src/InputEmulation.h index 1084f9ddf3..99e9735f07 100644 --- a/src/InputEmulation.h +++ b/src/InputEmulation.h @@ -9,17 +9,18 @@ namespace gamescope class GamescopeInputServer final : public IWaitable { public: - GamescopeInputServer(); - ~GamescopeInputServer(); + GamescopeInputServer( ); + ~GamescopeInputServer( ); bool Init( const char *pszSocketPath ); - virtual int GetFD() override; - virtual void OnPollIn() override; + virtual int GetFD( ) override; + virtual void OnPollIn( ) override; + private: eis *m_pEis = nullptr; - int m_nFd = -1; + int m_nFd = -1; - double m_flScrollAccum[2]{}; + double m_flScrollAccum[ 2 ]{}; }; -} +} // namespace gamescope diff --git a/src/LibInputHandler.cpp b/src/LibInputHandler.cpp index 145a5be93d..70a05151bd 100644 --- a/src/LibInputHandler.cpp +++ b/src/LibInputHandler.cpp @@ -1,14 +1,14 @@ #include "LibInputHandler.h" -#include #include -#include +#include #include +#include -#include "log.hpp" +#include "Utils/Defer.h" #include "backend.h" +#include "log.hpp" #include "wlserver.hpp" -#include "Utils/Defer.h" // Handles libinput in contexts where we don't have a session // and can't use the wlroots libinput stuff. @@ -23,24 +23,18 @@ namespace gamescope { static LogScope log_input_stealer( "InputStealer" ); - const libinput_interface CLibInputHandler::s_LibInputInterface = - { - .open_restricted = []( const char *pszPath, int nFlags, void *pUserData ) -> int - { - return open( pszPath, nFlags ); - }, + const libinput_interface CLibInputHandler::s_LibInputInterface = { + .open_restricted = + []( const char *pszPath, int nFlags, void *pUserData ) -> int + { return open( pszPath, nFlags ); }, .close_restricted = []( int nFd, void *pUserData ) -> void - { - close( nFd ); - }, + { close( nFd ); }, }; - CLibInputHandler::CLibInputHandler() - { - } + CLibInputHandler::CLibInputHandler( ) {} - CLibInputHandler::~CLibInputHandler() + CLibInputHandler::~CLibInputHandler( ) { if ( m_pLibInput ) { @@ -55,16 +49,17 @@ namespace gamescope } } - bool CLibInputHandler::Init() + bool CLibInputHandler::Init( ) { - m_pUdev = udev_new(); + m_pUdev = udev_new( ); if ( !m_pUdev ) { log_input_stealer.errorf( "Failed to create udev interface" ); return false; } - m_pLibInput = libinput_udev_create_context( &s_LibInputInterface, nullptr, m_pUdev ); + m_pLibInput = libinput_udev_create_context( + &s_LibInputInterface, nullptr, m_pUdev ); if ( !m_pLibInput ) { log_input_stealer.errorf( "Failed to create libinput context" ); @@ -74,28 +69,28 @@ namespace gamescope const char *pszSeatName = "seat0"; if ( libinput_udev_assign_seat( m_pLibInput, pszSeatName ) == -1 ) { - log_input_stealer.errorf( "Could not assign seat \"%s\"", pszSeatName ); + log_input_stealer.errorf( + "Could not assign seat \"%s\"", pszSeatName ); return false; } return true; } - int CLibInputHandler::GetFD() + int CLibInputHandler::GetFD( ) { - if ( !m_pLibInput ) - return -1; + if ( !m_pLibInput ) return -1; return libinput_get_fd( m_pLibInput ); } - void CLibInputHandler::OnPollIn() + void CLibInputHandler::OnPollIn( ) { static uint32_t s_uSequence = 0; libinput_dispatch( m_pLibInput ); - while ( libinput_event *pEvent = libinput_get_event( m_pLibInput ) ) + while ( libinput_event *pEvent = libinput_get_event( m_pLibInput ) ) { defer( libinput_event_destroy( pEvent ) ); @@ -105,100 +100,122 @@ namespace gamescope { case LIBINPUT_EVENT_POINTER_MOTION: { - libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent ); + libinput_event_pointer *pPointerEvent = + libinput_event_get_pointer_event( pEvent ); - double flDx = libinput_event_pointer_get_dx( pPointerEvent ); - double flDy = libinput_event_pointer_get_dy( pPointerEvent ); + double flDx = + libinput_event_pointer_get_dx( pPointerEvent ); + double flDy = + libinput_event_pointer_get_dy( pPointerEvent ); - GetBackend()->NotifyPhysicalInput( InputType::Mouse ); + GetBackend( )->NotifyPhysicalInput( InputType::Mouse ); - wlserver_lock(); + wlserver_lock( ); wlserver_mousemotion( flDx, flDy, ++s_uSequence ); - wlserver_unlock(); + wlserver_unlock( ); } break; case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { - libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent ); + libinput_event_pointer *pPointerEvent = + libinput_event_get_pointer_event( pEvent ); - double flX = libinput_event_pointer_get_absolute_x( pPointerEvent ); - double flY = libinput_event_pointer_get_absolute_y( pPointerEvent ); + double flX = + libinput_event_pointer_get_absolute_x( pPointerEvent ); + double flY = + libinput_event_pointer_get_absolute_y( pPointerEvent ); - GetBackend()->NotifyPhysicalInput( InputType::Mouse ); + GetBackend( )->NotifyPhysicalInput( InputType::Mouse ); - wlserver_lock(); + wlserver_lock( ); wlserver_mousewarp( flX, flY, ++s_uSequence, true ); - wlserver_unlock(); + wlserver_unlock( ); } break; case LIBINPUT_EVENT_POINTER_BUTTON: { - libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent ); - - uint32_t uButton = libinput_event_pointer_get_button( pPointerEvent ); - libinput_button_state eButtonState = libinput_event_pointer_get_button_state( pPointerEvent ); - - wlserver_lock(); - wlserver_mousebutton( uButton, eButtonState == LIBINPUT_BUTTON_STATE_PRESSED, ++s_uSequence ); - wlserver_unlock(); + libinput_event_pointer *pPointerEvent = + libinput_event_get_pointer_event( pEvent ); + + uint32_t uButton = + libinput_event_pointer_get_button( pPointerEvent ); + libinput_button_state eButtonState = + libinput_event_pointer_get_button_state( + pPointerEvent ); + + wlserver_lock( ); + wlserver_mousebutton( + uButton, + eButtonState == LIBINPUT_BUTTON_STATE_PRESSED, + ++s_uSequence ); + wlserver_unlock( ); } break; case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: { - libinput_event_pointer *pPointerEvent = libinput_event_get_pointer_event( pEvent ); + libinput_event_pointer *pPointerEvent = + libinput_event_get_pointer_event( pEvent ); - static constexpr libinput_pointer_axis eAxes[] = - { + static constexpr libinput_pointer_axis eAxes[] = { LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, }; for ( uint32_t i = 0; i < std::size( eAxes ); i++ ) { - libinput_pointer_axis eAxis = eAxes[i]; + libinput_pointer_axis eAxis = eAxes[ i ]; - if ( !libinput_event_pointer_has_axis( pPointerEvent, eAxis ) ) + if ( !libinput_event_pointer_has_axis( + pPointerEvent, eAxis ) ) continue; - double flScroll = libinput_event_pointer_get_scroll_value_v120( pPointerEvent, eAxis ); - m_flScrollAccum[i] += flScroll / 120.0; + double flScroll = + libinput_event_pointer_get_scroll_value_v120( + pPointerEvent, eAxis ); + m_flScrollAccum[ i ] += flScroll / 120.0; } } break; case LIBINPUT_EVENT_KEYBOARD_KEY: { - libinput_event_keyboard *pKeyboardEvent = libinput_event_get_keyboard_event( pEvent ); - uint32_t uKey = libinput_event_keyboard_get_key( pKeyboardEvent ); - libinput_key_state eState = libinput_event_keyboard_get_key_state( pKeyboardEvent ); - - wlserver_lock(); - wlserver_key( uKey, eState == LIBINPUT_KEY_STATE_PRESSED, ++ s_uSequence ); - wlserver_unlock(); + libinput_event_keyboard *pKeyboardEvent = + libinput_event_get_keyboard_event( pEvent ); + uint32_t uKey = + libinput_event_keyboard_get_key( pKeyboardEvent ); + libinput_key_state eState = + libinput_event_keyboard_get_key_state( pKeyboardEvent ); + + wlserver_lock( ); + wlserver_key( + uKey, + eState == LIBINPUT_KEY_STATE_PRESSED, + ++s_uSequence ); + wlserver_unlock( ); } break; default: break; } - } + } // Handle scrolling { - double flScrollX = m_flScrollAccum[0]; - double flScrollY = m_flScrollAccum[1]; - m_flScrollAccum[0] = 0.0; - m_flScrollAccum[1] = 0.0; + double flScrollX = m_flScrollAccum[ 0 ]; + double flScrollY = m_flScrollAccum[ 1 ]; + m_flScrollAccum[ 0 ] = 0.0; + m_flScrollAccum[ 1 ] = 0.0; if ( flScrollX != 0.0 || flScrollY != 0.0 ) { - wlserver_lock(); + wlserver_lock( ); wlserver_mousewheel( flScrollX, flScrollY, ++s_uSequence ); - wlserver_unlock(); + wlserver_unlock( ); } } } -} +} // namespace gamescope diff --git a/src/LibInputHandler.h b/src/LibInputHandler.h index d9853f34c7..68e14f4ae8 100644 --- a/src/LibInputHandler.h +++ b/src/LibInputHandler.h @@ -11,19 +11,20 @@ namespace gamescope class CLibInputHandler final : public IWaitable { public: - CLibInputHandler(); - ~CLibInputHandler(); + CLibInputHandler( ); + ~CLibInputHandler( ); - bool Init(); + bool Init( ); + + virtual int GetFD( ) override; + virtual void OnPollIn( ) override; - virtual int GetFD() override; - virtual void OnPollIn() override; private: - udev *m_pUdev = nullptr; + udev *m_pUdev = nullptr; libinput *m_pLibInput = nullptr; - double m_flScrollAccum[2]{}; + double m_flScrollAccum[ 2 ]{}; static const libinput_interface s_LibInputInterface; }; -} \ No newline at end of file +} // namespace gamescope diff --git a/src/Ratio.h b/src/Ratio.h index bfaf774afe..20fc897489 100644 --- a/src/Ratio.h +++ b/src/Ratio.h @@ -1,45 +1,42 @@ #pragma once -#include #include +#include #include +#include #include -#include namespace gamescope { - template - class Ratio + template class Ratio { public: - Ratio(T num, T denom) - { - Set( num, denom ); - } + Ratio( T num, T denom ) { Set( num, denom ); } Ratio( std::string_view view ) { - Set(0, 0); + Set( 0, 0 ); - size_t colon = view.find(":"); + size_t colon = view.find( ":" ); - if ( colon == std::string_view::npos ) - return; + if ( colon == std::string_view::npos ) return; std::string_view numStr = view.substr( 0, colon ); std::string_view denomStr = view.substr( colon + 1 ); T num = 0, denom = 0; - std::from_chars( numStr.data(), numStr.data() + numStr.size(), num ); - std::from_chars( denomStr.data(), denomStr.data() + denomStr.size(), denom ); + std::from_chars( + numStr.data( ), numStr.data( ) + numStr.size( ), num ); + std::from_chars( + denomStr.data( ), denomStr.data( ) + denomStr.size( ), denom ); Set( num, denom ); } - T Num() const { return m_num; } - T Denom() const { return m_denom; } + T Num( ) const { return m_num; } + T Denom( ) const { return m_denom; } - bool IsUndefined() const { return m_denom == 0; } + bool IsUndefined( ) const { return m_denom == 0; } void Set( T num, T denom ) { @@ -53,20 +50,26 @@ namespace gamescope return; } - m_num = num / gcd; + m_num = num / gcd; m_denom = denom / gcd; } - bool operator == ( const Ratio& other ) const { return Num() == other.Num() && Denom() == other.Denom(); } - bool operator != ( const Ratio& other ) const { return !(*this == other); } + bool operator==( const Ratio &other ) const + { return Num( ) == other.Num( ) && Denom( ) == other.Denom( ); } + bool operator!=( const Ratio &other ) const + { return !( *this == other ); } - bool operator >= ( const Ratio& other ) const { return Num() * other.Denom() >= other.Num() * Denom(); } - bool operator > ( const Ratio& other ) const { return Num() * other.Denom() > other.Num() * Denom(); } + bool operator>=( const Ratio &other ) const + { return Num( ) * other.Denom( ) >= other.Num( ) * Denom( ); } + bool operator>( const Ratio &other ) const + { return Num( ) * other.Denom( ) > other.Num( ) * Denom( ); } - bool operator < ( const Ratio& other ) const { return !( *this >= other ); } - bool operator <= ( const Ratio& other ) const { return !( *this > other ); } + bool operator<( const Ratio &other ) const + { return !( *this >= other ); } + bool operator<=( const Ratio &other ) const + { return !( *this > other ); } private: T m_num, m_denom; }; -} \ No newline at end of file +} // namespace gamescope diff --git a/src/Script/Script.cpp b/src/Script/Script.cpp index 6509b76919..8833b56fb6 100644 --- a/src/Script/Script.cpp +++ b/src/Script/Script.cpp @@ -1,123 +1,148 @@ #include "Script.h" -#include "convar.h" -#include "color_helpers.h" #include "../log.hpp" #include "Utils/DirHelpers.h" +#include "color_helpers.h" +#include "convar.h" -#include #include +#include namespace gamescope { using namespace std::literals; - static LogScope s_ScriptLog{ "script" }; + static LogScope s_ScriptLog{ "script" }; static LogScope s_ScriptMgrLog{ "scriptmgr" }; - static ConVar cv_script_use_local_scripts{ "script_use_local_scripts", false, "Whether or not to use the local scripts (../config) as opposed to the ones in /etc/gamescope.d" }; - static ConVar cv_script_use_user_scripts{ "script_use_user_scripts", true, "Whether or not to use user config scripts ($XDG_CONFIG_DIR/gamescope) at all." }; + static ConVar cv_script_use_local_scripts{ + "script_use_local_scripts", + false, + "Whether or not to use the local scripts (../config) as opposed to the " + "ones in /etc/gamescope.d" + }; + static ConVar cv_script_use_user_scripts{ + "script_use_user_scripts", + true, + "Whether or not to use user config scripts ($XDG_CONFIG_DIR/gamescope) " + "at all." + }; static inline void PanicFunction( sol::optional oMsg ) { - s_ScriptLog.errorf( "Lua is in a panic state and will now abort() the application" ); + s_ScriptLog.errorf( + "Lua is in a panic state and will now abort() the application" ); if ( oMsg ) { - s_ScriptLog.errorf( "\tError Message: %s", oMsg->c_str() ); + s_ScriptLog.errorf( "\tError Message: %s", oMsg->c_str( ) ); } - abort(); + abort( ); } - static inline int ExceptionFunction( lua_State* pState, sol::optional oException, sol::string_view psvDescription ) + static inline int ExceptionFunction( + lua_State *pState, + sol::optional oException, + sol::string_view psvDescription ) { // L is the lua state, which you can wrap in a state_view if necessary // maybe_exception will contain exception, if it exists - // description will either be the what() of the exception or a description saying that we hit the general-case catch(...) + // description will either be the what() of the exception or a + // description saying that we hit the general-case catch(...) - s_ScriptLog.errorf( "An exception occurred:\n %.*s", - (int)psvDescription.length(), psvDescription.data() ); + s_ScriptLog.errorf( + "An exception occurred:\n %.*s", + ( int )psvDescription.length( ), + psvDescription.data( ) ); // you must push 1 element onto the stack to be // transported through as the error object in Lua - // note that Lua -- and 99.5% of all Lua users and libraries -- expects a string - // so we push a single string (in our case, the description of the error) + // note that Lua -- and 99.5% of all Lua users and libraries -- expects + // a string so we push a single string (in our case, the description of + // the error) return sol::stack::push( pState, psvDescription ); } static inline void LuaErrorHandler( const std::string &msg ) { - s_ScriptLog.errorf( "An error occurred:\n %.*s", - (int)msg.length(), msg.data() ); + s_ScriptLog.errorf( + "An error occurred:\n %.*s", ( int )msg.length( ), msg.data( ) ); } int32_t CScriptManager::s_nNextScriptId = 0; - CScriptManager &CScriptManager::GlobalScriptScope() + CScriptManager &CScriptManager::GlobalScriptScope( ) { static CScriptManager s_State; return s_State; } - CScriptManager::CScriptManager() + CScriptManager::CScriptManager( ) { - m_State.open_libraries(); + m_State.open_libraries( ); static bool s_bSetDefaultHandler = false; if ( !s_bSetDefaultHandler ) { - m_State["_gamescope_error_handler"] = LuaErrorHandler; + m_State[ "_gamescope_error_handler" ] = LuaErrorHandler; - sol::protected_function::set_default_handler( m_State["_gamescope_error_handler"] ); + sol::protected_function::set_default_handler( + m_State[ "_gamescope_error_handler" ] ); s_bSetDefaultHandler = true; } - m_State.set_panic( sol::c_call ); + m_State.set_panic( + sol::c_call ); m_State.set_exception_handler( &ExceptionFunction ); m_Gamescope.Base = m_State.create_named_table( "gamescope" ); - m_Gamescope.Base["hook"] = [this]( std::string_view svName, sol::function fnFunc ) + m_Gamescope.Base[ "hook" ] = + [ this ]( std::string_view svName, sol::function fnFunc ) { - m_Hooks.emplace( std::make_pair( svName, Hook_t{ std::move( fnFunc ), m_nCurrentScriptId } ) ); + m_Hooks.emplace( + std::make_pair( + svName, + Hook_t{ std::move( fnFunc ), m_nCurrentScriptId } ) ); }; - m_Gamescope.Base.new_enum( "eotf", + m_Gamescope.Base.new_enum( + "eotf", { { "gamma22", EOTF_Gamma22 }, { "pq", EOTF_PQ }, { "count", EOTF_Count }, - } - ); - m_Gamescope.Base.new_enum( "log_priority", + } ); + m_Gamescope.Base.new_enum( + "log_priority", { { "silent", LOG_SILENT }, { "error", LOG_ERROR }, { "warning", LOG_WARNING }, { "info", LOG_INFO }, { "debug", LOG_DEBUG }, - } - ); - m_Gamescope.Base["log"] = []( LogPriority ePriority, std::string_view svText ) { s_ScriptLog.log( ePriority, svText ); }; + } ); + m_Gamescope.Base[ "log" ] = + []( LogPriority ePriority, std::string_view svText ) + { s_ScriptLog.log( ePriority, svText ); }; - m_Gamescope.Convars.Base = m_State.create_table(); + m_Gamescope.Convars.Base = m_State.create_table( ); m_Gamescope.Base.set( "convars", m_Gamescope.Convars.Base ); - m_Gamescope.Config.Base = m_State.create_table(); + m_Gamescope.Config.Base = m_State.create_table( ); m_Gamescope.Base.set( "config", m_Gamescope.Config.Base ); - m_Gamescope.Config.KnownDisplays = m_State.create_table(); - m_Gamescope.Config.Base.set( "known_displays", m_Gamescope.Config.KnownDisplays ); + m_Gamescope.Config.KnownDisplays = m_State.create_table( ); + m_Gamescope.Config.Base.set( + "known_displays", m_Gamescope.Config.KnownDisplays ); } - void CScriptManager::RunDefaultScripts() + void CScriptManager::RunDefaultScripts( ) { - const char *sScriptPathEnv = getenv("GAMESCOPE_SCRIPT_PATH"); + const char *sScriptPathEnv = getenv( "GAMESCOPE_SCRIPT_PATH" ); - if ( cv_script_use_local_scripts ) - { - RunFolder( "../scripts", true ); - } + if ( cv_script_use_local_scripts ) { RunFolder( "../scripts", true ); } else if ( sScriptPathEnv ) { - std::vector sScriptPaths = gamescope::Split( sScriptPathEnv, ":" ); + std::vector sScriptPaths = + gamescope::Split( sScriptPathEnv, ":" ); for ( const auto &sScriptPath : sScriptPaths ) { RunFolder( sScriptPath, true ); @@ -131,7 +156,8 @@ namespace gamescope if ( cv_script_use_user_scripts ) { - std::string sUserConfigs = std::string{ GetConfigDir() } + "/gamescope/scripts"; + std::string sUserConfigs = + std::string{ GetConfigDir( ) } + "/gamescope/scripts"; RunFolder( sUserConfigs, true ); } } @@ -144,7 +170,7 @@ namespace gamescope int32_t nPreviousScriptId = m_nCurrentScriptId; m_nCurrentScriptId = uScriptId; - State().script( svContents ); + State( ).script( svContents ); m_nCurrentScriptId = nPreviousScriptId; } } @@ -152,8 +178,10 @@ namespace gamescope { uint32_t uScriptId = s_nNextScriptId++; - s_ScriptMgrLog.infof( "Running script file '%.*s' (id: %u)", - int( svPath.length() ), svPath.data(), + s_ScriptMgrLog.infof( + "Running script file '%.*s' (id: %u)", + int( svPath.length( ) ), + svPath.data( ), uScriptId ); std::string sPath = std::string( svPath ); @@ -162,52 +190,61 @@ namespace gamescope int32_t nPreviousScriptId = m_nCurrentScriptId; m_nCurrentScriptId = uScriptId; - State().script_file( std::move( sPath ) ); + State( ).script_file( std::move( sPath ) ); m_nCurrentScriptId = nPreviousScriptId; } } - bool CScriptManager::RunFolder( std::string_view svDirectory, bool bRecursive ) + bool + CScriptManager::RunFolder( std::string_view svDirectory, bool bRecursive ) { - s_ScriptMgrLog.infof( "Loading scripts from: '%.*s'", - int( svDirectory.size() ), svDirectory.data() ); + s_ScriptMgrLog.infof( + "Loading scripts from: '%.*s'", + int( svDirectory.size( ) ), + svDirectory.data( ) ); std::filesystem::path dirConfig = std::filesystem::path{ svDirectory }; if ( !std::filesystem::is_directory( dirConfig ) ) { - s_ScriptMgrLog.warnf( "Directory '%.*s' does not exist", - int( svDirectory.size() ), svDirectory.data() ); + s_ScriptMgrLog.warnf( + "Directory '%.*s' does not exist", + int( svDirectory.size( ) ), + svDirectory.data( ) ); return false; } - if ( access( dirConfig.c_str(), R_OK | X_OK ) != 0 ) + if ( access( dirConfig.c_str( ), R_OK | X_OK ) != 0 ) { - s_ScriptMgrLog.warnf( "Cannot open directory '%.*s'", - int( svDirectory.size() ), svDirectory.data() ); + s_ScriptMgrLog.warnf( + "Cannot open directory '%.*s'", + int( svDirectory.size( ) ), + svDirectory.data( ) ); return false; } std::vector sFiles; std::vector sDirectories; - for ( const auto &iter : std::filesystem::directory_iterator( dirConfig ) ) + for ( const auto &iter : + std::filesystem::directory_iterator( dirConfig ) ) { - const std::filesystem::path &path = iter.path(); + const std::filesystem::path &path = iter.path( ); // XXX: is_regular_file -> What about symlinks? - if ( std::filesystem::is_regular_file( iter.status() ) && path.extension() == ".lua"sv ) + if ( std::filesystem::is_regular_file( iter.status( ) ) && + path.extension( ) == ".lua"sv ) { sFiles.push_back( path ); } - if ( bRecursive && std::filesystem::is_directory( iter.status() ) ) + if ( bRecursive && std::filesystem::is_directory( iter.status( ) ) ) { sDirectories.push_back( path ); } } - std::sort( sFiles.begin(), sFiles.end() ); - std::sort( sDirectories.begin(), sDirectories.end() ); + std::sort( sFiles.begin( ), sFiles.end( ) ); + std::sort( sDirectories.begin( ), sDirectories.end( ) ); for ( const auto &sPath : sFiles ) - { + { RunFile( sPath ); } @@ -222,57 +259,56 @@ namespace gamescope return true; } - void CScriptManager::InvalidateAllHooks() - { - m_Hooks.clear(); - } + void CScriptManager::InvalidateAllHooks( ) { m_Hooks.clear( ); } void CScriptManager::InvalidateHooksForScript( int32_t nScriptId ) { - if ( nScriptId < 0 ) - return; + if ( nScriptId < 0 ) return; - std::erase_if( m_Hooks, [ nScriptId ]( const auto &iter ) -> bool - { - return iter.second.nScriptId == nScriptId; - }); + std::erase_if( + m_Hooks, + [ nScriptId ]( const auto &iter ) -> bool + { return iter.second.nScriptId == nScriptId; } ); } // // GamescopeScript_t // - std::optional> GamescopeScript_t::Config_t::LookupDisplay( CScriptScopedLock &script, std::string_view psvVendor, uint16_t uProduct, std::string_view psvModel, std::string_view psvDataString ) + std::optional> + GamescopeScript_t::Config_t::LookupDisplay( + CScriptScopedLock &script, + std::string_view psvVendor, + uint16_t uProduct, + std::string_view psvModel, + std::string_view psvDataString ) { - int nMaxPrority = -1; + int nMaxPrority = -1; std::optional> oOutDisplay; - sol::table tDisplay = script->create_table(); - tDisplay["vendor"] = psvVendor; - tDisplay["product"] = uProduct; - tDisplay["model"] = psvModel; - tDisplay["data_string"] = psvDataString; + sol::table tDisplay = script->create_table( ); + tDisplay[ "vendor" ] = psvVendor; + tDisplay[ "product" ] = uProduct; + tDisplay[ "model" ] = psvModel; + tDisplay[ "data_string" ] = psvDataString; for ( auto iter : KnownDisplays ) { - sol::optional otTable = iter.second.as>(); - if ( !otTable ) - continue; + sol::optional otTable = + iter.second.as>( ); + if ( !otTable ) continue; sol::table tTable = *otTable; - sol::optional ofnMatches = tTable["matches"]; - if ( !ofnMatches ) - continue; + sol::optional ofnMatches = tTable[ "matches" ]; + if ( !ofnMatches ) continue; sol::function fnMatches = *ofnMatches; - if ( !fnMatches ) - continue; + if ( !fnMatches ) continue; - int nPriority = fnMatches( tDisplay); - if ( nPriority <= nMaxPrority ) - continue; + int nPriority = fnMatches( tDisplay ); + if ( nPriority <= nMaxPrority ) continue; - std::string_view psvKey = iter.first.as(); + std::string_view psvKey = iter.first.as( ); nMaxPrority = nPriority; oOutDisplay = std::make_pair( psvKey, tTable ); @@ -281,4 +317,4 @@ namespace gamescope return oOutDisplay; } -} +} // namespace gamescope diff --git a/src/Script/Script.h b/src/Script/Script.h index 1e5a5ff00f..46a18cdf0e 100644 --- a/src/Script/Script.h +++ b/src/Script/Script.h @@ -6,7 +6,7 @@ #if HAVE_SCRIPTING -#include + #include namespace gamescope { @@ -30,17 +30,23 @@ namespace gamescope sol::table KnownDisplays; - std::optional> LookupDisplay( CScriptScopedLock &script, std::string_view psvVendor, uint16_t uProduct, std::string_view psvModel, std::string_view psvDataString ); + std::optional> + LookupDisplay( + CScriptScopedLock &script, + std::string_view psvVendor, + uint16_t uProduct, + std::string_view psvModel, + std::string_view psvDataString ); } Config; }; class CScriptManager { public: - CScriptManager(); + CScriptManager( ); - template - void CallHook( std::string_view svName, Args&&... args ) + template + void CallHook( std::string_view svName, Args &&...args ) { auto range = m_Hooks.equal_range( svName ); for ( auto iter = range.first; iter != range.second; iter++ ) @@ -50,41 +56,42 @@ namespace gamescope Hook_t *pHook = &iter->second; m_nCurrentScriptId = pHook->nScriptId; - iter->second.fnCallback( std::forward( args )... ); + iter->second.fnCallback( std::forward( args )... ); m_nCurrentScriptId = nPreviousScriptId; } } - void RunDefaultScripts(); + void RunDefaultScripts( ); void RunScriptText( std::string_view svContents ); void RunFile( std::string_view svPath ); bool RunFolder( std::string_view svPath, bool bRecursive = false ); - void InvalidateAllHooks(); + void InvalidateAllHooks( ); void InvalidateHooksForScript( int32_t nScriptId ); - sol::state *operator->() { return &m_State; } + sol::state *operator->( ) { return &m_State; } - sol::state &State() { return m_State; } - GamescopeScript_t &Gamescope() { return m_Gamescope; } + sol::state &State( ) { return m_State; } + GamescopeScript_t &Gamescope( ) { return m_Gamescope; } - std::mutex &Mutex() { return m_mutMutex; } + std::mutex &Mutex( ) { return m_mutMutex; } protected: - static CScriptManager &GlobalScriptScope(); + static CScriptManager &GlobalScriptScope( ); friend CScriptScopedLock; + private: mutable std::mutex m_mutMutex; - sol::state m_State; + sol::state m_State; GamescopeScript_t m_Gamescope; struct Hook_t { sol::function fnCallback; - int32_t nScriptId = -1; + int32_t nScriptId = -1; }; MultiDict m_Hooks; @@ -97,52 +104,42 @@ namespace gamescope class CScriptScopedLock { public: - CScriptScopedLock() - : CScriptScopedLock{ CScriptManager::GlobalScriptScope() } - { - } + CScriptScopedLock( ) : + CScriptScopedLock{ CScriptManager::GlobalScriptScope( ) } + {} - CScriptScopedLock( CScriptManager &manager ) - : m_Lock{ manager.Mutex() } - , m_ScriptManager{ manager } - { - } + CScriptScopedLock( CScriptManager &manager ) : + m_Lock{ manager.Mutex( ) }, m_ScriptManager{ manager } + {} - ~CScriptScopedLock() - { - } + ~CScriptScopedLock( ) {} - CScriptManager &Manager() { return m_ScriptManager; } - sol::state *State() { return &m_ScriptManager.State(); } + CScriptManager &Manager( ) { return m_ScriptManager; } + sol::state *State( ) { return &m_ScriptManager.State( ); } + + sol::state *operator->( ) { return State( ); } - sol::state *operator ->() { return State(); } private: std::scoped_lock m_Lock; - CScriptManager &m_ScriptManager; + CScriptManager &m_ScriptManager; }; - template - T TableToVec( const sol::table &table ) + template T TableToVec( const sol::table &table ) { - if ( !table ) - return T{}; + if ( !table ) return T{}; T out{}; - for ( int i = 0; i < T::length(); i++ ) + for ( int i = 0; i < T::length( ); i++ ) { - std::array ppsvIndices - { - "x", "y", "z", "w" - }; + std::array ppsvIndices{ "x", "y", "z", "w" }; - sol::optional ofValue = table[ppsvIndices[i]]; - out[i] = ofValue ? *ofValue : 0; + sol::optional ofValue = table[ ppsvIndices[ i ] ]; + out[ i ] = ofValue ? *ofValue : 0; } return out; } - template - std::vector TableToVector( const sol::table &table ) + template std::vector TableToVector( const sol::table &table ) { std::vector out; @@ -150,15 +147,14 @@ namespace gamescope { for ( auto &iter : table ) { - sol::optional oValue = iter.second.as>(); - if ( oValue ) - out.emplace_back( *oValue ); + sol::optional oValue = iter.second.as>( ); + if ( oValue ) out.emplace_back( *oValue ); } } return out; } -} +} // namespace gamescope -#endif \ No newline at end of file +#endif diff --git a/src/Timeline.cpp b/src/Timeline.cpp index ec9a46197e..063783df03 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -1,13 +1,13 @@ -#include #include +#include #include "Timeline.h" -#include "wlserver.hpp" #include "rendervulkan.hpp" +#include "wlserver.hpp" -#include "wlr_begin.hpp" #include #include +#include "wlr_begin.hpp" #include "wlr_end.hpp" namespace gamescope @@ -16,51 +16,56 @@ namespace gamescope static uint32_t SyncobjFdToHandle( int32_t nFd ) { - int32_t nRet; + int32_t nRet; uint32_t uHandle = 0; - if ( ( nRet = drmSyncobjFDToHandle( g_device.drmRenderFd(), nFd, &uHandle ) ) < 0 ) + if ( ( nRet = drmSyncobjFDToHandle( + g_device.drmRenderFd( ), nFd, &uHandle ) ) < 0 ) return 0; return uHandle; } - /*static*/ std::shared_ptr CTimeline::Create( const TimelineCreateDesc_t &desc ) + /*static*/ std::shared_ptr + CTimeline::Create( const TimelineCreateDesc_t &desc ) { - std::shared_ptr pSemaphore = g_device.CreateTimelineSemaphore( desc.ulStartingPoint, true ); - if ( !pSemaphore ) - return nullptr; + std::shared_ptr pSemaphore = + g_device.CreateTimelineSemaphore( desc.ulStartingPoint, true ); + if ( !pSemaphore ) return nullptr; - return std::make_shared( pSemaphore->GetFd(), std::move( pSemaphore ) ); + return std::make_shared( + pSemaphore->GetFd( ), std::move( pSemaphore ) ); } - CTimeline::CTimeline( int32_t nSyncobjFd, std::shared_ptr pSemaphore ) - : CTimeline( nSyncobjFd, SyncobjFdToHandle( nSyncobjFd ), std::move( pSemaphore ) ) - { - } - - CTimeline::CTimeline( int32_t nSyncobjFd, uint32_t uSyncobjHandle, std::shared_ptr pSemaphore ) - : m_nSyncobjFd{ nSyncobjFd } - , m_uSyncobjHandle{ uSyncobjHandle } - , m_pVkSemaphore{ std::move( pSemaphore ) } - { - } - - CTimeline::~CTimeline() + CTimeline::CTimeline( + int32_t nSyncobjFd, + std::shared_ptr pSemaphore ) : + CTimeline( + nSyncobjFd, + SyncobjFdToHandle( nSyncobjFd ), + std::move( pSemaphore ) ) + {} + + CTimeline::CTimeline( + int32_t nSyncobjFd, + uint32_t uSyncobjHandle, + std::shared_ptr pSemaphore ) : + m_nSyncobjFd{ nSyncobjFd }, + m_uSyncobjHandle{ uSyncobjHandle }, + m_pVkSemaphore{ std::move( pSemaphore ) } + {} + + CTimeline::~CTimeline( ) { m_pVkSemaphore = nullptr; if ( m_uSyncobjHandle ) - drmSyncobjDestroy( GetDrmRenderFD(), m_uSyncobjHandle ); - if ( m_nSyncobjFd >= 0 ) - close( m_nSyncobjFd ); + drmSyncobjDestroy( GetDrmRenderFD( ), m_uSyncobjHandle ); + if ( m_nSyncobjFd >= 0 ) close( m_nSyncobjFd ); } - int32_t CTimeline::GetDrmRenderFD() - { - return g_device.drmRenderFd(); - } + int32_t CTimeline::GetDrmRenderFD( ) { return g_device.drmRenderFd( ); } - std::shared_ptr CTimeline::ToVkSemaphore() + std::shared_ptr CTimeline::ToVkSemaphore( ) { if ( !m_pVkSemaphore ) m_pVkSemaphore = g_device.ImportTimelineSemaphore( this ); @@ -70,31 +75,30 @@ namespace gamescope // CTimelinePoint - template - CTimelinePoint::CTimelinePoint( std::shared_ptr pTimeline, uint64_t ulPoint ) - : m_pTimeline{ std::move( pTimeline ) } - , m_ulPoint{ ulPoint } - { - } + template + CTimelinePoint::CTimelinePoint( + std::shared_ptr pTimeline, uint64_t ulPoint ) : + m_pTimeline{ std::move( pTimeline ) }, m_ulPoint{ ulPoint } + {} - template - CTimelinePoint::~CTimelinePoint() + template CTimelinePoint::~CTimelinePoint( ) { - if ( ShouldSignalOnDestruction() ) + if ( ShouldSignalOnDestruction( ) ) { - const uint32_t uHandle = m_pTimeline->GetSyncobjHandle(); + const uint32_t uHandle = m_pTimeline->GetSyncobjHandle( ); - drmSyncobjTimelineSignal( m_pTimeline->GetDrmRenderFD(), &uHandle, &m_ulPoint, 1 ); + drmSyncobjTimelineSignal( + m_pTimeline->GetDrmRenderFD( ), &uHandle, &m_ulPoint, 1 ); } } - template + template bool CTimelinePoint::Wait( int64_t lTimeout ) { - uint32_t uHandle = m_pTimeline->GetSyncobjHandle(); + uint32_t uHandle = m_pTimeline->GetSyncobjHandle( ); int nRet = drmSyncobjTimelineWait( - m_pTimeline->GetDrmRenderFD(), + m_pTimeline->GetDrmRenderFD( ), &uHandle, &m_ulPoint, 1, @@ -107,50 +111,54 @@ namespace gamescope // // Fence flags tl;dr - // 0 -> Wait for signal on a materialized fence, -ENOENT if not materialized + // 0 -> Wait for signal on a + // materialized fence, -ENOENT if not materialized // DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE -> Wait only for materialization - // DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT -> Wait for materialization + signal + // DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT -> Wait for materialization + + // signal // - template - std::pair CTimelinePoint::CreateEventFd() + template + std::pair CTimelinePoint::CreateEventFd( ) { assert( Type == TimelinePointType::Acquire ); - uint32_t uHandle = m_pTimeline->GetSyncobjHandle(); + uint32_t uHandle = m_pTimeline->GetSyncobjHandle( ); uint64_t ulSignalledPoint = 0; - int nRet = drmSyncobjQuery( m_pTimeline->GetDrmRenderFD(), &uHandle, &ulSignalledPoint, 1u ); + int nRet = drmSyncobjQuery( + m_pTimeline->GetDrmRenderFD( ), &uHandle, &ulSignalledPoint, 1u ); if ( nRet != 0 ) { s_TimelineLog.errorf_errno( "drmSyncobjQuery failed (%d)", nRet ); return k_InvalidEvent; } - if ( ulSignalledPoint >= m_ulPoint ) - { - return k_AlreadySignalledEvent; - } + if ( ulSignalledPoint >= m_ulPoint ) { return k_AlreadySignalledEvent; } else { const int32_t nExplicitSyncEventFd = eventfd( 0, EFD_CLOEXEC ); if ( nExplicitSyncEventFd < 0 ) { - s_TimelineLog.errorf_errno( "Failed to create eventfd (%d)", nExplicitSyncEventFd ); + s_TimelineLog.errorf_errno( + "Failed to create eventfd (%d)", nExplicitSyncEventFd ); return k_InvalidEvent; } - drm_syncobj_eventfd syncobjEventFd = - { - .handle = m_pTimeline->GetSyncobjHandle(), + drm_syncobj_eventfd syncobjEventFd = { + .handle = m_pTimeline->GetSyncobjHandle( ), // Only valid flags are: DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE // -> Wait for fence materialization rather than signal. - .flags = 0u, - .point = m_ulPoint, - .fd = nExplicitSyncEventFd, + .flags = 0u, + .point = m_ulPoint, + .fd = nExplicitSyncEventFd, }; - if ( drmIoctl( m_pTimeline->GetDrmRenderFD(), DRM_IOCTL_SYNCOBJ_EVENTFD, &syncobjEventFd ) != 0 ) + if ( drmIoctl( + m_pTimeline->GetDrmRenderFD( ), + DRM_IOCTL_SYNCOBJ_EVENTFD, + &syncobjEventFd ) != 0 ) { - s_TimelineLog.errorf_errno( "DRM_IOCTL_SYNCOBJ_EVENTFD failed" ); + s_TimelineLog.errorf_errno( + "DRM_IOCTL_SYNCOBJ_EVENTFD failed" ); close( nExplicitSyncEventFd ); return k_InvalidEvent; } @@ -162,4 +170,4 @@ namespace gamescope template class CTimelinePoint; template class CTimelinePoint; -} +} // namespace gamescope diff --git a/src/Timeline.h b/src/Timeline.h index 54611a0d31..5141228429 100644 --- a/src/Timeline.h +++ b/src/Timeline.h @@ -1,9 +1,9 @@ #pragma once #include -#include -#include #include +#include +#include #include "Utils/NonCopyable.h" @@ -25,64 +25,73 @@ namespace gamescope class CTimeline : public NonCopyable { public: - static std::shared_ptr Create( const TimelineCreateDesc_t &desc = {} ); + static std::shared_ptr + Create( const TimelineCreateDesc_t &desc = {} ); // Inherits nSyncobjFd's ref. - CTimeline( int32_t nSyncobjFd, std::shared_ptr pSemaphore = nullptr ); - CTimeline( int32_t nSyncobjFd, uint32_t uSyncobjHandle, std::shared_ptr pSemaphore = nullptr ); + CTimeline( + int32_t nSyncobjFd, + std::shared_ptr pSemaphore = nullptr ); + CTimeline( + int32_t nSyncobjFd, + uint32_t uSyncobjHandle, + std::shared_ptr pSemaphore = nullptr ); - CTimeline( CTimeline &&other ) - : m_nSyncobjFd{ std::exchange( other.m_nSyncobjFd, -1 ) } - , m_uSyncobjHandle{ std::exchange( other.m_uSyncobjHandle, 0 ) } - { - } - ~CTimeline(); + CTimeline( CTimeline &&other ) : + m_nSyncobjFd{ std::exchange( other.m_nSyncobjFd, -1 ) }, + m_uSyncobjHandle{ std::exchange( other.m_uSyncobjHandle, 0 ) } + {} + ~CTimeline( ); - static int32_t GetDrmRenderFD(); + static int32_t GetDrmRenderFD( ); - bool IsValid() const { return m_uSyncobjHandle != 0; } + bool IsValid( ) const { return m_uSyncobjHandle != 0; } - int32_t GetSyncobjFd() const { return m_nSyncobjFd; } - uint32_t GetSyncobjHandle() const { return m_uSyncobjHandle; } + int32_t GetSyncobjFd( ) const { return m_nSyncobjFd; } + uint32_t GetSyncobjHandle( ) const { return m_uSyncobjHandle; } + + std::shared_ptr ToVkSemaphore( ); - std::shared_ptr ToVkSemaphore(); - private: - int32_t m_nSyncobjFd = -1; + int32_t m_nSyncobjFd = -1; uint32_t m_uSyncobjHandle = 0; std::shared_ptr m_pVkSemaphore; }; - template - class CTimelinePoint : public NonCopyable + template class CTimelinePoint : public NonCopyable { public: - static constexpr std::pair k_InvalidEvent = { -1, false }; - static constexpr std::pair k_AlreadySignalledEvent = { -1, true }; + static constexpr std::pair k_InvalidEvent = { -1, + false }; + static constexpr std::pair k_AlreadySignalledEvent = { + -1, true + }; - CTimelinePoint( std::shared_ptr pTimeline, uint64_t ulPoint ); - ~CTimelinePoint(); + CTimelinePoint( + std::shared_ptr pTimeline, uint64_t ulPoint ); + ~CTimelinePoint( ); - void SetPoint( uint64_t ulPoint ) { m_ulPoint = ulPoint; } - uint64_t GetPoint() const { return m_ulPoint; } + void SetPoint( uint64_t ulPoint ) { m_ulPoint = ulPoint; } + uint64_t GetPoint( ) const { return m_ulPoint; } - std::shared_ptr &GetTimeline() { return m_pTimeline; } - const std::shared_ptr &GetTimeline() const { return m_pTimeline; } + std::shared_ptr &GetTimeline( ) { return m_pTimeline; } + const std::shared_ptr &GetTimeline( ) const + { return m_pTimeline; } - constexpr bool ShouldSignalOnDestruction() const { return Type == TimelinePointType::Release; } + constexpr bool ShouldSignalOnDestruction( ) const + { return Type == TimelinePointType::Release; } - bool Wait( int64_t lTimeout = std::numeric_limits::max() ); + bool Wait( int64_t lTimeout = std::numeric_limits::max( ) ); - std::pair CreateEventFd(); - private: + std::pair CreateEventFd( ); + private: std::shared_ptr m_pTimeline; - uint64_t m_ulPoint = 0; - + uint64_t m_ulPoint = 0; }; using CAcquireTimelinePoint = CTimelinePoint; using CReleaseTimelinePoint = CTimelinePoint; -} +} // namespace gamescope diff --git a/src/Utils/Algorithm.h b/src/Utils/Algorithm.h index eb51a79dff..288cbff938 100644 --- a/src/Utils/Algorithm.h +++ b/src/Utils/Algorithm.h @@ -6,31 +6,21 @@ namespace gamescope::Algorithm { - template - constexpr TObj *Begin( std::span span ) - { - return span.data(); - } + template constexpr TObj *Begin( std::span span ) + { return span.data( ); } - template - constexpr TObj *End( std::span span ) - { - return Begin( span ) + span.size(); - } + template constexpr TObj *End( std::span span ) + { return Begin( span ) + span.size( ); } - template + template constexpr const TObj *Begin( const std::vector &vec ) - { - return vec.data(); - } + { return vec.data( ); } - template + template constexpr const TObj *End( const std::vector &vec ) - { - return Begin( vec ) + vec.size(); - } + { return Begin( vec ) + vec.size( ); } - template + template constexpr TIter FindSimple( TIter pFirst, TIter pEnd, const TObj &obj ) { while ( pFirst != pEnd && *pFirst != obj ) @@ -39,24 +29,21 @@ namespace gamescope::Algorithm return pFirst; } - template + template constexpr TIter FindByFour( TIter pFirst, TIter pEnd, const TObj &obj ) { - typename std::iterator_traits< TIter >::difference_type ulTripCount = ( pEnd - pFirst ) >> 2; + typename std::iterator_traits::difference_type ulTripCount = + ( pEnd - pFirst ) >> 2; while ( ulTripCount-- > 0 ) { - if ( pFirst[0] == obj ) - return &pFirst[0]; + if ( pFirst[ 0 ] == obj ) return &pFirst[ 0 ]; - if ( pFirst[1] == obj ) - return &pFirst[1]; + if ( pFirst[ 1 ] == obj ) return &pFirst[ 1 ]; - if ( pFirst[2] == obj ) - return &pFirst[2]; + if ( pFirst[ 2 ] == obj ) return &pFirst[ 2 ]; - if ( pFirst[3] == obj ) - return &pFirst[3]; + if ( pFirst[ 3 ] == obj ) return &pFirst[ 3 ]; pFirst += 4; } @@ -65,31 +52,25 @@ namespace gamescope::Algorithm { case 3: { - if ( pFirst[0] == obj ) - return &pFirst[0]; + if ( pFirst[ 0 ] == obj ) return &pFirst[ 0 ]; - if ( pFirst[1] == obj ) - return &pFirst[1]; + if ( pFirst[ 1 ] == obj ) return &pFirst[ 1 ]; - if ( pFirst[2] == obj ) - return &pFirst[2]; + if ( pFirst[ 2 ] == obj ) return &pFirst[ 2 ]; return pEnd; } case 2: { - if ( pFirst[0] == obj ) - return &pFirst[0]; + if ( pFirst[ 0 ] == obj ) return &pFirst[ 0 ]; - if ( pFirst[1] == obj ) - return &pFirst[1]; + if ( pFirst[ 1 ] == obj ) return &pFirst[ 1 ]; return pEnd; } case 1: { - if ( pFirst[0] == obj ) - return &pFirst[0]; + if ( pFirst[ 0 ] == obj ) return &pFirst[ 0 ]; return pEnd; } @@ -99,48 +80,40 @@ namespace gamescope::Algorithm } default: { - __builtin_unreachable(); + __builtin_unreachable( ); } } } - template + template constexpr TIter Find( TIter pFirst, TIter pEnd, const TObj &obj ) - { - return FindSimple( pFirst, pEnd, obj ); - } + { return FindSimple( pFirst, pEnd, obj ); } - template + template constexpr TIter Find( std::span span, const TObj &obj ) - { - return Find( Begin( span ), End( span ), obj ); - } + { return Find( Begin( span ), End( span ), obj ); } - template + template constexpr TIter Find( const std::vector &vec, const TObj &obj ) - { - return Find( Begin( vec ), End( vec ), obj ); - } + { return Find( Begin( vec ), End( vec ), obj ); } - template + template constexpr bool ContainsShortcut( TIter pFirst, TIter pEnd, const TObj &obj ) - { - return Find( pFirst, pEnd, obj ) != pEnd; - } + { return Find( pFirst, pEnd, obj ) != pEnd; } - template - constexpr bool ContainsNoShortcut( TIter pFirst, TIter pEnd, const TObj &obj ) + template + constexpr bool + ContainsNoShortcut( TIter pFirst, TIter pEnd, const TObj &obj ) { bool bFound = false; - typename std::iterator_traits< TIter >::difference_type ulTripCount = ( pEnd - pFirst ) >> 2; + typename std::iterator_traits::difference_type ulTripCount = + ( pEnd - pFirst ) >> 2; while ( ulTripCount-- > 0 ) { - bFound |= pFirst[0] == obj || - pFirst[1] == obj || - pFirst[2] == obj || - pFirst[3] == obj; + bFound |= pFirst[ 0 ] == obj || pFirst[ 1 ] == obj || + pFirst[ 2 ] == obj || pFirst[ 3 ] == obj; pFirst += 4; } @@ -149,20 +122,18 @@ namespace gamescope::Algorithm { case 3: { - bFound |= pFirst[0] == obj || - pFirst[1] == obj || - pFirst[2] == obj; + bFound |= pFirst[ 0 ] == obj || pFirst[ 1 ] == obj || + pFirst[ 2 ] == obj; break; } case 2: { - bFound |= pFirst[0] == obj || - pFirst[1] == obj; + bFound |= pFirst[ 0 ] == obj || pFirst[ 1 ] == obj; break; } case 1: { - bFound |= pFirst[0] == obj; + bFound |= pFirst[ 0 ] == obj; break; } case 0: @@ -171,28 +142,23 @@ namespace gamescope::Algorithm } default: { - __builtin_unreachable(); + __builtin_unreachable( ); } } return bFound; } - template + template constexpr bool Contains( TIter pFirst, TIter pEnd, const TObj &obj ) - { - return ContainsNoShortcut( pFirst, pEnd, obj ); - } + { return ContainsNoShortcut( pFirst, pEnd, obj ); } - template + template constexpr bool Contains( std::span span, const TObj &obj ) - { - return Contains( Begin( span ), End( span ), obj ); - } + { return Contains( Begin( span ), End( span ), obj ); } - template - constexpr bool Contains( const std::vector &vec, const TObj &obj ) - { - return Contains( Begin( vec ), End( vec ), obj ); - } -} + template + constexpr bool + Contains( const std::vector &vec, const TObj &obj ) + { return Contains( Begin( vec ), End( vec ), obj ); } +} // namespace gamescope::Algorithm diff --git a/src/Utils/Defer.h b/src/Utils/Defer.h index 63b35109da..ba3f9d41e3 100644 --- a/src/Utils/Defer.h +++ b/src/Utils/Defer.h @@ -4,27 +4,21 @@ namespace gamescope { - template - class DeferHelper + template class DeferHelper { public: - DeferHelper( Func fnFunc ) - : m_fnFunc{ std::move( fnFunc ) } - { - } + DeferHelper( Func fnFunc ) : m_fnFunc{ std::move( fnFunc ) } {} - ~DeferHelper() - { - m_fnFunc(); - } + ~DeferHelper( ) { m_fnFunc( ); } private: Func m_fnFunc; }; -} +} // namespace gamescope -#define DEFER_1(x, y) x##y -#define DEFER_2(x, y) DEFER_1(x, y) -#define DEFER_3(x) DEFER_2(x, __COUNTER__) -#define defer(code) auto DEFER_3(_defer_) = ::gamescope::DeferHelper( [&](){ code; } ) +#define DEFER_1( x, y ) x##y +#define DEFER_2( x, y ) DEFER_1( x, y ) +#define DEFER_3( x ) DEFER_2( x, __COUNTER__ ) +#define defer( code ) \ + auto DEFER_3( _defer_ ) = ::gamescope::DeferHelper( [ & ]( ) { code; } ) diff --git a/src/Utils/Dict.h b/src/Utils/Dict.h index c17efd77c6..d342d24874 100644 --- a/src/Utils/Dict.h +++ b/src/Utils/Dict.h @@ -1,8 +1,8 @@ #pragma once -#include -#include #include +#include +#include #include namespace gamescope @@ -10,14 +10,19 @@ namespace gamescope struct StringHash { using is_transparent = void; - [[nodiscard]] size_t operator()( const char *string ) const { return std::hash{}( string ); } - [[nodiscard]] size_t operator()( std::string_view string ) const { return std::hash{}( string ); } - [[nodiscard]] size_t operator()( const std::string &string ) const { return std::hash{}( string ); } + [[nodiscard]] size_t operator( )( const char *string ) const + { return std::hash{}( string ); } + [[nodiscard]] size_t operator( )( std::string_view string ) const + { return std::hash{}( string ); } + [[nodiscard]] size_t operator( )( const std::string &string ) const + { return std::hash{}( string ); } }; - template - using Dict = std::unordered_map>; + template + using Dict = + std::unordered_map>; - template - using MultiDict = std::unordered_multimap>; -} + template + using MultiDict = + std::unordered_multimap>; +} // namespace gamescope diff --git a/src/Utils/DirHelpers.cpp b/src/Utils/DirHelpers.cpp index df0cf34fee..407051c76d 100644 --- a/src/Utils/DirHelpers.cpp +++ b/src/Utils/DirHelpers.cpp @@ -1,44 +1,38 @@ #include "DirHelpers.h" -#include #include +#include #include -namespace gamescope { - std::string_view GetHomeDir() +namespace gamescope +{ + std::string_view GetHomeDir( ) { - static std::string s_sHomeDir = []() -> std::string + static std::string s_sHomeDir = []( ) -> std::string { const char *pszHomeDir = getenv( "HOME" ); - if ( pszHomeDir ) - return pszHomeDir; - - return getpwuid( getuid() )->pw_dir; - }(); + if ( pszHomeDir ) return pszHomeDir; + + return getpwuid( getuid( ) )->pw_dir; + }( ); return s_sHomeDir; } - std::string GetLocalUsrDir() - { - return std::string{ GetHomeDir() } + "/.local"; - } + std::string GetLocalUsrDir( ) + { return std::string{ GetHomeDir( ) } + "/.local"; } - std::string GetUsrDir() - { - return "/usr"; - } + std::string GetUsrDir( ) { return "/usr"; } - std::string_view GetConfigDir() + std::string_view GetConfigDir( ) { - static std::string s_sConfigDir = []() -> std::string + static std::string s_sConfigDir = []( ) -> std::string { const char *pszConfigHome = getenv( "XDG_CONFIG_HOME" ); - if ( pszConfigHome && *pszConfigHome ) - return pszConfigHome; + if ( pszConfigHome && *pszConfigHome ) return pszConfigHome; - return std::string{ GetHomeDir() } + "/.config"; - }(); + return std::string{ GetHomeDir( ) } + "/.config"; + }( ); return s_sConfigDir; } -} +} // namespace gamescope diff --git a/src/Utils/DirHelpers.h b/src/Utils/DirHelpers.h index a0c8806aba..8902302602 100644 --- a/src/Utils/DirHelpers.h +++ b/src/Utils/DirHelpers.h @@ -4,8 +4,8 @@ namespace gamescope { - std::string_view GetHomeDir(); - std::string GetLocalUsrDir(); - std::string GetUsrDir(); - std::string_view GetConfigDir(); -} + std::string_view GetHomeDir( ); + std::string GetLocalUsrDir( ); + std::string GetUsrDir( ); + std::string_view GetConfigDir( ); +} // namespace gamescope diff --git a/src/Utils/NonCopyable.h b/src/Utils/NonCopyable.h index 8895efed51..f71f383e87 100644 --- a/src/Utils/NonCopyable.h +++ b/src/Utils/NonCopyable.h @@ -6,10 +6,10 @@ namespace gamescope class NonCopyable { public: - NonCopyable() = default; - NonCopyable(const NonCopyable &) = delete; + NonCopyable( ) = default; + NonCopyable( const NonCopyable & ) = delete; - void operator = (const NonCopyable &) = delete; + void operator=( const NonCopyable & ) = delete; }; -} +} // namespace gamescope diff --git a/src/Utils/Process.cpp b/src/Utils/Process.cpp index f5191af8ea..8f888d4657 100644 --- a/src/Utils/Process.cpp +++ b/src/Utils/Process.cpp @@ -1,34 +1,34 @@ #include "Process.h" #include "../Utils/Algorithm.h" +#include "../Utils/Defer.h" #include "../convar.h" #include "../log.hpp" -#include "../Utils/Defer.h" #include #include #include -#include #include +#include #include #include -#if defined(__linux__) -#if HAVE_LIBCAP -#include -#endif -#include -#elif defined(__DragonFly__) || defined(__FreeBSD__) -#include +#if defined( __linux__ ) + #if HAVE_LIBCAP + #include + #endif + #include +#elif defined( __DragonFly__ ) || defined( __FreeBSD__ ) + #include #endif -#include -#include #include +#include #include -#include -#include +#include #include -#include +#include +#include +#include extern const char *__progname; @@ -37,18 +37,17 @@ static LogScope s_ProcessLog( "process" ); namespace gamescope::Process { static bool IsDigit( char chChar ) - { - return chChar >= '0' && chChar <= '9'; - } + { return chChar >= '0' && chChar <= '9'; } - void BecomeSubreaper() + void BecomeSubreaper( ) { -#if defined(__linux__) +#if defined( __linux__ ) prctl( PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0 ); -#elif defined(__DragonFly__) || defined(__FreeBSD__) - procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL); +#elif defined( __DragonFly__ ) || defined( __FreeBSD__ ) + procctl( P_PID, getpid( ), PROC_REAP_ACQUIRE, NULL ); #else -#warning "Changing reaper process for children is not supported on this platform" + #warning \ + "Changing reaper process for children is not supported on this platform" #endif } @@ -58,7 +57,7 @@ namespace gamescope::Process // Kill myself when my parent dies. prctl( PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0 ); #else -#warning "Setting death signal is not supported on this platform" + #warning "Setting death signal is not supported on this platform" #endif } @@ -75,20 +74,18 @@ namespace gamescope::Process defer( closedir( pProcDir ) ); struct dirent *pEntry; - while ( ( pEntry = readdir( pProcDir ) ) ) + while (( pEntry = readdir( pProcDir ) )) { - if ( pEntry->d_type != DT_DIR ) - continue; + if ( pEntry->d_type != DT_DIR ) continue; - if ( !IsDigit( pEntry->d_name[0] ) ) - continue; + if ( !IsDigit( pEntry->d_name[ 0 ] ) ) continue; char szPath[ PATH_MAX ]; - snprintf( szPath, sizeof( szPath ), "/proc/%s/stat", pEntry->d_name ); - + snprintf( + szPath, sizeof( szPath ), "/proc/%s/stat", pEntry->d_name ); + FILE *pStatFile = fopen( szPath, "r" ); - if ( !pStatFile ) - continue; + if ( !pStatFile ) continue; defer( fclose( pStatFile ) ); pid_t nParentPid = -1; @@ -138,12 +135,12 @@ namespace gamescope::Process int nStatus = 0; if ( waitpid( nPid, &nStatus, 0 ) == -1 ) { - if ( errno == EINTR ) - continue; + if ( errno == EINTR ) continue; if ( errno != ECHILD ) { - s_ProcessLog.errorf_errno( "Wait for primary child failed." ); + s_ProcessLog.errorf_errno( + "Wait for primary child failed." ); } return std::nullopt; @@ -159,19 +156,17 @@ namespace gamescope::Process { for ( ;; ) { - int nStatus = 0; + int nStatus = 0; pid_t nDeadChild = waitpid( -1, &nStatus, 0 ); - if ( onStopPid && nDeadChild == *onStopPid ) - return true; + if ( onStopPid && nDeadChild == *onStopPid ) return true; - if ( nDeadChild == -1 && errno == ECHILD ) - return false; + if ( nDeadChild == -1 && errno == ECHILD ) return false; } } static std::optional g_oOriginalFDLimit{}; - void RaiseFdLimit() + void RaiseFdLimit( ) { if ( g_oOriginalFDLimit ) { @@ -182,7 +177,9 @@ namespace gamescope::Process rlimit originalLimit{}; if ( getrlimit( RLIMIT_NOFILE, &originalLimit ) != 0 ) { - s_ProcessLog.errorf( "Could not query maximum number of open files. Leaving at default value." ); + s_ProcessLog.errorf( + "Could not query maximum number of open files. Leaving at " + "default value." ); return; } @@ -192,25 +189,28 @@ namespace gamescope::Process return; } - rlimit newLimit = originalLimit; + rlimit newLimit = originalLimit; newLimit.rlim_cur = newLimit.rlim_max; if ( setrlimit( RLIMIT_NOFILE, &newLimit ) ) { - s_ProcessLog.errorf( "Failed to raise the maximum number of open files. Leaving at default value." ); + s_ProcessLog.errorf( + "Failed to raise the maximum number of open files. Leaving at " + "default value." ); return; } g_oOriginalFDLimit = originalLimit; } - void RestoreFdLimit() + void RestoreFdLimit( ) { - if ( !g_oOriginalFDLimit ) - return; + if ( !g_oOriginalFDLimit ) return; if ( setrlimit( RLIMIT_NOFILE, &*g_oOriginalFDLimit ) ) { - s_ProcessLog.errorf( "Failed to reset the maximum number of open files in child process." ); + s_ProcessLog.errorf( + "Failed to reset the maximum number of open files in child " + "process." ); s_ProcessLog.errorf( "Use of select() may fail." ); return; } @@ -218,26 +218,23 @@ namespace gamescope::Process g_oOriginalFDLimit = std::nullopt; } - void ResetSignals() + void ResetSignals( ) { sigset_t set; sigemptyset( &set ); sigprocmask( SIG_SETMASK, &set, nullptr ); } - static void ProcessPreSpawn() + static void ProcessPreSpawn( ) { - ResetSignals(); + ResetSignals( ); - RestoreFdLimit(); - RestoreNice(); - RestoreRealtime(); + RestoreFdLimit( ); + RestoreNice( ); + RestoreRealtime( ); } - bool CloseFd( int nFd ) - { - return close( nFd ) == 0; - } + bool CloseFd( int nFd ) { return close( nFd ) == 0; } void CloseAllFds( std::span nExcludedFds ) { @@ -250,30 +247,32 @@ namespace gamescope::Process defer( closedir( pProcDir ) ); struct dirent *pEntry; - while ( ( pEntry = readdir( pProcDir ) ) ) + while (( pEntry = readdir( pProcDir ) )) { std::optional onFd = Parse( pEntry->d_name ); - if ( !onFd ) - continue; + if ( !onFd ) continue; int nFd = *onFd; bool bExcluded = Algorithm::Contains( nExcludedFds, nFd ); - if ( bExcluded ) - continue; + if ( bExcluded ) continue; if ( !CloseFd( nFd ) ) { - s_ProcessLog.errorf_errno( "CloseAllFds failed to close FD %d", nFd ); + s_ProcessLog.errorf_errno( + "CloseAllFds failed to close FD %d", nFd ); } } } - pid_t SpawnProcess( char **argv, std::function fnPreambleInChild, bool bDoubleFork ) + pid_t SpawnProcess( + char **argv, + std::function fnPreambleInChild, + bool bDoubleFork ) { // Create a pipe for the child to return the grandchild's // PID into. - int nPidPipe[2] = { -1, -1 }; + int nPidPipe[ 2 ] = { -1, -1 }; if ( bDoubleFork ) { if ( pipe2( nPidPipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) @@ -283,50 +282,49 @@ namespace gamescope::Process } } - pid_t nChild = fork(); + pid_t nChild = fork( ); if ( nChild < 0 ) { if ( bDoubleFork ) { - CloseFd( nPidPipe[0] ); - CloseFd( nPidPipe[1] ); + CloseFd( nPidPipe[ 0 ] ); + CloseFd( nPidPipe[ 1 ] ); } s_ProcessLog.errorf_errno( "Failed to fork() child" ); return -1; } else if ( nChild == 0 ) { - std::array nExcludedFds = - {{ + std::array nExcludedFds = { { STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, - nPidPipe[0], // -1 if !bDoubleFork, which is fine. - nPidPipe[1], - }}; + nPidPipe[ 0 ], // -1 if !bDoubleFork, which is fine. + nPidPipe[ 1 ], + } }; CloseAllFds( nExcludedFds ); - ProcessPreSpawn(); + ProcessPreSpawn( ); if ( bDoubleFork ) { // Don't need the read pipe anymore. - CloseFd( nPidPipe[0] ); + CloseFd( nPidPipe[ 0 ] ); } - if ( fnPreambleInChild ) - fnPreambleInChild(); + if ( fnPreambleInChild ) fnPreambleInChild( ); if ( bDoubleFork ) { - pid_t nGrandChild = fork(); + pid_t nGrandChild = fork( ); if ( nGrandChild == 0 ) { - CloseFd( nPidPipe[1] ); + CloseFd( nPidPipe[ 1 ] ); - if ( execvp( argv[0], argv ) == -1 ) + if ( execvp( argv[ 0 ], argv ) == -1 ) { - s_ProcessLog.errorf_errno( "Failed to start process \"%s\"", argv[0] ); + s_ProcessLog.errorf_errno( + "Failed to start process \"%s\"", argv[ 0 ] ); } _exit( 0 ); } @@ -335,17 +333,20 @@ namespace gamescope::Process s_ProcessLog.errorf_errno( "Failed to fork() grandchild." ); } - ssize_t sszRet = write( nPidPipe[1], &nGrandChild, sizeof( nGrandChild ) ); - (void) sszRet; // Cannot handle this error here, it is checked on the other side anyway. - CloseFd( nPidPipe[1] ); + ssize_t sszRet = + write( nPidPipe[ 1 ], &nGrandChild, sizeof( nGrandChild ) ); + ( void )sszRet; // Cannot handle this error here, it is checked + // on the other side anyway. + CloseFd( nPidPipe[ 1 ] ); _exit( 0 ); } else { - if ( execvp( argv[0], argv ) == -1 ) + if ( execvp( argv[ 0 ], argv ) == -1 ) { - s_ProcessLog.errorf_errno( "Failed to start process \"%s\"", argv[0] ); + s_ProcessLog.errorf_errno( + "Failed to start process \"%s\"", argv[ 0 ] ); } _exit( 0 ); } @@ -360,16 +361,16 @@ namespace gamescope::Process // is fork to spawn a child to orphan. WaitForChild( nChild ); - // Now that the child process is done it must have written fully to the pipe. - // Read the PID back from the pipe and close it. - pid_t nGrandChild = 0; - ssize_t sszAmountRead = read( nPidPipe[0], &nGrandChild, sizeof( nGrandChild ) ); - CloseFd( nPidPipe[0] ); - CloseFd( nPidPipe[1] ); + // Now that the child process is done it must have written fully to + // the pipe. Read the PID back from the pipe and close it. + pid_t nGrandChild = 0; + ssize_t sszAmountRead = + read( nPidPipe[ 0 ], &nGrandChild, sizeof( nGrandChild ) ); + CloseFd( nPidPipe[ 0 ] ); + CloseFd( nPidPipe[ 1 ] ); // Sanity check what we got from the pipe. - if ( sszAmountRead != sizeof( nGrandChild ) ) - return -1; + if ( sszAmountRead != sizeof( nGrandChild ) ) return -1; return nGrandChild; } @@ -379,38 +380,38 @@ namespace gamescope::Process } } - pid_t SpawnProcessInWatchdog( char **argv, bool bRespawn, std::function fnPreambleInChild ) + pid_t SpawnProcessInWatchdog( + char **argv, bool bRespawn, std::function fnPreambleInChild ) { std::vector args; - args.push_back( (char *)"gamescopereaper" ); - if ( bRespawn ) - args.push_back( (char *)"--respawn" ); - args.push_back( (char *)"--" ); + args.push_back( ( char * )"gamescopereaper" ); + if ( bRespawn ) args.push_back( ( char * )"--respawn" ); + args.push_back( ( char * )"--" ); while ( *argv ) { args.push_back( *argv ); argv++; } args.push_back( NULL ); - return SpawnProcess( args.data(), fnPreambleInChild ); + return SpawnProcess( args.data( ), fnPreambleInChild ); } - bool HasCapSysNice() + bool HasCapSysNice( ) { -#if defined(__linux__) && HAVE_LIBCAP - static bool s_bHasCapSysNice = []() -> bool +#if defined( __linux__ ) && HAVE_LIBCAP + static bool s_bHasCapSysNice = []( ) -> bool { - cap_t pCaps = cap_get_proc(); - if ( !pCaps ) - return false; + cap_t pCaps = cap_get_proc( ); + if ( !pCaps ) return false; defer( cap_free( pCaps ) ); - cap_flag_value_t eNiceCapValue = CAP_CLEAR; - if ( cap_get_flag( pCaps, CAP_SYS_NICE, CAP_EFFECTIVE, &eNiceCapValue ) != 0 ) + cap_flag_value_t eNiceCapValue = CAP_CLEAR; + if ( cap_get_flag( + pCaps, CAP_SYS_NICE, CAP_EFFECTIVE, &eNiceCapValue ) != 0 ) return false; return eNiceCapValue == CAP_SET; - }(); + }( ); return s_bHasCapSysNice; #else @@ -420,46 +421,33 @@ namespace gamescope::Process std::optional g_oOldNice; std::optional g_oNewNice; - void SetNice( int nNice ) + void SetNice( int nNice ) { -#if defined(__linux__) - if ( !HasCapSysNice() ) - return; +#if defined( __linux__ ) + if ( !HasCapSysNice( ) ) return; - errno = 0; + errno = 0; int nOldNice = nice( 0 ); - if ( nOldNice != -1 || errno == 0 ) - { - g_oOldNice = nOldNice; - } + if ( nOldNice != -1 || errno == 0 ) { g_oOldNice = nOldNice; } - errno = 0; + errno = 0; int nNewNice = nice( -20 ); - if ( nNewNice != -1 || errno == 0 ) - { - g_oNewNice = nNewNice; - } + if ( nNewNice != -1 || errno == 0 ) { g_oNewNice = nNewNice; } #endif } - void RestoreNice() + void RestoreNice( ) { -#if defined(__linux__) - if ( !HasCapSysNice() ) - return; +#if defined( __linux__ ) + if ( !HasCapSysNice( ) ) return; - if ( !g_oOldNice || !g_oNewNice ) - return; + if ( !g_oOldNice || !g_oNewNice ) return; - if ( *g_oOldNice == *g_oNewNice ) - return; + if ( *g_oOldNice == *g_oNewNice ) return; - errno = 0; + errno = 0; int nNewNice = nice( *g_oOldNice - *g_oNewNice ); - if ( g_oNewNice != -1 || errno == 0 ) - { - g_oNewNice = nNewNice; - } + if ( g_oNewNice != -1 || errno == 0 ) { g_oNewNice = nNewNice; } if ( g_oOldNice == g_oNewNice ) { @@ -475,15 +463,17 @@ namespace gamescope::Process struct SchedulerInfo { - int nPolicy; + int nPolicy; struct sched_param SchedParam; - static std::optional Get() + static std::optional Get( ) { SchedulerInfo info{}; - if ( pthread_getschedparam( pthread_self(), &info.nPolicy, &info.SchedParam) ) + if ( pthread_getschedparam( + pthread_self( ), &info.nPolicy, &info.SchedParam ) ) { - s_ProcessLog.errorf_errno( "Failed to get old scheduler info." ); + s_ProcessLog.errorf_errno( + "Failed to get old scheduler info." ); return std::nullopt; } return info; @@ -491,20 +481,18 @@ namespace gamescope::Process }; std::optional g_oOldSchedulerInfo; - bool SetRealtime() + bool SetRealtime( ) { -#if defined(__linux__) - if ( !HasCapSysNice() ) - return false; +#if defined( __linux__ ) + if ( !HasCapSysNice( ) ) return false; - g_oOldSchedulerInfo = SchedulerInfo::Get(); - if ( !g_oOldSchedulerInfo ) - return false; + g_oOldSchedulerInfo = SchedulerInfo::Get( ); + if ( !g_oOldSchedulerInfo ) return false; struct sched_param newSched{}; sched_getparam( 0, &newSched ); newSched.sched_priority = sched_get_priority_min( SCHED_RR ); - if ( pthread_setschedparam( pthread_self(), SCHED_RR, &newSched ) ) + if ( pthread_setschedparam( pthread_self( ), SCHED_RR, &newSched ) ) { s_ProcessLog.errorf_errno( "Failed to set realtime scheduling." ); return false; @@ -514,18 +502,20 @@ namespace gamescope::Process #endif } - void RestoreRealtime() + void RestoreRealtime( ) { -#if defined(__linux__) - if ( !HasCapSysNice() ) - return; +#if defined( __linux__ ) + if ( !HasCapSysNice( ) ) return; - if ( !g_oOldSchedulerInfo ) - return; + if ( !g_oOldSchedulerInfo ) return; - if ( pthread_setschedparam( pthread_self(), g_oOldSchedulerInfo->nPolicy, &g_oOldSchedulerInfo->SchedParam ) ) + if ( pthread_setschedparam( + pthread_self( ), + g_oOldSchedulerInfo->nPolicy, + &g_oOldSchedulerInfo->SchedParam ) ) { - s_ProcessLog.errorf_errno( "Failed to restore from realtime scheduling." ); + s_ProcessLog.errorf_errno( + "Failed to restore from realtime scheduling." ); return; } @@ -533,9 +523,6 @@ namespace gamescope::Process #endif } - const char *GetProcessName() - { - return __progname; - } + const char *GetProcessName( ) { return __progname; } -} +} // namespace gamescope::Process diff --git a/src/Utils/Process.h b/src/Utils/Process.h index 24c87d302c..7aa94fec0f 100644 --- a/src/Utils/Process.h +++ b/src/Utils/Process.h @@ -1,14 +1,14 @@ #pragma once -#include #include +#include #include #include namespace gamescope::Process { - void BecomeSubreaper(); + void BecomeSubreaper( ); void SetDeathSignal( int nSignal ); void KillAllChildren( pid_t nParentPid, int nSignal ); @@ -25,22 +25,28 @@ namespace gamescope::Process bool CloseFd( int nFd ); - void RaiseFdLimit(); - void RestoreFdLimit(); - void ResetSignals(); + void RaiseFdLimit( ); + void RestoreFdLimit( ); + void ResetSignals( ); void CloseAllFds( std::span nExcludedFds ); - pid_t SpawnProcess( char **argv, std::function fnPreambleInChild = nullptr, bool bDoubleFork = false ); - pid_t SpawnProcessInWatchdog( char **argv, bool bRespawn = false, std::function fnPreambleInChild = nullptr ); + pid_t SpawnProcess( + char **argv, + std::function fnPreambleInChild = nullptr, + bool bDoubleFork = false ); + pid_t SpawnProcessInWatchdog( + char **argv, + bool bRespawn = false, + std::function fnPreambleInChild = nullptr ); - bool HasCapSysNice(); + bool HasCapSysNice( ); void SetNice( int nNice ); - void RestoreNice(); + void RestoreNice( ); - bool SetRealtime(); - void RestoreRealtime(); + bool SetRealtime( ); + void RestoreRealtime( ); - const char *GetProcessName(); + const char *GetProcessName( ); -} \ No newline at end of file +} // namespace gamescope::Process diff --git a/src/Utils/TempFiles.cpp b/src/Utils/TempFiles.cpp index b982b6d6af..888e7f0e0a 100644 --- a/src/Utils/TempFiles.cpp +++ b/src/Utils/TempFiles.cpp @@ -1,9 +1,9 @@ -#include -#include #include +#include +#include -#include #include +#include #include #include @@ -16,22 +16,16 @@ namespace gamescope { public: void Add( std::string sPath ) - { - m_DeferredUnlinks.emplace_front( sPath ); - } + { m_DeferredUnlinks.emplace_front( sPath ); } + private: class CDeferUnlink { public: - CDeferUnlink( std::string sPath ) - : m_sPath{ std::move( sPath ) } - { - } - - ~CDeferUnlink() - { - unlink( m_sPath.c_str() ); - } + CDeferUnlink( std::string sPath ) : m_sPath{ std::move( sPath ) } {} + + ~CDeferUnlink( ) { unlink( m_sPath.c_str( ) ); } + private: const std::string m_sPath; }; @@ -40,24 +34,22 @@ namespace gamescope }; static CDeferUnlinks s_DeferredUnlinks; - int MakeTempFile( char ( &pszOutPath )[ PATH_MAX ], const char *pszTemplate, bool bDeferUnlink ) + int MakeTempFile( + char ( &pszOutPath )[ PATH_MAX ], + const char *pszTemplate, + bool bDeferUnlink ) { const char *pXDGPath = getenv( "XDG_RUNTIME_DIR" ); - if ( !pXDGPath || !*pXDGPath ) - return -1; + if ( !pXDGPath || !*pXDGPath ) return -1; snprintf( pszOutPath, PATH_MAX, "%s/%s", pXDGPath, pszTemplate ); // Overwrites pszOutPath with the file path. int nFd = mkostemp( pszOutPath, O_CLOEXEC ); - if ( nFd < 0 ) - return -1; + if ( nFd < 0 ) return -1; // Unlink so it gets destroyed when Gamescope dies. - if ( bDeferUnlink ) - { - s_DeferredUnlinks.Add( pszOutPath ); - } + if ( bDeferUnlink ) { s_DeferredUnlinks.Add( pszOutPath ); } else { unlink( pszOutPath ); @@ -66,11 +58,14 @@ namespace gamescope return nFd; } - FILE *MakeTempFile( char ( &pszOutPath )[ PATH_MAX ], const char *pszTemplate, const char *pszMode, bool bDeferUnlink ) + FILE *MakeTempFile( + char ( &pszOutPath )[ PATH_MAX ], + const char *pszTemplate, + const char *pszMode, + bool bDeferUnlink ) { int nFd = MakeTempFile( pszOutPath, pszTemplate, bDeferUnlink ); - if ( nFd < 0 ) - return nullptr; + if ( nFd < 0 ) return nullptr; FILE *pFile = fdopen( nFd, pszMode ); if ( !pFile ) @@ -82,4 +77,4 @@ namespace gamescope // fclose will close the file **and** the underlying file descriptor. return pFile; } -} \ No newline at end of file +} // namespace gamescope diff --git a/src/Utils/TempFiles.h b/src/Utils/TempFiles.h index 3504cd76b2..1e1196486a 100644 --- a/src/Utils/TempFiles.h +++ b/src/Utils/TempFiles.h @@ -5,11 +5,22 @@ namespace gamescope { - static constexpr const char k_szGamescopeTempFileTemplate[] = "gamescope-temp-XXXXXXXX"; - static constexpr const char k_szGamescopeTempShmTemplate[] = "gamescope-shm-XXXXXXXX"; - static constexpr const char k_szGamescopeTempMangoappTemplate[] = "gamescope-mangoapp-XXXXXXXX"; - static constexpr const char k_szGamescopeTempLimiterTemplate[] = "gamescope-limiter-XXXXXXXX"; + static constexpr const char k_szGamescopeTempFileTemplate[] = + "gamescope-temp-XXXXXXXX"; + static constexpr const char k_szGamescopeTempShmTemplate[] = + "gamescope-shm-XXXXXXXX"; + static constexpr const char k_szGamescopeTempMangoappTemplate[] = + "gamescope-mangoapp-XXXXXXXX"; + static constexpr const char k_szGamescopeTempLimiterTemplate[] = + "gamescope-limiter-XXXXXXXX"; - int MakeTempFile( char ( &pszOutPath )[ PATH_MAX ], const char *pszTemplate, bool bDeferUnlink = false ); - FILE *MakeTempFile( char ( &pszOutPath )[ PATH_MAX ], const char *pszTemplate, const char *pszMode, bool bDeferUnlink = false ); -} \ No newline at end of file + int MakeTempFile( + char ( &pszOutPath )[ PATH_MAX ], + const char *pszTemplate, + bool bDeferUnlink = false ); + FILE *MakeTempFile( + char ( &pszOutPath )[ PATH_MAX ], + const char *pszTemplate, + const char *pszMode, + bool bDeferUnlink = false ); +} // namespace gamescope diff --git a/src/Utils/Version.cpp b/src/Utils/Version.cpp index e1a94aeddf..b4ae0726ab 100644 --- a/src/Utils/Version.cpp +++ b/src/Utils/Version.cpp @@ -7,8 +7,11 @@ namespace gamescope { - void PrintVersion() + void PrintVersion( ) { - console_log.infof( "%s version %s", Process::GetProcessName(), gamescope::k_szGamescopeVersion ); + console_log.infof( + "%s version %s", + Process::GetProcessName( ), + gamescope::k_szGamescopeVersion ); } -} \ No newline at end of file +} // namespace gamescope diff --git a/src/Utils/Version.h b/src/Utils/Version.h index 2b99966b3e..9185743bc1 100644 --- a/src/Utils/Version.h +++ b/src/Utils/Version.h @@ -2,5 +2,5 @@ namespace gamescope { - void PrintVersion(); + void PrintVersion( ); } diff --git a/src/WaylandServer/GamescopeActionBinding.h b/src/WaylandServer/GamescopeActionBinding.h index a9ddf46f1e..ee97fe7573 100644 --- a/src/WaylandServer/GamescopeActionBinding.h +++ b/src/WaylandServer/GamescopeActionBinding.h @@ -6,43 +6,41 @@ #include "../log.hpp" -#include +#include #include #include -#include #include -#include "convar.h" #include "Utils/Algorithm.h" +#include "convar.h" -#include "wlr_begin.hpp" -#include #include +#include +#include "wlr_begin.hpp" #include "wlr_end.hpp" using namespace std::literals; -uint64_t get_time_in_nanos(); +uint64_t get_time_in_nanos( ); inline LogScope log_binding( "binding" ); -static inline std::string ComputeDebugName( const std::unordered_set &syms ) +static inline std::string +ComputeDebugName( const std::unordered_set &syms ) { std::string sTriggerDebugName; - bool bFirst = true; + bool bFirst = true; for ( xkb_keysym_t uKeySym : syms ) { - if ( !bFirst ) - { - sTriggerDebugName += " + "; - } + if ( !bFirst ) { sTriggerDebugName += " + "; } bFirst = false; - char szName[256] = ""; + char szName[ 256 ] = ""; if ( xkb_keysym_get_name( uKeySym, szName, sizeof( szName ) ) <= 0 ) { - snprintf( szName, sizeof( szName ), "xkb_keysym_t( 0x%x )", uKeySym ); + snprintf( + szName, sizeof( szName ), "xkb_keysym_t( 0x%x )", uKeySym ); } sTriggerDebugName += szName; @@ -51,15 +49,14 @@ static inline std::string ComputeDebugName( const std::unordered_set k_mapKeysymRemapping[] -{ - // BUG: normalize to the same tab key. - // Sometimes when the keyboard comes online we'll receive key events as - // one of these two. We need to investigate why. For now lets just normalize - // to one of the two. - // TODO: A proper solution would probably be to have a set comparison that - // takes into account aliased characters, or uses a fully normalized set of - // chars. +static constexpr std::pair k_mapKeysymRemapping[]{ + // BUG: normalize to the same tab key. + // Sometimes when the keyboard comes online we'll receive key events as + // one of these two. We need to investigate why. For now lets just normalize + // to one of the two. + // TODO: A proper solution would probably be to have a set comparison that + // takes into account aliased characters, or uses a fully normalized set of + // chars. { XKB_KEY_ISO_Left_Tab, XKB_KEY_Tab }, { XKB_KEY_ISO_Enter, XKB_KEY_Return }, { XKB_KEY_Meta_L, XKB_KEY_Super_L }, @@ -73,8 +70,7 @@ static inline xkb_keysym_t NormalizeKeysymForHotkey( xkb_keysym_t uKeySym ) for ( auto [ uBadKey, uGoodKey ] : k_mapKeysymRemapping ) { - if ( uKeySym == uBadKey ) - uKeySym = uGoodKey; + if ( uKeySym == uBadKey ) uKeySym = uGoodKey; } return uKeySym; @@ -86,7 +82,7 @@ namespace gamescope::WaylandServer struct Keybind_t { std::unordered_set setKeySyms; - std::string sDebugName; + std::string sDebugName; }; /////////////////////////// @@ -95,33 +91,35 @@ namespace gamescope::WaylandServer class CGamescopeActionBinding : public CWaylandResource { public: - WL_PROTO_DEFINE( gamescope_action_binding, 1 ); + WL_PROTO_DEFINE( gamescope_action_binding, 1 ); - CGamescopeActionBinding( WaylandResourceDesc_t desc ) - : CWaylandResource( desc ) - { - s_Bindings.push_back( this ); - } + CGamescopeActionBinding( WaylandResourceDesc_t desc ) : + CWaylandResource( desc ) + { s_Bindings.push_back( this ); } - ~CGamescopeActionBinding() + ~CGamescopeActionBinding( ) { - std::erase_if( s_Bindings, [this]( CGamescopeActionBinding *pBinding ){ return pBinding == this; } ); + std::erase_if( + s_Bindings, + [ this ]( CGamescopeActionBinding *pBinding ) + { return pBinding == this; } ); } // gamescope_action_binding void SetDescription( const char *pszDescription ) - { - m_sDescription = pszDescription; - } + { m_sDescription = pszDescription; } void AddKeyboardTrigger( wl_array *pKeysymsArray ) { size_t zKeysymCount = pKeysymsArray->size / sizeof( xkb_keysym_t ); - std::span pKeysyms = std::span { - reinterpret_cast( pKeysymsArray->data ), - zKeysymCount }; + std::span pKeysyms = + std::span{ + reinterpret_cast( + pKeysymsArray->data ), + zKeysymCount + }; std::unordered_set setKeySyms; for ( xkb_keysym_t uKeySym : pKeysyms ) @@ -130,67 +128,81 @@ namespace gamescope::WaylandServer } std::string sTriggerDebugName = ComputeDebugName( setKeySyms ); - log_binding.infof( "(%s) -> Adding new trigger [%s].", m_sDescription.c_str(), sTriggerDebugName.c_str() ); - m_KeyboardTriggers.emplace_back( std::move( setKeySyms ), std::move( sTriggerDebugName ) ); + log_binding.infof( + "(%s) -> Adding new trigger [%s].", + m_sDescription.c_str( ), + sTriggerDebugName.c_str( ) ); + m_KeyboardTriggers.emplace_back( + std::move( setKeySyms ), std::move( sTriggerDebugName ) ); } - void ClearTriggers() + void ClearTriggers( ) { - log_binding.infof( "(%s) -> Cleared triggers.", m_sDescription.c_str() ); - m_KeyboardTriggers.clear(); + log_binding.infof( + "(%s) -> Cleared triggers.", m_sDescription.c_str( ) ); + m_KeyboardTriggers.clear( ); } void Arm( uint32_t uArmFlags ) { - log_binding.debugf( "(%s) -> Arming: %x.", m_sDescription.c_str(), uArmFlags ); + log_binding.debugf( + "(%s) -> Arming: %x.", m_sDescription.c_str( ), uArmFlags ); m_ouArmFlags = uArmFlags; } - void Disarm() + void Disarm( ) { - log_binding.debugf( "(%s) -> Disarming.", m_sDescription.c_str() ); + log_binding.debugf( "(%s) -> Disarming.", m_sDescription.c_str( ) ); m_ouArmFlags = std::nullopt; } // - bool IsArmed() { return m_ouArmFlags != std::nullopt; } - std::span GetKeyboardTriggers() { return m_KeyboardTriggers; } + bool IsArmed( ) { return m_ouArmFlags != std::nullopt; } + std::span GetKeyboardTriggers( ) + { return m_KeyboardTriggers; } - bool Execute() + bool Execute( ) { - if ( !IsArmed() ) - return false; + if ( !IsArmed( ) ) return false; uint32_t uArmFlags = *m_ouArmFlags; - bool bBlockInput = !!( uArmFlags & GAMESCOPE_ACTION_BINDING_ARM_FLAG_NO_BLOCK ); + bool bBlockInput = + !!( uArmFlags & GAMESCOPE_ACTION_BINDING_ARM_FLAG_NO_BLOCK ); - uint32_t uTriggerFlags = GAMESCOPE_ACTION_BINDING_TRIGGER_FLAG_KEYBOARD; + uint32_t uTriggerFlags = + GAMESCOPE_ACTION_BINDING_TRIGGER_FLAG_KEYBOARD; - uint64_t ulNow = get_time_in_nanos(); + uint64_t ulNow = get_time_in_nanos( ); static uint32_t s_uSequence = 0; uint32_t uTimeLo = static_cast( ulNow & 0xffffffff ); uint32_t uTimeHi = static_cast( ulNow >> 32 ); - log_binding.debugf( "(%s) -> Triggered!", m_sDescription.c_str() ); - gamescope_action_binding_send_triggered( GetResource(), s_uSequence++, uTriggerFlags, uTimeLo, uTimeHi ); + log_binding.debugf( "(%s) -> Triggered!", m_sDescription.c_str( ) ); + gamescope_action_binding_send_triggered( + GetResource( ), + s_uSequence++, + uTriggerFlags, + uTimeLo, + uTimeHi ); if ( uArmFlags & GAMESCOPE_ACTION_BINDING_ARM_FLAG_ONE_SHOT ) { - log_binding.debugf( "(%s) -> Disarming due to one-shot.", m_sDescription.c_str() ); - Disarm(); + log_binding.debugf( + "(%s) -> Disarming due to one-shot.", + m_sDescription.c_str( ) ); + Disarm( ); } return bBlockInput; } - static std::span GetBindings() - { - return s_Bindings; - } + static std::span GetBindings( ) + { return s_Bindings; } + private: - std::string m_sDescription; + std::string m_sDescription; std::vector m_KeyboardTriggers; std::optional m_ouArmFlags; @@ -198,37 +210,42 @@ namespace gamescope::WaylandServer static std::vector s_Bindings; }; - const struct gamescope_action_binding_interface CGamescopeActionBinding::Implementation = - { - .destroy = WL_PROTO_DESTROY(), - .set_description = WL_PROTO( CGamescopeActionBinding, SetDescription ), - .add_keyboard_trigger = WL_PROTO( CGamescopeActionBinding, AddKeyboardTrigger ), - .clear_triggers = WL_PROTO( CGamescopeActionBinding, ClearTriggers ), - .arm = WL_PROTO( CGamescopeActionBinding, Arm ), - .disarm = WL_PROTO( CGamescopeActionBinding, Disarm ), - }; + const struct gamescope_action_binding_interface + CGamescopeActionBinding::Implementation = { + .destroy = WL_PROTO_DESTROY( ), + .set_description = + WL_PROTO( CGamescopeActionBinding, SetDescription ), + .add_keyboard_trigger = + WL_PROTO( CGamescopeActionBinding, AddKeyboardTrigger ), + .clear_triggers = + WL_PROTO( CGamescopeActionBinding, ClearTriggers ), + .arm = WL_PROTO( CGamescopeActionBinding, Arm ), + .disarm = WL_PROTO( CGamescopeActionBinding, Disarm ), + }; std::vector CGamescopeActionBinding::s_Bindings; ////////////////////////////////// // CGamescopeActionBindingManager ////////////////////////////////// - class CGamescopeActionBindingManager : public CWaylandResource - { - public: - WL_PROTO_DEFINE( gamescope_action_binding_manager, 1 ); - WL_PROTO_DEFAULT_CONSTRUCTOR(); - - void CreateActionBinding( uint32_t uId ) - { - CWaylandResource::Create( m_pClient, m_uVersion, uId ); - } - }; - - const struct gamescope_action_binding_manager_interface CGamescopeActionBindingManager::Implementation = - { - .destroy = WL_PROTO_DESTROY(), - .create_action_binding = WL_PROTO( CGamescopeActionBindingManager, CreateActionBinding ), - }; + class CGamescopeActionBindingManager : public CWaylandResource + { + public: + WL_PROTO_DEFINE( gamescope_action_binding_manager, 1 ); + WL_PROTO_DEFAULT_CONSTRUCTOR( ); -} + void CreateActionBinding( uint32_t uId ) + { + CWaylandResource::Create( + m_pClient, m_uVersion, uId ); + } + }; + + const struct gamescope_action_binding_manager_interface + CGamescopeActionBindingManager::Implementation = { + .destroy = WL_PROTO_DESTROY( ), + .create_action_binding = + WL_PROTO( CGamescopeActionBindingManager, CreateActionBinding ), + }; + +} // namespace gamescope::WaylandServer diff --git a/src/WaylandServer/LinuxDrmSyncobj.h b/src/WaylandServer/LinuxDrmSyncobj.h index f7280a95ac..4ae02573c8 100644 --- a/src/WaylandServer/LinuxDrmSyncobj.h +++ b/src/WaylandServer/LinuxDrmSyncobj.h @@ -1,182 +1,191 @@ +#include "../Timeline.h" #include "WaylandProtocol.h" #include "WaylandServerLegacy.h" -#include "../Timeline.h" #include "linux-drm-syncobj-v1-protocol.h" namespace gamescope::WaylandServer { - class CLinuxDrmSyncobjTimeline : public CWaylandResource - { - public: - WL_PROTO_DEFINE( wp_linux_drm_syncobj_timeline_v1, 1 ); - - CLinuxDrmSyncobjTimeline( WaylandResourceDesc_t desc, std::shared_ptr pTimeline ) - : CWaylandResource( desc ) - , m_pTimeline{ std::move( pTimeline ) } - { - } - - std::shared_ptr GetTimeline() const - { - return m_pTimeline; - } - - private: - std::shared_ptr m_pTimeline; - }; - - const struct wp_linux_drm_syncobj_timeline_v1_interface CLinuxDrmSyncobjTimeline::Implementation = - { - .destroy = WL_PROTO_DESTROY(), - }; - - class CLinuxDrmSyncobjSurface : public CWaylandResource - { - public: - WL_PROTO_DEFINE( wp_linux_drm_syncobj_surface_v1, 1 ); - - CLinuxDrmSyncobjSurface( WaylandResourceDesc_t desc, wlserver_wl_surface_info *pWlSurfaceInfo ) - : CWaylandResource( desc ) - , m_pWlSurfaceInfo{ pWlSurfaceInfo } - { - } - - ~CLinuxDrmSyncobjSurface() - { - Detach(); - } - - bool HasExplicitSync() const - { - return m_pAcquireTimeline || m_pReleaseTimeline; - } - - std::shared_ptr ExtractAcquireTimelinePoint() const - { - if ( !m_pAcquireTimeline ) - return nullptr; - - return std::make_shared( m_pAcquireTimeline, m_ulAcquirePoint ); - } - - std::shared_ptr ExtractReleaseTimelinePoint() const - { - if ( !m_pReleaseTimeline ) - return nullptr; - - return std::make_shared( m_pReleaseTimeline, m_ulReleasePoint ); - } - - void Detach() - { - if ( m_pWlSurfaceInfo ) - { - assert( m_pWlSurfaceInfo->pSyncobjSurface == this ); - m_pWlSurfaceInfo->pSyncobjSurface = nullptr; - } - - m_pWlSurfaceInfo = nullptr; - } - - protected: - - void SetAcquirePoint( wl_resource *pWlTimeline, uint32_t uPointHi, uint32_t uPointLo ) - { - if ( !pWlTimeline ) - { - m_pAcquireTimeline = nullptr; - return; - } - - CLinuxDrmSyncobjTimeline *pTimeline = CWaylandResource::FromWlResource( pWlTimeline ); - m_pAcquireTimeline = pTimeline->GetTimeline(); - m_ulAcquirePoint = ToUint64( uPointHi, uPointLo ); - } - - void SetReleasePoint( wl_resource *pWlTimeline, uint32_t uPointHi, uint32_t uPointLo ) - { - if ( !pWlTimeline ) - { - m_pReleaseTimeline = nullptr; - return; - } - - CLinuxDrmSyncobjTimeline *pTimeline = CWaylandResource::FromWlResource( pWlTimeline ); - m_pReleaseTimeline = pTimeline->GetTimeline(); - m_ulReleasePoint = ToUint64( uPointHi, uPointLo ); - } - - private: - wlserver_wl_surface_info *m_pWlSurfaceInfo = nullptr; - - std::shared_ptr m_pAcquireTimeline; - uint64_t m_ulAcquirePoint = 0; - - std::shared_ptr m_pReleaseTimeline; - uint64_t m_ulReleasePoint = 0; - }; - - const struct wp_linux_drm_syncobj_surface_v1_interface CLinuxDrmSyncobjSurface::Implementation = - { - .destroy = WL_PROTO_DESTROY(), - .set_acquire_point = WL_PROTO( CLinuxDrmSyncobjSurface, SetAcquirePoint ), - .set_release_point = WL_PROTO( CLinuxDrmSyncobjSurface, SetReleasePoint ), - }; - - class CLinuxDrmSyncobjManager : public CWaylandResource - { - public: - WL_PROTO_DEFINE( wp_linux_drm_syncobj_manager_v1, 1 ); - WL_PROTO_DEFAULT_CONSTRUCTOR(); - - void GetSurface( uint32_t uId, wl_resource *pSurfaceResource ) - { - struct wlr_surface *pWlrSurface = wlr_surface_from_resource( pSurfaceResource ); - struct wlserver_wl_surface_info *pWlSurfaceInfo = get_wl_surface_info( pWlrSurface ); - if ( !pWlSurfaceInfo ) - { - wl_client_post_implementation_error( m_pClient, "No wl_surface" ); - return; - } - - if ( pWlSurfaceInfo->pSyncobjSurface ) - { - wl_resource_post_error( m_pResource, - WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_SURFACE_EXISTS, - "wp_linux_drm_syncobj_surface_v1 already created for this surface" ); - return; - } - - pWlSurfaceInfo->pSyncobjSurface = CWaylandResource::Create( m_pClient, m_uVersion, uId, pWlSurfaceInfo ); - } - - void ImportTimeline( uint32_t uId, int32_t nFd ) - { - // Transfer nFd to a new CTimeline. - std::shared_ptr pTimeline = std::make_shared( nFd ); - if ( !CheckAllocation( pTimeline ) ) - return; - - if ( !pTimeline->IsValid() ) + class CLinuxDrmSyncobjTimeline : public CWaylandResource + { + public: + WL_PROTO_DEFINE( wp_linux_drm_syncobj_timeline_v1, 1 ); + + CLinuxDrmSyncobjTimeline( + WaylandResourceDesc_t desc, std::shared_ptr pTimeline ) : + CWaylandResource( desc ), m_pTimeline{ std::move( pTimeline ) } + {} + + std::shared_ptr GetTimeline( ) const { return m_pTimeline; } + + private: + std::shared_ptr m_pTimeline; + }; + + const struct wp_linux_drm_syncobj_timeline_v1_interface + CLinuxDrmSyncobjTimeline::Implementation = { + .destroy = WL_PROTO_DESTROY( ), + }; + + class CLinuxDrmSyncobjSurface : public CWaylandResource + { + public: + WL_PROTO_DEFINE( wp_linux_drm_syncobj_surface_v1, 1 ); + + CLinuxDrmSyncobjSurface( + WaylandResourceDesc_t desc, + wlserver_wl_surface_info *pWlSurfaceInfo ) : + CWaylandResource( desc ), m_pWlSurfaceInfo{ pWlSurfaceInfo } + {} + + ~CLinuxDrmSyncobjSurface( ) { Detach( ); } + + bool HasExplicitSync( ) const + { return m_pAcquireTimeline || m_pReleaseTimeline; } + + std::shared_ptr + ExtractAcquireTimelinePoint( ) const + { + if ( !m_pAcquireTimeline ) return nullptr; + + return std::make_shared( + m_pAcquireTimeline, m_ulAcquirePoint ); + } + + std::shared_ptr + ExtractReleaseTimelinePoint( ) const + { + if ( !m_pReleaseTimeline ) return nullptr; + + return std::make_shared( + m_pReleaseTimeline, m_ulReleasePoint ); + } + + void Detach( ) + { + if ( m_pWlSurfaceInfo ) + { + assert( m_pWlSurfaceInfo->pSyncobjSurface == this ); + m_pWlSurfaceInfo->pSyncobjSurface = nullptr; + } + + m_pWlSurfaceInfo = nullptr; + } + + protected: + void SetAcquirePoint( + wl_resource *pWlTimeline, uint32_t uPointHi, uint32_t uPointLo ) + { + if ( !pWlTimeline ) { - wl_resource_post_error( m_pResource, + m_pAcquireTimeline = nullptr; + return; + } + + CLinuxDrmSyncobjTimeline *pTimeline = + CWaylandResource::FromWlResource( + pWlTimeline ); + m_pAcquireTimeline = pTimeline->GetTimeline( ); + m_ulAcquirePoint = ToUint64( uPointHi, uPointLo ); + } + + void SetReleasePoint( + wl_resource *pWlTimeline, uint32_t uPointHi, uint32_t uPointLo ) + { + if ( !pWlTimeline ) + { + m_pReleaseTimeline = nullptr; + return; + } + + CLinuxDrmSyncobjTimeline *pTimeline = + CWaylandResource::FromWlResource( + pWlTimeline ); + m_pReleaseTimeline = pTimeline->GetTimeline( ); + m_ulReleasePoint = ToUint64( uPointHi, uPointLo ); + } + + private: + wlserver_wl_surface_info *m_pWlSurfaceInfo = nullptr; + + std::shared_ptr m_pAcquireTimeline; + uint64_t m_ulAcquirePoint = 0; + + std::shared_ptr m_pReleaseTimeline; + uint64_t m_ulReleasePoint = 0; + }; + + const struct wp_linux_drm_syncobj_surface_v1_interface + CLinuxDrmSyncobjSurface::Implementation = { + .destroy = WL_PROTO_DESTROY( ), + .set_acquire_point = + WL_PROTO( CLinuxDrmSyncobjSurface, SetAcquirePoint ), + .set_release_point = + WL_PROTO( CLinuxDrmSyncobjSurface, SetReleasePoint ), + }; + + class CLinuxDrmSyncobjManager : public CWaylandResource + { + public: + WL_PROTO_DEFINE( wp_linux_drm_syncobj_manager_v1, 1 ); + WL_PROTO_DEFAULT_CONSTRUCTOR( ); + + void GetSurface( uint32_t uId, wl_resource *pSurfaceResource ) + { + struct wlr_surface *pWlrSurface = + wlr_surface_from_resource( pSurfaceResource ); + struct wlserver_wl_surface_info *pWlSurfaceInfo = + get_wl_surface_info( pWlrSurface ); + if ( !pWlSurfaceInfo ) + { + wl_client_post_implementation_error( + m_pClient, "No wl_surface" ); + return; + } + + if ( pWlSurfaceInfo->pSyncobjSurface ) + { + wl_resource_post_error( + m_pResource, + WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_SURFACE_EXISTS, + "wp_linux_drm_syncobj_surface_v1 already created for this " + "surface" ); + return; + } + + pWlSurfaceInfo->pSyncobjSurface = + CWaylandResource::Create( + m_pClient, m_uVersion, uId, pWlSurfaceInfo ); + } + + void ImportTimeline( uint32_t uId, int32_t nFd ) + { + // Transfer nFd to a new CTimeline. + std::shared_ptr pTimeline = + std::make_shared( nFd ); + if ( !CheckAllocation( pTimeline ) ) return; + + if ( !pTimeline->IsValid( ) ) + { + wl_resource_post_error( + m_pResource, WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_INVALID_TIMELINE, "Failed to import syncobj timeline" ); return; } - CWaylandResource::Create( m_pClient, m_uVersion, uId, std::move( pTimeline ) ); - } - - }; + CWaylandResource::Create( + m_pClient, m_uVersion, uId, std::move( pTimeline ) ); + } + }; - const struct wp_linux_drm_syncobj_manager_v1_interface CLinuxDrmSyncobjManager::Implementation = - { - .destroy = WL_PROTO_DESTROY(), - .get_surface = WL_PROTO( CLinuxDrmSyncobjManager, GetSurface ), - .import_timeline = WL_PROTO( CLinuxDrmSyncobjManager, ImportTimeline ), - }; + const struct wp_linux_drm_syncobj_manager_v1_interface + CLinuxDrmSyncobjManager::Implementation = { + .destroy = WL_PROTO_DESTROY( ), + .get_surface = WL_PROTO( CLinuxDrmSyncobjManager, GetSurface ), + .import_timeline = + WL_PROTO( CLinuxDrmSyncobjManager, ImportTimeline ), + }; -} +} // namespace gamescope::WaylandServer diff --git a/src/WaylandServer/Reshade.h b/src/WaylandServer/Reshade.h index 39e638c164..43da70a112 100644 --- a/src/WaylandServer/Reshade.h +++ b/src/WaylandServer/Reshade.h @@ -9,50 +9,41 @@ namespace gamescope::WaylandServer { - class CReshadeManager : public CWaylandResource - { - public: - WL_PROTO_DEFINE( gamescope_reshade, 1 ); - WL_PROTO_DEFAULT_CONSTRUCTOR(); - - void EffectReadyCallback( const char* path ) - { - gamescope_reshade_send_effect_ready(GetResource(), path); - } - - void SetEffect( const char *path ) - { - reshade_effect_manager_set_effect( path, - [this]( const char* callbackPath ) { EffectReadyCallback( callbackPath ); } - ); - } - - void EnableEffect() - { - reshade_effect_manager_enable_effect(); - } - - void SetUniformVariable( const char *key, struct wl_array *value ) - { - uint8_t* data_copy = new uint8_t[value->size]; - std::memcpy(data_copy, value->data, value->size); - reshade_effect_manager_set_uniform_variable( key, data_copy ); - } - - void DisableEffect() - { - reshade_effect_manager_disable_effect(); - } - - }; - - const struct gamescope_reshade_interface CReshadeManager::Implementation = - { - .destroy = WL_PROTO_DESTROY(), - .set_effect = WL_PROTO( CReshadeManager, SetEffect ), - .enable_effect = WL_PROTO( CReshadeManager, EnableEffect ), - .set_uniform_variable = WL_PROTO( CReshadeManager, SetUniformVariable ), - .disable_effect = WL_PROTO( CReshadeManager, DisableEffect ) - }; - -} + class CReshadeManager : public CWaylandResource + { + public: + WL_PROTO_DEFINE( gamescope_reshade, 1 ); + WL_PROTO_DEFAULT_CONSTRUCTOR( ); + + void EffectReadyCallback( const char *path ) + { gamescope_reshade_send_effect_ready( GetResource( ), path ); } + + void SetEffect( const char *path ) + { + reshade_effect_manager_set_effect( + path, + [ this ]( const char *callbackPath ) + { EffectReadyCallback( callbackPath ); } ); + } + + void EnableEffect( ) { reshade_effect_manager_enable_effect( ); } + + void SetUniformVariable( const char *key, struct wl_array *value ) + { + uint8_t *data_copy = new uint8_t[ value->size ]; + std::memcpy( data_copy, value->data, value->size ); + reshade_effect_manager_set_uniform_variable( key, data_copy ); + } + + void DisableEffect( ) { reshade_effect_manager_disable_effect( ); } + }; + + const struct gamescope_reshade_interface CReshadeManager::Implementation = { + .destroy = WL_PROTO_DESTROY( ), + .set_effect = WL_PROTO( CReshadeManager, SetEffect ), + .enable_effect = WL_PROTO( CReshadeManager, EnableEffect ), + .set_uniform_variable = WL_PROTO( CReshadeManager, SetUniformVariable ), + .disable_effect = WL_PROTO( CReshadeManager, DisableEffect ) + }; + +} // namespace gamescope::WaylandServer diff --git a/src/WaylandServer/WaylandDecls.h b/src/WaylandServer/WaylandDecls.h index e43623aa6a..75272a6581 100644 --- a/src/WaylandServer/WaylandDecls.h +++ b/src/WaylandServer/WaylandDecls.h @@ -4,8 +4,7 @@ namespace gamescope::WaylandServer { class CWaylandResource; - template - class CWaylandProtocol; + template class CWaylandProtocol; class CLinuxDrmSyncobjManager; class CLinuxDrmSyncobjSurface; @@ -16,6 +15,7 @@ namespace gamescope::WaylandServer using CReshade = CWaylandProtocol; class CGamescopeActionBindingManager; - using CGamescopeActionBindingProtocol = CWaylandProtocol; + using CGamescopeActionBindingProtocol = + CWaylandProtocol; -} +} // namespace gamescope::WaylandServer diff --git a/src/WaylandServer/WaylandProtocol.h b/src/WaylandServer/WaylandProtocol.h index c7146a1932..35a86d7c20 100644 --- a/src/WaylandServer/WaylandProtocol.h +++ b/src/WaylandServer/WaylandProtocol.h @@ -1,43 +1,44 @@ #pragma once -#include "WaylandResource.h" #include +#include "WaylandResource.h" namespace gamescope::WaylandServer { - template - class CWaylandProtocol : public NonCopyable - { - public: - CWaylandProtocol( wl_display *pDisplay ) - : m_pDisplay{ pDisplay } - { - ( CreateGlobal(), ... ); - } - private: - - template - void CreateGlobal() + template class CWaylandProtocol : public NonCopyable + { + public: + CWaylandProtocol( wl_display *pDisplay ) : m_pDisplay{ pDisplay } + { ( CreateGlobal( ), ... ); } + + private: + template void CreateGlobal( ) { - wl_global *pGlobal = wl_global_create( m_pDisplay, T::Interface, T::Version, this, - []( struct wl_client *pClient, void *pData, uint32_t uVersion, uint32_t uId ) - { - CWaylandProtocol *pProtocol = reinterpret_cast( pData ); - pProtocol->Bind( pClient, uVersion, uId ); - } ); + wl_global *pGlobal = wl_global_create( + m_pDisplay, + T::Interface, + T::Version, + this, + []( struct wl_client *pClient, + void *pData, + uint32_t uVersion, + uint32_t uId ) + { + CWaylandProtocol *pProtocol = + reinterpret_cast( pData ); + pProtocol->Bind( pClient, uVersion, uId ); + } ); m_pGlobals.emplace_back( pGlobal ); } - template - void Bind( struct wl_client *pClient, uint32_t uVersion, uint32_t uId ) - { - CWaylandResource::Create( pClient, uVersion, uId ); - } + template + void Bind( struct wl_client *pClient, uint32_t uVersion, uint32_t uId ) + { CWaylandResource::Create( pClient, uVersion, uId ); } - wl_display *m_pDisplay = nullptr; + wl_display *m_pDisplay = nullptr; std::vector m_pGlobals; - }; + }; -} +} // namespace gamescope::WaylandServer diff --git a/src/WaylandServer/WaylandResource.h b/src/WaylandServer/WaylandResource.h index 0a5994a0c2..aa6790a9e9 100644 --- a/src/WaylandServer/WaylandResource.h +++ b/src/WaylandServer/WaylandResource.h @@ -7,109 +7,109 @@ namespace gamescope::WaylandServer { - #define WL_PROTO_NULL() [] ( Args... args ) { } - #define WL_PROTO_DESTROY() [] ( wl_client *pClient, wl_resource *pResource, Args... args ) { wl_resource_destroy( pResource ); } - #define WL_PROTO( type, name ) \ - [] ( wl_client *pClient, wl_resource *pResource, Args... args ) \ - { \ - type *pThing = reinterpret_cast( wl_resource_get_user_data( pResource ) ); \ - pThing->name( std::forward(args)... ); \ - } - - #define WL_PROTO_DEFINE( type, version ) \ - static constexpr uint32_t Version = version; \ - static constexpr const struct wl_interface *Interface = & type##_interface; \ - static const struct type##_interface Implementation; - - #define WL_PROTO_DEFAULT_CONSTRUCTOR() \ - using CWaylandResource::CWaylandResource; - - struct WaylandResourceDesc_t - { - wl_client *pClient; - wl_resource *pResource; - uint32_t uVersion; - }; - - class CWaylandResource : public NonCopyable - { - public: - - CWaylandResource( WaylandResourceDesc_t desc ) - : m_pClient{ desc.pClient } - , m_pResource{ desc.pResource } - , m_uVersion{ desc.uVersion } - { - } - - ~CWaylandResource() - { - } - - template - static bool CheckAllocation( const T &object, wl_client *pClient ) - { - if ( !object ) - { - wl_client_post_no_memory( pClient ); - return false; - } - - return true; - } - - template - bool CheckAllocation( const T &object ) - { - return CheckAllocation( object, m_pClient ); - } - - template - static T *FromWlResource( wl_resource *pResource ) - { - T *pObject = reinterpret_cast( wl_resource_get_user_data( pResource ) ); - return pObject; - } - - template - static T *Create( wl_client *pClient, uint32_t uVersion, uint32_t uId, Args... args ) - { - wl_resource *pResource = wl_resource_create( pClient, T::Interface, uVersion, uId ); - if ( !CheckAllocation( pResource, pClient ) ) - return nullptr; - - WaylandResourceDesc_t desc = - { - .pClient = pClient, - .pResource = pResource, - .uVersion = uVersion, - }; - T *pThing = new T{ desc, std::forward(args)... }; - if ( !CheckAllocation( pThing, pClient ) ) - return nullptr; - - wl_resource_set_implementation( pResource, &T::Implementation, pThing, - []( wl_resource *pResource ) - { - T *pObject = CWaylandResource::FromWlResource( pResource ); - delete pObject; - }); - - return pThing; - } - - static uint64_t ToUint64( uint32_t uHi, uint32_t uLo ) - { - return (uint64_t(uHi) << 32) | uLo; - } - - wl_client *GetClient() const { return m_pClient; } - wl_resource *GetResource() const { return m_pResource; } - uint32_t GetVersion() const { return m_uVersion; } - protected: - wl_client *m_pClient = nullptr; - wl_resource *m_pResource = nullptr; - uint32_t m_uVersion = 0; - }; - -} +#define WL_PROTO_NULL( ) []( Args... args ) {} +#define WL_PROTO_DESTROY( ) \ + []( \ + wl_client *pClient, wl_resource *pResource, Args... args ) \ + { wl_resource_destroy( pResource ); } +#define WL_PROTO( type, name ) \ + []( \ + wl_client *pClient, wl_resource *pResource, Args... args ) \ + { \ + type *pThing = reinterpret_cast( \ + wl_resource_get_user_data( pResource ) ); \ + pThing->name( std::forward( args )... ); \ + } + +#define WL_PROTO_DEFINE( type, version ) \ + static constexpr uint32_t Version = version; \ + static constexpr const struct wl_interface *Interface = &type##_interface; \ + static const struct type##_interface Implementation; + +#define WL_PROTO_DEFAULT_CONSTRUCTOR( ) \ + using CWaylandResource::CWaylandResource; + + struct WaylandResourceDesc_t + { + wl_client *pClient; + wl_resource *pResource; + uint32_t uVersion; + }; + + class CWaylandResource : public NonCopyable + { + public: + CWaylandResource( WaylandResourceDesc_t desc ) : + m_pClient{ desc.pClient }, + m_pResource{ desc.pResource }, + m_uVersion{ desc.uVersion } + {} + + ~CWaylandResource( ) {} + + template + static bool CheckAllocation( const T &object, wl_client *pClient ) + { + if ( !object ) + { + wl_client_post_no_memory( pClient ); + return false; + } + + return true; + } + + template bool CheckAllocation( const T &object ) + { return CheckAllocation( object, m_pClient ); } + + template static T *FromWlResource( wl_resource *pResource ) + { + T *pObject = + reinterpret_cast( wl_resource_get_user_data( pResource ) ); + return pObject; + } + + template + static T *Create( + wl_client *pClient, uint32_t uVersion, uint32_t uId, Args... args ) + { + wl_resource *pResource = + wl_resource_create( pClient, T::Interface, uVersion, uId ); + if ( !CheckAllocation( pResource, pClient ) ) return nullptr; + + WaylandResourceDesc_t desc = { + .pClient = pClient, + .pResource = pResource, + .uVersion = uVersion, + }; + T *pThing = new T{ desc, std::forward( args )... }; + if ( !CheckAllocation( pThing, pClient ) ) return nullptr; + + wl_resource_set_implementation( + pResource, + &T::Implementation, + pThing, + []( wl_resource *pResource ) + { + T *pObject = + CWaylandResource::FromWlResource( pResource ); + delete pObject; + } ); + + return pThing; + } + + static uint64_t ToUint64( uint32_t uHi, uint32_t uLo ) + { return ( uint64_t( uHi ) << 32 ) | uLo; } + + wl_client *GetClient( ) const { return m_pClient; } + wl_resource *GetResource( ) const { return m_pResource; } + uint32_t GetVersion( ) const { return m_uVersion; } + + protected: + wl_client *m_pClient = nullptr; + wl_resource *m_pResource = nullptr; + uint32_t m_uVersion = 0; + }; + +} // namespace gamescope::WaylandServer diff --git a/src/WaylandServer/WaylandServerLegacy.h b/src/WaylandServer/WaylandServerLegacy.h index 63ee2ca17e..199a2b5014 100644 --- a/src/WaylandServer/WaylandServerLegacy.h +++ b/src/WaylandServer/WaylandServerLegacy.h @@ -1,18 +1,16 @@ #pragma once -#include -#include "WaylandDecls.h" #include #include #include +#include +#include "WaylandDecls.h" #include "vulkan_include.h" - -#include "wlr_begin.hpp" #include +#include "wlr_begin.hpp" #include "wlr_end.hpp" - namespace gamescope { class BackendBlob; @@ -23,40 +21,40 @@ struct wlserver_xdg_surface_info; struct wlserver_vk_swapchain_feedback { - uint32_t image_count; - VkFormat vk_format; - VkColorSpaceKHR vk_colorspace; - VkCompositeAlphaFlagBitsKHR vk_composite_alpha; - VkSurfaceTransformFlagBitsKHR vk_pre_transform; - VkBool32 vk_clipped; - std::shared_ptr vk_engine_name; - - std::shared_ptr hdr_metadata_blob; + uint32_t image_count; + VkFormat vk_format; + VkColorSpaceKHR vk_colorspace; + VkCompositeAlphaFlagBitsKHR vk_composite_alpha; + VkSurfaceTransformFlagBitsKHR vk_pre_transform; + VkBool32 vk_clipped; + std::shared_ptr vk_engine_name; + + std::shared_ptr hdr_metadata_blob; }; - struct wlserver_wl_surface_info { - wlserver_x11_surface_info *x11_surface = nullptr; - wlserver_xdg_surface_info *xdg_surface = nullptr; + wlserver_x11_surface_info *x11_surface = nullptr; + wlserver_xdg_surface_info *xdg_surface = nullptr; - gamescope::WaylandServer::CLinuxDrmSyncobjSurface *pSyncobjSurface = nullptr; + gamescope::WaylandServer::CLinuxDrmSyncobjSurface *pSyncobjSurface = + nullptr; - struct wlr_surface *wlr = nullptr; - struct wl_listener commit; - struct wl_listener destroy; + struct wlr_surface *wlr = nullptr; + struct wl_listener commit; + struct wl_listener destroy; - std::shared_ptr swapchain_feedback = {}; - std::optional oCurrentPresentMode; + std::shared_ptr swapchain_feedback = {}; + std::optional oCurrentPresentMode; - uint64_t sequence = 0; - std::vector pending_presentation_feedbacks; + uint64_t sequence = 0; + std::vector pending_presentation_feedbacks; - std::vector gamescope_swapchains; - std::optional present_id = std::nullopt; - uint64_t desired_present_time = 0; + std::vector gamescope_swapchains; + std::optional present_id = std::nullopt; + uint64_t desired_present_time = 0; - uint64_t last_refresh_cycle = 0; + uint64_t last_refresh_cycle = 0; }; -wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf); +wlserver_wl_surface_info *get_wl_surface_info( struct wlr_surface *wlr_surf ); diff --git a/src/backend.cpp b/src/backend.cpp index 98e7576561..c7f7226602 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -1,23 +1,29 @@ #include "backend.h" #include "Backends/DeferredBackend.h" -#include "vblankmanager.hpp" #include "convar.h" +#include "vblankmanager.hpp" #include "wlserver.hpp" -#include "wlr_begin.hpp" #include +#include "wlr_begin.hpp" #include "wlr_end.hpp" -extern void sleep_until_nanos(uint64_t nanos); -extern bool env_to_bool(const char *env); +extern void sleep_until_nanos( uint64_t nanos ); +extern bool env_to_bool( const char *env ); extern bool g_bAllowDeferredBackend; namespace gamescope { - ConVar cv_backend( "backend", "auto", "Override the backend selection (auto-detected or specified on the command line)." ); + ConVar cv_backend( + "backend", + "auto", + "Override the backend selection (auto-detected or specified on the " + "command line)." ); - ConVar cv_backend_virtual_connector_strategy( "backend_virtual_connector_strategy", VirtualConnectorStrategies::SingleApplication ); + ConVar cv_backend_virtual_connector_strategy( + "backend_virtual_connector_strategy", + VirtualConnectorStrategies::SingleApplication ); ///////////// // IBackend @@ -25,10 +31,7 @@ namespace gamescope static IBackend *s_pBackend = nullptr; - IBackend *IBackend::Get() - { - return s_pBackend; - } + IBackend *IBackend::Get( ) { return s_pBackend; } bool IBackend::Set( IBackend *pBackend ) { @@ -41,12 +44,12 @@ namespace gamescope if ( pBackend ) { s_pBackend = pBackend; - if ( !s_pBackend->Init() ) + if ( !s_pBackend->Init( ) ) { if ( g_bAllowDeferredBackend ) { s_pBackend = new CDeferredBackend( pBackend ); - if ( !s_pBackend->Init() ) + if ( !s_pBackend->Init( ) ) { delete s_pBackend; s_pBackend = nullptr; @@ -69,59 +72,56 @@ namespace gamescope // CBaseBackendFb ///////////////// - CBaseBackendFb::CBaseBackendFb() - { - } + CBaseBackendFb::CBaseBackendFb( ) {} - CBaseBackendFb::~CBaseBackendFb() + CBaseBackendFb::~CBaseBackendFb( ) { // I do not own the client buffer, but I released that in DecRef. - //assert( !HasLiveReferences() ); + // assert( !HasLiveReferences() ); } - uint32_t CBaseBackendFb::IncRef() + uint32_t CBaseBackendFb::IncRef( ) { - uint32_t uRefCount = IBackendFb::IncRef(); + uint32_t uRefCount = IBackendFb::IncRef( ); if ( m_pClientBuffer && !uRefCount ) { - wlserver_lock(); + wlserver_lock( ); wlr_buffer_lock( m_pClientBuffer ); wlserver_unlock( false ); } return uRefCount; } - uint32_t CBaseBackendFb::DecRef() + uint32_t CBaseBackendFb::DecRef( ) { wlr_buffer *pClientBuffer = m_pClientBuffer; - std::shared_ptr pReleasePoint = std::move( m_pReleasePoint ); + std::shared_ptr pReleasePoint = + std::move( m_pReleasePoint ); m_pReleasePoint = nullptr; - uint32_t uRefCount = IBackendFb::DecRef(); + uint32_t uRefCount = IBackendFb::DecRef( ); if ( uRefCount ) { - if ( pReleasePoint ) - m_pReleasePoint = std::move( pReleasePoint ); + if ( pReleasePoint ) m_pReleasePoint = std::move( pReleasePoint ); } else if ( pClientBuffer ) { - wlserver_lock(); + wlserver_lock( ); wlr_buffer_unlock( pClientBuffer ); - wlserver_unlock(); + wlserver_unlock( ); } return uRefCount; } void CBaseBackendFb::SetBuffer( wlr_buffer *pClientBuffer ) { - if ( m_pClientBuffer == pClientBuffer ) - return; + if ( m_pClientBuffer == pClientBuffer ) return; assert( m_pClientBuffer == nullptr ); m_pClientBuffer = pClientBuffer; - if ( GetRefCount() ) + if ( GetRefCount( ) ) { - wlserver_lock(); + wlserver_lock( ); wlr_buffer_lock( m_pClientBuffer ); wlserver_unlock( false ); } @@ -129,15 +129,16 @@ namespace gamescope m_pReleasePoint = nullptr; } - void CBaseBackendFb::SetReleasePoint( std::shared_ptr pReleasePoint ) + void CBaseBackendFb::SetReleasePoint( + std::shared_ptr pReleasePoint ) { m_pReleasePoint = pReleasePoint; - if ( m_pClientBuffer && GetRefCount() ) + if ( m_pClientBuffer && GetRefCount( ) ) { - wlserver_lock(); + wlserver_lock( ); wlr_buffer_unlock( m_pClientBuffer ); - wlserver_unlock(); + wlserver_unlock( ); m_pClientBuffer = nullptr; } } @@ -146,9 +147,10 @@ namespace gamescope // CBaseBackendConnector ///////////////////////// - VBlankScheduleTime CBaseBackendConnector::FrameSync() + VBlankScheduleTime CBaseBackendConnector::FrameSync( ) { - VBlankScheduleTime schedule = GetVBlankTimer().CalcNextWakeupTime( false ); + VBlankScheduleTime schedule = + GetVBlankTimer( ).CalcNextWakeupTime( false ); sleep_until_nanos( schedule.ulScheduledWakeupPoint ); return schedule; } @@ -157,70 +159,115 @@ namespace gamescope // CBaseBackend ///////////////// - bool CBaseBackend::NeedsFrameSync() const + bool CBaseBackend::NeedsFrameSync( ) const { - const bool bForceTimerFd = env_to_bool( getenv( "GAMESCOPE_DISABLE_TIMERFD" ) ); + const bool bForceTimerFd = + env_to_bool( getenv( "GAMESCOPE_DISABLE_TIMERFD" ) ); return bForceTimerFd; } - ConVar cv_touch_external_display_trackpad( "touch_external_display_trackpad", false, "If we are using an external display, should we treat the internal display's touch as a trackpad insteaad?" ); - ConVar cv_touch_click_mode( "touch_click_mode", TouchClickModes::Left, "The default action to perform on touch." ); - TouchClickMode CBaseBackend::GetTouchClickMode() + ConVar cv_touch_external_display_trackpad( + "touch_external_display_trackpad", + false, + "If we are using an external display, should we treat the internal " + "display's touch as a trackpad insteaad?" ); + ConVar cv_touch_click_mode( + "touch_click_mode", + TouchClickModes::Left, + "The default action to perform on touch." ); + TouchClickMode CBaseBackend::GetTouchClickMode( ) { - if ( cv_touch_external_display_trackpad && this->GetCurrentConnector() ) + if ( cv_touch_external_display_trackpad && + this->GetCurrentConnector( ) ) { - gamescope::GamescopeScreenType screenType = this->GetCurrentConnector()->GetScreenType(); - if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && cv_touch_click_mode == TouchClickMode::Passthrough ) + gamescope::GamescopeScreenType screenType = + this->GetCurrentConnector( )->GetScreenType( ); + if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && + cv_touch_click_mode == TouchClickMode::Passthrough ) return TouchClickMode::Trackpad; } return cv_touch_click_mode; } - void CBaseBackend::DumpDebugInfo() + void CBaseBackend::DumpDebugInfo( ) { - console_log.infof( "Uses Modifiers: %s", this->UsesModifiers() ? "true" : "false" ); - console_log.infof( "Supports Plane Hardware Cursor: %s (not relevant for nested backends)", this->SupportsPlaneHardwareCursor() ? "true" : "false" ); - console_log.infof( "Supports Tearing: %s", this->SupportsTearing() ? "true" : "false" ); - console_log.infof( "Uses Vulkan Swapchain: %s", this->UsesVulkanSwapchain() ? "true" : "false" ); - console_log.infof( "Is Session Based: %s", this->IsSessionBased() ? "true" : "false" ); - console_log.infof( "Supports Explicit Sync: %s", this->SupportsExplicitSync() ? "true" : "false" ); - console_log.infof( "Current Screen Type: %s", this->GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL ? "Internal" : "External" ); - console_log.infof( "Is Visible: %s", this->IsVisible() ? "true" : "false" ); - console_log.infof( "Is Paused: %s", this->IsPaused() ? "true" : "false" ); - console_log.infof( "Needs Frame Sync: %s", this->NeedsFrameSync() ? "true" : "false" ); - console_log.infof( "VRR Active: %s", this->GetCurrentConnector()->IsVRRActive() ? "true" : "false" ); - console_log.infof( "Total Presents Queued: %lu", this->GetCurrentConnector()->PresentationFeedback().TotalPresentsQueued() ); - console_log.infof( "Total Presents Completed: %lu", this->GetCurrentConnector()->PresentationFeedback().TotalPresentsCompleted() ); - console_log.infof( "Current Presents In Flight: %lu", this->GetCurrentConnector()->PresentationFeedback().CurrentPresentsInFlight() ); + console_log.infof( + "Uses Modifiers: %s", this->UsesModifiers( ) ? "true" : "false" ); + console_log.infof( + "Supports Plane Hardware Cursor: %s (not relevant for nested " + "backends)", + this->SupportsPlaneHardwareCursor( ) ? "true" : "false" ); + console_log.infof( + "Supports Tearing: %s", + this->SupportsTearing( ) ? "true" : "false" ); + console_log.infof( + "Uses Vulkan Swapchain: %s", + this->UsesVulkanSwapchain( ) ? "true" : "false" ); + console_log.infof( + "Is Session Based: %s", + this->IsSessionBased( ) ? "true" : "false" ); + console_log.infof( + "Supports Explicit Sync: %s", + this->SupportsExplicitSync( ) ? "true" : "false" ); + console_log.infof( + "Current Screen Type: %s", + this->GetScreenType( ) == GAMESCOPE_SCREEN_TYPE_INTERNAL + ? "Internal" + : "External" ); + console_log.infof( + "Is Visible: %s", this->IsVisible( ) ? "true" : "false" ); + console_log.infof( + "Is Paused: %s", this->IsPaused( ) ? "true" : "false" ); + console_log.infof( + "Needs Frame Sync: %s", + this->NeedsFrameSync( ) ? "true" : "false" ); + console_log.infof( + "VRR Active: %s", + this->GetCurrentConnector( )->IsVRRActive( ) ? "true" : "false" ); + console_log.infof( + "Total Presents Queued: %lu", + this->GetCurrentConnector( ) + ->PresentationFeedback( ) + .TotalPresentsQueued( ) ); + console_log.infof( + "Total Presents Completed: %lu", + this->GetCurrentConnector( ) + ->PresentationFeedback( ) + .TotalPresentsCompleted( ) ); + console_log.infof( + "Current Presents In Flight: %lu", + this->GetCurrentConnector( ) + ->PresentationFeedback( ) + .CurrentPresentsInFlight( ) ); } - bool CBaseBackend::UsesVirtualConnectors() - { - return false; - } - std::shared_ptr CBaseBackend::CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) + bool CBaseBackend::UsesVirtualConnectors( ) { return false; } + std::shared_ptr + CBaseBackend::CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) { assert( false ); return nullptr; } - ConCommand cc_backend_info( "backend_info", "Dump debug info about the backend state", - []( std::span svArgs ) - { - if ( !GetBackend() ) - return; + ConCommand cc_backend_info( + "backend_info", + "Dump debug info about the backend state", + []( std::span svArgs ) + { + if ( !GetBackend( ) ) return; - GetBackend()->DumpDebugInfo(); - }); + GetBackend( )->DumpDebugInfo( ); + } ); - ConCommand cc_backend_set_dirty( "backend_set_dirty", "Dirty the backend state and re-poll", - []( std::span svArgs ) - { - if ( !GetBackend() ) - return; + ConCommand cc_backend_set_dirty( + "backend_set_dirty", + "Dirty the backend state and re-poll", + []( std::span svArgs ) + { + if ( !GetBackend( ) ) return; - GetBackend()->DirtyState( true, true ); - }); + GetBackend( )->DirtyState( true, true ); + } ); -} +} // namespace gamescope diff --git a/src/backend.h b/src/backend.h index 880aa97240..8b928722e2 100644 --- a/src/backend.h +++ b/src/backend.h @@ -1,22 +1,22 @@ #pragma once -#include "color_helpers.h" -#include "gamescope_shared.h" -#include "vulkan_include.h" #include "Timeline.h" +#include "Utils/Algorithm.h" +#include "color_helpers.h" #include "convar.h" -#include "rc.h" #include "drm_include.h" -#include "Utils/Algorithm.h" +#include "gamescope_shared.h" +#include "rc.h" +#include "vulkan_include.h" +#include +#include #include -#include -#include #include #include -#include +#include #include -#include +#include struct wlr_buffer; struct wlr_dmabuf_attributes; @@ -45,48 +45,60 @@ namespace gamescope Count, }; } - using VirtualConnectorStrategy = VirtualConnectorStrategies::VirtualConnectorStrategy; + using VirtualConnectorStrategy = + VirtualConnectorStrategies::VirtualConnectorStrategy; using VirtualConnectorKey_t = uint64_t; - extern ConVar cv_backend_virtual_connector_strategy; + extern ConVar + cv_backend_virtual_connector_strategy; - static constexpr bool VirtualConnectorStrategyIsSingleOutput( VirtualConnectorStrategy eStrategy ) + static constexpr bool + VirtualConnectorStrategyIsSingleOutput( VirtualConnectorStrategy eStrategy ) { - return eStrategy == VirtualConnectorStrategies::SingleApplication || eStrategy == VirtualConnectorStrategies::SteamControlled; + return eStrategy == VirtualConnectorStrategies::SingleApplication || + eStrategy == VirtualConnectorStrategies::SteamControlled; } - static inline bool VirtualConnectorIsSingleOutput() + static inline bool VirtualConnectorIsSingleOutput( ) { - return VirtualConnectorStrategyIsSingleOutput( cv_backend_virtual_connector_strategy ); + return VirtualConnectorStrategyIsSingleOutput( + cv_backend_virtual_connector_strategy ); } - static inline bool VirtualConnectorInSteamPerAppState() + static inline bool VirtualConnectorInSteamPerAppState( ) { - return steamMode && cv_backend_virtual_connector_strategy == gamescope::VirtualConnectorStrategies::PerAppId; + return steamMode && cv_backend_virtual_connector_strategy == + gamescope::VirtualConnectorStrategies::PerAppId; } static inline bool VirtualConnectorKeyIsSteam( VirtualConnectorKey_t ulKey ) - { - return VirtualConnectorInSteamPerAppState() && ulKey == 769; - } + { return VirtualConnectorInSteamPerAppState( ) && ulKey == 769; } static constexpr uint64_t k_ulNonSteamWindowBit = ( uint64_t( 1 ) << 63u ); - static constexpr uint64_t k_ulReservedBit = ( uint64_t( 1 ) << 62u ); + static constexpr uint64_t k_ulReservedBit = ( uint64_t( 1 ) << 62u ); - static constexpr gamescope::VirtualConnectorKey_t k_ulSteamBootstrapperKey = ( uint64_t( 1 ) | k_ulReservedBit ); + static constexpr gamescope::VirtualConnectorKey_t k_ulSteamBootstrapperKey = + ( uint64_t( 1 ) | k_ulReservedBit ); - static inline bool VirtualConnectorKeyIsNonSteamWindow( VirtualConnectorKey_t ulKey ) + static inline bool + VirtualConnectorKeyIsNonSteamWindow( VirtualConnectorKey_t ulKey ) { - return VirtualConnectorInSteamPerAppState() && ( ulKey & k_ulNonSteamWindowBit ) == k_ulNonSteamWindowBit; + return VirtualConnectorInSteamPerAppState( ) && + ( ulKey & k_ulNonSteamWindowBit ) == k_ulNonSteamWindowBit; } - static inline std::string_view VirtualConnectorStrategyToString( VirtualConnectorStrategy eStrategy ) + static inline std::string_view + VirtualConnectorStrategyToString( VirtualConnectorStrategy eStrategy ) { switch ( eStrategy ) { - case VirtualConnectorStrategies::SingleApplication: return "SingleApplication"; - case VirtualConnectorStrategies::SteamControlled: return "SteamControlled"; - case VirtualConnectorStrategies::PerAppId: return "PerAppId"; - case VirtualConnectorStrategies::PerWindow: return "PerWindow"; + case VirtualConnectorStrategies::SingleApplication: + return "SingleApplication"; + case VirtualConnectorStrategies::SteamControlled: + return "SteamControlled"; + case VirtualConnectorStrategies::PerAppId: + return "PerAppId"; + case VirtualConnectorStrategies::PerWindow: + return "PerWindow"; default: return "Unknown"; } @@ -115,32 +127,29 @@ namespace gamescope struct BackendConnectorHDRInfo { // We still want to set up HDR info for Steam Deck LCD with some good - // target/mapping values for the display brightness for undocking from a HDR display, - // but don't want to expose HDR there as it is not good. + // target/mapping values for the display brightness for undocking from a + // HDR display, but don't want to expose HDR there as it is not good. bool bExposeHDRSupport = false; - bool bAlwaysPatchEdid = false; + bool bAlwaysPatchEdid = false; // The output encoding to use for HDR output. // For typical HDR10 displays, this will be PQ. - // For displays doing "traditional HDR" such as Steam Deck OLED, this is Gamma 2.2. + // For displays doing "traditional HDR" such as Steam Deck OLED, this is + // Gamma 2.2. EOTF eOutputEncodingEOTF = EOTF_Gamma22; - uint16_t uMaxContentLightLevel = 500; // Nits - uint16_t uMaxFrameAverageLuminance = 500; // Nits - uint16_t uMinContentLightLevel = 0; // Nits / 10000 + uint16_t uMaxContentLightLevel = 500; // Nits + uint16_t uMaxFrameAverageLuminance = 500; // Nits + uint16_t uMinContentLightLevel = 0; // Nits / 10000 std::shared_ptr pDefaultMetadataBlob; - bool IsHDRG22() const - { - return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_Gamma22; - } + bool IsHDRG22( ) const + { return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_Gamma22; } - bool ShouldPatchEDID() const - { - return bAlwaysPatchEdid || IsHDRG22(); - } + bool ShouldPatchEDID( ) const + { return bAlwaysPatchEdid || IsHDRG22( ); } - bool IsHDR10() const + bool IsHDR10( ) const { // PQ output encoding is always HDR10 (PQ + 2020) for us. // If that assumption changes, update me. @@ -158,13 +167,16 @@ namespace gamescope struct BackendPresentFeedback { public: - uint64_t CurrentPresentsInFlight() const { return TotalPresentsQueued() - TotalPresentsCompleted(); } + uint64_t CurrentPresentsInFlight( ) const + { return TotalPresentsQueued( ) - TotalPresentsCompleted( ); } // Across the lifetime of the backend. - uint64_t TotalPresentsQueued() const { return m_uQueuedPresents.load(); } - uint64_t TotalPresentsCompleted() const { return m_uCompletedPresents.load(); } + uint64_t TotalPresentsQueued( ) const + { return m_uQueuedPresents.load( ); } + uint64_t TotalPresentsCompleted( ) const + { return m_uCompletedPresents.load( ); } - std::atomic m_uQueuedPresents = { 0u }; + std::atomic m_uQueuedPresents = { 0u }; std::atomic m_uCompletedPresents = { 0u }; }; @@ -176,162 +188,176 @@ namespace gamescope class IBackendConnector { public: - virtual ~IBackendConnector() {} + virtual ~IBackendConnector( ) {} - virtual uint64_t GetConnectorID() const = 0; + virtual uint64_t GetConnectorID( ) const = 0; - virtual GamescopeScreenType GetScreenType() const = 0; - virtual GamescopePanelOrientation GetCurrentOrientation() const = 0; - virtual bool SupportsHDR() const = 0; - virtual bool IsHDRActive() const = 0; - virtual const BackendConnectorHDRInfo &GetHDRInfo() const = 0; - virtual bool IsVRRActive() const = 0; - virtual std::span GetModes() const = 0; + virtual GamescopeScreenType GetScreenType( ) const = 0; + virtual GamescopePanelOrientation GetCurrentOrientation( ) const = 0; + virtual bool SupportsHDR( ) const = 0; + virtual bool IsHDRActive( ) const = 0; + virtual const BackendConnectorHDRInfo &GetHDRInfo( ) const = 0; + virtual bool IsVRRActive( ) const = 0; + virtual std::span GetModes( ) const = 0; - virtual bool SupportsVRR() const = 0; + virtual bool SupportsVRR( ) const = 0; - virtual std::span GetRawEDID() const = 0; - virtual std::span GetValidDynamicRefreshRates() const = 0; + virtual std::span GetRawEDID( ) const = 0; + virtual std::span + GetValidDynamicRefreshRates( ) const = 0; virtual void GetNativeColorimetry( - bool bHDR10, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) const = 0; + bool bHDR10, + displaycolorimetry_t *displayColorimetry, + EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, + EOTF *outputEncodingEOTF ) const = 0; - virtual const char *GetName() const = 0; - virtual const char *GetMake() const = 0; - virtual const char *GetModel() const = 0; + virtual const char *GetName( ) const = 0; + virtual const char *GetMake( ) const = 0; + virtual const char *GetModel( ) const = 0; virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) = 0; - virtual VBlankScheduleTime FrameSync() = 0; - virtual BackendPresentFeedback& PresentationFeedback() = 0; + virtual VBlankScheduleTime FrameSync( ) = 0; + virtual BackendPresentFeedback &PresentationFeedback( ) = 0; - virtual uint64_t GetVirtualConnectorKey() const = 0; + virtual uint64_t GetVirtualConnectorKey( ) const = 0; - virtual INestedHints *GetNestedHints() = 0; + virtual INestedHints *GetNestedHints( ) = 0; - virtual void SetProperty( ConnectorProperty eProperty, std::any value ) = 0; + virtual void + SetProperty( ConnectorProperty eProperty, std::any value ) = 0; }; class CBaseBackendConnector : public IBackendConnector { public: - CBaseBackendConnector() - { - AssignConnectorId(); - } - CBaseBackendConnector( uint64_t ulVirtualConnectorKey ) - : m_ulVirtualConnectorKey{ ulVirtualConnectorKey } - { - AssignConnectorId(); - } + CBaseBackendConnector( ) { AssignConnectorId( ); } + CBaseBackendConnector( uint64_t ulVirtualConnectorKey ) : + m_ulVirtualConnectorKey{ ulVirtualConnectorKey } + { AssignConnectorId( ); } + + virtual ~CBaseBackendConnector( ) {} + + virtual uint64_t GetConnectorID( ) const override + { return m_ulConnectorId; } + virtual VBlankScheduleTime FrameSync( ) override; + virtual BackendPresentFeedback &PresentationFeedback( ) override + { return m_PresentFeedback; } + virtual uint64_t GetVirtualConnectorKey( ) const override + { return m_ulVirtualConnectorKey; } + virtual INestedHints *GetNestedHints( ) override { return nullptr; } + + virtual void + SetProperty( ConnectorProperty eProperty, std::any value ) override + {} - virtual ~CBaseBackendConnector() - { - - } - - virtual uint64_t GetConnectorID() const override { return m_ulConnectorId; } - virtual VBlankScheduleTime FrameSync() override; - virtual BackendPresentFeedback& PresentationFeedback() override { return m_PresentFeedback; } - virtual uint64_t GetVirtualConnectorKey() const override { return m_ulVirtualConnectorKey; } - virtual INestedHints *GetNestedHints() override { return nullptr; } - - virtual void SetProperty( ConnectorProperty eProperty, std::any value ) override { } protected: - uint64_t m_ulConnectorId = 0; - uint64_t m_ulVirtualConnectorKey = 0; + uint64_t m_ulConnectorId = 0; + uint64_t m_ulVirtualConnectorKey = 0; BackendPresentFeedback m_PresentFeedback{}; private: - void AssignConnectorId() + void AssignConnectorId( ) { static uint64_t s_ulLastConnectorId = 0; - m_ulConnectorId = ++s_ulLastConnectorId; + m_ulConnectorId = ++s_ulLastConnectorId; } }; class INestedHints { public: - virtual ~INestedHints() {} + virtual ~INestedHints( ) {} struct CursorInfo { std::vector pPixels; - uint32_t uWidth; - uint32_t uHeight; - uint32_t uXHotspot; - uint32_t uYHotspot; + uint32_t uWidth; + uint32_t uHeight; + uint32_t uXHotspot; + uint32_t uYHotspot; }; virtual void SetCursorImage( std::shared_ptr info ) = 0; - virtual void SetRelativeMouseMode( bool bRelative ) = 0; - virtual void SetVisible( bool bVisible ) = 0; - virtual void SetTitle( std::shared_ptr szTitle ) = 0; - virtual void SetIcon( std::shared_ptr> uIconPixels ) = 0; - virtual void SetSelection( std::shared_ptr szContents, GamescopeSelection eSelection ) = 0; - - virtual bool ShouldPaintCursor() { return false; } + virtual void SetRelativeMouseMode( bool bRelative ) = 0; + virtual void SetVisible( bool bVisible ) = 0; + virtual void SetTitle( std::shared_ptr szTitle ) = 0; + virtual void + SetIcon( std::shared_ptr> uIconPixels ) = 0; + virtual void SetSelection( + std::shared_ptr szContents, + GamescopeSelection eSelection ) = 0; + + virtual bool ShouldPaintCursor( ) { return false; } }; class IBackendFb : public IRcObject { public: virtual void SetBuffer( wlr_buffer *pClientBuffer ) = 0; - virtual void SetReleasePoint( std::shared_ptr pReleasePoint ) = 0; + virtual void SetReleasePoint( + std::shared_ptr pReleasePoint ) = 0; - virtual IBackendFb *EnsureImported() = 0; + virtual IBackendFb *EnsureImported( ) = 0; }; class IBackendPlane { public: - virtual ~IBackendPlane() = default; + virtual ~IBackendPlane( ) = default; }; class CBaseBackendFb : public IBackendFb { public: - CBaseBackendFb(); - virtual ~CBaseBackendFb(); + CBaseBackendFb( ); + virtual ~CBaseBackendFb( ); - uint32_t IncRef() override; - uint32_t DecRef() override; + uint32_t IncRef( ) override; + uint32_t DecRef( ) override; void SetBuffer( wlr_buffer *pClientBuffer ) override; - void SetReleasePoint( std::shared_ptr pReleasePoint ) override; + void SetReleasePoint( + std::shared_ptr pReleasePoint ) override; - virtual IBackendFb *EnsureImported() override { return this; }; + virtual IBackendFb *EnsureImported( ) override { return this; }; private: - wlr_buffer *m_pClientBuffer = nullptr; + wlr_buffer *m_pClientBuffer = nullptr; std::shared_ptr m_pReleasePoint; }; class IBackend { public: - virtual ~IBackend() {} - - virtual bool Init() = 0; - virtual bool PostInit() = 0; - virtual std::span GetInstanceExtensions() const = 0; - virtual std::span GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const = 0; - virtual VkImageLayout GetPresentLayout() const = 0; - virtual void GetPreferredOutputFormat( uint32_t *pPrimaryPlaneFormat, uint32_t *pOverlayPlaneFormat ) const = 0; - virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const = 0; - - virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) = 0; - virtual bool PollState() = 0; - - virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) = 0; - template - std::shared_ptr CreateBackendBlob( const T& thing ) + virtual ~IBackend( ) {} + + virtual bool Init( ) = 0; + virtual bool PostInit( ) = 0; + virtual std::span GetInstanceExtensions( ) const = 0; + virtual std::span + GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const = 0; + virtual VkImageLayout GetPresentLayout( ) const = 0; + virtual void GetPreferredOutputFormat( + uint32_t *pPrimaryPlaneFormat, + uint32_t *pOverlayPlaneFormat ) const = 0; + virtual bool + ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const = 0; + + virtual void + DirtyState( bool bForce = false, bool bForceModeset = false ) = 0; + virtual bool PollState( ) = 0; + + virtual std::shared_ptr CreateBackendBlob( + const std::type_info &type, std::span data ) = 0; + template + std::shared_ptr CreateBackendBlob( const T &thing ) { const uint8_t *pBegin = reinterpret_cast( &thing ); - const uint8_t *pEnd = pBegin + sizeof( T ); - return CreateBackendBlob( typeid( T ), std::span( pBegin, pEnd ) ); + const uint8_t *pEnd = pBegin + sizeof( T ); + return CreateBackendBlob( + typeid( T ), std::span( pBegin, pEnd ) ); } // For DRM, this is @@ -339,109 +365,120 @@ namespace gamescope // // shared_ptr owns the structure. // Rc manages acquire/release of buffer to/from client while imported. - virtual OwningRc ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) = 0; - - virtual bool UsesModifiers() const = 0; - virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const = 0; - inline bool SupportsFormat( uint32_t uDrmFormat ) const - { - return !this->GetSupportedModifiers( uDrmFormat ).empty(); - } - inline bool SupportsInvalidModifier( uint32_t uDrmFormat ) const - { - return Algorithm::Contains( this->GetSupportedModifiers( uDrmFormat ), DRM_FORMAT_MOD_INVALID ); - } - - virtual IBackendConnector *GetCurrentConnector() = 0; - virtual IBackendConnector *GetCurrentMouseConnector() + virtual OwningRc + ImportDmabufToBackend( wlr_dmabuf_attributes *pDmaBuf ) = 0; + + virtual bool UsesModifiers( ) const = 0; + virtual std::span + GetSupportedModifiers( uint32_t uDrmFormat ) const = 0; + inline bool SupportsFormat( uint32_t uDrmFormat ) const + { return !this->GetSupportedModifiers( uDrmFormat ).empty( ); } + inline bool SupportsInvalidModifier( uint32_t uDrmFormat ) const { - return this->GetCurrentConnector(); + return Algorithm::Contains( + this->GetSupportedModifiers( uDrmFormat ), + DRM_FORMAT_MOD_INVALID ); } - virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) = 0; - virtual bool SupportsPlaneHardwareCursor() const = 0; - virtual bool SupportsTearing() const = 0; + virtual IBackendConnector *GetCurrentConnector( ) = 0; + virtual IBackendConnector *GetCurrentMouseConnector( ) + { return this->GetCurrentConnector( ); } + virtual IBackendConnector * + GetConnector( GamescopeScreenType eScreenType ) = 0; - virtual bool UsesVulkanSwapchain() const = 0; - virtual bool IsSessionBased() const = 0; + virtual bool SupportsPlaneHardwareCursor( ) const = 0; + virtual bool SupportsTearing( ) const = 0; - virtual bool SupportsExplicitSync() const = 0; + virtual bool UsesVulkanSwapchain( ) const = 0; + virtual bool IsSessionBased( ) const = 0; + + virtual bool SupportsExplicitSync( ) const = 0; // Dumb helper we should remove to support multi display someday. - gamescope::GamescopeScreenType GetScreenType() + gamescope::GamescopeScreenType GetScreenType( ) { - if ( GetCurrentConnector() ) - return GetCurrentConnector()->GetScreenType(); + if ( GetCurrentConnector( ) ) + return GetCurrentConnector( )->GetScreenType( ); return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; } - virtual bool IsPaused() const = 0; - virtual bool IsVisible() const = 0; + virtual bool IsPaused( ) const = 0; + virtual bool IsVisible( ) const = 0; virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const = 0; // This will move to the connector and be deprecated soon. virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) = 0; - virtual void HackUpdatePatchedEdid() = 0; + virtual void HackUpdatePatchedEdid( ) = 0; - virtual bool NeedsFrameSync() const = 0; + virtual bool NeedsFrameSync( ) const = 0; - virtual TouchClickMode GetTouchClickMode() = 0; + virtual TouchClickMode GetTouchClickMode( ) = 0; - virtual void DumpDebugInfo() = 0; + virtual void DumpDebugInfo( ) = 0; - virtual bool UsesVirtualConnectors() = 0; - virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) = 0; + virtual bool UsesVirtualConnectors( ) = 0; + virtual std::shared_ptr + CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) = 0; virtual void NotifyPhysicalInput( InputType eInputType ) = 0; - virtual bool SupportsVROverlayForwarding() = 0; - virtual void ForwardFramebuffer( std::shared_ptr &pPlane, IBackendFb *pFramebuffer, const void *pData ) = 0; + virtual bool SupportsVROverlayForwarding( ) = 0; + virtual void ForwardFramebuffer( + std::shared_ptr &pPlane, + IBackendFb *pFramebuffer, + const void *pData ) = 0; - virtual bool NewlyInitted() = 0; + virtual bool NewlyInitted( ) = 0; - virtual bool ShouldFitWindows() = 0; + virtual bool ShouldFitWindows( ) = 0; - virtual void OnEndFrame() = 0; + virtual void OnEndFrame( ) = 0; - static IBackend *Get(); - template - static bool Set(); + static IBackend *Get( ); + template static bool Set( ); static bool Set( IBackend *pBackend ); + protected: friend BackendBlob; virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) = 0; + private: }; - class CBaseBackend : public IBackend { public: - virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override { return false; } - virtual void HackUpdatePatchedEdid() override {} + virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override + { return false; } + virtual void HackUpdatePatchedEdid( ) override {} - virtual bool NeedsFrameSync() const override; + virtual bool NeedsFrameSync( ) const override; - virtual TouchClickMode GetTouchClickMode() override; + virtual TouchClickMode GetTouchClickMode( ) override; - virtual void DumpDebugInfo() override; + virtual void DumpDebugInfo( ) override; - virtual bool UsesVirtualConnectors() override; - virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override; + virtual bool UsesVirtualConnectors( ) override; + virtual std::shared_ptr + CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override; virtual void NotifyPhysicalInput( InputType eInputType ) override {} - virtual bool SupportsVROverlayForwarding() override { return false; } - virtual void ForwardFramebuffer( std::shared_ptr &pPlane, IBackendFb *pFramebuffer, const void *pData ) override {} + virtual bool SupportsVROverlayForwarding( ) override { return false; } + virtual void ForwardFramebuffer( + std::shared_ptr &pPlane, + IBackendFb *pFramebuffer, + const void *pData ) override + {} - virtual bool NewlyInitted() override { return false; } + virtual bool NewlyInitted( ) override { return false; } - virtual bool ShouldFitWindows() override { return true; } + virtual bool ShouldFitWindows( ) override { return true; } - virtual void OnEndFrame() override {} + virtual void OnEndFrame( ) override {} }; // This is a blob of data that may be associated with @@ -451,58 +488,53 @@ namespace gamescope class BackendBlob { public: - BackendBlob() - { - } + BackendBlob( ) {} - BackendBlob( std::span data ) - : m_Data( data.begin(), data.end() ) - { - } + BackendBlob( std::span data ) : + m_Data( data.begin( ), data.end( ) ) + {} - BackendBlob( std::span data, uint32_t uBlob, bool bOwned ) - : m_Data( data.begin(), data.end() ) - , m_uBlob( uBlob ) - , m_bOwned( bOwned ) - { - } + BackendBlob( + std::span data, uint32_t uBlob, bool bOwned ) : + m_Data( data.begin( ), data.end( ) ), + m_uBlob( uBlob ), + m_bOwned( bOwned ) + {} - ~BackendBlob() + ~BackendBlob( ) { if ( m_bOwned ) { - IBackend *pBackend = IBackend::Get(); - if ( pBackend ) - pBackend->OnBackendBlobDestroyed( this ); + IBackend *pBackend = IBackend::Get( ); + if ( pBackend ) pBackend->OnBackendBlobDestroyed( this ); } } // No copy constructor, because we can't duplicate the blob handle. - BackendBlob( const BackendBlob& ) = delete; - BackendBlob& operator=( const BackendBlob& ) = delete; - // No move constructor, because we use shared_ptr anyway, but can be added if necessary. - BackendBlob( BackendBlob&& ) = delete; - BackendBlob& operator=( BackendBlob&& ) = delete; - - std::span GetData() const { return std::span( m_Data.begin(), m_Data.end() ); } - template - const T& View() const + BackendBlob( const BackendBlob & ) = delete; + BackendBlob &operator=( const BackendBlob & ) = delete; + // No move constructor, because we use shared_ptr anyway, but can be + // added if necessary. + BackendBlob( BackendBlob && ) = delete; + BackendBlob &operator=( BackendBlob && ) = delete; + + std::span GetData( ) const + { return std::span( m_Data.begin( ), m_Data.end( ) ); } + template const T &View( ) const { - assert( sizeof( T ) == m_Data.size() ); - return *reinterpret_cast( m_Data.data() ); + assert( sizeof( T ) == m_Data.size( ) ); + return *reinterpret_cast( m_Data.data( ) ); } - uint32_t GetBlobValue() const { return m_uBlob; } + uint32_t GetBlobValue( ) const { return m_uBlob; } private: std::vector m_Data; - uint32_t m_uBlob = 0; - bool m_bOwned = false; + uint32_t m_uBlob = 0; + bool m_bOwned = false; }; extern ConVar cv_touch_click_mode; -} +} // namespace gamescope -inline gamescope::IBackend *GetBackend() -{ - return gamescope::IBackend::Get(); -} +inline gamescope::IBackend *GetBackend( ) +{ return gamescope::IBackend::Get( ); } diff --git a/src/backends.h b/src/backends.h index 4e017bf581..5df52a94cd 100644 --- a/src/backends.h +++ b/src/backends.h @@ -19,4 +19,4 @@ namespace gamescope class COpenVRBackend; class CHeadlessBackend; class CWaylandBackend; -} +} // namespace gamescope diff --git a/src/color_bench.cpp b/src/color_bench.cpp index 105898629a..2f70f4fd5b 100644 --- a/src/color_bench.cpp +++ b/src/color_bench.cpp @@ -9,25 +9,28 @@ using color_bench::nLutEdgeSize3d; using color_bench::nLutSize1d; -uint16_t lut1d[nLutSize1d*4]; -uint16_t lut3d[nLutEdgeSize3d*nLutEdgeSize3d*nLutEdgeSize3d*4]; +uint16_t lut1d[ nLutSize1d * 4 ]; +uint16_t lut3d[ nLutEdgeSize3d * nLutEdgeSize3d * nLutEdgeSize3d * 4 ]; lut1d_t lut1d_float; lut3d_t lut3d_float; -static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) +static void +BenchmarkCalcColorTransform( EOTF inputEOTF, benchmark::State &state ) { - const primaries_t primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { 0.164f, 0.121f } }; - const glm::vec2 white = { 0.3070f, 0.3220f }; - const glm::vec2 destVirtualWhite = { 0.f, 0.f }; + const primaries_t primaries = { { 0.602f, 0.355f }, + { 0.340f, 0.574f }, + { 0.164f, 0.121f } }; + const glm::vec2 white = { 0.3070f, 0.3220f }; + const glm::vec2 destVirtualWhite = { 0.f, 0.f }; displaycolorimetry_t inputColorimetry{}; inputColorimetry.primaries = primaries; - inputColorimetry.white = white; + inputColorimetry.white = white; displaycolorimetry_t outputEncodingColorimetry{}; outputEncodingColorimetry.primaries = primaries; - outputEncodingColorimetry.white = white; + outputEncodingColorimetry.white = white; colormapping_t colorMapping{}; @@ -35,180 +38,209 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) tonemapping.bUseShaper = true; nightmode_t nightmode{}; - float flGain = 1.0f; - - for (auto _ : state) { - calcColorTransform( &lut1d_float, nLutSize1d, &lut3d_float, inputColorimetry, inputEOTF, - outputEncodingColorimetry, EOTF_Gamma22, - destVirtualWhite, k_EChromaticAdapatationMethod_XYZ, - colorMapping, nightmode, tonemapping, nullptr, flGain ); - for ( size_t i=0, end = lut1d_float.dataR.size(); i( + &lut1d_float, + nLutSize1d, + &lut3d_float, + inputColorimetry, + inputEOTF, + outputEncodingColorimetry, + EOTF_Gamma22, + destVirtualWhite, + k_EChromaticAdapatationMethod_XYZ, + colorMapping, + nightmode, + tonemapping, + nullptr, + flGain ); + for ( size_t i = 0, end = lut1d_float.dataR.size( ); i < end; ++i ) { - lut1d[4*i+0] = quantize_lut_value_16bit( lut1d_float.dataR[i] ); - lut1d[4*i+1] = quantize_lut_value_16bit( lut1d_float.dataG[i] ); - lut1d[4*i+2] = quantize_lut_value_16bit( lut1d_float.dataB[i] ); - lut1d[4*i+3] = 0; + lut1d[ 4 * i + 0 ] = + quantize_lut_value_16bit( lut1d_float.dataR[ i ] ); + lut1d[ 4 * i + 1 ] = + quantize_lut_value_16bit( lut1d_float.dataG[ i ] ); + lut1d[ 4 * i + 2 ] = + quantize_lut_value_16bit( lut1d_float.dataB[ i ] ); + lut1d[ 4 * i + 3 ] = 0; } - for ( size_t i=0, end = lut3d_float.data.size(); i -static __attribute__((noinline)) std::array GetFindTestValues() +template +static __attribute__(( noinline )) std::array GetFindTestValues( ) { - static std::array s_Values = []() + static std::array s_Values = []( ) { std::array values; for ( uint32_t i = 0; i < uSize; i++ ) - values[i] = rand() % 255; + values[ i ] = rand( ) % 255; return values; - }(); + }( ); return s_Values; } // Large -static void Benchmark_Find_Large_Gamescope(benchmark::State &state) +static void Benchmark_Find_Large_Gamescope( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - auto iter = gamescope::Algorithm::Find( values.begin(), values.end(), 765678478 ); + auto iter = gamescope::Algorithm::Find( + values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( iter ); } } -BENCHMARK(Benchmark_Find_Large_Gamescope); +BENCHMARK( Benchmark_Find_Large_Gamescope ); -static void Benchmark_Find_Large_Std(benchmark::State &state) +static void Benchmark_Find_Large_Std( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - auto iter = std::find( values.begin(), values.end(), 765678478 ); + auto iter = std::find( values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( iter ); } } -BENCHMARK(Benchmark_Find_Large_Std); +BENCHMARK( Benchmark_Find_Large_Std ); -static void Benchmark_Contains_Large_Gamescope(benchmark::State &state) +static void Benchmark_Contains_Large_Gamescope( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - bool bContains = gamescope::Algorithm::ContainsNoShortcut( values.begin(), values.end(), 765678478 ); + bool bContains = gamescope::Algorithm::ContainsNoShortcut( + values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( bContains ); } } -BENCHMARK(Benchmark_Contains_Large_Gamescope); +BENCHMARK( Benchmark_Contains_Large_Gamescope ); // -static void Benchmark_Find_Medium_Gamescope(benchmark::State &state) +static void Benchmark_Find_Medium_Gamescope( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - auto iter = gamescope::Algorithm::Find( values.begin(), values.end(), 765678478 ); + auto iter = gamescope::Algorithm::Find( + values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( iter ); } } -BENCHMARK(Benchmark_Find_Medium_Gamescope); +BENCHMARK( Benchmark_Find_Medium_Gamescope ); -static void Benchmark_Find_Medium_Std(benchmark::State &state) +static void Benchmark_Find_Medium_Std( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - auto iter = std::find( values.begin(), values.end(), 765678478 ); + auto iter = std::find( values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( iter ); } } -BENCHMARK(Benchmark_Find_Medium_Std); +BENCHMARK( Benchmark_Find_Medium_Std ); -static void Benchmark_Contains_Medium_Gamescope(benchmark::State &state) +static void Benchmark_Contains_Medium_Gamescope( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - bool bContains = gamescope::Algorithm::ContainsNoShortcut( values.begin(), values.end(), 765678478 ); + bool bContains = gamescope::Algorithm::ContainsNoShortcut( + values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( bContains ); } } -BENCHMARK(Benchmark_Contains_Medium_Gamescope); +BENCHMARK( Benchmark_Contains_Medium_Gamescope ); // -static void Benchmark_Find_Small_Gamescope(benchmark::State &state) +static void Benchmark_Find_Small_Gamescope( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - auto iter = gamescope::Algorithm::Find( values.begin(), values.end(), 765678478 ); + auto iter = gamescope::Algorithm::Find( + values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( iter ); } } -BENCHMARK(Benchmark_Find_Small_Gamescope); +BENCHMARK( Benchmark_Find_Small_Gamescope ); -static void Benchmark_Find_Small_Std(benchmark::State &state) +static void Benchmark_Find_Small_Std( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - auto iter = std::find( values.begin(), values.end(), 765678478 ); + auto iter = std::find( values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( iter ); } } -BENCHMARK(Benchmark_Find_Small_Std); +BENCHMARK( Benchmark_Find_Small_Std ); -static void Benchmark_Contains_Small_Gamescope(benchmark::State &state) +static void Benchmark_Contains_Small_Gamescope( benchmark::State &state ) { - std::array values = GetFindTestValues(); + std::array values = + GetFindTestValues( ); - for (auto _ : state) + for ( auto _ : state ) { - bool bContains = gamescope::Algorithm::ContainsNoShortcut( values.begin(), values.end(), 765678478 ); + bool bContains = gamescope::Algorithm::ContainsNoShortcut( + values.begin( ), values.end( ), 765678478 ); benchmark::DoNotOptimize( bContains ); } } -BENCHMARK(Benchmark_Contains_Small_Gamescope); +BENCHMARK( Benchmark_Contains_Small_Gamescope ); -BENCHMARK_MAIN(); +BENCHMARK_MAIN( ); diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp index 64430a5f0c..4fcf52122f 100644 --- a/src/color_helpers.cpp +++ b/src/color_helpers.cpp @@ -2,32 +2,32 @@ #include "color_helpers_impl.h" #include -#include #include +#include -#include -#include -#include #include #include +#include +#include +#include - -glm::vec3 xyY_to_XYZ( const glm::vec2 & xy, float Y ) +glm::vec3 xyY_to_XYZ( const glm::vec2 &xy, float Y ) { - if ( fabsf( xy.y ) < std::numeric_limits::min() ) + if ( fabsf( xy.y ) < std::numeric_limits::min( ) ) { return glm::vec3( 0.f ); } else { - return glm::vec3( Y * xy.x / xy.y, Y, ( 1.f - xy.x - xy.y ) * Y / xy.y ); + return glm::vec3( + Y * xy.x / xy.y, Y, ( 1.f - xy.x - xy.y ) * Y / xy.y ); } } -glm::vec2 XYZ_to_xy( const glm::vec3 & XYZ ) +glm::vec2 XYZ_to_xy( const glm::vec3 &XYZ ) { float sum = ( XYZ.x + XYZ.y + XYZ.z ); - if ( fabsf( sum ) < std::numeric_limits::min() ) + if ( fabsf( sum ) < std::numeric_limits::min( ) ) { return glm::vec2( 0.f ); } @@ -37,16 +37,14 @@ glm::vec2 XYZ_to_xy( const glm::vec3 & XYZ ) } } -glm::vec3 xy_to_xyz( const glm::vec2 & xy ) -{ - return glm::vec3( xy.x, xy.y, 1.f - xy.x - xy.y ); -} +glm::vec3 xy_to_xyz( const glm::vec2 &xy ) +{ return glm::vec3( xy.x, xy.y, 1.f - xy.x - xy.y ); } // Convert xy to CIE 1976 u'v' -glm::vec2 xy_to_uv( const glm::vec2 & xy ) +glm::vec2 xy_to_uv( const glm::vec2 &xy ) { float denom = -2.f * xy.x + 12.f * xy.y + 3.f; - if ( fabsf( denom ) < std::numeric_limits::min() ) + if ( fabsf( denom ) < std::numeric_limits::min( ) ) { return glm::vec2( 0.f ); } @@ -55,10 +53,10 @@ glm::vec2 xy_to_uv( const glm::vec2 & xy ) } // Convert CIE 1976 u'v' to xy -glm::vec2 uv_to_xy( const glm::vec2 & uv ) +glm::vec2 uv_to_xy( const glm::vec2 &uv ) { float denom = 6.f * uv.x - 16.f * uv.y + 12.f; - if ( fabsf( denom ) < std::numeric_limits::min() ) + if ( fabsf( denom ) < std::numeric_limits::min( ) ) { return glm::vec2( 0.f ); } @@ -66,29 +64,54 @@ glm::vec2 uv_to_xy( const glm::vec2 & uv ) return glm::vec2( 9.f * uv.x / denom, 4.f * uv.y / denom ); } -glm::mat3 normalised_primary_matrix( const primaries_t & rgbPrimaries, const glm::vec2 & whitePrimary, float whiteLuminance ) +glm::mat3 normalised_primary_matrix( + const primaries_t &rgbPrimaries, + const glm::vec2 &whitePrimary, + float whiteLuminance ) { - glm::mat3 matPrimaries( xy_to_xyz( rgbPrimaries.r ), xy_to_xyz( rgbPrimaries.g ), xy_to_xyz( rgbPrimaries.b ) ); + glm::mat3 matPrimaries( + xy_to_xyz( rgbPrimaries.r ), + xy_to_xyz( rgbPrimaries.g ), + xy_to_xyz( rgbPrimaries.b ) ); glm::vec3 whiteXYZ = xyY_to_XYZ( whitePrimary, whiteLuminance ); - glm::mat3 whiteScale = glm::diagonal3x3( glm::inverse( matPrimaries ) * whiteXYZ ); + glm::mat3 whiteScale = + glm::diagonal3x3( glm::inverse( matPrimaries ) * whiteXYZ ); return matPrimaries * whiteScale; } -glm::mat3 chromatic_adaptation_matrix( const glm::vec3 & sourceWhiteXYZ, const glm::vec3 & destWhiteXYZ, +glm::mat3 chromatic_adaptation_matrix( + const glm::vec3 &sourceWhiteXYZ, + const glm::vec3 &destWhiteXYZ, EChromaticAdaptationMethod eMethod ) { - static const glm::mat3 k_matBradford( 0.8951f,-0.7502f, 0.0389f, 0.2664f,1.7135f, -0.0685f, -0.1614f, 0.0367f, 1.0296f ); - glm::mat3 matAdaptation = eMethod == k_EChromaticAdapatationMethod_XYZ ? glm::diagonal3x3( glm::vec3(1,1,1) ) : k_matBradford; + static const glm::mat3 k_matBradford( + 0.8951f, + -0.7502f, + 0.0389f, + 0.2664f, + 1.7135f, + -0.0685f, + -0.1614f, + 0.0367f, + 1.0296f ); + glm::mat3 matAdaptation = eMethod == k_EChromaticAdapatationMethod_XYZ + ? glm::diagonal3x3( glm::vec3( 1, 1, 1 ) ) + : k_matBradford; glm::vec3 coneResponseDest = matAdaptation * destWhiteXYZ; glm::vec3 coneResponseSource = matAdaptation * sourceWhiteXYZ; - glm::vec3 scale = glm::vec3( coneResponseDest.x / coneResponseSource.x, coneResponseDest.y / coneResponseSource.y, coneResponseDest.z / coneResponseSource.z ); - return glm::inverse( matAdaptation ) * glm::diagonal3x3( scale ) * matAdaptation; + glm::vec3 scale = glm::vec3( + coneResponseDest.x / coneResponseSource.x, + coneResponseDest.y / coneResponseSource.y, + coneResponseDest.z / coneResponseSource.z ); + return glm::inverse( matAdaptation ) * glm::diagonal3x3( scale ) * + matAdaptation; } -displaycolorimetry_t lerp( const displaycolorimetry_t & a, const displaycolorimetry_t & b, float t ) +displaycolorimetry_t +lerp( const displaycolorimetry_t &a, const displaycolorimetry_t &b, float t ) { displaycolorimetry_t result; - primaries_t a_uv, b_uv; + primaries_t a_uv, b_uv; a_uv.r = xy_to_uv( a.primaries.r ); a_uv.g = xy_to_uv( a.primaries.g ); a_uv.b = xy_to_uv( a.primaries.b ); @@ -106,33 +129,34 @@ displaycolorimetry_t lerp( const displaycolorimetry_t & a, const displaycolorime result.primaries.g.y = flerp( a_uv.g.y, b_uv.g.y, t ); result.primaries.b.x = flerp( a_uv.b.x, b_uv.b.x, t ); result.primaries.b.y = flerp( a_uv.b.y, b_uv.b.y, t ); - result.white.x = flerp( a_white.x, b_white.x, t ); - result.white.y = flerp( a_white.y, b_white.y, t ); + result.white.x = flerp( a_white.x, b_white.x, t ); + result.white.y = flerp( a_white.y, b_white.y, t ); result.primaries.r = uv_to_xy( result.primaries.r ); result.primaries.g = uv_to_xy( result.primaries.g ); result.primaries.b = uv_to_xy( result.primaries.b ); - result.white = uv_to_xy( result.white ); + result.white = uv_to_xy( result.white ); return result; } -colormapping_t lerp( const colormapping_t & a, const colormapping_t & b, float t ) +colormapping_t lerp( const colormapping_t &a, const colormapping_t &b, float t ) { colormapping_t result; - result.blendEnableMinSat = flerp( a.blendEnableMinSat, b.blendEnableMinSat, t ); - result.blendEnableMaxSat = flerp( a.blendEnableMaxSat, b.blendEnableMaxSat, t ); + result.blendEnableMinSat = + flerp( a.blendEnableMinSat, b.blendEnableMinSat, t ); + result.blendEnableMaxSat = + flerp( a.blendEnableMaxSat, b.blendEnableMaxSat, t ); result.blendAmountMin = flerp( a.blendAmountMin, b.blendAmountMin, t ); result.blendAmountMax = flerp( a.blendAmountMax, b.blendAmountMax, t ); return result; } -inline bool close_enough(float a, float b, float epsilon = 0.001f) -{ - return fabsf(a - b) <= epsilon; -} +inline bool close_enough( float a, float b, float epsilon = 0.001f ) +{ return fabsf( a - b ) <= epsilon; } -std::shared_ptr LoadCubeLut( FILE *pFile, bool &bRaisesBlackLevelFloor ) +std::shared_ptr +LoadCubeLut( FILE *pFile, bool &bRaisesBlackLevelFloor ) { // R changes fastest // ... @@ -140,12 +164,12 @@ std::shared_ptr LoadCubeLut( FILE *pFile, bool &bRaisesBlackLevelFloor // %f %f %f // ... - bRaisesBlackLevelFloor = false; - std::shared_ptr lut3d = std::make_shared(); + bRaisesBlackLevelFloor = false; + std::shared_ptr lut3d = std::make_shared( ); glm::vec3 blackFloor = glm::vec3{ 0.0f, 0.0f, 0.0f }; - char line[2048]; + char line[ 2048 ]; while ( fgets( line, sizeof( line ), pFile ) ) { if ( lut3d->lutEdgeSize ) @@ -153,87 +177,103 @@ std::shared_ptr LoadCubeLut( FILE *pFile, bool &bRaisesBlackLevelFloor glm::vec3 val; if ( sscanf( line, "%f %f %f", &val.r, &val.g, &val.b ) == 3 ) { - if ( lut3d->data.empty() ) - { - blackFloor = val; - } + if ( lut3d->data.empty( ) ) { blackFloor = val; } lut3d->data.push_back( val ); } } else if ( sscanf( line, "LUT_3D_SIZE %d", &lut3d->lutEdgeSize ) == 1 ) { - if ( lut3d->lutEdgeSize < 2 || lut3d->lutEdgeSize > 128 ) // sanity check + if ( lut3d->lutEdgeSize < 2 || + lut3d->lutEdgeSize > 128 ) // sanity check { return nullptr; } - lut3d->data.reserve( lut3d->lutEdgeSize * lut3d->lutEdgeSize * lut3d->lutEdgeSize ); + lut3d->data.reserve( + lut3d->lutEdgeSize * lut3d->lutEdgeSize * lut3d->lutEdgeSize ); } } - int nExpectedElements = lut3d->lutEdgeSize * lut3d->lutEdgeSize * lut3d->lutEdgeSize; - bool bValid = ( nExpectedElements > 0 && ( nExpectedElements == (int) lut3d->data.size() ) ); - if ( !bValid ) - { - return nullptr; - } + int nExpectedElements = + lut3d->lutEdgeSize * lut3d->lutEdgeSize * lut3d->lutEdgeSize; + bool bValid = + ( nExpectedElements > 0 && + ( nExpectedElements == ( int )lut3d->data.size( ) ) ); + if ( !bValid ) { return nullptr; } - bRaisesBlackLevelFloor = !close_enough(blackFloor.x, 0.0f) || !close_enough(blackFloor.y, 0.0f) || !close_enough(blackFloor.z, 0.0f); + bRaisesBlackLevelFloor = !close_enough( blackFloor.x, 0.0f ) || + !close_enough( blackFloor.y, 0.0f ) || + !close_enough( blackFloor.z, 0.0f ); return lut3d; } -std::shared_ptr LoadCubeLut( const char *pchFileName, bool &bRaisesBlackLevelFloor ) +std::shared_ptr +LoadCubeLut( const char *pchFileName, bool &bRaisesBlackLevelFloor ) { bRaisesBlackLevelFloor = false; - + FILE *pFile = fopen( pchFileName, "r" ); - if ( !pFile ) - return nullptr; + if ( !pFile ) return nullptr; return LoadCubeLut( pFile, bRaisesBlackLevelFloor ); } -int GetLut3DIndexRedFastRGB(int indexR, int indexG, int indexB, int dim) -{ - return (indexR + (int)dim * (indexG + (int)dim * indexB)); -} +int GetLut3DIndexRedFastRGB( int indexR, int indexG, int indexB, int dim ) +{ return ( indexR + ( int )dim * ( indexG + ( int )dim * indexB ) ); } // Linear -inline void lerp_rgb(float* out, const float* a, const float* b, const float* z) +inline void +lerp_rgb( float *out, const float *a, const float *b, const float *z ) { - out[0] = (b[0] - a[0]) * z[0] + a[0]; - out[1] = (b[1] - a[1]) * z[1] + a[1]; - out[2] = (b[2] - a[2]) * z[2] + a[2]; + out[ 0 ] = ( b[ 0 ] - a[ 0 ] ) * z[ 0 ] + a[ 0 ]; + out[ 1 ] = ( b[ 1 ] - a[ 1 ] ) * z[ 1 ] + a[ 1 ]; + out[ 2 ] = ( b[ 2 ] - a[ 2 ] ) * z[ 2 ] + a[ 2 ]; } // Bilinear -inline void lerp_rgb(float* out, const float* a, const float* b, const float* c, - const float* d, const float* y, const float* z) +inline void lerp_rgb( + float *out, + const float *a, + const float *b, + const float *c, + const float *d, + const float *y, + const float *z ) { - float v1[3]; - float v2[3]; - lerp_rgb(v1, a, b, z); - lerp_rgb(v2, c, d, z); - lerp_rgb(out, v1, v2, y); + float v1[ 3 ]; + float v2[ 3 ]; + lerp_rgb( v1, a, b, z ); + lerp_rgb( v2, c, d, z ); + lerp_rgb( out, v1, v2, y ); } // Trilinear -inline void lerp_rgb(float* out, const float* a, const float* b, const float* c, const float* d, - const float* e, const float* f, const float* g, const float* h, - const float* x, const float* y, const float* z) +inline void lerp_rgb( + float *out, + const float *a, + const float *b, + const float *c, + const float *d, + const float *e, + const float *f, + const float *g, + const float *h, + const float *x, + const float *y, + const float *z ) { - float v1[3]; - float v2[3]; - lerp_rgb(v1, a,b,c,d,y,z); - lerp_rgb(v2, e,f,g,h,y,z); - lerp_rgb(out, v1, v2, x); + float v1[ 3 ]; + float v2[ 3 ]; + lerp_rgb( v1, a, b, c, d, y, z ); + lerp_rgb( v2, e, f, g, h, y, z ); + lerp_rgb( out, v1, v2, x ); } inline float ClampAndSanitize( float a, float min, float max ) { #ifndef __FAST_MATH__ - return std::isfinite( a ) ? std::min(std::max(min, a), max) : min; + return std::isfinite( a ) ? std::min( std::max( min, a ), max ) : min; #else - return std::min(std::max(min, a), max); + return std::min( std::max( min, a ), max ); #endif } @@ -241,219 +281,227 @@ inline float ClampAndSanitize( float a, float min, float max ) // https://github.com/AcademySoftwareFoundation/OpenColorIO/ops/lut3d/Lut3DOpCPU.cpp // License available in their repo and in our LICENSE file. -inline glm::vec3 ApplyLut3D_Trilinear( const lut3d_t & lut3d, const glm::vec3 & input ) +inline glm::vec3 +ApplyLut3D_Trilinear( const lut3d_t &lut3d, const glm::vec3 &input ) { - const float dimMinusOne = float(lut3d.lutEdgeSize) - 1.f; + const float dimMinusOne = float( lut3d.lutEdgeSize ) - 1.f; - float idx[3]; - idx[0] = input.r * dimMinusOne; - idx[1] = input.g * dimMinusOne; - idx[2] = input.b * dimMinusOne; + float idx[ 3 ]; + idx[ 0 ] = input.r * dimMinusOne; + idx[ 1 ] = input.g * dimMinusOne; + idx[ 2 ] = input.b * dimMinusOne; // NaNs become 0. - idx[0] = ClampAndSanitize(idx[0], 0.f, dimMinusOne); - idx[1] = ClampAndSanitize(idx[1], 0.f, dimMinusOne); - idx[2] = ClampAndSanitize(idx[2], 0.f, dimMinusOne); + idx[ 0 ] = ClampAndSanitize( idx[ 0 ], 0.f, dimMinusOne ); + idx[ 1 ] = ClampAndSanitize( idx[ 1 ], 0.f, dimMinusOne ); + idx[ 2 ] = ClampAndSanitize( idx[ 2 ], 0.f, dimMinusOne ); - int indexLow[3]; - indexLow[0] = static_cast(std::floor(idx[0])); - indexLow[1] = static_cast(std::floor(idx[1])); - indexLow[2] = static_cast(std::floor(idx[2])); + int indexLow[ 3 ]; + indexLow[ 0 ] = static_cast( std::floor( idx[ 0 ] ) ); + indexLow[ 1 ] = static_cast( std::floor( idx[ 1 ] ) ); + indexLow[ 2 ] = static_cast( std::floor( idx[ 2 ] ) ); - int indexHigh[3]; + int indexHigh[ 3 ]; // When the idx is exactly equal to an index (e.g. 0,1,2...) // then the computation of highIdx is wrong. However, // the delta is then equal to zero (e.g. idx-lowIdx), // so the highIdx has no impact. - indexHigh[0] = static_cast(std::ceil(idx[0])); - indexHigh[1] = static_cast(std::ceil(idx[1])); - indexHigh[2] = static_cast(std::ceil(idx[2])); + indexHigh[ 0 ] = static_cast( std::ceil( idx[ 0 ] ) ); + indexHigh[ 1 ] = static_cast( std::ceil( idx[ 1 ] ) ); + indexHigh[ 2 ] = static_cast( std::ceil( idx[ 2 ] ) ); - float delta[3]; - delta[0] = idx[0] - static_cast(indexLow[0]); - delta[1] = idx[1] - static_cast(indexLow[1]); - delta[2] = idx[2] - static_cast(indexLow[2]); + float delta[ 3 ]; + delta[ 0 ] = idx[ 0 ] - static_cast( indexLow[ 0 ] ); + delta[ 1 ] = idx[ 1 ] - static_cast( indexLow[ 1 ] ); + delta[ 2 ] = idx[ 2 ] - static_cast( indexLow[ 2 ] ); // Compute index into LUT for surrounding corners - const int n000 = - GetLut3DIndexRedFastRGB(indexLow[0], indexLow[1], indexLow[2], lut3d.lutEdgeSize); - const int n100 = - GetLut3DIndexRedFastRGB(indexHigh[0], indexLow[1], indexLow[2], lut3d.lutEdgeSize); - const int n010 = - GetLut3DIndexRedFastRGB(indexLow[0], indexHigh[1], indexLow[2], lut3d.lutEdgeSize); - const int n001 = - GetLut3DIndexRedFastRGB(indexLow[0], indexLow[1], indexHigh[2], lut3d.lutEdgeSize); - const int n110 = - GetLut3DIndexRedFastRGB(indexHigh[0], indexHigh[1], indexLow[2], lut3d.lutEdgeSize); - const int n101 = - GetLut3DIndexRedFastRGB(indexHigh[0], indexLow[1], indexHigh[2], lut3d.lutEdgeSize); - const int n011 = - GetLut3DIndexRedFastRGB(indexLow[0], indexHigh[1], indexHigh[2], lut3d.lutEdgeSize); - const int n111 = - GetLut3DIndexRedFastRGB(indexHigh[0], indexHigh[1], indexHigh[2], lut3d.lutEdgeSize); - - float x[3], y[3], z[3]; - x[0] = delta[0]; x[1] = delta[0]; x[2] = delta[0]; - y[0] = delta[1]; y[1] = delta[1]; y[2] = delta[1]; - z[0] = delta[2]; z[1] = delta[2]; z[2] = delta[2]; + const int n000 = GetLut3DIndexRedFastRGB( + indexLow[ 0 ], indexLow[ 1 ], indexLow[ 2 ], lut3d.lutEdgeSize ); + const int n100 = GetLut3DIndexRedFastRGB( + indexHigh[ 0 ], indexLow[ 1 ], indexLow[ 2 ], lut3d.lutEdgeSize ); + const int n010 = GetLut3DIndexRedFastRGB( + indexLow[ 0 ], indexHigh[ 1 ], indexLow[ 2 ], lut3d.lutEdgeSize ); + const int n001 = GetLut3DIndexRedFastRGB( + indexLow[ 0 ], indexLow[ 1 ], indexHigh[ 2 ], lut3d.lutEdgeSize ); + const int n110 = GetLut3DIndexRedFastRGB( + indexHigh[ 0 ], indexHigh[ 1 ], indexLow[ 2 ], lut3d.lutEdgeSize ); + const int n101 = GetLut3DIndexRedFastRGB( + indexHigh[ 0 ], indexLow[ 1 ], indexHigh[ 2 ], lut3d.lutEdgeSize ); + const int n011 = GetLut3DIndexRedFastRGB( + indexLow[ 0 ], indexHigh[ 1 ], indexHigh[ 2 ], lut3d.lutEdgeSize ); + const int n111 = GetLut3DIndexRedFastRGB( + indexHigh[ 0 ], indexHigh[ 1 ], indexHigh[ 2 ], lut3d.lutEdgeSize ); + + float x[ 3 ], y[ 3 ], z[ 3 ]; + x[ 0 ] = delta[ 0 ]; + x[ 1 ] = delta[ 0 ]; + x[ 2 ] = delta[ 0 ]; + y[ 0 ] = delta[ 1 ]; + y[ 1 ] = delta[ 1 ]; + y[ 2 ] = delta[ 1 ]; + z[ 0 ] = delta[ 2 ]; + z[ 1 ] = delta[ 2 ]; + z[ 2 ] = delta[ 2 ]; glm::vec3 out; - lerp_rgb((float *) &out, - (float *) &lut3d.data[n000].r, (float *) &lut3d.data[n001].r, - (float *) &lut3d.data[n010].r, (float *) &lut3d.data[n011].r, - (float *) &lut3d.data[n100].r, (float *) &lut3d.data[n101].r, - (float *) &lut3d.data[n110].r, (float *) &lut3d.data[n111].r, - x, y, z); + lerp_rgb( + ( float * )&out, + ( float * )&lut3d.data[ n000 ].r, + ( float * )&lut3d.data[ n001 ].r, + ( float * )&lut3d.data[ n010 ].r, + ( float * )&lut3d.data[ n011 ].r, + ( float * )&lut3d.data[ n100 ].r, + ( float * )&lut3d.data[ n101 ].r, + ( float * )&lut3d.data[ n110 ].r, + ( float * )&lut3d.data[ n111 ].r, + x, + y, + z ); return out; } -inline glm::vec3 ApplyLut3D_Tetrahedral( const lut3d_t & lut3d, const glm::vec3 & input ) +inline glm::vec3 +ApplyLut3D_Tetrahedral( const lut3d_t &lut3d, const glm::vec3 &input ) { - const float dimMinusOne = float(lut3d.lutEdgeSize) - 1.f; + const float dimMinusOne = float( lut3d.lutEdgeSize ) - 1.f; - float idx[3]; - idx[0] = input.r * dimMinusOne; - idx[1] = input.g * dimMinusOne; - idx[2] = input.b * dimMinusOne; + float idx[ 3 ]; + idx[ 0 ] = input.r * dimMinusOne; + idx[ 1 ] = input.g * dimMinusOne; + idx[ 2 ] = input.b * dimMinusOne; // NaNs become 0. - idx[0] = ClampAndSanitize(idx[0], 0.f, dimMinusOne); - idx[1] = ClampAndSanitize(idx[1], 0.f, dimMinusOne); - idx[2] = ClampAndSanitize(idx[2], 0.f, dimMinusOne); + idx[ 0 ] = ClampAndSanitize( idx[ 0 ], 0.f, dimMinusOne ); + idx[ 1 ] = ClampAndSanitize( idx[ 1 ], 0.f, dimMinusOne ); + idx[ 2 ] = ClampAndSanitize( idx[ 2 ], 0.f, dimMinusOne ); - int indexLow[3]; - indexLow[0] = static_cast(std::floor(idx[0])); - indexLow[1] = static_cast(std::floor(idx[1])); - indexLow[2] = static_cast(std::floor(idx[2])); + int indexLow[ 3 ]; + indexLow[ 0 ] = static_cast( std::floor( idx[ 0 ] ) ); + indexLow[ 1 ] = static_cast( std::floor( idx[ 1 ] ) ); + indexLow[ 2 ] = static_cast( std::floor( idx[ 2 ] ) ); - int indexHigh[3]; + int indexHigh[ 3 ]; // When the idx is exactly equal to an index (e.g. 0,1,2...) // then the computation of highIdx is wrong. However, // the delta is then equal to zero (e.g. idx-lowIdx), // so the highIdx has no impact. - indexHigh[0] = static_cast(std::ceil(idx[0])); - indexHigh[1] = static_cast(std::ceil(idx[1])); - indexHigh[2] = static_cast(std::ceil(idx[2])); + indexHigh[ 0 ] = static_cast( std::ceil( idx[ 0 ] ) ); + indexHigh[ 1 ] = static_cast( std::ceil( idx[ 1 ] ) ); + indexHigh[ 2 ] = static_cast( std::ceil( idx[ 2 ] ) ); - float fx = idx[0] - static_cast(indexLow[0]); - float fy = idx[1] - static_cast(indexLow[1]); - float fz = idx[2] - static_cast(indexLow[2]); + float fx = idx[ 0 ] - static_cast( indexLow[ 0 ] ); + float fy = idx[ 1 ] - static_cast( indexLow[ 1 ] ); + float fz = idx[ 2 ] - static_cast( indexLow[ 2 ] ); // Compute index into LUT for surrounding corners - const int n000 = - GetLut3DIndexRedFastRGB(indexLow[0], indexLow[1], indexLow[2], lut3d.lutEdgeSize); - const int n100 = - GetLut3DIndexRedFastRGB(indexHigh[0], indexLow[1], indexLow[2], lut3d.lutEdgeSize); - const int n010 = - GetLut3DIndexRedFastRGB(indexLow[0], indexHigh[1], indexLow[2], lut3d.lutEdgeSize); - const int n001 = - GetLut3DIndexRedFastRGB(indexLow[0], indexLow[1], indexHigh[2], lut3d.lutEdgeSize); - const int n110 = - GetLut3DIndexRedFastRGB(indexHigh[0], indexHigh[1], indexLow[2], lut3d.lutEdgeSize); - const int n101 = - GetLut3DIndexRedFastRGB(indexHigh[0], indexLow[1], indexHigh[2], lut3d.lutEdgeSize); - const int n011 = - GetLut3DIndexRedFastRGB(indexLow[0], indexHigh[1], indexHigh[2], lut3d.lutEdgeSize); - const int n111 = - GetLut3DIndexRedFastRGB(indexHigh[0], indexHigh[1], indexHigh[2], lut3d.lutEdgeSize); + const int n000 = GetLut3DIndexRedFastRGB( + indexLow[ 0 ], indexLow[ 1 ], indexLow[ 2 ], lut3d.lutEdgeSize ); + const int n100 = GetLut3DIndexRedFastRGB( + indexHigh[ 0 ], indexLow[ 1 ], indexLow[ 2 ], lut3d.lutEdgeSize ); + const int n010 = GetLut3DIndexRedFastRGB( + indexLow[ 0 ], indexHigh[ 1 ], indexLow[ 2 ], lut3d.lutEdgeSize ); + const int n001 = GetLut3DIndexRedFastRGB( + indexLow[ 0 ], indexLow[ 1 ], indexHigh[ 2 ], lut3d.lutEdgeSize ); + const int n110 = GetLut3DIndexRedFastRGB( + indexHigh[ 0 ], indexHigh[ 1 ], indexLow[ 2 ], lut3d.lutEdgeSize ); + const int n101 = GetLut3DIndexRedFastRGB( + indexHigh[ 0 ], indexLow[ 1 ], indexHigh[ 2 ], lut3d.lutEdgeSize ); + const int n011 = GetLut3DIndexRedFastRGB( + indexLow[ 0 ], indexHigh[ 1 ], indexHigh[ 2 ], lut3d.lutEdgeSize ); + const int n111 = GetLut3DIndexRedFastRGB( + indexHigh[ 0 ], indexHigh[ 1 ], indexHigh[ 2 ], lut3d.lutEdgeSize ); glm::vec3 out; - if (fx > fy) { - if (fy > fz) { - out = - (1 - fx) * lut3d.data[n000] + - (fx - fy) * lut3d.data[n100] + - (fy - fz) * lut3d.data[n110] + - (fz) * lut3d.data[n111]; + if ( fx > fy ) + { + if ( fy > fz ) + { + out = ( 1 - fx ) * lut3d.data[ n000 ] + + ( fx - fy ) * lut3d.data[ n100 ] + + ( fy - fz ) * lut3d.data[ n110 ] + ( fz )*lut3d.data[ n111 ]; } - else if (fx > fz) + else if ( fx > fz ) { - out = - (1 - fx) * lut3d.data[n000] + - (fx - fz) * lut3d.data[n100] + - (fz - fy) * lut3d.data[n101] + - (fy) * lut3d.data[n111]; + out = ( 1 - fx ) * lut3d.data[ n000 ] + + ( fx - fz ) * lut3d.data[ n100 ] + + ( fz - fy ) * lut3d.data[ n101 ] + ( fy )*lut3d.data[ n111 ]; } else { - out = - (1 - fz) * lut3d.data[n000] + - (fz - fx) * lut3d.data[n001] + - (fx - fy) * lut3d.data[n101] + - (fy) * lut3d.data[n111]; + out = ( 1 - fz ) * lut3d.data[ n000 ] + + ( fz - fx ) * lut3d.data[ n001 ] + + ( fx - fy ) * lut3d.data[ n101 ] + ( fy )*lut3d.data[ n111 ]; } } else { - if (fz > fy) + if ( fz > fy ) { - out = - (1 - fz) * lut3d.data[n000] + - (fz - fy) * lut3d.data[n001] + - (fy - fx) * lut3d.data[n011] + - (fx) * lut3d.data[n111]; + out = ( 1 - fz ) * lut3d.data[ n000 ] + + ( fz - fy ) * lut3d.data[ n001 ] + + ( fy - fx ) * lut3d.data[ n011 ] + ( fx )*lut3d.data[ n111 ]; } - else if (fz > fx) + else if ( fz > fx ) { - out = - (1 - fy) * lut3d.data[n000] + - (fy - fz) * lut3d.data[n010] + - (fz - fx) * lut3d.data[n011] + - (fx) * lut3d.data[n111]; + out = ( 1 - fy ) * lut3d.data[ n000 ] + + ( fy - fz ) * lut3d.data[ n010 ] + + ( fz - fx ) * lut3d.data[ n011 ] + ( fx )*lut3d.data[ n111 ]; } else { - out = - (1 - fy) * lut3d.data[n000] + - (fy - fx) * lut3d.data[n010] + - (fx - fz) * lut3d.data[n110] + - (fz) * lut3d.data[n111]; + out = ( 1 - fy ) * lut3d.data[ n000 ] + + ( fy - fx ) * lut3d.data[ n010 ] + + ( fx - fz ) * lut3d.data[ n110 ] + ( fz )*lut3d.data[ n111 ]; } } return out; } - -inline glm::vec3 ApplyLut1D_Linear( const lut1d_t & lut, const glm::vec3 & input ) +inline glm::vec3 ApplyLut1D_Linear( const lut1d_t &lut, const glm::vec3 &input ) { - const float dimMinusOne = float(lut.lutSize) - 1.f; - float idx[3]; - idx[0] = input.r * dimMinusOne; - idx[1] = input.g * dimMinusOne; - idx[2] = input.b * dimMinusOne; + const float dimMinusOne = float( lut.lutSize ) - 1.f; + float idx[ 3 ]; + idx[ 0 ] = input.r * dimMinusOne; + idx[ 1 ] = input.g * dimMinusOne; + idx[ 2 ] = input.b * dimMinusOne; // NaNs become 0. - idx[0] = ClampAndSanitize(idx[0], 0.f, dimMinusOne); - idx[1] = ClampAndSanitize(idx[1], 0.f, dimMinusOne); - idx[2] = ClampAndSanitize(idx[2], 0.f, dimMinusOne); + idx[ 0 ] = ClampAndSanitize( idx[ 0 ], 0.f, dimMinusOne ); + idx[ 1 ] = ClampAndSanitize( idx[ 1 ], 0.f, dimMinusOne ); + idx[ 2 ] = ClampAndSanitize( idx[ 2 ], 0.f, dimMinusOne ); - int indexLow[3]; - indexLow[0] = static_cast(std::floor(idx[0])); - indexLow[1] = static_cast(std::floor(idx[1])); - indexLow[2] = static_cast(std::floor(idx[2])); + int indexLow[ 3 ]; + indexLow[ 0 ] = static_cast( std::floor( idx[ 0 ] ) ); + indexLow[ 1 ] = static_cast( std::floor( idx[ 1 ] ) ); + indexLow[ 2 ] = static_cast( std::floor( idx[ 2 ] ) ); - int indexHigh[3]; + int indexHigh[ 3 ]; // When the idx is exactly equal to an index (e.g. 0,1,2...) // then the computation of highIdx is wrong. However, // the delta is then equal to zero (e.g. idx-lowIdx), // so the highIdx has no impact. - indexHigh[0] = static_cast(std::ceil(idx[0])); - indexHigh[1] = static_cast(std::ceil(idx[1])); - indexHigh[2] = static_cast(std::ceil(idx[2])); - - float delta[3]; - delta[0] = idx[0] - static_cast(indexLow[0]); - delta[1] = idx[1] - static_cast(indexLow[1]); - delta[2] = idx[2] - static_cast(indexLow[2]); - - float vLow[3] = { lut.dataR[indexLow[0]], lut.dataG[indexLow[1]], lut.dataB[indexLow[2]] }; - float vHigh[3] = { lut.dataR[indexHigh[0]], lut.dataG[indexHigh[1]], lut.dataB[indexHigh[2]] }; + indexHigh[ 0 ] = static_cast( std::ceil( idx[ 0 ] ) ); + indexHigh[ 1 ] = static_cast( std::ceil( idx[ 1 ] ) ); + indexHigh[ 2 ] = static_cast( std::ceil( idx[ 2 ] ) ); + + float delta[ 3 ]; + delta[ 0 ] = idx[ 0 ] - static_cast( indexLow[ 0 ] ); + delta[ 1 ] = idx[ 1 ] - static_cast( indexLow[ 1 ] ); + delta[ 2 ] = idx[ 2 ] - static_cast( indexLow[ 2 ] ); + + float vLow[ 3 ] = { lut.dataR[ indexLow[ 0 ] ], + lut.dataG[ indexLow[ 1 ] ], + lut.dataB[ indexLow[ 2 ] ] }; + float vHigh[ 3 ] = { lut.dataR[ indexHigh[ 0 ] ], + lut.dataG[ indexHigh[ 1 ] ], + lut.dataB[ indexHigh[ 2 ] ] }; glm::vec3 out; - lerp_rgb( (float *) &out, vLow, vHigh, delta ); + lerp_rgb( ( float * )&out, vLow, vHigh, delta ); return out; } @@ -466,45 +514,46 @@ inline glm::vec3 ApplyLut1D_Linear( const lut1d_t & lut, const glm::vec3 & input // val: The value to invert. // Return the result that would produce val if used // in a forward linear interpolation in the LUT. -inline float FindLutInv(const float * start, - const float startOffset, - const float * end, - const float scale, - const float val) +inline float FindLutInv( + const float *start, + const float startOffset, + const float *end, + const float scale, + const float val ) { - // Note that the LUT data pointed to by start/end must be in increasing order, - // regardless of whether the original LUT was increasing or decreasing because - // this function uses std::lower_bound(). + // Note that the LUT data pointed to by start/end must be in increasing + // order, regardless of whether the original LUT was increasing or + // decreasing because this function uses std::lower_bound(). // Clamp the value to the range of the LUT. const float cv = std::min( std::max( val, *start ), *end ); // std::lower_bound() - // "Returns an iterator pointing to the first element in the range [first,last) - // which does not compare less than val (but could be equal)." - // (NB: This is correct using either end or end+1 since lower_bound will return a - // value one greater than the second argument if no values in the array are >= cv.) + // "Returns an iterator pointing to the first element in the range + // [first,last) which does not compare less than val (but could be equal)." + // (NB: This is correct using either end or end+1 since lower_bound will + // return a + // value one greater than the second argument if no values in the array are + // >= cv.) // http://www.sgi.com/tech/stl/lower_bound.html - const float* lowbound = std::lower_bound(start, end, cv); + const float *lowbound = std::lower_bound( start, end, cv ); - // lower_bound() returns first entry >= val so decrement it unless val == *start. - if (lowbound > start) { - --lowbound; - } + // lower_bound() returns first entry >= val so decrement it unless val == + // *start. + if ( lowbound > start ) { --lowbound; } - const float* highbound = lowbound; - if (highbound < end) { - ++highbound; - } + const float *highbound = lowbound; + if ( highbound < end ) { ++highbound; } // Delta is the fractional distance of val between the adjacent LUT entries. float delta = 0.f; - if (*highbound > *lowbound) { // (handle flat spots by leaving delta = 0) - delta = (cv - *lowbound) / (*highbound - *lowbound); + if ( *highbound > *lowbound ) + { // (handle flat spots by leaving delta = 0) + delta = ( cv - *lowbound ) / ( *highbound - *lowbound ); } // Inds is the index difference from the effective start to lowbound. - const float inds = (float)( lowbound - start ); + const float inds = ( float )( lowbound - start ); // Correct for the fact that start is not the beginning of the LUT if it // starts with a flat spot. @@ -514,97 +563,114 @@ inline float FindLutInv(const float * start, const float totalInds = inds + startOffset; // Scale converts from units of [0,dim] to [0,outDepth]. - return (totalInds + delta) * scale; + return ( totalInds + delta ) * scale; } -int FindNonFlatStartIndex( const std::vector & data ) +int FindNonFlatStartIndex( const std::vector &data ) { - if ( !data.empty() ) + if ( !data.empty( ) ) { - for ( size_t nIndex = 1; nIndex < data.size(); ++nIndex ) + for ( size_t nIndex = 1; nIndex < data.size( ); ++nIndex ) { - if ( data[nIndex] != data[0] ) - { - return nIndex - 1; - } + if ( data[ nIndex ] != data[ 0 ] ) { return nIndex - 1; } } } return 0; } -void lut1d_t::finalize() +void lut1d_t::finalize( ) { startIndexR = FindNonFlatStartIndex( dataR ); startIndexG = FindNonFlatStartIndex( dataG ); startIndexB = FindNonFlatStartIndex( dataB ); } -inline glm::vec3 ApplyLut1D_Inverse_Linear( const lut1d_t & lut, const glm::vec3 & input ) +inline glm::vec3 +ApplyLut1D_Inverse_Linear( const lut1d_t &lut, const glm::vec3 &input ) { // Disallow inverse if not finalized - if ( lut.startIndexR < 0 ) - { - return glm::vec3( -1.f ); - } + if ( lut.startIndexR < 0 ) { return glm::vec3( -1.f ); } return glm::vec3( - FindLutInv( lut.dataR.data() + lut.startIndexR, lut.startIndexR, lut.dataR.data() + lut.dataR.size() - 1, 1.f / ( lut.dataR.size() - 1.f ), input.r ), - FindLutInv( lut.dataG.data() + lut.startIndexG, lut.startIndexG, lut.dataG.data() + lut.dataG.size() - 1, 1.f / ( lut.dataG.size() - 1.f ), input.g ), - FindLutInv( lut.dataB.data() + lut.startIndexB, lut.startIndexB, lut.dataB.data() + lut.dataB.size() - 1, 1.f / ( lut.dataB.size() - 1.f ), input.b ) ); + FindLutInv( + lut.dataR.data( ) + lut.startIndexR, + lut.startIndexR, + lut.dataR.data( ) + lut.dataR.size( ) - 1, + 1.f / ( lut.dataR.size( ) - 1.f ), + input.r ), + FindLutInv( + lut.dataG.data( ) + lut.startIndexG, + lut.startIndexG, + lut.dataG.data( ) + lut.dataG.size( ) - 1, + 1.f / ( lut.dataG.size( ) - 1.f ), + input.g ), + FindLutInv( + lut.dataB.data( ) + lut.startIndexB, + lut.startIndexB, + lut.dataB.data( ) + lut.dataB.size( ) - 1, + 1.f / ( lut.dataB.size( ) - 1.f ), + input.b ) ); } -inline glm::vec3 hsv_to_rgb( const glm::vec3 & hsv ) +inline glm::vec3 hsv_to_rgb( const glm::vec3 &hsv ) { - if ( fabsf( hsv.y ) < std::numeric_limits::min() ) + if ( fabsf( hsv.y ) < std::numeric_limits::min( ) ) { - return glm::vec3( hsv.z ) ; + return glm::vec3( hsv.z ); } float flHue = positive_mod( hsv.x, 1.f ); flHue *= 6.f; - int i = flHue; // integer part - float f = flHue - i; // fractional part + int i = flHue; // integer part + float f = flHue - i; // fractional part - float p = hsv.z * ( 1.f - hsv.y ); - float q = hsv.z * ( 1.f - hsv.y * f ); - float t = hsv.z * ( 1.f - hsv.y * ( 1.f - f ) ); + float p = hsv.z * ( 1.f - hsv.y ); + float q = hsv.z * ( 1.f - hsv.y * f ); + float t = hsv.z * ( 1.f - hsv.y * ( 1.f - f ) ); - switch(i) - { - case 0: return glm::vec3( hsv.z, t, p ); break; - case 1: return glm::vec3( q, hsv.z, p ); break; - case 2: return glm::vec3( p, hsv.z, t ); break; - case 3: return glm::vec3( p, q, hsv.z ); break; - case 4: return glm::vec3( t, p, hsv.z ); break; - case 5: return glm::vec3( hsv.z, p, q ); break; - } + switch ( i ) + { + case 0: + return glm::vec3( hsv.z, t, p ); + break; + case 1: + return glm::vec3( q, hsv.z, p ); + break; + case 2: + return glm::vec3( p, hsv.z, t ); + break; + case 3: + return glm::vec3( p, q, hsv.z ); + break; + case 4: + return glm::vec3( t, p, hsv.z ); + break; + case 5: + return glm::vec3( hsv.z, p, q ); + break; + } return glm::vec3( 0 ); } - -inline glm::vec3 rgb_to_hsv( const glm::vec3 & rgb ) +inline glm::vec3 rgb_to_hsv( const glm::vec3 &rgb ) { - float flMax = std::max( std::max( rgb.x, rgb.y ), rgb.z ); - float flMin = std::min( std::min( rgb.x, rgb.y ), rgb.z ); + float flMax = std::max( std::max( rgb.x, rgb.y ), rgb.z ); + float flMin = std::min( std::min( rgb.x, rgb.y ), rgb.z ); float flDelta = flMax - flMin; glm::vec3 hsv; - hsv.y = ( fabsf( flMax ) < std::numeric_limits::min() ) ? 0.f : flDelta / flMax; + hsv.y = ( fabsf( flMax ) < std::numeric_limits::min( ) ) + ? 0.f + : flDelta / flMax; hsv.z = flMax; - if (hsv.y == 0.f) - { - hsv.x = -1.0f; - } + if ( hsv.y == 0.f ) { hsv.x = -1.0f; } else { - if ( rgb.x == flMax ) - { - hsv.x = (rgb.y - rgb.z) / flDelta; - } + if ( rgb.x == flMax ) { hsv.x = ( rgb.y - rgb.z ) / flDelta; } else if ( rgb.y == flMax ) { hsv.x = 2.f + ( rgb.z - rgb.x ) / flDelta; @@ -616,53 +682,49 @@ inline glm::vec3 rgb_to_hsv( const glm::vec3 & rgb ) hsv.x /= 6.f; - if ( hsv.x < 0.f ) - { - hsv.x += 1.f; - } + if ( hsv.x < 0.f ) { hsv.x += 1.f; } } return hsv; } -bool BOutOfGamut( const glm::vec3 & color ) +bool BOutOfGamut( const glm::vec3 &color ) { - return ( color.x<0.f || color.x > 1.f || color.y<0.f || color.y > 1.f || color.z<0.f || color.z > 1.f ); + return ( + color.x < 0.f || color.x > 1.f || color.y < 0.f || color.y > 1.f || + color.z < 0.f || color.z > 1.f ); } -template -inline T calcEOTFToLinear( const T & input, EOTF eotf, const tonemapping_t & tonemapping ) +template +inline T +calcEOTFToLinear( const T &input, EOTF eotf, const tonemapping_t &tonemapping ) { if ( eotf == EOTF_Gamma22 ) { return glm::pow( input, T( 2.2f ) ) * tonemapping.g22_luminance; } - else if ( eotf == EOTF_PQ ) - { - return pq_to_nits( input ); - } + else if ( eotf == EOTF_PQ ) { return pq_to_nits( input ); } - return T(0); + return T( 0 ); } -template -inline T calcLinearToEOTF( const T & input, EOTF eotf, const tonemapping_t & tonemapping ) +template +inline T +calcLinearToEOTF( const T &input, EOTF eotf, const tonemapping_t &tonemapping ) { if ( eotf == EOTF_Gamma22 ) { T val = input; if ( tonemapping.g22_luminance > 0.f ) { - val = glm::clamp( input / tonemapping.g22_luminance, T( 0.f ), T( 1.f ) ); + val = glm::clamp( + input / tonemapping.g22_luminance, T( 0.f ), T( 1.f ) ); } - return glm::pow( val, T( 1.f/2.2f ) ); - } - else if ( eotf == EOTF_PQ ) - { - return T( nits_to_pq(input) ); + return glm::pow( val, T( 1.f / 2.2f ) ); } + else if ( eotf == EOTF_PQ ) { return T( nits_to_pq( input ) ); } - return T(0); + return T( 0 ); } // input is from 0->1 @@ -670,8 +732,13 @@ inline T calcLinearToEOTF( const T & input, EOTF eotf, const tonemapping_t & ton bool g_bUseSourceEOTFForShaper = false; -template -inline T applyShaper( const T & input, EOTF source, EOTF dest, const tonemapping_t & tonemapping, float flGain ) +template +inline T applyShaper( + const T &input, + EOTF source, + EOTF dest, + const tonemapping_t &tonemapping, + float flGain ) { if ( ( source == dest && flGain == 1.f ) || !tonemapping.bUseShaper ) { @@ -679,128 +746,183 @@ inline T applyShaper( const T & input, EOTF source, EOTF dest, const tonemapping } T flLinear = flGain * calcEOTFToLinear( input, source, tonemapping ); - flLinear = tonemapping.apply( flLinear ); + flLinear = tonemapping.apply( flLinear ); - return calcLinearToEOTF( flLinear, g_bUseSourceEOTFForShaper ? source : dest, tonemapping ); + return calcLinearToEOTF( + flLinear, g_bUseSourceEOTFForShaper ? source : dest, tonemapping ); } bool g_bHuePreservationWhenClipping = false; -template -void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, - lut3d_t * pLut3d, - const displaycolorimetry_t & source, EOTF sourceEOTF, - const displaycolorimetry_t & dest, EOTF destEOTF, - const glm::vec2 & destVirtualWhite, EChromaticAdaptationMethod eMethod, - const colormapping_t & mapping, const nightmode_t & nightmode, const tonemapping_t & tonemapping, - const lut3d_t * pLook, float flGain ) +template +void calcColorTransform( + lut1d_t *pShaper, + int nLutSize1d, + lut3d_t *pLut3d, + const displaycolorimetry_t &source, + EOTF sourceEOTF, + const displaycolorimetry_t &dest, + EOTF destEOTF, + const glm::vec2 &destVirtualWhite, + EChromaticAdaptationMethod eMethod, + const colormapping_t &mapping, + const nightmode_t &nightmode, + const tonemapping_t &tonemapping, + const lut3d_t *pLook, + float flGain ) { // Generate shaper lut - // Note: while this is typically a 1D approximation of our end to end transform, - // it need not be! Conceptually this is just to determine the interpolation properties... - // The 3d lut should be considered a 'matched' pair where the transform is only complete - // when applying both. I.e., you can put ANY transform in here, and it should work. - - static constexpr int32_t nLutEdgeSize3d = static_cast(lutEdgeSize3d); + // Note: while this is typically a 1D approximation of our end to end + // transform, it need not be! Conceptually this is just to determine the + // interpolation properties... The 3d lut should be considered a 'matched' + // pair where the transform is only complete when applying both. I.e., you + // can put ANY transform in here, and it should work. + + static constexpr int32_t nLutEdgeSize3d = + static_cast( lutEdgeSize3d ); if ( pShaper ) { - float flScale = 1.f / ( (float) nLutSize1d - 1.f ); + float flScale = 1.f / ( ( float )nLutSize1d - 1.f ); pShaper->resize( nLutSize1d ); - for ( int nVal=0; nValdataR[nVal] = shapedSourceColor.r; - pShaper->dataG[nVal] = shapedSourceColor.g; - pShaper->dataB[nVal] = shapedSourceColor.b; + glm::vec3 sourceColorEOTFEncoded = { nVal * flScale, + nVal * flScale, + nVal * flScale }; + glm::vec3 shapedSourceColor = applyShaper( + sourceColorEOTFEncoded, + sourceEOTF, + destEOTF, + tonemapping, + flGain ); + pShaper->dataR[ nVal ] = shapedSourceColor.r; + pShaper->dataG[ nVal ] = shapedSourceColor.g; + pShaper->dataB[ nVal ] = shapedSourceColor.b; } - pShaper->finalize(); + pShaper->finalize( ); } if ( pLut3d ) { - glm::mat3 xyz_from_dest = normalised_primary_matrix( dest.primaries, dest.white, 1.f ); + glm::mat3 xyz_from_dest = + normalised_primary_matrix( dest.primaries, dest.white, 1.f ); glm::mat3 dest_from_xyz = glm::inverse( xyz_from_dest ); - glm::mat3 xyz_from_source = normalised_primary_matrix( source.primaries, source.white, 1.f ); - glm::mat3 dest_from_source = dest_from_xyz * xyz_from_source; // XYZ scaling for white point adjustment + glm::mat3 xyz_from_source = + normalised_primary_matrix( source.primaries, source.white, 1.f ); + glm::mat3 dest_from_source = + dest_from_xyz * + xyz_from_source; // XYZ scaling for white point adjustment // Precalc night mode scalars & digital gain - // amount and saturation are overdetermined but we separate the two as they conceptually represent - // different quantities, and this preserves forwards algorithmic compatibility - glm::vec3 nightModeMultHSV( nightmode.hue, clamp01( nightmode.saturation * nightmode.amount ), 1.f ); - glm::vec3 vMultLinear = glm::pow( hsv_to_rgb( nightModeMultHSV ), glm::vec3( 2.2f ) ); + // amount and saturation are overdetermined but we separate the two as + // they conceptually represent different quantities, and this preserves + // forwards algorithmic compatibility + glm::vec3 nightModeMultHSV( + nightmode.hue, + clamp01( nightmode.saturation * nightmode.amount ), + 1.f ); + glm::vec3 vMultLinear = + glm::pow( hsv_to_rgb( nightModeMultHSV ), glm::vec3( 2.2f ) ); vMultLinear = vMultLinear * flGain; // Calculate the virtual white point adaptation glm::mat3x3 whitePointDestAdaptation = glm::mat3x3( 1.f ); // identity if ( destVirtualWhite.x > 0.01f && destVirtualWhite.y > 0.01f ) { - // if source white is within tiny tolerance of sourceWhitePointOverride - // don't do the override? (aka two quantizations of d65) - glm::mat3x3 virtualWhiteXYZFromPhysicalWhiteXYZ = chromatic_adaptation_matrix( - xy_to_xyz( dest.white ), xy_to_xyz( destVirtualWhite ), eMethod ); - whitePointDestAdaptation = dest_from_xyz * virtualWhiteXYZFromPhysicalWhiteXYZ * xyz_from_dest; - - // Consider lerp-ing the gain limiting between 0-1? That would allow partial clipping - // so that contrast ratios wouldnt be sacrified too bad with alternate white points + // if source white is within tiny tolerance of + // sourceWhitePointOverride don't do the override? (aka two + // quantizations of d65) + glm::mat3x3 virtualWhiteXYZFromPhysicalWhiteXYZ = + chromatic_adaptation_matrix( + xy_to_xyz( dest.white ), + xy_to_xyz( destVirtualWhite ), + eMethod ); + whitePointDestAdaptation = dest_from_xyz * + virtualWhiteXYZFromPhysicalWhiteXYZ * + xyz_from_dest; + + // Consider lerp-ing the gain limiting between 0-1? That would allow + // partial clipping so that contrast ratios wouldnt be sacrified too + // bad with alternate white points static const bool k_bLimitGain = true; if ( k_bLimitGain ) { - glm::vec3 white = whitePointDestAdaptation * glm::vec3(1.f, 1.f, 1.f ); - float whiteMax = std::max( white.r, std::max( white.g, white.b ) ); + glm::vec3 white = + whitePointDestAdaptation * glm::vec3( 1.f, 1.f, 1.f ); + float whiteMax = + std::max( white.r, std::max( white.g, white.b ) ); float normScale = 1.f / whiteMax; - whitePointDestAdaptation = whitePointDestAdaptation * glm::diagonal3x3( glm::vec3( normScale ) ); + whitePointDestAdaptation = + whitePointDestAdaptation * + glm::diagonal3x3( glm::vec3( normScale ) ); } } // Precalculate source color EOTF encoded per-edge. - glm::vec3 vSourceColorEOTFEncodedEdge[nLutEdgeSize3d]; - float flEdgeScale = 1.f / ( (float) nLutEdgeSize3d - 1.f ); + glm::vec3 vSourceColorEOTFEncodedEdge[ nLutEdgeSize3d ]; + float flEdgeScale = 1.f / ( ( float )nLutEdgeSize3d - 1.f ); for ( int nIndex = 0; nIndex < nLutEdgeSize3d; ++nIndex ) { - vSourceColorEOTFEncodedEdge[nIndex] = glm::vec3( nIndex * flEdgeScale ); + vSourceColorEOTFEncodedEdge[ nIndex ] = + glm::vec3( nIndex * flEdgeScale ); if ( pShaper ) { - vSourceColorEOTFEncodedEdge[nIndex] = ApplyLut1D_Inverse_Linear( *pShaper, vSourceColorEOTFEncodedEdge[nIndex] ); + vSourceColorEOTFEncodedEdge[ nIndex ] = + ApplyLut1D_Inverse_Linear( + *pShaper, vSourceColorEOTFEncodedEdge[ nIndex ] ); } } pLut3d->resize( nLutEdgeSize3d ); - - for ( int nBlue=0; nBluedata.empty() ) + if ( pLook && !pLook->data.empty( ) ) { - sourceColorEOTFEncoded = ApplyLut3D_Tetrahedral( *pLook, sourceColorEOTFEncoded ); + sourceColorEOTFEncoded = ApplyLut3D_Tetrahedral( + *pLook, sourceColorEOTFEncoded ); } - // Convert to linearized display referred for source colorimetry - glm::vec3 sourceColorLinear = calcEOTFToLinear( sourceColorEOTFEncoded, sourceEOTF, tonemapping ); + // Convert to linearized display referred for source + // colorimetry + glm::vec3 sourceColorLinear = calcEOTFToLinear( + sourceColorEOTFEncoded, sourceEOTF, tonemapping ); // Convert to dest colorimetry (linearized display referred) - glm::vec3 destColorLinear = dest_from_source * sourceColorLinear; + glm::vec3 destColorLinear = + dest_from_source * sourceColorLinear; // Do a naive blending with native gamut based on saturation // ( A very simplified form of gamut mapping ) // float colorSaturation = rgb_to_hsv( sourceColor ).y; float colorSaturation = rgb_to_hsv( sourceColorLinear ).y; - float amount = cfit( colorSaturation, mapping.blendEnableMinSat, mapping.blendEnableMaxSat, mapping.blendAmountMin, mapping.blendAmountMax ); - destColorLinear = glm::mix( destColorLinear, sourceColorLinear, amount ); + float amount = cfit( + colorSaturation, + mapping.blendEnableMinSat, + mapping.blendEnableMaxSat, + mapping.blendAmountMin, + mapping.blendAmountMax ); + destColorLinear = + glm::mix( destColorLinear, sourceColorLinear, amount ); // Apply linear Mult destColorLinear = vMultLinear * destColorLinear; // Apply destination virtual white point mapping - destColorLinear = whitePointDestAdaptation * destColorLinear; + destColorLinear = + whitePointDestAdaptation * destColorLinear; // Apply tonemapping destColorLinear = tonemapping.apply( destColorLinear ); @@ -808,8 +930,12 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, // Hue preservation if ( g_bHuePreservationWhenClipping ) { - float flMax = std::max( std::max( destColorLinear.r, destColorLinear.g ), destColorLinear.b ); - // TODO: Don't use g22_luminance here or in tonemapping, use whatever maxContentLightLevel is for the connector. + float flMax = std::max( + std::max( destColorLinear.r, destColorLinear.g ), + destColorLinear.b ); + // TODO: Don't use g22_luminance here or in tonemapping, + // use whatever maxContentLightLevel is for the + // connector. if ( flMax > tonemapping.g22_luminance + 1.0f ) { destColorLinear /= flMax; @@ -818,49 +944,56 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, } // Apply dest EOTF - glm::vec3 destColorEOTFEncoded = calcLinearToEOTF( destColorLinear, destEOTF, tonemapping ); + glm::vec3 destColorEOTFEncoded = calcLinearToEOTF( + destColorLinear, destEOTF, tonemapping ); // Write LUT - pLut3d->data[GetLut3DIndexRedFastRGB( nRed, nGreen, nBlue, nLutEdgeSize3d )] = destColorEOTFEncoded; + pLut3d->data[ GetLut3DIndexRedFastRGB( + nRed, nGreen, nBlue, nLutEdgeSize3d ) ] = + destColorEOTFEncoded; } } } } } -bool BIsWideGamut( const displaycolorimetry_t & nativeDisplayOutput ) +bool BIsWideGamut( const displaycolorimetry_t &nativeDisplayOutput ) { // Use red as a sentinal for a wide-gamut display - return ( nativeDisplayOutput.primaries.r.x > 0.650f && nativeDisplayOutput.primaries.r.y < 0.320f ); + return ( + nativeDisplayOutput.primaries.r.x > 0.650f && + nativeDisplayOutput.primaries.r.y < 0.320f ); } -void buildSDRColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *pMapping, - float flSDRGamutWideness, const displaycolorimetry_t & nativeDisplayOutput ) +void buildSDRColorimetry( + displaycolorimetry_t *pColorimetry, + colormapping_t *pMapping, + float flSDRGamutWideness, + const displaycolorimetry_t &nativeDisplayOutput ) { - if ( BIsWideGamut( nativeDisplayOutput) ) + if ( BIsWideGamut( nativeDisplayOutput ) ) { // If not set, make it native. - if (flSDRGamutWideness < 0 ) - flSDRGamutWideness = 1.0f; + if ( flSDRGamutWideness < 0 ) flSDRGamutWideness = 1.0f; displaycolorimetry_t r709NativeWhite = displaycolorimetry_709; - r709NativeWhite.white = nativeDisplayOutput.white; + r709NativeWhite.white = nativeDisplayOutput.white; // 0.0: 709 // 1.0: Native colormapping_t noRemap; noRemap.blendEnableMinSat = 0.7f; noRemap.blendEnableMaxSat = 1.0f; - noRemap.blendAmountMin = 0.0f; - noRemap.blendAmountMax = 0.0f; - *pMapping = noRemap; - *pColorimetry = lerp( r709NativeWhite, nativeDisplayOutput, flSDRGamutWideness ); + noRemap.blendAmountMin = 0.0f; + noRemap.blendAmountMax = 0.0f; + *pMapping = noRemap; + *pColorimetry = + lerp( r709NativeWhite, nativeDisplayOutput, flSDRGamutWideness ); } else { // If not set, make it native. - if (flSDRGamutWideness < 0 ) - flSDRGamutWideness = 0.0f; + if ( flSDRGamutWideness < 0 ) flSDRGamutWideness = 0.0f; // 0.0: Native // 0.5: Generic wide gamut display w/smooth mapping @@ -871,8 +1004,8 @@ void buildSDRColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *p colormapping_t smoothRemap; smoothRemap.blendEnableMinSat = 0.7f; smoothRemap.blendEnableMaxSat = 1.0f; - smoothRemap.blendAmountMin = 0.0f; - smoothRemap.blendAmountMax = 1.f; + smoothRemap.blendAmountMin = 0.0f; + smoothRemap.blendAmountMax = 1.f; // Assume linear saturation computation // This is a partial (25%) blending to the unit cube @@ -880,49 +1013,57 @@ void buildSDRColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *p colormapping_t partialRemap; partialRemap.blendEnableMinSat = 0.7f; partialRemap.blendEnableMaxSat = 1.0f; - partialRemap.blendAmountMin = 0.0f; - partialRemap.blendAmountMax = 0.25; + partialRemap.blendAmountMin = 0.0f; + partialRemap.blendAmountMax = 0.25; - displaycolorimetry_t wideGamutNativeWhite = displaycolorimetry_widegamutgeneric; + displaycolorimetry_t wideGamutNativeWhite = + displaycolorimetry_widegamutgeneric; wideGamutNativeWhite.white = nativeDisplayOutput.white; if ( flSDRGamutWideness < 0.5f ) { float t = cfit( flSDRGamutWideness, 0.f, 0.5f, 0.0f, 1.0f ); - *pColorimetry = lerp( nativeDisplayOutput, wideGamutNativeWhite, t ); + *pColorimetry = + lerp( nativeDisplayOutput, wideGamutNativeWhite, t ); *pMapping = smoothRemap; } else { - float t = cfit( flSDRGamutWideness, 0.5f, 1.0f, 0.0f, 1.0f ); + float t = cfit( flSDRGamutWideness, 0.5f, 1.0f, 0.0f, 1.0f ); *pColorimetry = wideGamutNativeWhite; - *pMapping = lerp( smoothRemap, partialRemap, t ); + *pMapping = lerp( smoothRemap, partialRemap, t ); } } } -void buildPQColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *pMapping, const displaycolorimetry_t & nativeDisplayOutput ) +void buildPQColorimetry( + displaycolorimetry_t *pColorimetry, + colormapping_t *pMapping, + const displaycolorimetry_t &nativeDisplayOutput ) { *pColorimetry = displaycolorimetry_2020; colormapping_t noRemap; noRemap.blendEnableMinSat = 0.0f; noRemap.blendEnableMaxSat = 1.0f; - noRemap.blendAmountMin = 0.0f; - noRemap.blendAmountMax = 0.0f; - *pMapping = noRemap; + noRemap.blendAmountMin = 0.0f; + noRemap.blendAmountMax = 0.0f; + *pMapping = noRemap; } -bool approxEqual( const glm::vec3 & a, const glm::vec3 & b, float flTolerance = 1e-5f ) +bool approxEqual( + const glm::vec3 &a, const glm::vec3 &b, float flTolerance = 1e-5f ) { - glm::vec3 v = glm::abs(a - b); + glm::vec3 v = glm::abs( a - b ); return ( v.x < flTolerance && v.y < flTolerance && v.z < flTolerance ); } -const glm::mat3 k_xyz_from_709 = normalised_primary_matrix( displaycolorimetry_709.primaries, displaycolorimetry_709.white, 1.f ); +const glm::mat3 k_xyz_from_709 = normalised_primary_matrix( + displaycolorimetry_709.primaries, displaycolorimetry_709.white, 1.f ); const glm::mat3 k_709_from_xyz = glm::inverse( k_xyz_from_709 ); -const glm::mat3 k_xyz_from_2020 = normalised_primary_matrix( displaycolorimetry_2020.primaries, displaycolorimetry_2020.white, 1.f ); +const glm::mat3 k_xyz_from_2020 = normalised_primary_matrix( + displaycolorimetry_2020.primaries, displaycolorimetry_2020.white, 1.f ); const glm::mat3 k_2020_from_xyz = glm::inverse( k_xyz_from_2020 ); const glm::mat3 k_2020_from_709 = k_2020_from_xyz * k_xyz_from_709; diff --git a/src/color_helpers.h b/src/color_helpers.h index cefa74e403..1260d6324a 100644 --- a/src/color_helpers.h +++ b/src/color_helpers.h @@ -3,54 +3,46 @@ #define GLM_ENABLE_EXPERIMENTAL 1 #include -#include #include -#include +#include #include +#include -#include // glm::vec2 -#include // glm::vec3 -#include // glm::mat3 #include +#include // glm::mat3 +#include // glm::vec2 +#include // glm::vec3 // Color utils inline int quantize( float fVal, float fMaxVal ) -{ - return std::max( 0.f, std::min( fMaxVal, rintf( fVal * fMaxVal ) ) ); -} +{ return std::max( 0.f, std::min( fMaxVal, rintf( fVal * fMaxVal ) ) ); } inline uint16_t quantize_lut_value_16bit( float flValue ) -{ - return (uint16_t)quantize( flValue, (float)UINT16_MAX ); -} +{ return ( uint16_t )quantize( flValue, ( float )UINT16_MAX ); } inline float clamp01( float val ) -{ - return std::max( 0.f, std::min( 1.f, val ) ); -} +{ return std::max( 0.f, std::min( 1.f, val ) ); } inline float clamp( float val, float lo, float hi ) -{ - return std::max( lo, std::min( hi, val ) ); -} +{ return std::max( lo, std::min( hi, val ) ); } inline float cfit( float x, float i1, float i2, float f1, float f2 ) -{ - return f1+(f2-f1)*clamp01( (x-i1)/(i2-i1) ); -} +{ return f1 + ( f2 - f1 ) * clamp01( ( x - i1 ) / ( i2 - i1 ) ); } inline float srgb_to_linear( float fVal ) { - return ( fVal < 0.04045f ) ? fVal / 12.92f : std::pow( ( fVal + 0.055f) / 1.055f, 2.4f ); + return ( fVal < 0.04045f ) ? fVal / 12.92f + : std::pow( ( fVal + 0.055f ) / 1.055f, 2.4f ); } inline float linear_to_srgb( float fVal ) { - return ( fVal < 0.0031308f ) ? fVal * 12.92f : std::pow( fVal, 1.0f / 2.4f ) * 1.055f - 0.055f; + return ( fVal < 0.0031308f ) + ? fVal * 12.92f + : std::pow( fVal, 1.0f / 2.4f ) * 1.055f - 0.055f; } -template -inline T pq_to_nits( const T& pq ) +template inline T pq_to_nits( const T &pq ) { const float c1 = 0.8359375f; const float c2 = 18.8515625f; @@ -59,435 +51,452 @@ inline T pq_to_nits( const T& pq ) const float oo_m1 = 1.0f / 0.1593017578125f; const float oo_m2 = 1.0f / 78.84375f; - T num = glm::max(glm::pow(pq, T(oo_m2)) - c1, T(0.0f)); - T den = c2 - c3 * glm::pow(pq, T(oo_m2)); + T num = glm::max( glm::pow( pq, T( oo_m2 ) ) - c1, T( 0.0f ) ); + T den = c2 - c3 * glm::pow( pq, T( oo_m2 ) ); - return glm::pow(num / den, T(oo_m1)) * 10000.0f; + return glm::pow( num / den, T( oo_m1 ) ) * 10000.0f; } -template -inline T nits_to_pq( const T& nits ) -{ - T y = glm::clamp(nits / 10000.0f, T(0.0f), T(1.0f)); - const float c1 = 0.8359375f; - const float c2 = 18.8515625f; - const float c3 = 18.6875f; - const float m1 = 0.1593017578125f; - const float m2 = 78.84375f; - T num = c1 + c2 * glm::pow(y, T(m1)); - T den = T(1.0) + c3 * glm::pow(y, T(m1)); - T n = glm::pow(num / den, T(m2)); +template inline T nits_to_pq( const T &nits ) +{ + T y = glm::clamp( nits / 10000.0f, T( 0.0f ), T( 1.0f ) ); + const float c1 = 0.8359375f; + const float c2 = 18.8515625f; + const float c3 = 18.6875f; + const float m1 = 0.1593017578125f; + const float m2 = 78.84375f; + T num = c1 + c2 * glm::pow( y, T( m1 ) ); + T den = T( 1.0 ) + c3 * glm::pow( y, T( m1 ) ); + T n = glm::pow( num / den, T( m2 ) ); return n; } struct tonemap_info_t { - bool operator == (const tonemap_info_t&) const = default; - bool operator != (const tonemap_info_t&) const = default; - - float flBlackPointNits = 0.f; - float flWhitePointNits = 0.f; - - bool BIsValid() const - { - return ( flWhitePointNits > flBlackPointNits ); - } - - void reset() - { - flBlackPointNits = 0.f; - flWhitePointNits = 0.f; - } + bool operator==( const tonemap_info_t & ) const = default; + bool operator!=( const tonemap_info_t & ) const = default; + + float flBlackPointNits = 0.f; + float flWhitePointNits = 0.f; + + bool BIsValid( ) const { return ( flWhitePointNits > flBlackPointNits ); } + + void reset( ) + { + flBlackPointNits = 0.f; + flWhitePointNits = 0.f; + } }; -// Apply an HDR tonemapping according to eetf 2390 (R-REP-BT.2390-8-2020-PDF-E.pdf) -// sourceXXX == "Mastering Display" == Lw, Lb (in the paper) -// targetXXX == "Target Display" == Lmin, Lmax (in the paper) -// Be warned... PQ in, PQ out, for ALL params [0,1] -// This does not imply this function has anything to do with PQ -// (it's quite sensible to apply it to linear values created in other ways... you just have to -// PQ all params first, and undo the output) -// Values outside of 0-1 are not defined +// Apply an HDR tonemapping according to eetf 2390 +// (R-REP-BT.2390-8-2020-PDF-E.pdf) sourceXXX == "Mastering Display" == Lw, Lb +// (in the paper) targetXXX == "Target Display" == Lmin, Lmax (in the paper) Be +// warned... PQ in, PQ out, for ALL params [0,1] This does not imply this +// function has anything to do with PQ (it's quite sensible to apply it to +// linear values created in other ways... you just have to PQ all params first, +// and undo the output) Values outside of 0-1 are not defined struct eetf_2390_t { - void init( const tonemap_info_t & source, const tonemap_info_t & target ) - { - init_pq( - nits_to_pq( source.flBlackPointNits ), - nits_to_pq( source.flWhitePointNits ), - nits_to_pq( target.flBlackPointNits ), - nits_to_pq( target.flWhitePointNits ) ); - } - - void init_pq( float sourceBlackPQ, float sourceWhitePQ, float targetBlackPQ, float targetWhitePQ ) - { - m_sourceBlackPQ = sourceBlackPQ; - m_sourcePQScale = sourceWhitePQ - sourceBlackPQ; - m_invSourcePQScale = m_sourcePQScale > 0.f ? 1.f / m_sourcePQScale : 0.f; - m_minLumPQ = ( targetBlackPQ - sourceBlackPQ ) * m_invSourcePQScale; - m_maxLumPQ = ( targetWhitePQ - sourceBlackPQ ) * m_invSourcePQScale; - m_ks = 1.5 * m_maxLumPQ - 0.5; // TODO : return false if ks == 1.f? - } - - inline float apply( float inputNits ) const - { - return pq_to_nits( apply_pq( nits_to_pq( inputNits ) ) ); - } - - // Raw PQ transfer function - inline float apply_pq( float valuePQ ) const - { - // normalize PQ based on the mastering (source) display (E1) - float e1 = ( valuePQ - m_sourceBlackPQ ) * m_invSourcePQScale; - - // Apply high end rolloff - float e2 = e1 < m_ks ? e1 : _eetf_2390_spline( e1, m_ks, m_maxLumPQ ); - - // Apply low end pedestal - float one_min_e2 = 1.f - e2; - float one_min_e2_sq = one_min_e2 * one_min_e2; - float e3 = e2 + m_minLumPQ * one_min_e2_sq * one_min_e2_sq; - - // Re-apply mastering (source) transform - return e3 * m_sourcePQScale + m_sourceBlackPQ; - } - - // "Max RGB" approach, as defined in "Color Volume and Hue-Preservation in HDR Tone Mapping" - // Digital Object Identifier 10.5594/JMI.2020.2984046 - // Date of publication: 4 May 2020 - inline glm::vec3 apply_max_rgb( const glm::vec3 & inputNits ) const - { - float input_scalar_nits = std::max( inputNits.r, std::max( inputNits.g, inputNits.b ) ); - float output_scalar_nits = pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); - float gain = input_scalar_nits > 0.f ? output_scalar_nits / input_scalar_nits : 0.f; - return inputNits * gain; - } - - inline glm::vec3 apply_luma_rgb( const glm::vec3 & inputNits ) const - { - float input_scalar_nits = 0.2627f * inputNits.r + 0.6780f * inputNits.g + 0.0593f * inputNits.b; - float output_scalar_nits = pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); - float gain = input_scalar_nits > 0.f ? output_scalar_nits / input_scalar_nits : 0.f; - return inputNits * gain; - } - - inline glm::vec3 apply_independent_rgb( const glm::vec3 & inputNits ) const - { - glm::vec3 inputPQ = nits_to_pq( inputNits ); - glm::vec3 outputPQ = { apply_pq( inputPQ.r ), apply_pq( inputPQ.g ), apply_pq( inputPQ.b ) }; - return pq_to_nits( outputPQ ); - } - - private: - float m_sourceBlackPQ = 0.f; - float m_sourcePQScale = 0.f; - float m_invSourcePQScale = 0.f; - float m_minLumPQ = 0.f; - float m_maxLumPQ = 0.f; - float m_ks = 0.f; - - inline float _eetf_2390_spline( float value, float ks, float maxLum ) const - { - float t = ( value - ks ) / ( 1.f - ks ); // TODO : guard against ks == 1.f? - float t_sq = t*t; - float t_cub = t_sq*t; - float v1 = ( 2.f * t_cub - 3.f * t_sq + 1.f ) * ks; - float v2 = ( t_cub - 2.f * t_sq + t ) * ( 1.f - ks ); - float v3 = (-2.f * t_cub + 3.f * t_sq ) * maxLum; - return v1 + v2 + v3; - } + void init( const tonemap_info_t &source, const tonemap_info_t &target ) + { + init_pq( + nits_to_pq( source.flBlackPointNits ), + nits_to_pq( source.flWhitePointNits ), + nits_to_pq( target.flBlackPointNits ), + nits_to_pq( target.flWhitePointNits ) ); + } + + void init_pq( + float sourceBlackPQ, + float sourceWhitePQ, + float targetBlackPQ, + float targetWhitePQ ) + { + m_sourceBlackPQ = sourceBlackPQ; + m_sourcePQScale = sourceWhitePQ - sourceBlackPQ; + m_invSourcePQScale = + m_sourcePQScale > 0.f ? 1.f / m_sourcePQScale : 0.f; + m_minLumPQ = ( targetBlackPQ - sourceBlackPQ ) * m_invSourcePQScale; + m_maxLumPQ = ( targetWhitePQ - sourceBlackPQ ) * m_invSourcePQScale; + m_ks = 1.5 * m_maxLumPQ - 0.5; // TODO : return false if ks == 1.f? + } + + inline float apply( float inputNits ) const + { return pq_to_nits( apply_pq( nits_to_pq( inputNits ) ) ); } + + // Raw PQ transfer function + inline float apply_pq( float valuePQ ) const + { + // normalize PQ based on the mastering (source) display (E1) + float e1 = ( valuePQ - m_sourceBlackPQ ) * m_invSourcePQScale; + + // Apply high end rolloff + float e2 = e1 < m_ks ? e1 : _eetf_2390_spline( e1, m_ks, m_maxLumPQ ); + + // Apply low end pedestal + float one_min_e2 = 1.f - e2; + float one_min_e2_sq = one_min_e2 * one_min_e2; + float e3 = e2 + m_minLumPQ * one_min_e2_sq * one_min_e2_sq; + + // Re-apply mastering (source) transform + return e3 * m_sourcePQScale + m_sourceBlackPQ; + } + + // "Max RGB" approach, as defined in "Color Volume and Hue-Preservation in + // HDR Tone Mapping" Digital Object Identifier 10.5594/JMI.2020.2984046 Date + // of publication: 4 May 2020 + inline glm::vec3 apply_max_rgb( const glm::vec3 &inputNits ) const + { + float input_scalar_nits = + std::max( inputNits.r, std::max( inputNits.g, inputNits.b ) ); + float output_scalar_nits = + pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); + float gain = input_scalar_nits > 0.f + ? output_scalar_nits / input_scalar_nits + : 0.f; + return inputNits * gain; + } + + inline glm::vec3 apply_luma_rgb( const glm::vec3 &inputNits ) const + { + float input_scalar_nits = 0.2627f * inputNits.r + + 0.6780f * inputNits.g + 0.0593f * inputNits.b; + float output_scalar_nits = + pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); + float gain = input_scalar_nits > 0.f + ? output_scalar_nits / input_scalar_nits + : 0.f; + return inputNits * gain; + } + + inline glm::vec3 apply_independent_rgb( const glm::vec3 &inputNits ) const + { + glm::vec3 inputPQ = nits_to_pq( inputNits ); + glm::vec3 outputPQ = { apply_pq( inputPQ.r ), + apply_pq( inputPQ.g ), + apply_pq( inputPQ.b ) }; + return pq_to_nits( outputPQ ); + } + +private: + float m_sourceBlackPQ = 0.f; + float m_sourcePQScale = 0.f; + float m_invSourcePQScale = 0.f; + float m_minLumPQ = 0.f; + float m_maxLumPQ = 0.f; + float m_ks = 0.f; + + inline float _eetf_2390_spline( float value, float ks, float maxLum ) const + { + float t = + ( value - ks ) / ( 1.f - ks ); // TODO : guard against ks == 1.f? + float t_sq = t * t; + float t_cub = t_sq * t; + float v1 = ( 2.f * t_cub - 3.f * t_sq + 1.f ) * ks; + float v2 = ( t_cub - 2.f * t_sq + t ) * ( 1.f - ks ); + float v3 = ( -2.f * t_cub + 3.f * t_sq ) * maxLum; + return v1 + v2 + v3; + } }; -inline float flerp( float a, float b, float t ) -{ - return a + t * (b - a); -} +inline float flerp( float a, float b, float t ) { return a + t * ( b - a ); } -inline float safe_pow(float x, float y) +inline float safe_pow( float x, float y ) { - // Avoids pow(x, 1.0f) != x. - if (y == 1.0f) - return x; + // Avoids pow(x, 1.0f) != x. + if ( y == 1.0f ) return x; - return std::pow(std::max(x, 0.0f), y); + return std::pow( std::max( x, 0.0f ), y ); } inline float positive_mod( float flX, float flPeriod ) { - float flVal = fmodf( flX, flPeriod ); - return ( flVal < 0 ) ? flVal + flPeriod : fabsf( flVal ); // fabs fixes -0 + float flVal = fmodf( flX, flPeriod ); + return ( flVal < 0 ) ? flVal + flPeriod : fabsf( flVal ); // fabs fixes -0 } // Colorimetry functions related to color space conversions struct primaries_t { - bool operator == (const primaries_t&) const = default; - bool operator != (const primaries_t&) const = default; + bool operator==( const primaries_t & ) const = default; + bool operator!=( const primaries_t & ) const = default; - glm::vec2 r; - glm::vec2 g; - glm::vec2 b; + glm::vec2 r; + glm::vec2 g; + glm::vec2 b; }; enum EOTF { - EOTF_Gamma22 = 0, - EOTF_PQ = 1, + EOTF_Gamma22 = 0, + EOTF_PQ = 1, - EOTF_Count = 2, + EOTF_Count = 2, }; struct displaycolorimetry_t { - bool operator == (const displaycolorimetry_t&) const = default; - bool operator != (const displaycolorimetry_t&) const = default; - primaries_t primaries; - glm::vec2 white; + bool operator==( const displaycolorimetry_t & ) const = default; + bool operator!=( const displaycolorimetry_t & ) const = default; + primaries_t primaries; + glm::vec2 white; }; struct nightmode_t { - bool operator == (const nightmode_t&) const = default; - bool operator != (const nightmode_t&) const = default; + bool operator==( const nightmode_t & ) const = default; + bool operator!=( const nightmode_t & ) const = default; - float amount; // [0 = disabled, 1.f = on] - float hue; // [0,1] + float amount; // [0 = disabled, 1.f = on] + float hue; // [0,1] float saturation; // [0,1] }; struct colormapping_t { - bool operator == (const colormapping_t&) const = default; - bool operator != (const colormapping_t&) const = default; + bool operator==( const colormapping_t & ) const = default; + bool operator!=( const colormapping_t & ) const = default; - float blendEnableMinSat; - float blendEnableMaxSat; - float blendAmountMin; - float blendAmountMax; + float blendEnableMinSat; + float blendEnableMaxSat; + float blendAmountMin; + float blendAmountMax; }; -displaycolorimetry_t lerp( const displaycolorimetry_t & a, const displaycolorimetry_t & b, float t ); -colormapping_t lerp( const colormapping_t & a, const colormapping_t & b, float t ); +displaycolorimetry_t +lerp( const displaycolorimetry_t &a, const displaycolorimetry_t &b, float t ); +colormapping_t +lerp( const colormapping_t &a, const colormapping_t &b, float t ); // These values are directly exposed to steam // Values must be stable over time enum ETonemapOperator { - ETonemapOperator_None = 0, - ETonemapOperator_EETF2390_Luma = 1, - ETonemapOperator_EETF2390_Independent = 2, - ETonemapOperator_EETF2390_MaxChan = 3, + ETonemapOperator_None = 0, + ETonemapOperator_EETF2390_Luma = 1, + ETonemapOperator_EETF2390_Independent = 2, + ETonemapOperator_EETF2390_MaxChan = 3, }; struct tonemapping_t { - bool bUseShaper = true; - float g22_luminance = 1.f; // what luminance should be applied for g22 EOTF conversions? - ETonemapOperator eOperator = ETonemapOperator_None; - eetf_2390_t eetf2390; - - inline glm::vec3 apply( const glm::vec3 & inputNits ) const - { - switch ( eOperator ) - { - case ETonemapOperator_EETF2390_Luma: - return eetf2390.apply_luma_rgb( inputNits ); - case ETonemapOperator_EETF2390_Independent: - return eetf2390.apply_independent_rgb( inputNits ); - case ETonemapOperator_EETF2390_MaxChan: - return eetf2390.apply_max_rgb( inputNits ); - default: - return inputNits; - } - } + bool bUseShaper = true; + float g22_luminance = + 1.f; // what luminance should be applied for g22 EOTF conversions? + ETonemapOperator eOperator = ETonemapOperator_None; + eetf_2390_t eetf2390; + + inline glm::vec3 apply( const glm::vec3 &inputNits ) const + { + switch ( eOperator ) + { + case ETonemapOperator_EETF2390_Luma: + return eetf2390.apply_luma_rgb( inputNits ); + case ETonemapOperator_EETF2390_Independent: + return eetf2390.apply_independent_rgb( inputNits ); + case ETonemapOperator_EETF2390_MaxChan: + return eetf2390.apply_max_rgb( inputNits ); + default: + return inputNits; + } + } }; // Exposed in external atoms. Dont change the values enum EChromaticAdaptationMethod { - k_EChromaticAdapatationMethod_XYZ = 0, + k_EChromaticAdapatationMethod_XYZ = 0, k_EChromaticAdapatationMethod_Bradford = 1, }; -glm::mat3 chromatic_adaptation_matrix( const glm::vec3 & sourceWhiteXYZ, const glm::vec3 & destWhiteXYZ, - EChromaticAdaptationMethod eMethod ); +glm::mat3 chromatic_adaptation_matrix( + const glm::vec3 &sourceWhiteXYZ, + const glm::vec3 &destWhiteXYZ, + EChromaticAdaptationMethod eMethod ); struct lut1d_t { - int lutSize = 0; - std::vector dataR; - std::vector dataG; - std::vector dataB; - - // Some LUTs start with a flat section... - // Where does the non-flat part start? - // (impacts the inverse computation) - int startIndexR = -1; - int startIndexG = -1; - int startIndexB = -1; - - void finalize(); // calculates start indicies - - void resize( int lutSize_ ) - { - lutSize = lutSize_; - dataR.resize( lutSize_ ); - dataG.resize( lutSize_ ); - dataB.resize( lutSize_ ); - startIndexR = -1; - startIndexG = -1; - startIndexB = -1; - } + int lutSize = 0; + std::vector dataR; + std::vector dataG; + std::vector dataB; + + // Some LUTs start with a flat section... + // Where does the non-flat part start? + // (impacts the inverse computation) + int startIndexR = -1; + int startIndexG = -1; + int startIndexB = -1; + + void finalize( ); // calculates start indicies + + void resize( int lutSize_ ) + { + lutSize = lutSize_; + dataR.resize( lutSize_ ); + dataG.resize( lutSize_ ); + dataB.resize( lutSize_ ); + startIndexR = -1; + startIndexG = -1; + startIndexB = -1; + } }; struct lut3d_t { - int lutEdgeSize = 0; - std::vector data; // R changes fastest - - void resize( int lutEdgeSize_ ) - { - lutEdgeSize = lutEdgeSize_; - data.resize( lutEdgeSize_ * lutEdgeSize_ * lutEdgeSize_ ); - } -}; + int lutEdgeSize = 0; + std::vector data; // R changes fastest -std::shared_ptr LoadCubeLut( FILE *pFile, bool &bRaisesBlackLevelFloor ); -std::shared_ptr LoadCubeLut( const char *pchFileName, bool &bRaisesBlackLevelFloor ); + void resize( int lutEdgeSize_ ) + { + lutEdgeSize = lutEdgeSize_; + data.resize( lutEdgeSize_ * lutEdgeSize_ * lutEdgeSize_ ); + } +}; -// Generate a color transform from the source colorspace, to the dest colorspace, -// nLutSize1d is the number of color entries in the shaper lut -// I.e., for a shaper lut with 256 input colors nLutSize1d = 256, countof(pRgbxData1d) = 1024 -// nLutEdgeSize3d is the number of color entries, per edge, in the 3d lut -// I.e., for a 17x17x17 lut nLutEdgeSize3d = 17, countof(pRgbxData3d) = 19652 +std::shared_ptr +LoadCubeLut( FILE *pFile, bool &bRaisesBlackLevelFloor ); +std::shared_ptr +LoadCubeLut( const char *pchFileName, bool &bRaisesBlackLevelFloor ); + +// Generate a color transform from the source colorspace, to the dest +// colorspace, nLutSize1d is the number of color entries in the shaper lut I.e., +// for a shaper lut with 256 input colors nLutSize1d = 256, +// countof(pRgbxData1d) = 1024 nLutEdgeSize3d is the number of color entries, +// per edge, in the 3d lut I.e., for a 17x17x17 lut nLutEdgeSize3d = 17, +// countof(pRgbxData3d) = 19652 // // If the white points differ, this performs an absolute colorimetric match // Look luts are optional, but if specified applied in the sourceEOTF space -template -void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, - lut3d_t * pLut3d, - const displaycolorimetry_t & source, EOTF sourceEOTF, - const displaycolorimetry_t & dest, EOTF destEOTF, - const glm::vec2 & destVirtualWhite, EChromaticAdaptationMethod eMethod, - const colormapping_t & mapping, const nightmode_t & nightmode, const tonemapping_t & tonemapping, - const lut3d_t * pLook, float flGain ); - -#define REGISTER_LUT_EDGE_SIZE(size) template void calcColorTransform<(size)>( lut1d_t * pShaper, int nLutSize1d, \ - lut3d_t * pLut3d, \ - const displaycolorimetry_t & source, EOTF sourceEOTF, \ - const displaycolorimetry_t & dest, EOTF destEOTF, \ - const glm::vec2 & destVirtualWhite, EChromaticAdaptationMethod eMethod, \ - const colormapping_t & mapping, const nightmode_t & nightmode, const tonemapping_t & tonemapping, \ - const lut3d_t * pLook, float flGain ) +template +void calcColorTransform( + lut1d_t *pShaper, + int nLutSize1d, + lut3d_t *pLut3d, + const displaycolorimetry_t &source, + EOTF sourceEOTF, + const displaycolorimetry_t &dest, + EOTF destEOTF, + const glm::vec2 &destVirtualWhite, + EChromaticAdaptationMethod eMethod, + const colormapping_t &mapping, + const nightmode_t &nightmode, + const tonemapping_t &tonemapping, + const lut3d_t *pLook, + float flGain ); + +#define REGISTER_LUT_EDGE_SIZE( size ) \ + template void calcColorTransform<( size )>( \ + lut1d_t * pShaper, \ + int nLutSize1d, \ + lut3d_t *pLut3d, \ + const displaycolorimetry_t &source, \ + EOTF sourceEOTF, \ + const displaycolorimetry_t &dest, \ + EOTF destEOTF, \ + const glm::vec2 &destVirtualWhite, \ + EChromaticAdaptationMethod eMethod, \ + const colormapping_t &mapping, \ + const nightmode_t &nightmode, \ + const tonemapping_t &tonemapping, \ + const lut3d_t *pLook, \ + float flGain ) // Build colorimetry and a gamut mapping for the given SDR configuration // Note: the output colorimetry will use the native display's white point // Only the color gamut will change -void buildSDRColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *pMapping, - float flSDRGamutWideness, const displaycolorimetry_t & nativeDisplayOutput ); +void buildSDRColorimetry( + displaycolorimetry_t *pColorimetry, + colormapping_t *pMapping, + float flSDRGamutWideness, + const displaycolorimetry_t &nativeDisplayOutput ); // Build colorimetry and a gamut mapping for the given PQ configuration -void buildPQColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *pMapping, const displaycolorimetry_t & nativeDisplayOutput ); +void buildPQColorimetry( + displaycolorimetry_t *pColorimetry, + colormapping_t *pMapping, + const displaycolorimetry_t &nativeDisplayOutput ); // Colormetry helper functions for DRM, kindly taken from Weston: // https://gitlab.freedesktop.org/wayland/weston/-/blob/main/libweston/backend-drm/kms-color.c // Licensed under MIT. -// Josh: I changed the asserts to clamps here (going to 0, rather than 1) to deal better with -// bad EDIDs (that have 0'ed out metadata) and naughty clients. +// Josh: I changed the asserts to clamps here (going to 0, rather than 1) to +// deal better with bad EDIDs (that have 0'ed out metadata) and naughty clients. -static inline uint16_t -color_xy_to_u16(float v) +static inline uint16_t color_xy_to_u16( float v ) { - //assert(v >= 0.0f); - //assert(v <= 1.0f); - v = std::clamp(v, 0.0f, 1.0f); + // assert(v >= 0.0f); + // assert(v <= 1.0f); + v = std::clamp( v, 0.0f, 1.0f ); // CTA-861-G // 6.9.1 Static Metadata Type 1 // chromaticity coordinate encoding - return (uint16_t)round(v * 50000.0f); + return ( uint16_t )round( v * 50000.0f ); } -static inline float -color_xy_from_u16(uint16_t v) -{ - return v / 50000.0f; -} +static inline float color_xy_from_u16( uint16_t v ) { return v / 50000.0f; } -static inline uint16_t -nits_to_u16(float nits) +static inline uint16_t nits_to_u16( float nits ) { - //assert(nits >= 1.0f); - //assert(nits <= 65535.0f); - nits = std::clamp(nits, 0.0f, 65535.0f); - - // CTA-861-G - // 6.9.1 Static Metadata Type 1 - // max display mastering luminance, max content light level, - // max frame-average light level - return (uint16_t)round(nits); -} + // assert(nits >= 1.0f); + // assert(nits <= 65535.0f); + nits = std::clamp( nits, 0.0f, 65535.0f ); -static inline float -nits_from_u16(uint16_t v) -{ - return float(v); + // CTA-861-G + // 6.9.1 Static Metadata Type 1 + // max display mastering luminance, max content light level, + // max frame-average light level + return ( uint16_t )round( nits ); } -static inline uint16_t -nits_to_u16_dark(float nits) -{ - //assert(nits >= 0.0001f); - //assert(nits <= 6.5535f); - nits = std::clamp(nits, 0.0f, 6.5535f); - - // CTA-861-G - // 6.9.1 Static Metadata Type 1 - // min display mastering luminance - return (uint16_t)round(nits * 10000.0f); -} +static inline float nits_from_u16( uint16_t v ) { return float( v ); } -static inline float -nits_from_u16_dark(uint16_t v) +static inline uint16_t nits_to_u16_dark( float nits ) { - return v / 10000.0f; + // assert(nits >= 0.0001f); + // assert(nits <= 6.5535f); + nits = std::clamp( nits, 0.0f, 6.5535f ); + + // CTA-861-G + // 6.9.1 Static Metadata Type 1 + // min display mastering luminance + return ( uint16_t )round( nits * 10000.0f ); } -static constexpr displaycolorimetry_t displaycolorimetry_steamdeck_spec -{ - .primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { 0.164f, 0.121f } }, - .white = { 0.3070f, 0.3220f }, // not D65 +static inline float nits_from_u16_dark( uint16_t v ) { return v / 10000.0f; } + +static constexpr displaycolorimetry_t displaycolorimetry_steamdeck_spec{ + .primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { 0.164f, 0.121f } }, + .white = { 0.3070f, 0.3220f }, // not D65 }; -static constexpr displaycolorimetry_t displaycolorimetry_steamdeck_measured -{ - .primaries = { { 0.603f, 0.349f }, { 0.335f, 0.571f }, { 0.163f, 0.115f } }, - .white = { 0.296f, 0.307f }, // not D65 +static constexpr displaycolorimetry_t displaycolorimetry_steamdeck_measured{ + .primaries = { { 0.603f, 0.349f }, { 0.335f, 0.571f }, { 0.163f, 0.115f } }, + .white = { 0.296f, 0.307f }, // not D65 }; -static constexpr displaycolorimetry_t displaycolorimetry_709 -{ - .primaries = { { 0.64f, 0.33f }, { 0.30f, 0.60f }, { 0.15f, 0.06f } }, - .white = { 0.3127f, 0.3290f }, // D65 +static constexpr displaycolorimetry_t displaycolorimetry_709{ + .primaries = { { 0.64f, 0.33f }, { 0.30f, 0.60f }, { 0.15f, 0.06f } }, + .white = { 0.3127f, 0.3290f }, // D65 }; // Our "saturated SDR target", per jeremys -static constexpr displaycolorimetry_t displaycolorimetry_widegamutgeneric -{ - .primaries = { { 0.6825f, 0.3165f }, { 0.241f, 0.719f }, { 0.138f, 0.050f } }, - .white = { 0.3127f, 0.3290f }, // D65 +static constexpr displaycolorimetry_t displaycolorimetry_widegamutgeneric{ + .primaries = { { 0.6825f, 0.3165f }, + { 0.241f, 0.719f }, + { 0.138f, 0.050f } }, + .white = { 0.3127f, 0.3290f }, // D65 }; -static constexpr displaycolorimetry_t displaycolorimetry_2020 -{ - .primaries = { { 0.708f, 0.292f }, { 0.170f, 0.797f }, { 0.131f, 0.046f } }, - .white = { 0.3127f, 0.3290f }, // D65 +static constexpr displaycolorimetry_t displaycolorimetry_2020{ + .primaries = { { 0.708f, 0.292f }, { 0.170f, 0.797f }, { 0.131f, 0.046f } }, + .white = { 0.3127f, 0.3290f }, // D65 }; - extern const glm::mat3 k_xyz_from_709; extern const glm::mat3 k_709_from_xyz; diff --git a/src/color_helpers_impl.h b/src/color_helpers_impl.h index 3e8c2d5a5e..8619dc1ce8 100644 --- a/src/color_helpers_impl.h +++ b/src/color_helpers_impl.h @@ -1,20 +1,23 @@ #pragma once #include "color_helpers.h" -namespace rendervulkan { - static constexpr uint32_t s_nLutEdgeSize3d = 17; - static constexpr uint32_t s_nLutSize1d = 4096; -} +namespace rendervulkan +{ + static constexpr uint32_t s_nLutEdgeSize3d = 17; + static constexpr uint32_t s_nLutSize1d = 4096; +} // namespace rendervulkan -namespace color_bench { - static constexpr uint32_t nLutEdgeSize3d = 17; - static constexpr uint32_t nLutSize1d = 4096; -} +namespace color_bench +{ + static constexpr uint32_t nLutEdgeSize3d = 17; + static constexpr uint32_t nLutSize1d = 4096; +} // namespace color_bench -namespace ns_color_tests { - [[maybe_unused]] static constexpr uint32_t nLutEdgeSize3d = 17; +namespace ns_color_tests +{ + [[maybe_unused]] static constexpr uint32_t nLutEdgeSize3d = 17; } #ifdef COLOR_HELPERS_CPP -REGISTER_LUT_EDGE_SIZE(rendervulkan::s_nLutEdgeSize3d); -#endif \ No newline at end of file +REGISTER_LUT_EDGE_SIZE( rendervulkan::s_nLutEdgeSize3d ); +#endif diff --git a/src/color_tests.cpp b/src/color_tests.cpp index 2d682bf661..6672b22ebf 100644 --- a/src/color_tests.cpp +++ b/src/color_tests.cpp @@ -1,7 +1,7 @@ -#include "color_helpers.h" #include +#include "color_helpers.h" -//#include +// #include #include /* @@ -16,8 +16,8 @@ lut3d_t lut3d_float; static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) { - const primaries_t primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { 0.164f, 0.121f } }; - const glm::vec2 white = { 0.3070f, 0.3220f }; + const primaries_t primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { +0.164f, 0.121f } }; const glm::vec2 white = { 0.3070f, 0.3220f }; displaycolorimetry_t inputColorimetry{}; inputColorimetry.primaries = primaries; @@ -36,10 +36,10 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) float flGain = 1.0f; for (auto _ : state) { - calcColorTransform( &lut1d_float, nLutSize1d, &lut3d_float, inputColorimetry, inputEOTF, - outputEncodingColorimetry, EOTF_Gamma22, - colorMapping, nightmode, tonemapping, nullptr, flGain ); - for ( size_t i=0, end = lut1d_float.dataR.size(); i( &lut1d_float, nLutSize1d, +&lut3d_float, inputColorimetry, inputEOTF, outputEncodingColorimetry, +EOTF_Gamma22, colorMapping, nightmode, tonemapping, nullptr, flGain ); for ( +size_t i=0, end = lut1d_float.dataR.size(); i 0.1 - 1000 float sourceBlackNits = 0.01f; float sourceWhiteNits = 5000.0f; - float sourceBlackPQ = nits_to_pq( sourceBlackNits ); - float sourceWhitePQ = nits_to_pq( sourceWhiteNits ); + float sourceBlackPQ = nits_to_pq( sourceBlackNits ); + float sourceWhitePQ = nits_to_pq( sourceWhiteNits ); - printf("source\t%0.02f - %0.01f\t\tPQ10: %0.1f %0.1f \n", sourceBlackNits, sourceWhiteNits, sourceBlackPQ * 1023.f, sourceWhitePQ * 1023.f ); + printf( + "source\t%0.02f - %0.01f\t\tPQ10: %0.1f %0.1f \n", + sourceBlackNits, + sourceWhiteNits, + sourceBlackPQ * 1023.f, + sourceWhitePQ * 1023.f ); float destBlackNits = 0.1f; float destWhiteNits = 1000.0f; - float destBlackPQ = nits_to_pq( destBlackNits ); - float destWhitePQ = nits_to_pq( destWhiteNits ); - printf("dest\t%0.02f - %0.01f\t\tPQ10: %0.1f %0.1f\n", destBlackNits, destWhiteNits, destBlackPQ * 1023.f, destWhitePQ * 1023.f ); - printf("\n"); + float destBlackPQ = nits_to_pq( destBlackNits ); + float destWhitePQ = nits_to_pq( destWhiteNits ); + printf( + "dest\t%0.02f - %0.01f\t\tPQ10: %0.1f %0.1f\n", + destBlackNits, + destWhiteNits, + destBlackPQ * 1023.f, + destWhitePQ * 1023.f ); + printf( "\n" ); eetf_2390_t eetf; eetf.init_pq( sourceBlackPQ, sourceWhitePQ, destBlackPQ, destWhitePQ ); - for ( size_t nLevel=0; nLevel < 12; ++nLevel ) + for ( size_t nLevel = 0; nLevel < 12; ++nLevel ) { - float flInputNits = vLumaLevels[nLevel]; - float inputPQ = nits_to_pq( flInputNits ); - float tonemappedOutputPQ = eetf.apply_pq( inputPQ ); + float flInputNits = vLumaLevels[ nLevel ]; + float inputPQ = nits_to_pq( flInputNits ); + float tonemappedOutputPQ = eetf.apply_pq( inputPQ ); float tonemappedOutputNits = pq_to_nits( tonemappedOutputPQ ); - printf("value\t%0.03f -> %0.03f\tPQ10: %0.1f -> %0.1f\n", flInputNits, tonemappedOutputNits, inputPQ * 1023.f, tonemappedOutputPQ * 1023.f ); + printf( + "value\t%0.03f -> %0.03f\tPQ10: %0.1f -> %0.1f\n", + flInputNits, + tonemappedOutputNits, + inputPQ * 1023.f, + tonemappedOutputPQ * 1023.f ); } } -int main(int argc, char* argv[]) +int main( int argc, char *argv[] ) { - printf("color_tests\n"); + printf( "color_tests\n" ); // test_eetf2390_mono(); - color_tests(); + color_tests( ); return 0; -} \ No newline at end of file +} diff --git a/src/commit.cpp b/src/commit.cpp index 0fe216e9d1..b7d5715b3a 100644 --- a/src/commit.cpp +++ b/src/commit.cpp @@ -1,115 +1,111 @@ -#include "wlserver.hpp" +#include "commit.h" #include "rendervulkan.hpp" #include "steamcompmgr.hpp" -#include "commit.h" +#include "wlserver.hpp" #include "gpuvis_trace_utils.h" extern gamescope::CAsyncWaiter> g_ImageWaiter; -commit_t::commit_t() +commit_t::commit_t( ) { static uint64_t maxCommmitID = 0; - commitID = ++maxCommmitID; + commitID = ++maxCommmitID; } -commit_t::~commit_t() +commit_t::~commit_t( ) { { std::unique_lock lock( m_WaitableCommitStateMutex ); - CloseFenceInternal(); + CloseFenceInternal( ); } - if ( vulkanTex != nullptr ) - vulkanTex = nullptr; + if ( vulkanTex != nullptr ) vulkanTex = nullptr; - wlserver_lock(); - if (!presentation_feedbacks.empty()) + wlserver_lock( ); + if ( !presentation_feedbacks.empty( ) ) { - wlserver_presentation_feedback_discard(surf, presentation_feedbacks); - // presentation_feedbacks cleared by wlserver_presentation_feedback_discard + wlserver_presentation_feedback_discard( surf, presentation_feedbacks ); + // presentation_feedbacks cleared by + // wlserver_presentation_feedback_discard } wlr_buffer_unlock( buf ); - wlserver_unlock(); + wlserver_unlock( ); } -GamescopeAppTextureColorspace commit_t::colorspace() const +GamescopeAppTextureColorspace commit_t::colorspace( ) const { VkColorSpaceKHR colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - if (feedback && vulkanTex) - colorspace = feedback->vk_colorspace; + if ( feedback && vulkanTex ) colorspace = feedback.value( )->vk_colorspace; - if (!vulkanTex) - return GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; + if ( !vulkanTex ) return GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; - return VkColorSpaceToGamescopeAppTextureColorSpace(vulkanTex->format(), colorspace); + return VkColorSpaceToGamescopeAppTextureColorSpace( + vulkanTex->format( ), colorspace ); } -int commit_t::GetFD() -{ - return m_nCommitFence; -} +int commit_t::GetFD( ) { return m_nCommitFence; } -void commit_t::OnPollIn() +void commit_t::OnPollIn( ) { gpuvis_trace_end_ctx_printf( commitID, "wait fence" ); { std::unique_lock lock( m_WaitableCommitStateMutex ); - if ( !CloseFenceInternal() ) - return; + if ( !CloseFenceInternal( ) ) return; } - Signal(); + Signal( ); - nudge_steamcompmgr(); + nudge_steamcompmgr( ); } -void commit_t::Signal() +void commit_t::Signal( ) { - uint64_t now = get_time_in_nanos(); + uint64_t now = get_time_in_nanos( ); present_time = now; uint64_t frametime; if ( m_bMangoNudge ) { static uint64_t lastFrameTime = now; - frametime = now - lastFrameTime; - lastFrameTime = now; + frametime = now - lastFrameTime; + lastFrameTime = now; } // TODO: Move this so it's called in the main loop. // Instead of looping over all the windows like before. // When we get the new IWaitable stuff in there. { - std::unique_lock< std::mutex > lock( m_pDoneCommits->listCommitsDoneLock ); - m_pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ - .winSeq = win_seq, - .commitID = commitID, - .desiredPresentTime = desired_present_time, - .fifo = fifo, - } ); + std::unique_lock lock( + m_pDoneCommits->listCommitsDoneLock ); + m_pDoneCommits->listCommitsDone.push_back( + CommitDoneEntry_t{ + .winSeq = win_seq, + .commitID = commitID, + .desiredPresentTime = desired_present_time, + .fifo = fifo, + } ); } if ( m_bMangoNudge ) - mangoapp_update( IsPerfOverlayFIFO() ? uint64_t(~0ull) : frametime, frametime, uint64_t(~0ull) ); + mangoapp_update( + IsPerfOverlayFIFO( ) ? uint64_t( ~0ull ) : frametime, + frametime, + uint64_t( ~0ull ) ); } -void commit_t::OnPollHangUp() +void commit_t::OnPollHangUp( ) { std::unique_lock lock( m_WaitableCommitStateMutex ); - CloseFenceInternal(); + CloseFenceInternal( ); } -bool commit_t::IsPerfOverlayFIFO() -{ - return fifo || is_steam; -} +bool commit_t::IsPerfOverlayFIFO( ) { return fifo || is_steam; } // Returns true if we had a fence that was closed. -bool commit_t::CloseFenceInternal() +bool commit_t::CloseFenceInternal( ) { - if ( m_nCommitFence < 0 ) - return false; + if ( m_nCommitFence < 0 ) return false; // Will automatically remove from epoll! g_ImageWaiter.RemoveWaitable( this ); @@ -118,38 +114,42 @@ bool commit_t::CloseFenceInternal() return true; } -void commit_t::SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ) +void commit_t::SetFence( + int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ) { std::unique_lock lock( m_WaitableCommitStateMutex ); - CloseFenceInternal(); + CloseFenceInternal( ); m_nCommitFence = nFence; - m_bMangoNudge = bMangoNudge; + m_bMangoNudge = bMangoNudge; m_pDoneCommits = pDoneCommits; } -void calc_scale_factor(float &out_scale_x, float &out_scale_y, float sourceWidth, float sourceHeight); +void calc_scale_factor( + float &out_scale_x, + float &out_scale_y, + float sourceWidth, + float sourceHeight ); -bool commit_t::ShouldPreemptivelyUpscale() +bool commit_t::ShouldPreemptivelyUpscale( ) { // Don't pre-emptively upscale if we are not a FIFO commit. // Don't want to FSR upscale 1000fps content. - if ( !fifo ) - return false; + if ( !fifo ) return false; // If we support the upscaling filter in hardware, don't // pre-emptively do it via shaders. - if ( DoesHardwareSupportUpscaleFilter( g_upscaleFilter ) ) - return false; + if ( DoesHardwareSupportUpscaleFilter( g_upscaleFilter ) ) return false; - if ( !vulkanTex ) - return false; + if ( !vulkanTex ) return false; float flScaleX = 1.0f; float flScaleY = 1.0f; - // I wish this function was more programatic with its inputs, but it does do exactly what we want right now... - // It should also return a std::pair or a glm uvec - calc_scale_factor( flScaleX, flScaleY, vulkanTex->width(), vulkanTex->height() ); + // I wish this function was more programatic with its inputs, but it does do + // exactly what we want right now... It should also return a std::pair or a + // glm uvec + calc_scale_factor( + flScaleX, flScaleY, vulkanTex->width( ), vulkanTex->height( ) ); return !close_enough( flScaleX, 1.0f ) || !close_enough( flScaleY, 1.0f ); } diff --git a/src/commit.h b/src/commit.h index 0f098a9313..eafac8216f 100644 --- a/src/commit.h +++ b/src/commit.h @@ -1,5 +1,5 @@ -#include "steamcompmgr_shared.hpp" #include "Utils/NonCopyable.h" +#include "steamcompmgr_shared.hpp" #include #include "main.hpp" @@ -9,75 +9,83 @@ class CVulkanTexture; struct UpscaledTexture_t { - GamescopeUpscaleFilter eFilter{}; - GamescopeUpscaleScaler eScaler{}; - uint32_t uOutputWidth = 0; - uint32_t uOutputHeight = 0; - gamescope::Rc pTexture{}; - VkColorSpaceKHR colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + GamescopeUpscaleFilter eFilter{}; + GamescopeUpscaleScaler eScaler{}; + uint32_t uOutputWidth = 0; + uint32_t uOutputHeight = 0; + gamescope::Rc pTexture{}; + VkColorSpaceKHR colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; }; -struct commit_t final : public gamescope::RcObject, public gamescope::IWaitable, public gamescope::NonCopyable +struct commit_t final : public gamescope::RcObject, + public gamescope::IWaitable, + public gamescope::NonCopyable { - commit_t(); - ~commit_t(); - - GamescopeAppTextureColorspace colorspace() const; - - // For waitable: - int GetFD() final; - void OnPollIn() final; - void Signal(); - void OnPollHangUp() final; - - bool IsPerfOverlayFIFO(); - - // Returns true if we had a fence that was closed. - bool CloseFenceInternal(); - void SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ); - - bool ShouldPreemptivelyUpscale(); - - struct wlr_buffer *buf = nullptr; - gamescope::Rc vulkanTex; - std::optional upscaledTexture; - - gamescope::Rc GetTexture( GamescopeUpscaleFilter eFilter, GamescopeUpscaleScaler eScaler, GamescopeAppTextureColorspace &colorspace ) - { - if ( upscaledTexture && - upscaledTexture->eFilter == eFilter && - upscaledTexture->eScaler == eScaler && - upscaledTexture->uOutputWidth == g_nOutputWidth && - upscaledTexture->uOutputHeight == g_nOutputHeight ) - { - colorspace = VkColorSpaceToGamescopeAppTextureColorSpace( upscaledTexture->pTexture->format(), upscaledTexture->colorspace ); - return upscaledTexture->pTexture; - } - - colorspace = this->colorspace(); - return vulkanTex; - } - - uint64_t commitID = 0; - bool done = false; - bool async = false; - bool fifo = false; - bool is_steam = false; - uint32_t appID = 0; - std::optional feedback = std::nullopt; - - uint64_t win_seq = 0; - struct wlr_surface *surf = nullptr; - std::vector presentation_feedbacks; - - std::optional present_id = std::nullopt; - uint64_t desired_present_time = 0; - uint64_t earliest_present_time = 0; - uint64_t present_margin = 0; - uint64_t present_time = 0; - - std::mutex m_WaitableCommitStateMutex; - int m_nCommitFence = -1; - bool m_bMangoNudge = false; - CommitDoneList_t *m_pDoneCommits = nullptr; // I hate this -}; \ No newline at end of file + commit_t( ); + ~commit_t( ); + + GamescopeAppTextureColorspace colorspace( ) const; + + // For waitable: + int GetFD( ) final; + void OnPollIn( ) final; + void Signal( ); + void OnPollHangUp( ) final; + + bool IsPerfOverlayFIFO( ); + + // Returns true if we had a fence that was closed. + bool CloseFenceInternal( ); + void + SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ); + + bool ShouldPreemptivelyUpscale( ); + + struct wlr_buffer *buf = nullptr; + gamescope::Rc vulkanTex; + std::optional upscaledTexture; + + gamescope::Rc GetTexture( + GamescopeUpscaleFilter eFilter, + GamescopeUpscaleScaler eScaler, + GamescopeAppTextureColorspace &colorspace ) + { + if ( upscaledTexture && upscaledTexture->eFilter == eFilter && + upscaledTexture->eScaler == eScaler && + upscaledTexture->uOutputWidth == g_nOutputWidth && + upscaledTexture->uOutputHeight == g_nOutputHeight ) + { + colorspace = VkColorSpaceToGamescopeAppTextureColorSpace( + upscaledTexture->pTexture->format( ), + upscaledTexture->colorspace ); + return upscaledTexture->pTexture; + } + + colorspace = this->colorspace( ); + return vulkanTex; + } + + uint64_t commitID = 0; + bool done = false; + bool async = false; + bool fifo = false; + bool is_steam = false; + uint32_t appID = 0; + std::optional> feedback = + std::nullopt; + + uint64_t win_seq = 0; + struct wlr_surface *surf = nullptr; + std::vector presentation_feedbacks; + + std::optional present_id = std::nullopt; + uint64_t desired_present_time = 0; + uint64_t earliest_present_time = 0; + uint64_t present_margin = 0; + uint64_t present_time = 0; + + std::mutex m_WaitableCommitStateMutex; + int m_nCommitFence = -1; + bool m_bMangoNudge = false; + CommitDoneList_t *m_pDoneCommits = nullptr; // I hate this +}; diff --git a/src/convar.cpp b/src/convar.cpp index d96a2b5e8a..7ba9a07a72 100644 --- a/src/convar.cpp +++ b/src/convar.cpp @@ -4,42 +4,41 @@ #include #include -LogScope console_log("console"); +LogScope console_log( "console" ); -extern void PrintGamescopeVersion(); +extern void PrintGamescopeVersion( ); namespace gamescope { - ConCommand::ConCommand( std::string_view pszName, std::string_view pszDescription, ConCommandFunc func, bool bRegisterScript ) - : m_pszName{ pszName } - , m_pszDescription{ pszDescription } - , m_Func{ func } + ConCommand::ConCommand( + std::string_view pszName, + std::string_view pszDescription, + ConCommandFunc func, + bool bRegisterScript ) : + m_pszName{ pszName }, m_pszDescription{ pszDescription }, m_Func{ func } { - assert( !GetCommands().contains( pszName ) ); - GetCommands()[ std::string( pszName ) ] = this; + assert( !GetCommands( ).contains( pszName ) ); + GetCommands( )[ std::string( pszName ) ] = this; #if HAVE_SCRIPTING - if ( bRegisterScript ) - RegisterScript( pszName, this ); + if ( bRegisterScript ) RegisterScript( pszName, this ); #endif } - ConCommand::~ConCommand() - { - GetCommands().erase( GetCommands().find( m_pszName ) ); - } + ConCommand::~ConCommand( ) + { GetCommands( ).erase( GetCommands( ).find( m_pszName ) ); } bool ConCommand::Exec( std::span args ) { - if ( args.size() < 1 ) + if ( args.size( ) < 1 ) { console_log.warnf( "No command specified." ); return false; } - std::string_view commandName = args[0]; - auto iter = GetCommands().find( commandName ); - if ( iter == GetCommands().end() ) + std::string_view commandName = args[ 0 ]; + auto iter = GetCommands( ).find( commandName ); + if ( iter == GetCommands( ).end( ) ) { console_log.warnf( "Command not found." ); return false; @@ -49,38 +48,48 @@ namespace gamescope return true; } - Dict& ConCommand::GetCommands() + Dict &ConCommand::GetCommands( ) { static Dict s_Commands; return s_Commands; } - static ConCommand cc_help("help", "List all Gamescope convars and commands", - []( std::span args ) - { - auto &commands = ConCommand::GetCommands(); + static ConCommand cc_help( + "help", + "List all Gamescope convars and commands", + []( std::span args ) + { + auto &commands = ConCommand::GetCommands( ); - struct CommandHelp { std::string_view pszName; std::string_view pszDesc; }; - std::vector commandHelps; - for ( auto &command : commands ) - commandHelps.emplace_back( command.second->GetName(), command.second->GetDescription() ); - std::sort( commandHelps.begin(), commandHelps.end(), - []( const CommandHelp &a, const CommandHelp &b ) + struct CommandHelp { - return a.pszName < b.pszName; - }); + std::string_view pszName; + std::string_view pszDesc; + }; + std::vector commandHelps; + for ( auto &command : commands ) + commandHelps.emplace_back( + command.second->GetName( ), + command.second->GetDescription( ) ); + std::sort( + commandHelps.begin( ), + commandHelps.end( ), + []( const CommandHelp &a, const CommandHelp &b ) + { return a.pszName < b.pszName; } ); - for ( auto &help : commandHelps ) - { - console_log.infof( "%.*s: %.*s", - (int)help.pszName.size(), help.pszName.data(), - (int)help.pszDesc.size(), help.pszDesc.data() ); - } - }); + for ( auto &help : commandHelps ) + { + console_log.infof( + "%.*s: %.*s", + ( int )help.pszName.size( ), + help.pszName.data( ), + ( int )help.pszDesc.size( ), + help.pszDesc.data( ) ); + } + } ); - static ConCommand cc_version("version", "Print current Gamescope version", - []( std::span args ) - { - PrintVersion(); - }); -} + static ConCommand cc_version( + "version", + "Print current Gamescope version", + []( std::span args ) { PrintVersion( ); } ); +} // namespace gamescope diff --git a/src/convar.h b/src/convar.h index e80bcd0602..1ca1d1a09f 100644 --- a/src/convar.h +++ b/src/convar.h @@ -1,13 +1,13 @@ #pragma once +#include +#include +#include +#include #include #include #include -#include -#include #include -#include -#include #include "Utils/Dict.h" @@ -19,82 +19,81 @@ namespace gamescope { class ConCommand; - template - inline std::string ToString( const T &thing ) - { - return std::to_string( thing ); - } + template inline std::string ToString( const T &thing ) + { return std::to_string( thing ); } - template <> - inline std::string ToString( const std::string &sThing ) - { - return sThing; - } + template<> inline std::string ToString( const std::string &sThing ) + { return sThing; } - template <> - inline std::string ToString( const std::string_view &svThing ) - { - return std::string( svThing ); - } + template<> inline std::string ToString( const std::string_view &svThing ) + { return std::string( svThing ); } - template - inline std::optional Parse( std::string_view chars ) + template inline std::optional Parse( std::string_view chars ) { - T obj; - auto result = std::from_chars( chars.begin(), chars.end(), obj ); - if ( result.ec == std::errc{} ) - return obj; + T obj; + auto result = std::from_chars( chars.begin( ), chars.end( ), obj ); + if ( result.ec == std::errc{} ) return obj; else return std::nullopt; } - template <> - inline std::optional Parse( std::string_view chars ) + template<> inline std::optional Parse( std::string_view chars ) { std::optional oNumber = Parse( chars ); - if ( oNumber ) - return !!*oNumber; + if ( oNumber ) return !!*oNumber; - if ( chars == "true" ) - return true; + if ( chars == "true" ) return true; else return false; } - inline void Split( std::vector &tokens, std::string_view string, std::string_view delims = " " ) + inline void Split( + std::vector &tokens, + std::string_view string, + std::string_view delims = " " ) { size_t end = 0; - for ( size_t start = 0; start < string.size() && end != std::string_view::npos; start = end + 1 ) + for ( size_t start = 0; + start < string.size( ) && end != std::string_view::npos; + start = end + 1 ) { end = string.find_first_of( delims, start ); if ( start != end ) - tokens.emplace_back( string.substr( start, end-start ) ); + tokens.emplace_back( string.substr( start, end - start ) ); } } - inline std::vector Split( std::string_view string, std::string_view delims = " " ) + inline std::vector + Split( std::string_view string, std::string_view delims = " " ) { std::vector tokens; Split( tokens, string, delims ); return tokens; } - namespace detail { struct ConVarScriptRegistrar; } + namespace detail + { + struct ConVarScriptRegistrar; + } class ConCommand { friend struct detail::ConVarScriptRegistrar; - using ConCommandFunc = std::function )>; + using ConCommandFunc = + std::function )>; public: - ConCommand( std::string_view pszName, std::string_view pszDescription, ConCommandFunc func, bool bRegisterScript = true ); - ~ConCommand(); + ConCommand( + std::string_view pszName, + std::string_view pszDescription, + ConCommandFunc func, + bool bRegisterScript = true ); + ~ConCommand( ); void Invoke( std::span args ) { - if ( m_Func ) - m_Func( args ); + if ( m_Func ) m_Func( args ); } // Calls it with space separated args. @@ -109,39 +108,45 @@ namespace gamescope static bool Exec( std::span args ); - std::string_view GetName() const { return m_pszName; } - std::string_view GetDescription() const { return m_pszDescription; } + std::string_view GetName( ) const { return m_pszName; } + std::string_view GetDescription( ) const { return m_pszDescription; } - static Dict& GetCommands(); + static Dict &GetCommands( ); #if HAVE_SCRIPTING static void RegisterScript( std::string_view name, ConCommand *cmd ); #endif protected: std::string_view m_pszName; std::string_view m_pszDescription; - ConCommandFunc m_Func; + ConCommandFunc m_Func; }; - - template - class ConVar : public ConCommand + template class ConVar : public ConCommand { friend struct detail::ConVarScriptRegistrar; - using ConVarCallbackFunc = std::function &)>; + using ConVarCallbackFunc = std::function & )>; + public: - ConVar( std::string_view pszName, T defaultValue = T{}, std::string_view pszDescription = "", ConVarCallbackFunc func = nullptr, bool bRunCallbackAtStartup = false, bool bRegisterScript = true ) - : ConCommand( pszName, pszDescription, [this]( std::span pArgs ){ this->InvokeFunc( pArgs ); }, false ) - , m_Value{ defaultValue } - , m_Callback{ func } + ConVar( + std::string_view pszName, + T defaultValue = T{}, + std::string_view pszDescription = "", + ConVarCallbackFunc func = nullptr, + bool bRunCallbackAtStartup = false, + bool bRegisterScript = true ) : + ConCommand( + pszName, + pszDescription, + [ this ]( std::span pArgs ) + { this->InvokeFunc( pArgs ); }, + false ), + m_Value{ defaultValue }, + m_Callback{ func } { - if ( bRunCallbackAtStartup ) - { - RunCallback(); - } + if ( bRunCallbackAtStartup ) { RunCallback( ); } #if HAVE_SCRIPTING - if ( bRegisterScript ) - RegisterScript( pszName, this ); + if ( bRegisterScript ) RegisterScript( pszName, this ); #endif } @@ -149,20 +154,16 @@ namespace gamescope static void RegisterScript( std::string_view name, ConVar *cv ); #endif - const T& Get() const - { - return m_Value; - } + const T &Get( ) const { return m_Value; } - template - void SetValue( const J &newValue ) + template void SetValue( const J &newValue ) { m_Value = T{ newValue }; - RunCallback(); + RunCallback( ); } - void RunCallback() + void RunCallback( ) { if ( !m_bInCallback && m_Callback ) { @@ -172,66 +173,80 @@ namespace gamescope } } - template - ConVar& operator =( const J &newValue ) { SetValue( newValue ); return *this; } + template ConVar &operator=( const J &newValue ) + { + SetValue( newValue ); + return *this; + } - operator T() const { return m_Value; } + operator T( ) const { return m_Value; } // SFINAE for std::string... - operator std::string_view() const { return m_Value; } - - template bool operator == ( const J &other ) const { return m_Value == other; } - template bool operator != ( const J &other ) const { return m_Value != other; } - template auto operator <=>( const J &other ) const { return m_Value <=> other; } - - template bool operator == ( const ConVar &other ) const { return *this == other.Get(); } - template bool operator != ( const ConVar &other ) const { return *this != other.Get(); } - template auto operator <=>( const ConVar &other ) const { return *this <=> other.Get(); } - - T operator | (T other) { return m_Value | other; } - T &operator |=(T other) { return m_Value |= other; } - T operator & (T other) { return m_Value & other; } - T &operator &=(T other) { return m_Value &= other; } + operator std::string_view( ) const { return m_Value; } + + template bool operator==( const J &other ) const + { return m_Value == other; } + template bool operator!=( const J &other ) const + { return m_Value != other; } + template auto operator<=>( const J &other ) const + { return m_Value <=> other; } + + template bool operator==( const ConVar &other ) const + { return *this == other.Get( ); } + template bool operator!=( const ConVar &other ) const + { return *this != other.Get( ); } + template auto operator<=>( const ConVar &other ) const + { return *this <=> other.Get( ); } + + T operator|( T other ) { return m_Value | other; } + T &operator|=( T other ) { return m_Value |= other; } + T operator&( T other ) { return m_Value & other; } + T &operator&=( T other ) { return m_Value &= other; } void InvokeFunc( std::span pArgs ) { - if ( pArgs.size() == 1 ) + if ( pArgs.size( ) == 1 ) { // We should move to std format for logging and stuff. // This is kinda gross and grody! std::string sValue = ToString( m_Value ); - console_log.infof( "%.*s: %.*s\n%.*s", - (int)m_pszName.length(), m_pszName.data(), - (int)sValue.length(), sValue.data(), - (int)m_pszDescription.length(), m_pszDescription.data() ); + console_log.infof( + "%.*s: %.*s\n%.*s", + ( int )m_pszName.length( ), + m_pszName.data( ), + ( int )sValue.length( ), + sValue.data( ), + ( int )m_pszDescription.length( ), + m_pszDescription.data( ) ); return; } - if ( pArgs.size() != 2 ) - return; + if ( pArgs.size( ) != 2 ) return; if constexpr ( std::is_enum::value ) { using Underlying = std::underlying_type::type; - std::optional oResult = Parse( pArgs[1] ); + std::optional oResult = + Parse( pArgs[ 1 ] ); SetValue( oResult ? static_cast( *oResult ) : T{} ); } - else if constexpr ( std::is_integral::value || std::is_floating_point::value ) + else if constexpr ( + std::is_integral::value || std::is_floating_point::value ) { - std::optional oResult = Parse( pArgs[1] ); + std::optional oResult = Parse( pArgs[ 1 ] ); SetValue( oResult ? *oResult : T{} ); } else { - SetValue( pArgs[1] ); + SetValue( pArgs[ 1 ] ); } } + private: - T m_Value{}; + T m_Value{}; ConVarCallbackFunc m_Callback; - bool m_bInCallback; + bool m_bInCallback; }; - -} +} // namespace gamescope diff --git a/src/convar_script.cpp b/src/convar_script.cpp index aaaf497297..c57abaadd2 100644 --- a/src/convar_script.cpp +++ b/src/convar_script.cpp @@ -1,8 +1,8 @@ #if HAVE_SCRIPTING -#include "convar.h" -#include "Script/Script.h" -#include "backend.h" + #include "Script/Script.h" + #include "backend.h" + #include "convar.h" namespace gamescope { @@ -10,46 +10,55 @@ namespace gamescope { struct ConVarScriptRegistrar { - static sol::usertype RegisterConCommand() + static sol::usertype RegisterConCommand( ) { - return CScriptScopedLock()->new_usertype( "concommand", - "name", &ConCommand::m_pszName, - "description", &ConCommand::m_pszDescription, - "call", &ConCommand::CallWithArgString ); + return CScriptScopedLock( )->new_usertype( + "concommand", + "name", + &ConCommand::m_pszName, + "description", + &ConCommand::m_pszDescription, + "call", + &ConCommand::CallWithArgString ); } - template - static sol::usertype> RegisterConVarType() + template + static sol::usertype> RegisterConVarType( ) { - return CScriptScopedLock()->new_usertype>( - typeid( ConVar ).name(), - "name", &ConVar::m_pszName, - "description", &ConVar::m_pszDescription, - "call", &ConVar::CallWithArgString, - "value", &ConVar::m_Value ); + return CScriptScopedLock( )->new_usertype>( + typeid( ConVar ).name( ), + "name", + &ConVar::m_pszName, + "description", + &ConVar::m_pszDescription, + "call", + &ConVar::CallWithArgString, + "value", + &ConVar::m_Value ); } }; - } + } // namespace detail void ConCommand::RegisterScript( std::string_view name, ConCommand *cmd ) - { - CScriptScopedLock().Manager().Gamescope().Convars.Base[name] = cmd; - } + { CScriptScopedLock( ).Manager( ).Gamescope( ).Convars.Base[ name ] = cmd; } - template + template void ConVar::RegisterScript( std::string_view name, ConVar *cv ) - { - CScriptScopedLock().Manager().Gamescope().Convars.Base[name] = cv; - } + { CScriptScopedLock( ).Manager( ).Gamescope( ).Convars.Base[ name ] = cv; } -#define REGISTER_CONVAR_TYPE_IMPL( T, N ) \ - static auto s_ConVarType_##N = detail::ConVarScriptRegistrar::RegisterConVarType(); \ - template void ConVar::RegisterScript( std::string_view, ConVar * ); + #define REGISTER_CONVAR_TYPE_IMPL( T, N ) \ + static auto s_ConVarType_##N = \ + detail::ConVarScriptRegistrar::RegisterConVarType( ); \ + template void ConVar::RegisterScript( \ + std::string_view, ConVar * ); -#define REGISTER_CONVAR_TYPE_EXPAND( T, N ) REGISTER_CONVAR_TYPE_IMPL( T, N ) -#define REGISTER_CONVAR_TYPE( T ) REGISTER_CONVAR_TYPE_EXPAND( T, __COUNTER__ ) + #define REGISTER_CONVAR_TYPE_EXPAND( T, N ) \ + REGISTER_CONVAR_TYPE_IMPL( T, N ) + #define REGISTER_CONVAR_TYPE( T ) \ + REGISTER_CONVAR_TYPE_EXPAND( T, __COUNTER__ ) - static auto s_ConCommandType = detail::ConVarScriptRegistrar::RegisterConCommand(); + static auto s_ConCommandType = + detail::ConVarScriptRegistrar::RegisterConCommand( ); REGISTER_CONVAR_TYPE( bool ) REGISTER_CONVAR_TYPE( int ) REGISTER_CONVAR_TYPE( float ) @@ -58,6 +67,6 @@ namespace gamescope REGISTER_CONVAR_TYPE( std::string ) REGISTER_CONVAR_TYPE( VirtualConnectorStrategy ) REGISTER_CONVAR_TYPE( TouchClickMode ) -} +} // namespace gamescope #endif diff --git a/src/drm_include.h b/src/drm_include.h index d0ed3ce9e2..72e00a07bd 100644 --- a/src/drm_include.h +++ b/src/drm_include.h @@ -1,88 +1,94 @@ #pragma once -#include -#include #include #include +#include +#include -#include "wlr_begin.hpp" #include #include +#include "wlr_begin.hpp" #include "wlr_end.hpp" #include "hdmi.h" // Josh: Okay whatever, this header isn't // available for whatever stupid reason. :v -//#include -enum drm_color_encoding { - DRM_COLOR_YCBCR_BT601, - DRM_COLOR_YCBCR_BT709, - DRM_COLOR_YCBCR_BT2020, - DRM_COLOR_ENCODING_MAX, +// #include +enum drm_color_encoding +{ + DRM_COLOR_YCBCR_BT601, + DRM_COLOR_YCBCR_BT709, + DRM_COLOR_YCBCR_BT2020, + DRM_COLOR_ENCODING_MAX, }; -enum drm_color_range { - DRM_COLOR_YCBCR_LIMITED_RANGE, - DRM_COLOR_YCBCR_FULL_RANGE, - DRM_COLOR_RANGE_MAX, +enum drm_color_range +{ + DRM_COLOR_YCBCR_LIMITED_RANGE, + DRM_COLOR_YCBCR_FULL_RANGE, + DRM_COLOR_RANGE_MAX, }; -enum amdgpu_transfer_function { - AMDGPU_TRANSFER_FUNCTION_DEFAULT, - AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF, - AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF, - AMDGPU_TRANSFER_FUNCTION_PQ_EOTF, - AMDGPU_TRANSFER_FUNCTION_IDENTITY, - AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF, - AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF, - AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF, - AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF, - AMDGPU_TRANSFER_FUNCTION_BT709_OETF, - AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF, - AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF, - AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF, - AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF, - AMDGPU_TRANSFER_FUNCTION_COUNT +enum amdgpu_transfer_function +{ + AMDGPU_TRANSFER_FUNCTION_DEFAULT, + AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF, + AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF, + AMDGPU_TRANSFER_FUNCTION_PQ_EOTF, + AMDGPU_TRANSFER_FUNCTION_IDENTITY, + AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF, + AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF, + AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF, + AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF, + AMDGPU_TRANSFER_FUNCTION_BT709_OETF, + AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF, + AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF, + AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF, + AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF, + AMDGPU_TRANSFER_FUNCTION_COUNT }; -enum drm_panel_orientation { - DRM_MODE_PANEL_ORIENTATION_UNKNOWN = -1, - DRM_MODE_PANEL_ORIENTATION_NORMAL = 0, - DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP, - DRM_MODE_PANEL_ORIENTATION_LEFT_UP, - DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, +enum drm_panel_orientation +{ + DRM_MODE_PANEL_ORIENTATION_UNKNOWN = -1, + DRM_MODE_PANEL_ORIENTATION_NORMAL = 0, + DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP, + DRM_MODE_PANEL_ORIENTATION_LEFT_UP, + DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, }; -enum drm_colorspace { - /* For Default case, driver will set the colorspace */ - DRM_MODE_COLORIMETRY_DEFAULT = 0, - /* CEA 861 Normal Colorimetry options */ - DRM_MODE_COLORIMETRY_NO_DATA = 0, - DRM_MODE_COLORIMETRY_SMPTE_170M_YCC = 1, - DRM_MODE_COLORIMETRY_BT709_YCC = 2, - /* CEA 861 Extended Colorimetry Options */ - DRM_MODE_COLORIMETRY_XVYCC_601 = 3, - DRM_MODE_COLORIMETRY_XVYCC_709 = 4, - DRM_MODE_COLORIMETRY_SYCC_601 = 5, - DRM_MODE_COLORIMETRY_OPYCC_601 = 6, - DRM_MODE_COLORIMETRY_OPRGB = 7, - DRM_MODE_COLORIMETRY_BT2020_CYCC = 8, - DRM_MODE_COLORIMETRY_BT2020_RGB = 9, - DRM_MODE_COLORIMETRY_BT2020_YCC = 10, - /* Additional Colorimetry extension added as part of CTA 861.G */ - DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 = 11, - DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER = 12, - /* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format */ - DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED = 13, - DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT = 14, - DRM_MODE_COLORIMETRY_BT601_YCC = 15, - DRM_MODE_COLORIMETRY_COUNT +enum drm_colorspace +{ + /* For Default case, driver will set the colorspace */ + DRM_MODE_COLORIMETRY_DEFAULT = 0, + /* CEA 861 Normal Colorimetry options */ + DRM_MODE_COLORIMETRY_NO_DATA = 0, + DRM_MODE_COLORIMETRY_SMPTE_170M_YCC = 1, + DRM_MODE_COLORIMETRY_BT709_YCC = 2, + /* CEA 861 Extended Colorimetry Options */ + DRM_MODE_COLORIMETRY_XVYCC_601 = 3, + DRM_MODE_COLORIMETRY_XVYCC_709 = 4, + DRM_MODE_COLORIMETRY_SYCC_601 = 5, + DRM_MODE_COLORIMETRY_OPYCC_601 = 6, + DRM_MODE_COLORIMETRY_OPRGB = 7, + DRM_MODE_COLORIMETRY_BT2020_CYCC = 8, + DRM_MODE_COLORIMETRY_BT2020_RGB = 9, + DRM_MODE_COLORIMETRY_BT2020_YCC = 10, + /* Additional Colorimetry extension added as part of CTA 861.G */ + DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 = 11, + DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER = 12, + /* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format + */ + DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED = 13, + DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT = 14, + DRM_MODE_COLORIMETRY_BT601_YCC = 15, + DRM_MODE_COLORIMETRY_COUNT }; /* Content type options */ -#define DRM_MODE_CONTENT_TYPE_NO_DATA 0 -#define DRM_MODE_CONTENT_TYPE_GRAPHICS 1 -#define DRM_MODE_CONTENT_TYPE_PHOTO 2 -#define DRM_MODE_CONTENT_TYPE_CINEMA 3 -#define DRM_MODE_CONTENT_TYPE_GAME 4 +#define DRM_MODE_CONTENT_TYPE_NO_DATA 0 +#define DRM_MODE_CONTENT_TYPE_GRAPHICS 1 +#define DRM_MODE_CONTENT_TYPE_PHOTO 2 +#define DRM_MODE_CONTENT_TYPE_CINEMA 3 +#define DRM_MODE_CONTENT_TYPE_GAME 4 diff --git a/src/edid.cpp b/src/edid.cpp index 6215829313..c06079038f 100644 --- a/src/edid.cpp +++ b/src/edid.cpp @@ -1,130 +1,148 @@ #include "edid.h" #include "backend.h" -#include "log.hpp" #include "hdmi.h" +#include "log.hpp" #include #include -#include #include +#include extern "C" { -#include "libdisplay-info/info.h" -#include "libdisplay-info/edid.h" #include "libdisplay-info/cta.h" +#include "libdisplay-info/edid.h" +#include "libdisplay-info/info.h" } -static LogScope edid_log("edid"); +static LogScope edid_log( "edid" ); namespace gamescope { - static constexpr uint32_t EDID_MAX_BLOCK_COUNT = 256; - static constexpr uint32_t EDID_BLOCK_SIZE = 128; - static constexpr uint32_t EDID_MAX_STANDARD_TIMING_COUNT = 8; - static constexpr uint32_t EDID_BYTE_DESCRIPTOR_COUNT = 4; - static constexpr uint32_t EDID_BYTE_DESCRIPTOR_SIZE = 18; + static constexpr uint32_t EDID_MAX_BLOCK_COUNT = 256; + static constexpr uint32_t EDID_BLOCK_SIZE = 128; + static constexpr uint32_t EDID_MAX_STANDARD_TIMING_COUNT = 8; + static constexpr uint32_t EDID_BYTE_DESCRIPTOR_COUNT = 4; + static constexpr uint32_t EDID_BYTE_DESCRIPTOR_SIZE = 18; static constexpr uint32_t EDID_MAX_DESCRIPTOR_STANDARD_TIMING_COUNT = 6; - static constexpr uint32_t EDID_MAX_DESCRIPTOR_COLOR_POINT_COUNT = 2; - static constexpr uint32_t EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT = 44; + static constexpr uint32_t EDID_MAX_DESCRIPTOR_COLOR_POINT_COUNT = 2; + static constexpr uint32_t EDID_MAX_DESCRIPTOR_ESTABLISHED_TIMING_III_COUNT = + 44; static constexpr uint32_t EDID_MAX_DESCRIPTOR_CVT_TIMING_CODES_COUNT = 4; - static inline uint8_t get_bit_range(uint8_t val, size_t high, size_t low) + static inline uint8_t get_bit_range( uint8_t val, size_t high, size_t low ) { - size_t n; + size_t n; uint8_t bitmask; - assert(high <= 7 && high >= low); + assert( high <= 7 && high >= low ); - n = high - low + 1; - bitmask = (uint8_t) ((1 << n) - 1); - return (uint8_t) (val >> low) & bitmask; + n = high - low + 1; + bitmask = ( uint8_t )( ( 1 << n ) - 1 ); + return ( uint8_t )( val >> low ) & bitmask; } - static inline void set_bit_range(uint8_t *val, size_t high, size_t low, uint8_t bits) + static inline void + set_bit_range( uint8_t *val, size_t high, size_t low, uint8_t bits ) { - size_t n; + size_t n; uint8_t bitmask; - assert(high <= 7 && high >= low); + assert( high <= 7 && high >= low ); - n = high - low + 1; - bitmask = (uint8_t) ((1 << n) - 1); - assert((bits & ~bitmask) == 0); + n = high - low + 1; + bitmask = ( uint8_t )( ( 1 << n ) - 1 ); + assert( ( bits & ~bitmask ) == 0 ); - *val &= ~(bitmask << low); - *val |= (uint8_t)(bits << low); + *val &= ~( bitmask << low ); + *val |= ( uint8_t )( bits << low ); } - - static inline void patch_edid_checksum(uint8_t* block) + static inline void patch_edid_checksum( uint8_t *block ) { uint8_t sum = 0; - for (uint32_t i = 0; i < EDID_BLOCK_SIZE - 1; i++) - sum += block[i]; + for ( uint32_t i = 0; i < EDID_BLOCK_SIZE - 1; i++ ) + sum += block[ i ]; - uint8_t checksum = uint32_t(256) - uint32_t(sum); + uint8_t checksum = uint32_t( 256 ) - uint32_t( sum ); - block[127] = checksum; + block[ 127 ] = checksum; } - static bool validate_block_checksum(const uint8_t* data) + static bool validate_block_checksum( const uint8_t *data ) { uint8_t sum = 0; - size_t i; + size_t i; - for (i = 0; i < EDID_BLOCK_SIZE; i++) { - sum += data[i]; + for ( i = 0; i < EDID_BLOCK_SIZE; i++ ) + { + sum += data[ i ]; } return sum == 0; } - static uint8_t encode_max_luminance(float nits) + static uint8_t encode_max_luminance( float nits ) { - if (nits == 0.0f) - return 0; + if ( nits == 0.0f ) return 0; - return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); + return ceilf( ( logf( nits / 50.0f ) / logf( 2.0f ) ) * 32.0f ); } - std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ) + std::optional> PatchEdid( + std::span pEdid, + const BackendConnectorHDRInfo &hdrInfo, + bool bRotate ) { // A zero length indicates that the edid parsing failed. - if ( pEdid.empty() ) - return std::nullopt; + if ( pEdid.empty( ) ) return std::nullopt; - std::vector edid( pEdid.begin(), pEdid.end() ); + std::vector edid( pEdid.begin( ), pEdid.end( ) ); if ( bRotate ) { // Patch width, height. - edid_log.infof("Patching dims %ux%u -> %ux%u", edid[0x15], edid[0x16], edid[0x16], edid[0x15]); - std::swap(edid[0x15], edid[0x16]); - - for (uint32_t i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++) + edid_log.infof( + "Patching dims %ux%u -> %ux%u", + edid[ 0x15 ], + edid[ 0x16 ], + edid[ 0x16 ], + edid[ 0x15 ] ); + std::swap( edid[ 0x15 ], edid[ 0x16 ] ); + + for ( uint32_t i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++ ) { - uint8_t *byte_desc_data = &edid[0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE]; - if (byte_desc_data[0] || byte_desc_data[1]) + uint8_t *byte_desc_data = + &edid[ 0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE ]; + if ( byte_desc_data[ 0 ] || byte_desc_data[ 1 ] ) { - uint32_t horiz = (get_bit_range(byte_desc_data[4], 7, 4) << 8) | byte_desc_data[2]; - uint32_t vert = (get_bit_range(byte_desc_data[7], 7, 4) << 8) | byte_desc_data[5]; - edid_log.infof("Patching res %ux%u -> %ux%u", horiz, vert, vert, horiz); - std::swap(byte_desc_data[4], byte_desc_data[7]); - std::swap(byte_desc_data[2], byte_desc_data[5]); + uint32_t horiz = + ( get_bit_range( byte_desc_data[ 4 ], 7, 4 ) << 8 ) | + byte_desc_data[ 2 ]; + uint32_t vert = + ( get_bit_range( byte_desc_data[ 7 ], 7, 4 ) << 8 ) | + byte_desc_data[ 5 ]; + edid_log.infof( + "Patching res %ux%u -> %ux%u", + horiz, + vert, + vert, + horiz ); + std::swap( byte_desc_data[ 4 ], byte_desc_data[ 7 ] ); + std::swap( byte_desc_data[ 2 ], byte_desc_data[ 5 ] ); break; } } - patch_edid_checksum(&edid[0]); + patch_edid_checksum( &edid[ 0 ] ); } // If we are debugging HDR support lazily on a regular Deck, - // just hotpatch the edid for the game so we get values we want as if we had - // an external display attached. - // (Allows for debugging undocked fallback without undocking/redocking) - if ( !hdrInfo.ShouldPatchEDID() ) + // just hotpatch the edid for the game so we get values we want as if we + // had an external display attached. (Allows for debugging undocked + // fallback without undocking/redocking) + if ( !hdrInfo.ShouldPatchEDID( ) ) return bRotate ? std::optional( edid ) : std::nullopt; // TODO: Allow for override of min luminance @@ -133,73 +151,96 @@ namespace gamescope g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits : g_ColorMgmt.pending.flInternalDisplayBrightness; #endif - // TODO(JoshA): Need to resolve flInternalDisplayBrightness vs new connector hdrinfo mechanism. + // TODO(JoshA): Need to resolve flInternalDisplayBrightness vs new + // connector hdrinfo mechanism. - edid_log.infof("[edid] Patching HDR static metadata:\n" + edid_log.infof( + "[edid] Patching HDR static metadata:\n" " - Max peak luminance = %u nits\n" " - Max frame average luminance = %u nits", - hdrInfo.uMaxContentLightLevel, hdrInfo.uMaxFrameAverageLuminance ); - const uint8_t new_hdr_static_metadata_block[] - { - (1 << HDMI_EOTF_SDR) | (1 << HDMI_EOTF_TRADITIONAL_HDR) | (1 << HDMI_EOTF_ST2084), /* supported eotfs */ - 1, /* type 1 */ - encode_max_luminance( float( hdrInfo.uMaxContentLightLevel ) ), /* desired content max peak luminance */ - encode_max_luminance( float( hdrInfo.uMaxFrameAverageLuminance ) ), /* desired content max frame avg luminance */ - 0, /* desired content min luminance -- 0 is technically "undefined" */ + hdrInfo.uMaxContentLightLevel, + hdrInfo.uMaxFrameAverageLuminance ); + const uint8_t new_hdr_static_metadata_block[]{ + ( 1 << HDMI_EOTF_SDR ) | ( 1 << HDMI_EOTF_TRADITIONAL_HDR ) | + ( 1 << HDMI_EOTF_ST2084 ), /* supported eotfs */ + 1, /* type 1 */ + encode_max_luminance( + float( hdrInfo.uMaxContentLightLevel ) ), /* desired content max + peak luminance */ + encode_max_luminance( + float( hdrInfo.uMaxFrameAverageLuminance ) ), /* desired content + max frame avg + luminance */ + 0, /* desired content min luminance -- 0 is technically "undefined" + */ }; - int ext_count = int(edid.size() / EDID_BLOCK_SIZE) - 1; - assert(ext_count == edid[0x7E]); - bool has_cta_block = false; + int ext_count = int( edid.size( ) / EDID_BLOCK_SIZE ) - 1; + assert( ext_count == edid[ 0x7E ] ); + bool has_cta_block = false; bool has_hdr_metadata_block = false; - for (int i = 0; i < ext_count; i++) + for ( int i = 0; i < ext_count; i++ ) { - uint8_t *ext_data = &edid[EDID_BLOCK_SIZE + i * EDID_BLOCK_SIZE]; - uint8_t tag = ext_data[0]; - if (tag == DI_EDID_EXT_CEA) + uint8_t *ext_data = &edid[ EDID_BLOCK_SIZE + i * EDID_BLOCK_SIZE ]; + uint8_t tag = ext_data[ 0 ]; + if ( tag == DI_EDID_EXT_CEA ) { - has_cta_block = true; - uint8_t dtd_start = ext_data[2]; - uint8_t flags = ext_data[3]; - if (dtd_start == 0) + has_cta_block = true; + uint8_t dtd_start = ext_data[ 2 ]; + uint8_t flags = ext_data[ 3 ]; + if ( dtd_start == 0 ) { - edid_log.infof("Hmmmm.... dtd start is 0. Interesting... Not going further! :-("); + edid_log.infof( + "Hmmmm.... dtd start is 0. Interesting... Not going " + "further! :-(" ); continue; } - if (flags != 0) + if ( flags != 0 ) { - edid_log.infof("Hmmmm.... non-zero CTA flags. Interesting... Not going further! :-("); + edid_log.infof( + "Hmmmm.... non-zero CTA flags. Interesting... Not " + "going further! :-(" ); continue; } const int CTA_HEADER_SIZE = 4; - int j = CTA_HEADER_SIZE; - while (j < dtd_start) + int j = CTA_HEADER_SIZE; + while ( j < dtd_start ) { - uint8_t data_block_header = ext_data[j]; - uint8_t data_block_tag = get_bit_range(data_block_header, 7, 5); - uint8_t data_block_size = get_bit_range(data_block_header, 4, 0); + uint8_t data_block_header = ext_data[ j ]; + uint8_t data_block_tag = + get_bit_range( data_block_header, 7, 5 ); + uint8_t data_block_size = + get_bit_range( data_block_header, 4, 0 ); - if (j + 1 + data_block_size > dtd_start) + if ( j + 1 + data_block_size > dtd_start ) { - edid_log.infof("Hmmmm.... CTA malformatted. Interesting... Not going further! :-("); + edid_log.infof( + "Hmmmm.... CTA malformatted. Interesting... Not " + "going further! :-(" ); break; } - uint8_t *data_block = &ext_data[j + 1]; - if (data_block_tag == 7) // extended + uint8_t *data_block = &ext_data[ j + 1 ]; + if ( data_block_tag == 7 ) // extended { - uint8_t extended_tag = data_block[0]; - uint8_t *extended_block = &data_block[1]; - uint8_t extended_block_size = data_block_size - 1; + uint8_t extended_tag = data_block[ 0 ]; + uint8_t *extended_block = &data_block[ 1 ]; + uint8_t extended_block_size = data_block_size - 1; - if (extended_tag == 6) // hdr static + if ( extended_tag == 6 ) // hdr static { - if (extended_block_size >= sizeof(new_hdr_static_metadata_block)) + if ( extended_block_size >= + sizeof( new_hdr_static_metadata_block ) ) { - edid_log.infof("Patching existing HDR Metadata with our own!"); - memcpy(extended_block, new_hdr_static_metadata_block, sizeof(new_hdr_static_metadata_block)); + edid_log.infof( + "Patching existing HDR Metadata with our " + "own!" ); + memcpy( + extended_block, + new_hdr_static_metadata_block, + sizeof( new_hdr_static_metadata_block ) ); has_hdr_metadata_block = true; } } @@ -208,75 +249,103 @@ namespace gamescope j += 1 + data_block_size; // account for header size. } - if (!has_hdr_metadata_block) + if ( !has_hdr_metadata_block ) { - const int hdr_metadata_block_size_plus_headers = sizeof(new_hdr_static_metadata_block) + 2; // +1 for header, +1 for extended header -> +2 - edid_log.infof("No HDR metadata block to patch... Trying to insert one."); + const int hdr_metadata_block_size_plus_headers = + sizeof( new_hdr_static_metadata_block ) + + 2; // +1 for header, +1 for extended header -> +2 + edid_log.infof( + "No HDR metadata block to patch... Trying to insert " + "one." ); // Assert that the end of the data blocks == dtd_start - if (dtd_start != j) + if ( dtd_start != j ) { - edid_log.infof("dtd_start != end of blocks. Giving up patching. I'm too scared to attempt it."); + edid_log.infof( + "dtd_start != end of blocks. Giving up patching. " + "I'm too scared to attempt it." ); } // Move back the dtd to make way for our block at the end. - uint8_t *dtd = &ext_data[dtd_start]; - memmove(dtd + hdr_metadata_block_size_plus_headers, dtd, hdr_metadata_block_size_plus_headers); + uint8_t *dtd = &ext_data[ dtd_start ]; + memmove( + dtd + hdr_metadata_block_size_plus_headers, + dtd, + hdr_metadata_block_size_plus_headers ); dtd_start += hdr_metadata_block_size_plus_headers; // Data block is where the dtd was. uint8_t *data_block = dtd; // header - data_block[0] = 0; - set_bit_range(&data_block[0], 7, 5, 7); // extended tag - set_bit_range(&data_block[0], 4, 0, sizeof(new_hdr_static_metadata_block) + 1); // size (+1 for extended header, does not include normal header) + data_block[ 0 ] = 0; + set_bit_range( &data_block[ 0 ], 7, 5, 7 ); // extended tag + set_bit_range( + &data_block[ 0 ], + 4, + 0, + sizeof( new_hdr_static_metadata_block ) + + 1 ); // size (+1 for extended header, does not + // include normal header) // extended header - data_block[1] = 6; // hdr metadata extended tag - memcpy(&data_block[2], new_hdr_static_metadata_block, sizeof(new_hdr_static_metadata_block)); + data_block[ 1 ] = 6; // hdr metadata extended tag + memcpy( + &data_block[ 2 ], + new_hdr_static_metadata_block, + sizeof( new_hdr_static_metadata_block ) ); } - patch_edid_checksum(ext_data); - bool sum_valid = validate_block_checksum(ext_data); - edid_log.infof("CTA Checksum valid? %s", sum_valid ? "Y" : "N"); + patch_edid_checksum( ext_data ); + bool sum_valid = validate_block_checksum( ext_data ); + edid_log.infof( + "CTA Checksum valid? %s", sum_valid ? "Y" : "N" ); } } - if (!has_cta_block) + if ( !has_cta_block ) { - edid_log.infof("Couldn't patch for HDR metadata as we had no CTA block! Womp womp =c"); + edid_log.infof( + "Couldn't patch for HDR metadata as we had no CTA block! Womp " + "womp =c" ); } - bool sum_valid = validate_block_checksum(&edid[0]); - edid_log.infof("BASE Checksum valid? %s", sum_valid ? "Y" : "N"); + bool sum_valid = validate_block_checksum( &edid[ 0 ] ); + edid_log.infof( "BASE Checksum valid? %s", sum_valid ? "Y" : "N" ); return edid; } - const char *GetPatchedEdidPath() + const char *GetPatchedEdidPath( ) { - const char *pszPatchedEdidPath = getenv( "GAMESCOPE_PATCHED_EDID_FILE" ); - if ( !pszPatchedEdidPath || !*pszPatchedEdidPath ) - return nullptr; + const char *pszPatchedEdidPath = + getenv( "GAMESCOPE_PATCHED_EDID_FILE" ); + if ( !pszPatchedEdidPath || !*pszPatchedEdidPath ) return nullptr; return pszPatchedEdidPath; } - void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ) + void WritePatchedEdid( + std::span pEdid, + const BackendConnectorHDRInfo &hdrInfo, + bool bRotate ) { - const char *pszPatchedEdidPath = GetPatchedEdidPath(); - if ( !pszPatchedEdidPath ) - return; + const char *pszPatchedEdidPath = GetPatchedEdidPath( ); + if ( !pszPatchedEdidPath ) return; std::span pEdidToWrite = pEdid; auto oPatchedEdid = PatchEdid( pEdid, hdrInfo, bRotate ); if ( oPatchedEdid ) - pEdidToWrite = std::span{ oPatchedEdid->begin(), oPatchedEdid->end() }; + pEdidToWrite = std::span{ oPatchedEdid->begin( ), + oPatchedEdid->end( ) }; - char szTmpFilename[PATH_MAX]; - snprintf( szTmpFilename, sizeof( szTmpFilename ), "%s.tmp", pszPatchedEdidPath ); + char szTmpFilename[ PATH_MAX ]; + snprintf( + szTmpFilename, + sizeof( szTmpFilename ), + "%s.tmp", + pszPatchedEdidPath ); FILE *pFile = fopen( szTmpFilename, "wb" ); if ( !pFile ) @@ -285,7 +354,7 @@ namespace gamescope return; } - fwrite( pEdidToWrite.data(), 1, pEdidToWrite.size(), pFile ); + fwrite( pEdidToWrite.data( ), 1, pEdidToWrite.size( ), pFile ); fflush( pFile ); fclose( pFile ); @@ -297,8 +366,7 @@ namespace gamescope // From gamescope_base_edid.bin // Fake little thing we can patch. // It has one modeline of 90Hz, like Deck OLED. - static constexpr uint8_t s_GamescopeBaseEdid[] = - { + static constexpr uint8_t s_GamescopeBaseEdid[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1a, 0x47, 0x69, 0x69, 0x38, 0xf4, 0x01, 0x00, 0xff, 0x20, 0x01, 0x04, 0xa5, 0x0a, 0x10, 0x78, 0x17, 0x3c, 0x71, 0xae, 0x51, 0x3c, 0xb9, 0x23, 0x0c, 0x50, 0x54, 0x00, @@ -324,35 +392,53 @@ namespace gamescope }; std::vector GenerateSimpleEdid( uint32_t uWidth, uint32_t uHeight ) { - uWidth = std::min( uWidth, 3840 ); + uWidth = std::min( uWidth, 3840 ); uHeight = std::min( uHeight, 3840 ); // Does not patch refresh, nothing has cared about this yet. - std::vector edid( s_GamescopeBaseEdid, s_GamescopeBaseEdid + std::size( s_GamescopeBaseEdid ) ); + std::vector edid( + s_GamescopeBaseEdid, + s_GamescopeBaseEdid + std::size( s_GamescopeBaseEdid ) ); - for (uint32_t i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++) + for ( uint32_t i = 0; i < EDID_BYTE_DESCRIPTOR_COUNT; i++ ) { - uint8_t *byte_desc_data = &edid[0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE]; - if (byte_desc_data[0] || byte_desc_data[1]) + uint8_t *byte_desc_data = + &edid[ 0x36 + i * EDID_BYTE_DESCRIPTOR_SIZE ]; + if ( byte_desc_data[ 0 ] || byte_desc_data[ 1 ] ) { - uint32_t oldHoriz = (get_bit_range(byte_desc_data[4], 7, 4) << 8) | byte_desc_data[2]; - uint32_t oldVert = (get_bit_range(byte_desc_data[7], 7, 4) << 8) | byte_desc_data[5]; - - set_bit_range( &byte_desc_data[4], 7, 4, ( uWidth >> 8 ) & 0xff ); - byte_desc_data[2] = uWidth & 0xff; - - set_bit_range( &byte_desc_data[7], 7, 4, ( uHeight >> 8 ) & 0xff ); - byte_desc_data[5] = uHeight & 0xff; - - uint32_t horiz = (get_bit_range(byte_desc_data[4], 7, 4) << 8) | byte_desc_data[2]; - uint32_t vert = (get_bit_range(byte_desc_data[7], 7, 4) << 8) | byte_desc_data[5]; - edid_log.infof( "Patching res %ux%u -> %ux%u", oldHoriz, oldVert, horiz, vert ); + uint32_t oldHoriz = + ( get_bit_range( byte_desc_data[ 4 ], 7, 4 ) << 8 ) | + byte_desc_data[ 2 ]; + uint32_t oldVert = + ( get_bit_range( byte_desc_data[ 7 ], 7, 4 ) << 8 ) | + byte_desc_data[ 5 ]; + + set_bit_range( + &byte_desc_data[ 4 ], 7, 4, ( uWidth >> 8 ) & 0xff ); + byte_desc_data[ 2 ] = uWidth & 0xff; + + set_bit_range( + &byte_desc_data[ 7 ], 7, 4, ( uHeight >> 8 ) & 0xff ); + byte_desc_data[ 5 ] = uHeight & 0xff; + + uint32_t horiz = + ( get_bit_range( byte_desc_data[ 4 ], 7, 4 ) << 8 ) | + byte_desc_data[ 2 ]; + uint32_t vert = + ( get_bit_range( byte_desc_data[ 7 ], 7, 4 ) << 8 ) | + byte_desc_data[ 5 ]; + edid_log.infof( + "Patching res %ux%u -> %ux%u", + oldHoriz, + oldVert, + horiz, + vert ); break; } } - patch_edid_checksum(&edid[0]); + patch_edid_checksum( &edid[ 0 ] ); return edid; } -} +} // namespace gamescope diff --git a/src/edid.h b/src/edid.h index d0ed88f460..6c506236da 100644 --- a/src/edid.h +++ b/src/edid.h @@ -1,17 +1,24 @@ #pragma once -#include #include #include +#include #include namespace gamescope { struct BackendConnectorHDRInfo; - const char *GetPatchedEdidPath(); - void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ); - std::vector GenerateSimpleEdid( uint32_t uWidth, uint32_t uHeight ); + const char *GetPatchedEdidPath( ); + void WritePatchedEdid( + std::span pEdid, + const BackendConnectorHDRInfo &hdrInfo, + bool bRotate ); + std::vector + GenerateSimpleEdid( uint32_t uWidth, uint32_t uHeight ); - std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ); -} \ No newline at end of file + std::optional> PatchEdid( + std::span pEdid, + const BackendConnectorHDRInfo &hdrInfo, + bool bRotate ); +} // namespace gamescope diff --git a/src/gamescope_base_edid.bin b/src/gamescope_base_edid.bin index 0a671f7915..1509e46570 100644 Binary files a/src/gamescope_base_edid.bin and b/src/gamescope_base_edid.bin differ diff --git a/src/gamescope_shared.h b/src/gamescope_shared.h index ac552d492b..21276716e5 100644 --- a/src/gamescope_shared.h +++ b/src/gamescope_shared.h @@ -4,56 +4,56 @@ namespace gamescope { - class BackendBlob; + class BackendBlob; - enum GamescopeModeGeneration - { - GAMESCOPE_MODE_GENERATE_CVT, - GAMESCOPE_MODE_GENERATE_FIXED, - }; + enum GamescopeModeGeneration + { + GAMESCOPE_MODE_GENERATE_CVT, + GAMESCOPE_MODE_GENERATE_FIXED, + }; - enum GamescopeScreenType - { - GAMESCOPE_SCREEN_TYPE_INTERNAL, - GAMESCOPE_SCREEN_TYPE_EXTERNAL, + enum GamescopeScreenType + { + GAMESCOPE_SCREEN_TYPE_INTERNAL, + GAMESCOPE_SCREEN_TYPE_EXTERNAL, - GAMESCOPE_SCREEN_TYPE_COUNT - }; -} + GAMESCOPE_SCREEN_TYPE_COUNT + }; +} // namespace gamescope enum GamescopeAppTextureColorspace { - GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR = 0, - GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, - GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB, - GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ, - GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, + GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR = 0, + GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, + GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB, + GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ, + GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU, }; const uint32_t GamescopeAppTextureColorspace_Count = 5; -const uint32_t GamescopeAppTextureColorspace_Bits = 3; +const uint32_t GamescopeAppTextureColorspace_Bits = 3; inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) { - return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB || - colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; + return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB || + colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; } enum GamescopeSelection { - GAMESCOPE_SELECTION_CLIPBOARD, - GAMESCOPE_SELECTION_PRIMARY, + GAMESCOPE_SELECTION_CLIPBOARD, + GAMESCOPE_SELECTION_PRIMARY, - GAMESCOPE_SELECTION_COUNT, + GAMESCOPE_SELECTION_COUNT, }; enum GamescopePanelOrientation { - GAMESCOPE_PANEL_ORIENTATION_0, // normal - GAMESCOPE_PANEL_ORIENTATION_270, // right - GAMESCOPE_PANEL_ORIENTATION_90, // left - GAMESCOPE_PANEL_ORIENTATION_180, // upside down + GAMESCOPE_PANEL_ORIENTATION_0, // normal + GAMESCOPE_PANEL_ORIENTATION_270, // right + GAMESCOPE_PANEL_ORIENTATION_90, // left + GAMESCOPE_PANEL_ORIENTATION_180, // upside down - GAMESCOPE_PANEL_ORIENTATION_AUTO, + GAMESCOPE_PANEL_ORIENTATION_AUTO, }; // Disable partial composition for now until we get diff --git a/src/gpuvis_trace_utils.h b/src/gpuvis_trace_utils.h index 60089adb1b..4cecfd7234 100644 --- a/src/gpuvis_trace_utils.h +++ b/src/gpuvis_trace_utils.h @@ -35,41 +35,42 @@ #include #if !defined( __linux__ ) -#define GPUVIS_TRACE_UTILS_DISABLE + #define GPUVIS_TRACE_UTILS_DISABLE #endif #if defined( __clang__ ) || defined( __GNUC__ ) -// printf-style warnings for user functions. -#define GPUVIS_ATTR_PRINTF( _x, _y ) __attribute__( ( __format__( __printf__, _x, _y ) ) ) -#define GPUVIS_MAY_BE_UNUSED __attribute__( ( unused ) ) -#define GPUVIS_CLEANUP_FUNC( x ) __attribute__( ( __cleanup__( x ) ) ) + // printf-style warnings for user functions. + #define GPUVIS_ATTR_PRINTF( _x, _y ) \ + __attribute__(( __format__( __printf__, _x, _y ) )) + #define GPUVIS_MAY_BE_UNUSED __attribute__(( unused )) + #define GPUVIS_CLEANUP_FUNC( x ) __attribute__(( __cleanup__( x ) )) #else -#define GPUVIS_ATTR_PRINTF( _x, _y ) -#define GPUVIS_MAY_BE_UNUSED -#define GPUVIS_CLEANUP_FUNC( x ) + #define GPUVIS_ATTR_PRINTF( _x, _y ) + #define GPUVIS_MAY_BE_UNUSED + #define GPUVIS_CLEANUP_FUNC( x ) #endif #if !defined( GPUVIS_TRACE_UTILS_DISABLE ) -#include -#include -#include - -#ifdef __cplusplus - #define GPUVIS_EXTERN extern "C" - #if __cplusplus>=201103L - #define THREAD_LOCAL thread_local - #else - #define THREAD_LOCAL __thread - #endif -#else - #define GPUVIS_EXTERN extern -#endif - -// From kernel/trace/trace.h -#ifndef TRACE_BUF_SIZE -#define TRACE_BUF_SIZE 1024 -#endif + #include + #include + #include + + #ifdef __cplusplus + #define GPUVIS_EXTERN extern "C" + #if __cplusplus >= 201103L + #define THREAD_LOCAL thread_local + #else + #define THREAD_LOCAL __thread + #endif + #else + #define GPUVIS_EXTERN extern + #endif + + // From kernel/trace/trace.h + #ifndef TRACE_BUF_SIZE + #define TRACE_BUF_SIZE 1024 + #endif // Try to open tracefs trace_marker file for writing. Returns -1 on error. GPUVIS_EXTERN int gpuvis_trace_init( void ); @@ -77,25 +78,40 @@ GPUVIS_EXTERN int gpuvis_trace_init( void ); GPUVIS_EXTERN void gpuvis_trace_shutdown( void ); // Write user event to tracefs trace_marker. -GPUVIS_EXTERN int gpuvis_trace_printf( const char *fmt, ... ) GPUVIS_ATTR_PRINTF( 1, 2 ); -GPUVIS_EXTERN int gpuvis_trace_vprintf( const char *fmt, va_list ap ) GPUVIS_ATTR_PRINTF( 1, 0 ); +GPUVIS_EXTERN int gpuvis_trace_printf( const char *fmt, ... ) + GPUVIS_ATTR_PRINTF( 1, 2 ); +GPUVIS_EXTERN int gpuvis_trace_vprintf( const char *fmt, va_list ap ) + GPUVIS_ATTR_PRINTF( 1, 0 ); // Write user event (with duration=XXms) to tracefs trace_marker. -GPUVIS_EXTERN int gpuvis_trace_duration_printf( float duration, const char *fmt, ... ) GPUVIS_ATTR_PRINTF( 2, 3 ); -GPUVIS_EXTERN int gpuvis_trace_duration_vprintf( float duration, const char *fmt, va_list ap ) GPUVIS_ATTR_PRINTF( 2, 0 ); +GPUVIS_EXTERN int +gpuvis_trace_duration_printf( float duration, const char *fmt, ... ) + GPUVIS_ATTR_PRINTF( 2, 3 ); +GPUVIS_EXTERN int +gpuvis_trace_duration_vprintf( float duration, const char *fmt, va_list ap ) + GPUVIS_ATTR_PRINTF( 2, 0 ); // Write user event (with begin_ctx=XX) to tracefs trace_marker. -GPUVIS_EXTERN int gpuvis_trace_begin_ctx_printf( unsigned int ctx, const char *fmt, ... ) GPUVIS_ATTR_PRINTF( 2, 3 ); -GPUVIS_EXTERN int gpuvis_trace_begin_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) GPUVIS_ATTR_PRINTF( 2, 0 ); +GPUVIS_EXTERN int +gpuvis_trace_begin_ctx_printf( unsigned int ctx, const char *fmt, ... ) + GPUVIS_ATTR_PRINTF( 2, 3 ); +GPUVIS_EXTERN int +gpuvis_trace_begin_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) + GPUVIS_ATTR_PRINTF( 2, 0 ); // Write user event (with end_ctx=XX) to tracefs trace_marker. -GPUVIS_EXTERN int gpuvis_trace_end_ctx_printf( unsigned int ctx, const char *fmt, ... ) GPUVIS_ATTR_PRINTF( 2, 3 ); -GPUVIS_EXTERN int gpuvis_trace_end_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) GPUVIS_ATTR_PRINTF( 2, 0 ); +GPUVIS_EXTERN int +gpuvis_trace_end_ctx_printf( unsigned int ctx, const char *fmt, ... ) + GPUVIS_ATTR_PRINTF( 2, 3 ); +GPUVIS_EXTERN int +gpuvis_trace_end_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) + GPUVIS_ATTR_PRINTF( 2, 0 ); // Execute "trace-cmd start -b 2000 -D -i -e sched:sched_switch -e ..." GPUVIS_EXTERN int gpuvis_start_tracing( unsigned int kbuffersize ); // Execute "trace-cmd extract" -GPUVIS_EXTERN int gpuvis_trigger_capture_and_keep_tracing( char *filename, size_t size ); +GPUVIS_EXTERN int +gpuvis_trigger_capture_and_keep_tracing( char *filename, size_t size ); // Execute "trace-cmd reset" GPUVIS_EXTERN int gpuvis_stop_tracing( void ); @@ -105,49 +121,50 @@ GPUVIS_EXTERN int gpuvis_tracing_on( void ); // Get tracefs directory. Ie: /sys/kernel/tracing. Returns "" on error. GPUVIS_EXTERN const char *gpuvis_get_tracefs_dir( void ); -// Get tracefs file path in buf. Ie: /sys/kernel/tracing/trace_marker. Returns NULL on error. -GPUVIS_EXTERN const char *gpuvis_get_tracefs_filename( char *buf, size_t buflen, const char *file ); +// Get tracefs file path in buf. Ie: /sys/kernel/tracing/trace_marker. Returns +// NULL on error. +GPUVIS_EXTERN const char * +gpuvis_get_tracefs_filename( char *buf, size_t buflen, const char *file ); // Internal function used by GPUVIS_COUNT_HOT_FUNC_CALLS macro GPUVIS_EXTERN void gpuvis_count_hot_func_calls_internal_( const char *func ); struct GpuvisTraceBlock; -static inline void gpuvis_trace_block_begin( struct GpuvisTraceBlock *block, const char *str ); +static inline void +gpuvis_trace_block_begin( struct GpuvisTraceBlock *block, const char *str ); static inline void gpuvis_trace_block_end( struct GpuvisTraceBlock *block ); struct GpuvisTraceBlockf; -static inline void gpuvis_trace_blockf_vbegin( struct GpuvisTraceBlockf *block, const char *fmt, va_list ap ); -static inline void gpuvis_trace_blockf_begin( struct GpuvisTraceBlockf *block, const char *fmt, ... ) GPUVIS_ATTR_PRINTF( 2, 3 ); +static inline void gpuvis_trace_blockf_vbegin( + struct GpuvisTraceBlockf *block, const char *fmt, va_list ap ); +static inline void gpuvis_trace_blockf_begin( + struct GpuvisTraceBlockf *block, const char *fmt, ... ) + GPUVIS_ATTR_PRINTF( 2, 3 ); static inline void gpuvis_trace_blockf_end( struct GpuvisTraceBlockf *block ); -#define LNAME3( _name, _line ) _name ## _line -#define LNAME2( _name, _line ) LNAME3( _name, _line ) -#define LNAME( _name ) LNAME2( _name, __LINE__ ) + #define LNAME3( _name, _line ) _name##_line + #define LNAME2( _name, _line ) LNAME3( _name, _line ) + #define LNAME( _name ) LNAME2( _name, __LINE__ ) struct GpuvisTraceBlock { - uint64_t m_t0; + uint64_t m_t0; const char *m_str; -#ifdef __cplusplus + #ifdef __cplusplus GpuvisTraceBlock( const char *str ) - { - gpuvis_trace_block_begin( this, str ); - } + { gpuvis_trace_block_begin( this, str ); } - ~GpuvisTraceBlock() - { - gpuvis_trace_block_end( this ); - } -#endif + ~GpuvisTraceBlock( ) { gpuvis_trace_block_end( this ); } + #endif }; struct GpuvisTraceBlockf { uint64_t m_t0; - char m_buf[ TRACE_BUF_SIZE ]; + char m_buf[ TRACE_BUF_SIZE ]; -#ifdef __cplusplus + #ifdef __cplusplus GpuvisTraceBlockf( const char *fmt, ... ) GPUVIS_ATTR_PRINTF( 2, 3 ) { va_list args; @@ -156,88 +173,89 @@ struct GpuvisTraceBlockf va_end( args ); } - ~GpuvisTraceBlockf() - { - gpuvis_trace_blockf_end( this ); - } -#endif + ~GpuvisTraceBlockf( ) { gpuvis_trace_blockf_end( this ); } + #endif }; -#ifdef __cplusplus + #ifdef __cplusplus -#define GPUVIS_TRACE_BLOCK( _conststr ) GpuvisTraceBlock LNAME( gpuvistimeblock )( _conststr ) -#define GPUVIS_TRACE_BLOCKF( _fmt, ... ) GpuvisTraceBlockf LNAME( gpuvistimeblock )( _fmt, __VA_ARGS__ ) + #define GPUVIS_TRACE_BLOCK( _conststr ) \ + GpuvisTraceBlock LNAME( gpuvistimeblock )( _conststr ) + #define GPUVIS_TRACE_BLOCKF( _fmt, ... ) \ + GpuvisTraceBlockf LNAME( gpuvistimeblock )( _fmt, __VA_ARGS__ ) -#else + #else -#if defined( __clang__ ) || defined( __GNUC__ ) + #if defined( __clang__ ) || defined( __GNUC__ ) -#define GPUVIS_TRACE_BLOCKF_INIT( _unique, _fmt, ... ) \ - ({ \ - struct GpuvisTraceBlockf _unique; \ - gpuvis_trace_blockf_begin( & _unique, _fmt, __VA_ARGS__ ); \ - _unique; \ - }) - -#define GPUVIS_TRACE_BLOCKF( _fmt, ...) \ - GPUVIS_CLEANUP_FUNC( gpuvis_trace_blockf_end ) GPUVIS_MAY_BE_UNUSED struct GpuvisTraceBlockf LNAME( gpuvistimeblock ) = \ - GPUVIS_TRACE_BLOCKF_INIT( LNAME( gpuvistimeblock_init ), _fmt, __VA_ARGS__ ) - -#define GPUVIS_TRACE_BLOCK( _conststr ) \ - GPUVIS_CLEANUP_FUNC( gpuvis_trace_block_end ) GPUVIS_MAY_BE_UNUSED struct GpuvisTraceBlock LNAME( gpuvistimeblock ) = \ - {\ - .m_t0 = gpuvis_gettime_u64(), \ - .m_str = _conststr \ - } + #define GPUVIS_TRACE_BLOCKF_INIT( _unique, _fmt, ... ) \ + ( { \ + struct GpuvisTraceBlockf _unique; \ + gpuvis_trace_blockf_begin( &_unique, _fmt, __VA_ARGS__ ); \ + _unique; \ + } ) -#else + #define GPUVIS_TRACE_BLOCKF( _fmt, ... ) \ + GPUVIS_CLEANUP_FUNC( gpuvis_trace_blockf_end ) \ + GPUVIS_MAY_BE_UNUSED struct GpuvisTraceBlockf LNAME( \ + gpuvistimeblock ) = \ + GPUVIS_TRACE_BLOCKF_INIT( \ + LNAME( gpuvistimeblock_init ), _fmt, __VA_ARGS__ ) + + #define GPUVIS_TRACE_BLOCK( _conststr ) \ + GPUVIS_CLEANUP_FUNC( gpuvis_trace_block_end ) \ + GPUVIS_MAY_BE_UNUSED struct GpuvisTraceBlock LNAME( \ + gpuvistimeblock ) = { .m_t0 = gpuvis_gettime_u64( ), \ + .m_str = _conststr } + + #else -#define GPUVIS_TRACE_BLOCKF( _fmt, ... ) -#define GPUVIS_TRACE_BLOCK( _conststr ) + #define GPUVIS_TRACE_BLOCKF( _fmt, ... ) + #define GPUVIS_TRACE_BLOCK( _conststr ) -#endif // __clang__ || __GNUC__ + #endif // __clang__ || __GNUC__ -#endif // __cplusplus + #endif // __cplusplus static inline uint64_t gpuvis_gettime_u64( void ) { struct timespec ts; clock_gettime( CLOCK_MONOTONIC, &ts ); - return ( ( uint64_t )ts.tv_sec * 1000000000LL) + ts.tv_nsec; + return ( ( uint64_t )ts.tv_sec * 1000000000LL ) + ts.tv_nsec; } static inline void gpuvis_trace_block_finalize( uint64_t m_t0, const char *str ) { - uint64_t dt = gpuvis_gettime_u64() - m_t0; + uint64_t dt = gpuvis_gettime_u64( ) - m_t0; // The cpu clock_gettime() functions seems to vary compared to the // ftrace event timestamps. If we don't reduce the duration here, // scopes oftentimes won't stack correctly when they're drawn. - if ( dt > 11000 ) - dt -= 11000; + if ( dt > 11000 ) dt -= 11000; gpuvis_trace_printf( "%s (lduration=-%lu)", str, dt ); } -static inline void gpuvis_trace_block_begin( struct GpuvisTraceBlock* block, const char *str ) +static inline void +gpuvis_trace_block_begin( struct GpuvisTraceBlock *block, const char *str ) { block->m_str = str; - block->m_t0 = gpuvis_gettime_u64(); + block->m_t0 = gpuvis_gettime_u64( ); } static inline void gpuvis_trace_block_end( struct GpuvisTraceBlock *block ) -{ - gpuvis_trace_block_finalize(block->m_t0, block->m_str); -} +{ gpuvis_trace_block_finalize( block->m_t0, block->m_str ); } -static inline void gpuvis_trace_blockf_vbegin( struct GpuvisTraceBlockf *block, const char *fmt, va_list ap) +static inline void gpuvis_trace_blockf_vbegin( + struct GpuvisTraceBlockf *block, const char *fmt, va_list ap ) { - vsnprintf(block->m_buf, sizeof(block->m_buf), fmt, ap); - block->m_t0 = gpuvis_gettime_u64(); + vsnprintf( block->m_buf, sizeof( block->m_buf ), fmt, ap ); + block->m_t0 = gpuvis_gettime_u64( ); } -static inline void gpuvis_trace_blockf_begin( struct GpuvisTraceBlockf *block, const char *fmt, ... ) +static inline void gpuvis_trace_blockf_begin( + struct GpuvisTraceBlockf *block, const char *fmt, ... ) { va_list args; @@ -247,106 +265,127 @@ static inline void gpuvis_trace_blockf_begin( struct GpuvisTraceBlockf *block, c } static inline void gpuvis_trace_blockf_end( struct GpuvisTraceBlockf *block ) -{ - gpuvis_trace_block_finalize( block->m_t0, block->m_buf ); -} +{ gpuvis_trace_block_finalize( block->m_t0, block->m_buf ); } -#define GPUVIS_COUNT_HOT_FUNC_CALLS() gpuvis_count_hot_func_calls_internal_( __func__ ); + #define GPUVIS_COUNT_HOT_FUNC_CALLS( ) \ + gpuvis_count_hot_func_calls_internal_( __func__ ); #else -static inline int gpuvis_trace_init() { return -1; } -static inline void gpuvis_trace_shutdown() {} +static inline int gpuvis_trace_init( ) { return -1; } +static inline void gpuvis_trace_shutdown( ) {} static inline int gpuvis_trace_printf( const char *fmt, ... ) { return 0; } -static inline int gpuvis_trace_vprintf( const char *fmt, va_list ap ) { return 0; } - -static inline int gpuvis_trace_duration_printf( float duration, const char *fmt, ... ) { return 0; } -static inline int gpuvis_trace_duration_vprintf( float duration, const char *fmt, va_list ap ) { return 0; } - -static inline int gpuvis_trace_begin_ctx_printf( unsigned int ctx, const char *fmt, ... ) { return 0; } -static inline int gpuvis_trace_begin_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) { return 0; } - -static inline int gpuvis_trace_end_ctx_printf( unsigned int ctx, const char *fmt, ... ) { return 0; } -static inline int gpuvis_trace_end_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) { return 0; } +static inline int gpuvis_trace_vprintf( const char *fmt, va_list ap ) +{ return 0; } + +static inline int +gpuvis_trace_duration_printf( float duration, const char *fmt, ... ) +{ return 0; } +static inline int +gpuvis_trace_duration_vprintf( float duration, const char *fmt, va_list ap ) +{ return 0; } + +static inline int +gpuvis_trace_begin_ctx_printf( unsigned int ctx, const char *fmt, ... ) +{ return 0; } +static inline int +gpuvis_trace_begin_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) +{ return 0; } + +static inline int +gpuvis_trace_end_ctx_printf( unsigned int ctx, const char *fmt, ... ) +{ return 0; } +static inline int +gpuvis_trace_end_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) +{ return 0; } static inline int gpuvis_start_tracing( unsigned int kbuffersize ) { return 0; } -static inline int gpuvis_trigger_capture_and_keep_tracing( char *filename, size_t size ) { return 0; } -static inline int gpuvis_stop_tracing() { return 0; } +static inline int +gpuvis_trigger_capture_and_keep_tracing( char *filename, size_t size ) +{ return 0; } +static inline int gpuvis_stop_tracing( ) { return 0; } -static inline int gpuvis_tracing_on() { return -1; } +static inline int gpuvis_tracing_on( ) { return -1; } -static inline const char *gpuvis_get_tracefs_dir() { return ""; } -static inline const char *gpuvis_get_tracefs_filename( char *buf, size_t buflen, const char *file ) { return NULL; } +static inline const char *gpuvis_get_tracefs_dir( ) { return ""; } +static inline const char * +gpuvis_get_tracefs_filename( char *buf, size_t buflen, const char *file ) +{ return NULL; } struct GpuvisTraceBlock; -static inline void gpuvis_trace_block_begin( struct GpuvisTraceBlock *block, const char *str ) {} +static inline void +gpuvis_trace_block_begin( struct GpuvisTraceBlock *block, const char *str ) +{} static inline void gpuvis_trace_block_end( struct GpuvisTraceBlock *block ) {} struct GpuvisTraceBlockf; -static inline void gpuvis_trace_blockf_vbegin( struct GpuvisTraceBlockf *block, const char *fmt, va_list ap ) {} -static inline void gpuvis_trace_blockf_begin( struct GpuvisTraceBlockf *block, const char *fmt, ... ) {} +static inline void gpuvis_trace_blockf_vbegin( + struct GpuvisTraceBlockf *block, const char *fmt, va_list ap ) +{} +static inline void gpuvis_trace_blockf_begin( + struct GpuvisTraceBlockf *block, const char *fmt, ... ) +{} static inline void gpuvis_trace_blockf_end( struct GpuvisTraceBlockf *block ) {} -#define GPUVIS_TRACE_BLOCK( _conststr ) -#define GPUVIS_TRACE_BLOCKF( _fmt, ... ) + #define GPUVIS_TRACE_BLOCK( _conststr ) + #define GPUVIS_TRACE_BLOCKF( _fmt, ... ) -#define GPUVIS_COUNT_HOT_FUNC_CALLS() + #define GPUVIS_COUNT_HOT_FUNC_CALLS( ) #endif // !GPUVIS_TRACE_UTILS_DISABLE -#if defined( GPUVIS_TRACE_IMPLEMENTATION ) && !defined( GPUVIS_TRACE_UTILS_DISABLE ) - -////////////////////////////////////////////////////////////////////////////// -// -// IMPLEMENTATION SECTION -// - -#define _GNU_SOURCE 1 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#undef GPUVIS_EXTERN -#ifdef __cplusplus -#define GPUVIS_EXTERN extern "C" -#else -#define GPUVIS_EXTERN -#endif - -#ifndef TRACEFS_MAGIC -#define TRACEFS_MAGIC 0x74726163 -#endif - -#define GPUVIS_STR( x ) #x -#define GPUVIS_STR_VALUE( x ) GPUVIS_STR( x ) - -static int g_trace_fd = -2; -static int g_tracefs_dir_inited = 0; +#if defined( GPUVIS_TRACE_IMPLEMENTATION ) && \ + !defined( GPUVIS_TRACE_UTILS_DISABLE ) + + ////////////////////////////////////////////////////////////////////////////// + // + // IMPLEMENTATION SECTION + // + + #define _GNU_SOURCE 1 + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #undef GPUVIS_EXTERN + #ifdef __cplusplus + #define GPUVIS_EXTERN extern "C" + #else + #define GPUVIS_EXTERN + #endif + + #ifndef TRACEFS_MAGIC + #define TRACEFS_MAGIC 0x74726163 + #endif + + #define GPUVIS_STR( x ) #x + #define GPUVIS_STR_VALUE( x ) GPUVIS_STR( x ) + +static int g_trace_fd = -2; +static int g_tracefs_dir_inited = 0; static char g_tracefs_dir[ PATH_MAX ]; -#ifdef __cplusplus -#include + #ifdef __cplusplus + #include struct funcinfo_t { uint64_t tfirst = 0; - uint64_t tlast = 0; - uint32_t count = 0; + uint64_t tlast = 0; + uint32_t count = 0; }; -static std::unordered_map< pid_t, std::unordered_map< const char *, funcinfo_t > > g_hotfuncs; -#endif // __cplusplus +static std::unordered_map> + g_hotfuncs; + #endif // __cplusplus -static pid_t gpuvis_gettid() -{ - return ( pid_t )syscall( SYS_gettid ); -} +static pid_t gpuvis_gettid( ) { return ( pid_t )syscall( SYS_gettid ); } static int exec_tracecmd( const char *cmd ) { @@ -385,14 +424,16 @@ static int exec_tracecmd( const char *cmd ) return ret; } -GPUVIS_EXTERN int gpuvis_trace_init() +GPUVIS_EXTERN int gpuvis_trace_init( ) { if ( g_trace_fd == -2 ) { char filename[ PATH_MAX ]; - // The "trace_marker" file allows userspace to write into the ftrace buffer. - if ( !gpuvis_get_tracefs_filename( filename, sizeof( filename ), "trace_marker" ) ) + // The "trace_marker" file allows userspace to write into the ftrace + // buffer. + if ( !gpuvis_get_tracefs_filename( + filename, sizeof( filename ), "trace_marker" ) ) g_trace_fd = -1; else g_trace_fd = open( filename, O_WRONLY ); @@ -401,18 +442,17 @@ GPUVIS_EXTERN int gpuvis_trace_init() return g_trace_fd; } -#if !defined( __cplusplus ) -static void flush_hot_func_calls() + #if !defined( __cplusplus ) +static void flush_hot_func_calls( ) { //$ TODO: hot func calls for C } -#else -static void flush_hot_func_calls() + #else +static void flush_hot_func_calls( ) { - if ( g_hotfuncs.empty() ) - return; + if ( g_hotfuncs.empty( ) ) return; - uint64_t t0 = gpuvis_gettime_u64(); + uint64_t t0 = gpuvis_gettime_u64( ); for ( auto &x : g_hotfuncs ) { @@ -420,42 +460,51 @@ static void flush_hot_func_calls() { if ( y.second.count ) { - pid_t tid = x.first; - const char *func = y.first; - uint64_t offset = t0 - y.second.tfirst; - uint64_t duration = y.second.tlast - y.second.tfirst; - - gpuvis_trace_printf( "%s calls:%u (lduration=%lu tid=%d offset=-%lu)\n", - func, y.second.count, duration, tid, offset ); + pid_t tid = x.first; + const char *func = y.first; + uint64_t offset = t0 - y.second.tfirst; + uint64_t duration = y.second.tlast - y.second.tfirst; + + gpuvis_trace_printf( + "%s calls:%u (lduration=%lu tid=%d offset=-%lu)\n", + func, + y.second.count, + duration, + tid, + offset ); } } } - g_hotfuncs.clear(); + g_hotfuncs.clear( ); } GPUVIS_EXTERN void gpuvis_count_hot_func_calls_internal_( const char *func ) { - static THREAD_LOCAL pid_t s_tid = gpuvis_gettid(); + static THREAD_LOCAL pid_t s_tid = gpuvis_gettid( ); - uint64_t t0 = gpuvis_gettime_u64(); - auto &x = g_hotfuncs[ s_tid ]; - auto &y = x[ func ]; + uint64_t t0 = gpuvis_gettime_u64( ); + auto &x = g_hotfuncs[ s_tid ]; + auto &y = x[ func ]; if ( !y.count ) { - y.count = 1; + y.count = 1; y.tfirst = t0; - y.tlast = t0 + 1; + y.tlast = t0 + 1; } else if ( t0 - y.tlast >= 3 * 1000000 ) // 3ms { - gpuvis_trace_printf( "%s calls:%u (lduration=%lu offset=-%lu)\n", - func, y.count, y.tlast - y.tfirst, t0 - y.tfirst ); - - y.count = 1; + gpuvis_trace_printf( + "%s calls:%u (lduration=%lu offset=-%lu)\n", + func, + y.count, + y.tlast - y.tfirst, + t0 - y.tfirst ); + + y.count = 1; y.tfirst = t0; - y.tlast = t0 + 1; + y.tlast = t0 + 1; } else { @@ -463,36 +512,35 @@ GPUVIS_EXTERN void gpuvis_count_hot_func_calls_internal_( const char *func ) y.count++; } } -#endif // __cplusplus + #endif // __cplusplus -GPUVIS_EXTERN void gpuvis_trace_shutdown() +GPUVIS_EXTERN void gpuvis_trace_shutdown( ) { - flush_hot_func_calls(); + flush_hot_func_calls( ); - if ( g_trace_fd >= 0 ) - close( g_trace_fd ); + if ( g_trace_fd >= 0 ) close( g_trace_fd ); g_trace_fd = -2; g_tracefs_dir_inited = 0; - g_tracefs_dir[ 0 ] = 0; + g_tracefs_dir[ 0 ] = 0; } -static int trace_printf_impl( const char *keystr, const char *fmt, va_list ap ) GPUVIS_ATTR_PRINTF( 2, 0 ); +static int trace_printf_impl( const char *keystr, const char *fmt, va_list ap ) + GPUVIS_ATTR_PRINTF( 2, 0 ); static int trace_printf_impl( const char *keystr, const char *fmt, va_list ap ) { int ret = -1; - if ( gpuvis_trace_init() >= 0 ) + if ( gpuvis_trace_init( ) >= 0 ) { - int n; + int n; char buf[ TRACE_BUF_SIZE ]; n = vsnprintf( buf, sizeof( buf ), fmt, ap ); if ( ( n > 0 ) || ( !n && keystr ) ) { - if ( ( size_t )n >= sizeof( buf ) ) - n = sizeof( buf ) - 1; + if ( ( size_t )n >= sizeof( buf ) ) n = sizeof( buf ) - 1; if ( keystr && keystr[ 0 ] ) { @@ -515,7 +563,7 @@ static int trace_printf_impl( const char *keystr, const char *fmt, va_list ap ) GPUVIS_EXTERN int gpuvis_trace_printf( const char *fmt, ... ) { - int ret; + int ret; va_list ap; va_start( ap, fmt ); @@ -526,13 +574,12 @@ GPUVIS_EXTERN int gpuvis_trace_printf( const char *fmt, ... ) } GPUVIS_EXTERN int gpuvis_trace_vprintf( const char *fmt, va_list ap ) -{ - return trace_printf_impl( NULL, fmt, ap ); -} +{ return trace_printf_impl( NULL, fmt, ap ); } -GPUVIS_EXTERN int gpuvis_trace_duration_printf( float duration, const char *fmt, ... ) +GPUVIS_EXTERN int +gpuvis_trace_duration_printf( float duration, const char *fmt, ... ) { - int ret; + int ret; va_list ap; va_start( ap, fmt ); @@ -542,18 +589,24 @@ GPUVIS_EXTERN int gpuvis_trace_duration_printf( float duration, const char *fmt, return ret; } -GPUVIS_EXTERN int gpuvis_trace_duration_vprintf( float duration, const char *fmt, va_list ap ) +GPUVIS_EXTERN int +gpuvis_trace_duration_vprintf( float duration, const char *fmt, va_list ap ) { char keystr[ 128 ]; - snprintf( keystr, sizeof( keystr ), " (duration=%f)", duration ); //$ TODO: Try this with more precision? + snprintf( + keystr, + sizeof( keystr ), + " (duration=%f)", + duration ); //$ TODO: Try this with more precision? return trace_printf_impl( keystr, fmt, ap ); } -GPUVIS_EXTERN int gpuvis_trace_begin_ctx_printf( unsigned int ctx, const char *fmt, ... ) +GPUVIS_EXTERN int +gpuvis_trace_begin_ctx_printf( unsigned int ctx, const char *fmt, ... ) { - int ret; + int ret; va_list ap; va_start( ap, fmt ); @@ -563,7 +616,8 @@ GPUVIS_EXTERN int gpuvis_trace_begin_ctx_printf( unsigned int ctx, const char *f return ret; } -GPUVIS_EXTERN int gpuvis_trace_begin_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) +GPUVIS_EXTERN int +gpuvis_trace_begin_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) { char keystr[ 128 ]; @@ -572,9 +626,10 @@ GPUVIS_EXTERN int gpuvis_trace_begin_ctx_vprintf( unsigned int ctx, const char * return trace_printf_impl( keystr, fmt, ap ); } -GPUVIS_EXTERN int gpuvis_trace_end_ctx_printf( unsigned int ctx, const char *fmt, ... ) +GPUVIS_EXTERN int +gpuvis_trace_end_ctx_printf( unsigned int ctx, const char *fmt, ... ) { - int ret; + int ret; va_list ap; va_start( ap, fmt ); @@ -584,7 +639,8 @@ GPUVIS_EXTERN int gpuvis_trace_end_ctx_printf( unsigned int ctx, const char *fmt return ret; } -GPUVIS_EXTERN int gpuvis_trace_end_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) +GPUVIS_EXTERN int +gpuvis_trace_end_ctx_vprintf( unsigned int ctx, const char *fmt, va_list ap ) { char keystr[ 128 ]; @@ -615,58 +671,63 @@ GPUVIS_EXTERN int gpuvis_start_tracing( unsigned int kbuffersize ) " -e i915:i915_flip_complete" " -e i915:intel_gpu_freq_change" " -e i915:i915_gem_request_add" - " -e i915:i915_gem_request_submit" // Require CONFIG_DRM_I915_LOW_LEVEL_TRACEPOINTS - " -e i915:i915_gem_request_in" // Kconfig option to be enabled. - " -e i915:i915_gem_request_out" // + " -e i915:i915_gem_request_submit" // Require + // CONFIG_DRM_I915_LOW_LEVEL_TRACEPOINTS + " -e i915:i915_gem_request_in" // Kconfig option to be enabled. + " -e i915:i915_gem_request_out" // " -e i915:intel_engine_notify" " -e i915:i915_gem_request_wait_begin" " -e i915:i915_gem_request_wait_end 2>&1"; char cmd[ 8192 ]; - if ( !kbuffersize ) - kbuffersize = 16 * 1024; + if ( !kbuffersize ) kbuffersize = 16 * 1024; snprintf( cmd, sizeof( cmd ), fmt, kbuffersize ); return exec_tracecmd( cmd ); } -GPUVIS_EXTERN int gpuvis_trigger_capture_and_keep_tracing( char *filename, size_t size ) +GPUVIS_EXTERN int +gpuvis_trigger_capture_and_keep_tracing( char *filename, size_t size ) { int ret = -1; - if ( filename ) - filename[ 0 ] = 0; + if ( filename ) filename[ 0 ] = 0; - flush_hot_func_calls(); + flush_hot_func_calls( ); - if ( gpuvis_tracing_on() ) + if ( gpuvis_tracing_on( ) ) { - char datetime[ 128 ]; - char cmd[ PATH_MAX ]; - char exebuf[ PATH_MAX ]; + char datetime[ 128 ]; + char cmd[ PATH_MAX ]; + char exebuf[ PATH_MAX ]; const char *exename = NULL; - time_t t = time( NULL ); - struct tm *tmp = localtime( &t ); + time_t t = time( NULL ); + struct tm *tmp = localtime( &t ); strftime( datetime, sizeof( datetime ), "%Y-%m-%d_%H-%M-%S", tmp ); datetime[ sizeof( datetime ) - 1 ] = 0; - ssize_t cbytes = readlink( "/proc/self/exe", exebuf, sizeof( exebuf ) - 1 ); + ssize_t cbytes = + readlink( "/proc/self/exe", exebuf, sizeof( exebuf ) - 1 ); if ( cbytes > 0 ) { exebuf[ cbytes ] = 0; - exename = strrchr( exebuf, '/' ); + exename = strrchr( exebuf, '/' ); } exename = exename ? ( exename + 1 ) : "trace"; // Stop tracing exec_tracecmd( "trace-cmd stop 2>&1" ); - // Save the trace data to something like "glxgears_2017-10-13_17-52-56.dat" - snprintf( cmd, sizeof( cmd ), - "trace-cmd extract -k -o \"%s_%s.dat\" > /tmp/blah.log 2>&1 &", - exename, datetime ); + // Save the trace data to something like + // "glxgears_2017-10-13_17-52-56.dat" + snprintf( + cmd, + sizeof( cmd ), + "trace-cmd extract -k -o \"%s_%s.dat\" > /tmp/blah.log 2>&1 &", + exename, + datetime ); cmd[ sizeof( cmd ) - 1 ] = 0; ret = system( cmd ); @@ -681,11 +742,11 @@ GPUVIS_EXTERN int gpuvis_trigger_capture_and_keep_tracing( char *filename, size_ return ret; } -GPUVIS_EXTERN int gpuvis_stop_tracing() +GPUVIS_EXTERN int gpuvis_stop_tracing( ) { - flush_hot_func_calls(); + flush_hot_func_calls( ); - int ret = exec_tracecmd( "trace-cmd reset 2>&1"); + int ret = exec_tracecmd( "trace-cmd reset 2>&1" ); // Try freeing any snapshot buffers as well exec_tracecmd( "trace-cmd snapshot -f 2>&1" ); @@ -693,9 +754,9 @@ GPUVIS_EXTERN int gpuvis_stop_tracing() return ret; } -GPUVIS_EXTERN int gpuvis_tracing_on() +GPUVIS_EXTERN int gpuvis_tracing_on( ) { - int ret = -1; + int ret = -1; char buf[ 32 ]; char filename[ PATH_MAX ]; @@ -705,8 +766,7 @@ GPUVIS_EXTERN int gpuvis_tracing_on() if ( fd >= 0 ) { - if ( read( fd, buf, sizeof( buf ) ) > 0 ) - ret = atoi( buf ); + if ( read( fd, buf, sizeof( buf ) ) > 0 ) ret = atoi( buf ); close( fd ); } @@ -722,20 +782,20 @@ static int is_tracefs_dir( const char *dir ) return !statfs( dir, &stat ) && ( stat.f_type == TRACEFS_MAGIC ); } -GPUVIS_EXTERN const char *gpuvis_get_tracefs_dir() +GPUVIS_EXTERN const char *gpuvis_get_tracefs_dir( ) { if ( !g_tracefs_dir_inited ) { - size_t i; - static const char *tracefs_dirs[] = - { + size_t i; + static const char *tracefs_dirs[] = { "/sys/kernel/tracing", "/sys/kernel/debug/tracing", "/tracing", "/trace", }; - for ( i = 0; i < sizeof( tracefs_dirs ) / sizeof( tracefs_dirs[ 0 ] ); i++ ) + for ( i = 0; i < sizeof( tracefs_dirs ) / sizeof( tracefs_dirs[ 0 ] ); + i++ ) { if ( is_tracefs_dir( tracefs_dirs[ i ] ) ) { @@ -748,13 +808,18 @@ GPUVIS_EXTERN const char *gpuvis_get_tracefs_dir() if ( !g_tracefs_dir[ 0 ] ) { FILE *fp; - char type[ 128 ]; - char dir[ PATH_MAX + 1 ]; + char type[ 128 ]; + char dir[ PATH_MAX + 1 ]; fp = fopen( "/proc/mounts", "r" ); if ( fp ) { - while ( fscanf( fp, "%*s %" GPUVIS_STR_VALUE( PATH_MAX ) "s %127s %*s %*d %*d\n", dir, type ) == 2 ) + while ( fscanf( + fp, + "%*s %" GPUVIS_STR_VALUE( + PATH_MAX ) "s %127s %*s %*d %*d\n", + dir, + type ) == 2 ) { if ( !strcmp( type, "tracefs" ) && is_tracefs_dir( dir ) ) { @@ -774,23 +839,26 @@ GPUVIS_EXTERN const char *gpuvis_get_tracefs_dir() return g_tracefs_dir; } -GPUVIS_EXTERN const char *gpuvis_get_tracefs_filename( char *buf, size_t buflen, const char *file ) +GPUVIS_EXTERN const char * +gpuvis_get_tracefs_filename( char *buf, size_t buflen, const char *file ) { - const char *tracefs_dir = gpuvis_get_tracefs_dir(); + const char *tracefs_dir = gpuvis_get_tracefs_dir( ); if ( tracefs_dir[ 0 ] ) { -// truncation is ok here -#if defined( __GNUC__ ) && !defined(__llvm__) && !defined(__INTEL_COMPILER) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-truncation" -#endif + // truncation is ok here + #if defined( __GNUC__ ) && !defined( __llvm__ ) && \ + !defined( __INTEL_COMPILER ) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wformat-truncation" + #endif snprintf( buf, buflen, "%s/%s", tracefs_dir, file ); -#if defined( __GNUC__ ) && !defined(__llvm__) && !defined(__INTEL_COMPILER) -#pragma GCC diagnostic pop -#endif + #if defined( __GNUC__ ) && !defined( __llvm__ ) && \ + !defined( __INTEL_COMPILER ) + #pragma GCC diagnostic pop + #endif buf[ buflen - 1 ] = 0; diff --git a/src/hdmi.h b/src/hdmi.h index 92d1495196..1119fc54fa 100644 --- a/src/hdmi.h +++ b/src/hdmi.h @@ -9,7 +9,7 @@ #define HDMI_EOTF_HLG 3 #if HAVE_DRM -#include + #include #else /** * struct hdr_metadata_infoframe - HDR Metadata Infoframe Data. @@ -20,63 +20,66 @@ * Userspace is expected to pass the metadata information as per * the format described in this structure. */ -struct hdr_metadata_infoframe { - /** - * @eotf: Electro-Optical Transfer Function (EOTF) - * used in the stream. - */ - __u8 eotf; - /** - * @metadata_type: Static_Metadata_Descriptor_ID. - */ - __u8 metadata_type; - /** - * @display_primaries: Color Primaries of the Data. - * These are coded as unsigned 16-bit values in units of - * 0.00002, where 0x0000 represents zero and 0xC350 - * represents 1.0000. - * @display_primaries.x: X cordinate of color primary. - * @display_primaries.y: Y cordinate of color primary. - */ - struct { - uint16_t x, y; - } display_primaries[3]; - /** - * @white_point: White Point of Colorspace Data. - * These are coded as unsigned 16-bit values in units of - * 0.00002, where 0x0000 represents zero and 0xC350 - * represents 1.0000. - * @white_point.x: X cordinate of whitepoint of color primary. - * @white_point.y: Y cordinate of whitepoint of color primary. - */ - struct { - uint16_t x, y; - } white_point; - /** - * @max_display_mastering_luminance: Max Mastering Display Luminance. - * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - */ - uint16_t max_display_mastering_luminance; - /** - * @min_display_mastering_luminance: Min Mastering Display Luminance. - * This value is coded as an unsigned 16-bit value in units of - * 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF - * represents 6.5535 cd/m2. - */ - uint16_t min_display_mastering_luminance; - /** - * @max_cll: Max Content Light Level. - * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - */ - uint16_t max_cll; - /** - * @max_fall: Max Frame Average Light Level. - * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - */ - uint16_t max_fall; +struct hdr_metadata_infoframe +{ + /** + * @eotf: Electro-Optical Transfer Function (EOTF) + * used in the stream. + */ + __u8 eotf; + /** + * @metadata_type: Static_Metadata_Descriptor_ID. + */ + __u8 metadata_type; + /** + * @display_primaries: Color Primaries of the Data. + * These are coded as unsigned 16-bit values in units of + * 0.00002, where 0x0000 represents zero and 0xC350 + * represents 1.0000. + * @display_primaries.x: X cordinate of color primary. + * @display_primaries.y: Y cordinate of color primary. + */ + struct + { + uint16_t x, y; + } display_primaries[ 3 ]; + /** + * @white_point: White Point of Colorspace Data. + * These are coded as unsigned 16-bit values in units of + * 0.00002, where 0x0000 represents zero and 0xC350 + * represents 1.0000. + * @white_point.x: X cordinate of whitepoint of color primary. + * @white_point.y: Y cordinate of whitepoint of color primary. + */ + struct + { + uint16_t x, y; + } white_point; + /** + * @max_display_mastering_luminance: Max Mastering Display Luminance. + * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, + * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. + */ + uint16_t max_display_mastering_luminance; + /** + * @min_display_mastering_luminance: Min Mastering Display Luminance. + * This value is coded as an unsigned 16-bit value in units of + * 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF + * represents 6.5535 cd/m2. + */ + uint16_t min_display_mastering_luminance; + /** + * @max_cll: Max Content Light Level. + * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, + * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. + */ + uint16_t max_cll; + /** + * @max_fall: Max Frame Average Light Level. + * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, + * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. + */ + uint16_t max_fall; }; /** @@ -84,17 +87,19 @@ struct hdr_metadata_infoframe { * * Metadata Information to be passed from userspace */ -struct hdr_output_metadata { - /** - * @metadata_type: Static_Metadata_Descriptor_ID. - */ - uint32_t metadata_type; - /** - * @hdmi_metadata_type1: HDR Metadata Infoframe. - */ - union { - struct hdr_metadata_infoframe hdmi_metadata_type1; - }; +struct hdr_output_metadata +{ + /** + * @metadata_type: Static_Metadata_Descriptor_ID. + */ + uint32_t metadata_type; + /** + * @hdmi_metadata_type1: HDR Metadata Infoframe. + */ + union + { + struct hdr_metadata_infoframe hdmi_metadata_type1; + }; }; #endif diff --git a/src/ime.cpp b/src/ime.cpp index 80c3307717..e043013dc9 100644 --- a/src/ime.cpp +++ b/src/ime.cpp @@ -1,10 +1,10 @@ #include "ime.hpp" -#include "wlserver.hpp" #include "log.hpp" +#include "wlserver.hpp" #include -#include #include +#include #include #include @@ -12,10 +12,10 @@ #include -#include "wlr_begin.hpp" #include #include #include +#include "wlr_begin.hpp" #include "wlr_end.hpp" struct wlserver_input_method_manager *global_manager = nullptr; @@ -25,42 +25,40 @@ struct wlserver_input_method_manager *global_manager = nullptr; static const uint32_t UTF8_INVALID = 0xFFFD; -static size_t utf8_size(const char *str) +static size_t utf8_size( const char *str ) { - uint8_t u8 = (uint8_t)str[0]; - if (u8 == 0) { - return 0; - } else if ((u8 & 0x80) == 0) { - return 1; - } else if ((u8 & 0xE0) == 0xC0) { - return 2; - } else if ((u8 & 0xF0) == 0xE0) { - return 3; - } else if ((u8 & 0xF8) == 0xF0) { - return 4; - } else { - return 0; - } + uint8_t u8 = ( uint8_t )str[ 0 ]; + if ( u8 == 0 ) { return 0; } + else if ( ( u8 & 0x80 ) == 0 ) { return 1; } + else if ( ( u8 & 0xE0 ) == 0xC0 ) { return 2; } + else if ( ( u8 & 0xF0 ) == 0xE0 ) { return 3; } + else if ( ( u8 & 0xF8 ) == 0xF0 ) { return 4; } + else + { + return 0; + } } -static uint32_t utf8_decode(const char **str_ptr) +static uint32_t utf8_decode( const char **str_ptr ) { - const char *str = *str_ptr; - size_t size = utf8_size(str); - if (size == 0) { - *str_ptr = &str[1]; - return UTF8_INVALID; - } - - *str_ptr = &str[size]; - - const uint32_t masks[] = { 0x7F, 0x1F, 0x0F, 0x07 }; - uint32_t ret = (uint32_t)str[0] & masks[size - 1]; - for (size_t i = 1; i < size; i++) { - ret <<= 6; - ret |= str[i] & 0x3F; - } - return ret; + const char *str = *str_ptr; + size_t size = utf8_size( str ); + if ( size == 0 ) + { + *str_ptr = &str[ 1 ]; + return UTF8_INVALID; + } + + *str_ptr = &str[ size ]; + + const uint32_t masks[] = { 0x7F, 0x1F, 0x0F, 0x07 }; + uint32_t ret = ( uint32_t )str[ 0 ] & masks[ size - 1 ]; + for ( size_t i = 1; i < size; i++ ) + { + ret <<= 6; + ret |= str[ i ] & 0x3F; + } + return ret; } #define IME_MANAGER_VERSION 3 @@ -68,579 +66,707 @@ static uint32_t utf8_decode(const char **str_ptr) /* Some clients assume keycodes are coming from evdev and interpret them. Only * use keys that would normally produce characters for our emulated events. */ static const uint32_t allow_keycodes[] = { - KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL, - KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, - KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_GRAVE, KEY_BACKSLASH, - KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_DOT, KEY_SLASH, + KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, + KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, + KEY_MINUS, KEY_EQUAL, KEY_Q, KEY_W, KEY_E, + KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, + KEY_O, KEY_P, KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_A, + KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, + KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, + KEY_GRAVE, KEY_BACKSLASH, KEY_Z, KEY_X, KEY_C, + KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, + KEY_DOT, KEY_SLASH, }; -static const size_t allow_keycodes_len = sizeof(allow_keycodes) / sizeof(allow_keycodes[0]); - -struct wlserver_input_method_key { - uint32_t keycode; - xkb_keysym_t keysym; -}; +static const size_t allow_keycodes_len = + sizeof( allow_keycodes ) / sizeof( allow_keycodes[ 0 ] ); -static std::unordered_map actions = { - { GAMESCOPE_INPUT_METHOD_ACTION_SUBMIT, { KEY_ENTER, XKB_KEY_Return } }, - { GAMESCOPE_INPUT_METHOD_ACTION_DELETE_LEFT, { KEY_BACKSPACE, XKB_KEY_BackSpace } }, - { GAMESCOPE_INPUT_METHOD_ACTION_DELETE_RIGHT, { KEY_DELETE, XKB_KEY_Delete } }, - { GAMESCOPE_INPUT_METHOD_ACTION_MOVE_LEFT, { KEY_LEFT, XKB_KEY_Left } }, - { GAMESCOPE_INPUT_METHOD_ACTION_MOVE_RIGHT, { KEY_RIGHT, XKB_KEY_Right } }, - { GAMESCOPE_INPUT_METHOD_ACTION_MOVE_UP, { KEY_UP, XKB_KEY_Up } }, - { GAMESCOPE_INPUT_METHOD_ACTION_MOVE_DOWN, { KEY_DOWN, XKB_KEY_Down } }, +struct wlserver_input_method_key +{ + uint32_t keycode; + xkb_keysym_t keysym; }; -struct wlserver_input_method { - struct wl_resource *resource; - struct wlserver_input_method_manager *manager; - uint32_t serial; - - struct { - char *string; - enum gamescope_input_method_action action; - } pending; - - // Used to send emulated input events - struct wlr_keyboard keyboard; - std::deque keys; - uint32_t next_keycode_index; - int32_t held_keycode; - xkb_mod_mask_t held_modifier_mask; - struct wlr_keyboard_modifiers prev_mods; - - struct wl_event_source *ime_reset_ime_keyboard_event_source; - struct wl_event_source *ime_release_ime_keypress_event_source; - - uint32_t uFakeTimestamp = 0; +static std::unordered_map< + enum gamescope_input_method_action, + struct wlserver_input_method_key> + actions = { + { GAMESCOPE_INPUT_METHOD_ACTION_SUBMIT, { KEY_ENTER, XKB_KEY_Return } }, + { GAMESCOPE_INPUT_METHOD_ACTION_DELETE_LEFT, + { KEY_BACKSPACE, XKB_KEY_BackSpace } }, + { GAMESCOPE_INPUT_METHOD_ACTION_DELETE_RIGHT, + { KEY_DELETE, XKB_KEY_Delete } }, + { GAMESCOPE_INPUT_METHOD_ACTION_MOVE_LEFT, { KEY_LEFT, XKB_KEY_Left } }, + { GAMESCOPE_INPUT_METHOD_ACTION_MOVE_RIGHT, + { KEY_RIGHT, XKB_KEY_Right } }, + { GAMESCOPE_INPUT_METHOD_ACTION_MOVE_UP, { KEY_UP, XKB_KEY_Up } }, + { GAMESCOPE_INPUT_METHOD_ACTION_MOVE_DOWN, { KEY_DOWN, XKB_KEY_Down } }, + }; + +struct wlserver_input_method +{ + struct wl_resource *resource; + struct wlserver_input_method_manager *manager; + uint32_t serial; + + struct + { + char *string; + enum gamescope_input_method_action action; + } pending; + + // Used to send emulated input events + struct wlr_keyboard keyboard; + std::deque keys; + uint32_t next_keycode_index; + int32_t held_keycode; + xkb_mod_mask_t held_modifier_mask; + struct wlr_keyboard_modifiers prev_mods; + + struct wl_event_source *ime_reset_ime_keyboard_event_source; + struct wl_event_source *ime_release_ime_keypress_event_source; + + uint32_t uFakeTimestamp = 0; }; -struct wlserver_input_method_manager { - struct wl_global *global; - struct wlserver_t *server; +struct wlserver_input_method_manager +{ + struct wl_global *global; + struct wlserver_t *server; - struct wl_event_source *ime_reset_keyboard_event_source; + struct wl_event_source *ime_reset_keyboard_event_source; }; -static LogScope ime_log("ime"); +static LogScope ime_log( "ime" ); -static xkb_keysym_t keysym_from_ch(uint32_t ch) +static xkb_keysym_t keysym_from_ch( uint32_t ch ) { - // There's a bug in libxkbcommon where the EURO symbol doesn't map to the correct keysym - if (ch == 0x20ac) { - return XKB_KEY_EuroSign; - } - if ((ch >= 0x3130 && ch <= 0x318f) // Hangul Compatibility Jamo that CEF does not like as keysyms - || (ch >= 0x11A8 && ch <= 0x11C2) // subset of Hangul Jamo that CEF does not like as keysyms - || (ch == 0x11EB) // continued - || (ch == 0x11F0) // continued - || (ch == 0x11F9) // continued - || (ch == 0x2030)) // PER MILLE SIGN - { - return ch | 0x1000000; - } - return xkb_utf32_to_keysym(ch); + // There's a bug in libxkbcommon where the EURO symbol doesn't map to the + // correct keysym + if ( ch == 0x20ac ) { return XKB_KEY_EuroSign; } + if ( ( ch >= 0x3130 && ch <= 0x318f ) // Hangul Compatibility Jamo that CEF + // does not like as keysyms + || ( ch >= 0x11A8 && ch <= 0x11C2 ) // subset of Hangul Jamo that CEF + // does not like as keysyms + || ( ch == 0x11EB ) // continued + || ( ch == 0x11F0 ) // continued + || ( ch == 0x11F9 ) // continued + || ( ch == 0x2030 ) ) // PER MILLE SIGN + { + return ch | 0x1000000; + } + return xkb_utf32_to_keysym( ch ); } -static uint32_t keycode_from_ch(struct wlserver_input_method *ime, uint32_t ch) +static uint32_t +keycode_from_ch( struct wlserver_input_method *ime, uint32_t ch ) { - xkb_keysym_t keysym = keysym_from_ch(ch); - if (keysym == XKB_KEY_NoSymbol) { - return XKB_KEYCODE_INVALID; - } - - // Repeated chars can re-use keycode - if (!ime->keys.empty() && ime->keys.back().keysym == keysym) - { - return ime->keys.back().keycode; - } - - if (ime->keys.size() >= allow_keycodes_len) { - // TODO: maybe use keycodes above KEY_MAX? - ime_log.errorf("Key codes wrapped within 100ms!"); - ime->keys.pop_front(); - // FALLTHROUGH and allow re-use (oldest key probably fine anyway) - } - - uint32_t keycode = allow_keycodes[ime->next_keycode_index++ % allow_keycodes_len]; - ime->keys.push_back((struct wlserver_input_method_key){ keycode, keysym }); - return keycode; + xkb_keysym_t keysym = keysym_from_ch( ch ); + if ( keysym == XKB_KEY_NoSymbol ) { return XKB_KEYCODE_INVALID; } + + // Repeated chars can re-use keycode + if ( !ime->keys.empty( ) && ime->keys.back( ).keysym == keysym ) + { + return ime->keys.back( ).keycode; + } + + if ( ime->keys.size( ) >= allow_keycodes_len ) + { + // TODO: maybe use keycodes above KEY_MAX? + ime_log.errorf( "Key codes wrapped within 100ms!" ); + ime->keys.pop_front( ); + // FALLTHROUGH and allow re-use (oldest key probably fine anyway) + } + + uint32_t keycode = + allow_keycodes[ ime->next_keycode_index++ % allow_keycodes_len ]; + ime->keys.push_back( + ( struct wlserver_input_method_key ){ keycode, keysym } ); + return keycode; } -static bool generate_keymap_key(FILE *f, uint32_t keycode, xkb_keysym_t keysym) +static bool +generate_keymap_key( FILE *f, uint32_t keycode, xkb_keysym_t keysym ) { - char keysym_name[256]; - int ret = xkb_keysym_get_name(keysym, keysym_name, sizeof(keysym_name)); - if (ret <= 0) { - ime_log.errorf("xkb_keysym_get_name failed for keysym %u", keysym); - return false; - } - - fprintf(f, " key {[ %s ]};\n", keycode, keysym_name); - return true; + char keysym_name[ 256 ]; + int ret = xkb_keysym_get_name( keysym, keysym_name, sizeof( keysym_name ) ); + if ( ret <= 0 ) + { + ime_log.errorf( "xkb_keysym_get_name failed for keysym %u", keysym ); + return false; + } + + fprintf( f, " key {[ %s ]};\n", keycode, keysym_name ); + return true; } -static struct xkb_keymap *generate_keymap(struct wlserver_input_method *ime) +static struct xkb_keymap *generate_keymap( struct wlserver_input_method *ime ) { - uint32_t keycode_offset = 8; - - char *str = NULL; - size_t str_size = 0; - FILE *f = open_memstream(&str, &str_size); - - // min/max from the set of all allow_keycodes and actions - uint32_t min_keycode = KEY_1; - uint32_t max_keycode = KEY_DELETE; - fprintf(f, - "xkb_keymap {\n" - "\n" - "xkb_keycodes \"(unnamed)\" {\n" - " minimum = %u;\n" - " maximum = %u;\n", - keycode_offset + min_keycode, - keycode_offset + max_keycode - ); - - for (const auto& kk : ime->keys) { - uint32_t keycode = kk.keycode; - fprintf(f, " = %u;\n", keycode, keycode + keycode_offset); - } - for (const auto& kv : actions) { - uint32_t keycode = kv.second.keycode; - fprintf(f, " = %u;\n", keycode, keycode + keycode_offset); - } - - // TODO: should we really be including "complete" here? squeekboard seems - // to get away with some other workarounds: - // https://gitlab.gnome.org/World/Phosh/squeekboard/-/blob/fc411d680b0138042b95b8a630401607726113d4/src/keyboard.rs#L180 - fprintf(f, - "};\n" - "\n" - "xkb_types \"(unnamed)\" { include \"complete\" };\n" - "\n" - "xkb_compatibility \"(unnamed)\" { include \"complete\" };\n" - "\n" - "xkb_symbols \"(unnamed)\" {\n" - ); - - for (const auto& kk : ime->keys) { - if (!generate_keymap_key(f, kk.keycode, kk.keysym)) - { - fclose(f); - free(str); - return nullptr; - } - } - for (const auto& kv : actions) { - const auto kk = kv.second; - if (!generate_keymap_key(f, kk.keycode, kk.keysym)) - { - fclose(f); - free(str); - return nullptr; - } - } - - fprintf(f, - "};\n" - "\n" - "};\n" - ); - - fclose(f); - - struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - struct xkb_keymap *keymap = xkb_keymap_new_from_buffer(context, str, str_size, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); - xkb_context_unref(context); - - free(str); - - return keymap; + uint32_t keycode_offset = 8; + + char *str = NULL; + size_t str_size = 0; + FILE *f = open_memstream( &str, &str_size ); + + // min/max from the set of all allow_keycodes and actions + uint32_t min_keycode = KEY_1; + uint32_t max_keycode = KEY_DELETE; + fprintf( + f, + "xkb_keymap {\n" + "\n" + "xkb_keycodes \"(unnamed)\" {\n" + " minimum = %u;\n" + " maximum = %u;\n", + keycode_offset + min_keycode, + keycode_offset + max_keycode ); + + for ( const auto &kk : ime->keys ) + { + uint32_t keycode = kk.keycode; + fprintf( f, " = %u;\n", keycode, keycode + keycode_offset ); + } + for ( const auto &kv : actions ) + { + uint32_t keycode = kv.second.keycode; + fprintf( f, " = %u;\n", keycode, keycode + keycode_offset ); + } + + // TODO: should we really be including "complete" here? squeekboard seems + // to get away with some other workarounds: + // https://gitlab.gnome.org/World/Phosh/squeekboard/-/blob/fc411d680b0138042b95b8a630401607726113d4/src/keyboard.rs#L180 + fprintf( + f, + "};\n" + "\n" + "xkb_types \"(unnamed)\" { include \"complete\" };\n" + "\n" + "xkb_compatibility \"(unnamed)\" { include \"complete\" };\n" + "\n" + "xkb_symbols \"(unnamed)\" {\n" ); + + for ( const auto &kk : ime->keys ) + { + if ( !generate_keymap_key( f, kk.keycode, kk.keysym ) ) + { + fclose( f ); + free( str ); + return nullptr; + } + } + for ( const auto &kv : actions ) + { + const auto kk = kv.second; + if ( !generate_keymap_key( f, kk.keycode, kk.keysym ) ) + { + fclose( f ); + free( str ); + return nullptr; + } + } + + fprintf( + f, + "};\n" + "\n" + "};\n" ); + + fclose( f ); + + struct xkb_context *context = xkb_context_new( XKB_CONTEXT_NO_FLAGS ); + struct xkb_keymap *keymap = xkb_keymap_new_from_buffer( + context, + str, + str_size, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS ); + xkb_context_unref( context ); + + free( str ); + + return keymap; } -static int release_key_if_needed(void *data) +static int release_key_if_needed( void *data ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)data; - struct wlr_seat *seat = ime->manager->server->wlr.seat; - - if (ime->held_keycode >= 0) - wlr_seat_keyboard_notify_key(seat, 0, ime->held_keycode, WL_KEYBOARD_KEY_STATE_RELEASED); - ime->held_keycode = -1; - - if (ime->held_modifier_mask) - { - if (ime->held_modifier_mask & WLR_MODIFIER_ALT) - wlr_seat_keyboard_notify_key(seat, 0, KEY_LEFTALT, WL_KEYBOARD_KEY_STATE_RELEASED); - if (ime->held_modifier_mask & WLR_MODIFIER_CTRL) - wlr_seat_keyboard_notify_key(seat, 0, KEY_LEFTCTRL, WL_KEYBOARD_KEY_STATE_RELEASED); - if (ime->held_modifier_mask & WLR_MODIFIER_SHIFT) - wlr_seat_keyboard_notify_key(seat, 0, KEY_LEFTSHIFT, WL_KEYBOARD_KEY_STATE_RELEASED); - wlr_seat_keyboard_notify_modifiers(seat, &ime->prev_mods); - } - ime->prev_mods = wlr_keyboard_modifiers{0}; - ime->held_modifier_mask = 0; - - return 0; + struct wlserver_input_method *ime = ( struct wlserver_input_method * )data; + struct wlr_seat *seat = ime->manager->server->wlr.seat; + + if ( ime->held_keycode >= 0 ) + wlr_seat_keyboard_notify_key( + seat, 0, ime->held_keycode, WL_KEYBOARD_KEY_STATE_RELEASED ); + ime->held_keycode = -1; + + if ( ime->held_modifier_mask ) + { + if ( ime->held_modifier_mask & WLR_MODIFIER_ALT ) + wlr_seat_keyboard_notify_key( + seat, 0, KEY_LEFTALT, WL_KEYBOARD_KEY_STATE_RELEASED ); + if ( ime->held_modifier_mask & WLR_MODIFIER_CTRL ) + wlr_seat_keyboard_notify_key( + seat, 0, KEY_LEFTCTRL, WL_KEYBOARD_KEY_STATE_RELEASED ); + if ( ime->held_modifier_mask & WLR_MODIFIER_SHIFT ) + wlr_seat_keyboard_notify_key( + seat, 0, KEY_LEFTSHIFT, WL_KEYBOARD_KEY_STATE_RELEASED ); + wlr_seat_keyboard_notify_modifiers( seat, &ime->prev_mods ); + } + ime->prev_mods = wlr_keyboard_modifiers{ 0 }; + ime->held_modifier_mask = 0; + + return 0; } -static void press_key(struct wlserver_input_method *ime, uint32_t keycode, struct wlr_keyboard_modifiers *pmods = nullptr) +static void press_key( + struct wlserver_input_method *ime, + uint32_t keycode, + struct wlr_keyboard_modifiers *pmods = nullptr ) { - struct wlr_seat *seat = ime->manager->server->wlr.seat; - - release_key_if_needed(ime); - - if (pmods) - { - if (seat->keyboard_state.keyboard != nullptr) - ime->prev_mods = seat->keyboard_state.keyboard->modifiers; - wlr_seat_keyboard_notify_modifiers(seat, pmods); - ime->held_modifier_mask = pmods->depressed & ~ime->prev_mods.depressed; - if (ime->held_modifier_mask & WLR_MODIFIER_SHIFT) - wlr_seat_keyboard_notify_key(seat, 0, KEY_LEFTSHIFT, WL_KEYBOARD_KEY_STATE_PRESSED); - if (ime->held_modifier_mask & WLR_MODIFIER_CTRL) - wlr_seat_keyboard_notify_key(seat, 0, KEY_LEFTCTRL, WL_KEYBOARD_KEY_STATE_PRESSED); - if (ime->held_modifier_mask & WLR_MODIFIER_ALT) - wlr_seat_keyboard_notify_key(seat, 0, KEY_LEFTALT, WL_KEYBOARD_KEY_STATE_PRESSED); - } - - // Note: Xwayland doesn't care about the time field of the events - wlr_seat_keyboard_notify_key(seat, 0, keycode, WL_KEYBOARD_KEY_STATE_PRESSED); - ime->held_keycode = keycode; - - wl_event_source_timer_update(ime->ime_release_ime_keypress_event_source, 30 /* ms */); + struct wlr_seat *seat = ime->manager->server->wlr.seat; + + release_key_if_needed( ime ); + + if ( pmods ) + { + if ( seat->keyboard_state.keyboard != nullptr ) + ime->prev_mods = seat->keyboard_state.keyboard->modifiers; + wlr_seat_keyboard_notify_modifiers( seat, pmods ); + ime->held_modifier_mask = pmods->depressed & ~ime->prev_mods.depressed; + if ( ime->held_modifier_mask & WLR_MODIFIER_SHIFT ) + wlr_seat_keyboard_notify_key( + seat, 0, KEY_LEFTSHIFT, WL_KEYBOARD_KEY_STATE_PRESSED ); + if ( ime->held_modifier_mask & WLR_MODIFIER_CTRL ) + wlr_seat_keyboard_notify_key( + seat, 0, KEY_LEFTCTRL, WL_KEYBOARD_KEY_STATE_PRESSED ); + if ( ime->held_modifier_mask & WLR_MODIFIER_ALT ) + wlr_seat_keyboard_notify_key( + seat, 0, KEY_LEFTALT, WL_KEYBOARD_KEY_STATE_PRESSED ); + } + + // Note: Xwayland doesn't care about the time field of the events + wlr_seat_keyboard_notify_key( + seat, 0, keycode, WL_KEYBOARD_KEY_STATE_PRESSED ); + ime->held_keycode = keycode; + + wl_event_source_timer_update( + ime->ime_release_ime_keypress_event_source, 30 /* ms */ ); } -static bool try_type_keysym(struct wlserver_input_method *ime, xkb_keysym_t keysym) +static bool +try_type_keysym( struct wlserver_input_method *ime, xkb_keysym_t keysym ) { - struct wlr_seat *seat = ime->manager->server->wlr.seat; - struct wlr_keyboard *keyboard = ime->manager->server->wlr.virtual_keyboard_device; - - struct xkb_keymap *keymap = keyboard->keymap; - xkb_keycode_t min_keycode = xkb_keymap_min_keycode(keymap); - xkb_keycode_t max_keycode = xkb_keymap_max_keycode(keymap); - for (xkb_keycode_t keycode = min_keycode; keycode <= max_keycode; keycode++) { - xkb_layout_index_t num_layouts = xkb_keymap_num_layouts_for_key(keymap, keycode); - for (xkb_layout_index_t layout = 0; layout < num_layouts; layout++) { - xkb_level_index_t num_levels = xkb_keymap_num_levels_for_key(keymap, keycode, layout); - for (xkb_level_index_t level = 0; level < num_levels; level++) { - const xkb_keysym_t *syms = nullptr; - int num_syms = xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms); - if (num_syms != 1) { - continue; - } - if (syms[0] != keysym) { - continue; - } - - xkb_mod_mask_t mask; - size_t num_masks = xkb_keymap_key_get_mods_for_level(keymap, keycode, layout, level, &mask, 1); - if (num_masks != 1) { - continue; - } - - xkb_mod_mask_t allowed = WLR_MODIFIER_SHIFT | WLR_MODIFIER_CTRL | WLR_MODIFIER_ALT; - if ((mask & allowed) != mask) { - continue; - } - - release_key_if_needed(ime); // before keymap change - wlr_seat_set_keyboard(seat, keyboard); - - struct wlr_keyboard_modifiers mods = { - .depressed = mask, - }; - assert(keycode >= 8); - press_key(ime, keycode - 8, &mods); - - return true; - } - } - } - - return false; + struct wlr_seat *seat = ime->manager->server->wlr.seat; + struct wlr_keyboard *keyboard = + ime->manager->server->wlr.virtual_keyboard_device; + + struct xkb_keymap *keymap = keyboard->keymap; + xkb_keycode_t min_keycode = xkb_keymap_min_keycode( keymap ); + xkb_keycode_t max_keycode = xkb_keymap_max_keycode( keymap ); + for ( xkb_keycode_t keycode = min_keycode; keycode <= max_keycode; + keycode++ ) + { + xkb_layout_index_t num_layouts = + xkb_keymap_num_layouts_for_key( keymap, keycode ); + for ( xkb_layout_index_t layout = 0; layout < num_layouts; layout++ ) + { + xkb_level_index_t num_levels = + xkb_keymap_num_levels_for_key( keymap, keycode, layout ); + for ( xkb_level_index_t level = 0; level < num_levels; level++ ) + { + const xkb_keysym_t *syms = nullptr; + int num_syms = xkb_keymap_key_get_syms_by_level( + keymap, keycode, layout, level, &syms ); + if ( num_syms != 1 ) { continue; } + if ( syms[ 0 ] != keysym ) { continue; } + + xkb_mod_mask_t mask; + size_t num_masks = xkb_keymap_key_get_mods_for_level( + keymap, keycode, layout, level, &mask, 1 ); + if ( num_masks != 1 ) { continue; } + + xkb_mod_mask_t allowed = + WLR_MODIFIER_SHIFT | WLR_MODIFIER_CTRL | WLR_MODIFIER_ALT; + if ( ( mask & allowed ) != mask ) { continue; } + + release_key_if_needed( ime ); // before keymap change + wlr_seat_set_keyboard( seat, keyboard ); + + struct wlr_keyboard_modifiers mods = { + .depressed = mask, + }; + assert( keycode >= 8 ); + press_key( ime, keycode - 8, &mods ); + + return true; + } + } + } + + return false; } -void type_text(struct wlserver_input_method *ime, const char *text) +void type_text( struct wlserver_input_method *ime, const char *text ) { - // If possible, try to type the character without switching the keymap - // ...unless we're already using a fancy keymap - if (utf8_size(text) == 1 && text[1] == '\0' && ime->keys.empty()) { - xkb_keysym_t keysym = keysym_from_ch(text[0]); - if (keysym != XKB_KEY_NoSymbol && try_type_keysym(ime, keysym)) { - return; - } - } - - std::vector keycodes; - while (text[0] != '\0') { - uint32_t ch = utf8_decode(&text); - - xkb_keycode_t keycode = keycode_from_ch(ime, ch); - if (keycode == XKB_KEYCODE_INVALID) { - ime_log.errorf("warning: cannot type character U+%X", ch); - continue; - } - - keycodes.push_back(keycode); - } - - struct xkb_keymap *keymap = generate_keymap(ime); - if (keymap == nullptr) { - ime_log.errorf("failed to generate keymap"); - return; - } - wlr_keyboard_set_keymap(&ime->keyboard, keymap); - xkb_keymap_unref(keymap); - - struct wlr_seat *seat = ime->manager->server->wlr.seat; - release_key_if_needed(ime); // before keymap change - wlr_seat_set_keyboard(seat, &ime->keyboard); - - // Note: Xwayland doesn't care about the time field of the events - for (size_t i = 0; i < keycodes.size(); i++) { - press_key(ime, keycodes[i]); - } - - // Reset keymap when we're idle for a while - wl_event_source_timer_update(ime->ime_reset_ime_keyboard_event_source, 100 /* ms */); + // If possible, try to type the character without switching the keymap + // ...unless we're already using a fancy keymap + if ( utf8_size( text ) == 1 && text[ 1 ] == '\0' && ime->keys.empty( ) ) + { + xkb_keysym_t keysym = keysym_from_ch( text[ 0 ] ); + if ( keysym != XKB_KEY_NoSymbol && try_type_keysym( ime, keysym ) ) + { + return; + } + } + + std::vector keycodes; + while ( text[ 0 ] != '\0' ) + { + uint32_t ch = utf8_decode( &text ); + + xkb_keycode_t keycode = keycode_from_ch( ime, ch ); + if ( keycode == XKB_KEYCODE_INVALID ) + { + ime_log.errorf( "warning: cannot type character U+%X", ch ); + continue; + } + + keycodes.push_back( keycode ); + } + + struct xkb_keymap *keymap = generate_keymap( ime ); + if ( keymap == nullptr ) + { + ime_log.errorf( "failed to generate keymap" ); + return; + } + wlr_keyboard_set_keymap( &ime->keyboard, keymap ); + xkb_keymap_unref( keymap ); + + struct wlr_seat *seat = ime->manager->server->wlr.seat; + release_key_if_needed( ime ); // before keymap change + wlr_seat_set_keyboard( seat, &ime->keyboard ); + + // Note: Xwayland doesn't care about the time field of the events + for ( size_t i = 0; i < keycodes.size( ); i++ ) + { + press_key( ime, keycodes[ i ] ); + } + + // Reset keymap when we're idle for a while + wl_event_source_timer_update( + ime->ime_reset_ime_keyboard_event_source, 100 /* ms */ ); } -void perform_action(struct wlserver_input_method *ime, enum gamescope_input_method_action action) +void perform_action( + struct wlserver_input_method *ime, + enum gamescope_input_method_action action ) { - if (actions.count(action) == 0) { - ime_log.errorf("unsupported action %d", action); - return; - } - - const struct wlserver_input_method_key key = actions[action]; - // type with default keymap if no crazy keymap is currently active - if (ime->keys.empty() && try_type_keysym(ime, key.keysym)) { - return; - } - - // Keymap always contains all actions[] - - struct xkb_keymap *keymap = generate_keymap(ime); - if (keymap == nullptr) { - ime_log.errorf("failed to generate keymap"); - return; - } - wlr_keyboard_set_keymap(&ime->keyboard, keymap); - xkb_keymap_unref(keymap); - - struct wlr_seat *seat = ime->manager->server->wlr.seat; - release_key_if_needed(ime); // before keymap change - wlr_seat_set_keyboard(seat, &ime->keyboard); - - press_key(ime, key.keycode); - - // Reset keymap when we're idle for a while - wl_event_source_timer_update(ime->ime_reset_ime_keyboard_event_source, 100 /* ms */); + if ( actions.count( action ) == 0 ) + { + ime_log.errorf( "unsupported action %d", action ); + return; + } + + const struct wlserver_input_method_key key = actions[ action ]; + // type with default keymap if no crazy keymap is currently active + if ( ime->keys.empty( ) && try_type_keysym( ime, key.keysym ) ) { return; } + + // Keymap always contains all actions[] + + struct xkb_keymap *keymap = generate_keymap( ime ); + if ( keymap == nullptr ) + { + ime_log.errorf( "failed to generate keymap" ); + return; + } + wlr_keyboard_set_keymap( &ime->keyboard, keymap ); + xkb_keymap_unref( keymap ); + + struct wlr_seat *seat = ime->manager->server->wlr.seat; + release_key_if_needed( ime ); // before keymap change + wlr_seat_set_keyboard( seat, &ime->keyboard ); + + press_key( ime, key.keycode ); + + // Reset keymap when we're idle for a while + wl_event_source_timer_update( + ime->ime_reset_ime_keyboard_event_source, 100 /* ms */ ); } -static void ime_handle_commit(struct wl_client *client, struct wl_resource *ime_resource, uint32_t serial) +static void ime_handle_commit( + struct wl_client *client, + struct wl_resource *ime_resource, + uint32_t serial ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)wl_resource_get_user_data(ime_resource); - - if (serial != ime->serial) { - return; - } - - if (ime->pending.string != nullptr) { - type_text(ime, ime->pending.string); - } - if (ime->pending.action != GAMESCOPE_INPUT_METHOD_ACTION_NONE) { - perform_action(ime, ime->pending.action); - } - - free(ime->pending.string); - ime->pending.string = nullptr; - ime->pending.action = GAMESCOPE_INPUT_METHOD_ACTION_NONE; - - // Steam's virtual keyboard is based on XTest and relies on the keymap to - // be reset. However, resetting it immediately is racy: clients will - // interpret the keycodes we've just sent with the new keymap. To - // workaround these issues, wait for a bit before resetting the keymap. - wl_event_source_timer_update(ime->manager->ime_reset_keyboard_event_source, 100 /* ms */); + struct wlserver_input_method *ime = + ( struct wlserver_input_method * )wl_resource_get_user_data( + ime_resource ); + + if ( serial != ime->serial ) { return; } + + if ( ime->pending.string != nullptr ) + { + type_text( ime, ime->pending.string ); + } + if ( ime->pending.action != GAMESCOPE_INPUT_METHOD_ACTION_NONE ) + { + perform_action( ime, ime->pending.action ); + } + + free( ime->pending.string ); + ime->pending.string = nullptr; + ime->pending.action = GAMESCOPE_INPUT_METHOD_ACTION_NONE; + + // Steam's virtual keyboard is based on XTest and relies on the keymap to + // be reset. However, resetting it immediately is racy: clients will + // interpret the keycodes we've just sent with the new keymap. To + // workaround these issues, wait for a bit before resetting the keymap. + wl_event_source_timer_update( + ime->manager->ime_reset_keyboard_event_source, 100 /* ms */ ); } -static void ime_handle_set_string(struct wl_client *client, struct wl_resource *ime_resource, const char *text) +static void ime_handle_set_string( + struct wl_client *client, + struct wl_resource *ime_resource, + const char *text ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)wl_resource_get_user_data(ime_resource); - free(ime->pending.string); - ime->pending.string = strdup(text); + struct wlserver_input_method *ime = + ( struct wlserver_input_method * )wl_resource_get_user_data( + ime_resource ); + free( ime->pending.string ); + ime->pending.string = strdup( text ); } -static void ime_handle_set_action(struct wl_client *client, struct wl_resource *ime_resource, uint32_t action) +static void ime_handle_set_action( + struct wl_client *client, + struct wl_resource *ime_resource, + uint32_t action ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)wl_resource_get_user_data(ime_resource); - ime->pending.action = (enum gamescope_input_method_action)action; + struct wlserver_input_method *ime = + ( struct wlserver_input_method * )wl_resource_get_user_data( + ime_resource ); + ime->pending.action = ( enum gamescope_input_method_action )action; } -static void ime_handle_destroy(struct wl_client *client, struct wl_resource *ime_resource) -{ - wl_resource_destroy(ime_resource); -} +static void +ime_handle_destroy( struct wl_client *client, struct wl_resource *ime_resource ) +{ wl_resource_destroy( ime_resource ); } -static void ime_handle_pointer_motion(struct wl_client *client, struct wl_resource *ime_resource, wl_fixed_t dx, wl_fixed_t dy) +static void ime_handle_pointer_motion( + struct wl_client *client, + struct wl_resource *ime_resource, + wl_fixed_t dx, + wl_fixed_t dy ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)wl_resource_get_user_data(ime_resource); - - wlserver_mousemotion(wl_fixed_to_double(dx), wl_fixed_to_double(dy), ++ime->uFakeTimestamp); + struct wlserver_input_method *ime = + ( struct wlserver_input_method * )wl_resource_get_user_data( + ime_resource ); + + wlserver_mousemotion( + wl_fixed_to_double( dx ), + wl_fixed_to_double( dy ), + ++ime->uFakeTimestamp ); } -static void ime_handle_pointer_warp(struct wl_client *client, struct wl_resource *ime_resource, wl_fixed_t x, wl_fixed_t y) +static void ime_handle_pointer_warp( + struct wl_client *client, + struct wl_resource *ime_resource, + wl_fixed_t x, + wl_fixed_t y ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)wl_resource_get_user_data(ime_resource); - - wlserver_mousewarp(wl_fixed_to_double(x), wl_fixed_to_double(y), ++ime->uFakeTimestamp, false ); + struct wlserver_input_method *ime = + ( struct wlserver_input_method * )wl_resource_get_user_data( + ime_resource ); + + wlserver_mousewarp( + wl_fixed_to_double( x ), + wl_fixed_to_double( y ), + ++ime->uFakeTimestamp, + false ); } -static void ime_handle_pointer_wheel(struct wl_client *client, struct wl_resource *ime_resource, int32_t x, int32_t y) +static void ime_handle_pointer_wheel( + struct wl_client *client, + struct wl_resource *ime_resource, + int32_t x, + int32_t y ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)wl_resource_get_user_data(ime_resource); + struct wlserver_input_method *ime = + ( struct wlserver_input_method * )wl_resource_get_user_data( + ime_resource ); - wlserver_mousewheel( x / 120.0, y / 120.0, ++ime->uFakeTimestamp); + wlserver_mousewheel( x / 120.0, y / 120.0, ++ime->uFakeTimestamp ); } -static void ime_handle_pointer_button(struct wl_client *client, struct wl_resource *ime_resource, uint32_t button, uint32_t state) +static void ime_handle_pointer_button( + struct wl_client *client, + struct wl_resource *ime_resource, + uint32_t button, + uint32_t state ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)wl_resource_get_user_data(ime_resource); - - wlserver_mousebutton( button, state == GAMESCOPE_INPUT_METHOD_BUTTON_STATE_PRESSED, ++ime->uFakeTimestamp); + struct wlserver_input_method *ime = + ( struct wlserver_input_method * )wl_resource_get_user_data( + ime_resource ); + + wlserver_mousebutton( + button, + state == GAMESCOPE_INPUT_METHOD_BUTTON_STATE_PRESSED, + ++ime->uFakeTimestamp ); } static const struct gamescope_input_method_interface ime_impl = { - .destroy = ime_handle_destroy, - .commit = ime_handle_commit, - .set_string = ime_handle_set_string, - .set_action = ime_handle_set_action, - - .pointer_motion = ime_handle_pointer_motion, - .pointer_warp = ime_handle_pointer_warp, - .pointer_wheel = ime_handle_pointer_wheel, - .pointer_button = ime_handle_pointer_button, + .destroy = ime_handle_destroy, + .commit = ime_handle_commit, + .set_string = ime_handle_set_string, + .set_action = ime_handle_set_action, + + .pointer_motion = ime_handle_pointer_motion, + .pointer_warp = ime_handle_pointer_warp, + .pointer_wheel = ime_handle_pointer_wheel, + .pointer_button = ime_handle_pointer_button, }; -void destroy_ime(struct wlserver_input_method *ime) -{ - wlr_keyboard_finish(&ime->keyboard); -} +void destroy_ime( struct wlserver_input_method *ime ) +{ wlr_keyboard_finish( &ime->keyboard ); } -static void ime_handle_resource_destroy(struct wl_resource *ime_resource) +static void ime_handle_resource_destroy( struct wl_resource *ime_resource ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)wl_resource_get_user_data(ime_resource); - if (ime == nullptr) - return; + struct wlserver_input_method *ime = + ( struct wlserver_input_method * )wl_resource_get_user_data( + ime_resource ); + if ( ime == nullptr ) return; - destroy_ime(ime); + destroy_ime( ime ); - delete ime; + delete ime; } -static void handle_led_update(struct wlr_keyboard *keyboard, uint32_t leds) {} +static void handle_led_update( struct wlr_keyboard *keyboard, uint32_t leds ) {} static const struct wlr_keyboard_impl keyboard_impl = { - .led_update = handle_led_update, + .led_update = handle_led_update, }; -static int reset_ime_keyboard(void *data) +static int reset_ime_keyboard( void *data ) { - struct wlserver_input_method *ime = (struct wlserver_input_method *)data; + struct wlserver_input_method *ime = ( struct wlserver_input_method * )data; - release_key_if_needed(ime); - ime->keys.clear(); - ime->next_keycode_index = 0; // preserve old behavior; could just let this keep going + release_key_if_needed( ime ); + ime->keys.clear( ); + ime->next_keycode_index = + 0; // preserve old behavior; could just let this keep going - return 0; + return 0; } -static void manager_handle_create_input_method(struct wl_client *client, struct wl_resource *manager_resource, struct wl_resource *seat_resource, uint32_t id) +static void manager_handle_create_input_method( + struct wl_client *client, + struct wl_resource *manager_resource, + struct wl_resource *seat_resource, + uint32_t id ) { - struct wlserver_input_method_manager *manager = (struct wlserver_input_method_manager *)wl_resource_get_user_data(manager_resource); - - uint32_t version = wl_resource_get_version(manager_resource); - struct wl_resource *ime_resource = wl_resource_create(client, &gamescope_input_method_interface, version, id); - wl_resource_set_implementation(ime_resource, &ime_impl, nullptr, ime_handle_resource_destroy); - - struct wlserver_input_method *ime = new wlserver_input_method(); - ime->resource = ime_resource; - ime->manager = manager; - ime->serial = 1; - ime->next_keycode_index = 0; - ime->held_keycode = -1; - ime->held_modifier_mask = 0; - ime->prev_mods = wlr_keyboard_modifiers{0}; - - wlr_keyboard_init(&ime->keyboard, &keyboard_impl, "ime"); - - wlr_keyboard_set_repeat_info(&ime->keyboard, 0, 0); - - wl_resource_set_user_data(ime->resource, ime); - gamescope_input_method_send_done(ime->resource, ime->serial); - - ime->ime_reset_ime_keyboard_event_source = wl_event_loop_add_timer(manager->server->event_loop, reset_ime_keyboard, ime); - ime->ime_release_ime_keypress_event_source = wl_event_loop_add_timer(manager->server->event_loop, release_key_if_needed, ime); + struct wlserver_input_method_manager *manager = + ( struct wlserver_input_method_manager * )wl_resource_get_user_data( + manager_resource ); + + uint32_t version = wl_resource_get_version( manager_resource ); + struct wl_resource *ime_resource = wl_resource_create( + client, &gamescope_input_method_interface, version, id ); + wl_resource_set_implementation( + ime_resource, &ime_impl, nullptr, ime_handle_resource_destroy ); + + struct wlserver_input_method *ime = new wlserver_input_method( ); + ime->resource = ime_resource; + ime->manager = manager; + ime->serial = 1; + ime->next_keycode_index = 0; + ime->held_keycode = -1; + ime->held_modifier_mask = 0; + ime->prev_mods = wlr_keyboard_modifiers{ 0 }; + + wlr_keyboard_init( &ime->keyboard, &keyboard_impl, "ime" ); + + wlr_keyboard_set_repeat_info( &ime->keyboard, 0, 0 ); + + wl_resource_set_user_data( ime->resource, ime ); + gamescope_input_method_send_done( ime->resource, ime->serial ); + + ime->ime_reset_ime_keyboard_event_source = wl_event_loop_add_timer( + manager->server->event_loop, reset_ime_keyboard, ime ); + ime->ime_release_ime_keypress_event_source = wl_event_loop_add_timer( + manager->server->event_loop, release_key_if_needed, ime ); } -struct wlserver_input_method *create_local_ime() +struct wlserver_input_method *create_local_ime( ) { - struct wlserver_input_method *ime = new wlserver_input_method(); - ime->resource = nullptr; - ime->manager = global_manager; - ime->serial = 1; - ime->next_keycode_index = 0; - ime->held_keycode = -1; - ime->held_modifier_mask = 0; - ime->prev_mods = wlr_keyboard_modifiers{0}; + struct wlserver_input_method *ime = new wlserver_input_method( ); + ime->resource = nullptr; + ime->manager = global_manager; + ime->serial = 1; + ime->next_keycode_index = 0; + ime->held_keycode = -1; + ime->held_modifier_mask = 0; + ime->prev_mods = wlr_keyboard_modifiers{ 0 }; - wlr_keyboard_init(&ime->keyboard, &keyboard_impl, "local_ime"); + wlr_keyboard_init( &ime->keyboard, &keyboard_impl, "local_ime" ); - wlr_keyboard_set_repeat_info(&ime->keyboard, 0, 0); + wlr_keyboard_set_repeat_info( &ime->keyboard, 0, 0 ); - ime->ime_reset_ime_keyboard_event_source = wl_event_loop_add_timer(global_manager->server->event_loop, reset_ime_keyboard, ime); - ime->ime_release_ime_keypress_event_source = wl_event_loop_add_timer(global_manager->server->event_loop, release_key_if_needed, ime); + ime->ime_reset_ime_keyboard_event_source = wl_event_loop_add_timer( + global_manager->server->event_loop, reset_ime_keyboard, ime ); + ime->ime_release_ime_keypress_event_source = wl_event_loop_add_timer( + global_manager->server->event_loop, release_key_if_needed, ime ); - return ime; + return ime; } -static void manager_handle_destroy(struct wl_client *client, struct wl_resource *manager_resource) -{ - wl_resource_destroy(manager_resource); -} +static void manager_handle_destroy( + struct wl_client *client, struct wl_resource *manager_resource ) +{ wl_resource_destroy( manager_resource ); } static const struct gamescope_input_method_manager_interface manager_impl = { - .destroy = manager_handle_destroy, - .create_input_method = manager_handle_create_input_method, + .destroy = manager_handle_destroy, + .create_input_method = manager_handle_create_input_method, }; -static void manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) +static void manager_bind( + struct wl_client *client, void *data, uint32_t version, uint32_t id ) { - struct wlserver_input_method_manager *manager = (struct wlserver_input_method_manager *)data; + struct wlserver_input_method_manager *manager = + ( struct wlserver_input_method_manager * )data; - struct wl_resource *resource = wl_resource_create(client, &gamescope_input_method_manager_interface, version, id); - wl_resource_set_implementation(resource, &manager_impl, manager, nullptr); + struct wl_resource *resource = wl_resource_create( + client, &gamescope_input_method_manager_interface, version, id ); + wl_resource_set_implementation( resource, &manager_impl, manager, nullptr ); } -static int reset_keyboard(void *data) +static int reset_keyboard( void *data ) { - struct wlserver_t *wlserver = (struct wlserver_t *)data; + struct wlserver_t *wlserver = ( struct wlserver_t * )data; - // Reset the keyboard if it's not set or set to an IME's - struct wlr_seat *seat = wlserver->wlr.seat; - if (seat->keyboard_state.keyboard == nullptr || seat->keyboard_state.keyboard->data == nullptr) { - wlr_seat_set_keyboard(seat, wlserver->wlr.virtual_keyboard_device); - } + // Reset the keyboard if it's not set or set to an IME's + struct wlr_seat *seat = wlserver->wlr.seat; + if ( seat->keyboard_state.keyboard == nullptr || + seat->keyboard_state.keyboard->data == nullptr ) + { + wlr_seat_set_keyboard( seat, wlserver->wlr.virtual_keyboard_device ); + } - return 0; + return 0; } -void create_ime_manager(struct wlserver_t *wlserver) +void create_ime_manager( struct wlserver_t *wlserver ) { - struct wlserver_input_method_manager *manager = new wlserver_input_method_manager(); - manager->server = wlserver; - manager->global = wl_global_create(wlserver->display, &gamescope_input_method_manager_interface, IME_MANAGER_VERSION, manager, manager_bind); - manager->ime_reset_keyboard_event_source = wl_event_loop_add_timer(wlserver->event_loop, reset_keyboard, wlserver); - - global_manager = manager; + struct wlserver_input_method_manager *manager = + new wlserver_input_method_manager( ); + manager->server = wlserver; + manager->global = wl_global_create( + wlserver->display, + &gamescope_input_method_manager_interface, + IME_MANAGER_VERSION, + manager, + manager_bind ); + manager->ime_reset_keyboard_event_source = wl_event_loop_add_timer( + wlserver->event_loop, reset_keyboard, wlserver ); + + global_manager = manager; } diff --git a/src/ime.hpp b/src/ime.hpp index 08870835c2..a9454e6b40 100644 --- a/src/ime.hpp +++ b/src/ime.hpp @@ -2,12 +2,14 @@ #pragma once -#include "wlserver.hpp" #include "gamescope-input-method-protocol.h" +#include "wlserver.hpp" -void create_ime_manager(struct wlserver_t *wlserver); +void create_ime_manager( struct wlserver_t *wlserver ); -struct wlserver_input_method *create_local_ime(); -void destroy_ime(struct wlserver_input_method * ime); -void type_text(struct wlserver_input_method *ime, const char *text); -void perform_action(struct wlserver_input_method *ime, enum gamescope_input_method_action action); +struct wlserver_input_method *create_local_ime( ); +void destroy_ime( struct wlserver_input_method *ime ); +void type_text( struct wlserver_input_method *ime, const char *text ); +void perform_action( + struct wlserver_input_method *ime, + enum gamescope_input_method_action action ); diff --git a/src/layer_defines.h b/src/layer_defines.h index 6ebfd3221d..f9b2d49b9c 100644 --- a/src/layer_defines.h +++ b/src/layer_defines.h @@ -6,13 +6,14 @@ namespace GamescopeLayerClient { // GAMESCOPE_LAYER_CLIENT_FLAGS - namespace Flag { - static constexpr uint32_t DisableHDR = 1u << 0; - static constexpr uint32_t ForceBypass = 1u << 1; + namespace Flag + { + static constexpr uint32_t DisableHDR = 1u << 0; + static constexpr uint32_t ForceBypass = 1u << 1; static constexpr uint32_t FrameLimiterAware = 1u << 2; - static constexpr uint32_t NoSuboptimal = 1u << 3; + static constexpr uint32_t NoSuboptimal = 1u << 3; static constexpr uint32_t ForceSwapchainExtent = 1u << 4; - } + } // namespace Flag using Flags = uint32_t; -} \ No newline at end of file +} // namespace GamescopeLayerClient diff --git a/src/log.cpp b/src/log.cpp index 7b502a4ae1..598736a29d 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,175 +1,203 @@ -#include #include +#include #include -#include #include -#include "Utils/Process.h" #include "Utils/Defer.h" +#include "Utils/Process.h" #include "convar.h" #include "log.hpp" static constexpr std::string_view GetLogPriorityText( LogPriority ePriority ) { - switch ( ePriority ) - { - case LOG_SILENT: return "[\e[0;37m" "Shh.." "\e[0m]"; - case LOG_ERROR: return "[\e[0;31m" "Error" "\e[0m]"; - case LOG_WARNING: return "[\e[0;33m" "Warn" "\e[0m] "; - case LOG_DEBUG: return "[\e[0;35m" "Debug" "\e[0m]"; - default: - case LOG_INFO: return "[\e[0;34m" "Info" "\e[0m] "; - } + switch ( ePriority ) + { + case LOG_SILENT: + return "[\e[0;37m" + "Shh.." + "\e[0m]"; + case LOG_ERROR: + return "[\e[0;31m" + "Error" + "\e[0m]"; + case LOG_WARNING: + return "[\e[0;33m" + "Warn" + "\e[0m] "; + case LOG_DEBUG: + return "[\e[0;35m" + "Debug" + "\e[0m]"; + default: + case LOG_INFO: + return "[\e[0;34m" + "Info" + "\e[0m] "; + } } static constexpr std::string_view GetLogName( LogPriority ePriority ) { - switch ( ePriority ) - { - case LOG_SILENT: return "silent"; - case LOG_ERROR: return "error"; - case LOG_WARNING: return "warning"; - case LOG_DEBUG: return "debug"; - default: - case LOG_INFO: return "info"; - } + switch ( ePriority ) + { + case LOG_SILENT: + return "silent"; + case LOG_ERROR: + return "error"; + case LOG_WARNING: + return "warning"; + case LOG_DEBUG: + return "debug"; + default: + case LOG_INFO: + return "info"; + } } static constexpr LogPriority GetPriorityFromString( std::string_view psvScope ) { - if ( psvScope == "silent" ) - return LOG_SILENT; - else if ( psvScope == "error" ) - return LOG_ERROR; - else if ( psvScope == "warning" ) - return LOG_WARNING; - else if ( psvScope == "debug" ) - return LOG_DEBUG; - else - return LOG_INFO; + if ( psvScope == "silent" ) return LOG_SILENT; + else if ( psvScope == "error" ) + return LOG_ERROR; + else if ( psvScope == "warning" ) + return LOG_WARNING; + else if ( psvScope == "debug" ) + return LOG_DEBUG; + else + return LOG_INFO; } struct LogConVar_t { - LogConVar_t( LogScope *pScope, std::string_view psvName, LogPriority eDefaultPriority ) - : sName{ std::format( "log_{}", psvName ) } - , sDescription{ std::format( "Max logging priority for the {} channel. Valid options are: [ silent, error, warning, debug, info ].", psvName ) } - , convar - { sName, std::string( GetLogName( eDefaultPriority ) ), sDescription, - [ pScope ]( gamescope::ConVar &cvar ) - { - pScope->SetPriority( GetPriorityFromString( cvar ) ); - }, - } - { - - } - std::string sName; - std::string sDescription; - - gamescope::ConVar convar; + LogConVar_t( + LogScope *pScope, + std::string_view psvName, + LogPriority eDefaultPriority ) : + sName{ std::format( "log_{}", psvName ) }, + sDescription{ std::format( + "Max logging priority for the {} channel. Valid options are: [ " + "silent, error, warning, debug, info ].", + psvName ) }, + convar{ + sName, + std::string( GetLogName( eDefaultPriority ) ), + sDescription, + [ pScope ]( gamescope::ConVar &cvar ) + { pScope->SetPriority( GetPriorityFromString( cvar ) ); }, + } + {} + std::string sName; + std::string sDescription; + + gamescope::ConVar convar; }; -LogScope::LogScope( std::string_view psvName, LogPriority eMaxPriority ) - : LogScope( psvName, psvName, eMaxPriority ) -{ -} +LogScope::LogScope( std::string_view psvName, LogPriority eMaxPriority ) : + LogScope( psvName, psvName, eMaxPriority ) +{} -LogScope::LogScope( std::string_view psvName, std::string_view psvPrefix, LogPriority eMaxPriority ) - : m_psvName{ psvName } - , m_psvPrefix{ psvPrefix } - , m_eMaxPriority{ eMaxPriority } - , m_pEnableConVar{ std::make_unique( this, psvName, eMaxPriority ) } -{ -} +LogScope::LogScope( + std::string_view psvName, + std::string_view psvPrefix, + LogPriority eMaxPriority ) : + m_psvName{ psvName }, + m_psvPrefix{ psvPrefix }, + m_eMaxPriority{ eMaxPriority }, + m_pEnableConVar{ std::make_unique( + this, psvName, eMaxPriority ) } +{} -LogScope::~LogScope() -{ -} +LogScope::~LogScope( ) {} bool LogScope::Enabled( LogPriority ePriority ) const -{ - return ePriority <= m_eMaxPriority; -} +{ return ePriority <= m_eMaxPriority; } -void LogScope::vlogf(enum LogPriority priority, const char *fmt, va_list args) +void LogScope::vlogf( enum LogPriority priority, const char *fmt, va_list args ) { - if ( !Enabled( priority ) ) - return; + if ( !Enabled( priority ) ) return; - char *buf = nullptr; - vasprintf(&buf, fmt, args); - if (!buf) - return; - defer( free(buf); ); + char *buf = nullptr; + vasprintf( &buf, fmt, args ); + if ( !buf ) return; + defer( free( buf ); ); - std::string_view svBuf = buf; - log(priority, svBuf); + std::string_view svBuf = buf; + log( priority, svBuf ); } -void LogScope::log(enum LogPriority priority, std::string_view psvText) +void LogScope::log( enum LogPriority priority, std::string_view psvText ) { - if ( !Enabled( priority ) ) - return; - - for (auto& listener : m_LoggingListeners) - listener.second( priority, m_psvPrefix, psvText ); - - std::string_view psvLogName = GetLogPriorityText( priority ); - if ( bPrefixEnabled ) - fprintf(stderr, "[%s] %.*s \e[0;37m%.*s:\e[0m %.*s\n", - gamescope::Process::GetProcessName(), - (int)psvLogName.size(), psvLogName.data(), - (int)this->m_psvPrefix.size(), this->m_psvPrefix.data(), - (int)psvText.size(), psvText.data()); - else - fprintf(stderr, "%.*s\n", (int)psvText.size(), psvText.data()); + if ( !Enabled( priority ) ) return; + + for ( auto &listener : m_LoggingListeners ) + listener.second( priority, m_psvPrefix, psvText ); + + std::string_view psvLogName = GetLogPriorityText( priority ); + if ( bPrefixEnabled ) + fprintf( + stderr, + "[%s] %.*s \e[0;37m%.*s:\e[0m %.*s\n", + gamescope::Process::GetProcessName( ), + ( int )psvLogName.size( ), + psvLogName.data( ), + ( int )this->m_psvPrefix.size( ), + this->m_psvPrefix.data( ), + ( int )psvText.size( ), + psvText.data( ) ); + else + fprintf( stderr, "%.*s\n", ( int )psvText.size( ), psvText.data( ) ); } -void LogScope::logf(enum LogPriority priority, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - this->vlogf(priority, fmt, args); - va_end(args); +void LogScope::logf( enum LogPriority priority, const char *fmt, ... ) +{ + va_list args; + va_start( args, fmt ); + this->vlogf( priority, fmt, args ); + va_end( args ); } -void LogScope::warnf(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - this->vlogf(LOG_WARNING, fmt, args); - va_end(args); +void LogScope::warnf( const char *fmt, ... ) +{ + va_list args; + va_start( args, fmt ); + this->vlogf( LOG_WARNING, fmt, args ); + va_end( args ); } -void LogScope::errorf(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - this->vlogf(LOG_ERROR, fmt, args); - va_end(args); +void LogScope::errorf( const char *fmt, ... ) +{ + va_list args; + va_start( args, fmt ); + this->vlogf( LOG_ERROR, fmt, args ); + va_end( args ); } -void LogScope::infof(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - this->vlogf(LOG_INFO, fmt, args); - va_end(args); +void LogScope::infof( const char *fmt, ... ) +{ + va_list args; + va_start( args, fmt ); + this->vlogf( LOG_INFO, fmt, args ); + va_end( args ); } -void LogScope::debugf(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - this->vlogf(LOG_DEBUG, fmt, args); - va_end(args); +void LogScope::debugf( const char *fmt, ... ) +{ + va_list args; + va_start( args, fmt ); + this->vlogf( LOG_DEBUG, fmt, args ); + va_end( args ); } -void LogScope::errorf_errno(const char *fmt, ...) { - const char *err = strerror(errno); +void LogScope::errorf_errno( const char *fmt, ... ) +{ + const char *err = strerror( errno ); - static char buf[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); + static char buf[ 1024 ]; + va_list args; + va_start( args, fmt ); + vsnprintf( buf, sizeof( buf ), fmt, args ); + va_end( args ); - this->logf(LOG_ERROR, "%s: %s", buf, err); + this->logf( LOG_ERROR, "%s: %s", buf, err ); } diff --git a/src/log.hpp b/src/log.hpp index 8225b0038e..0ce735d95f 100644 --- a/src/log.hpp +++ b/src/log.hpp @@ -3,23 +3,24 @@ #include #include -#include #include +#include #include #ifdef __GNUC__ -#define ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) + #define ATTRIB_PRINTF( start, end ) \ + __attribute__(( format( printf, start, end ) )) #else -#define ATTRIB_PRINTF(start, end) + #define ATTRIB_PRINTF( start, end ) #endif enum LogPriority { - LOG_SILENT, - LOG_ERROR, - LOG_WARNING, - LOG_INFO, - LOG_DEBUG, + LOG_SILENT, + LOG_ERROR, + LOG_WARNING, + LOG_INFO, + LOG_DEBUG, }; struct LogConVar_t; @@ -27,36 +28,45 @@ struct LogConVar_t; class LogScope { public: - LogScope( std::string_view psvName, LogPriority eMaxPriority = LOG_INFO ); - LogScope( std::string_view psvName, std::string_view psvPrefix, LogPriority eMaxPriority = LOG_INFO ); - ~LogScope(); + LogScope( std::string_view psvName, LogPriority eMaxPriority = LOG_INFO ); + LogScope( + std::string_view psvName, + std::string_view psvPrefix, + LogPriority eMaxPriority = LOG_INFO ); + ~LogScope( ); - bool Enabled( LogPriority ePriority ) const; - void SetPriority( LogPriority ePriority ) { m_eMaxPriority = ePriority; } + bool Enabled( LogPriority ePriority ) const; + void SetPriority( LogPriority ePriority ) { m_eMaxPriority = ePriority; } - void vlogf(enum LogPriority priority, const char *fmt, va_list args) ATTRIB_PRINTF(3, 0); - void log(enum LogPriority priority, std::string_view psvText); + void vlogf( enum LogPriority priority, const char *fmt, va_list args ) + ATTRIB_PRINTF( 3, 0 ); + void log( enum LogPriority priority, std::string_view psvText ); - void warnf(const char *fmt, ...) ATTRIB_PRINTF(2, 3); - void errorf(const char *fmt, ...) ATTRIB_PRINTF(2, 3); - void infof(const char *fmt, ...) ATTRIB_PRINTF(2, 3); - void debugf(const char *fmt, ...) ATTRIB_PRINTF(2, 3); + void warnf( const char *fmt, ... ) ATTRIB_PRINTF( 2, 3 ); + void errorf( const char *fmt, ... ) ATTRIB_PRINTF( 2, 3 ); + void infof( const char *fmt, ... ) ATTRIB_PRINTF( 2, 3 ); + void debugf( const char *fmt, ... ) ATTRIB_PRINTF( 2, 3 ); - void errorf_errno(const char *fmt, ...) ATTRIB_PRINTF(2, 3); + void errorf_errno( const char *fmt, ... ) ATTRIB_PRINTF( 2, 3 ); - bool bPrefixEnabled = true; + bool bPrefixEnabled = true; - using LoggingListenerFunc = std::function; - std::unordered_map m_LoggingListeners; + using LoggingListenerFunc = std::function; + std::unordered_map m_LoggingListeners; private: - void vprintf(enum LogPriority priority, const char *fmt, va_list args) ATTRIB_PRINTF(3, 0); - void logf(enum LogPriority priority, const char *fmt, ...) ATTRIB_PRINTF(3, 4); + void vprintf( enum LogPriority priority, const char *fmt, va_list args ) + ATTRIB_PRINTF( 3, 0 ); + void logf( enum LogPriority priority, const char *fmt, ... ) + ATTRIB_PRINTF( 3, 4 ); + + std::string_view m_psvName; + std::string_view m_psvPrefix; - std::string_view m_psvName; - std::string_view m_psvPrefix; + LogPriority m_eMaxPriority = LOG_INFO; - LogPriority m_eMaxPriority = LOG_INFO; - - std::unique_ptr m_pEnableConVar; + std::unique_ptr m_pEnableConVar; }; diff --git a/src/main.cpp b/src/main.cpp index 446dacb81b..6d2e270fcb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,41 +3,41 @@ #include #include +#include #include -#include #include -#include -#include #include +#include +#include #if HAVE_LIBCAP -#include + #include #endif +#include #include #include -#include +#include +#include #include #include #include -#include -#include +#include "Utils/Defer.h" +#include "Utils/Process.h" +#include "Utils/TempFiles.h" +#include "Utils/Version.h" +#include "convar.h" +#include "gpuvis_trace_utils.h" #include "main.hpp" -#include "steamcompmgr.hpp" #include "rendervulkan.hpp" +#include "steamcompmgr.hpp" #include "wlserver.hpp" -#include "convar.h" -#include "gpuvis_trace_utils.h" -#include "Utils/TempFiles.h" -#include "Utils/Version.h" -#include "Utils/Process.h" -#include "Utils/Defer.h" #include "backends.h" #include "refresh_rate.h" #if HAVE_PIPEWIRE -#include "pipewire.hpp" + #include "pipewire.hpp" #endif #include @@ -48,259 +48,319 @@ EStreamColorspace g_ForcedNV12ColorSpace = k_EStreamColorspace_Unknown; extern gamescope::ConVar cv_adaptive_sync; extern gamescope::ConVar cv_shutdown_on_primary_child_death; -const char *gamescope_optstring = nullptr; -const char *g_pOriginalDisplay = nullptr; +const char *gamescope_optstring = nullptr; +const char *g_pOriginalDisplay = nullptr; const char *g_pOriginalWaylandDisplay = nullptr; bool g_bAllowDeferredBackend = false; int g_nCursorScaleHeight = -1; -const struct option *gamescope_options = (struct option[]){ - { "help", no_argument, nullptr, 0 }, - { "version", no_argument, nullptr, 0 }, - { "nested-width", required_argument, nullptr, 'w' }, - { "nested-height", required_argument, nullptr, 'h' }, - { "nested-refresh", required_argument, nullptr, 'r' }, - { "max-scale", required_argument, nullptr, 'm' }, - { "scaler", required_argument, nullptr, 'S' }, - { "filter", required_argument, nullptr, 'F' }, - { "output-width", required_argument, nullptr, 'W' }, - { "output-height", required_argument, nullptr, 'H' }, - { "sharpness", required_argument, nullptr, 0 }, - { "fsr-sharpness", required_argument, nullptr, 0 }, - { "rt", no_argument, nullptr, 0 }, - { "prefer-vk-device", required_argument, 0 }, - { "expose-wayland", no_argument, 0 }, - { "mouse-sensitivity", required_argument, nullptr, 's' }, - { "mangoapp", no_argument, nullptr, 0 }, - { "adaptive-sync", no_argument, nullptr, 0 }, - - { "backend", required_argument, nullptr, 0 }, - - // nested mode options - { "nested-unfocused-refresh", required_argument, nullptr, 'o' }, - { "borderless", no_argument, nullptr, 'b' }, - { "fullscreen", no_argument, nullptr, 'f' }, - { "grab", no_argument, nullptr, 'g' }, - { "force-grab-cursor", no_argument, nullptr, 0 }, - { "display-index", required_argument, nullptr, 0 }, - - // embedded mode options - { "disable-layers", no_argument, nullptr, 0 }, - { "debug-layers", no_argument, nullptr, 0 }, - { "prefer-output", required_argument, nullptr, 'O' }, - { "default-touch-mode", required_argument, nullptr, 0 }, - { "generate-drm-mode", required_argument, nullptr, 0 }, - { "immediate-flips", no_argument, nullptr, 0 }, - { "framerate-limit", required_argument, nullptr, 0 }, - - // openvr options +const struct option *gamescope_options = ( struct option[] ){ + { "help", no_argument, nullptr, 0 }, + { "version", no_argument, nullptr, 0 }, + { "nested-width", required_argument, nullptr, 'w' }, + { "nested-height", required_argument, nullptr, 'h' }, + { "nested-refresh", required_argument, nullptr, 'r' }, + { "max-scale", required_argument, nullptr, 'm' }, + { "scaler", required_argument, nullptr, 'S' }, + { "filter", required_argument, nullptr, 'F' }, + { "output-width", required_argument, nullptr, 'W' }, + { "output-height", required_argument, nullptr, 'H' }, + { "sharpness", required_argument, nullptr, 0 }, + { "fsr-sharpness", required_argument, nullptr, 0 }, + { "rt", no_argument, nullptr, 0 }, + { "prefer-vk-device", required_argument, 0 }, + { "expose-wayland", no_argument, 0 }, + { "mouse-sensitivity", required_argument, nullptr, 's' }, + { "mangoapp", no_argument, nullptr, 0 }, + { "adaptive-sync", no_argument, nullptr, 0 }, + + { "backend", required_argument, nullptr, 0 }, + + // nested mode options + { "nested-unfocused-refresh", required_argument, nullptr, 'o' }, + { "borderless", no_argument, nullptr, 'b' }, + { "fullscreen", no_argument, nullptr, 'f' }, + { "grab", no_argument, nullptr, 'g' }, + { "force-grab-cursor", no_argument, nullptr, 0 }, + { "display-index", required_argument, nullptr, 0 }, + + // embedded mode options + { "disable-layers", no_argument, nullptr, 0 }, + { "debug-layers", no_argument, nullptr, 0 }, + { "prefer-output", required_argument, nullptr, 'O' }, + { "default-touch-mode", required_argument, nullptr, 0 }, + { "generate-drm-mode", required_argument, nullptr, 0 }, + { "immediate-flips", no_argument, nullptr, 0 }, + { "framerate-limit", required_argument, nullptr, 0 }, + +// openvr options #if HAVE_OPENVR - { "vr-overlay-key", required_argument, nullptr, 0 }, - { "vr-app-overlay-key", required_argument, nullptr, 0 }, - { "vr-overlay-explicit-name", required_argument, nullptr, 0 }, - { "vr-overlay-default-name", required_argument, nullptr, 0 }, - { "vr-overlay-icon", required_argument, nullptr, 0 }, - { "vr-overlay-show-immediately", no_argument, nullptr, 0 }, - { "vr-overlay-enable-control-bar", no_argument, nullptr, 0 }, - { "vr-overlay-enable-control-bar-keyboard", no_argument, nullptr, 0 }, - { "vr-overlay-enable-control-bar-close", no_argument, nullptr, 0 }, - { "vr-overlay-enable-click-stabilization", no_argument, nullptr, 0 }, - { "vr-overlay-modal", no_argument, nullptr, 0 }, - { "vr-overlay-physical-width", required_argument, nullptr, 0 }, - { "vr-overlay-physical-curvature", required_argument, nullptr, 0 }, - { "vr-overlay-physical-pre-curve-pitch", required_argument, nullptr, 0 }, - { "vr-scroll-speed", required_argument, nullptr, 0 }, - { "vr-session-manager", no_argument, nullptr, 0 }, + { "vr-overlay-key", required_argument, nullptr, 0 }, + { "vr-app-overlay-key", required_argument, nullptr, 0 }, + { "vr-overlay-explicit-name", required_argument, nullptr, 0 }, + { "vr-overlay-default-name", required_argument, nullptr, 0 }, + { "vr-overlay-icon", required_argument, nullptr, 0 }, + { "vr-overlay-show-immediately", no_argument, nullptr, 0 }, + { "vr-overlay-enable-control-bar", no_argument, nullptr, 0 }, + { "vr-overlay-enable-control-bar-keyboard", no_argument, nullptr, 0 }, + { "vr-overlay-enable-control-bar-close", no_argument, nullptr, 0 }, + { "vr-overlay-enable-click-stabilization", no_argument, nullptr, 0 }, + { "vr-overlay-modal", no_argument, nullptr, 0 }, + { "vr-overlay-physical-width", required_argument, nullptr, 0 }, + { "vr-overlay-physical-curvature", required_argument, nullptr, 0 }, + { "vr-overlay-physical-pre-curve-pitch", required_argument, nullptr, 0 }, + { "vr-scroll-speed", required_argument, nullptr, 0 }, + { "vr-session-manager", no_argument, nullptr, 0 }, #endif - // wlserver options - { "xwayland-count", required_argument, nullptr, 0 }, - - // steamcompmgr options - { "cursor", required_argument, nullptr, 0 }, - { "cursor-hotspot", required_argument, nullptr, 0 }, - { "cursor-scale-height", required_argument, nullptr, 0 }, - { "virtual-connector-strategy", required_argument, nullptr, 0 }, - { "ready-fd", required_argument, nullptr, 'R' }, - { "stats-path", required_argument, nullptr, 'T' }, - { "hide-cursor-delay", required_argument, nullptr, 'C' }, - { "debug-focus", no_argument, nullptr, 0 }, - { "synchronous-x11", no_argument, nullptr, 0 }, - { "debug-hud", no_argument, nullptr, 'v' }, - { "debug-events", no_argument, nullptr, 0 }, - { "steam", no_argument, nullptr, 'e' }, - { "force-composition", no_argument, nullptr, 'c' }, - { "composite-debug", no_argument, nullptr, 0 }, - { "disable-xres", no_argument, nullptr, 'x' }, - { "fade-out-duration", required_argument, nullptr, 0 }, - { "force-orientation", required_argument, nullptr, 0 }, - { "force-windows-fullscreen", no_argument, nullptr, 0 }, - - { "disable-color-management", no_argument, nullptr, 0 }, - { "sdr-gamut-wideness", required_argument, nullptr, 0 }, - { "hdr-enabled", no_argument, nullptr, 0 }, - { "hdr-sdr-content-nits", required_argument, nullptr, 0 }, - { "hdr-itm-enabled", no_argument, nullptr, 0 }, - { "hdr-itm-sdr-nits", required_argument, nullptr, 0 }, - { "hdr-itm-target-nits", required_argument, nullptr, 0 }, - { "hdr-debug-force-support", no_argument, nullptr, 0 }, - { "hdr-debug-force-output", no_argument, nullptr, 0 }, - { "hdr-debug-heatmap", no_argument, nullptr, 0 }, - - { "reshade-effect", required_argument, nullptr, 0 }, - { "reshade-technique-idx", required_argument, nullptr, 0 }, - - // Steam Deck options - { "mura-map", required_argument, nullptr, 0 }, - - { "allow-deferred-backend", no_argument, nullptr, 0 }, - { "keep-alive", no_argument, nullptr, 0 }, - - {} // keep last + // wlserver options + { "xwayland-count", required_argument, nullptr, 0 }, + + // steamcompmgr options + { "cursor", required_argument, nullptr, 0 }, + { "cursor-hotspot", required_argument, nullptr, 0 }, + { "cursor-scale-height", required_argument, nullptr, 0 }, + { "virtual-connector-strategy", required_argument, nullptr, 0 }, + { "ready-fd", required_argument, nullptr, 'R' }, + { "stats-path", required_argument, nullptr, 'T' }, + { "hide-cursor-delay", required_argument, nullptr, 'C' }, + { "debug-focus", no_argument, nullptr, 0 }, + { "synchronous-x11", no_argument, nullptr, 0 }, + { "debug-hud", no_argument, nullptr, 'v' }, + { "debug-events", no_argument, nullptr, 0 }, + { "steam", no_argument, nullptr, 'e' }, + { "force-composition", no_argument, nullptr, 'c' }, + { "composite-debug", no_argument, nullptr, 0 }, + { "disable-xres", no_argument, nullptr, 'x' }, + { "fade-out-duration", required_argument, nullptr, 0 }, + { "force-orientation", required_argument, nullptr, 0 }, + { "force-windows-fullscreen", no_argument, nullptr, 0 }, + + { "disable-color-management", no_argument, nullptr, 0 }, + { "sdr-gamut-wideness", required_argument, nullptr, 0 }, + { "hdr-enabled", no_argument, nullptr, 0 }, + { "hdr-sdr-content-nits", required_argument, nullptr, 0 }, + { "hdr-itm-enabled", no_argument, nullptr, 0 }, + { "hdr-itm-sdr-nits", required_argument, nullptr, 0 }, + { "hdr-itm-target-nits", required_argument, nullptr, 0 }, + { "hdr-debug-force-support", no_argument, nullptr, 0 }, + { "hdr-debug-force-output", no_argument, nullptr, 0 }, + { "hdr-debug-heatmap", no_argument, nullptr, 0 }, + + { "reshade-effect", required_argument, nullptr, 0 }, + { "reshade-technique-idx", required_argument, nullptr, 0 }, + + // Steam Deck options + { "mura-map", required_argument, nullptr, 0 }, + + { "allow-deferred-backend", no_argument, nullptr, 0 }, + { "keep-alive", no_argument, nullptr, 0 }, + + {} // keep last }; const char usage[] = - "usage: gamescope [options...] -- [command...]\n" - "\n" - "Options:\n" - " --help show help message\n" - " -W, --output-width output width\n" - " -H, --output-height output height\n" - " -w, --nested-width game width\n" - " -h, --nested-height game height\n" - " -r, --nested-refresh game refresh rate (frames per second)\n" - " -m, --max-scale maximum scale factor\n" - " -S, --scaler upscaler type (auto, integer, fit, fill, stretch)\n" - " -F, --filter upscaler filter (linear, nearest, fsr, nis, pixel)\n" - " fsr => AMD FidelityFXâ„¢ Super Resolution 1.0\n" - " nis => NVIDIA Image Scaling v1.0.3\n" - " --sharpness, --fsr-sharpness upscaler sharpness from 0 (max) to 20 (min)\n" - " --expose-wayland support wayland clients using xdg-shell\n" - " -s, --mouse-sensitivity multiply mouse movement by given decimal number\n" - " --backend select rendering backend\n" - " auto => autodetect (default)\n" + "usage: gamescope [options...] -- [command...]\n" + "\n" + "Options:\n" + " --help show help message\n" + " -W, --output-width output width\n" + " -H, --output-height output height\n" + " -w, --nested-width game width\n" + " -h, --nested-height game height\n" + " -r, --nested-refresh game refresh rate (frames per second)\n" + " -m, --max-scale maximum scale factor\n" + " -S, --scaler upscaler type (auto, integer, fit, fill, " + "stretch)\n" + " -F, --filter upscaler filter (linear, nearest, fsr, " + "nis, pixel)\n" + " fsr => AMD FidelityFXâ„¢ Super " + "Resolution 1.0\n" + " nis => NVIDIA Image Scaling v1.0.3\n" + " --sharpness, --fsr-sharpness upscaler sharpness from 0 (max) to 20 " + "(min)\n" + " --expose-wayland support wayland clients using xdg-shell\n" + " -s, --mouse-sensitivity multiply mouse movement by given decimal " + "number\n" + " --backend select rendering backend\n" + " auto => autodetect (default)\n" #if HAVE_DRM - " drm => use DRM backend (standalone display session)\n" + " drm => use DRM backend (standalone " + "display session)\n" #endif #if HAVE_SDL2 - " sdl => use SDL backend\n" + " sdl => use SDL backend\n" #endif #if HAVE_OPENVR - " openvr => use OpenVR backend (outputs as a VR overlay)\n" + " openvr => use OpenVR backend " + "(outputs as a VR overlay)\n" #endif - " headless => use headless backend (no window, no DRM output)\n" - " wayland => use Wayland backend\n" - " --cursor path to default cursor image\n" - " -R, --ready-fd notify FD when ready\n" - " --rt Use realtime scheduling\n" - " -T, --stats-path write statistics to path\n" - " -C, --hide-cursor-delay hide cursor image after delay\n" - " -e, --steam enable Steam integration\n" - " --xwayland-count create N xwayland servers\n" - " --prefer-vk-device prefer Vulkan device for compositing (ex: 1002:7300)\n" - " --force-orientation rotate the internal display (left, right, normal, upsidedown)\n" - " --force-windows-fullscreen force windows inside of gamescope to be the size of the nested display (fullscreen)\n" - " --cursor-scale-height if specified, sets a base output height to linearly scale the cursor against.\n" - " --virtual-connector-strategy Specifies how we should make virtual connectors.\n" - " --hdr-enabled enable HDR output (needs Gamescope WSI layer enabled for support from clients)\n" - " If this is not set, and there is a HDR client, it will be tonemapped SDR.\n" - " --sdr-gamut-wideness Set the 'wideness' of the gamut for SDR comment. 0 - 1.\n" - " --hdr-sdr-content-nits set the luminance of SDR content in nits. Default: 400 nits.\n" - " --hdr-itm-enabled enable SDR->HDR inverse tone mapping. only works for SDR input.\n" - " --hdr-itm-sdr-nits set the luminance of SDR content in nits used as the input for the inverse tone mapping process.\n" - " Default: 100 nits, Max: 1000 nits\n" - " --hdr-itm-target-nits set the target luminace of the inverse tone mapping process.\n" - " Default: 1000 nits, Max: 10000 nits\n" - " --framerate-limit Set a simple framerate limit. Used as a divisor of the refresh rate, rounds down eg 60 / 59 -> 60fps, 60 / 25 -> 30fps. Default: 0, disabled.\n" - " --mangoapp Launch with the mangoapp (mangohud) performance overlay enabled. You should use this instead of using mangohud on the game or gamescope.\n" - " --adaptive-sync Enable adaptive sync if available (variable rate refresh)\n" - "\n" - "Nested mode options:\n" - " -o, --nested-unfocused-refresh game refresh rate when unfocused\n" - " -b, --borderless make the window borderless\n" - " -f, --fullscreen make the window fullscreen\n" - " -g, --grab grab the keyboard\n" - " --force-grab-cursor always use relative mouse mode instead of flipping dependent on cursor visibility.\n" - " --display-index forces gamescope to use a specific display in nested mode." - "\n" - "Embedded mode options:\n" - " -O, --prefer-output list of connectors in order of preference (ex: DP-1,DP-2,DP-3,HDMI-A-1)\n" - " --default-touch-mode 0: hover, 1: left, 2: right, 3: middle, 4: passthrough\n" - " --generate-drm-mode DRM mode generation algorithm (cvt, fixed)\n" - " --immediate-flips Enable immediate flips, may result in tearing\n" - "\n" + " headless => use headless backend (no " + "window, no DRM output)\n" + " wayland => use Wayland backend\n" + " --cursor path to default cursor image\n" + " -R, --ready-fd notify FD when ready\n" + " --rt Use realtime scheduling\n" + " -T, --stats-path write statistics to path\n" + " -C, --hide-cursor-delay hide cursor image after delay\n" + " -e, --steam enable Steam integration\n" + " --xwayland-count create N xwayland servers\n" + " --prefer-vk-device prefer Vulkan device for compositing " + "(ex: 1002:7300)\n" + " --force-orientation rotate the internal display (left, " + "right, normal, upsidedown)\n" + " --force-windows-fullscreen force windows inside of gamescope to be " + "the size of the nested display (fullscreen)\n" + " --cursor-scale-height if specified, sets a base output height " + "to linearly scale the cursor against.\n" + " --virtual-connector-strategy Specifies how we should make virtual " + "connectors.\n" + " --hdr-enabled enable HDR output (needs Gamescope WSI " + "layer enabled for support from clients)\n" + " If this is not set, and there is a HDR " + "client, it will be tonemapped SDR.\n" + " --sdr-gamut-wideness Set the 'wideness' of the gamut for SDR " + "comment. 0 - 1.\n" + " --hdr-sdr-content-nits set the luminance of SDR content in " + "nits. Default: 400 nits.\n" + " --hdr-itm-enabled enable SDR->HDR inverse tone mapping. " + "only works for SDR input.\n" + " --hdr-itm-sdr-nits set the luminance of SDR content in nits " + "used as the input for the inverse tone mapping process.\n" + " Default: 100 nits, Max: 1000 nits\n" + " --hdr-itm-target-nits set the target luminace of the inverse " + "tone mapping process.\n" + " Default: 1000 nits, Max: 10000 nits\n" + " --framerate-limit Set a simple framerate limit. Used as a " + "divisor of the refresh rate, rounds down eg 60 / 59 -> 60fps, 60 / 25 -> " + "30fps. Default: 0, disabled.\n" + " --mangoapp Launch with the mangoapp (mangohud) " + "performance overlay enabled. You should use this instead of using " + "mangohud on the game or gamescope.\n" + " --adaptive-sync Enable adaptive sync if available " + "(variable rate refresh)\n" + "\n" + "Nested mode options:\n" + " -o, --nested-unfocused-refresh game refresh rate when unfocused\n" + " -b, --borderless make the window borderless\n" + " -f, --fullscreen make the window fullscreen\n" + " -g, --grab grab the keyboard\n" + " --force-grab-cursor always use relative mouse mode instead " + "of flipping dependent on cursor visibility.\n" + " --display-index forces gamescope to use a specific " + "display in nested mode." + "\n" + "Embedded mode options:\n" + " -O, --prefer-output list of connectors in order of " + "preference (ex: DP-1,DP-2,DP-3,HDMI-A-1)\n" + " --default-touch-mode 0: hover, 1: left, 2: right, 3: middle, " + "4: passthrough\n" + " --generate-drm-mode DRM mode generation algorithm (cvt, " + "fixed)\n" + " --immediate-flips Enable immediate flips, may result in " + "tearing\n" + "\n" #if HAVE_OPENVR - "VR mode options:\n" - " --vr-overlay-key Sets the SteamVR overlay key to this string\n" - " --vr-app-overlay-key Sets the SteamVR overlay key to use for child apps\n" - " --vr-overlay-explicit-name Force the SteamVR overlay name to always be this string\n" - " --vr-overlay-default-name Sets the fallback SteamVR overlay name when there is no window title\n" - " --vr-overlay-icon Sets the SteamVR overlay icon to this file\n" - " --vr-overlay-show-immediately Makes our VR overlay take focus immediately\n" - " --vr-overlay-enable-control-bar Enables the SteamVR control bar\n" - " --vr-overlay-enable-control-bar-keyboard Enables the SteamVR keyboard button on the control bar\n" - " --vr-overlay-enable-control-bar-close Enables the SteamVR close button on the control bar\n" - " --vr-overlay-enable-click-stabilization Enables the SteamVR click stabilization\n" - " --vr-overlay-modal Makes our VR overlay appear as a modal\n" - " --vr-overlay-physical-width Sets the physical width of our VR overlay in metres\n" - " --vr-overlay-physical-curvature Sets the curvature of our VR overlay\n" - " --vr-overlay-physical-pre-curve-pitch Sets the pre-curve pitch of our VR overlay\n" - " --vr-scrolls-speed Mouse scrolling speed of trackpad scroll in VR. Default: 8.0\n" - "\n" + "VR mode options:\n" + " --vr-overlay-key Sets the SteamVR overlay key " + "to this string\n" + " --vr-app-overlay-key Sets the SteamVR overlay key " + "to use for child apps\n" + " --vr-overlay-explicit-name Force the SteamVR overlay name " + "to always be this string\n" + " --vr-overlay-default-name Sets the fallback SteamVR " + "overlay name when there is no window title\n" + " --vr-overlay-icon Sets the SteamVR overlay icon " + "to this file\n" + " --vr-overlay-show-immediately Makes our VR overlay take " + "focus immediately\n" + " --vr-overlay-enable-control-bar Enables the SteamVR control " + "bar\n" + " --vr-overlay-enable-control-bar-keyboard Enables the SteamVR keyboard " + "button on the control bar\n" + " --vr-overlay-enable-control-bar-close Enables the SteamVR close " + "button on the control bar\n" + " --vr-overlay-enable-click-stabilization Enables the SteamVR click " + "stabilization\n" + " --vr-overlay-modal Makes our VR overlay appear as " + "a modal\n" + " --vr-overlay-physical-width Sets the physical width of our " + "VR overlay in metres\n" + " --vr-overlay-physical-curvature Sets the curvature of our VR " + "overlay\n" + " --vr-overlay-physical-pre-curve-pitch Sets the pre-curve pitch of " + "our VR overlay\n" + " --vr-scrolls-speed Mouse scrolling speed of " + "trackpad scroll in VR. Default: 8.0\n" + "\n" #endif - "Debug options:\n" - " --disable-layers disable libliftoff (hardware planes)\n" - " --debug-layers debug libliftoff\n" - " --debug-focus debug XWM focus\n" - " --synchronous-x11 force X11 connection synchronization\n" - " --debug-hud paint HUD with debug info\n" - " --debug-events debug X11 events\n" - " --force-composition disable direct scan-out\n" - " --composite-debug draw frame markers on alternating corners of the screen when compositing\n" - " --disable-color-management disable color management\n" - " --disable-xres disable XRes for PID lookup\n" - " --hdr-debug-force-support forces support for HDR, etc even if the display doesn't support it. HDR clients will be outputted as SDR still in that case.\n" - " --hdr-debug-force-output forces support and output to HDR10 PQ even if the output does not support it (will look very wrong if it doesn't)\n" - " --hdr-debug-heatmap displays a heatmap-style debug view of HDR luminence across the scene in nits." - "\n" - "Reshade shader options:\n" - " --reshade-effect sets the name of a reshade shader to use in either /usr/share/gamescope/reshade/Shaders or ~/.local/share/gamescope/reshade/Shaders\n" - " --reshade-technique-idx sets technique idx to use from the reshade effect\n" - "\n" - "Steam Deck options:\n" - " --mura-map Set the mura compensation map to use for the display. Takes in a path to the mura map.\n" - "\n" - "Platform options:\n" - " --allow-deferred-backend Allows initting the backend in a deferred way, if it doesn't work immediately. (Note: This has some very minor correctness compromises that you should consider wrt. your platform with modifiers, etc).\n" - " --keep-alive Keep Gamescope alive even when the primary process has died.\n" - "\n" - "Keyboard shortcuts:\n" - " Super + F toggle fullscreen\n" - " Super + N toggle nearest neighbour filtering\n" - " Super + U toggle FSR upscaling\n" - " Super + Y toggle NIS upscaling\n" - " Super + I increase FSR sharpness by 1\n" - " Super + O decrease FSR sharpness by 1\n" - " Super + S take a screenshot\n" - " Super + G toggle keyboard grab\n" - ""; - -std::atomic< bool > g_bRun{true}; - -int g_nNestedWidth = 0; -int g_nNestedHeight = 0; -int g_nNestedRefresh = 0; + "Debug options:\n" + " --disable-layers disable libliftoff (hardware planes)\n" + " --debug-layers debug libliftoff\n" + " --debug-focus debug XWM focus\n" + " --synchronous-x11 force X11 connection synchronization\n" + " --debug-hud paint HUD with debug info\n" + " --debug-events debug X11 events\n" + " --force-composition disable direct scan-out\n" + " --composite-debug draw frame markers on alternating " + "corners of the screen when compositing\n" + " --disable-color-management disable color management\n" + " --disable-xres disable XRes for PID lookup\n" + " --hdr-debug-force-support forces support for HDR, etc even if the " + "display doesn't support it. HDR clients will be outputted as SDR still in " + "that case.\n" + " --hdr-debug-force-output forces support and output to HDR10 PQ " + "even if the output does not support it (will look very wrong if it " + "doesn't)\n" + " --hdr-debug-heatmap displays a heatmap-style debug view of " + "HDR luminence across the scene in nits." + "\n" + "Reshade shader options:\n" + " --reshade-effect sets the name of a reshade shader to use " + "in either /usr/share/gamescope/reshade/Shaders or " + "~/.local/share/gamescope/reshade/Shaders\n" + " --reshade-technique-idx sets technique idx to use from the " + "reshade effect\n" + "\n" + "Steam Deck options:\n" + " --mura-map Set the mura compensation map to use for " + "the display. Takes in a path to the mura map.\n" + "\n" + "Platform options:\n" + " --allow-deferred-backend Allows initting the backend in a " + "deferred way, if it doesn't work immediately. (Note: This has some very " + "minor correctness compromises that you should consider wrt. your platform " + "with modifiers, etc).\n" + " --keep-alive Keep Gamescope alive even when the " + "primary process has died.\n" + "\n" + "Keyboard shortcuts:\n" + " Super + F toggle fullscreen\n" + " Super + N toggle nearest neighbour filtering\n" + " Super + U toggle FSR upscaling\n" + " Super + Y toggle NIS upscaling\n" + " Super + I increase FSR sharpness by 1\n" + " Super + O decrease FSR sharpness by 1\n" + " Super + S take a screenshot\n" + " Super + G toggle keyboard grab\n" + ""; + +std::atomic g_bRun{ true }; + +int g_nNestedWidth = 0; +int g_nNestedHeight = 0; +int g_nNestedRefresh = 0; int g_nNestedUnfocusedRefresh = 0; -int g_nNestedDisplayIndex = 0; +int g_nNestedDisplayIndex = 0; -uint32_t g_nOutputWidth = 0; -uint32_t g_nOutputHeight = 0; -int g_nOutputRefresh = 0; -bool g_bOutputHDREnabled = false; +uint32_t g_nOutputWidth = 0; +uint32_t g_nOutputHeight = 0; +int g_nOutputRefresh = 0; +bool g_bOutputHDREnabled = false; -bool g_bFullscreen = false; +bool g_bFullscreen = false; bool g_bForceRelativeMouse = false; bool g_bGrabbed = false; @@ -312,9 +372,10 @@ GamescopeUpscaleScaler g_upscaleScaler = GamescopeUpscaleScaler::AUTO; GamescopeUpscaleFilter g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; GamescopeUpscaleScaler g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; -int g_upscaleFilterSharpness = 2; +int g_upscaleFilterSharpness = 2; -gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; +gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = + gamescope::GAMESCOPE_MODE_GENERATE_CVT; bool g_bBorderlessOutputWindow = false; @@ -327,234 +388,286 @@ uint32_t g_preferDeviceID = 0; pthread_t g_mainThread; -static void steamCompMgrThreadRun(int argc, char **argv); +static void steamCompMgrThreadRun( int argc, char **argv ); -static std::string build_optstring(const struct option *options) +static std::string build_optstring( const struct option *options ) { - std::string optstring; - for (size_t i = 0; options[i].name != nullptr; i++) { - if (!options[i].name || !options[i].val) - continue; + std::string optstring; + for ( size_t i = 0; options[ i ].name != nullptr; i++ ) + { + if ( !options[ i ].name || !options[ i ].val ) continue; - assert(optstring.find((char) options[i].val) == std::string::npos); + assert( + optstring.find( ( char )options[ i ].val ) == std::string::npos ); - char str[] = { (char) options[i].val, '\0' }; - optstring.append(str); + char str[] = { ( char )options[ i ].val, '\0' }; + optstring.append( str ); - if (options[i].has_arg) - optstring.append(":"); - } - return optstring; + if ( options[ i ].has_arg ) optstring.append( ":" ); + } + return optstring; } -static gamescope::GamescopeModeGeneration parse_gamescope_mode_generation( const char *str ) +static gamescope::GamescopeModeGeneration +parse_gamescope_mode_generation( const char *str ) { - if ( str == "cvt"sv ) - { - return gamescope::GAMESCOPE_MODE_GENERATE_CVT; - } - else if ( str == "fixed"sv ) - { - return gamescope::GAMESCOPE_MODE_GENERATE_FIXED; - } - else - { - fprintf( stderr, "gamescope: invalid value for --generate-drm-mode\n" ); - exit(1); - } + if ( str == "cvt"sv ) { return gamescope::GAMESCOPE_MODE_GENERATE_CVT; } + else if ( str == "fixed"sv ) + { + return gamescope::GAMESCOPE_MODE_GENERATE_FIXED; + } + else + { + fprintf( stderr, "gamescope: invalid value for --generate-drm-mode\n" ); + exit( 1 ); + } } -GamescopePanelOrientation g_DesiredInternalOrientation = GAMESCOPE_PANEL_ORIENTATION_AUTO; -static GamescopePanelOrientation force_orientation(const char *str) +GamescopePanelOrientation g_DesiredInternalOrientation = + GAMESCOPE_PANEL_ORIENTATION_AUTO; +static GamescopePanelOrientation force_orientation( const char *str ) { - if (strcmp(str, "normal") == 0) { - return GAMESCOPE_PANEL_ORIENTATION_0; - } else if (strcmp(str, "right") == 0) { - return GAMESCOPE_PANEL_ORIENTATION_270; - } else if (strcmp(str, "left") == 0) { - return GAMESCOPE_PANEL_ORIENTATION_90; - } else if (strcmp(str, "upsidedown") == 0) { - return GAMESCOPE_PANEL_ORIENTATION_180; - } else { - fprintf( stderr, "gamescope: invalid value for --force-orientation\n" ); - exit(1); - } + if ( strcmp( str, "normal" ) == 0 ) + { + return GAMESCOPE_PANEL_ORIENTATION_0; + } + else if ( strcmp( str, "right" ) == 0 ) + { + return GAMESCOPE_PANEL_ORIENTATION_270; + } + else if ( strcmp( str, "left" ) == 0 ) + { + return GAMESCOPE_PANEL_ORIENTATION_90; + } + else if ( strcmp( str, "upsidedown" ) == 0 ) + { + return GAMESCOPE_PANEL_ORIENTATION_180; + } + else + { + fprintf( stderr, "gamescope: invalid value for --force-orientation\n" ); + exit( 1 ); + } } -static enum GamescopeUpscaleScaler parse_upscaler_scaler(const char *str) +static enum GamescopeUpscaleScaler parse_upscaler_scaler( const char *str ) { - if (strcmp(str, "auto") == 0) { - return GamescopeUpscaleScaler::AUTO; - } else if (strcmp(str, "integer") == 0) { - return GamescopeUpscaleScaler::INTEGER; - } else if (strcmp(str, "fit") == 0) { - return GamescopeUpscaleScaler::FIT; - } else if (strcmp(str, "fill") == 0) { - return GamescopeUpscaleScaler::FILL; - } else if (strcmp(str, "stretch") == 0) { - return GamescopeUpscaleScaler::STRETCH; - } else { - fprintf( stderr, "gamescope: invalid value for --scaler\n" ); - exit(1); - } + if ( strcmp( str, "auto" ) == 0 ) { return GamescopeUpscaleScaler::AUTO; } + else if ( strcmp( str, "integer" ) == 0 ) + { + return GamescopeUpscaleScaler::INTEGER; + } + else if ( strcmp( str, "fit" ) == 0 ) + { + return GamescopeUpscaleScaler::FIT; + } + else if ( strcmp( str, "fill" ) == 0 ) + { + return GamescopeUpscaleScaler::FILL; + } + else if ( strcmp( str, "stretch" ) == 0 ) + { + return GamescopeUpscaleScaler::STRETCH; + } + else + { + fprintf( stderr, "gamescope: invalid value for --scaler\n" ); + exit( 1 ); + } } -static enum GamescopeUpscaleFilter parse_upscaler_filter(const char *str) +static enum GamescopeUpscaleFilter parse_upscaler_filter( const char *str ) { - if (strcmp(str, "linear") == 0) { - return GamescopeUpscaleFilter::LINEAR; - } else if (strcmp(str, "nearest") == 0) { - return GamescopeUpscaleFilter::NEAREST; - } else if (strcmp(str, "fsr") == 0) { - return GamescopeUpscaleFilter::FSR; - } else if (strcmp(str, "nis") == 0) { - return GamescopeUpscaleFilter::NIS; - } else if (strcmp(str, "pixel") == 0) { - return GamescopeUpscaleFilter::PIXEL; - } else { - fprintf( stderr, "gamescope: invalid value for --filter\n" ); - exit(1); - } + if ( strcmp( str, "linear" ) == 0 ) + { + return GamescopeUpscaleFilter::LINEAR; + } + else if ( strcmp( str, "nearest" ) == 0 ) + { + return GamescopeUpscaleFilter::NEAREST; + } + else if ( strcmp( str, "fsr" ) == 0 ) + { + return GamescopeUpscaleFilter::FSR; + } + else if ( strcmp( str, "nis" ) == 0 ) + { + return GamescopeUpscaleFilter::NIS; + } + else if ( strcmp( str, "pixel" ) == 0 ) + { + return GamescopeUpscaleFilter::PIXEL; + } + else + { + fprintf( stderr, "gamescope: invalid value for --filter\n" ); + exit( 1 ); + } } -static enum gamescope::GamescopeBackend parse_backend_name(const char *str) +static enum gamescope::GamescopeBackend parse_backend_name( const char *str ) { - if (strcmp(str, "auto") == 0) { - return gamescope::GamescopeBackend::Auto; + if ( strcmp( str, "auto" ) == 0 ) + { + return gamescope::GamescopeBackend::Auto; #if HAVE_DRM - } else if (strcmp(str, "drm") == 0) { - return gamescope::GamescopeBackend::DRM; + } + else if ( strcmp( str, "drm" ) == 0 ) + { + return gamescope::GamescopeBackend::DRM; #endif #if HAVE_SDL2 - } else if (strcmp(str, "sdl") == 0) { - return gamescope::GamescopeBackend::SDL; + } + else if ( strcmp( str, "sdl" ) == 0 ) + { + return gamescope::GamescopeBackend::SDL; #endif #if HAVE_OPENVR - } else if (strcmp(str, "openvr") == 0) { - return gamescope::GamescopeBackend::OpenVR; + } + else if ( strcmp( str, "openvr" ) == 0 ) + { + return gamescope::GamescopeBackend::OpenVR; #endif - } else if (strcmp(str, "headless") == 0) { - return gamescope::GamescopeBackend::Headless; - } else if (strcmp(str, "wayland") == 0) { - return gamescope::GamescopeBackend::Wayland; - } else { - fprintf( stderr, "gamescope: invalid value for --backend\n" ); - exit(1); - } + } + else if ( strcmp( str, "headless" ) == 0 ) + { + return gamescope::GamescopeBackend::Headless; + } + else if ( strcmp( str, "wayland" ) == 0 ) + { + return gamescope::GamescopeBackend::Wayland; + } + else + { + fprintf( stderr, "gamescope: invalid value for --backend\n" ); + exit( 1 ); + } } -static int parse_integer(const char *str, const char *optionName) +static int parse_integer( const char *str, const char *optionName ) { - auto result = gamescope::Parse(str); - if ( result.has_value() ) - { - return result.value(); - } - else - { - fprintf( stderr, "gamescope: invalid value for --%s, \"%s\" is either not an integer or is far too large\n", optionName, str ); - exit(1); - } + auto result = gamescope::Parse( str ); + if ( result.has_value( ) ) { return result.value( ); } + else + { + fprintf( + stderr, + "gamescope: invalid value for --%s, \"%s\" is either not an " + "integer or is far too large\n", + optionName, + str ); + exit( 1 ); + } } -static float parse_float(const char *str, const char *optionName) +static float parse_float( const char *str, const char *optionName ) { - auto result = gamescope::Parse(str); - if ( result.has_value() ) - { - return result.value(); - } - else - { - fprintf( stderr, "gamescope: invalid value for --%s, \"%s\" could not be interpreted as a real number\n", optionName, str ); - exit(1); - } + auto result = gamescope::Parse( str ); + if ( result.has_value( ) ) { return result.value( ); } + else + { + fprintf( + stderr, + "gamescope: invalid value for --%s, \"%s\" could not be " + "interpreted as a real number\n", + optionName, + str ); + exit( 1 ); + } } struct sigaction handle_signal_action = {}; -void ShutdownGamescope() +void ShutdownGamescope( ) { - g_bRun = false; + g_bRun = false; - nudge_steamcompmgr(); + nudge_steamcompmgr( ); } -static gamescope::ConCommand cc_shutdown( "shutdown", "Cleanly shutdown gamescope", -[]( std::span svArgs ) -{ - console_log.infof( "Shutting down..." ); - ShutdownGamescope(); -}); +static gamescope::ConCommand cc_shutdown( + "shutdown", + "Cleanly shutdown gamescope", + []( std::span svArgs ) + { + console_log.infof( "Shutting down..." ); + ShutdownGamescope( ); + } ); static void handle_signal( int sig ) { - switch ( sig ) { - case SIGUSR2: - gamescope::CScreenshotManager::Get().TakeScreenshot( true ); - break; - case SIGHUP: - case SIGQUIT: - case SIGTERM: - case SIGINT: - ShutdownGamescope(); - break; - case SIGUSR1: - fprintf( stderr, "gamescope: hi :3\n" ); - break; - default: - assert( false ); // unreachable - } + switch ( sig ) + { + case SIGUSR2: + gamescope::CScreenshotManager::Get( ).TakeScreenshot( true ); + break; + case SIGHUP: + case SIGQUIT: + case SIGTERM: + case SIGINT: + ShutdownGamescope( ); + break; + case SIGUSR1: + fprintf( stderr, "gamescope: hi :3\n" ); + break; + default: + assert( false ); // unreachable + } } static EStreamColorspace parse_colorspace_string( const char *pszStr ) { - if ( !pszStr || !*pszStr ) - return k_EStreamColorspace_Unknown; - - if ( !strcmp( pszStr, "k_EStreamColorspace_BT601" ) ) - return k_EStreamColorspace_BT601; - else if ( !strcmp( pszStr, "k_EStreamColorspace_BT601_Full" ) ) - return k_EStreamColorspace_BT601_Full; - else if ( !strcmp( pszStr, "k_EStreamColorspace_BT709" ) ) - return k_EStreamColorspace_BT709; - else if ( !strcmp( pszStr, "k_EStreamColorspace_BT709_Full" ) ) - return k_EStreamColorspace_BT709_Full; - else - return k_EStreamColorspace_Unknown; + if ( !pszStr || !*pszStr ) return k_EStreamColorspace_Unknown; + + if ( !strcmp( pszStr, "k_EStreamColorspace_BT601" ) ) + return k_EStreamColorspace_BT601; + else if ( !strcmp( pszStr, "k_EStreamColorspace_BT601_Full" ) ) + return k_EStreamColorspace_BT601_Full; + else if ( !strcmp( pszStr, "k_EStreamColorspace_BT709" ) ) + return k_EStreamColorspace_BT709; + else if ( !strcmp( pszStr, "k_EStreamColorspace_BT709_Full" ) ) + return k_EStreamColorspace_BT709_Full; + else + return k_EStreamColorspace_Unknown; } - - - -static bool g_bSupportsWaylandPresentationTime = false; +static bool g_bSupportsWaylandPresentationTime = false; static constexpr wl_registry_listener s_registryListener = { - .global = [](void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { - if (interface == "wp_presentation"sv) + .global = + []( void *data, + wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version ) + { + if ( interface == "wp_presentation"sv ) g_bSupportsWaylandPresentationTime = true; }, - .global_remove = [](void* data, wl_registry* registry, uint32_t name) { - }, + .global_remove = []( void *data, wl_registry *registry, uint32_t name ) {}, }; -static bool CheckWaylandPresentationTime() +static bool CheckWaylandPresentationTime( ) { - wl_display *display = wl_display_connect(g_pOriginalWaylandDisplay); - if (!display) { - fprintf(stderr, "Failed to connect to wayland socket: %s.\n", g_pOriginalWaylandDisplay); - exit(1); + wl_display *display = wl_display_connect( g_pOriginalWaylandDisplay ); + if ( !display ) + { + fprintf( + stderr, + "Failed to connect to wayland socket: %s.\n", + g_pOriginalWaylandDisplay ); + exit( 1 ); return false; - } - wl_registry *registry = wl_display_get_registry(display); + } + wl_registry *registry = wl_display_get_registry( display ); - wl_registry_add_listener(registry, &s_registryListener, nullptr); + wl_registry_add_listener( registry, &s_registryListener, nullptr ); - wl_display_dispatch(display); - wl_display_roundtrip(display); + wl_display_dispatch( display ); + wl_display_roundtrip( display ); - wl_registry_destroy(registry); - wl_display_disconnect(display); + wl_registry_destroy( registry ); + wl_display_disconnect( display ); return g_bSupportsWaylandPresentationTime; } @@ -590,273 +703,355 @@ static bool IsInDebugSession() } #endif -bool steamMode = false; +bool steamMode = false; bool g_bLaunchMangoapp = false; -static void UpdateCompatEnvVars() +static void UpdateCompatEnvVars( ) { - // Legacy env vars for compat. - if ( steamMode ) - { - // We have NIS support. - setenv( "STEAM_GAMESCOPE_NIS_SUPPORTED", "1", 0 ); - // Have SteamRT's xdg-open send http:// and https:// URLs to Steam - setenv( "SRT_URLOPEN_PREFER_STEAM", "1", 0 ); - if ( g_nXWaylandCount > 1 ) - { - setenv( "STEAM_MULTIPLE_XWAYLANDS", "1", 0 ); - } - // If the backend exposes tearing, expose that to Steam. - if ( GetBackend()->SupportsTearing() ) - { - setenv( "STEAM_GAMESCOPE_TEARING_SUPPORTED", "1", 0 ); - setenv( "STEAM_GAMESCOPE_HAS_TEARING_SUPPORT", "1", 0 ); - } - // We always support VRR (but not necessarily on every connector, etc.) - setenv( "STEAM_GAMESCOPE_VRR_SUPPORTED", "1", 0 ); - // We no longer need to set GAMESCOPE_EXTERNAL_OVERLAY from steam, mangoapp now does it itself - setenv( "STEAM_DISABLE_MANGOAPP_ATOM_WORKAROUND", "1", 0 ); - // Enable horizontal mangoapp bar - setenv( "STEAM_MANGOAPP_HORIZONTAL_SUPPORTED", "1", 0 ); - // Scaling support - setenv( "STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT", "1", 0 ); - // We support HDR. - setenv( "STEAM_GAMESCOPE_HDR_SUPPORTED", "1", 0 ); - // Gamescope WSI layer implements this. - setenv( "STEAM_GAMESCOPE_DYNAMIC_FPSLIMITER", "1", 0 ); - - // Set input method modules for Qt/GTK that will show the Steam keyboard - // These are mostly SteamOS specific, and are set by our Gamescope session, - // but might be useful for you. - //setenv( "QT_IM_MODULE", "steam", 1 ); - //setenv( "GTK_IM_MODULE", "Steam", 1 ); - //setenv( "QT_QPA_PLATFORM_THEME", "kde", 1 ); - - // Maybe we should expose a backend check for this... - // STEAM_GAMESCOPE_COLOR_MANAGED - // STEAM_GAMESCOPE_VIRTUAL_WHITE - - // STEAM_USE_DYNAMIC_VRS is RADV specific, so don't expose this right now. - - setenv( "STEAM_MANGOAPP_PRESETS_SUPPORTED", "1", 0 ); - setenv( "STEAM_USE_MANGOAPP", "1", 0 ); - } - - // Always set this to false, we never want buffers to be waited on by Mesa. - // That is our job! - setenv( "vk_xwayland_wait_ready", "false", 1 ); - if ( g_nCursorScaleHeight > 0 ) - { - // We always want the biggest cursor size so we can scale it. - setenv( "XCURSOR_SIZE", "256", 1 ); - } - - // Legacy support for SteamOS. - setenv( "XWAYLAND_FORCE_ENABLE_EXTRA_MODES", "1", 1 ); - - // Don't minimise stuff on focus loss with SDL. - setenv( "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS", "0", 1 ); - - const char *pszMangoConfigPath = getenv( "MANGOHUD_CONFIGFILE" ); - if ( (g_bLaunchMangoapp && steamMode) && ( !pszMangoConfigPath || !*pszMangoConfigPath ) ) - { - char szMangoConfigPath[ PATH_MAX ]; - FILE *pMangoConfigFile = gamescope::MakeTempFile( szMangoConfigPath, gamescope::k_szGamescopeTempMangoappTemplate, "w", true ); - if ( pMangoConfigFile ) - { - setenv( "MANGOHUD_CONFIGFILE", szMangoConfigPath, 1 ); - - if ( steamMode ) - { - const char szDefaultConfig[] = "no_display"; - fwrite( szDefaultConfig, 1, sizeof( szDefaultConfig ), pMangoConfigFile ); - } - fclose( pMangoConfigFile ); - } - } - - const char *pszLimiterFile = getenv( "GAMESCOPE_LIMITER_FILE" ); - if ( !pszLimiterFile || !*pszLimiterFile ) - { - char szLimiterPath[ PATH_MAX ]; - int nLimiterFd = gamescope::MakeTempFile( szLimiterPath, gamescope::k_szGamescopeTempLimiterTemplate, true ); - if ( nLimiterFd >= 0 ) - { - setenv( "GAMESCOPE_LIMITER_FILE", szLimiterPath, 1 ); - gamescope::Process::CloseFd( nLimiterFd ); - } - } + // Legacy env vars for compat. + if ( steamMode ) + { + // We have NIS support. + setenv( "STEAM_GAMESCOPE_NIS_SUPPORTED", "1", 0 ); + // Have SteamRT's xdg-open send http:// and https:// URLs to Steam + setenv( "SRT_URLOPEN_PREFER_STEAM", "1", 0 ); + if ( g_nXWaylandCount > 1 ) + { + setenv( "STEAM_MULTIPLE_XWAYLANDS", "1", 0 ); + } + // If the backend exposes tearing, expose that to Steam. + if ( GetBackend( )->SupportsTearing( ) ) + { + setenv( "STEAM_GAMESCOPE_TEARING_SUPPORTED", "1", 0 ); + setenv( "STEAM_GAMESCOPE_HAS_TEARING_SUPPORT", "1", 0 ); + } + // We always support VRR (but not necessarily on every connector, etc.) + setenv( "STEAM_GAMESCOPE_VRR_SUPPORTED", "1", 0 ); + // We no longer need to set GAMESCOPE_EXTERNAL_OVERLAY from steam, + // mangoapp now does it itself + setenv( "STEAM_DISABLE_MANGOAPP_ATOM_WORKAROUND", "1", 0 ); + // Enable horizontal mangoapp bar + setenv( "STEAM_MANGOAPP_HORIZONTAL_SUPPORTED", "1", 0 ); + // Scaling support + setenv( "STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT", "1", 0 ); + // We support HDR. + setenv( "STEAM_GAMESCOPE_HDR_SUPPORTED", "1", 0 ); + // Gamescope WSI layer implements this. + setenv( "STEAM_GAMESCOPE_DYNAMIC_FPSLIMITER", "1", 0 ); + + // Set input method modules for Qt/GTK that will show the Steam keyboard + // These are mostly SteamOS specific, and are set by our Gamescope + // session, but might be useful for you. + // setenv( "QT_IM_MODULE", "steam", 1 ); + // setenv( "GTK_IM_MODULE", "Steam", 1 ); + // setenv( "QT_QPA_PLATFORM_THEME", "kde", 1 ); + + // Maybe we should expose a backend check for this... + // STEAM_GAMESCOPE_COLOR_MANAGED + // STEAM_GAMESCOPE_VIRTUAL_WHITE + + // STEAM_USE_DYNAMIC_VRS is RADV specific, so don't expose this right + // now. + + setenv( "STEAM_MANGOAPP_PRESETS_SUPPORTED", "1", 0 ); + setenv( "STEAM_USE_MANGOAPP", "1", 0 ); + } + + // Always set this to false, we never want buffers to be waited on by Mesa. + // That is our job! + setenv( "vk_xwayland_wait_ready", "false", 1 ); + if ( g_nCursorScaleHeight > 0 ) + { + // We always want the biggest cursor size so we can scale it. + setenv( "XCURSOR_SIZE", "256", 1 ); + } + + // Legacy support for SteamOS. + setenv( "XWAYLAND_FORCE_ENABLE_EXTRA_MODES", "1", 1 ); + + // Don't minimise stuff on focus loss with SDL. + setenv( "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS", "0", 1 ); + + const char *pszMangoConfigPath = getenv( "MANGOHUD_CONFIGFILE" ); + if ( ( g_bLaunchMangoapp && steamMode ) && + ( !pszMangoConfigPath || !*pszMangoConfigPath ) ) + { + char szMangoConfigPath[ PATH_MAX ]; + FILE *pMangoConfigFile = gamescope::MakeTempFile( + szMangoConfigPath, + gamescope::k_szGamescopeTempMangoappTemplate, + "w", + true ); + if ( pMangoConfigFile ) + { + setenv( "MANGOHUD_CONFIGFILE", szMangoConfigPath, 1 ); + + if ( steamMode ) + { + const char szDefaultConfig[] = "no_display"; + fwrite( + szDefaultConfig, + 1, + sizeof( szDefaultConfig ), + pMangoConfigFile ); + } + fclose( pMangoConfigFile ); + } + } + + const char *pszLimiterFile = getenv( "GAMESCOPE_LIMITER_FILE" ); + if ( !pszLimiterFile || !*pszLimiterFile ) + { + char szLimiterPath[ PATH_MAX ]; + int nLimiterFd = gamescope::MakeTempFile( + szLimiterPath, gamescope::k_szGamescopeTempLimiterTemplate, true ); + if ( nLimiterFd >= 0 ) + { + setenv( "GAMESCOPE_LIMITER_FILE", szLimiterPath, 1 ); + gamescope::Process::CloseFd( nLimiterFd ); + } + } } -int g_nPreferredOutputWidth = 0; -int g_nPreferredOutputHeight = 0; -bool g_bExposeWayland = false; -const char *g_sOutputName = nullptr; -bool g_bDebugLayers = false; -bool g_bForceDisableColorMgmt = false; -bool g_bRt = false; +int g_nPreferredOutputWidth = 0; +int g_nPreferredOutputHeight = 0; +bool g_bExposeWayland = false; +const char *g_sOutputName = nullptr; +bool g_bDebugLayers = false; +bool g_bForceDisableColorMgmt = false; +bool g_bRt = false; // This will go away when we remove the getopt stuff from vr session. // For now... -int g_argc; +int g_argc; char **g_argv; extern char **environ; -int main(int argc, char **argv) +int main( int argc, char **argv ) { - g_argc = argc; - g_argv = argv; - - // Force disable this horrible broken layer. - setenv("DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1", "1", 1); - - static std::string optstring = build_optstring(gamescope_options); - gamescope_optstring = optstring.c_str(); - - gamescope::GamescopeBackend eCurrentBackend = gamescope::GamescopeBackend::Auto; - - gamescope::PrintVersion(); - - int o; - int opt_index = -1; - while ((o = getopt_long(argc, argv, gamescope_optstring, gamescope_options, &opt_index)) != -1) - { - const char *opt_name; - switch (o) { - case 'w': - g_nNestedWidth = parse_integer( optarg, "nested-width" ); - break; - case 'h': - g_nNestedHeight = parse_integer( optarg, "nested-height" ); - break; - case 'r': - g_nNestedRefresh = gamescope::ConvertHztomHz( parse_integer( optarg, "nested-refresh" ) ); - break; - case 'W': - g_nPreferredOutputWidth = parse_integer( optarg, "output-width" ); - break; - case 'H': - g_nPreferredOutputHeight = parse_integer( optarg, "output-height" ); - break; - case 'o': - g_nNestedUnfocusedRefresh = gamescope::ConvertHztomHz( parse_integer( optarg, "nested-unfocused-refresh" ) ); - break; - case 'm': - g_flMaxWindowScale = parse_float( optarg, "max-scale" ); - break; - case 'S': - g_wantedUpscaleScaler = parse_upscaler_scaler(optarg); - break; - case 'F': - g_wantedUpscaleFilter = parse_upscaler_filter(optarg); - break; - case 'b': - g_bBorderlessOutputWindow = true; - break; - case 'f': - g_bFullscreen = true; - break; - case 'O': - g_sOutputName = optarg; - break; - case 'g': - g_bGrabbed = true; - break; - case 's': - g_mouseSensitivity = parse_float( optarg, "mouse-sensitivity" ); - break; - case 'e': - steamMode = true; - if ( gamescope::cv_backend_virtual_connector_strategy == gamescope::VirtualConnectorStrategies::SingleApplication ) - gamescope::cv_backend_virtual_connector_strategy = gamescope::VirtualConnectorStrategies::SteamControlled; - break; - case 0: // long options without a short option - opt_name = gamescope_options[opt_index].name; - if (strcmp(opt_name, "help") == 0) { - fprintf(stderr, "%s", usage); - return 0; - } else if (strcmp(opt_name, "version") == 0) { - // We always print the version to stderr anyway. - return 0; - } else if (strcmp(opt_name, "debug-layers") == 0) { - g_bDebugLayers = true; - } else if (strcmp(opt_name, "disable-color-management") == 0) { - g_bForceDisableColorMgmt = true; - } else if (strcmp(opt_name, "xwayland-count") == 0) { - g_nXWaylandCount = parse_integer( optarg, opt_name ); - } else if (strcmp(opt_name, "composite-debug") == 0) { - cv_composite_debug |= CompositeDebugFlag::Markers; - cv_composite_debug |= CompositeDebugFlag::PlaneBorders; - } else if (strcmp(opt_name, "hdr-debug-heatmap") == 0) { - cv_composite_debug |= CompositeDebugFlag::Heatmap; - } else if (strcmp(opt_name, "default-touch-mode") == 0) { - gamescope::cv_touch_click_mode = (gamescope::TouchClickMode) parse_integer( optarg, opt_name ); - } else if (strcmp(opt_name, "generate-drm-mode") == 0) { - g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg ); - } else if (strcmp(opt_name, "force-orientation") == 0) { - g_DesiredInternalOrientation = force_orientation( optarg ); - } else if (strcmp(opt_name, "sharpness") == 0 || - strcmp(opt_name, "fsr-sharpness") == 0) { - g_upscaleFilterSharpness = parse_integer( optarg, opt_name ); - } else if (strcmp(opt_name, "rt") == 0) { - g_bRt = true; - } else if (strcmp(opt_name, "prefer-vk-device") == 0) { - unsigned vendorID; - unsigned deviceID; - sscanf( optarg, "%X:%X", &vendorID, &deviceID ); - g_preferVendorID = vendorID; - g_preferDeviceID = deviceID; - } else if (strcmp(opt_name, "immediate-flips") == 0) { - cv_tearing_enabled = true; - } else if (strcmp(opt_name, "force-grab-cursor") == 0) { - g_bForceRelativeMouse = true; - } else if (strcmp(opt_name, "display-index") == 0) { - g_nNestedDisplayIndex = parse_integer( optarg, opt_name ); - } else if (strcmp(opt_name, "adaptive-sync") == 0) { - cv_adaptive_sync = true; - } else if (strcmp(opt_name, "expose-wayland") == 0) { - g_bExposeWayland = true; - } else if (strcmp(opt_name, "backend") == 0) { - eCurrentBackend = parse_backend_name( optarg ); - } else if (strcmp(opt_name, "cursor-scale-height") == 0) { - g_nCursorScaleHeight = parse_integer(optarg, opt_name); - } else if (strcmp(opt_name, "mangoapp") == 0) { - g_bLaunchMangoapp = true; - } else if (strcmp(opt_name, "allow-deferred-backend") == 0) { - g_bAllowDeferredBackend = true; - } else if (strcmp(opt_name, "keep-alive") == 0) { - cv_shutdown_on_primary_child_death = false; - } else if (strcmp(opt_name, "virtual-connector-strategy") == 0) { - for ( uint32_t i = 0; i < gamescope::VirtualConnectorStrategies::Count; i++ ) - { - gamescope::VirtualConnectorStrategy eStrategy = - static_cast( i ); - if ( optarg == gamescope::VirtualConnectorStrategyToString( eStrategy ) ) - { - gamescope::cv_backend_virtual_connector_strategy = eStrategy; - - } - } - } - break; - case '?': - fprintf( stderr, "See --help for a list of options.\n" ); - return 1; - } - } - - if ( gamescope::Process::HasCapSysNice() ) - { - gamescope::Process::SetNice( -20 ); - - if ( g_bRt ) - gamescope::Process::SetRealtime(); - } - else - { - fprintf( stderr, "No CAP_SYS_NICE, falling back to regular-priority compute and threads.\nPerformance will be affected.\n" ); - } + g_argc = argc; + g_argv = argv; + + // Force disable this horrible broken layer. + setenv( "DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1", "1", 1 ); + + static std::string optstring = build_optstring( gamescope_options ); + gamescope_optstring = optstring.c_str( ); + + gamescope::GamescopeBackend eCurrentBackend = + gamescope::GamescopeBackend::Auto; + + gamescope::PrintVersion( ); + + int o; + int opt_index = -1; + while ( ( o = getopt_long( + argc, + argv, + gamescope_optstring, + gamescope_options, + &opt_index ) ) != -1 ) + { + const char *opt_name; + switch ( o ) + { + case 'w': + g_nNestedWidth = parse_integer( optarg, "nested-width" ); + break; + case 'h': + g_nNestedHeight = parse_integer( optarg, "nested-height" ); + break; + case 'r': + g_nNestedRefresh = gamescope::ConvertHztomHz( + parse_integer( optarg, "nested-refresh" ) ); + break; + case 'W': + g_nPreferredOutputWidth = + parse_integer( optarg, "output-width" ); + break; + case 'H': + g_nPreferredOutputHeight = + parse_integer( optarg, "output-height" ); + break; + case 'o': + g_nNestedUnfocusedRefresh = gamescope::ConvertHztomHz( + parse_integer( optarg, "nested-unfocused-refresh" ) ); + break; + case 'm': + g_flMaxWindowScale = parse_float( optarg, "max-scale" ); + break; + case 'S': + g_wantedUpscaleScaler = parse_upscaler_scaler( optarg ); + break; + case 'F': + g_wantedUpscaleFilter = parse_upscaler_filter( optarg ); + break; + case 'b': + g_bBorderlessOutputWindow = true; + break; + case 'f': + g_bFullscreen = true; + break; + case 'O': + g_sOutputName = optarg; + break; + case 'g': + g_bGrabbed = true; + break; + case 's': + g_mouseSensitivity = parse_float( optarg, "mouse-sensitivity" ); + break; + case 'e': + steamMode = true; + if ( gamescope::cv_backend_virtual_connector_strategy == + gamescope::VirtualConnectorStrategies::SingleApplication ) + gamescope::cv_backend_virtual_connector_strategy = + gamescope::VirtualConnectorStrategies::SteamControlled; + break; + case 0: // long options without a short option + opt_name = gamescope_options[ opt_index ].name; + if ( strcmp( opt_name, "help" ) == 0 ) + { + fprintf( stderr, "%s", usage ); + return 0; + } + else if ( strcmp( opt_name, "version" ) == 0 ) + { + // We always print the version to stderr anyway. + return 0; + } + else if ( strcmp( opt_name, "debug-layers" ) == 0 ) + { + g_bDebugLayers = true; + } + else if ( strcmp( opt_name, "disable-color-management" ) == 0 ) + { + g_bForceDisableColorMgmt = true; + } + else if ( strcmp( opt_name, "xwayland-count" ) == 0 ) + { + g_nXWaylandCount = parse_integer( optarg, opt_name ); + } + else if ( strcmp( opt_name, "composite-debug" ) == 0 ) + { + cv_composite_debug |= CompositeDebugFlag::Markers; + cv_composite_debug |= CompositeDebugFlag::PlaneBorders; + } + else if ( strcmp( opt_name, "hdr-debug-heatmap" ) == 0 ) + { + cv_composite_debug |= CompositeDebugFlag::Heatmap; + } + else if ( strcmp( opt_name, "default-touch-mode" ) == 0 ) + { + gamescope::cv_touch_click_mode = + ( gamescope::TouchClickMode )parse_integer( + optarg, opt_name ); + } + else if ( strcmp( opt_name, "generate-drm-mode" ) == 0 ) + { + g_eGamescopeModeGeneration = + parse_gamescope_mode_generation( optarg ); + } + else if ( strcmp( opt_name, "force-orientation" ) == 0 ) + { + g_DesiredInternalOrientation = force_orientation( optarg ); + } + else if ( + strcmp( opt_name, "sharpness" ) == 0 || + strcmp( opt_name, "fsr-sharpness" ) == 0 ) + { + g_upscaleFilterSharpness = + parse_integer( optarg, opt_name ); + } + else if ( strcmp( opt_name, "rt" ) == 0 ) { g_bRt = true; } + else if ( strcmp( opt_name, "prefer-vk-device" ) == 0 ) + { + unsigned vendorID; + unsigned deviceID; + sscanf( optarg, "%X:%X", &vendorID, &deviceID ); + g_preferVendorID = vendorID; + g_preferDeviceID = deviceID; + } + else if ( strcmp( opt_name, "immediate-flips" ) == 0 ) + { + cv_tearing_enabled = true; + } + else if ( strcmp( opt_name, "force-grab-cursor" ) == 0 ) + { + g_bForceRelativeMouse = true; + } + else if ( strcmp( opt_name, "display-index" ) == 0 ) + { + g_nNestedDisplayIndex = parse_integer( optarg, opt_name ); + } + else if ( strcmp( opt_name, "adaptive-sync" ) == 0 ) + { + cv_adaptive_sync = true; + } + else if ( strcmp( opt_name, "expose-wayland" ) == 0 ) + { + g_bExposeWayland = true; + } + else if ( strcmp( opt_name, "backend" ) == 0 ) + { + eCurrentBackend = parse_backend_name( optarg ); + } + else if ( strcmp( opt_name, "cursor-scale-height" ) == 0 ) + { + g_nCursorScaleHeight = parse_integer( optarg, opt_name ); + } + else if ( strcmp( opt_name, "mangoapp" ) == 0 ) + { + g_bLaunchMangoapp = true; + } + else if ( strcmp( opt_name, "allow-deferred-backend" ) == 0 ) + { + g_bAllowDeferredBackend = true; + } + else if ( strcmp( opt_name, "keep-alive" ) == 0 ) + { + cv_shutdown_on_primary_child_death = false; + } + else if ( + strcmp( opt_name, "virtual-connector-strategy" ) == 0 ) + { + for ( uint32_t i = 0; + i < gamescope::VirtualConnectorStrategies::Count; + i++ ) + { + gamescope::VirtualConnectorStrategy eStrategy = + static_cast( + i ); + if ( optarg == + gamescope::VirtualConnectorStrategyToString( + eStrategy ) ) + { + gamescope::cv_backend_virtual_connector_strategy = + eStrategy; + } + } + } + break; + case '?': + fprintf( stderr, "See --help for a list of options.\n" ); + return 1; + } + } + + if ( gamescope::Process::HasCapSysNice( ) ) + { + gamescope::Process::SetNice( -20 ); + + if ( g_bRt ) gamescope::Process::SetRealtime( ); + } + else + { + fprintf( + stderr, + "No CAP_SYS_NICE, falling back to regular-priority compute and " + "threads.\nPerformance will be affected.\n" ); + } #if 0 while( !IsInDebugSession() ) @@ -865,236 +1060,257 @@ int main(int argc, char **argv) } #endif - gamescope::Process::RaiseFdLimit(); - - if ( gpuvis_trace_init() != -1 ) - { - fprintf( stderr, "Tracing is enabled\n"); - } - - { - gamescope::CScriptScopedLock script; - script.Manager().RunDefaultScripts(); - - // Allow overriding the value of any ConVar with a suitably named environment variable - // (gamescope_ConVar_name=override_value). This takes precedence over the ConVar's - // default value and over values assigned by default scripts. - char **s = environ; - const char *prefix = "gamescope_"; - size_t prefix_len = strlen( prefix ); - for ( ; *s; s++ ) - { - char *envvar = strdup( *s ); - if ( strncmp( envvar, prefix, prefix_len ) == 0 ) { - std::string_view name( strtok( envvar, "=" ) + prefix_len ); - if ( ! name.empty() ) - { - std::string_view value( strtok( nullptr, "=" ) ); - if ( script.Manager().Gamescope().Convars.Base[name].valid() ) - { - auto override_script = std::format( "gamescope.convars.{}.value = {}", name, value ); - console_log.infof( "Overriding from environment variable: %s", override_script.c_str() ); - script->script( override_script ); - } - } - } - free( envvar ); - } - } - - XInitThreads(); - g_mainThread = pthread_self(); - - g_pOriginalDisplay = getenv("DISPLAY"); - g_pOriginalWaylandDisplay = getenv("WAYLAND_DISPLAY"); - - // Allow overriding the selected backend (even the backend - // requested on the command line) in a startup script. - auto backendOverride = parse_backend_name( gamescope::cv_backend.Get().c_str() ); - if ( backendOverride != gamescope::GamescopeBackend::Auto ) - { - eCurrentBackend = backendOverride; - } - - if ( eCurrentBackend == gamescope::GamescopeBackend::Auto ) - { - if ( g_pOriginalWaylandDisplay != NULL ) - eCurrentBackend = gamescope::GamescopeBackend::Wayland; - else if ( g_pOriginalDisplay != NULL ) - eCurrentBackend = gamescope::GamescopeBackend::SDL; - else - eCurrentBackend = gamescope::GamescopeBackend::DRM; - } + gamescope::Process::RaiseFdLimit( ); + + if ( gpuvis_trace_init( ) != -1 ) + { + fprintf( stderr, "Tracing is enabled\n" ); + } + + { + gamescope::CScriptScopedLock script; + script.Manager( ).RunDefaultScripts( ); + + // Allow overriding the value of any ConVar with a suitably named + // environment variable (gamescope_ConVar_name=override_value). This + // takes precedence over the ConVar's default value and over values + // assigned by default scripts. + char **s = environ; + const char *prefix = "gamescope_"; + size_t prefix_len = strlen( prefix ); + for ( ; *s; s++ ) + { + char *envvar = strdup( *s ); + if ( strncmp( envvar, prefix, prefix_len ) == 0 ) + { + std::string_view name( strtok( envvar, "=" ) + prefix_len ); + if ( !name.empty( ) ) + { + std::string_view value( strtok( nullptr, "=" ) ); + if ( script.Manager( ) + .Gamescope( ) + .Convars.Base[ name ] + .valid( ) ) + { + auto override_script = std::format( + "gamescope.convars.{}.value = {}", name, value ); + console_log.infof( + "Overriding from environment variable: %s", + override_script.c_str( ) ); + script->script( override_script ); + } + } + } + free( envvar ); + } + } + + XInitThreads( ); + g_mainThread = pthread_self( ); + + g_pOriginalDisplay = getenv( "DISPLAY" ); + g_pOriginalWaylandDisplay = getenv( "WAYLAND_DISPLAY" ); + + // Allow overriding the selected backend (even the backend + // requested on the command line) in a startup script. + auto backendOverride = + parse_backend_name( gamescope::cv_backend.Get( ).c_str( ) ); + if ( backendOverride != gamescope::GamescopeBackend::Auto ) + { + eCurrentBackend = backendOverride; + } + + if ( eCurrentBackend == gamescope::GamescopeBackend::Auto ) + { + if ( g_pOriginalWaylandDisplay != NULL ) + eCurrentBackend = gamescope::GamescopeBackend::Wayland; + else if ( g_pOriginalDisplay != NULL ) + eCurrentBackend = gamescope::GamescopeBackend::SDL; + else + eCurrentBackend = gamescope::GamescopeBackend::DRM; + } - if ( g_pOriginalWaylandDisplay != NULL ) - { - if (CheckWaylandPresentationTime()) + if ( g_pOriginalWaylandDisplay != NULL ) + { + if ( CheckWaylandPresentationTime( ) ) { - // Default to SDL_VIDEODRIVER wayland under Wayland and force enable vk_khr_present_wait - // (not enabled by default in Mesa because instance does not know if Wayland - // compositor supports wp_presentation, but we can check that ourselves.) - setenv("vk_khr_present_wait", "true", 0); - setenv("SDL_VIDEODRIVER", "wayland", 0); + // Default to SDL_VIDEODRIVER wayland under Wayland and force enable + // vk_khr_present_wait (not enabled by default in Mesa because + // instance does not know if Wayland + // compositor supports wp_presentation, but we can check that + // ourselves.) + setenv( "vk_khr_present_wait", "true", 0 ); + setenv( "SDL_VIDEODRIVER", "wayland", 0 ); } else { - fprintf(stderr, - "Your Wayland compositor does NOT support wp_presentation/presentation-time which is required for VK_KHR_present_wait and VK_KHR_present_id.\n" - "Please complain to your compositor vendor for support. Falling back to X11 window with less accurate present wait.\n"); - setenv("SDL_VIDEODRIVER", "x11", 1); + fprintf( + stderr, + "Your Wayland compositor does NOT support " + "wp_presentation/presentation-time which is required for " + "VK_KHR_present_wait and VK_KHR_present_id.\n" + "Please complain to your compositor vendor for support. " + "Falling back to X11 window with less accurate present " + "wait.\n" ); + setenv( "SDL_VIDEODRIVER", "x11", 1 ); } - } + } - g_ForcedNV12ColorSpace = parse_colorspace_string( getenv( "GAMESCOPE_NV12_COLORSPACE" ) ); + g_ForcedNV12ColorSpace = + parse_colorspace_string( getenv( "GAMESCOPE_NV12_COLORSPACE" ) ); - switch ( eCurrentBackend ) - { + switch ( eCurrentBackend ) + { #if HAVE_DRM - case gamescope::GamescopeBackend::DRM: - gamescope::IBackend::Set(); - break; + case gamescope::GamescopeBackend::DRM: + gamescope::IBackend::Set( ); + break; #endif #if HAVE_SDL2 - case gamescope::GamescopeBackend::SDL: - gamescope::IBackend::Set(); - break; + case gamescope::GamescopeBackend::SDL: + gamescope::IBackend::Set( ); + break; #endif #if HAVE_OPENVR - case gamescope::GamescopeBackend::OpenVR: - gamescope::IBackend::Set(); - break; + case gamescope::GamescopeBackend::OpenVR: + gamescope::IBackend::Set( ); + break; #endif - case gamescope::GamescopeBackend::Headless: - gamescope::IBackend::Set(); - break; + case gamescope::GamescopeBackend::Headless: + gamescope::IBackend::Set( ); + break; - case gamescope::GamescopeBackend::Wayland: - gamescope::IBackend::Set(); + case gamescope::GamescopeBackend::Wayland: + gamescope::IBackend::Set( ); #if HAVE_SDL2 - if ( !GetBackend() ) - gamescope::IBackend::Set(); + if ( !GetBackend( ) ) + gamescope::IBackend::Set( ); #endif - break; - default: - abort(); - } - - if ( !GetBackend() ) - { - fprintf( stderr, "Failed to create backend.\n" ); - return 1; - } - - UpdateCompatEnvVars(); - - if ( !vulkan_init_formats() ) - { - fprintf( stderr, "vulkan_init_formats failed\n" ); - return 1; - } - - if ( !vulkan_make_output() ) - { - fprintf( stderr, "vulkan_make_output failed\n" ); - return 1; - } - - // Prevent our clients from connecting to the parent compositor - unsetenv("WAYLAND_DISPLAY"); - - // If DRM format modifiers aren't supported, prevent our clients from using - // DCC, as this can cause tiling artifacts. - if ( !vulkan_supports_modifiers() ) - { - const char *pchR600Debug = getenv( "R600_DEBUG" ); - - if ( pchR600Debug == nullptr ) - { - setenv( "R600_DEBUG", "nodcc", 1 ); - } - else if ( strstr( pchR600Debug, "nodcc" ) == nullptr ) - { - std::string strPreviousR600Debug = pchR600Debug; - strPreviousR600Debug.append( ",nodcc" ); - setenv( "R600_DEBUG", strPreviousR600Debug.c_str(), 1 ); - } - } - - if ( g_nNestedHeight == 0 ) - { - if ( g_nNestedWidth != 0 ) - { - fprintf( stderr, "Cannot specify -w without -h\n" ); - return 1; - } - g_nNestedWidth = g_nOutputWidth; - g_nNestedHeight = g_nOutputHeight; - } - if ( g_nNestedWidth == 0 ) - g_nNestedWidth = g_nNestedHeight * 16 / 9; - - if ( !wlserver_init() ) - { - fprintf( stderr, "Failed to initialize wlserver\n" ); - return 1; - } - - gamescope_xwayland_server_t *base_server = wlserver_get_xwayland_server(0); + break; + default: + abort( ); + } + + if ( !GetBackend( ) ) + { + fprintf( stderr, "Failed to create backend.\n" ); + return 1; + } + + UpdateCompatEnvVars( ); + + if ( !vulkan_init_formats( ) ) + { + fprintf( stderr, "vulkan_init_formats failed\n" ); + return 1; + } + + if ( !vulkan_make_output( ) ) + { + fprintf( stderr, "vulkan_make_output failed\n" ); + return 1; + } + + // Prevent our clients from connecting to the parent compositor + unsetenv( "WAYLAND_DISPLAY" ); + + // If DRM format modifiers aren't supported, prevent our clients from using + // DCC, as this can cause tiling artifacts. + if ( !vulkan_supports_modifiers( ) ) + { + const char *pchR600Debug = getenv( "R600_DEBUG" ); + + if ( pchR600Debug == nullptr ) { setenv( "R600_DEBUG", "nodcc", 1 ); } + else if ( strstr( pchR600Debug, "nodcc" ) == nullptr ) + { + std::string strPreviousR600Debug = pchR600Debug; + strPreviousR600Debug.append( ",nodcc" ); + setenv( "R600_DEBUG", strPreviousR600Debug.c_str( ), 1 ); + } + } - setenv("DISPLAY", base_server->get_nested_display_name(), 1); - if ( g_bExposeWayland ) - setenv("XDG_SESSION_TYPE", "wayland", 1); - else - setenv("XDG_SESSION_TYPE", "x11", 1); - setenv("XDG_CURRENT_DESKTOP", "gamescope", 1); - if (g_nXWaylandCount > 1) - { - for (int i = 1; i < g_nXWaylandCount; i++) - { - char env_name[64]; - snprintf(env_name, sizeof(env_name), "STEAM_GAME_DISPLAY_%d", i - 1); - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(i); - setenv(env_name, server->get_nested_display_name(), 1); - } - } - else - { - setenv("STEAM_GAME_DISPLAY_0", base_server->get_nested_display_name(), 1); - } - setenv("GAMESCOPE_WAYLAND_DISPLAY", wlserver_get_wl_display_name(), 1); - if ( g_bExposeWayland ) - setenv("WAYLAND_DISPLAY", wlserver_get_wl_display_name(), 1); + if ( g_nNestedHeight == 0 ) + { + if ( g_nNestedWidth != 0 ) + { + fprintf( stderr, "Cannot specify -w without -h\n" ); + return 1; + } + g_nNestedWidth = g_nOutputWidth; + g_nNestedHeight = g_nOutputHeight; + } + if ( g_nNestedWidth == 0 ) g_nNestedWidth = g_nNestedHeight * 16 / 9; + + if ( !wlserver_init( ) ) + { + fprintf( stderr, "Failed to initialize wlserver\n" ); + return 1; + } + + gamescope_xwayland_server_t *base_server = + wlserver_get_xwayland_server( 0 ); + + setenv( "DISPLAY", base_server->get_nested_display_name( ), 1 ); + if ( g_bExposeWayland ) setenv( "XDG_SESSION_TYPE", "wayland", 1 ); + else + setenv( "XDG_SESSION_TYPE", "x11", 1 ); + setenv( "XDG_CURRENT_DESKTOP", "gamescope", 1 ); + if ( g_nXWaylandCount > 1 ) + { + for ( int i = 1; i < g_nXWaylandCount; i++ ) + { + char env_name[ 64 ]; + snprintf( + env_name, sizeof( env_name ), "STEAM_GAME_DISPLAY_%d", i - 1 ); + gamescope_xwayland_server_t *server = + wlserver_get_xwayland_server( i ); + setenv( env_name, server->get_nested_display_name( ), 1 ); + } + } + else + { + setenv( + "STEAM_GAME_DISPLAY_0", + base_server->get_nested_display_name( ), + 1 ); + } + setenv( "GAMESCOPE_WAYLAND_DISPLAY", wlserver_get_wl_display_name( ), 1 ); + if ( g_bExposeWayland ) + setenv( "WAYLAND_DISPLAY", wlserver_get_wl_display_name( ), 1 ); #if HAVE_PIPEWIRE - if ( !init_pipewire() ) - { - fprintf( stderr, "Warning: failed to setup PipeWire, screen capture won't be available\n" ); - } + if ( !init_pipewire( ) ) + { + fprintf( + stderr, + "Warning: failed to setup PipeWire, screen capture won't be " + "available\n" ); + } #endif - std::thread steamCompMgrThread( steamCompMgrThreadRun, argc, argv ); + std::thread steamCompMgrThread( steamCompMgrThreadRun, argc, argv ); - handle_signal_action.sa_handler = handle_signal; - sigaction(SIGHUP, &handle_signal_action, nullptr); - sigaction(SIGINT, &handle_signal_action, nullptr); - sigaction(SIGQUIT, &handle_signal_action, nullptr); - sigaction(SIGTERM, &handle_signal_action, nullptr); - sigaction(SIGUSR1, &handle_signal_action, nullptr); - sigaction(SIGUSR2, &handle_signal_action, nullptr); + handle_signal_action.sa_handler = handle_signal; + sigaction( SIGHUP, &handle_signal_action, nullptr ); + sigaction( SIGINT, &handle_signal_action, nullptr ); + sigaction( SIGQUIT, &handle_signal_action, nullptr ); + sigaction( SIGTERM, &handle_signal_action, nullptr ); + sigaction( SIGUSR1, &handle_signal_action, nullptr ); + sigaction( SIGUSR2, &handle_signal_action, nullptr ); - wlserver_run(); + wlserver_run( ); - steamCompMgrThread.join(); + steamCompMgrThread.join( ); - gamescope::Process::KillAllChildren( getpid(), SIGTERM ); - gamescope::Process::WaitForAllChildren(); + gamescope::Process::KillAllChildren( getpid( ), SIGTERM ); + gamescope::Process::WaitForAllChildren( ); } -static void steamCompMgrThreadRun(int argc, char **argv) +static void steamCompMgrThreadRun( int argc, char **argv ) { - pthread_setname_np( pthread_self(), "gamescope-xwm" ); + pthread_setname_np( pthread_self( ), "gamescope-xwm" ); - steamcompmgr_main( argc, argv ); + steamcompmgr_main( argc, argv ); - pthread_kill( g_mainThread, SIGINT ); + pthread_kill( g_mainThread, SIGINT ); } diff --git a/src/main.hpp b/src/main.hpp index 2e6fb833af..03bb15d866 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -4,29 +4,29 @@ #include -extern const char *gamescope_optstring; +extern const char *gamescope_optstring; extern const struct option *gamescope_options; -extern std::atomic< bool > g_bRun; +extern std::atomic g_bRun; extern int g_nNestedWidth; extern int g_nNestedHeight; -extern int g_nNestedRefresh; // mHz +extern int g_nNestedRefresh; // mHz extern int g_nNestedUnfocusedRefresh; // mHz extern int g_nNestedDisplayIndex; extern uint32_t g_nOutputWidth; extern uint32_t g_nOutputHeight; -extern bool g_bForceRelativeMouse; -extern int g_nOutputRefresh; // mHz -extern bool g_bOutputHDREnabled; -extern bool g_bForceInternal; +extern bool g_bForceRelativeMouse; +extern int g_nOutputRefresh; // mHz +extern bool g_bOutputHDREnabled; +extern bool g_bForceInternal; extern bool g_bFullscreen; extern bool g_bGrabbed; -extern float g_mouseSensitivity; +extern float g_mouseSensitivity; extern const char *g_sOutputName; enum class GamescopeUpscaleFilter : uint32_t @@ -40,9 +40,11 @@ enum class GamescopeUpscaleFilter : uint32_t FROM_VIEW = 0xF, // internal }; -static constexpr bool DoesHardwareSupportUpscaleFilter( GamescopeUpscaleFilter eFilter ) +static constexpr bool +DoesHardwareSupportUpscaleFilter( GamescopeUpscaleFilter eFilter ) { - // Could do nearest someday... AMDGPU DC supports custom tap placement to an extent. + // Could do nearest someday... AMDGPU DC supports custom tap placement to an + // extent. return eFilter == GamescopeUpscaleFilter::LINEAR; } @@ -60,7 +62,7 @@ extern GamescopeUpscaleFilter g_upscaleFilter; extern GamescopeUpscaleScaler g_upscaleScaler; extern GamescopeUpscaleFilter g_wantedUpscaleFilter; extern GamescopeUpscaleScaler g_wantedUpscaleScaler; -extern int g_upscaleFilterSharpness; +extern int g_upscaleFilterSharpness; extern bool g_bBorderlessOutputWindow; @@ -72,4 +74,3 @@ extern int g_nXWaylandCount; extern uint32_t g_preferVendorID; extern uint32_t g_preferDeviceID; - diff --git a/src/mangoapp.cpp b/src/mangoapp.cpp index a985efc735..eeb8db84f2 100644 --- a/src/mangoapp.cpp +++ b/src/mangoapp.cpp @@ -1,105 +1,116 @@ +#include #include -#include #include -#include +#include -#include "steamcompmgr.hpp" -#include "refresh_rate.h" #include "main.hpp" +#include "refresh_rate.h" +#include "steamcompmgr.hpp" -static bool inited = false; -static int msgid = 0; -extern bool g_bAppWantsHDRCached; +static bool inited = false; +static int msgid = 0; +extern bool g_bAppWantsHDRCached; extern uint32_t g_focusedBaseAppId; -struct mangoapp_msg_header { - long msg_type; // Message queue ID, never change +struct mangoapp_msg_header +{ + long msg_type; // Message queue ID, never change uint32_t version; // for major changes in the way things work // -} __attribute__((packed)); +} __attribute__(( packed )); -struct mangoapp_msg_v1 { +struct mangoapp_msg_v1 +{ struct mangoapp_msg_header hdr; uint32_t pid; uint64_t app_frametime_ns; - uint8_t fsrUpscale; - uint8_t fsrSharpness; + uint8_t fsrUpscale; + uint8_t fsrSharpness; uint64_t visible_frametime_ns; uint64_t latency_ns; uint32_t outputWidth; uint32_t outputHeight; uint16_t displayRefresh; - bool bAppWantsHDR : 1; - bool bSteamFocused : 1; - char engineName[40]; - + bool bAppWantsHDR : 1; + bool bSteamFocused : 1; + char engineName[ 40 ]; + // WARNING: Always ADD fields, never remove or repurpose fields -} __attribute__((packed)) mangoapp_msg_v1; +} __attribute__(( packed )) mangoapp_msg_v1; -void init_mangoapp(){ - int key = ftok("mangoapp", 65); - msgid = msgget(key, 0666 | IPC_CREAT); +void init_mangoapp( ) +{ + int key = ftok( "mangoapp", 65 ); + msgid = msgget( key, 0666 | IPC_CREAT ); mangoapp_msg_v1.hdr.msg_type = 1; - mangoapp_msg_v1.hdr.version = 1; - mangoapp_msg_v1.fsrUpscale = 0; + mangoapp_msg_v1.hdr.version = 1; + mangoapp_msg_v1.fsrUpscale = 0; mangoapp_msg_v1.fsrSharpness = 0; - inited = true; + inited = true; } -void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns ) { - if (!inited) - init_mangoapp(); +void mangoapp_update( + uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns ) +{ + if ( !inited ) init_mangoapp( ); mangoapp_msg_v1.visible_frametime_ns = visible_frametime; - mangoapp_msg_v1.fsrUpscale = g_bFSRActive; - mangoapp_msg_v1.fsrSharpness = g_upscaleFilterSharpness; - mangoapp_msg_v1.app_frametime_ns = app_frametime_ns; - mangoapp_msg_v1.latency_ns = latency_ns; - mangoapp_msg_v1.pid = focusWindow_pid; - mangoapp_msg_v1.outputWidth = g_nOutputWidth; - mangoapp_msg_v1.outputHeight = g_nOutputHeight; - mangoapp_msg_v1.displayRefresh = (uint16_t) gamescope::ConvertmHzToHz( g_nOutputRefresh ); - mangoapp_msg_v1.bAppWantsHDR = g_bAppWantsHDRCached; + mangoapp_msg_v1.fsrUpscale = g_bFSRActive; + mangoapp_msg_v1.fsrSharpness = g_upscaleFilterSharpness; + mangoapp_msg_v1.app_frametime_ns = app_frametime_ns; + mangoapp_msg_v1.latency_ns = latency_ns; + mangoapp_msg_v1.pid = focusWindow_pid; + mangoapp_msg_v1.outputWidth = g_nOutputWidth; + mangoapp_msg_v1.outputHeight = g_nOutputHeight; + mangoapp_msg_v1.displayRefresh = + ( uint16_t )gamescope::ConvertmHzToHz( g_nOutputRefresh ); + mangoapp_msg_v1.bAppWantsHDR = g_bAppWantsHDRCached; mangoapp_msg_v1.bSteamFocused = g_focusedBaseAppId == 769; - memset(mangoapp_msg_v1.engineName, 0, sizeof(mangoapp_msg_v1.engineName)); + memset( + mangoapp_msg_v1.engineName, 0, sizeof( mangoapp_msg_v1.engineName ) ); std::shared_ptr engine = focusWindow_engine; - if (engine) - engine->copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char)); + if ( engine ) + engine->copy( + mangoapp_msg_v1.engineName, + sizeof( mangoapp_msg_v1.engineName ) / sizeof( char ) ); else - std::string("gamescope").copy(mangoapp_msg_v1.engineName, sizeof(mangoapp_msg_v1.engineName) / sizeof(char)); - msgsnd(msgid, &mangoapp_msg_v1, sizeof(mangoapp_msg_v1) - sizeof(mangoapp_msg_v1.hdr.msg_type), IPC_NOWAIT); + std::string( "gamescope" ) + .copy( + mangoapp_msg_v1.engineName, + sizeof( mangoapp_msg_v1.engineName ) / sizeof( char ) ); + msgsnd( + msgid, + &mangoapp_msg_v1, + sizeof( mangoapp_msg_v1 ) - sizeof( mangoapp_msg_v1.hdr.msg_type ), + IPC_NOWAIT ); } -extern uint64_t g_uCurrentBasePlaneCommitID; -extern bool g_bCurrentBasePlaneIsFifo; -extern uint32_t g_uCurrentBasePlaneAppID; +extern uint64_t g_uCurrentBasePlaneCommitID; +extern bool g_bCurrentBasePlaneIsFifo; +extern uint32_t g_uCurrentBasePlaneAppID; extern gamescope::ConVar cv_mangoapp_use_output_timing; void mangoapp_output_update( uint64_t vblanktime ) { - if ( !g_bCurrentBasePlaneIsFifo ) - { - return; - } + if ( !g_bCurrentBasePlaneIsFifo ) { return; } - static uint64_t s_uLastBasePlaneCommitID = 0; - if ( s_uLastBasePlaneCommitID != g_uCurrentBasePlaneCommitID ) - { - static uint64_t s_uLastBasePlaneUpdateVBlankTime = vblanktime; - uint64_t last_frametime = s_uLastBasePlaneUpdateVBlankTime; - uint64_t frametime = vblanktime - last_frametime; - s_uLastBasePlaneUpdateVBlankTime = vblanktime; - s_uLastBasePlaneCommitID = g_uCurrentBasePlaneCommitID; - if ( last_frametime > vblanktime ) - return; + static uint64_t s_uLastBasePlaneCommitID = 0; + if ( s_uLastBasePlaneCommitID != g_uCurrentBasePlaneCommitID ) + { + static uint64_t s_uLastBasePlaneUpdateVBlankTime = vblanktime; + uint64_t last_frametime = s_uLastBasePlaneUpdateVBlankTime; + uint64_t frametime = vblanktime - last_frametime; + s_uLastBasePlaneUpdateVBlankTime = vblanktime; + s_uLastBasePlaneCommitID = g_uCurrentBasePlaneCommitID; + if ( last_frametime > vblanktime ) return; - mangoapp_update( frametime, uint64_t(~0ull), uint64_t(~0ull) ); + mangoapp_update( frametime, uint64_t( ~0ull ), uint64_t( ~0ull ) ); if ( cv_mangoapp_use_output_timing ) { - wlserver_lock(); + wlserver_lock( ); wlserver_app_presented( g_uCurrentBasePlaneAppID, frametime ); - wlserver_unlock(); + wlserver_unlock( ); } - } + } } diff --git a/src/meson.build b/src/meson.build index 806fe5d1e6..4e90806dd7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,140 +10,166 @@ dep_xres = dependency('xres') dep_xmu = dependency('xmu') dep_xi = dependency('xi') -drm_dep = dependency('libdrm', version: '>= 2.4.113', required: get_option('drm_backend')) +drm_dep = dependency('libdrm', version : '>= 2.4.113', required : get_option('drm_backend')) eis_dep = dependency('libeis-1.0', required : get_option('input_emulation')) -wayland_server = dependency('wayland-server', version: '>=1.21') -wayland_protos = dependency('wayland-protocols', version: '>=1.17') +wayland_server = dependency('wayland-server', version : '>=1.21') +wayland_protos = dependency('wayland-protocols', version : '>=1.17') xkbcommon = dependency('xkbcommon') thread_dep = dependency('threads') -cap_dep = dependency('libcap', required: get_option('rt_cap')) -epoll_dep = dependency('epoll-shim', required: false) -sdl2_dep = dependency('SDL2', required: get_option('sdl2_backend')) -avif_dep = dependency('libavif', version: '>=1.0.0', required: get_option('avif_screenshots')) +cap_dep = dependency('libcap', required : get_option('rt_cap')) +epoll_dep = dependency('epoll-shim', required : false) +sdl2_dep = dependency('SDL2', required : get_option('sdl2_backend')) +avif_dep = dependency('libavif', version : '>=1.0.0', required : get_option('avif_screenshots')) pixman_dep = dependency('pixman-1') udev_dep = dependency('libudev') -wlroots_dep = dependency( - 'wlroots-0.18', - version: ['>= 0.18.0', '< 0.19.0'], - fallback: 'wlroots', - default_options: ['default_library=static', 'examples=false', 'xwayland=enabled', 'backends=libinput', 'renderers=[]', 'allocators=[]', 'session=enabled'], +di_dep = dependency( + 'libdisplay-info', + version : ['>= 0.0.0', '< 0.4.0'], + fallback : 'libdisplay-info', + default_options : ['default_library=static'], ) -displayinfo_dep = dependency( - 'libdisplay-info', - version: ['>= 0.0.0', '< 0.4.0'], - fallback: 'libdisplay-info', - default_options: ['default_library=static'], +wlroots_dep = dependency( + 'wlroots-0.18', + version : ['>= 0.18.0', '< 0.19.0'], + fallback : 'wlroots', + default_options : [ + 'default_library=static', + 'examples=false', + 'xwayland=enabled', + 'backends=auto', + 'renderers=auto', + 'allocators=auto', + 'session=enabled', + 'libliftoff=enabled' + ], ) libdecor_dep = dependency('libdecor-0') -glsl_compiler = find_program('glslang', 'glslangValidator', native: true) +glsl_compiler = find_program('glslang', 'glslangValidator', native : true) -# Use --depfile to rebuild shaders when included files have changed. Sadly debian based -# distros don't have up-to-date glslang so we need to check for support first. -if run_command(glsl_compiler, ['--version', '--depfile', 'dummy.dep'], check: false).returncode() == 0 - glsl_generator = generator( +# Use-- depfile to rebuild shaders when included files have changed. Sadly +# Debian-based distros don't have up-to-date glslang so we need to check for +# support first. +if run_command( glsl_compiler, - output : ['@BASENAME@.h'], - arguments : ['-V', '@INPUT@', '--vn', '@BASENAME@', '-o', '@OUTPUT@', '--depfile', '@DEPFILE@', '--quiet'], - depfile : '@BASENAME@.h.d', - ) + ['--version', '--depfile', 'dummy.dep'], + check : false +).returncode() == 0 + glsl_generator = generator( + glsl_compiler, + output : ['@BASENAME@.h'], + arguments : [ + '-V', + '@INPUT@', + '--vn', + '@BASENAME@', + '-o', + '@OUTPUT@', + '--depfile', + '@DEPFILE@', + '--quiet' + ], + depfile : '@BASENAME@.h.d', + ) else - glsl_generator = generator( - glsl_compiler, - output : ['@BASENAME@.h'], - arguments : ['-V', '@INPUT@', '--vn', '@BASENAME@', '-o', '@OUTPUT@'], - ) + glsl_generator = generator( + glsl_compiler, + output : ['@BASENAME@.h'], + arguments : ['-V', '@INPUT@', '--vn', '@BASENAME@', '-o', '@OUTPUT@'], + ) endif shader_src = [ - 'shaders/cs_composite_blit.comp', - 'shaders/cs_composite_blur.comp', - 'shaders/cs_composite_blur_cond.comp', - 'shaders/cs_composite_rcas.comp', - 'shaders/cs_easu.comp', - 'shaders/cs_easu_fp16.comp', - 'shaders/cs_gaussian_blur_horizontal.comp', - 'shaders/cs_nis.comp', - 'shaders/cs_nis_fp16.comp', - 'shaders/cs_rgb_to_nv12.comp', + 'shaders/cs_composite_blit.comp', + 'shaders/cs_composite_blur.comp', + 'shaders/cs_composite_blur_cond.comp', + 'shaders/cs_composite_rcas.comp', + 'shaders/cs_easu.comp', + 'shaders/cs_easu_fp16.comp', + 'shaders/cs_gaussian_blur_horizontal.comp', + 'shaders/cs_nis.comp', + 'shaders/cs_nis_fp16.comp', + 'shaders/cs_rgb_to_nv12.comp', ] spirv_shaders = glsl_generator.process(shader_src) reshade_src = [ - 'reshade/source/effect_codegen_spirv.cpp', - 'reshade/source/effect_expression.cpp', - 'reshade/source/effect_lexer.cpp', - 'reshade/source/effect_parser_exp.cpp', - 'reshade/source/effect_parser_stmt.cpp', - 'reshade/source/effect_preprocessor.cpp', - 'reshade/source/effect_symbol_table.cpp', + 'reshade/source/effect_codegen_spirv.cpp', + 'reshade/source/effect_expression.cpp', + 'reshade/source/effect_lexer.cpp', + 'reshade/source/effect_parser_exp.cpp', + 'reshade/source/effect_parser_stmt.cpp', + 'reshade/source/effect_preprocessor.cpp', + 'reshade/source/effect_symbol_table.cpp', ] -reshade_include = include_directories([ - 'reshade/source', - 'reshade/include', - '../thirdparty/SPIRV-Headers/include/spirv/unified1' -]) +reshade_include = include_directories( + [ + 'reshade/source', + 'reshade/include', + '../thirdparty/SPIRV-Headers/include/spirv/unified1' + ] +) sol2_include = include_directories(['../thirdparty']) required_wlroots_features = ['xwayland'] src = [ - 'Backends/HeadlessBackend.cpp', - 'Backends/WaylandBackend.cpp', - 'Utils/DirHelpers.cpp', - 'Utils/TempFiles.cpp', - 'Utils/Version.cpp', - 'Utils/Process.cpp', - 'Script/Script.cpp', - 'BufferMemo.cpp', - 'steamcompmgr.cpp', - 'convar.cpp', - 'convar_script.cpp', - 'commit.cpp', - 'color_helpers.cpp', - 'main.cpp', - 'edid.cpp', - 'wlserver.cpp', - 'vblankmanager.cpp', - 'rendervulkan.cpp', - 'log.cpp', - 'ime.cpp', - 'mangoapp.cpp', - 'Timeline.cpp', - 'reshade_effect_manager.cpp', - 'backend.cpp', - 'x11cursor.cpp', - 'InputEmulation.cpp', - 'LibInputHandler.cpp', + 'Backends/HeadlessBackend.cpp', + 'Backends/WaylandBackend.cpp', + 'Utils/DirHelpers.cpp', + 'Utils/TempFiles.cpp', + 'Utils/Version.cpp', + 'Utils/Process.cpp', + 'Script/Script.cpp', + 'BufferMemo.cpp', + 'steamcompmgr.cpp', + 'convar.cpp', + 'convar_script.cpp', + 'commit.cpp', + 'color_helpers.cpp', + 'main.cpp', + 'edid.cpp', + 'wlserver.cpp', + 'vblankmanager.cpp', + 'rendervulkan.cpp', + 'log.cpp', + 'ime.cpp', + 'mangoapp.cpp', + 'Timeline.cpp', + 'reshade_effect_manager.cpp', + 'backend.cpp', + 'x11cursor.cpp', + 'InputEmulation.cpp', + 'LibInputHandler.cpp', ] -luajit_dep = dependency( 'luajit' ) -libinput_dep = dependency('libinput', required: true) +luajit_dep = dependency('luajit') +libinput_dep = dependency('libinput', required : true) gamescope_cpp_args = [] if drm_dep.found() - src += 'Backends/DRMBackend.cpp' - src += 'modegen.cpp' - required_wlroots_features += 'libinput_backend' - liftoff_dep = dependency( - 'libliftoff', - version: ['>= 0.5.0', '< 0.6.0'], - fallback: 'libliftoff', - default_options: ['default_library=static'], - ) + src += 'Backends/DRMBackend.cpp' + src += 'modegen.cpp' + required_wlroots_features += 'libinput_backend' + liftoff = dependency( + 'libliftoff', + version : ['>= 0.5.0', '< 0.6.0'], + fallback : 'libliftoff', + default_options : ['default_library=static'], + ) else - liftoff_dep = dependency('', required: false) + liftoff = dependency('', required : false) endif if sdl2_dep.found() - src += 'Backends/SDLBackend.cpp' + src += 'Backends/SDLBackend.cpp' endif gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) @@ -158,17 +184,17 @@ src += protocols_server_src src += protocols_client_src if pipewire_dep.found() - src += 'pipewire.cpp' + src += 'pipewire.cpp' endif if openvr_dep.found() - src += 'Backends/OpenVRBackend.cpp' + src += 'Backends/OpenVRBackend.cpp' endif foreach feat : required_wlroots_features - if wlroots_dep.get_variable('have_' + feat) != 'true' - error('Cannot use wlroots built without ' + feat + ' support') - endif + if wlroots_dep.get_variable('have_' + feat) != 'true' + error('Cannot use wlroots built without ' + feat + ' support') + endif endforeach cc = meson.get_compiler('c') @@ -176,55 +202,55 @@ compiler_name = cc.get_id() compiler_version = cc.version() vcs_tag_cmd = ['git', 'describe', '--always', '--tags', '--dirty=+'] -vcs_tag = run_command(vcs_tag_cmd, check: false).stdout().strip() +vcs_tag = run_command(vcs_tag_cmd, check : false).stdout().strip() version_tag = vcs_tag + ' (' + compiler_name + ' ' + compiler_version + ')' gamescope_version_conf = configuration_data() gamescope_version_conf.set('VCS_TAG', version_tag) gamescope_version = configure_file( - input : 'GamescopeVersion.h.in', - output : 'GamescopeVersion.h', - configuration : gamescope_version_conf + input : 'GamescopeVersion.h.in', + output : 'GamescopeVersion.h', + configuration : gamescope_version_conf ) - executable( +executable( 'gamescope', src, reshade_src, gamescope_version, include_directories : [reshade_include, sol2_include], - dependencies: [ - dep_wayland, dep_x11, dep_xdamage, dep_xcomposite, dep_xrender, dep_xext, dep_xfixes, - dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, - xkbcommon, thread_dep, sdl2_dep, wlroots_dep, - vulkan_dep, liftoff_dep, dep_xtst, dep_xmu, cap_dep, epoll_dep, pipewire_dep, librt_dep, - stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi, - libdecor_dep, eis_dep, luajit_dep, libinput_dep, pixman_dep, udev_dep, + dependencies : [ + dep_wayland, dep_x11, dep_xdamage, dep_xcomposite, dep_xrender, dep_xext, dep_xfixes, + dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, + xkbcommon, thread_dep, sdl2_dep, wlroots_dep, + vulkan_dep, liftoff, dep_xtst, dep_xmu, cap_dep, epoll_dep, pipewire_dep, librt_dep, + stb_dep, di_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi, + libdecor_dep, eis_dep, luajit_dep, libinput_dep, pixman_dep, udev_dep, ], - install: true, - cpp_args: gamescope_cpp_args, - ) + install : true, + cpp_args : gamescope_cpp_args, +) gamescope_core_src = [ - 'convar.cpp', - 'log.cpp', - 'Utils/DirHelpers.cpp', - 'Utils/Process.cpp', - 'Utils/Version.cpp', + 'convar.cpp', + 'log.cpp', + 'Utils/DirHelpers.cpp', + 'Utils/Process.cpp', + 'Utils/Version.cpp', ] if pipewire_dep.found() - executable( 'gamescopestream', ['Apps/gamescopestream.cpp'], gamescope_core_src, gamescope_version, protocols_client_src, dependencies: [ pipewire_dep, dep_wayland, libdecor_dep, cap_dep ], install: true ) + executable('gamescopestream', ['Apps/gamescopestream.cpp'], gamescope_core_src, gamescope_version, protocols_client_src, dependencies : [pipewire_dep, dep_wayland, libdecor_dep, cap_dep], install : true) endif -executable('gamescopereaper', ['Apps/gamescopereaper.cpp', gamescope_core_src], gamescope_version, dependencies: [cap_dep], install:true ) +executable('gamescopereaper', ['Apps/gamescopereaper.cpp', gamescope_core_src], gamescope_version, dependencies : [cap_dep], install : true) -benchmark_dep = dependency('benchmark', required: get_option('benchmark'), disabler: true) -executable('gamescope_color_microbench', ['color_bench.cpp', 'color_helpers.cpp'], gamescope_core_src, gamescope_version, dependencies:[benchmark_dep, glm_dep, cap_dep]) +benchmark_dep = dependency('benchmark', required : get_option('benchmark'), disabler : true) +executable('gamescope_color_microbench', ['color_bench.cpp', 'color_helpers.cpp'], gamescope_core_src, gamescope_version, dependencies : [benchmark_dep, glm_dep, cap_dep]) -executable('gamescope_color_tests', ['color_tests.cpp', 'color_helpers.cpp'], gamescope_core_src, gamescope_version, dependencies:[glm_dep, cap_dep]) +executable('gamescope_color_tests', ['color_tests.cpp', 'color_helpers.cpp'], gamescope_core_src, gamescope_version, dependencies : [glm_dep, cap_dep]) -executable('gamescopectl', ['Apps/gamescopectl.cpp'], gamescope_core_src, gamescope_version, protocols_client_src, dependencies: [dep_wayland, cap_dep], install:true ) +executable('gamescopectl', ['Apps/gamescopectl.cpp'], gamescope_core_src, gamescope_version, protocols_client_src, dependencies : [dep_wayland, cap_dep], install : true) -executable('gamescope-type', ['Apps/gamescope_type.c'], protocols_client_src, dependencies: [dep_wayland, cap_dep], install:true ) +executable('gamescope-type', ['Apps/gamescope_type.c'], protocols_client_src, dependencies : [dep_wayland, cap_dep], install : true) -executable('gamescope_hotkey_example', ['Apps/gamescope_hotkey_example.cpp'], gamescope_core_src, gamescope_version, protocols_client_src, dependencies: [dep_wayland, xkbcommon, cap_dep], install: false ) +executable('gamescope_hotkey_example', ['Apps/gamescope_hotkey_example.cpp'], gamescope_core_src, gamescope_version, protocols_client_src, dependencies : [dep_wayland, xkbcommon, cap_dep], install : false) diff --git a/src/messagey.h b/src/messagey.h index 640fb4d72f..d7587080d2 100644 --- a/src/messagey.h +++ b/src/messagey.h @@ -21,280 +21,336 @@ #pragma once -#include -#include #include #include +#include +#include -#include -#include #include +#include +#include -namespace messagey { +namespace messagey +{ static constexpr int MaxButtons = 8; namespace MessageBoxFlag { - static constexpr uint32_t Error = (1u << 0); - static constexpr uint32_t Warning = (1u << 1); + static constexpr uint32_t Error = ( 1u << 0 ); + static constexpr uint32_t Warning = ( 1u << 1 ); - static constexpr uint32_t Information = (1u << 2); - static constexpr uint32_t ButtonsLeftToRight = (1u << 3); - static constexpr uint32_t ButtonsRightToLeft = (1u << 4); + static constexpr uint32_t Information = ( 1u << 2 ); + static constexpr uint32_t ButtonsLeftToRight = ( 1u << 3 ); + static constexpr uint32_t ButtonsRightToLeft = ( 1u << 4 ); - static constexpr uint32_t Simple_OK = (1u << 5); - static constexpr uint32_t Simple_Cancel = (1u << 6); - } + static constexpr uint32_t Simple_OK = ( 1u << 5 ); + static constexpr uint32_t Simple_Cancel = ( 1u << 6 ); + } // namespace MessageBoxFlag using MessageBoxFlags = uint32_t; - namespace MessageBoxButtonFlag { - static constexpr uint32_t ReturnKeyDefault = (1u << 0); - static constexpr uint32_t EscapeKeyDefault = (1u << 1); - } + namespace MessageBoxButtonFlag + { + static constexpr uint32_t ReturnKeyDefault = ( 1u << 0 ); + static constexpr uint32_t EscapeKeyDefault = ( 1u << 1 ); + } // namespace MessageBoxButtonFlag using MessageBoxButtonFlags = uint32_t; struct MessageBoxButtonData { - uint32_t flags; - int buttonid; - const char* text; + uint32_t flags; + int buttonid; + const char *text; }; struct MessageBoxData { uint32_t flags; - const char* title; - const char* message; + const char *title; + const char *message; - int numbuttons; + int numbuttons; const MessageBoxButtonData *buttons; }; struct ErrorData { bool valid = false; - char str[256]; + char str[ 256 ]; }; - inline ErrorData* GetErrBuf() + inline ErrorData *GetErrBuf( ) { static thread_local ErrorData err; return &err; } - inline ErrorData* GetError() + inline ErrorData *GetError( ) { - ErrorData *err = GetErrBuf(); - if (!err->valid) - return nullptr; + ErrorData *err = GetErrBuf( ); + if ( !err->valid ) return nullptr; return err; } - inline int SetError(const char *fmt, ...) + inline int SetError( const char *fmt, ... ) { - if (fmt != nullptr) { - va_list ap; - ErrorData *error = GetErrBuf(); + if ( fmt != nullptr ) + { + va_list ap; + ErrorData *error = GetErrBuf( ); error->valid = true; - va_start(ap, fmt); - vsnprintf(error->str, sizeof(ErrorData::str), fmt, ap); - va_end(ap); + va_start( ap, fmt ); + vsnprintf( error->str, sizeof( ErrorData::str ), fmt, ap ); + va_end( ap ); } return -1; } - inline int Show(const MessageBoxData *messageboxdata, int *buttonid) + inline int Show( const MessageBoxData *messageboxdata, int *buttonid ) { - int fd_pipe[2]; /* fd_pipe[0]: read end of pipe, fd_pipe[1]: write end of pipe */ + int fd_pipe[ 2 ]; /* fd_pipe[0]: read end of pipe, fd_pipe[1]: write end + of pipe */ pid_t pid1; - const char *zenityDisableEnv = getenv("GAMESCOPE_ZENITY_DISABLE"); - if (zenityDisableEnv && *zenityDisableEnv && atoi(zenityDisableEnv) != 0) + const char *zenityDisableEnv = getenv( "GAMESCOPE_ZENITY_DISABLE" ); + if ( zenityDisableEnv && *zenityDisableEnv && + atoi( zenityDisableEnv ) != 0 ) return 0; - if (messageboxdata->numbuttons > MaxButtons) { - return SetError("Too many buttons (%d max allowed)", MaxButtons); + if ( messageboxdata->numbuttons > MaxButtons ) + { + return SetError( "Too many buttons (%d max allowed)", MaxButtons ); } - if (pipe(fd_pipe) != 0) { /* create a pipe */ - return SetError("pipe() failed: %s", strerror(errno)); + if ( pipe( fd_pipe ) != 0 ) + { /* create a pipe */ + return SetError( "pipe() failed: %s", strerror( errno ) ); } - pid1 = fork(); - if (pid1 == 0) { /* child process */ - int argc = 5, i; - const char* argv[5 + 2/* icon name */ + 2/* title */ + 2/* message */ + 2*MaxButtons + 1/* nullptr */] = { - "zenity", "--question", "--switch", "--no-wrap", "--no-markup" - }; - - close(fd_pipe[0]); /* no reading from pipe */ + pid1 = fork( ); + if ( pid1 == 0 ) + { /* child process */ + int argc = 5, i; + const char *argv + [ 5 + 2 /* icon name */ + 2 /* title */ + 2 /* message */ + + 2 * MaxButtons + 1 /* nullptr */ ] = { "zenity", + "--question", + "--switch", + "--no-wrap", + "--no-markup" }; + + close( fd_pipe[ 0 ] ); /* no reading from pipe */ /* write stdout in pipe */ - if (dup2(fd_pipe[1], STDOUT_FILENO) == -1) { - _exit(128); - } + if ( dup2( fd_pipe[ 1 ], STDOUT_FILENO ) == -1 ) { _exit( 128 ); } - argv[argc++] = "--icon-name"; - if (messageboxdata->flags & MessageBoxFlag::Error) - argv[argc++] = "dialog-error"; - else if (messageboxdata->flags & MessageBoxFlag::Warning) - argv[argc++] = "dialog-warning"; - else if (messageboxdata->flags & MessageBoxFlag::Information) - argv[argc++] = "dialog-information"; - - if (messageboxdata->title && messageboxdata->title[0]) { - argv[argc++] = "--title"; - argv[argc++] = messageboxdata->title; - } else { - argv[argc++] = "--title=\"\""; + argv[ argc++ ] = "--icon-name"; + if ( messageboxdata->flags & MessageBoxFlag::Error ) + argv[ argc++ ] = "dialog-error"; + else if ( messageboxdata->flags & MessageBoxFlag::Warning ) + argv[ argc++ ] = "dialog-warning"; + else if ( messageboxdata->flags & MessageBoxFlag::Information ) + argv[ argc++ ] = "dialog-information"; + + if ( messageboxdata->title && messageboxdata->title[ 0 ] ) + { + argv[ argc++ ] = "--title"; + argv[ argc++ ] = messageboxdata->title; + } + else + { + argv[ argc++ ] = "--title=\"\""; } - if (messageboxdata->message && messageboxdata->message[0]) { - argv[argc++] = "--text"; - argv[argc++] = messageboxdata->message; - } else { - argv[argc++] = "--text=\"\""; + if ( messageboxdata->message && messageboxdata->message[ 0 ] ) + { + argv[ argc++ ] = "--text"; + argv[ argc++ ] = messageboxdata->message; + } + else + { + argv[ argc++ ] = "--text=\"\""; } - for (i = 0; i < messageboxdata->numbuttons; ++i) { - if (messageboxdata->buttons[i].text && messageboxdata->buttons[i].text[0]) { - argv[argc++] = "--extra-button"; - argv[argc++] = messageboxdata->buttons[i].text; - } else { - argv[argc++] = "--extra-button=\"\""; + for ( i = 0; i < messageboxdata->numbuttons; ++i ) + { + if ( messageboxdata->buttons[ i ].text && + messageboxdata->buttons[ i ].text[ 0 ] ) + { + argv[ argc++ ] = "--extra-button"; + argv[ argc++ ] = messageboxdata->buttons[ i ].text; + } + else + { + argv[ argc++ ] = "--extra-button=\"\""; } } - argv[argc] = nullptr; + argv[ argc ] = nullptr; /* const casting argv is fine: - * https://pubs.opengroup.org/onlinepubs/9699919799/functions/fexecve.html -> rational - */ - execvp("zenity", (char **)argv); - _exit(129); - } else if (pid1 < 0) { - close(fd_pipe[0]); - close(fd_pipe[1]); - return SetError("fork() failed: %s", strerror(errno)); - } else { + * https://pubs.opengroup.org/onlinepubs/9699919799/functions/fexecve.html + * -> rational + */ + execvp( "zenity", ( char ** )argv ); + _exit( 129 ); + } + else if ( pid1 < 0 ) + { + close( fd_pipe[ 0 ] ); + close( fd_pipe[ 1 ] ); + return SetError( "fork() failed: %s", strerror( errno ) ); + } + else + { int status; - if (waitpid(pid1, &status, 0) == pid1) { - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) < 128) { - int i; + if ( waitpid( pid1, &status, 0 ) == pid1 ) + { + if ( WIFEXITED( status ) ) + { + if ( WEXITSTATUS( status ) < 128 ) + { + int i; size_t output_len = 1; - char* output = nullptr; - char* tmp = nullptr; - FILE* stdout = nullptr; - - close(fd_pipe[1]); /* no writing to pipe */ - /* At this point, if no button ID is needed, we can just bail as soon as the - * process has completed. - */ - if (buttonid == NULL) { - close(fd_pipe[0]); + char *output = nullptr; + char *tmp = nullptr; + FILE *stdout = nullptr; + + close( fd_pipe[ 1 ] ); /* no writing to pipe */ + /* At this point, if no button ID is needed, we can just + * bail as soon as the process has completed. + */ + if ( buttonid == NULL ) + { + close( fd_pipe[ 0 ] ); return 0; } *buttonid = -1; /* find button with longest text */ - for (i = 0; i < messageboxdata->numbuttons; ++i) { - if (messageboxdata->buttons[i].text != NULL) { - const size_t button_len = strlen(messageboxdata->buttons[i].text); - if (button_len > output_len) { + for ( i = 0; i < messageboxdata->numbuttons; ++i ) + { + if ( messageboxdata->buttons[ i ].text != NULL ) + { + const size_t button_len = + strlen( messageboxdata->buttons[ i ].text ); + if ( button_len > output_len ) + { output_len = button_len; } } } - output = (char *)malloc(output_len + 1); - if (!output) { - close(fd_pipe[0]); - return SetError("Out of memory"); + output = ( char * )malloc( output_len + 1 ); + if ( !output ) + { + close( fd_pipe[ 0 ] ); + return SetError( "Out of memory" ); } - output[0] = '\0'; - - stdout = fdopen(fd_pipe[0], "r"); - if (!stdout) { - free(output); - close(fd_pipe[0]); - return SetError("Couldn't open pipe for reading: %s", strerror(errno)); + output[ 0 ] = '\0'; + + stdout = fdopen( fd_pipe[ 0 ], "r" ); + if ( !stdout ) + { + free( output ); + close( fd_pipe[ 0 ] ); + return SetError( + "Couldn't open pipe for reading: %s", + strerror( errno ) ); } - tmp = fgets(output, output_len + 1, stdout); - fclose(stdout); + tmp = fgets( output, output_len + 1, stdout ); + fclose( stdout ); - if ((tmp == NULL) || (*tmp == '\0') || (*tmp == '\n')) { - free(output); + if ( ( tmp == NULL ) || ( *tmp == '\0' ) || + ( *tmp == '\n' ) ) + { + free( output ); return 0; /* User simply closed the dialog */ } /* It likes to add a newline... */ - tmp = strrchr(output, '\n'); - if (tmp != NULL) { - *tmp = '\0'; - } + tmp = strrchr( output, '\n' ); + if ( tmp != NULL ) { *tmp = '\0'; } /* Check which button got pressed */ - for (i = 0; i < messageboxdata->numbuttons; i += 1) { - if (messageboxdata->buttons[i].text != NULL) { - if (strcmp(output, messageboxdata->buttons[i].text) == 0) { - *buttonid = messageboxdata->buttons[i].buttonid; + for ( i = 0; i < messageboxdata->numbuttons; i += 1 ) + { + if ( messageboxdata->buttons[ i ].text != NULL ) + { + if ( strcmp( + output, + messageboxdata->buttons[ i ].text ) == + 0 ) + { + *buttonid = + messageboxdata->buttons[ i ].buttonid; break; } } } - free(output); - return 0; /* success! */ - } else { - return SetError("zenity reported error or failed to launch: %d", WEXITSTATUS(status)); + free( output ); + return 0; /* success! */ } - } else { - return SetError("zenity failed for some reason"); + else + { + return SetError( + "zenity reported error or failed to launch: %d", + WEXITSTATUS( status ) ); + } + } + else + { + return SetError( "zenity failed for some reason" ); } - } else { - return SetError("Waiting on zenity failed: %s", strerror(errno)); + } + else + { + return SetError( + "Waiting on zenity failed: %s", strerror( errno ) ); } } } - inline int ShowSimple(const char *message, const char *caption, MessageBoxFlags flags, int *buttonid) + inline int ShowSimple( + const char *message, + const char *caption, + MessageBoxFlags flags, + int *buttonid ) { - int buttonCount = 0; - MessageBoxButtonData buttons[2]; + int buttonCount = 0; + MessageBoxButtonData buttons[ 2 ]; - bool hasCancel = !!(flags & MessageBoxFlag::Simple_Cancel); - bool hasOK = !!(flags & MessageBoxFlag::Simple_OK); + bool hasCancel = !!( flags & MessageBoxFlag::Simple_Cancel ); + bool hasOK = !!( flags & MessageBoxFlag::Simple_OK ); - if (hasCancel) { - buttons[buttonCount++] = - { - .flags = MessageBoxButtonFlag::EscapeKeyDefault | (hasOK ? 0 : MessageBoxButtonFlag::ReturnKeyDefault), + if ( hasCancel ) + { + buttons[ buttonCount++ ] = { + .flags = MessageBoxButtonFlag::EscapeKeyDefault | + ( hasOK ? 0 : MessageBoxButtonFlag::ReturnKeyDefault ), .buttonid = 0, .text = "Cancel", }; } - if (hasOK) { - buttons[buttonCount++] = - { + if ( hasOK ) + { + buttons[ buttonCount++ ] = { .flags = MessageBoxButtonFlag::ReturnKeyDefault, .buttonid = 1, .text = "OK", }; } - MessageBoxData data = - { - .flags = flags, - .title = caption, - .message = message, + MessageBoxData data = { + .flags = flags, + .title = caption, + .message = message, .numbuttons = buttonCount, .buttons = buttons, }; - return Show(&data, buttonid); + return Show( &data, buttonid ); } -} - +} // namespace messagey diff --git a/src/modegen.cpp b/src/modegen.cpp index 1cf317c6c9..8f4e11ec6d 100644 --- a/src/modegen.cpp +++ b/src/modegen.cpp @@ -57,9 +57,9 @@ /* Scaling factor weighting - default 20 */ #define CVT_J_FACTOR 20 -#define CVT_M_PRIME CVT_M_FACTOR * CVT_K_FACTOR / 256 -#define CVT_C_PRIME (CVT_C_FACTOR - CVT_J_FACTOR) * CVT_K_FACTOR / 256 + \ - CVT_J_FACTOR +#define CVT_M_PRIME CVT_M_FACTOR *CVT_K_FACTOR / 256 +#define CVT_C_PRIME \ + ( CVT_C_FACTOR - CVT_J_FACTOR ) * CVT_K_FACTOR / 256 + CVT_J_FACTOR /* Minimum vertical blanking interval time (µs) - default 460 */ #define CVT_RB_MIN_VBLANK 460.0 @@ -89,194 +89,230 @@ * * This function is borrowed from xorg-xserver's xf86CVTmode. */ -void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, - float vrefresh, bool reduced, bool interlaced) { - bool margins = false; - float vfield_rate, hperiod; - int hdisplay_rnd, hmargin; - int vdisplay_rnd, vmargin, vsync; - float interlace; /* Please rename this */ - - /* CVT default is 60.0Hz */ - if (!vrefresh) { - vrefresh = 60.0; - } - - /* 1. Required field rate */ - if (interlaced) { - vfield_rate = vrefresh * 2; - } else { - vfield_rate = vrefresh; - } - - /* 2. Horizontal pixels */ - hdisplay_rnd = hdisplay - (hdisplay % CVT_H_GRANULARITY); - - /* 3. Determine left and right borders */ - if (margins) { - /* right margin is actually exactly the same as left */ - hmargin = (((float) hdisplay_rnd) * CVT_MARGIN_PERCENTAGE / 100.0); - hmargin -= hmargin % CVT_H_GRANULARITY; - } else { - hmargin = 0; - } - - /* 4. Find total active pixels */ - mode->hdisplay = hdisplay_rnd + 2 * hmargin; - - /* 5. Find number of lines per field */ - if (interlaced) { - vdisplay_rnd = vdisplay / 2; - } else { - vdisplay_rnd = vdisplay; - } - - /* 6. Find top and bottom margins */ - /* nope. */ - if (margins) { - /* top and bottom margins are equal again. */ - vmargin = (((float) vdisplay_rnd) * CVT_MARGIN_PERCENTAGE / 100.0); - } else { - vmargin = 0; - } - - mode->vdisplay = vdisplay + 2 * vmargin; - - /* 7. interlace */ - if (interlaced) { - interlace = 0.5; - } else { - interlace = 0.0; - } - - /* Determine vsync Width from aspect ratio */ - if (!(vdisplay % 3) && ((vdisplay * 4 / 3) == hdisplay)) { - vsync = 4; - } else if (!(vdisplay % 9) && ((vdisplay * 16 / 9) == hdisplay)) { - vsync = 5; - } else if (!(vdisplay % 10) && ((vdisplay * 16 / 10) == hdisplay)) { - vsync = 6; - } else if (!(vdisplay % 4) && ((vdisplay * 5 / 4) == hdisplay)) { - vsync = 7; - } else if (!(vdisplay % 9) && ((vdisplay * 15 / 9) == hdisplay)) { - vsync = 7; - } else { /* Custom */ - vsync = 10; - } - - if (!reduced) { /* simplified GTF calculation */ - float hblank_percentage; - int vsync_and_back_porch, vblank_porch; - int hblank; - - /* 8. Estimated Horizontal period */ - hperiod = ((float) (1000000.0 / vfield_rate - CVT_MIN_VSYNC_BP)) / - (vdisplay_rnd + 2 * vmargin + CVT_MIN_V_PORCH + interlace); - - /* 9. Find number of lines in sync + backporch */ - if (((int) (CVT_MIN_VSYNC_BP / hperiod) + 1) < - (vsync + CVT_MIN_V_PORCH)) { - vsync_and_back_porch = vsync + CVT_MIN_V_PORCH; - } else { - vsync_and_back_porch = (int) (CVT_MIN_VSYNC_BP / hperiod) + 1; - } - - /* 10. Find number of lines in back porch */ - vblank_porch = vsync_and_back_porch - vsync; - (void) vblank_porch; - - /* 11. Find total number of lines in vertical field */ - mode->vtotal = vdisplay_rnd + 2 * vmargin + vsync_and_back_porch + interlace - + CVT_MIN_V_PORCH; - - /* 12. Find ideal blanking duty cycle from formula */ - hblank_percentage = CVT_C_PRIME - CVT_M_PRIME * hperiod / 1000.0; - - /* 13. Blanking time */ - if (hblank_percentage < 20) { - hblank_percentage = 20; - } - - hblank = mode->hdisplay * hblank_percentage / (100.0 - hblank_percentage); - hblank -= hblank % (2 * CVT_H_GRANULARITY); - - /* 14. Find total number of pixels in a line. */ - mode->htotal = mode->hdisplay + hblank; - - /* Fill in hsync values */ - mode->hsync_end = mode->hdisplay + hblank / 2; - - mode->hsync_start = mode->hsync_end - - (mode->htotal * CVT_HSYNC_PERCENTAGE) / 100; - mode->hsync_start += CVT_H_GRANULARITY - - mode->hsync_start % CVT_H_GRANULARITY; - - /* Fill in vsync values */ - mode->vsync_start = mode->vdisplay + CVT_MIN_V_PORCH; - mode->vsync_end = mode->vsync_start + vsync; - } else { /* reduced blanking */ - int vbi_lines; - - /* 8. Estimate Horizontal period. */ - hperiod = ((float) (1000000.0 / vfield_rate - CVT_RB_MIN_VBLANK)) / - (vdisplay_rnd + 2 * vmargin); - - /* 9. Find number of lines in vertical blanking */ - vbi_lines = ((float) CVT_RB_MIN_VBLANK) / hperiod + 1; - - /* 10. Check if vertical blanking is sufficient */ - if (vbi_lines < (CVT_RB_VFPORCH + vsync + CVT_MIN_V_BPORCH)) { - vbi_lines = CVT_RB_VFPORCH + vsync + CVT_MIN_V_BPORCH; - } - - /* 11. Find total number of lines in vertical field */ - mode->vtotal = vdisplay_rnd + 2 * vmargin + interlace + vbi_lines; - - /* 12. Find total number of pixels in a line */ - mode->htotal = mode->hdisplay + CVT_RB_H_BLANK; - - /* Fill in hsync values */ - mode->hsync_end = mode->hdisplay + CVT_RB_H_BLANK / 2; - mode->hsync_start = mode->hsync_end - CVT_RB_H_SYNC; - - /* Fill in vsync values */ - mode->vsync_start = mode->vdisplay + CVT_RB_VFPORCH; - mode->vsync_end = mode->vsync_start + vsync; - } - - /* 15/13. Find pixel clock frequency (kHz for xf86) */ - mode->clock = mode->htotal * 1000.0 / hperiod; - mode->clock -= mode->clock % CVT_CLOCK_STEP; - - /* 17/15. Find actual Field rate */ - mode->vrefresh = (1000.0 * ((float) mode->clock)) / - ((float) (mode->htotal * mode->vtotal)); - - /* 18/16. Find actual vertical frame frequency */ - /* ignore - just set the mode flag for interlaced */ - if (interlaced) { - mode->vtotal *= 2; - mode->flags |= DRM_MODE_FLAG_INTERLACE; - } - - snprintf(mode->name, sizeof(mode->name), "%dx%d", hdisplay, vdisplay); - - if (reduced) { - mode->flags |= DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC; - } else { - mode->flags |= DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC; - } +void generate_cvt_mode( + drmModeModeInfo *mode, + int hdisplay, + int vdisplay, + float vrefresh, + bool reduced, + bool interlaced ) +{ + bool margins = false; + float vfield_rate, hperiod; + int hdisplay_rnd, hmargin; + int vdisplay_rnd, vmargin, vsync; + float interlace; /* Please rename this */ + + /* CVT default is 60.0Hz */ + if ( !vrefresh ) { vrefresh = 60.0; } + + /* 1. Required field rate */ + if ( interlaced ) { vfield_rate = vrefresh * 2; } + else + { + vfield_rate = vrefresh; + } + + /* 2. Horizontal pixels */ + hdisplay_rnd = hdisplay - ( hdisplay % CVT_H_GRANULARITY ); + + /* 3. Determine left and right borders */ + if ( margins ) + { + /* right margin is actually exactly the same as left */ + hmargin = ( ( ( float )hdisplay_rnd ) * CVT_MARGIN_PERCENTAGE / 100.0 ); + hmargin -= hmargin % CVT_H_GRANULARITY; + } + else + { + hmargin = 0; + } + + /* 4. Find total active pixels */ + mode->hdisplay = hdisplay_rnd + 2 * hmargin; + + /* 5. Find number of lines per field */ + if ( interlaced ) { vdisplay_rnd = vdisplay / 2; } + else + { + vdisplay_rnd = vdisplay; + } + + /* 6. Find top and bottom margins */ + /* nope. */ + if ( margins ) + { + /* top and bottom margins are equal again. */ + vmargin = ( ( ( float )vdisplay_rnd ) * CVT_MARGIN_PERCENTAGE / 100.0 ); + } + else + { + vmargin = 0; + } + + mode->vdisplay = vdisplay + 2 * vmargin; + + /* 7. interlace */ + if ( interlaced ) { interlace = 0.5; } + else + { + interlace = 0.0; + } + + /* Determine vsync Width from aspect ratio */ + if ( !( vdisplay % 3 ) && ( ( vdisplay * 4 / 3 ) == hdisplay ) ) + { + vsync = 4; + } + else if ( !( vdisplay % 9 ) && ( ( vdisplay * 16 / 9 ) == hdisplay ) ) + { + vsync = 5; + } + else if ( !( vdisplay % 10 ) && ( ( vdisplay * 16 / 10 ) == hdisplay ) ) + { + vsync = 6; + } + else if ( !( vdisplay % 4 ) && ( ( vdisplay * 5 / 4 ) == hdisplay ) ) + { + vsync = 7; + } + else if ( !( vdisplay % 9 ) && ( ( vdisplay * 15 / 9 ) == hdisplay ) ) + { + vsync = 7; + } + else + { /* Custom */ + vsync = 10; + } + + if ( !reduced ) + { /* simplified GTF calculation */ + float hblank_percentage; + int vsync_and_back_porch, vblank_porch; + int hblank; + + /* 8. Estimated Horizontal period */ + hperiod = ( ( float )( 1000000.0 / vfield_rate - CVT_MIN_VSYNC_BP ) ) / + ( vdisplay_rnd + 2 * vmargin + CVT_MIN_V_PORCH + interlace ); + + /* 9. Find number of lines in sync + backporch */ + if ( ( ( int )( CVT_MIN_VSYNC_BP / hperiod ) + 1 ) < + ( vsync + CVT_MIN_V_PORCH ) ) + { + vsync_and_back_porch = vsync + CVT_MIN_V_PORCH; + } + else + { + vsync_and_back_porch = ( int )( CVT_MIN_VSYNC_BP / hperiod ) + 1; + } + + /* 10. Find number of lines in back porch */ + vblank_porch = vsync_and_back_porch - vsync; + ( void )vblank_porch; + + /* 11. Find total number of lines in vertical field */ + mode->vtotal = vdisplay_rnd + 2 * vmargin + vsync_and_back_porch + + interlace + CVT_MIN_V_PORCH; + + /* 12. Find ideal blanking duty cycle from formula */ + hblank_percentage = CVT_C_PRIME - CVT_M_PRIME * hperiod / 1000.0; + + /* 13. Blanking time */ + if ( hblank_percentage < 20 ) { hblank_percentage = 20; } + + hblank = + mode->hdisplay * hblank_percentage / ( 100.0 - hblank_percentage ); + hblank -= hblank % ( 2 * CVT_H_GRANULARITY ); + + /* 14. Find total number of pixels in a line. */ + mode->htotal = mode->hdisplay + hblank; + + /* Fill in hsync values */ + mode->hsync_end = mode->hdisplay + hblank / 2; + + mode->hsync_start = + mode->hsync_end - ( mode->htotal * CVT_HSYNC_PERCENTAGE ) / 100; + mode->hsync_start += + CVT_H_GRANULARITY - mode->hsync_start % CVT_H_GRANULARITY; + + /* Fill in vsync values */ + mode->vsync_start = mode->vdisplay + CVT_MIN_V_PORCH; + mode->vsync_end = mode->vsync_start + vsync; + } + else + { /* reduced blanking */ + int vbi_lines; + + /* 8. Estimate Horizontal period. */ + hperiod = ( ( float )( 1000000.0 / vfield_rate - CVT_RB_MIN_VBLANK ) ) / + ( vdisplay_rnd + 2 * vmargin ); + + /* 9. Find number of lines in vertical blanking */ + vbi_lines = ( ( float )CVT_RB_MIN_VBLANK ) / hperiod + 1; + + /* 10. Check if vertical blanking is sufficient */ + if ( vbi_lines < ( CVT_RB_VFPORCH + vsync + CVT_MIN_V_BPORCH ) ) + { + vbi_lines = CVT_RB_VFPORCH + vsync + CVT_MIN_V_BPORCH; + } + + /* 11. Find total number of lines in vertical field */ + mode->vtotal = vdisplay_rnd + 2 * vmargin + interlace + vbi_lines; + + /* 12. Find total number of pixels in a line */ + mode->htotal = mode->hdisplay + CVT_RB_H_BLANK; + + /* Fill in hsync values */ + mode->hsync_end = mode->hdisplay + CVT_RB_H_BLANK / 2; + mode->hsync_start = mode->hsync_end - CVT_RB_H_SYNC; + + /* Fill in vsync values */ + mode->vsync_start = mode->vdisplay + CVT_RB_VFPORCH; + mode->vsync_end = mode->vsync_start + vsync; + } + + /* 15/13. Find pixel clock frequency (kHz for xf86) */ + mode->clock = mode->htotal * 1000.0 / hperiod; + mode->clock -= mode->clock % CVT_CLOCK_STEP; + + /* 17/15. Find actual Field rate */ + mode->vrefresh = ( 1000.0 * ( ( float )mode->clock ) ) / + ( ( float )( mode->htotal * mode->vtotal ) ); + + /* 18/16. Find actual vertical frame frequency */ + /* ignore - just set the mode flag for interlaced */ + if ( interlaced ) + { + mode->vtotal *= 2; + mode->flags |= DRM_MODE_FLAG_INTERLACE; + } + + snprintf( mode->name, sizeof( mode->name ), "%dx%d", hdisplay, vdisplay ); + + if ( reduced ) + { + mode->flags |= DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC; + } + else + { + mode->flags |= DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC; + } } -void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int vrefresh ) +void generate_fixed_mode( + drmModeModeInfo *mode, const drmModeModeInfo *base, int vrefresh ) { - *mode = *base; - if (!vrefresh) - vrefresh = 60; - - mode->clock = ( ( mode->htotal * mode->vtotal * vrefresh ) + 999 ) / 1000; - mode->vrefresh = (1000 * mode->clock) / (mode->htotal * mode->vtotal); - - snprintf(mode->name, sizeof(mode->name), "%dx%d@%d.00", mode->hdisplay, mode->vdisplay, vrefresh); + *mode = *base; + if ( !vrefresh ) vrefresh = 60; + + mode->clock = ( ( mode->htotal * mode->vtotal * vrefresh ) + 999 ) / 1000; + mode->vrefresh = ( 1000 * mode->clock ) / ( mode->htotal * mode->vtotal ); + + snprintf( + mode->name, + sizeof( mode->name ), + "%dx%d@%d.00", + mode->hdisplay, + mode->vdisplay, + vrefresh ); } - diff --git a/src/modegen.hpp b/src/modegen.hpp index 7c2e690890..383c38af25 100644 --- a/src/modegen.hpp +++ b/src/modegen.hpp @@ -7,7 +7,12 @@ #include #include "gamescope_shared.h" -void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, - float vrefresh, bool reduced, bool interlaced); -void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, - int vrefresh ); +void generate_cvt_mode( + drmModeModeInfo *mode, + int hdisplay, + int vdisplay, + float vrefresh, + bool reduced, + bool interlaced ); +void generate_fixed_mode( + drmModeModeInfo *mode, const drmModeModeInfo *base, int vrefresh ); diff --git a/src/pipewire.cpp b/src/pipewire.cpp index 7a44c4efa5..504d1e2124 100644 --- a/src/pipewire.cpp +++ b/src/pipewire.cpp @@ -10,16 +10,17 @@ #include #include +#include "log.hpp" #include "main.hpp" #include "pipewire.hpp" -#include "log.hpp" #include -static LogScope pwr_log("pipewire"); +static LogScope pwr_log( "pipewire" ); -static struct pipewire_state pipewire_state = { .stream_node_id = SPA_ID_INVALID }; -static int nudgePipe[2] = { -1, -1 }; +static struct pipewire_state pipewire_state = { .stream_node_id = + SPA_ID_INVALID }; +static int nudgePipe[ 2 ] = { -1, -1 }; // Pending buffer for PipeWire → steamcompmgr static std::atomic out_buffer; @@ -34,589 +35,746 @@ static uint32_t s_nCaptureHeight; static uint32_t s_nOutputWidth; static uint32_t s_nOutputHeight; -static void destroy_buffer(struct pipewire_buffer *buffer) { - assert(buffer->buffer == nullptr); - - switch (buffer->type) { - case SPA_DATA_MemFd: - { - off_t size = buffer->shm.stride * buffer->video_info.size.height; - if (buffer->video_info.format == SPA_VIDEO_FORMAT_NV12) { - size += buffer->shm.stride * ((buffer->video_info.size.height + 1) / 2); - } - munmap(buffer->shm.data, size); - close(buffer->shm.fd); - break; - } - case SPA_DATA_DmaBuf: - break; // nothing to do - default: - assert(false); // unreachable - } - - // If out_buffer == buffer, then set it to nullptr. - // We don't care about the result. - struct pipewire_buffer *buffer1 = buffer; - out_buffer.compare_exchange_strong(buffer1, nullptr); - struct pipewire_buffer *buffer2 = buffer; - in_buffer.compare_exchange_strong(buffer2, nullptr); - - delete buffer; -} - -void pipewire_destroy_buffer(struct pipewire_buffer *buffer) +static void destroy_buffer( struct pipewire_buffer *buffer ) { - destroy_buffer(buffer); + assert( buffer->buffer == nullptr ); + + switch ( buffer->type ) + { + case SPA_DATA_MemFd: + { + off_t size = buffer->shm.stride * buffer->video_info.size.height; + if ( buffer->video_info.format == SPA_VIDEO_FORMAT_NV12 ) + { + size += buffer->shm.stride * + ( ( buffer->video_info.size.height + 1 ) / 2 ); + } + munmap( buffer->shm.data, size ); + close( buffer->shm.fd ); + break; + } + case SPA_DATA_DmaBuf: + break; // nothing to do + default: + assert( false ); // unreachable + } + + // If out_buffer == buffer, then set it to nullptr. + // We don't care about the result. + struct pipewire_buffer *buffer1 = buffer; + out_buffer.compare_exchange_strong( buffer1, nullptr ); + struct pipewire_buffer *buffer2 = buffer; + in_buffer.compare_exchange_strong( buffer2, nullptr ); + + delete buffer; } -static void calculate_capture_size() +void pipewire_destroy_buffer( struct pipewire_buffer *buffer ) +{ destroy_buffer( buffer ); } + +static void calculate_capture_size( ) { - s_nCaptureWidth = s_nOutputWidth; - s_nCaptureHeight = s_nOutputHeight; - - if (s_nRequestedWidth > 0 && s_nRequestedHeight > 0 && - (s_nOutputWidth > s_nRequestedWidth || s_nOutputHeight > s_nRequestedHeight)) { - // Need to clamp to the smallest dimension - float flRatioW = static_cast(s_nRequestedWidth) / s_nOutputWidth; - float flRatioH = static_cast(s_nRequestedHeight) / s_nOutputHeight; - if (flRatioW <= flRatioH) { - s_nCaptureWidth = s_nRequestedWidth; - s_nCaptureHeight = static_cast(ceilf(flRatioW * s_nOutputHeight)); - } else { - s_nCaptureWidth = static_cast(ceilf(flRatioH * s_nOutputWidth)); - s_nCaptureHeight = s_nRequestedHeight; - } - } + s_nCaptureWidth = s_nOutputWidth; + s_nCaptureHeight = s_nOutputHeight; + + if ( s_nRequestedWidth > 0 && s_nRequestedHeight > 0 && + ( s_nOutputWidth > s_nRequestedWidth || + s_nOutputHeight > s_nRequestedHeight ) ) + { + // Need to clamp to the smallest dimension + float flRatioW = + static_cast( s_nRequestedWidth ) / s_nOutputWidth; + float flRatioH = + static_cast( s_nRequestedHeight ) / s_nOutputHeight; + if ( flRatioW <= flRatioH ) + { + s_nCaptureWidth = s_nRequestedWidth; + s_nCaptureHeight = + static_cast( ceilf( flRatioW * s_nOutputHeight ) ); + } + else + { + s_nCaptureWidth = + static_cast( ceilf( flRatioH * s_nOutputWidth ) ); + s_nCaptureHeight = s_nRequestedHeight; + } + } } -static void build_format_params(struct spa_pod_builder *builder, spa_video_format format, std::vector ¶ms) { - struct spa_rectangle size = SPA_RECTANGLE(s_nCaptureWidth, s_nCaptureHeight); - struct spa_rectangle min_requested_size = { 0, 0 }; - struct spa_rectangle max_requested_size = { UINT32_MAX, UINT32_MAX }; - struct spa_fraction framerate = SPA_FRACTION(0, 1); - uint64_t modifier = DRM_FORMAT_MOD_LINEAR; - - struct spa_pod_frame obj_frame, choice_frame; - spa_pod_builder_push_object(builder, &obj_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(builder, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerate), - SPA_FORMAT_VIDEO_requested_size, SPA_POD_CHOICE_RANGE_Rectangle( &min_requested_size, &min_requested_size, &max_requested_size ), - SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_CHOICE_RANGE_Long( 0ll, INT64_MIN, INT64_MAX ), - 0); - if (format == SPA_VIDEO_FORMAT_NV12) { - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_CHOICE_ENUM_Id(3, - SPA_VIDEO_COLOR_MATRIX_BT601, - SPA_VIDEO_COLOR_MATRIX_BT601, - SPA_VIDEO_COLOR_MATRIX_BT709), - SPA_FORMAT_VIDEO_colorRange, SPA_POD_CHOICE_ENUM_Id(3, - SPA_VIDEO_COLOR_RANGE_16_235, - SPA_VIDEO_COLOR_RANGE_16_235, - SPA_VIDEO_COLOR_RANGE_0_255), - 0); - } - spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); - spa_pod_builder_push_choice(builder, &choice_frame, SPA_CHOICE_Enum, 0); - spa_pod_builder_long(builder, modifier); // default - spa_pod_builder_long(builder, modifier); - spa_pod_builder_pop(builder, &choice_frame); - params.push_back((const struct spa_pod *) spa_pod_builder_pop(builder, &obj_frame)); - - spa_pod_builder_push_object(builder, &obj_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(builder, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerate), - SPA_FORMAT_VIDEO_requested_size, SPA_POD_CHOICE_RANGE_Rectangle( &min_requested_size, &min_requested_size, &max_requested_size ), - SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_CHOICE_RANGE_Long( 0ll, INT64_MIN, INT64_MAX ), - 0); - if (format == SPA_VIDEO_FORMAT_NV12) { - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_CHOICE_ENUM_Id(3, - SPA_VIDEO_COLOR_MATRIX_BT601, - SPA_VIDEO_COLOR_MATRIX_BT601, - SPA_VIDEO_COLOR_MATRIX_BT709), - SPA_FORMAT_VIDEO_colorRange, SPA_POD_CHOICE_ENUM_Id(3, - SPA_VIDEO_COLOR_RANGE_16_235, - SPA_VIDEO_COLOR_RANGE_16_235, - SPA_VIDEO_COLOR_RANGE_0_255), - 0); - } - params.push_back((const struct spa_pod *) spa_pod_builder_pop(builder, &obj_frame)); - -// for (auto& param : params) -// spa_debug_format(2, nullptr, param); +static void build_format_params( + struct spa_pod_builder *builder, + spa_video_format format, + std::vector ¶ms ) +{ + struct spa_rectangle size = + SPA_RECTANGLE( s_nCaptureWidth, s_nCaptureHeight ); + struct spa_rectangle min_requested_size = { 0, 0 }; + struct spa_rectangle max_requested_size = { UINT32_MAX, UINT32_MAX }; + struct spa_fraction framerate = SPA_FRACTION( 0, 1 ); + uint64_t modifier = DRM_FORMAT_MOD_LINEAR; + + struct spa_pod_frame obj_frame, choice_frame; + spa_pod_builder_push_object( + builder, &obj_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat ); + spa_pod_builder_add( + builder, + SPA_FORMAT_mediaType, + SPA_POD_Id( SPA_MEDIA_TYPE_video ), + SPA_FORMAT_mediaSubtype, + SPA_POD_Id( SPA_MEDIA_SUBTYPE_raw ), + SPA_FORMAT_VIDEO_format, + SPA_POD_Id( format ), + SPA_FORMAT_VIDEO_size, + SPA_POD_Rectangle( &size ), + SPA_FORMAT_VIDEO_framerate, + SPA_POD_Fraction( &framerate ), + SPA_FORMAT_VIDEO_requested_size, + SPA_POD_CHOICE_RANGE_Rectangle( + &min_requested_size, &min_requested_size, &max_requested_size ), + SPA_FORMAT_VIDEO_gamescope_focus_appid, + SPA_POD_CHOICE_RANGE_Long( 0ll, INT64_MIN, INT64_MAX ), + 0 ); + if ( format == SPA_VIDEO_FORMAT_NV12 ) + { + spa_pod_builder_add( + builder, + SPA_FORMAT_VIDEO_colorMatrix, + SPA_POD_CHOICE_ENUM_Id( + 3, + SPA_VIDEO_COLOR_MATRIX_BT601, + SPA_VIDEO_COLOR_MATRIX_BT601, + SPA_VIDEO_COLOR_MATRIX_BT709 ), + SPA_FORMAT_VIDEO_colorRange, + SPA_POD_CHOICE_ENUM_Id( + 3, + SPA_VIDEO_COLOR_RANGE_16_235, + SPA_VIDEO_COLOR_RANGE_16_235, + SPA_VIDEO_COLOR_RANGE_0_255 ), + 0 ); + } + spa_pod_builder_prop( + builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY ); + spa_pod_builder_push_choice( builder, &choice_frame, SPA_CHOICE_Enum, 0 ); + spa_pod_builder_long( builder, modifier ); // default + spa_pod_builder_long( builder, modifier ); + spa_pod_builder_pop( builder, &choice_frame ); + params.push_back( + ( const struct spa_pod * )spa_pod_builder_pop( builder, &obj_frame ) ); + + spa_pod_builder_push_object( + builder, &obj_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat ); + spa_pod_builder_add( + builder, + SPA_FORMAT_mediaType, + SPA_POD_Id( SPA_MEDIA_TYPE_video ), + SPA_FORMAT_mediaSubtype, + SPA_POD_Id( SPA_MEDIA_SUBTYPE_raw ), + SPA_FORMAT_VIDEO_format, + SPA_POD_Id( format ), + SPA_FORMAT_VIDEO_size, + SPA_POD_Rectangle( &size ), + SPA_FORMAT_VIDEO_framerate, + SPA_POD_Fraction( &framerate ), + SPA_FORMAT_VIDEO_requested_size, + SPA_POD_CHOICE_RANGE_Rectangle( + &min_requested_size, &min_requested_size, &max_requested_size ), + SPA_FORMAT_VIDEO_gamescope_focus_appid, + SPA_POD_CHOICE_RANGE_Long( 0ll, INT64_MIN, INT64_MAX ), + 0 ); + if ( format == SPA_VIDEO_FORMAT_NV12 ) + { + spa_pod_builder_add( + builder, + SPA_FORMAT_VIDEO_colorMatrix, + SPA_POD_CHOICE_ENUM_Id( + 3, + SPA_VIDEO_COLOR_MATRIX_BT601, + SPA_VIDEO_COLOR_MATRIX_BT601, + SPA_VIDEO_COLOR_MATRIX_BT709 ), + SPA_FORMAT_VIDEO_colorRange, + SPA_POD_CHOICE_ENUM_Id( + 3, + SPA_VIDEO_COLOR_RANGE_16_235, + SPA_VIDEO_COLOR_RANGE_16_235, + SPA_VIDEO_COLOR_RANGE_0_255 ), + 0 ); + } + params.push_back( + ( const struct spa_pod * )spa_pod_builder_pop( builder, &obj_frame ) ); + + // for (auto& param : params) + // spa_debug_format(2, nullptr, param); } - -static std::vector build_format_params(struct spa_pod_builder *builder) +static std::vector +build_format_params( struct spa_pod_builder *builder ) { - std::vector params; + std::vector params; - build_format_params(builder, SPA_VIDEO_FORMAT_BGRx, params); - build_format_params(builder, SPA_VIDEO_FORMAT_NV12, params); + build_format_params( builder, SPA_VIDEO_FORMAT_BGRx, params ); + build_format_params( builder, SPA_VIDEO_FORMAT_NV12, params ); - return params; + return params; } -static void request_buffer(struct pipewire_state *state) +static void request_buffer( struct pipewire_state *state ) { - struct pw_buffer *pw_buffer = pw_stream_dequeue_buffer(state->stream); - if (!pw_buffer) { - pwr_log.errorf("warning: out of buffers"); - return; - } - - struct pipewire_buffer *buffer = (struct pipewire_buffer *) pw_buffer->user_data; - buffer->copying = true; - - // Past this exchange, the PipeWire thread shares the buffer with the - // steamcompmgr thread - struct pipewire_buffer *old = out_buffer.exchange(buffer); - assert(old == nullptr); + struct pw_buffer *pw_buffer = pw_stream_dequeue_buffer( state->stream ); + if ( !pw_buffer ) + { + pwr_log.errorf( "warning: out of buffers" ); + return; + } + + struct pipewire_buffer *buffer = + ( struct pipewire_buffer * )pw_buffer->user_data; + buffer->copying = true; + + // Past this exchange, the PipeWire thread shares the buffer with the + // steamcompmgr thread + struct pipewire_buffer *old = out_buffer.exchange( buffer ); + assert( old == nullptr ); } -static void copy_buffer(struct pipewire_state *state, struct pipewire_buffer *buffer) +static void +copy_buffer( struct pipewire_state *state, struct pipewire_buffer *buffer ) { - gamescope::OwningRc &tex = buffer->texture; - assert(tex != nullptr); - - struct pw_buffer *pw_buffer = buffer->buffer; - struct spa_buffer *spa_buffer = pw_buffer->buffer; - - bool needs_reneg = buffer->video_info.size.width != tex->width() || buffer->video_info.size.height != tex->height(); - - struct spa_meta_header *header = (struct spa_meta_header *) spa_buffer_find_meta_data(spa_buffer, SPA_META_Header, sizeof(*header)); - if (header != nullptr) { - header->pts = -1; - header->flags = needs_reneg ? SPA_META_HEADER_FLAG_CORRUPTED : 0; - header->seq = state->seq++; - header->dts_offset = 0; - } - - float *requested_size_scale = (float *) spa_buffer_find_meta_data(spa_buffer, SPA_META_requested_size_scale, sizeof(*requested_size_scale)); - if (requested_size_scale != nullptr) { - *requested_size_scale = ((float)tex->width() / g_nOutputWidth); - } - - struct spa_chunk *chunk = spa_buffer->datas[0].chunk; - chunk->flags = needs_reneg ? SPA_CHUNK_FLAG_CORRUPTED : 0; - - struct wlr_dmabuf_attributes dmabuf; - switch (buffer->type) { - case SPA_DATA_MemFd: - chunk->offset = 0; - chunk->size = state->video_info.size.height * buffer->shm.stride; - if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { - chunk->size += ((state->video_info.size.height + 1)/2 * buffer->shm.stride); - } - chunk->stride = buffer->shm.stride; - - if (!needs_reneg) { - uint8_t *pMappedData = tex->mappedData(); - - if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { - for (uint32_t i = 0; i < tex->height(); i++) { - const uint32_t lumaPwOffset = 0; - memcpy( - &buffer->shm.data[lumaPwOffset + i * buffer->shm.stride], - &pMappedData [tex->lumaOffset() + i * tex->lumaRowPitch()], - std::min(buffer->shm.stride, tex->lumaRowPitch())); - } - - for (uint32_t i = 0; i < (tex->height() + 1) / 2; i++) { - const uint32_t chromaPwOffset = tex->height() * buffer->shm.stride; - memcpy( - &buffer->shm.data[chromaPwOffset + i * buffer->shm.stride], - &pMappedData [tex->chromaOffset() + i * tex->chromaRowPitch()], - std::min(buffer->shm.stride, tex->chromaRowPitch())); - } - } - else - { - for (uint32_t i = 0; i < tex->height(); i++) { - memcpy( - &buffer->shm.data[i * buffer->shm.stride], - &pMappedData [i * tex->rowPitch()], - std::min(buffer->shm.stride, tex->rowPitch())); - } - } - } - break; - case SPA_DATA_DmaBuf: - dmabuf = tex->dmabuf(); - assert(dmabuf.n_planes == 1); - chunk->offset = dmabuf.offset[0]; - chunk->stride = dmabuf.stride[0]; - chunk->size = dmabuf.height * chunk->stride; - if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { - chunk->size += ((dmabuf.height + 1)/2 * chunk->stride); - } - break; - default: - assert(false); // unreachable - } + gamescope::OwningRc &tex = buffer->texture; + assert( tex != nullptr ); + + struct pw_buffer *pw_buffer = buffer->buffer; + struct spa_buffer *spa_buffer = pw_buffer->buffer; + + bool needs_reneg = buffer->video_info.size.width != tex->width( ) || + buffer->video_info.size.height != tex->height( ); + + struct spa_meta_header *header = + ( struct spa_meta_header * )spa_buffer_find_meta_data( + spa_buffer, SPA_META_Header, sizeof( *header ) ); + if ( header != nullptr ) + { + header->pts = -1; + header->flags = needs_reneg ? SPA_META_HEADER_FLAG_CORRUPTED : 0; + header->seq = state->seq++; + header->dts_offset = 0; + } + + float *requested_size_scale = ( float * )spa_buffer_find_meta_data( + spa_buffer, + SPA_META_requested_size_scale, + sizeof( *requested_size_scale ) ); + if ( requested_size_scale != nullptr ) + { + *requested_size_scale = ( ( float )tex->width( ) / g_nOutputWidth ); + } + + struct spa_chunk *chunk = spa_buffer->datas[ 0 ].chunk; + chunk->flags = needs_reneg ? SPA_CHUNK_FLAG_CORRUPTED : 0; + + struct wlr_dmabuf_attributes dmabuf; + switch ( buffer->type ) + { + case SPA_DATA_MemFd: + chunk->offset = 0; + chunk->size = state->video_info.size.height * buffer->shm.stride; + if ( state->video_info.format == SPA_VIDEO_FORMAT_NV12 ) + { + chunk->size += + ( ( state->video_info.size.height + 1 ) / 2 * + buffer->shm.stride ); + } + chunk->stride = buffer->shm.stride; + + if ( !needs_reneg ) + { + uint8_t *pMappedData = tex->mappedData( ); + + if ( state->video_info.format == SPA_VIDEO_FORMAT_NV12 ) + { + for ( uint32_t i = 0; i < tex->height( ); i++ ) + { + const uint32_t lumaPwOffset = 0; + memcpy( + &buffer->shm + .data[ lumaPwOffset + i * buffer->shm.stride ], + &pMappedData + [ tex->lumaOffset( ) + + i * tex->lumaRowPitch( ) ], + std::min( + buffer->shm.stride, tex->lumaRowPitch( ) ) ); + } + + for ( uint32_t i = 0; i < ( tex->height( ) + 1 ) / 2; i++ ) + { + const uint32_t chromaPwOffset = + tex->height( ) * buffer->shm.stride; + memcpy( + &buffer->shm.data + [ chromaPwOffset + i * buffer->shm.stride ], + &pMappedData + [ tex->chromaOffset( ) + + i * tex->chromaRowPitch( ) ], + std::min( + buffer->shm.stride, tex->chromaRowPitch( ) ) ); + } + } + else + { + for ( uint32_t i = 0; i < tex->height( ); i++ ) + { + memcpy( + &buffer->shm.data[ i * buffer->shm.stride ], + &pMappedData[ i * tex->rowPitch( ) ], + std::min( + buffer->shm.stride, tex->rowPitch( ) ) ); + } + } + } + break; + case SPA_DATA_DmaBuf: + dmabuf = tex->dmabuf( ); + assert( dmabuf.n_planes == 1 ); + chunk->offset = dmabuf.offset[ 0 ]; + chunk->stride = dmabuf.stride[ 0 ]; + chunk->size = dmabuf.height * chunk->stride; + if ( state->video_info.format == SPA_VIDEO_FORMAT_NV12 ) + { + chunk->size += ( ( dmabuf.height + 1 ) / 2 * chunk->stride ); + } + break; + default: + assert( false ); // unreachable + } } -static void dispatch_nudge(struct pipewire_state *state, int fd) +static void dispatch_nudge( struct pipewire_state *state, int fd ) { - while (true) { - static char buf[1024]; - if (read(fd, buf, sizeof(buf)) < 0) { - if (errno != EAGAIN) - pwr_log.errorf_errno("dispatch_nudge: read failed"); - break; - } - } - - if (g_nOutputWidth != s_nOutputWidth || g_nOutputHeight != s_nOutputHeight) { - s_nOutputWidth = g_nOutputWidth; - s_nOutputHeight = g_nOutputHeight; - calculate_capture_size(); - } - if (s_nCaptureWidth != state->video_info.size.width || s_nCaptureHeight != state->video_info.size.height) { - pwr_log.debugf("renegotiating stream params (size: %dx%d)", s_nCaptureWidth, s_nCaptureHeight); - - uint8_t buf[4096]; - struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - std::vector format_params = build_format_params(&builder); - int ret = pw_stream_update_params(state->stream, format_params.data(), format_params.size()); - if (ret < 0) { - pwr_log.errorf("pw_stream_update_params failed"); - } - } - - struct pipewire_buffer *buffer = in_buffer.exchange(nullptr); - if (buffer != nullptr) { - // We now completely own the buffer, it's no longer shared with the - // steamcompmgr thread. - - buffer->copying = false; - - if (buffer->buffer != nullptr) { - copy_buffer(state, buffer); - - int ret = pw_stream_queue_buffer(state->stream, buffer->buffer); - if (ret < 0) { - pwr_log.errorf("pw_stream_queue_buffer failed"); - } - } else { - destroy_buffer(buffer); - } - } + while ( true ) + { + static char buf[ 1024 ]; + if ( read( fd, buf, sizeof( buf ) ) < 0 ) + { + if ( errno != EAGAIN ) + pwr_log.errorf_errno( "dispatch_nudge: read failed" ); + break; + } + } + + if ( g_nOutputWidth != s_nOutputWidth || + g_nOutputHeight != s_nOutputHeight ) + { + s_nOutputWidth = g_nOutputWidth; + s_nOutputHeight = g_nOutputHeight; + calculate_capture_size( ); + } + if ( s_nCaptureWidth != state->video_info.size.width || + s_nCaptureHeight != state->video_info.size.height ) + { + pwr_log.debugf( + "renegotiating stream params (size: %dx%d)", + s_nCaptureWidth, + s_nCaptureHeight ); + + uint8_t buf[ 4096 ]; + struct spa_pod_builder builder = + SPA_POD_BUILDER_INIT( buf, sizeof( buf ) ); + std::vector format_params = + build_format_params( &builder ); + int ret = pw_stream_update_params( + state->stream, format_params.data( ), format_params.size( ) ); + if ( ret < 0 ) { pwr_log.errorf( "pw_stream_update_params failed" ); } + } + + struct pipewire_buffer *buffer = in_buffer.exchange( nullptr ); + if ( buffer != nullptr ) + { + // We now completely own the buffer, it's no longer shared with the + // steamcompmgr thread. + + buffer->copying = false; + + if ( buffer->buffer != nullptr ) + { + copy_buffer( state, buffer ); + + int ret = pw_stream_queue_buffer( state->stream, buffer->buffer ); + if ( ret < 0 ) + { + pwr_log.errorf( "pw_stream_queue_buffer failed" ); + } + } + else + { + destroy_buffer( buffer ); + } + } } -static void stream_handle_state_changed(void *data, enum pw_stream_state old_stream_state, enum pw_stream_state stream_state, const char *error) +static void stream_handle_state_changed( + void *data, + enum pw_stream_state old_stream_state, + enum pw_stream_state stream_state, + const char *error ) { - struct pipewire_state *state = (struct pipewire_state *) data; - - pwr_log.infof("stream state changed: %s", pw_stream_state_as_string(stream_state)); - - switch (stream_state) { - case PW_STREAM_STATE_PAUSED: - if (state->stream_node_id == SPA_ID_INVALID) { - state->stream_node_id = pw_stream_get_node_id(state->stream); - } - state->streaming = false; - state->seq = 0; - break; - case PW_STREAM_STATE_STREAMING: - state->streaming = true; - break; - case PW_STREAM_STATE_ERROR: - case PW_STREAM_STATE_UNCONNECTED: - state->running = false; - break; - default: - break; - } + struct pipewire_state *state = ( struct pipewire_state * )data; + + pwr_log.infof( + "stream state changed: %s", pw_stream_state_as_string( stream_state ) ); + + switch ( stream_state ) + { + case PW_STREAM_STATE_PAUSED: + if ( state->stream_node_id == SPA_ID_INVALID ) + { + state->stream_node_id = pw_stream_get_node_id( state->stream ); + } + state->streaming = false; + state->seq = 0; + break; + case PW_STREAM_STATE_STREAMING: + state->streaming = true; + break; + case PW_STREAM_STATE_ERROR: + case PW_STREAM_STATE_UNCONNECTED: + state->running = false; + break; + default: + break; + } } -static void stream_handle_param_changed(void *data, uint32_t id, const struct spa_pod *param) +static void stream_handle_param_changed( + void *data, uint32_t id, const struct spa_pod *param ) { - struct pipewire_state *state = (struct pipewire_state *) data; - - if (param == nullptr || id != SPA_PARAM_Format) - return; - - struct spa_gamescope gamescope_info{}; - - int ret = spa_format_video_raw_parse_with_gamescope(param, &state->video_info, &gamescope_info); - if (ret < 0) { - pwr_log.errorf("spa_format_video_raw_parse failed"); - return; - } - s_nRequestedWidth = gamescope_info.requested_size.width; - s_nRequestedHeight = gamescope_info.requested_size.height; - calculate_capture_size(); - - state->gamescope_info = gamescope_info; - - int bpp = 4; - if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { - bpp = 1; - } - - state->shm_stride = SPA_ROUND_UP_N(state->video_info.size.width * bpp, 4); - - const struct spa_pod_prop *modifier_prop = spa_pod_find_prop(param, nullptr, SPA_FORMAT_VIDEO_modifier); - state->dmabuf = modifier_prop != nullptr; - - uint8_t buf[1024]; - struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - - int buffers = 4; - int shm_size = state->shm_stride * state->video_info.size.height; - if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { - shm_size += ((state->video_info.size.height + 1) / 2) * state->shm_stride; - } - int data_type = state->dmabuf ? (1 << SPA_DATA_DmaBuf) : (1 << SPA_DATA_MemFd); - - const struct spa_pod *buffers_param = - (const struct spa_pod *) spa_pod_builder_add_object(&builder, - SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, 1, 8), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(shm_size), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(state->shm_stride), - SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(data_type)); - const struct spa_pod *meta_param = - (const struct spa_pod *) spa_pod_builder_add_object(&builder, - SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - const struct spa_pod *scale_param = - (const struct spa_pod *) spa_pod_builder_add_object(&builder, - SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_requested_size_scale), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(float))); - const struct spa_pod *params[] = { buffers_param, meta_param, scale_param }; - - ret = pw_stream_update_params(state->stream, params, sizeof(params) / sizeof(params[0])); - if (ret != 0) { - pwr_log.errorf("pw_stream_update_params failed"); - } - - pwr_log.debugf("format changed (size: %dx%d, requested %dx%d, format %d, stride %d, size: %d, dmabuf: %d)", - state->video_info.size.width, state->video_info.size.height, - s_nRequestedWidth, s_nRequestedHeight, - state->video_info.format, state->shm_stride, shm_size, state->dmabuf); + struct pipewire_state *state = ( struct pipewire_state * )data; + + if ( param == nullptr || id != SPA_PARAM_Format ) return; + + struct spa_gamescope gamescope_info{}; + + int ret = spa_format_video_raw_parse_with_gamescope( + param, &state->video_info, &gamescope_info ); + if ( ret < 0 ) + { + pwr_log.errorf( "spa_format_video_raw_parse failed" ); + return; + } + s_nRequestedWidth = gamescope_info.requested_size.width; + s_nRequestedHeight = gamescope_info.requested_size.height; + calculate_capture_size( ); + + state->gamescope_info = gamescope_info; + + int bpp = 4; + if ( state->video_info.format == SPA_VIDEO_FORMAT_NV12 ) { bpp = 1; } + + state->shm_stride = SPA_ROUND_UP_N( state->video_info.size.width * bpp, 4 ); + + const struct spa_pod_prop *modifier_prop = + spa_pod_find_prop( param, nullptr, SPA_FORMAT_VIDEO_modifier ); + state->dmabuf = modifier_prop != nullptr; + + uint8_t buf[ 1024 ]; + struct spa_pod_builder builder = SPA_POD_BUILDER_INIT( buf, sizeof( buf ) ); + + int buffers = 4; + int shm_size = state->shm_stride * state->video_info.size.height; + if ( state->video_info.format == SPA_VIDEO_FORMAT_NV12 ) + { + shm_size += + ( ( state->video_info.size.height + 1 ) / 2 ) * state->shm_stride; + } + int data_type = + state->dmabuf ? ( 1 << SPA_DATA_DmaBuf ) : ( 1 << SPA_DATA_MemFd ); + + const struct spa_pod *buffers_param = + ( const struct spa_pod * )spa_pod_builder_add_object( + &builder, + SPA_TYPE_OBJECT_ParamBuffers, + SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, + SPA_POD_CHOICE_RANGE_Int( buffers, 1, 8 ), + SPA_PARAM_BUFFERS_blocks, + SPA_POD_Int( 1 ), + SPA_PARAM_BUFFERS_size, + SPA_POD_Int( shm_size ), + SPA_PARAM_BUFFERS_stride, + SPA_POD_Int( state->shm_stride ), + SPA_PARAM_BUFFERS_dataType, + SPA_POD_CHOICE_FLAGS_Int( data_type ) ); + const struct spa_pod *meta_param = + ( const struct spa_pod * )spa_pod_builder_add_object( + &builder, + SPA_TYPE_OBJECT_ParamMeta, + SPA_PARAM_Meta, + SPA_PARAM_META_type, + SPA_POD_Id( SPA_META_Header ), + SPA_PARAM_META_size, + SPA_POD_Int( sizeof( struct spa_meta_header ) ) ); + const struct spa_pod *scale_param = + ( const struct spa_pod * )spa_pod_builder_add_object( + &builder, + SPA_TYPE_OBJECT_ParamMeta, + SPA_PARAM_Meta, + SPA_PARAM_META_type, + SPA_POD_Id( SPA_META_requested_size_scale ), + SPA_PARAM_META_size, + SPA_POD_Int( sizeof( float ) ) ); + const struct spa_pod *params[] = { buffers_param, meta_param, scale_param }; + + ret = pw_stream_update_params( + state->stream, params, sizeof( params ) / sizeof( params[ 0 ] ) ); + if ( ret != 0 ) { pwr_log.errorf( "pw_stream_update_params failed" ); } + + pwr_log.debugf( + "format changed (size: %dx%d, requested %dx%d, format %d, stride %d, " + "size: %d, dmabuf: %d)", + state->video_info.size.width, + state->video_info.size.height, + s_nRequestedWidth, + s_nRequestedHeight, + state->video_info.format, + state->shm_stride, + shm_size, + state->dmabuf ); } -static void randname(char *buf) +static void randname( char *buf ) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - long r = ts.tv_nsec; - for (int i = 0; i < 6; ++i) { - buf[i] = 'A'+(r&15)+(r&16)*2; - r >>= 5; - } + struct timespec ts; + clock_gettime( CLOCK_REALTIME, &ts ); + long r = ts.tv_nsec; + for ( int i = 0; i < 6; ++i ) + { + buf[ i ] = 'A' + ( r & 15 ) + ( r & 16 ) * 2; + r >>= 5; + } } -static int anonymous_shm_open(void) +static int anonymous_shm_open( void ) { - char name[] = "/gamescope-pw-XXXXXX"; - int retries = 100; - - do { - randname(name + strlen(name) - 6); - - --retries; - // shm_open guarantees that O_CLOEXEC is set - int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); - if (fd >= 0) { - shm_unlink(name); - return fd; - } - } while (retries > 0 && errno == EEXIST); - - return -1; + char name[] = "/gamescope-pw-XXXXXX"; + int retries = 100; + + do + { + randname( name + strlen( name ) - 6 ); + + --retries; + // shm_open guarantees that O_CLOEXEC is set + int fd = shm_open( name, O_RDWR | O_CREAT | O_EXCL, 0600 ); + if ( fd >= 0 ) + { + shm_unlink( name ); + return fd; + } + } while ( retries > 0 && errno == EEXIST ); + + return -1; } -uint32_t spa_format_to_drm(uint32_t spa_format) +uint32_t spa_format_to_drm( uint32_t spa_format ) { - switch (spa_format) - { - case SPA_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; - default: - case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888; - } + switch ( spa_format ) + { + case SPA_VIDEO_FORMAT_NV12: + return DRM_FORMAT_NV12; + default: + case SPA_VIDEO_FORMAT_BGR: + return DRM_FORMAT_XRGB8888; + } } -static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffer) +static void +stream_handle_add_buffer( void *user_data, struct pw_buffer *pw_buffer ) { - struct pipewire_state *state = (struct pipewire_state *) user_data; - - struct spa_buffer *spa_buffer = pw_buffer->buffer; - struct spa_data *spa_data = &spa_buffer->datas[0]; - - struct pipewire_buffer *buffer = new pipewire_buffer(); - buffer->buffer = pw_buffer; - buffer->video_info = state->video_info; - buffer->gamescope_info = state->gamescope_info; - - bool is_dmabuf = (spa_data->type & (1 << SPA_DATA_DmaBuf)) != 0; - bool is_memfd = (spa_data->type & (1 << SPA_DATA_MemFd)) != 0; - - EStreamColorspace colorspace = k_EStreamColorspace_Unknown; - switch (state->video_info.color_matrix) { - case SPA_VIDEO_COLOR_MATRIX_BT601: - switch (state->video_info.color_range) { - case SPA_VIDEO_COLOR_RANGE_16_235: - colorspace = k_EStreamColorspace_BT601; - break; - case SPA_VIDEO_COLOR_RANGE_0_255: - colorspace = k_EStreamColorspace_BT601_Full; - break; - default: - break; - } - break; - case SPA_VIDEO_COLOR_MATRIX_BT709: - switch (state->video_info.color_range) { - case SPA_VIDEO_COLOR_RANGE_16_235: - colorspace = k_EStreamColorspace_BT709; - break; - case SPA_VIDEO_COLOR_RANGE_0_255: - colorspace = k_EStreamColorspace_BT709_Full; - break; - default: - break; - } - break; - default: - break; - } - - uint32_t drmFormat = spa_format_to_drm(state->video_info.format); - - buffer->texture = new CVulkanTexture(); - CVulkanTexture::createFlags screenshotImageFlags; - screenshotImageFlags.bMappable = true; - screenshotImageFlags.bTransferDst = true; - screenshotImageFlags.bStorage = true; - if (is_dmabuf || drmFormat == DRM_FORMAT_NV12) - { - screenshotImageFlags.bExportable = true; - screenshotImageFlags.bLinear = true; // TODO: support multi-planar DMA-BUF export via PipeWire - } - bool bImageInitSuccess = buffer->texture->BInit( s_nCaptureWidth, s_nCaptureHeight, 1u, drmFormat, screenshotImageFlags ); - if ( !bImageInitSuccess ) - { - pwr_log.errorf("Failed to initialize pipewire texture"); - goto error; - } - buffer->texture->setStreamColorspace(colorspace); - - if (is_dmabuf) { - const struct wlr_dmabuf_attributes dmabuf = buffer->texture->dmabuf(); - if (dmabuf.n_planes != 1) - { - pwr_log.errorf("dmabuf.n_planes != 1"); - goto error; - } - - off_t size = lseek(dmabuf.fd[0], 0, SEEK_END); - if (size < 0) { - pwr_log.errorf_errno("lseek failed"); - goto error; - } - - buffer->type = SPA_DATA_DmaBuf; - - spa_data->type = SPA_DATA_DmaBuf; - spa_data->flags = SPA_DATA_FLAG_READABLE; - spa_data->fd = dmabuf.fd[0]; - spa_data->mapoffset = dmabuf.offset[0]; - spa_data->maxsize = size; - spa_data->data = nullptr; - } else if (is_memfd) { - int fd = anonymous_shm_open(); - if (fd < 0) { - pwr_log.errorf("failed to create shm file"); - goto error; - } - - off_t size = state->shm_stride * state->video_info.size.height; - if (state->video_info.format == SPA_VIDEO_FORMAT_NV12) { - size += state->shm_stride * ((state->video_info.size.height + 1) / 2); - } - if (ftruncate(fd, size) != 0) { - pwr_log.errorf_errno("ftruncate failed"); - close(fd); - goto error; - } - - void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - pwr_log.errorf_errno("mmap failed"); - close(fd); - goto error; - } - - buffer->type = SPA_DATA_MemFd; - buffer->shm.stride = state->shm_stride; - buffer->shm.data = (uint8_t *) data; - buffer->shm.fd = fd; - - spa_data->type = SPA_DATA_MemFd; - spa_data->flags = SPA_DATA_FLAG_READABLE; - spa_data->fd = fd; - spa_data->mapoffset = 0; - spa_data->maxsize = size; - spa_data->data = data; - } else { - pwr_log.errorf("unsupported data type"); - spa_data->type = SPA_DATA_Invalid; - goto error; - } - - pw_buffer->user_data = buffer; - - return; + struct pipewire_state *state = ( struct pipewire_state * )user_data; + + struct spa_buffer *spa_buffer = pw_buffer->buffer; + struct spa_data *spa_data = &spa_buffer->datas[ 0 ]; + + struct pipewire_buffer *buffer = new pipewire_buffer( ); + buffer->buffer = pw_buffer; + buffer->video_info = state->video_info; + buffer->gamescope_info = state->gamescope_info; + + bool is_dmabuf = ( spa_data->type & ( 1 << SPA_DATA_DmaBuf ) ) != 0; + bool is_memfd = ( spa_data->type & ( 1 << SPA_DATA_MemFd ) ) != 0; + + EStreamColorspace colorspace = k_EStreamColorspace_Unknown; + switch ( state->video_info.color_matrix ) + { + case SPA_VIDEO_COLOR_MATRIX_BT601: + switch ( state->video_info.color_range ) + { + case SPA_VIDEO_COLOR_RANGE_16_235: + colorspace = k_EStreamColorspace_BT601; + break; + case SPA_VIDEO_COLOR_RANGE_0_255: + colorspace = k_EStreamColorspace_BT601_Full; + break; + default: + break; + } + break; + case SPA_VIDEO_COLOR_MATRIX_BT709: + switch ( state->video_info.color_range ) + { + case SPA_VIDEO_COLOR_RANGE_16_235: + colorspace = k_EStreamColorspace_BT709; + break; + case SPA_VIDEO_COLOR_RANGE_0_255: + colorspace = k_EStreamColorspace_BT709_Full; + break; + default: + break; + } + break; + default: + break; + } + + uint32_t drmFormat = spa_format_to_drm( state->video_info.format ); + + buffer->texture = new CVulkanTexture( ); + CVulkanTexture::createFlags screenshotImageFlags; + screenshotImageFlags.bMappable = true; + screenshotImageFlags.bTransferDst = true; + screenshotImageFlags.bStorage = true; + if ( is_dmabuf || drmFormat == DRM_FORMAT_NV12 ) + { + screenshotImageFlags.bExportable = true; + screenshotImageFlags.bLinear = + true; // TODO: support multi-planar DMA-BUF export via PipeWire + } + bool bImageInitSuccess = buffer->texture->BInit( + s_nCaptureWidth, + s_nCaptureHeight, + 1u, + drmFormat, + screenshotImageFlags ); + if ( !bImageInitSuccess ) + { + pwr_log.errorf( "Failed to initialize pipewire texture" ); + goto error; + } + buffer->texture->setStreamColorspace( colorspace ); + + if ( is_dmabuf ) + { + const struct wlr_dmabuf_attributes dmabuf = buffer->texture->dmabuf( ); + if ( dmabuf.n_planes != 1 ) + { + pwr_log.errorf( "dmabuf.n_planes != 1" ); + goto error; + } + + off_t size = lseek( dmabuf.fd[ 0 ], 0, SEEK_END ); + if ( size < 0 ) + { + pwr_log.errorf_errno( "lseek failed" ); + goto error; + } + + buffer->type = SPA_DATA_DmaBuf; + + spa_data->type = SPA_DATA_DmaBuf; + spa_data->flags = SPA_DATA_FLAG_READABLE; + spa_data->fd = dmabuf.fd[ 0 ]; + spa_data->mapoffset = dmabuf.offset[ 0 ]; + spa_data->maxsize = size; + spa_data->data = nullptr; + } + else if ( is_memfd ) + { + int fd = anonymous_shm_open( ); + if ( fd < 0 ) + { + pwr_log.errorf( "failed to create shm file" ); + goto error; + } + + off_t size = state->shm_stride * state->video_info.size.height; + if ( state->video_info.format == SPA_VIDEO_FORMAT_NV12 ) + { + size += state->shm_stride * + ( ( state->video_info.size.height + 1 ) / 2 ); + } + if ( ftruncate( fd, size ) != 0 ) + { + pwr_log.errorf_errno( "ftruncate failed" ); + close( fd ); + goto error; + } + + void *data = + mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); + if ( data == MAP_FAILED ) + { + pwr_log.errorf_errno( "mmap failed" ); + close( fd ); + goto error; + } + + buffer->type = SPA_DATA_MemFd; + buffer->shm.stride = state->shm_stride; + buffer->shm.data = ( uint8_t * )data; + buffer->shm.fd = fd; + + spa_data->type = SPA_DATA_MemFd; + spa_data->flags = SPA_DATA_FLAG_READABLE; + spa_data->fd = fd; + spa_data->mapoffset = 0; + spa_data->maxsize = size; + spa_data->data = data; + } + else + { + pwr_log.errorf( "unsupported data type" ); + spa_data->type = SPA_DATA_Invalid; + goto error; + } + + pw_buffer->user_data = buffer; + + return; error: - delete buffer; + delete buffer; } -static void stream_handle_remove_buffer(void *data, struct pw_buffer *pw_buffer) +static void +stream_handle_remove_buffer( void *data, struct pw_buffer *pw_buffer ) { - struct pipewire_buffer *buffer = (struct pipewire_buffer *) pw_buffer->user_data; + struct pipewire_buffer *buffer = + ( struct pipewire_buffer * )pw_buffer->user_data; - buffer->buffer = nullptr; + buffer->buffer = nullptr; - if (!buffer->copying) { - destroy_buffer(buffer); - } + if ( !buffer->copying ) { destroy_buffer( buffer ); } } static const struct pw_stream_events stream_events = { - .version = PW_VERSION_STREAM_EVENTS, - .state_changed = stream_handle_state_changed, - .param_changed = stream_handle_param_changed, - .add_buffer = stream_handle_add_buffer, - .remove_buffer = stream_handle_remove_buffer, - .process = nullptr, + .version = PW_VERSION_STREAM_EVENTS, + .state_changed = stream_handle_state_changed, + .param_changed = stream_handle_param_changed, + .add_buffer = stream_handle_add_buffer, + .remove_buffer = stream_handle_remove_buffer, + .process = nullptr, }; -enum pipewire_event_type { - EVENT_PIPEWIRE, - EVENT_NUDGE, - EVENT_COUNT // keep last +enum pipewire_event_type +{ + EVENT_PIPEWIRE, + EVENT_NUDGE, + EVENT_COUNT // keep last }; -static void run_pipewire(struct pipewire_state *state) +static void run_pipewire( struct pipewire_state *state ) { - pthread_setname_np( pthread_self(), "gamescope-pw" ); + pthread_setname_np( pthread_self( ), "gamescope-pw" ); - struct pollfd pollfds[] = { + struct pollfd pollfds[] = { [EVENT_PIPEWIRE] = { .fd = pw_loop_get_fd(state->loop), .events = POLLIN, @@ -627,155 +785,173 @@ static void run_pipewire(struct pipewire_state *state) }, }; - while (state->running) { - int ret = poll(pollfds, EVENT_COUNT, -1); - if (ret < 0) { - pwr_log.errorf_errno("poll failed"); - break; - } - - if (pollfds[EVENT_PIPEWIRE].revents & POLLHUP) { - pwr_log.errorf("lost connection to server"); - break; - } - - assert(!(pollfds[EVENT_NUDGE].revents & POLLHUP)); - - if (pollfds[EVENT_PIPEWIRE].revents & POLLIN) { - pw_loop_enter(state->loop); - ret = pw_loop_iterate(state->loop, -1); - pw_loop_leave(state->loop); - if (ret < 0) { - pwr_log.errorf("pw_loop_iterate failed"); - break; - } - } - - if (pollfds[EVENT_NUDGE].revents & POLLIN) { - dispatch_nudge(state, nudgePipe[0]); - } - } - - pwr_log.infof("exiting"); - pw_stream_destroy(state->stream); - pw_core_disconnect(state->core); - pw_context_destroy(state->context); - pw_loop_destroy(state->loop); + while ( state->running ) + { + int ret = poll( pollfds, EVENT_COUNT, -1 ); + if ( ret < 0 ) + { + pwr_log.errorf_errno( "poll failed" ); + break; + } + + if ( pollfds[ EVENT_PIPEWIRE ].revents & POLLHUP ) + { + pwr_log.errorf( "lost connection to server" ); + break; + } + + assert( !( pollfds[ EVENT_NUDGE ].revents & POLLHUP ) ); + + if ( pollfds[ EVENT_PIPEWIRE ].revents & POLLIN ) + { + pw_loop_enter( state->loop ); + ret = pw_loop_iterate( state->loop, -1 ); + pw_loop_leave( state->loop ); + if ( ret < 0 ) + { + pwr_log.errorf( "pw_loop_iterate failed" ); + break; + } + } + + if ( pollfds[ EVENT_NUDGE ].revents & POLLIN ) + { + dispatch_nudge( state, nudgePipe[ 0 ] ); + } + } + + pwr_log.infof( "exiting" ); + pw_stream_destroy( state->stream ); + pw_core_disconnect( state->core ); + pw_context_destroy( state->context ); + pw_loop_destroy( state->loop ); } -bool init_pipewire(void) +bool init_pipewire( void ) { - struct pipewire_state *state = &pipewire_state; - - pw_init(nullptr, nullptr); - - if (pipe2(nudgePipe, O_CLOEXEC | O_NONBLOCK) != 0) { - pwr_log.errorf_errno("pipe2 failed"); - return false; - } - - state->loop = pw_loop_new(nullptr); - if (!state->loop) { - pwr_log.errorf("pw_loop_new failed"); - return false; - } - - state->context = pw_context_new(state->loop, nullptr, 0); - if (!state->context) { - pwr_log.errorf("pw_context_new failed"); - return false; - } - - state->core = pw_context_connect(state->context, nullptr, 0); - if (!state->core) { - pwr_log.errorf("pw_context_connect failed"); - return false; - } - - state->stream = pw_stream_new(state->core, "gamescope", - pw_properties_new( - PW_KEY_MEDIA_CLASS, "Video/Source", - nullptr)); - if (!state->stream) { - pwr_log.errorf("pw_stream_new failed"); - return false; - } - - static struct spa_hook stream_hook; - pw_stream_add_listener(state->stream, &stream_hook, &stream_events, state); - - s_nRequestedWidth = 0; - s_nRequestedHeight = 0; - s_nOutputWidth = g_nOutputWidth; - s_nOutputHeight = g_nOutputHeight; - calculate_capture_size(); - - uint8_t buf[4096]; - struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - std::vector format_params = build_format_params(&builder); - - enum pw_stream_flags flags = (enum pw_stream_flags)(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS); - int ret = pw_stream_connect(state->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, flags, format_params.data(), format_params.size()); - if (ret != 0) { - pwr_log.errorf("pw_stream_connect failed"); - return false; - } - - state->running = true; - ret = 0; - pw_loop_enter(state->loop); - while (state->stream_node_id == SPA_ID_INVALID) { - ret = pw_loop_iterate(state->loop, -1); - if (ret < 0) - break; - } - pw_loop_leave(state->loop); - - if (ret < 0) { - pwr_log.errorf("pw_loop_iterate failed"); - return false; - } - - pwr_log.infof("stream available on node ID: %u", state->stream_node_id); - - std::thread thread(run_pipewire, state); - thread.detach(); - - return true; + struct pipewire_state *state = &pipewire_state; + + pw_init( nullptr, nullptr ); + + if ( pipe2( nudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) + { + pwr_log.errorf_errno( "pipe2 failed" ); + return false; + } + + state->loop = pw_loop_new( nullptr ); + if ( !state->loop ) + { + pwr_log.errorf( "pw_loop_new failed" ); + return false; + } + + state->context = pw_context_new( state->loop, nullptr, 0 ); + if ( !state->context ) + { + pwr_log.errorf( "pw_context_new failed" ); + return false; + } + + state->core = pw_context_connect( state->context, nullptr, 0 ); + if ( !state->core ) + { + pwr_log.errorf( "pw_context_connect failed" ); + return false; + } + + state->stream = pw_stream_new( + state->core, + "gamescope", + pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", nullptr ) ); + if ( !state->stream ) + { + pwr_log.errorf( "pw_stream_new failed" ); + return false; + } + + static struct spa_hook stream_hook; + pw_stream_add_listener( + state->stream, &stream_hook, &stream_events, state ); + + s_nRequestedWidth = 0; + s_nRequestedHeight = 0; + s_nOutputWidth = g_nOutputWidth; + s_nOutputHeight = g_nOutputHeight; + calculate_capture_size( ); + + uint8_t buf[ 4096 ]; + struct spa_pod_builder builder = SPA_POD_BUILDER_INIT( buf, sizeof( buf ) ); + std::vector format_params = + build_format_params( &builder ); + + enum pw_stream_flags flags = ( enum pw_stream_flags )( + PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS ); + int ret = pw_stream_connect( + state->stream, + PW_DIRECTION_OUTPUT, + PW_ID_ANY, + flags, + format_params.data( ), + format_params.size( ) ); + if ( ret != 0 ) + { + pwr_log.errorf( "pw_stream_connect failed" ); + return false; + } + + state->running = true; + ret = 0; + pw_loop_enter( state->loop ); + while ( state->stream_node_id == SPA_ID_INVALID ) + { + ret = pw_loop_iterate( state->loop, -1 ); + if ( ret < 0 ) break; + } + pw_loop_leave( state->loop ); + + if ( ret < 0 ) + { + pwr_log.errorf( "pw_loop_iterate failed" ); + return false; + } + + pwr_log.infof( "stream available on node ID: %u", state->stream_node_id ); + + std::thread thread( run_pipewire, state ); + thread.detach( ); + + return true; } -uint32_t get_pipewire_stream_node_id(void) -{ - return pipewire_state.stream_node_id; -} +uint32_t get_pipewire_stream_node_id( void ) +{ return pipewire_state.stream_node_id; } -bool pipewire_is_streaming() +bool pipewire_is_streaming( ) { - struct pipewire_state *state = &pipewire_state; - return state->streaming; + struct pipewire_state *state = &pipewire_state; + return state->streaming; } -struct pipewire_buffer *dequeue_pipewire_buffer(void) +struct pipewire_buffer *dequeue_pipewire_buffer( void ) { - struct pipewire_state *state = &pipewire_state; - if (state->streaming) { - request_buffer(state); - } - return out_buffer.exchange(nullptr); + struct pipewire_state *state = &pipewire_state; + if ( state->streaming ) { request_buffer( state ); } + return out_buffer.exchange( nullptr ); } -void push_pipewire_buffer(struct pipewire_buffer *buffer) +void push_pipewire_buffer( struct pipewire_buffer *buffer ) { - struct pipewire_buffer *old = in_buffer.exchange(buffer); - if ( old != nullptr ) - { - pwr_log.errorf_errno("push_pipewire_buffer: Already had a buffer?!"); - } - nudge_pipewire(); + struct pipewire_buffer *old = in_buffer.exchange( buffer ); + if ( old != nullptr ) + { + pwr_log.errorf_errno( "push_pipewire_buffer: Already had a buffer?!" ); + } + nudge_pipewire( ); } -void nudge_pipewire(void) +void nudge_pipewire( void ) { - if (write(nudgePipe[1], "\n", 1) < 0) - pwr_log.errorf_errno("nudge_pipewire: write failed"); + if ( write( nudgePipe[ 1 ], "\n", 1 ) < 0 ) + pwr_log.errorf_errno( "nudge_pipewire: write failed" ); } diff --git a/src/pipewire.hpp b/src/pipewire.hpp index b4d7e296d0..89f9d14db7 100644 --- a/src/pipewire.hpp +++ b/src/pipewire.hpp @@ -1,27 +1,36 @@ #pragma once #include +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnan-infinity-disabled" +#pragma clang diagnostic ignored "-Wsign-compare" +#endif #include +#ifdef __clang__ +#pragma clang diagnostic pop +#endif #include -#include "rendervulkan.hpp" #include "pipewire_gamescope.hpp" +#include "rendervulkan.hpp" -struct pipewire_state { - struct pw_loop *loop; - struct pw_context *context; - struct pw_core *core; - bool running; +struct pipewire_state +{ + struct pw_loop *loop; + struct pw_context *context; + struct pw_core *core; + bool running; - struct pw_stream *stream; - uint32_t stream_node_id; - std::atomic streaming; - struct spa_video_info_raw video_info; - struct spa_gamescope gamescope_info; - uint64_t focus_appid; - bool dmabuf; - int shm_stride; - uint64_t seq; + struct pw_stream *stream; + uint32_t stream_node_id; + std::atomic streaming; + struct spa_video_info_raw video_info; + struct spa_gamescope gamescope_info; + uint64_t focus_appid; + bool dmabuf; + int shm_stride; + uint64_t seq; }; /** @@ -29,36 +38,35 @@ struct pipewire_state { * shared with the steamcompmgr thread (via dequeue_pipewire_buffer and * push_pipewire_buffer) for copying. */ -struct pipewire_buffer { - enum spa_data_type type; // SPA_DATA_MemFd or SPA_DATA_DmaBuf - struct spa_video_info_raw video_info; - struct spa_gamescope gamescope_info; - gamescope::OwningRc texture; +struct pipewire_buffer +{ + enum spa_data_type type; // SPA_DATA_MemFd or SPA_DATA_DmaBuf + struct spa_video_info_raw video_info; + struct spa_gamescope gamescope_info; + gamescope::OwningRc texture; - // Only used for SPA_DATA_MemFd - struct { - int stride; - uint8_t *data; - int fd; - } shm; + // Only used for SPA_DATA_MemFd + struct + { + int stride; + uint8_t *data; + int fd; + } shm; - // The following fields are not thread-safe + // The following fields are not thread-safe - // The PipeWire buffer, or nullptr if it's been destroyed. - std::atomic buffer; - bool IsStale() const - { - return buffer == nullptr; - } - // We pass the buffer to the steamcompmgr thread for copying. This is set - // to true if the buffer is currently owned by the steamcompmgr thread. - bool copying; + // The PipeWire buffer, or nullptr if it's been destroyed. + std::atomic buffer; + bool IsStale( ) const { return buffer == nullptr; } + // We pass the buffer to the steamcompmgr thread for copying. This is set + // to true if the buffer is currently owned by the steamcompmgr thread. + bool copying; }; -bool init_pipewire(void); -uint32_t get_pipewire_stream_node_id(void); -struct pipewire_buffer *dequeue_pipewire_buffer(void); -bool pipewire_is_streaming(); -void pipewire_destroy_buffer(struct pipewire_buffer *buffer); -void push_pipewire_buffer(struct pipewire_buffer *buffer); -void nudge_pipewire(void); +bool init_pipewire( void ); +uint32_t get_pipewire_stream_node_id( void ); +struct pipewire_buffer *dequeue_pipewire_buffer( void ); +bool pipewire_is_streaming( ); +void pipewire_destroy_buffer( struct pipewire_buffer *buffer ); +void push_pipewire_buffer( struct pipewire_buffer *buffer ); +void nudge_pipewire( void ); diff --git a/src/pipewire_gamescope.hpp b/src/pipewire_gamescope.hpp index ee731b47ff..5adebeaaf9 100644 --- a/src/pipewire_gamescope.hpp +++ b/src/pipewire_gamescope.hpp @@ -3,42 +3,64 @@ #include #include -enum { - SPA_FORMAT_VIDEO_requested_size = 0x70000, +enum +{ + SPA_FORMAT_VIDEO_requested_size = 0x70000, SPA_FORMAT_VIDEO_gamescope_focus_appid = 0x70001, }; -enum { +enum +{ SPA_META_requested_size_scale = 0x70000 }; struct spa_gamescope { spa_rectangle requested_size; - uint64_t focus_appid; + uint64_t focus_appid; }; -static inline int -spa_format_video_raw_parse_with_gamescope(const struct spa_pod *format, struct spa_video_info_raw *info, spa_gamescope *gamescope_info) +static inline int spa_format_video_raw_parse_with_gamescope( + const struct spa_pod *format, + struct spa_video_info_raw *info, + spa_gamescope *gamescope_info ) { - return spa_pod_parse_object(format, - SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_VIDEO_format, SPA_POD_Id(&info->format), - SPA_FORMAT_VIDEO_modifier, SPA_POD_OPT_Long(&info->modifier), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), - SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate), - SPA_FORMAT_VIDEO_views, SPA_POD_OPT_Int(&info->views), - SPA_FORMAT_VIDEO_interlaceMode, SPA_POD_OPT_Id(&info->interlace_mode), - SPA_FORMAT_VIDEO_pixelAspectRatio, SPA_POD_OPT_Fraction(&info->pixel_aspect_ratio), - SPA_FORMAT_VIDEO_multiviewMode, SPA_POD_OPT_Id(&info->multiview_mode), - SPA_FORMAT_VIDEO_multiviewFlags, SPA_POD_OPT_Id(&info->multiview_flags), - SPA_FORMAT_VIDEO_chromaSite, SPA_POD_OPT_Id(&info->chroma_site), - SPA_FORMAT_VIDEO_colorRange, SPA_POD_OPT_Id(&info->color_range), - SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_OPT_Id(&info->color_matrix), - SPA_FORMAT_VIDEO_transferFunction, SPA_POD_OPT_Id(&info->transfer_function), - SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_OPT_Id(&info->color_primaries), - SPA_FORMAT_VIDEO_requested_size, SPA_POD_OPT_Rectangle(&gamescope_info->requested_size), - SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_OPT_Long(&gamescope_info->focus_appid)); + return spa_pod_parse_object( + format, + SPA_TYPE_OBJECT_Format, + NULL, + SPA_FORMAT_VIDEO_format, + SPA_POD_Id( &info->format ), + SPA_FORMAT_VIDEO_modifier, + SPA_POD_OPT_Long( &info->modifier ), + SPA_FORMAT_VIDEO_size, + SPA_POD_Rectangle( &info->size ), + SPA_FORMAT_VIDEO_framerate, + SPA_POD_Fraction( &info->framerate ), + SPA_FORMAT_VIDEO_maxFramerate, + SPA_POD_OPT_Fraction( &info->max_framerate ), + SPA_FORMAT_VIDEO_views, + SPA_POD_OPT_Int( &info->views ), + SPA_FORMAT_VIDEO_interlaceMode, + SPA_POD_OPT_Id( &info->interlace_mode ), + SPA_FORMAT_VIDEO_pixelAspectRatio, + SPA_POD_OPT_Fraction( &info->pixel_aspect_ratio ), + SPA_FORMAT_VIDEO_multiviewMode, + SPA_POD_OPT_Id( &info->multiview_mode ), + SPA_FORMAT_VIDEO_multiviewFlags, + SPA_POD_OPT_Id( &info->multiview_flags ), + SPA_FORMAT_VIDEO_chromaSite, + SPA_POD_OPT_Id( &info->chroma_site ), + SPA_FORMAT_VIDEO_colorRange, + SPA_POD_OPT_Id( &info->color_range ), + SPA_FORMAT_VIDEO_colorMatrix, + SPA_POD_OPT_Id( &info->color_matrix ), + SPA_FORMAT_VIDEO_transferFunction, + SPA_POD_OPT_Id( &info->transfer_function ), + SPA_FORMAT_VIDEO_colorPrimaries, + SPA_POD_OPT_Id( &info->color_primaries ), + SPA_FORMAT_VIDEO_requested_size, + SPA_POD_OPT_Rectangle( &gamescope_info->requested_size ), + SPA_FORMAT_VIDEO_gamescope_focus_appid, + SPA_POD_OPT_Long( &gamescope_info->focus_appid ) ); } - diff --git a/src/rc.h b/src/rc.h index 980dd5c4a8..82d7d10379 100644 --- a/src/rc.h +++ b/src/rc.h @@ -1,39 +1,32 @@ #pragma once -#include #include +#include namespace gamescope { class RcObject { public: - virtual ~RcObject() - { - } + virtual ~RcObject( ) {} - uint32_t IncRef() + uint32_t IncRef( ) { uint32_t uRefCount = m_uRefCount++; - if ( !uRefCount ) - IncRefPrivate(); + if ( !uRefCount ) IncRefPrivate( ); return uRefCount; } - uint32_t DecRef() + uint32_t DecRef( ) { uint32_t uRefCount = --m_uRefCount; - if ( !uRefCount ) - DecRefPrivate(); + if ( !uRefCount ) DecRefPrivate( ); return uRefCount; } - uint32_t IncRefPrivate() - { - return m_uRefPrivate++; - } + uint32_t IncRefPrivate( ) { return m_uRefPrivate++; } - uint32_t DecRefPrivate() + uint32_t DecRefPrivate( ) { uint32_t uRefPrivate = --m_uRefPrivate; if ( !uRefPrivate ) @@ -41,23 +34,18 @@ namespace gamescope m_uRefPrivate += 0x80000000; delete this; } - + return uRefPrivate; } - uint32_t GetRefCount() const - { - return m_uRefCount; - } + uint32_t GetRefCount( ) const { return m_uRefCount; } - uint32_t GetRefCountPrivate() const - { - return m_uRefPrivate; - } + uint32_t GetRefCountPrivate( ) const { return m_uRefPrivate; } - bool HasLiveReferences() const + bool HasLiveReferences( ) const { - return bool( m_uRefCount.load() | ( m_uRefPrivate.load() & 0x7FFFFFFF ) ); + return bool( + m_uRefCount.load( ) | ( m_uRefPrivate.load( ) & 0x7FFFFFFF ) ); } private: @@ -68,151 +56,120 @@ namespace gamescope class IRcObject : public RcObject { public: - virtual uint32_t IncRef() - { - return RcObject::IncRef(); - } + virtual uint32_t IncRef( ) { return RcObject::IncRef( ); } - virtual uint32_t DecRef() - { - return RcObject::DecRef(); - } + virtual uint32_t DecRef( ) { return RcObject::DecRef( ); } }; - template - struct RcRef_ + template struct RcRef_ { - static void IncRef( T* pObject ) { pObject->IncRef(); } - static void DecRef( T* pObject ) { pObject->DecRef(); } + static void IncRef( T *pObject ) { pObject->IncRef( ); } + static void DecRef( T *pObject ) { pObject->DecRef( ); } }; - template - struct RcRef_ + template struct RcRef_ { - static void IncRef( T* pObject ) { pObject->IncRefPrivate(); } - static void DecRef( T* pObject ) { pObject->DecRefPrivate(); } + static void IncRef( T *pObject ) { pObject->IncRefPrivate( ); } + static void DecRef( T *pObject ) { pObject->DecRefPrivate( ); } }; - template - class Rc + template class Rc { - template - friend class Rc; + template friend class Rc; using RcRef = RcRef_; + public: - Rc() { } - Rc( std::nullptr_t ) { } + Rc( ) {} + Rc( std::nullptr_t ) {} - Rc( T* pObject ) - : m_pObject{ pObject } - { - this->IncRef(); - } + Rc( T *pObject ) : m_pObject{ pObject } { this->IncRef( ); } - Rc( const Rc& other ) - : m_pObject{ other.m_pObject } - { - this->IncRef(); - } + Rc( const Rc &other ) : m_pObject{ other.m_pObject } + { this->IncRef( ); } - template - Rc( const Rc& other ) - : m_pObject{ other.m_pObject } - { - this->IncRef(); - } + template + Rc( const Rc &other ) : m_pObject{ other.m_pObject } + { this->IncRef( ); } - Rc( Rc&& other ) - : m_pObject{ other.m_pObject } - { - other.m_pObject = nullptr; - } + Rc( Rc &&other ) : m_pObject{ other.m_pObject } + { other.m_pObject = nullptr; } - template - Rc( Rc&& other ) - : m_pObject{ other.m_pObject } - { - other.m_pObject = nullptr; - } + template + Rc( Rc &&other ) : m_pObject{ other.m_pObject } + { other.m_pObject = nullptr; } - Rc& operator = ( std::nullptr_t ) + Rc &operator=( std::nullptr_t ) { - this->DecRef(); + this->DecRef( ); m_pObject = nullptr; return *this; } - Rc& operator = ( const Rc& other ) + Rc &operator=( const Rc &other ) { - other.IncRef(); - this->DecRef(); + other.IncRef( ); + this->DecRef( ); m_pObject = other.m_pObject; return *this; } - template - Rc& operator = ( const Rc& other ) + template Rc &operator=( const Rc &other ) { - other.IncRef(); - this->DecRef(); + other.IncRef( ); + this->DecRef( ); m_pObject = other.m_pObject; return *this; } - Rc& operator = ( Rc&& other ) + Rc &operator=( Rc &&other ) { - this->DecRef(); + this->DecRef( ); this->m_pObject = other.m_pObject; other.m_pObject = nullptr; return *this; } - template - Rc& operator = ( Rc&& other ) + template Rc &operator=( Rc &&other ) { - this->DecRef(); + this->DecRef( ); this->m_pObject = other.m_pObject; other.m_pObject = nullptr; return *this; } - ~Rc() - { - this->DecRef(); - } + ~Rc( ) { this->DecRef( ); } - T& operator * () const { return *m_pObject; } - T* operator -> () const { return m_pObject; } - T* get() const { return m_pObject; } + T &operator*( ) const { return *m_pObject; } + T *operator->( ) const { return m_pObject; } + T *get( ) const { return m_pObject; } - bool operator == ( const Rc& other ) const { return m_pObject == other.m_pObject; } - bool operator != ( const Rc& other ) const { return m_pObject != other.m_pObject; } + bool operator==( const Rc &other ) const + { return m_pObject == other.m_pObject; } + bool operator!=( const Rc &other ) const + { return m_pObject != other.m_pObject; } - bool operator == ( T *pOther ) const { return m_pObject == pOther; } - bool operator != ( T *pOther ) const { return m_pObject == pOther; } + bool operator==( T *pOther ) const { return m_pObject == pOther; } + bool operator!=( T *pOther ) const { return m_pObject == pOther; } - bool operator == ( std::nullptr_t ) const { return m_pObject == nullptr; } - bool operator != ( std::nullptr_t ) const { return m_pObject != nullptr; } + bool operator==( std::nullptr_t ) const { return m_pObject == nullptr; } + bool operator!=( std::nullptr_t ) const { return m_pObject != nullptr; } - operator bool() const { return m_pObject != nullptr; } + operator bool( ) const { return m_pObject != nullptr; } private: - T* m_pObject = nullptr; + T *m_pObject = nullptr; - inline void IncRef() const + inline void IncRef( ) const { - if ( m_pObject != nullptr ) - RcRef::IncRef( m_pObject ); + if ( m_pObject != nullptr ) RcRef::IncRef( m_pObject ); } - inline void DecRef() const + inline void DecRef( ) const { - if ( m_pObject != nullptr ) - RcRef::DecRef( m_pObject ); + if ( m_pObject != nullptr ) RcRef::DecRef( m_pObject ); } }; - template - using OwningRc = Rc; -} \ No newline at end of file + template using OwningRc = Rc; +} // namespace gamescope diff --git a/src/refresh_rate.h b/src/refresh_rate.h index cc7c0f7134..d2a3eddf29 100644 --- a/src/refresh_rate.h +++ b/src/refresh_rate.h @@ -5,9 +5,7 @@ namespace gamescope { constexpr int32_t ConvertHztomHz( int32_t nRefreshHz ) - { - return nRefreshHz * 1'000; - } + { return nRefreshHz * 1'000; } constexpr int32_t ConvertmHzToHz( int32_t nRefreshmHz ) { @@ -20,24 +18,16 @@ namespace gamescope } constexpr uint32_t ConvertHztomHz( uint32_t nRefreshHz ) - { - return nRefreshHz * 1'000; - } + { return nRefreshHz * 1'000; } constexpr uint32_t ConvertmHzToHz( uint32_t nRefreshmHz ) - { - return ( nRefreshmHz + 499 ) / 1'000; - } + { return ( nRefreshmHz + 499 ) / 1'000; } constexpr float ConvertHztomHz( float flRefreshHz ) - { - return flRefreshHz * 1000.0f; - } + { return flRefreshHz * 1000.0f; } constexpr float ConvertmHzToHz( float nRefreshmHz ) - { - return ( nRefreshmHz ) / 1'000.0; - } + { return ( nRefreshmHz ) / 1'000.0; } constexpr uint32_t RefreshCycleTomHz( int32_t nCycle ) { @@ -50,4 +40,4 @@ namespace gamescope // Same thing. return RefreshCycleTomHz( nmHz ); } -} +} // namespace gamescope diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp index 0073ce7a70..ee019b0eb0 100644 --- a/src/rendervulkan.cpp +++ b/src/rendervulkan.cpp @@ -1,42 +1,43 @@ // Initialize Vulkan and composite stuff with a compute queue +#include +#include +#include #include +#include #include -#include #include #include #include -#include -#include -#include #include -#include -#include "vulkan_include.h" +#include #include "Utils/Algorithm.h" +#include "vulkan_include.h" -#if defined(__linux__) -#include +#if defined( __linux__ ) + #include #endif // Used to remove the config struct alignment specified by the NIS header -#define NIS_ALIGNED(x) -// NIS_Config needs to be included before the X11 headers because of conflicting defines introduced by X11 +#define NIS_ALIGNED( x ) +// NIS_Config needs to be included before the X11 headers because of conflicting +// defines introduced by X11 #include "shaders/NVIDIAImageScaling/NIS/NIS_Config.h" #include #include "hdmi.h" #if HAVE_DRM -#include "drm_include.h" + #include "drm_include.h" #endif -#include "wlr_begin.hpp" #include +#include "wlr_begin.hpp" #include "wlr_end.hpp" -#include "rendervulkan.hpp" +#include "Utils/Process.h" +#include "log.hpp" #include "main.hpp" +#include "rendervulkan.hpp" #include "steamcompmgr.hpp" -#include "log.hpp" -#include "Utils/Process.h" #include "cs_composite_blit.h" #include "cs_composite_blur.h" @@ -58,534 +59,717 @@ extern bool g_bWasPartialComposite; extern bool g_bAllowDeferredBackend; -static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601_limited = {{ - { 0.257f, 0.504f, 0.098f, 0.0625f }, - { -0.148f, -0.291f, 0.439f, 0.5f }, - { 0.439f, -0.368f, -0.071f, 0.5f }, -}}; - -static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601 = {{ - { 0.299f, 0.587f, 0.114f, 0.0f }, - { -0.169f, -0.331f, 0.500f, 0.5f }, - { 0.500f, -0.419f, -0.081f, 0.5f }, -}}; - -static constexpr mat3x4 g_rgb2yuv_srgb_to_bt709_limited = {{ - { 0.1826f, 0.6142f, 0.0620f, 0.0625f }, - { -0.1006f, -0.3386f, 0.4392f, 0.5f }, - { 0.4392f, -0.3989f, -0.0403f, 0.5f }, -}}; - -static constexpr mat3x4 g_rgb2yuv_srgb_to_bt709_full = {{ - { 0.2126f, 0.7152f, 0.0722f, 0.0f }, - { -0.1146f, -0.3854f, 0.5000f, 0.5f }, - { 0.5000f, -0.4542f, -0.0458f, 0.5f }, -}}; - -static const mat3x4& colorspace_to_conversion_from_srgb_matrix(EStreamColorspace colorspace) { - switch (colorspace) { - default: - case k_EStreamColorspace_BT601: return g_rgb2yuv_srgb_to_bt601_limited; - case k_EStreamColorspace_BT601_Full: return g_rgb2yuv_srgb_to_bt601; - case k_EStreamColorspace_BT709: return g_rgb2yuv_srgb_to_bt709_limited; - case k_EStreamColorspace_BT709_Full: return g_rgb2yuv_srgb_to_bt709_full; - } +static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601_limited = { { + { 0.257f, 0.504f, 0.098f, 0.0625f }, + { -0.148f, -0.291f, 0.439f, 0.5f }, + { 0.439f, -0.368f, -0.071f, 0.5f }, +} }; + +static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601 = { { + { 0.299f, 0.587f, 0.114f, 0.0f }, + { -0.169f, -0.331f, 0.500f, 0.5f }, + { 0.500f, -0.419f, -0.081f, 0.5f }, +} }; + +static constexpr mat3x4 g_rgb2yuv_srgb_to_bt709_limited = { { + { 0.1826f, 0.6142f, 0.0620f, 0.0625f }, + { -0.1006f, -0.3386f, 0.4392f, 0.5f }, + { 0.4392f, -0.3989f, -0.0403f, 0.5f }, +} }; + +static constexpr mat3x4 g_rgb2yuv_srgb_to_bt709_full = { { + { 0.2126f, 0.7152f, 0.0722f, 0.0f }, + { -0.1146f, -0.3854f, 0.5000f, 0.5f }, + { 0.5000f, -0.4542f, -0.0458f, 0.5f }, +} }; + +static const mat3x4 & +colorspace_to_conversion_from_srgb_matrix( EStreamColorspace colorspace ) +{ + switch ( colorspace ) + { + default: + case k_EStreamColorspace_BT601: + return g_rgb2yuv_srgb_to_bt601_limited; + case k_EStreamColorspace_BT601_Full: + return g_rgb2yuv_srgb_to_bt601; + case k_EStreamColorspace_BT709: + return g_rgb2yuv_srgb_to_bt709_limited; + case k_EStreamColorspace_BT709_Full: + return g_rgb2yuv_srgb_to_bt709_full; + } } PFN_vkGetInstanceProcAddr g_pfn_vkGetInstanceProcAddr; -PFN_vkCreateInstance g_pfn_vkCreateInstance; +PFN_vkCreateInstance g_pfn_vkCreateInstance; -static VkResult vulkan_load_module() +static VkResult vulkan_load_module( ) { - static VkResult s_result = []() - { - void* pModule = dlopen( "libvulkan.so.1", RTLD_NOW | RTLD_LOCAL ); - if ( !pModule ) - pModule = dlopen( "libvulkan.so", RTLD_NOW | RTLD_LOCAL ); - if ( !pModule ) - return VK_ERROR_INITIALIZATION_FAILED; - - g_pfn_vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym( pModule, "vkGetInstanceProcAddr" ); - if ( !g_pfn_vkGetInstanceProcAddr ) - return VK_ERROR_INITIALIZATION_FAILED; - - g_pfn_vkCreateInstance = (PFN_vkCreateInstance) g_pfn_vkGetInstanceProcAddr( nullptr, "vkCreateInstance" ); - if ( !g_pfn_vkCreateInstance ) - return VK_ERROR_INITIALIZATION_FAILED; - - return VK_SUCCESS; - }(); - - return s_result; + static VkResult s_result = []( ) + { + void *pModule = dlopen( "libvulkan.so.1", RTLD_NOW | RTLD_LOCAL ); + if ( !pModule ) + pModule = dlopen( "libvulkan.so", RTLD_NOW | RTLD_LOCAL ); + if ( !pModule ) return VK_ERROR_INITIALIZATION_FAILED; + + g_pfn_vkGetInstanceProcAddr = ( PFN_vkGetInstanceProcAddr )dlsym( + pModule, "vkGetInstanceProcAddr" ); + if ( !g_pfn_vkGetInstanceProcAddr ) + return VK_ERROR_INITIALIZATION_FAILED; + + g_pfn_vkCreateInstance = + ( PFN_vkCreateInstance )g_pfn_vkGetInstanceProcAddr( + nullptr, "vkCreateInstance" ); + if ( !g_pfn_vkCreateInstance ) return VK_ERROR_INITIALIZATION_FAILED; + + return VK_SUCCESS; + }( ); + + return s_result; } VulkanOutput_t g_output; -uint32_t g_uCompositeDebug = 0u; -gamescope::ConVar cv_composite_debug{ "composite_debug", 0, "Debug composition flags" }; +uint32_t g_uCompositeDebug = 0u; +gamescope::ConVar cv_composite_debug{ "composite_debug", + 0, + "Debug composition flags" }; -static std::map< VkFormat, std::map< uint64_t, VkDrmFormatModifierPropertiesEXT > > DRMModifierProps = {}; -static std::unordered_map> s_SampledModifierFormats = {}; -static struct wlr_drm_format_set sampledShmFormats = {}; -static struct wlr_drm_format_set sampledDRMFormats = {}; +static std::map> + DRMModifierProps = {}; +static std::unordered_map> + s_SampledModifierFormats = {}; +static struct wlr_drm_format_set sampledShmFormats = {}; +static struct wlr_drm_format_set sampledDRMFormats = {}; std::span GetSupportedSampleModifiers( uint32_t uDrmFormat ) { - auto iter = s_SampledModifierFormats.find( uDrmFormat ); - if ( iter == s_SampledModifierFormats.end() ) - return std::span{}; + auto iter = s_SampledModifierFormats.find( uDrmFormat ); + if ( iter == s_SampledModifierFormats.end( ) ) + return std::span{}; - return std::span{ iter->second.begin(), iter->second.end() }; + return std::span{ iter->second.begin( ), + iter->second.end( ) }; } -static LogScope vk_log("vulkan"); +static LogScope vk_log( "vulkan" ); -static void vk_errorf(VkResult result, const char *fmt, ...) { - static char buf[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); +static void vk_errorf( VkResult result, const char *fmt, ... ) +{ + static char buf[ 1024 ]; + va_list args; + va_start( args, fmt ); + vsnprintf( buf, sizeof( buf ), fmt, args ); + va_end( args ); - vk_log.errorf("%s (VkResult: %d)", buf, result); + vk_log.errorf( "%s (VkResult: %d)", buf, result ); } // For when device is up and it would be totally fatal to fail -#define vk_check( x ) \ - do \ - { \ - VkResult check_res = VK_SUCCESS; \ - if ( ( check_res = ( x ) ) != VK_SUCCESS ) \ - { \ - vk_errorf( check_res, #x " failed!" ); \ - abort(); \ - } \ - } while ( 0 ) +#define vk_check( x ) \ + do \ + { \ + VkResult check_res = VK_SUCCESS; \ + if ( ( check_res = ( x ) ) != VK_SUCCESS ) \ + { \ + vk_errorf( check_res, #x " failed!" ); \ + abort( ); \ + } \ + } while ( 0 ) template -Target *pNextFind(const Base *base, VkStructureType sType) +Target *pNextFind( const Base *base, VkStructureType sType ) { - for ( ; base; base = (const Base *)base->pNext ) - { - if (base->sType == sType) - return (Target *) base; - } - return nullptr; + for ( ; base; base = ( const Base * )base->pNext ) + { + if ( base->sType == sType ) return ( Target * )base; + } + return nullptr; } -#define VK_STRUCTURE_TYPE_WSI_IMAGE_CREATE_INFO_MESA (VkStructureType)1000001002 -#define VK_STRUCTURE_TYPE_WSI_MEMORY_ALLOCATE_INFO_MESA (VkStructureType)1000001003 +#define VK_STRUCTURE_TYPE_WSI_IMAGE_CREATE_INFO_MESA \ + ( VkStructureType )1000001002 +#define VK_STRUCTURE_TYPE_WSI_MEMORY_ALLOCATE_INFO_MESA \ + ( VkStructureType )1000001003 -struct wsi_image_create_info { - VkStructureType sType; - const void *pNext; - bool scanout; +struct wsi_image_create_info +{ + VkStructureType sType; + const void *pNext; + bool scanout; - uint32_t modifier_count; - const uint64_t *modifiers; + uint32_t modifier_count; + const uint64_t *modifiers; }; -struct wsi_memory_allocate_info { +struct wsi_memory_allocate_info +{ VkStructureType sType; - const void *pNext; - bool implicit_sync; + const void *pNext; + bool implicit_sync; }; -// DRM doesn't always have 32bit floating point formats, so add our own if necessary +// DRM doesn't always have 32bit floating point formats, so add our own if +// necessary #ifndef DRM_FORMAT_ABGR32323232F -#define DRM_FORMAT_ABGR32323232F fourcc_code('A', 'B', '8', 'F') + #define DRM_FORMAT_ABGR32323232F fourcc_code( 'A', 'B', '8', 'F' ) #endif #ifndef DRM_FORMAT_R16F -#define DRM_FORMAT_R16F fourcc_code('R', '1', '6', 'F') + #define DRM_FORMAT_R16F fourcc_code( 'R', '1', '6', 'F' ) #endif #ifndef DRM_FORMAT_R32F -#define DRM_FORMAT_R32F fourcc_code('R', '3', '2', 'F') + #define DRM_FORMAT_R32F fourcc_code( 'R', '3', '2', 'F' ) #endif -struct { - uint32_t DRMFormat; - VkFormat vkFormat; - VkFormat vkFormatSrgb; - uint32_t bpp; - bool bHasAlpha; - bool internal; +struct +{ + uint32_t DRMFormat; + VkFormat vkFormat; + VkFormat vkFormatSrgb; + uint32_t bpp; + bool bHasAlpha; + bool internal; } s_DRMVKFormatTable[] = { - { DRM_FORMAT_ARGB8888, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB, 4, true, false }, - { DRM_FORMAT_XRGB8888, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB, 4, false, false }, - { DRM_FORMAT_ABGR8888, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB, 4, true, false }, - { DRM_FORMAT_XBGR8888, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB, 4, false, false }, - { DRM_FORMAT_RGB565, VK_FORMAT_R5G6B5_UNORM_PACK16, VK_FORMAT_R5G6B5_UNORM_PACK16, 1, false, false }, - { DRM_FORMAT_NV12, VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, 0, false, false }, - { DRM_FORMAT_ABGR16161616F, VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT, 8, true, false }, - { DRM_FORMAT_XBGR16161616F, VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT, 8, false, false }, - { DRM_FORMAT_ABGR16161616, VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_UNORM, 8, true, false }, - { DRM_FORMAT_XBGR16161616, VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_UNORM, 8, false, false }, - { DRM_FORMAT_ABGR2101010, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4, true, false }, - { DRM_FORMAT_XBGR2101010, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4, false, false }, - { DRM_FORMAT_ARGB2101010, VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_FORMAT_A2R10G10B10_UNORM_PACK32, 4, true, false }, - { DRM_FORMAT_XRGB2101010, VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_FORMAT_A2R10G10B10_UNORM_PACK32, 4, false, false }, - - { DRM_FORMAT_R8, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, 1, false, true }, - { DRM_FORMAT_R16, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, 2, false, true }, - { DRM_FORMAT_GR88, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_UNORM, 2, false, true }, - { DRM_FORMAT_GR1616, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16_UNORM, 4, false, true }, - { DRM_FORMAT_ABGR32323232F, VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT, 16,true, true }, - { DRM_FORMAT_R16F, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16_SFLOAT, 2, false, true }, - { DRM_FORMAT_R32F, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_SFLOAT, 4, false, true }, - { DRM_FORMAT_INVALID, VK_FORMAT_UNDEFINED, VK_FORMAT_UNDEFINED, false, true }, + { DRM_FORMAT_ARGB8888, + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_B8G8R8A8_SRGB, + 4, + true, + false }, + { DRM_FORMAT_XRGB8888, + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_B8G8R8A8_SRGB, + 4, + false, + false }, + { DRM_FORMAT_ABGR8888, + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_R8G8B8A8_SRGB, + 4, + true, + false }, + { DRM_FORMAT_XBGR8888, + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_R8G8B8A8_SRGB, + 4, + false, + false }, + { DRM_FORMAT_RGB565, + VK_FORMAT_R5G6B5_UNORM_PACK16, + VK_FORMAT_R5G6B5_UNORM_PACK16, + 1, + false, + false }, + { DRM_FORMAT_NV12, + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + 0, + false, + false }, + { DRM_FORMAT_ABGR16161616F, + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_R16G16B16A16_SFLOAT, + 8, + true, + false }, + { DRM_FORMAT_XBGR16161616F, + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_R16G16B16A16_SFLOAT, + 8, + false, + false }, + { DRM_FORMAT_ABGR16161616, + VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_UNORM, + 8, + true, + false }, + { DRM_FORMAT_XBGR16161616, + VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_UNORM, + 8, + false, + false }, + { DRM_FORMAT_ABGR2101010, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + 4, + true, + false }, + { DRM_FORMAT_XBGR2101010, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + 4, + false, + false }, + { DRM_FORMAT_ARGB2101010, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + 4, + true, + false }, + { DRM_FORMAT_XRGB2101010, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + 4, + false, + false }, + + { DRM_FORMAT_R8, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, 1, false, true }, + { DRM_FORMAT_R16, + VK_FORMAT_R16_UNORM, + VK_FORMAT_R16_UNORM, + 2, + false, + true }, + { DRM_FORMAT_GR88, + VK_FORMAT_R8G8_UNORM, + VK_FORMAT_R8G8_UNORM, + 2, + false, + true }, + { DRM_FORMAT_GR1616, + VK_FORMAT_R16G16_UNORM, + VK_FORMAT_R16G16_UNORM, + 4, + false, + true }, + { DRM_FORMAT_ABGR32323232F, + VK_FORMAT_R32G32B32A32_SFLOAT, + VK_FORMAT_R32G32B32A32_SFLOAT, + 16, + true, + true }, + { DRM_FORMAT_R16F, + VK_FORMAT_R16_SFLOAT, + VK_FORMAT_R16_SFLOAT, + 2, + false, + true }, + { DRM_FORMAT_R32F, + VK_FORMAT_R32_SFLOAT, + VK_FORMAT_R32_SFLOAT, + 4, + false, + true }, + { DRM_FORMAT_INVALID, + VK_FORMAT_UNDEFINED, + VK_FORMAT_UNDEFINED, + false, + true }, }; -uint32_t VulkanFormatToDRM( VkFormat vkFormat, std::optional obHasAlphaOverride ) +uint32_t +VulkanFormatToDRM( VkFormat vkFormat, std::optional obHasAlphaOverride ) { - for ( int i = 0; s_DRMVKFormatTable[i].vkFormat != VK_FORMAT_UNDEFINED; i++ ) - { - if ( ( s_DRMVKFormatTable[i].vkFormat == vkFormat || s_DRMVKFormatTable[i].vkFormatSrgb == vkFormat ) && ( !obHasAlphaOverride || s_DRMVKFormatTable[i].bHasAlpha == *obHasAlphaOverride ) ) - { - return s_DRMVKFormatTable[i].DRMFormat; - } - } - - return DRM_FORMAT_INVALID; + for ( int i = 0; s_DRMVKFormatTable[ i ].vkFormat != VK_FORMAT_UNDEFINED; + i++ ) + { + if ( ( s_DRMVKFormatTable[ i ].vkFormat == vkFormat || + s_DRMVKFormatTable[ i ].vkFormatSrgb == vkFormat ) && + ( !obHasAlphaOverride || + s_DRMVKFormatTable[ i ].bHasAlpha == *obHasAlphaOverride ) ) + { + return s_DRMVKFormatTable[ i ].DRMFormat; + } + } + + return DRM_FORMAT_INVALID; } VkFormat DRMFormatToVulkan( uint32_t nDRMFormat, bool bSrgb ) { - for ( int i = 0; s_DRMVKFormatTable[i].vkFormat != VK_FORMAT_UNDEFINED; i++ ) - { - if ( s_DRMVKFormatTable[i].DRMFormat == nDRMFormat ) - { - return bSrgb ? s_DRMVKFormatTable[i].vkFormatSrgb : s_DRMVKFormatTable[i].vkFormat; - } - } - - return VK_FORMAT_UNDEFINED; + for ( int i = 0; s_DRMVKFormatTable[ i ].vkFormat != VK_FORMAT_UNDEFINED; + i++ ) + { + if ( s_DRMVKFormatTable[ i ].DRMFormat == nDRMFormat ) + { + return bSrgb ? s_DRMVKFormatTable[ i ].vkFormatSrgb + : s_DRMVKFormatTable[ i ].vkFormat; + } + } + + return VK_FORMAT_UNDEFINED; } bool DRMFormatHasAlpha( uint32_t nDRMFormat ) { - for ( int i = 0; s_DRMVKFormatTable[i].vkFormat != VK_FORMAT_UNDEFINED; i++ ) - { - if ( s_DRMVKFormatTable[i].DRMFormat == nDRMFormat ) - { - return s_DRMVKFormatTable[i].bHasAlpha; - } - } - - return false; + for ( int i = 0; s_DRMVKFormatTable[ i ].vkFormat != VK_FORMAT_UNDEFINED; + i++ ) + { + if ( s_DRMVKFormatTable[ i ].DRMFormat == nDRMFormat ) + { + return s_DRMVKFormatTable[ i ].bHasAlpha; + } + } + + return false; } uint32_t DRMFormatGetBPP( uint32_t nDRMFormat ) { - for ( int i = 0; s_DRMVKFormatTable[i].vkFormat != VK_FORMAT_UNDEFINED; i++ ) - { - if ( s_DRMVKFormatTable[i].DRMFormat == nDRMFormat ) - { - return s_DRMVKFormatTable[i].bpp; - } - } - - return false; + for ( int i = 0; s_DRMVKFormatTable[ i ].vkFormat != VK_FORMAT_UNDEFINED; + i++ ) + { + if ( s_DRMVKFormatTable[ i ].DRMFormat == nDRMFormat ) + { + return s_DRMVKFormatTable[ i ].bpp; + } + } + + return false; } -bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface) +bool CVulkanDevice::BInit( VkInstance instance, VkSurfaceKHR surface ) { - assert(instance); - assert(!m_bInitialized); + assert( instance ); + assert( !m_bInitialized ); - g_output.surface = surface; + g_output.surface = surface; - m_instance = instance; - #define VK_FUNC(x) vk.x = (PFN_vk##x) g_pfn_vkGetInstanceProcAddr(instance, "vk"#x); - VULKAN_INSTANCE_FUNCTIONS - #undef VK_FUNC + m_instance = instance; +#define VK_FUNC( x ) \ + vk.x = ( PFN_vk##x )g_pfn_vkGetInstanceProcAddr( instance, "vk" #x ); + VULKAN_INSTANCE_FUNCTIONS +#undef VK_FUNC - if (!selectPhysDev(surface)) - return false; - if (!createDevice()) - return false; - if (!createLayouts()) - return false; - if (!createPools()) - return false; - if (!createShaders()) - return false; - if (!createScratchResources()) - return false; + if ( !selectPhysDev( surface ) ) return false; + if ( !createDevice( ) ) return false; + if ( !createLayouts( ) ) return false; + if ( !createPools( ) ) return false; + if ( !createShaders( ) ) return false; + if ( !createScratchResources( ) ) return false; - m_bInitialized = true; + m_bInitialized = true; - std::thread piplelineThread([this](){compileAllPipelines();}); - piplelineThread.detach(); + std::thread piplelineThread( [ this ]( ) { compileAllPipelines( ); } ); + piplelineThread.detach( ); - g_reshadeManager.init(this); + g_reshadeManager.init( this ); - return true; + return true; } -extern bool env_to_bool(const char *env); +extern bool env_to_bool( const char *env ); -bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) +bool CVulkanDevice::selectPhysDev( VkSurfaceKHR surface ) { - uint32_t deviceCount = 0; - vk.EnumeratePhysicalDevices(instance(), &deviceCount, nullptr); - std::vector physDevs(deviceCount); - vk.EnumeratePhysicalDevices(instance(), &deviceCount, physDevs.data()); - if (deviceCount < physDevs.size()) - physDevs.resize(deviceCount); - - bool bTryComputeOnly = true; - - // In theory vkBasalt might want to filter out compute-only queue families to force our hand here - const char *pchEnableVkBasalt = getenv( "ENABLE_VKBASALT" ); - if ( pchEnableVkBasalt != nullptr && pchEnableVkBasalt[0] == '1' ) - { - bTryComputeOnly = false; - } - - for (auto cphysDev : physDevs) - { - VkPhysicalDeviceProperties deviceProperties; - vk.GetPhysicalDeviceProperties(cphysDev, &deviceProperties); - - if (deviceProperties.apiVersion < VK_API_VERSION_1_2) - continue; - - uint32_t queueFamilyCount = 0; - vk.GetPhysicalDeviceQueueFamilyProperties(cphysDev, &queueFamilyCount, nullptr); - std::vector queueFamilyProperties(queueFamilyCount); - vk.GetPhysicalDeviceQueueFamilyProperties(cphysDev, &queueFamilyCount, queueFamilyProperties.data()); - - uint32_t generalIndex = ~0u; - uint32_t computeOnlyIndex = ~0u; - for (uint32_t i = 0; i < queueFamilyCount; ++i) { - const VkQueueFlags generalBits = VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT; - if ((queueFamilyProperties[i].queueFlags & generalBits) == generalBits ) - generalIndex = std::min(generalIndex, i); - else if (bTryComputeOnly && queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) - computeOnlyIndex = std::min(computeOnlyIndex, i); - } - - if (generalIndex != ~0u || computeOnlyIndex != ~0u) - { - // Select the device if it's the first one or the preferred one - if (!m_physDev || - (g_preferVendorID == deviceProperties.vendorID && g_preferDeviceID == deviceProperties.deviceID)) - { - // if we have a surface, check that the queue family can actually present on it - if (surface) { - VkBool32 canPresent = false; - vk.GetPhysicalDeviceSurfaceSupportKHR( cphysDev, generalIndex, surface, &canPresent ); - if ( !canPresent ) - { - vk_log.infof( "physical device %04x:%04x queue doesn't support presenting on our surface, testing next one..", deviceProperties.vendorID, deviceProperties.deviceID ); - continue; - } - if (computeOnlyIndex != ~0u) - { - vk.GetPhysicalDeviceSurfaceSupportKHR( cphysDev, computeOnlyIndex, surface, &canPresent ); - if ( !canPresent ) - { - vk_log.infof( "physical device %04x:%04x compute queue doesn't support presenting on our surface, using graphics queue", deviceProperties.vendorID, deviceProperties.deviceID ); - computeOnlyIndex = ~0u; - } - } - } - - m_queueFamily = computeOnlyIndex == ~0u ? generalIndex : computeOnlyIndex; - m_generalQueueFamily = generalIndex; - m_physDev = cphysDev; - - /* When Intel uses compute-only queue for Gamescope composition, some games - * experience performance loss. Using the general queue alleviates the issue - * for now. - * See: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/4452 - * - * TODO: Remove vendorID check for Intel once issue is resolved. - */ - if (deviceProperties.vendorID == 0x8086) /* Intel */ - { - vk_log.infof("Intel device detected, forcing general queue family instead of compute-only queue"); - m_queueFamily = generalIndex; - } - else if ( env_to_bool( getenv( "GAMESCOPE_FORCE_GENERAL_QUEUE" ) ) ) - m_queueFamily = generalIndex; - } - } - } - - if (!m_physDev) - { - vk_log.errorf("failed to find physical device"); - return false; - } - - VkPhysicalDeviceProperties props; - vk.GetPhysicalDeviceProperties( m_physDev, &props ); - vk_log.infof( "selecting physical device '%s': queue family %x (general queue family %x)", props.deviceName, m_queueFamily, m_generalQueueFamily ); - - return true; -} + uint32_t deviceCount = 0; + vk.EnumeratePhysicalDevices( instance( ), &deviceCount, nullptr ); + std::vector physDevs( deviceCount ); + vk.EnumeratePhysicalDevices( instance( ), &deviceCount, physDevs.data( ) ); + if ( deviceCount < physDevs.size( ) ) physDevs.resize( deviceCount ); + + bool bTryComputeOnly = true; + + // In theory vkBasalt might want to filter out compute-only queue families + // to force our hand here + const char *pchEnableVkBasalt = getenv( "ENABLE_VKBASALT" ); + if ( pchEnableVkBasalt != nullptr && pchEnableVkBasalt[ 0 ] == '1' ) + { + bTryComputeOnly = false; + } -bool CVulkanDevice::createDevice() -{ - uint32_t supportedExtensionCount; - vk.EnumerateDeviceExtensionProperties( physDev(), NULL, &supportedExtensionCount, NULL ); + for ( auto cphysDev : physDevs ) + { + VkPhysicalDeviceProperties deviceProperties; + vk.GetPhysicalDeviceProperties( cphysDev, &deviceProperties ); + + if ( deviceProperties.apiVersion < VK_API_VERSION_1_2 ) continue; + + uint32_t queueFamilyCount = 0; + vk.GetPhysicalDeviceQueueFamilyProperties( + cphysDev, &queueFamilyCount, nullptr ); + std::vector queueFamilyProperties( + queueFamilyCount ); + vk.GetPhysicalDeviceQueueFamilyProperties( + cphysDev, &queueFamilyCount, queueFamilyProperties.data( ) ); + + uint32_t generalIndex = ~0u; + uint32_t computeOnlyIndex = ~0u; + for ( uint32_t i = 0; i < queueFamilyCount; ++i ) + { + const VkQueueFlags generalBits = + VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT; + if ( ( queueFamilyProperties[ i ].queueFlags & generalBits ) == + generalBits ) + generalIndex = std::min( generalIndex, i ); + else if ( + bTryComputeOnly && + queueFamilyProperties[ i ].queueFlags & VK_QUEUE_COMPUTE_BIT ) + computeOnlyIndex = std::min( computeOnlyIndex, i ); + } + + if ( generalIndex != ~0u || computeOnlyIndex != ~0u ) + { + // Select the device if it's the first one or the preferred one + if ( !m_physDev || + ( g_preferVendorID == deviceProperties.vendorID && + g_preferDeviceID == deviceProperties.deviceID ) ) + { + // if we have a surface, check that the queue family can + // actually present on it + if ( surface ) + { + VkBool32 canPresent = false; + vk.GetPhysicalDeviceSurfaceSupportKHR( + cphysDev, generalIndex, surface, &canPresent ); + if ( !canPresent ) + { + vk_log.infof( + "physical device %04x:%04x queue doesn't support " + "presenting on our surface, testing next one..", + deviceProperties.vendorID, + deviceProperties.deviceID ); + continue; + } + if ( computeOnlyIndex != ~0u ) + { + vk.GetPhysicalDeviceSurfaceSupportKHR( + cphysDev, computeOnlyIndex, surface, &canPresent ); + if ( !canPresent ) + { + vk_log.infof( + "physical device %04x:%04x compute queue " + "doesn't support presenting on our surface, " + "using graphics queue", + deviceProperties.vendorID, + deviceProperties.deviceID ); + computeOnlyIndex = ~0u; + } + } + } + + m_queueFamily = + computeOnlyIndex == ~0u ? generalIndex : computeOnlyIndex; + m_generalQueueFamily = generalIndex; + m_physDev = cphysDev; + + /* When Intel uses compute-only queue for Gamescope composition, + * some games experience performance loss. Using the general + * queue alleviates the issue for now. See: + * https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/4452 + * + * TODO: Remove vendorID check for Intel once issue is resolved. + */ + if ( deviceProperties.vendorID == 0x8086 ) /* Intel */ + { + vk_log.infof( + "Intel device detected, forcing general queue family " + "instead of compute-only queue" ); + m_queueFamily = generalIndex; + } + else if ( + env_to_bool( getenv( "GAMESCOPE_FORCE_GENERAL_QUEUE" ) ) ) + m_queueFamily = generalIndex; + } + } + } + + if ( !m_physDev ) + { + vk_log.errorf( "failed to find physical device" ); + return false; + } + + VkPhysicalDeviceProperties props; + vk.GetPhysicalDeviceProperties( m_physDev, &props ); + vk_log.infof( + "selecting physical device '%s': queue family %x (general queue family " + "%x)", + props.deviceName, + m_queueFamily, + m_generalQueueFamily ); - m_supportedExts.resize(supportedExtensionCount); - vk.EnumerateDeviceExtensionProperties( physDev(), NULL, &supportedExtensionCount, m_supportedExts.data() ); + return true; +} - if ( !GetBackend()->ValidPhysicalDevice( physDev() ) ) { - vk_log.errorf( "not a valid physical device" ); - return false; - } +bool CVulkanDevice::createDevice( ) +{ + uint32_t supportedExtensionCount; + vk.EnumerateDeviceExtensionProperties( + physDev( ), NULL, &supportedExtensionCount, NULL ); - vk.GetPhysicalDeviceMemoryProperties( physDev(), &m_memoryProperties ); + m_supportedExts.resize( supportedExtensionCount ); + vk.EnumerateDeviceExtensionProperties( + physDev( ), NULL, &supportedExtensionCount, m_supportedExts.data( ) ); - bool hasDrmProps = vulkan_has_drm_props(); - bool supportsForeignQueue = false; - bool supportsHDRMetadata = false; - for (const auto& ext : m_supportedExts) { - if ( strcmp(ext.extensionName, VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME) == 0 ) - m_bSupportsModifiers = true; + if ( !GetBackend( )->ValidPhysicalDevice( physDev( ) ) ) + { + vk_log.errorf( "not a valid physical device" ); + return false; + } - if ( strcmp(ext.extensionName, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME) == 0 ) - supportsForeignQueue = true; + vk.GetPhysicalDeviceMemoryProperties( physDev( ), &m_memoryProperties ); - if ( strcmp(ext.extensionName, VK_EXT_HDR_METADATA_EXTENSION_NAME) == 0 ) - supportsHDRMetadata = true; - } + bool hasDrmProps = vulkan_has_drm_props( ); + bool supportsForeignQueue = false; + bool supportsHDRMetadata = false; + for ( const auto &ext : m_supportedExts ) + { + if ( strcmp( + ext.extensionName, + VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME ) == 0 ) + m_bSupportsModifiers = true; + + if ( strcmp( + ext.extensionName, + VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME ) == 0 ) + supportsForeignQueue = true; + + if ( strcmp( ext.extensionName, VK_EXT_HDR_METADATA_EXTENSION_NAME ) == + 0 ) + supportsHDRMetadata = true; + } - vk_log.infof( "physical device %s DRM format modifiers", m_bSupportsModifiers ? "supports" : "does not support" ); + vk_log.infof( + "physical device %s DRM format modifiers", + m_bSupportsModifiers ? "supports" : "does not support" ); - if ( !hasDrmProps ) { - // This could happen when e.g. running the lavapipe driver - // (without an actual physical device) - vk_log.warnf( "physical device doesn't support VK_EXT_physical_device_drm" ); - } else { + if ( !hasDrmProps ) + { + // This could happen when e.g. running the lavapipe driver + // (without an actual physical device) + vk_log.warnf( + "physical device doesn't support VK_EXT_physical_device_drm" ); + } + else + { #if HAVE_DRM - VkPhysicalDeviceDrmPropertiesEXT drmProps = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, - }; - VkPhysicalDeviceProperties2 props2 = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, - .pNext = &drmProps, - }; - vk.GetPhysicalDeviceProperties2( physDev(), &props2 ); - - if ( !GetBackend()->UsesVulkanSwapchain() && !drmProps.hasPrimary ) { - vk_log.errorf( "physical device has no primary node" ); - return false; - } - if ( !drmProps.hasRender ) { - vk_log.errorf( "physical device has no render node" ); - return false; - } - - dev_t renderDevId = makedev( drmProps.renderMajor, drmProps.renderMinor ); - drmDevice *drmDev = nullptr; - if (drmGetDeviceFromDevId(renderDevId, 0, &drmDev) != 0) { - vk_log.errorf( "drmGetDeviceFromDevId() failed" ); - return false; - } - assert(drmDev->available_nodes & (1 << DRM_NODE_RENDER)); - const char *drmRenderName = drmDev->nodes[DRM_NODE_RENDER]; - - m_drmRendererFd = open( drmRenderName, O_RDWR | O_CLOEXEC ); - drmFreeDevice(&drmDev); - if ( m_drmRendererFd < 0 ) { - vk_log.errorf_errno( "failed to open DRM render node" ); - return false; - } - - if ( drmProps.hasPrimary ) { - m_bHasDrmPrimaryDevId = true; - m_drmPrimaryDevId = makedev( drmProps.primaryMajor, drmProps.primaryMinor ); - } + VkPhysicalDeviceDrmPropertiesEXT drmProps = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, + }; + VkPhysicalDeviceProperties2 props2 = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + .pNext = &drmProps, + }; + vk.GetPhysicalDeviceProperties2( physDev( ), &props2 ); + + if ( !GetBackend( )->UsesVulkanSwapchain( ) && !drmProps.hasPrimary ) + { + vk_log.errorf( "physical device has no primary node" ); + return false; + } + if ( !drmProps.hasRender ) + { + vk_log.errorf( "physical device has no render node" ); + return false; + } + + dev_t renderDevId = + makedev( drmProps.renderMajor, drmProps.renderMinor ); + drmDevice *drmDev = nullptr; + if ( drmGetDeviceFromDevId( renderDevId, 0, &drmDev ) != 0 ) + { + vk_log.errorf( "drmGetDeviceFromDevId() failed" ); + return false; + } + assert( drmDev->available_nodes & ( 1 << DRM_NODE_RENDER ) ); + const char *drmRenderName = drmDev->nodes[ DRM_NODE_RENDER ]; + + m_drmRendererFd = open( drmRenderName, O_RDWR | O_CLOEXEC ); + drmFreeDevice( &drmDev ); + if ( m_drmRendererFd < 0 ) + { + vk_log.errorf_errno( "failed to open DRM render node" ); + return false; + } + + if ( drmProps.hasPrimary ) + { + m_bHasDrmPrimaryDevId = true; + m_drmPrimaryDevId = + makedev( drmProps.primaryMajor, drmProps.primaryMinor ); + } #else - vk_log.warnf( "built without DRM support" ); + vk_log.warnf( "built without DRM support" ); #endif - } - - if ( m_bSupportsModifiers && !supportsForeignQueue ) { - vk_log.infof( "The vulkan driver does not support foreign queues," - " disabling modifier support."); - m_bSupportsModifiers = false; - } - - { - VkPhysicalDeviceVulkan12Features vulkan12Features = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, - }; - VkPhysicalDeviceFeatures2 features2 = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, - .pNext = &vulkan12Features, - }; - vk.GetPhysicalDeviceFeatures2( physDev(), &features2 ); - - m_bSupportsFp16 = vulkan12Features.shaderFloat16 && features2.features.shaderInt16; - } - - float queuePriorities = 1.0f; - - VkDeviceQueueGlobalPriorityCreateInfoEXT queueCreateInfoEXT = { - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT, - .pNext = nullptr, - .globalPriority = VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT - }; + } - VkDeviceQueueCreateInfo queueCreateInfos[2] = - { - { - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - .pNext = gamescope::Process::HasCapSysNice() ? &queueCreateInfoEXT : nullptr, - .queueFamilyIndex = m_queueFamily, - .queueCount = 1, - .pQueuePriorities = &queuePriorities - }, - { - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - .pNext = gamescope::Process::HasCapSysNice() ? &queueCreateInfoEXT : nullptr, - .queueFamilyIndex = m_generalQueueFamily, - .queueCount = 1, - .pQueuePriorities = &queuePriorities - }, - }; + if ( m_bSupportsModifiers && !supportsForeignQueue ) + { + vk_log.infof( + "The vulkan driver does not support foreign queues," + " disabling modifier support." ); + m_bSupportsModifiers = false; + } - std::vector< const char * > enabledExtensions; + { + VkPhysicalDeviceVulkan12Features vulkan12Features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, + }; + VkPhysicalDeviceFeatures2 features2 = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, + .pNext = &vulkan12Features, + }; + vk.GetPhysicalDeviceFeatures2( physDev( ), &features2 ); + + m_bSupportsFp16 = + vulkan12Features.shaderFloat16 && features2.features.shaderInt16; + } - if ( GetBackend()->UsesVulkanSwapchain() ) - { - enabledExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME ); - enabledExtensions.push_back( VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME ); + float queuePriorities = 1.0f; + + VkDeviceQueueGlobalPriorityCreateInfoEXT queueCreateInfoEXT = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT, + .pNext = nullptr, + .globalPriority = VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT + }; + + VkDeviceQueueCreateInfo queueCreateInfos[ 2 ] = { + { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = gamescope::Process::HasCapSysNice( ) ? &queueCreateInfoEXT + : nullptr, + .queueFamilyIndex = m_queueFamily, + .queueCount = 1, + .pQueuePriorities = &queuePriorities }, + { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = gamescope::Process::HasCapSysNice( ) ? &queueCreateInfoEXT + : nullptr, + .queueFamilyIndex = m_generalQueueFamily, + .queueCount = 1, + .pQueuePriorities = &queuePriorities }, + }; + + std::vector enabledExtensions; + + if ( GetBackend( )->UsesVulkanSwapchain( ) ) + { + enabledExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME ); + enabledExtensions.push_back( + VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME ); - enabledExtensions.push_back( VK_KHR_PRESENT_ID_EXTENSION_NAME ); - enabledExtensions.push_back( VK_KHR_PRESENT_WAIT_EXTENSION_NAME ); - } + enabledExtensions.push_back( VK_KHR_PRESENT_ID_EXTENSION_NAME ); + enabledExtensions.push_back( VK_KHR_PRESENT_WAIT_EXTENSION_NAME ); + } - if ( m_bSupportsModifiers ) - { - enabledExtensions.push_back( VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME ); - enabledExtensions.push_back( VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME ); - } + if ( m_bSupportsModifiers ) + { + enabledExtensions.push_back( + VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME ); + enabledExtensions.push_back( + VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME ); + } - enabledExtensions.push_back( VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME ); - enabledExtensions.push_back( VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME ); + enabledExtensions.push_back( VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME ); + enabledExtensions.push_back( + VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME ); - enabledExtensions.push_back( VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME ); + enabledExtensions.push_back( VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME ); - enabledExtensions.push_back( VK_EXT_ROBUSTNESS_2_EXTENSION_NAME ); + enabledExtensions.push_back( VK_EXT_ROBUSTNESS_2_EXTENSION_NAME ); #if 0 enabledExtensions.push_back( VK_KHR_MAINTENANCE_5_EXTENSION_NAME ); #endif - if ( supportsHDRMetadata ) - enabledExtensions.push_back( VK_EXT_HDR_METADATA_EXTENSION_NAME ); + if ( supportsHDRMetadata ) + enabledExtensions.push_back( VK_EXT_HDR_METADATA_EXTENSION_NAME ); - for ( auto& extension : GetBackend()->GetDeviceExtensions( physDev() ) ) - enabledExtensions.push_back( extension ); + for ( auto &extension : GetBackend( )->GetDeviceExtensions( physDev( ) ) ) + enabledExtensions.push_back( extension ); #if 0 VkPhysicalDeviceMaintenance5FeaturesKHR maintenance5 = { @@ -594,27 +778,27 @@ bool CVulkanDevice::createDevice() }; #endif - VkPhysicalDeviceVulkan13Features features13 = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, + VkPhysicalDeviceVulkan13Features features13 = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, #if 0 .pNext = &maintenance5, #endif - .dynamicRendering = VK_TRUE, - }; - - VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR, - .pNext = &features13, - .presentWait = VK_TRUE, - }; - - VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR, - .pNext = &presentWaitFeatures, - .presentId = VK_TRUE, - }; - - VkPhysicalDeviceFeatures2 features2 = { + .dynamicRendering = VK_TRUE, + }; + + VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR, + .pNext = &features13, + .presentWait = VK_TRUE, + }; + + VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR, + .pNext = &presentWaitFeatures, + .presentId = VK_TRUE, + }; + + VkPhysicalDeviceFeatures2 features2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &presentIdFeatures, .features = { @@ -622,1192 +806,1332 @@ bool CVulkanDevice::createDevice() }, }; - VkDeviceCreateInfo deviceCreateInfo = { - .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pNext = &features2, - .queueCreateInfoCount = m_queueFamily == m_generalQueueFamily ? 1u : 2u, - .pQueueCreateInfos = queueCreateInfos, - .enabledExtensionCount = (uint32_t)enabledExtensions.size(), - .ppEnabledExtensionNames = enabledExtensions.data(), - }; + VkDeviceCreateInfo deviceCreateInfo = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &features2, + .queueCreateInfoCount = m_queueFamily == m_generalQueueFamily ? 1u : 2u, + .pQueueCreateInfos = queueCreateInfos, + .enabledExtensionCount = ( uint32_t )enabledExtensions.size( ), + .ppEnabledExtensionNames = enabledExtensions.data( ), + }; + + VkPhysicalDeviceVulkan12Features vulkan12Features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, + .pNext = std::exchange( features2.pNext, &vulkan12Features ), + .shaderFloat16 = m_bSupportsFp16, + .scalarBlockLayout = VK_TRUE, + .timelineSemaphore = VK_TRUE, + }; + + VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcrFeatures = { + .sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, + .pNext = std::exchange( features2.pNext, &ycbcrFeatures ), + .samplerYcbcrConversion = VK_TRUE, + }; + + VkPhysicalDeviceRobustness2FeaturesEXT robustness2Features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, + .pNext = std::exchange( features2.pNext, &robustness2Features ), + .nullDescriptor = VK_TRUE, + }; + + VkResult res = + vk.CreateDevice( physDev( ), &deviceCreateInfo, nullptr, &m_device ); + if ( res == VK_ERROR_NOT_PERMITTED_KHR && + gamescope::Process::HasCapSysNice( ) ) + { + fprintf( + stderr, + "vkCreateDevice failed with a high-priority queue (general + " + "compute). Falling back to regular priority (general).\n" ); + queueCreateInfos[ 1 ].pNext = nullptr; + res = vk.CreateDevice( + physDev( ), &deviceCreateInfo, nullptr, &m_device ); + + if ( res == VK_ERROR_NOT_PERMITTED_KHR && + gamescope::Process::HasCapSysNice( ) ) + { + fprintf( + stderr, + "vkCreateDevice failed with a high-priority queue (compute). " + "Falling back to regular priority (all).\n" ); + queueCreateInfos[ 0 ].pNext = nullptr; + res = vk.CreateDevice( + physDev( ), &deviceCreateInfo, nullptr, &m_device ); + } + } - VkPhysicalDeviceVulkan12Features vulkan12Features = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, - .pNext = std::exchange(features2.pNext, &vulkan12Features), - .shaderFloat16 = m_bSupportsFp16, - .scalarBlockLayout = VK_TRUE, - .timelineSemaphore = VK_TRUE, - }; + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateDevice failed" ); + return false; + } - VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcrFeatures = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, - .pNext = std::exchange(features2.pNext, &ycbcrFeatures), - .samplerYcbcrConversion = VK_TRUE, - }; +#define VK_FUNC( x ) \ + vk.x = ( PFN_vk##x )vk.GetDeviceProcAddr( device( ), "vk" #x ); + VULKAN_DEVICE_FUNCTIONS +#undef VK_FUNC - VkPhysicalDeviceRobustness2FeaturesEXT robustness2Features = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, - .pNext = std::exchange(features2.pNext, &robustness2Features), - .nullDescriptor = VK_TRUE, - }; + vk.GetDeviceQueue( device( ), m_queueFamily, 0, &m_queue ); + if ( m_queueFamily == m_generalQueueFamily ) m_generalQueue = m_queue; + else + vk.GetDeviceQueue( + device( ), m_generalQueueFamily, 0, &m_generalQueue ); - VkResult res = vk.CreateDevice(physDev(), &deviceCreateInfo, nullptr, &m_device); - if ( res == VK_ERROR_NOT_PERMITTED_KHR && gamescope::Process::HasCapSysNice() ) - { - fprintf(stderr, "vkCreateDevice failed with a high-priority queue (general + compute). Falling back to regular priority (general).\n"); - queueCreateInfos[1].pNext = nullptr; - res = vk.CreateDevice(physDev(), &deviceCreateInfo, nullptr, &m_device); - - - if ( res == VK_ERROR_NOT_PERMITTED_KHR && gamescope::Process::HasCapSysNice() ) - { - fprintf(stderr, "vkCreateDevice failed with a high-priority queue (compute). Falling back to regular priority (all).\n"); - queueCreateInfos[0].pNext = nullptr; - res = vk.CreateDevice(physDev(), &deviceCreateInfo, nullptr, &m_device); - } - } - - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateDevice failed" ); - return false; - } - - #define VK_FUNC(x) vk.x = (PFN_vk##x) vk.GetDeviceProcAddr(device(), "vk"#x); - VULKAN_DEVICE_FUNCTIONS - #undef VK_FUNC - - vk.GetDeviceQueue(device(), m_queueFamily, 0, &m_queue); - if ( m_queueFamily == m_generalQueueFamily ) - m_generalQueue = m_queue; - else - vk.GetDeviceQueue(device(), m_generalQueueFamily, 0, &m_generalQueue); - - return true; + return true; } -static VkSamplerYcbcrModelConversion colorspaceToYCBCRModel( EStreamColorspace colorspace ) +static VkSamplerYcbcrModelConversion +colorspaceToYCBCRModel( EStreamColorspace colorspace ) { - switch (colorspace) - { - default: - case k_EStreamColorspace_Unknown: - return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709; - - case k_EStreamColorspace_BT601: - case k_EStreamColorspace_BT601_Full: - return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601; - - case k_EStreamColorspace_BT709: - case k_EStreamColorspace_BT709_Full: - return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709; - } -} + switch ( colorspace ) + { + default: + case k_EStreamColorspace_Unknown: + return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709; -static VkSamplerYcbcrRange colorspaceToYCBCRRange( EStreamColorspace colorspace ) -{ - switch (colorspace) - { - default: - case k_EStreamColorspace_Unknown: - return VK_SAMPLER_YCBCR_RANGE_ITU_FULL; - - case k_EStreamColorspace_BT709: - case k_EStreamColorspace_BT601: - return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW; - - case k_EStreamColorspace_BT601_Full: - case k_EStreamColorspace_BT709_Full: - return VK_SAMPLER_YCBCR_RANGE_ITU_FULL; - } + case k_EStreamColorspace_BT601: + case k_EStreamColorspace_BT601_Full: + return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601; + + case k_EStreamColorspace_BT709: + case k_EStreamColorspace_BT709_Full: + return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709; + } } -bool CVulkanDevice::createLayouts() +static VkSamplerYcbcrRange +colorspaceToYCBCRRange( EStreamColorspace colorspace ) { - VkFormatProperties nv12Properties; - vk.GetPhysicalDeviceFormatProperties(physDev(), VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, &nv12Properties); - bool cosited = nv12Properties.optimalTilingFeatures & VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT; - - VkSamplerYcbcrConversionCreateInfo ycbcrSamplerConversionCreateInfo = - { - .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, - .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, - .ycbcrModel = colorspaceToYCBCRModel( g_ForcedNV12ColorSpace ), - .ycbcrRange = colorspaceToYCBCRRange( g_ForcedNV12ColorSpace ), - .xChromaOffset = cosited ? VK_CHROMA_LOCATION_COSITED_EVEN : VK_CHROMA_LOCATION_MIDPOINT, - .yChromaOffset = cosited ? VK_CHROMA_LOCATION_COSITED_EVEN : VK_CHROMA_LOCATION_MIDPOINT, - .chromaFilter = VK_FILTER_LINEAR, - .forceExplicitReconstruction = VK_FALSE, - }; + switch ( colorspace ) + { + default: + case k_EStreamColorspace_Unknown: + return VK_SAMPLER_YCBCR_RANGE_ITU_FULL; - vk.CreateSamplerYcbcrConversion( device(), &ycbcrSamplerConversionCreateInfo, nullptr, &m_ycbcrConversion ); + case k_EStreamColorspace_BT709: + case k_EStreamColorspace_BT601: + return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW; - VkSamplerYcbcrConversionInfo ycbcrSamplerConversionInfo = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, - .conversion = m_ycbcrConversion, - }; + case k_EStreamColorspace_BT601_Full: + case k_EStreamColorspace_BT709_Full: + return VK_SAMPLER_YCBCR_RANGE_ITU_FULL; + } +} - VkSamplerCreateInfo ycbcrSamplerInfo = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = &ycbcrSamplerConversionInfo, - .magFilter = VK_FILTER_LINEAR, - .minFilter = VK_FILTER_LINEAR, - .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, - }; - - vk.CreateSampler( device(), &ycbcrSamplerInfo, nullptr, &m_ycbcrSampler ); - - // Create an array of our ycbcrSampler to fill up - std::array ycbcrSamplers; - for (auto& sampler : ycbcrSamplers) - sampler = m_ycbcrSampler; - - std::array layoutBindings = { - VkDescriptorSetLayoutBinding { - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - VkDescriptorSetLayoutBinding { - .binding = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - VkDescriptorSetLayoutBinding { - .binding = 2, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - VkDescriptorSetLayoutBinding { - .binding = 3, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = VKR_SAMPLER_SLOTS, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - VkDescriptorSetLayoutBinding { - .binding = 4, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = VKR_SAMPLER_SLOTS, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = ycbcrSamplers.data(), - }, - VkDescriptorSetLayoutBinding { - .binding = 5, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = VKR_LUT3D_COUNT, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - VkDescriptorSetLayoutBinding { - .binding = 6, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = VKR_LUT3D_COUNT, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - }, - }; +bool CVulkanDevice::createLayouts( ) +{ + VkFormatProperties nv12Properties; + vk.GetPhysicalDeviceFormatProperties( + physDev( ), VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, &nv12Properties ); + bool cosited = nv12Properties.optimalTilingFeatures & + VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT; + + VkSamplerYcbcrConversionCreateInfo ycbcrSamplerConversionCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, + .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .ycbcrModel = colorspaceToYCBCRModel( g_ForcedNV12ColorSpace ), + .ycbcrRange = colorspaceToYCBCRRange( g_ForcedNV12ColorSpace ), + .xChromaOffset = cosited ? VK_CHROMA_LOCATION_COSITED_EVEN + : VK_CHROMA_LOCATION_MIDPOINT, + .yChromaOffset = cosited ? VK_CHROMA_LOCATION_COSITED_EVEN + : VK_CHROMA_LOCATION_MIDPOINT, + .chromaFilter = VK_FILTER_LINEAR, + .forceExplicitReconstruction = VK_FALSE, + }; + + vk.CreateSamplerYcbcrConversion( + device( ), + &ycbcrSamplerConversionCreateInfo, + nullptr, + &m_ycbcrConversion ); + + VkSamplerYcbcrConversionInfo ycbcrSamplerConversionInfo = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, + .conversion = m_ycbcrConversion, + }; + + VkSamplerCreateInfo ycbcrSamplerInfo = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = &ycbcrSamplerConversionInfo, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, + }; + + vk.CreateSampler( device( ), &ycbcrSamplerInfo, nullptr, &m_ycbcrSampler ); + + // Create an array of our ycbcrSampler to fill up + std::array ycbcrSamplers; + for ( auto &sampler : ycbcrSamplers ) + sampler = m_ycbcrSampler; + + std::array layoutBindings = { + VkDescriptorSetLayoutBinding{ + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }, + VkDescriptorSetLayoutBinding{ + .binding = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }, + VkDescriptorSetLayoutBinding{ + .binding = 2, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }, + VkDescriptorSetLayoutBinding{ + .binding = 3, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = VKR_SAMPLER_SLOTS, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }, + VkDescriptorSetLayoutBinding{ + .binding = 4, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = VKR_SAMPLER_SLOTS, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = ycbcrSamplers.data( ), + }, + VkDescriptorSetLayoutBinding{ + .binding = 5, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = VKR_LUT3D_COUNT, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }, + VkDescriptorSetLayoutBinding{ + .binding = 6, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = VKR_LUT3D_COUNT, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + }, + }; + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = ( uint32_t )layoutBindings.size( ), + .pBindings = layoutBindings.data( ) + }; + + VkResult res = vk.CreateDescriptorSetLayout( + device( ), &descriptorSetLayoutCreateInfo, 0, &m_descriptorSetLayout ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateDescriptorSetLayout failed" ); + return false; + } - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = - { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .bindingCount = (uint32_t)layoutBindings.size(), - .pBindings = layoutBindings.data() - }; + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 1, + .pSetLayouts = &m_descriptorSetLayout, + }; - VkResult res = vk.CreateDescriptorSetLayout(device(), &descriptorSetLayoutCreateInfo, 0, &m_descriptorSetLayout); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateDescriptorSetLayout failed" ); - return false; - } - - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = 1, - .pSetLayouts = &m_descriptorSetLayout, - }; - - res = vk.CreatePipelineLayout(device(), &pipelineLayoutCreateInfo, nullptr, &m_pipelineLayout); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreatePipelineLayout failed" ); - return false; - } - - return true; + res = vk.CreatePipelineLayout( + device( ), &pipelineLayoutCreateInfo, nullptr, &m_pipelineLayout ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreatePipelineLayout failed" ); + return false; + } + + return true; } -bool CVulkanDevice::createPools() +bool CVulkanDevice::createPools( ) { - VkCommandPoolCreateInfo commandPoolCreateInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, - .queueFamilyIndex = m_queueFamily, - }; + VkCommandPoolCreateInfo commandPoolCreateInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = m_queueFamily, + }; + + VkResult res = vk.CreateCommandPool( + device( ), &commandPoolCreateInfo, nullptr, &m_commandPool ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateCommandPool failed" ); + return false; + } - VkResult res = vk.CreateCommandPool(device(), &commandPoolCreateInfo, nullptr, &m_commandPool); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateCommandPool failed" ); - return false; - } - - VkCommandPoolCreateInfo generalCommandPoolCreateInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, - .queueFamilyIndex = m_generalQueueFamily, - }; + VkCommandPoolCreateInfo generalCommandPoolCreateInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = m_generalQueueFamily, + }; + + res = vk.CreateCommandPool( + device( ), + &generalCommandPoolCreateInfo, + nullptr, + &m_generalCommandPool ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateCommandPool failed" ); + return false; + } - res = vk.CreateCommandPool(device(), &generalCommandPoolCreateInfo, nullptr, &m_generalCommandPool); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateCommandPool failed" ); - return false; - } - - VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, - .type = VK_IMAGE_TYPE_2D, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_SAMPLED_BIT, - }; + VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + .format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + .type = VK_IMAGE_TYPE_2D, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT, + }; + + VkSamplerYcbcrConversionImageFormatProperties ycbcrProps = { + .sType = + VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES, + }; + + VkImageFormatProperties2 imageFormatProps = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + .pNext = &ycbcrProps, + }; + + res = vk.GetPhysicalDeviceImageFormatProperties2( + physDev( ), &imageFormatInfo, &imageFormatProps ); + + VkDescriptorPoolSize poolSizes[ 3 ]{ + { + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + uint32_t( m_descriptorSets.size( ) ), + }, + { + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + uint32_t( m_descriptorSets.size( ) ) * 2, + }, + { + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + uint32_t( m_descriptorSets.size( ) ) * + ( ( ( ycbcrProps.combinedImageSamplerDescriptorCount + 1 ) * + VKR_SAMPLER_SLOTS ) + + ( 2 * VKR_LUT3D_COUNT ) ), + }, + }; + + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = uint32_t( m_descriptorSets.size( ) ), + .poolSizeCount = sizeof( poolSizes ) / sizeof( poolSizes[ 0 ] ), + .pPoolSizes = poolSizes, + }; + + res = vk.CreateDescriptorPool( + device( ), &descriptorPoolCreateInfo, nullptr, &m_descriptorPool ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateDescriptorPool failed" ); + return false; + } - VkSamplerYcbcrConversionImageFormatProperties ycbcrProps = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES, - }; + return true; +} - VkImageFormatProperties2 imageFormatProps = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - .pNext = &ycbcrProps, - }; +bool CVulkanDevice::createShaders( ) +{ + struct ShaderInfo_t + { + const uint32_t *spirv; + uint32_t size; + }; + + std::array shaderInfos; +#define SHADER( type, array ) \ + shaderInfos[ SHADER_TYPE_##type ] = { array, sizeof( array ) } + SHADER( BLIT, cs_composite_blit ); + SHADER( BLUR, cs_composite_blur ); + SHADER( BLUR_COND, cs_composite_blur_cond ); + SHADER( BLUR_FIRST_PASS, cs_gaussian_blur_horizontal ); + SHADER( RCAS, cs_composite_rcas ); + if ( m_bSupportsFp16 ) + { + SHADER( EASU, cs_easu_fp16 ); + SHADER( NIS, cs_nis_fp16 ); + } + else + { + SHADER( EASU, cs_easu ); + SHADER( NIS, cs_nis ); + } + SHADER( RGB_TO_NV12, cs_rgb_to_nv12 ); +#undef SHADER - res = vk.GetPhysicalDeviceImageFormatProperties2( physDev(), &imageFormatInfo, &imageFormatProps ); + for ( uint32_t i = 0; i < shaderInfos.size( ); i++ ) + { + VkShaderModuleCreateInfo shaderCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = shaderInfos[ i ].size, + .pCode = shaderInfos[ i ].spirv, + }; + + VkResult res = vk.CreateShaderModule( + device( ), &shaderCreateInfo, nullptr, &m_shaderModules[ i ] ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateShaderModule failed" ); + return false; + } + } - VkDescriptorPoolSize poolSizes[3] { - { - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - uint32_t(m_descriptorSets.size()), - }, - { - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - uint32_t(m_descriptorSets.size()) * 2, - }, - { - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - uint32_t(m_descriptorSets.size()) * (((ycbcrProps.combinedImageSamplerDescriptorCount + 1) * VKR_SAMPLER_SLOTS) + (2 * VKR_LUT3D_COUNT)), - }, - }; - - VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .maxSets = uint32_t(m_descriptorSets.size()), - .poolSizeCount = sizeof(poolSizes) / sizeof(poolSizes[0]), - .pPoolSizes = poolSizes, - }; - - res = vk.CreateDescriptorPool(device(), &descriptorPoolCreateInfo, nullptr, &m_descriptorPool); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateDescriptorPool failed" ); - return false; - } - - return true; + return true; } -bool CVulkanDevice::createShaders() +bool CVulkanDevice::createScratchResources( ) { - struct ShaderInfo_t - { - const uint32_t* spirv; - uint32_t size; - }; + std::vector descriptorSetLayouts( + m_descriptorSets.size( ), m_descriptorSetLayout ); + + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = m_descriptorPool, + .descriptorSetCount = ( uint32_t )descriptorSetLayouts.size( ), + .pSetLayouts = descriptorSetLayouts.data( ), + }; + + VkResult res = vk.AllocateDescriptorSets( + device( ), &descriptorSetAllocateInfo, m_descriptorSets.data( ) ); + if ( res != VK_SUCCESS ) + { + vk_log.errorf( "vkAllocateDescriptorSets failed" ); + return false; + } - std::array shaderInfos; -#define SHADER(type, array) shaderInfos[SHADER_TYPE_##type] = {array , sizeof(array)} - SHADER(BLIT, cs_composite_blit); - SHADER(BLUR, cs_composite_blur); - SHADER(BLUR_COND, cs_composite_blur_cond); - SHADER(BLUR_FIRST_PASS, cs_gaussian_blur_horizontal); - SHADER(RCAS, cs_composite_rcas); - if (m_bSupportsFp16) - { - SHADER(EASU, cs_easu_fp16); - SHADER(NIS, cs_nis_fp16); - } - else - { - SHADER(EASU, cs_easu); - SHADER(NIS, cs_nis); - } - SHADER(RGB_TO_NV12, cs_rgb_to_nv12); -#undef SHADER + // Make and map upload buffer - for (uint32_t i = 0; i < shaderInfos.size(); i++) - { - VkShaderModuleCreateInfo shaderCreateInfo = { - .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .codeSize = shaderInfos[i].size, - .pCode = shaderInfos[i].spirv, - }; + VkBufferCreateInfo bufferCreateInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = upload_buffer_size, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + }; - VkResult res = vk.CreateShaderModule(device(), &shaderCreateInfo, nullptr, &m_shaderModules[i]); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateShaderModule failed" ); - return false; - } - } + res = vk.CreateBuffer( + device( ), &bufferCreateInfo, nullptr, &m_uploadBuffer ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateBuffer failed" ); + return false; + } - return true; -} + VkMemoryRequirements memRequirements; + vk.GetBufferMemoryRequirements( + device( ), m_uploadBuffer, &memRequirements ); -bool CVulkanDevice::createScratchResources() -{ - std::vector descriptorSetLayouts(m_descriptorSets.size(), m_descriptorSetLayout); - - VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .descriptorPool = m_descriptorPool, - .descriptorSetCount = (uint32_t)descriptorSetLayouts.size(), - .pSetLayouts = descriptorSetLayouts.data(), - }; - - VkResult res = vk.AllocateDescriptorSets(device(), &descriptorSetAllocateInfo, m_descriptorSets.data()); - if ( res != VK_SUCCESS ) - { - vk_log.errorf( "vkAllocateDescriptorSets failed" ); - return false; - } - - // Make and map upload buffer - - VkBufferCreateInfo bufferCreateInfo = { - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .size = upload_buffer_size, - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - }; + uint32_t memTypeIndex = findMemoryType( + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + memRequirements.memoryTypeBits ); + if ( memTypeIndex == ~0u ) + { + vk_log.errorf( "findMemoryType failed" ); + return false; + } - res = vk.CreateBuffer( device(), &bufferCreateInfo, nullptr, &m_uploadBuffer ); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateBuffer failed" ); - return false; - } - - VkMemoryRequirements memRequirements; - vk.GetBufferMemoryRequirements(device(), m_uploadBuffer, &memRequirements); - - uint32_t memTypeIndex = findMemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT|VK_MEMORY_PROPERTY_HOST_COHERENT_BIT|VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, memRequirements.memoryTypeBits ); - if ( memTypeIndex == ~0u ) - { - vk_log.errorf( "findMemoryType failed" ); - return false; - } - - VkMemoryAllocateInfo allocInfo = { - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .allocationSize = memRequirements.size, - .memoryTypeIndex = memTypeIndex, - }; - - vk.AllocateMemory( device(), &allocInfo, nullptr, &m_uploadBufferMemory); - - vk.BindBufferMemory( device(), m_uploadBuffer, m_uploadBufferMemory, 0 ); - - res = vk.MapMemory( device(), m_uploadBufferMemory, 0, VK_WHOLE_SIZE, 0, (void**)&m_uploadBufferData ); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkMapMemory failed" ); - return false; - } - - VkSemaphoreTypeCreateInfo timelineCreateInfo = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, - .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, - }; + VkMemoryAllocateInfo allocInfo = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memRequirements.size, + .memoryTypeIndex = memTypeIndex, + }; - VkSemaphoreCreateInfo semCreateInfo = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - .pNext = &timelineCreateInfo, - }; + vk.AllocateMemory( device( ), &allocInfo, nullptr, &m_uploadBufferMemory ); - res = vk.CreateSemaphore( device(), &semCreateInfo, NULL, &m_scratchTimelineSemaphore ); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateSemaphore failed" ); - return false; - } + vk.BindBufferMemory( device( ), m_uploadBuffer, m_uploadBufferMemory, 0 ); - return true; -} + res = vk.MapMemory( + device( ), + m_uploadBufferMemory, + 0, + VK_WHOLE_SIZE, + 0, + ( void ** )&m_uploadBufferData ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkMapMemory failed" ); + return false; + } -VkSampler CVulkanDevice::sampler( SamplerState key ) -{ - if ( m_samplerCache.count(key) != 0 ) - return m_samplerCache[key]; - - VkSampler ret = VK_NULL_HANDLE; - - VkSamplerCreateInfo samplerCreateInfo = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .magFilter = key.bNearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR, - .minFilter = key.bNearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR, - .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - .borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, - .unnormalizedCoordinates = key.bUnnormalized, - }; + VkSemaphoreTypeCreateInfo timelineCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, + }; - vk.CreateSampler( device(), &samplerCreateInfo, nullptr, &ret ); + VkSemaphoreCreateInfo semCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &timelineCreateInfo, + }; - m_samplerCache[key] = ret; + res = vk.CreateSemaphore( + device( ), &semCreateInfo, NULL, &m_scratchTimelineSemaphore ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateSemaphore failed" ); + return false; + } - return ret; + return true; } -VkPipeline CVulkanDevice::compilePipeline(uint32_t layerCount, uint32_t ycbcrMask, ShaderType type, uint32_t blur_layer_count, uint32_t composite_debug, uint32_t colorspace_mask, uint32_t output_eotf, bool itm_enable) +VkSampler CVulkanDevice::sampler( SamplerState key ) { - const std::array specializationEntries = {{ - { - .constantID = 0, - .offset = sizeof(uint32_t) * 0, - .size = sizeof(uint32_t) - }, - { - .constantID = 1, - .offset = sizeof(uint32_t) * 1, - .size = sizeof(uint32_t) - }, - { - .constantID = 2, - .offset = sizeof(uint32_t) * 2, - .size = sizeof(uint32_t) - }, - { - .constantID = 3, - .offset = sizeof(uint32_t) * 3, - .size = sizeof(uint32_t) - }, - { - .constantID = 4, - .offset = sizeof(uint32_t) * 4, - .size = sizeof(uint32_t) - }, + if ( m_samplerCache.count( key ) != 0 ) return m_samplerCache[ key ]; - { - .constantID = 5, - .offset = sizeof(uint32_t) * 5, - .size = sizeof(uint32_t) - }, + VkSampler ret = VK_NULL_HANDLE; - { - .constantID = 6, - .offset = sizeof(uint32_t) * 6, - .size = sizeof(uint32_t) - }, - }}; - - struct { - uint32_t layerCount; - uint32_t ycbcrMask; - uint32_t debug; - uint32_t blur_layer_count; - uint32_t colorspace_mask; - uint32_t output_eotf; - uint32_t itm_enable; - } specializationData = { - .layerCount = layerCount, - .ycbcrMask = ycbcrMask, - .debug = composite_debug, - .blur_layer_count = blur_layer_count, - .colorspace_mask = colorspace_mask, - .output_eotf = output_eotf, - .itm_enable = itm_enable, - }; + VkSamplerCreateInfo samplerCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = key.bNearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR, + .minFilter = key.bNearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, + .unnormalizedCoordinates = key.bUnnormalized, + }; - VkSpecializationInfo specializationInfo = { - .mapEntryCount = uint32_t(specializationEntries.size()), - .pMapEntries = specializationEntries.data(), - .dataSize = sizeof(specializationData), - .pData = &specializationData, - }; + vk.CreateSampler( device( ), &samplerCreateInfo, nullptr, &ret ); - VkComputePipelineCreateInfo computePipelineCreateInfo = { - .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, - .stage = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .stage = VK_SHADER_STAGE_COMPUTE_BIT, - .module = m_shaderModules[type], - .pName = "main", - .pSpecializationInfo = &specializationInfo - }, - .layout = m_pipelineLayout, - }; + m_samplerCache[ key ] = ret; - VkPipeline result; + return ret; +} - VkResult res = vk.CreateComputePipelines(device(), VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &result); - if (res != VK_SUCCESS) { - vk_errorf( res, "vkCreateComputePipelines failed" ); - return VK_NULL_HANDLE; - } +VkPipeline CVulkanDevice::compilePipeline( + uint32_t layerCount, + uint32_t ycbcrMask, + ShaderType type, + uint32_t blur_layer_count, + uint32_t composite_debug, + uint32_t colorspace_mask, + uint32_t output_eotf, + bool itm_enable ) +{ + const std::array specializationEntries = { { + { .constantID = 0, + .offset = sizeof( uint32_t ) * 0, + .size = sizeof( uint32_t ) }, + { .constantID = 1, + .offset = sizeof( uint32_t ) * 1, + .size = sizeof( uint32_t ) }, + { .constantID = 2, + .offset = sizeof( uint32_t ) * 2, + .size = sizeof( uint32_t ) }, + { .constantID = 3, + .offset = sizeof( uint32_t ) * 3, + .size = sizeof( uint32_t ) }, + { .constantID = 4, + .offset = sizeof( uint32_t ) * 4, + .size = sizeof( uint32_t ) }, + + { .constantID = 5, + .offset = sizeof( uint32_t ) * 5, + .size = sizeof( uint32_t ) }, + + { .constantID = 6, + .offset = sizeof( uint32_t ) * 6, + .size = sizeof( uint32_t ) }, + } }; + + struct + { + uint32_t layerCount; + uint32_t ycbcrMask; + uint32_t debug; + uint32_t blur_layer_count; + uint32_t colorspace_mask; + uint32_t output_eotf; + uint32_t itm_enable; + } specializationData = { + .layerCount = layerCount, + .ycbcrMask = ycbcrMask, + .debug = composite_debug, + .blur_layer_count = blur_layer_count, + .colorspace_mask = colorspace_mask, + .output_eotf = output_eotf, + .itm_enable = itm_enable, + }; + + VkSpecializationInfo specializationInfo = { + .mapEntryCount = uint32_t( specializationEntries.size( ) ), + .pMapEntries = specializationEntries.data( ), + .dataSize = sizeof( specializationData ), + .pData = &specializationData, + }; + + VkComputePipelineCreateInfo computePipelineCreateInfo = { + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .stage = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = m_shaderModules[ type ], + .pName = "main", + .pSpecializationInfo = &specializationInfo }, + .layout = m_pipelineLayout, + }; + + VkPipeline result; + + VkResult res = vk.CreateComputePipelines( + device( ), + VK_NULL_HANDLE, + 1, + &computePipelineCreateInfo, + nullptr, + &result ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateComputePipelines failed" ); + return VK_NULL_HANDLE; + } - return result; + return result; } -void CVulkanDevice::compileAllPipelines() +void CVulkanDevice::compileAllPipelines( ) { - pthread_setname_np( pthread_self(), "gamescope-shdr" ); - - std::array pipelineInfos; -#define SHADER(type, layer_count, max_ycbcr, blur_layers) pipelineInfos[SHADER_TYPE_##type] = {SHADER_TYPE_##type, layer_count, max_ycbcr, blur_layers} - SHADER(BLIT, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, 1); - SHADER(BLUR, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, k_nMaxBlurLayers); - SHADER(BLUR_COND, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, k_nMaxBlurLayers); - SHADER(BLUR_FIRST_PASS, 1, 2, 1); - SHADER(RCAS, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, 1); - SHADER(EASU, 1, 1, 1); - SHADER(NIS, 1, 1, 1); - SHADER(RGB_TO_NV12, 1, 1, 1); + pthread_setname_np( pthread_self( ), "gamescope-shdr" ); + + std::array pipelineInfos; +#define SHADER( type, layer_count, max_ycbcr, blur_layers ) \ + pipelineInfos[ SHADER_TYPE_##type ] = { \ + SHADER_TYPE_##type, layer_count, max_ycbcr, blur_layers \ + } + SHADER( BLIT, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, 1 ); + SHADER( + BLUR, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, k_nMaxBlurLayers ); + SHADER( + BLUR_COND, + k_nMaxLayers, + k_nMaxYcbcrMask_ToPreCompile, + k_nMaxBlurLayers ); + SHADER( BLUR_FIRST_PASS, 1, 2, 1 ); + SHADER( RCAS, k_nMaxLayers, k_nMaxYcbcrMask_ToPreCompile, 1 ); + SHADER( EASU, 1, 1, 1 ); + SHADER( NIS, 1, 1, 1 ); + SHADER( RGB_TO_NV12, 1, 1, 1 ); #undef SHADER - for (auto& info : pipelineInfos) { - for (uint32_t layerCount = 1; layerCount <= info.layerCount; layerCount++) { - for (uint32_t ycbcrMask = 0; ycbcrMask < info.ycbcrMask; ycbcrMask++) { - for (uint32_t blur_layers = 1; blur_layers <= info.blurLayerCount; blur_layers++) { - if (ycbcrMask >= (1u << (layerCount + 1))) - continue; - if (blur_layers > layerCount) - continue; - - VkPipeline newPipeline = compilePipeline(layerCount, ycbcrMask, info.shaderType, blur_layers, info.compositeDebug, info.colorspaceMask, info.outputEOTF, info.itmEnable); - { - std::lock_guard lock(m_pipelineMutex); - PipelineInfo_t key = {info.shaderType, layerCount, ycbcrMask, blur_layers, info.compositeDebug}; - auto result = m_pipelineMap.emplace(std::make_pair(key, newPipeline)); - if (!result.second) - vk.DestroyPipeline(device(), newPipeline, nullptr); - } - } - } - } - } + for ( auto &info : pipelineInfos ) + { + for ( uint32_t layerCount = 1; layerCount <= info.layerCount; + layerCount++ ) + { + for ( uint32_t ycbcrMask = 0; ycbcrMask < info.ycbcrMask; + ycbcrMask++ ) + { + for ( uint32_t blur_layers = 1; + blur_layers <= info.blurLayerCount; + blur_layers++ ) + { + if ( ycbcrMask >= ( 1u << ( layerCount + 1 ) ) ) continue; + if ( blur_layers > layerCount ) continue; + + VkPipeline newPipeline = compilePipeline( + layerCount, + ycbcrMask, + info.shaderType, + blur_layers, + info.compositeDebug, + info.colorspaceMask, + info.outputEOTF, + info.itmEnable ); + { + std::lock_guard lock( m_pipelineMutex ); + PipelineInfo_t key = { info.shaderType, + layerCount, + ycbcrMask, + blur_layers, + info.compositeDebug }; + auto result = m_pipelineMap.emplace( + std::make_pair( key, newPipeline ) ); + if ( !result.second ) + vk.DestroyPipeline( + device( ), newPipeline, nullptr ); + } + } + } + } + } } extern bool g_bSteamIsActiveWindow; -VkPipeline CVulkanDevice::pipeline(ShaderType type, uint32_t layerCount, uint32_t ycbcrMask, uint32_t blur_layers, uint32_t colorspace_mask, uint32_t output_eotf, bool itm_enable) +VkPipeline CVulkanDevice::pipeline( + ShaderType type, + uint32_t layerCount, + uint32_t ycbcrMask, + uint32_t blur_layers, + uint32_t colorspace_mask, + uint32_t output_eotf, + bool itm_enable ) { - uint32_t effective_debug = g_uCompositeDebug; - if ( g_bSteamIsActiveWindow ) - effective_debug &= ~(CompositeDebugFlag::Heatmap | CompositeDebugFlag::Heatmap_MSWCG | CompositeDebugFlag::Heatmap_Hard); - - std::lock_guard lock(m_pipelineMutex); - PipelineInfo_t key = {type, layerCount, ycbcrMask, blur_layers, effective_debug, colorspace_mask, output_eotf, itm_enable}; - auto search = m_pipelineMap.find(key); - if (search == m_pipelineMap.end()) - { - VkPipeline result = compilePipeline(layerCount, ycbcrMask, type, blur_layers, effective_debug, colorspace_mask, output_eotf, itm_enable); - m_pipelineMap[key] = result; - return result; - } - else - { - return search->second; - } + uint32_t effective_debug = g_uCompositeDebug; + if ( g_bSteamIsActiveWindow ) + effective_debug &= + ~( CompositeDebugFlag::Heatmap | CompositeDebugFlag::Heatmap_MSWCG | + CompositeDebugFlag::Heatmap_Hard ); + + std::lock_guard lock( m_pipelineMutex ); + PipelineInfo_t key = { type, layerCount, ycbcrMask, + blur_layers, effective_debug, colorspace_mask, + output_eotf, itm_enable }; + auto search = m_pipelineMap.find( key ); + if ( search == m_pipelineMap.end( ) ) + { + VkPipeline result = compilePipeline( + layerCount, + ycbcrMask, + type, + blur_layers, + effective_debug, + colorspace_mask, + output_eotf, + itm_enable ); + m_pipelineMap[ key ] = result; + return result; + } + else + { + return search->second; + } } - -int32_t CVulkanDevice::findMemoryType( VkMemoryPropertyFlags properties, uint32_t requiredTypeBits ) +int32_t CVulkanDevice::findMemoryType( + VkMemoryPropertyFlags properties, uint32_t requiredTypeBits ) { - for ( uint32_t i = 0; i < m_memoryProperties.memoryTypeCount; i++ ) - { - if ( ( ( 1 << i ) & requiredTypeBits ) == 0 ) - continue; - - if ( ( properties & m_memoryProperties.memoryTypes[ i ].propertyFlags ) != properties ) - continue; - - return i; - } - - return -1; + for ( uint32_t i = 0; i < m_memoryProperties.memoryTypeCount; i++ ) + { + if ( ( ( 1 << i ) & requiredTypeBits ) == 0 ) continue; + + if ( ( properties & + m_memoryProperties.memoryTypes[ i ].propertyFlags ) != + properties ) + continue; + + return i; + } + + return -1; } -std::unique_ptr CVulkanDevice::commandBuffer() +std::unique_ptr CVulkanDevice::commandBuffer( ) { - std::unique_ptr cmdBuffer; - if (m_unusedCmdBufs.empty()) - { - VkCommandBuffer rawCmdBuffer; - VkCommandBufferAllocateInfo commandBufferAllocateInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .commandPool = m_commandPool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = 1 - }; + std::unique_ptr cmdBuffer; + if ( m_unusedCmdBufs.empty( ) ) + { + VkCommandBuffer rawCmdBuffer; + VkCommandBufferAllocateInfo commandBufferAllocateInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = m_commandPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1 + }; + + VkResult res = vk.AllocateCommandBuffers( + device( ), &commandBufferAllocateInfo, &rawCmdBuffer ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkAllocateCommandBuffers failed" ); + return nullptr; + } + + cmdBuffer = std::make_unique( + this, rawCmdBuffer, queue( ), queueFamily( ) ); + } + else + { + cmdBuffer = std::move( m_unusedCmdBufs.back( ) ); + m_unusedCmdBufs.pop_back( ); + } - VkResult res = vk.AllocateCommandBuffers( device(), &commandBufferAllocateInfo, &rawCmdBuffer ); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkAllocateCommandBuffers failed" ); - return nullptr; - } - - cmdBuffer = std::make_unique(this, rawCmdBuffer, queue(), queueFamily()); - } - else - { - cmdBuffer = std::move(m_unusedCmdBufs.back()); - m_unusedCmdBufs.pop_back(); - } - - cmdBuffer->begin(); - return cmdBuffer; + cmdBuffer->begin( ); + return cmdBuffer; } -uint64_t CVulkanDevice::submitInternal( CVulkanCmdBuffer* cmdBuffer ) +uint64_t CVulkanDevice::submitInternal( CVulkanCmdBuffer *cmdBuffer ) { - cmdBuffer->end(); - - // The seq no of the last submission. - const uint64_t lastSubmissionSeqNo = m_submissionSeqNo++; - - // This is the seq no of the command buffer we are going to submit. - const uint64_t nextSeqNo = lastSubmissionSeqNo + 1; - - std::vector pSignalSemaphores; - std::vector ulSignalPoints; - - std::vector uWaitStageFlags; - std::vector pWaitSemaphores; - std::vector ulWaitPoints; - - pSignalSemaphores.push_back( m_scratchTimelineSemaphore ); - ulSignalPoints.push_back( nextSeqNo ); - - for ( auto &dep : cmdBuffer->GetExternalSignals() ) - { - pSignalSemaphores.push_back( dep.pTimelineSemaphore->pVkSemaphore ); - ulSignalPoints.push_back( dep.ulPoint ); - } - - for ( auto &dep : cmdBuffer->GetExternalDependencies() ) - { - pWaitSemaphores.push_back( dep.pTimelineSemaphore->pVkSemaphore ); - ulWaitPoints.push_back( dep.ulPoint ); - uWaitStageFlags.push_back( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ); - } - - VkTimelineSemaphoreSubmitInfo timelineInfo = { - .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, - // no need to ensure order of cmd buffer submission, we only have one queue - .waitSemaphoreValueCount = static_cast( ulWaitPoints.size() ), - .pWaitSemaphoreValues = ulWaitPoints.data(), - .signalSemaphoreValueCount = static_cast( ulSignalPoints.size() ), - .pSignalSemaphoreValues = ulSignalPoints.data(), - }; + cmdBuffer->end( ); - VkCommandBuffer rawCmdBuffer = cmdBuffer->rawBuffer(); - - VkSubmitInfo submitInfo = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = &timelineInfo, - .waitSemaphoreCount = static_cast( pWaitSemaphores.size() ), - .pWaitSemaphores = pWaitSemaphores.data(), - .pWaitDstStageMask = uWaitStageFlags.data(), - .commandBufferCount = 1, - .pCommandBuffers = &rawCmdBuffer, - .signalSemaphoreCount = static_cast( pSignalSemaphores.size() ), - .pSignalSemaphores = pSignalSemaphores.data(), - }; + // The seq no of the last submission. + const uint64_t lastSubmissionSeqNo = m_submissionSeqNo++; + + // This is the seq no of the command buffer we are going to submit. + const uint64_t nextSeqNo = lastSubmissionSeqNo + 1; + + std::vector pSignalSemaphores; + std::vector ulSignalPoints; - vk_check( vk.QueueSubmit( cmdBuffer->queue(), 1, &submitInfo, VK_NULL_HANDLE ) ); + std::vector uWaitStageFlags; + std::vector pWaitSemaphores; + std::vector ulWaitPoints; - return nextSeqNo; + pSignalSemaphores.push_back( m_scratchTimelineSemaphore ); + ulSignalPoints.push_back( nextSeqNo ); + + for ( auto &dep : cmdBuffer->GetExternalSignals( ) ) + { + pSignalSemaphores.push_back( dep.pTimelineSemaphore->pVkSemaphore ); + ulSignalPoints.push_back( dep.ulPoint ); + } + + for ( auto &dep : cmdBuffer->GetExternalDependencies( ) ) + { + pWaitSemaphores.push_back( dep.pTimelineSemaphore->pVkSemaphore ); + ulWaitPoints.push_back( dep.ulPoint ); + uWaitStageFlags.push_back( + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ); + } + + VkTimelineSemaphoreSubmitInfo timelineInfo = { + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, + // no need to ensure order of cmd buffer submission, we only have one + // queue + .waitSemaphoreValueCount = + static_cast( ulWaitPoints.size( ) ), + .pWaitSemaphoreValues = ulWaitPoints.data( ), + .signalSemaphoreValueCount = + static_cast( ulSignalPoints.size( ) ), + .pSignalSemaphoreValues = ulSignalPoints.data( ), + }; + + VkCommandBuffer rawCmdBuffer = cmdBuffer->rawBuffer( ); + + VkSubmitInfo submitInfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = &timelineInfo, + .waitSemaphoreCount = static_cast( pWaitSemaphores.size( ) ), + .pWaitSemaphores = pWaitSemaphores.data( ), + .pWaitDstStageMask = uWaitStageFlags.data( ), + .commandBufferCount = 1, + .pCommandBuffers = &rawCmdBuffer, + .signalSemaphoreCount = + static_cast( pSignalSemaphores.size( ) ), + .pSignalSemaphores = pSignalSemaphores.data( ), + }; + + vk_check( + vk.QueueSubmit( cmdBuffer->queue( ), 1, &submitInfo, VK_NULL_HANDLE ) ); + + return nextSeqNo; } -uint64_t CVulkanDevice::submit( std::unique_ptr cmdBuffer) +uint64_t CVulkanDevice::submit( std::unique_ptr cmdBuffer ) { - uint64_t nextSeqNo = submitInternal(cmdBuffer.get()); - m_pendingCmdBufs.emplace(nextSeqNo, std::move(cmdBuffer)); - return nextSeqNo; + uint64_t nextSeqNo = submitInternal( cmdBuffer.get( ) ); + m_pendingCmdBufs.emplace( nextSeqNo, std::move( cmdBuffer ) ); + return nextSeqNo; } void CVulkanDevice::garbageCollect( void ) { - uint64_t currentSeqNo; - vk_check( vk.GetSemaphoreCounterValue(device(), m_scratchTimelineSemaphore, ¤tSeqNo) ); + uint64_t currentSeqNo; + vk_check( vk.GetSemaphoreCounterValue( + device( ), m_scratchTimelineSemaphore, ¤tSeqNo ) ); - resetCmdBuffers(currentSeqNo); + resetCmdBuffers( currentSeqNo ); } -VulkanTimelineSemaphore_t::~VulkanTimelineSemaphore_t() +VulkanTimelineSemaphore_t::~VulkanTimelineSemaphore_t( ) { - if ( pVkSemaphore != VK_NULL_HANDLE ) - { - pDevice->vk.DestroySemaphore( pDevice->device(), pVkSemaphore, nullptr ); - pVkSemaphore = VK_NULL_HANDLE; - } + if ( pVkSemaphore != VK_NULL_HANDLE ) + { + pDevice->vk.DestroySemaphore( + pDevice->device( ), pVkSemaphore, nullptr ); + pVkSemaphore = VK_NULL_HANDLE; + } } -int VulkanTimelineSemaphore_t::GetFd() const +int VulkanTimelineSemaphore_t::GetFd( ) const { - const VkSemaphoreGetFdInfoKHR semaphoreGetInfo = - { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, - .semaphore = pVkSemaphore, - .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, - }; - - int32_t nFd = -1; - VkResult res = VK_SUCCESS; - if ( ( res = pDevice->vk.GetSemaphoreFdKHR( pDevice->device(), &semaphoreGetInfo, &nFd ) ) != VK_SUCCESS ) - { - vk_errorf( res, "vkGetSemaphoreFdKHR failed" ); - return -1; - } + const VkSemaphoreGetFdInfoKHR semaphoreGetInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, + .semaphore = pVkSemaphore, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, + }; + + int32_t nFd = -1; + VkResult res = VK_SUCCESS; + if ( ( res = pDevice->vk.GetSemaphoreFdKHR( + pDevice->device( ), &semaphoreGetInfo, &nFd ) ) != VK_SUCCESS ) + { + vk_errorf( res, "vkGetSemaphoreFdKHR failed" ); + return -1; + } - return nFd; + return nFd; } -std::shared_ptr CVulkanDevice::CreateTimelineSemaphore( uint64_t ulStartPoint, bool bShared ) +std::shared_ptr +CVulkanDevice::CreateTimelineSemaphore( uint64_t ulStartPoint, bool bShared ) { - std::shared_ptr pSemaphore = std::make_unique(); - pSemaphore->pDevice = this; - - VkSemaphoreCreateInfo createInfo = - { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - }; - - VkSemaphoreTypeCreateInfo typeInfo = - { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, - .pNext = std::exchange( createInfo.pNext, &typeInfo ), - .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, - .initialValue = ulStartPoint, - }; - - VkExportSemaphoreCreateInfo exportInfo = - { - .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, - .pNext = bShared ? std::exchange( createInfo.pNext, &exportInfo ) : nullptr, - // This is a syncobj fd for any drivers using syncobj. - .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, - }; - - VkResult res; - if ( ( res = vk.CreateSemaphore( m_device, &createInfo, nullptr, &pSemaphore->pVkSemaphore ) ) != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateSemaphore failed" ); - return nullptr; - } + std::shared_ptr pSemaphore = + std::make_unique( ); + pSemaphore->pDevice = this; + + VkSemaphoreCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + + VkSemaphoreTypeCreateInfo typeInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, + .pNext = std::exchange( createInfo.pNext, &typeInfo ), + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, + .initialValue = ulStartPoint, + }; + + VkExportSemaphoreCreateInfo exportInfo = { + .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, + .pNext = + bShared ? std::exchange( createInfo.pNext, &exportInfo ) : nullptr, + // This is a syncobj fd for any drivers using syncobj. + .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, + }; + + VkResult res; + if ( ( res = vk.CreateSemaphore( + m_device, &createInfo, nullptr, &pSemaphore->pVkSemaphore ) ) != + VK_SUCCESS ) + { + vk_errorf( res, "vkCreateSemaphore failed" ); + return nullptr; + } - return pSemaphore; + return pSemaphore; } -std::shared_ptr CVulkanDevice::ImportTimelineSemaphore( gamescope::CTimeline *pTimeline ) +std::shared_ptr +CVulkanDevice::ImportTimelineSemaphore( gamescope::CTimeline *pTimeline ) { - std::shared_ptr pSemaphore = std::make_unique(); - pSemaphore->pDevice = this; - - const VkSemaphoreTypeCreateInfo typeInfo = - { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, - .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, - }; - - const VkSemaphoreCreateInfo createInfo = - { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - .pNext = &typeInfo, - }; - - VkResult res; - if ( ( res = vk.CreateSemaphore( m_device, &createInfo, nullptr, &pSemaphore->pVkSemaphore ) ) != VK_SUCCESS ) - { - vk_errorf( res, "vkCreateSemaphore failed" ); - return nullptr; - } + std::shared_ptr pSemaphore = + std::make_unique( ); + pSemaphore->pDevice = this; + + const VkSemaphoreTypeCreateInfo typeInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, + }; + + const VkSemaphoreCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &typeInfo, + }; + + VkResult res; + if ( ( res = vk.CreateSemaphore( + m_device, &createInfo, nullptr, &pSemaphore->pVkSemaphore ) ) != + VK_SUCCESS ) + { + vk_errorf( res, "vkCreateSemaphore failed" ); + return nullptr; + } // "Importing a semaphore payload from a file descriptor transfers // ownership of the file descriptor from the application to the Vulkan // implementation. The application must not perform any operations on // the file descriptor after a successful import." - // - // Thus, we must dup. - - VkImportSemaphoreFdInfoKHR importFdInfo = - { - .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, - .pNext = nullptr, - .semaphore = pSemaphore->pVkSemaphore, - .flags = 0, // not temporary - .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, - .fd = dup( pTimeline->GetSyncobjFd() ), - }; - if ( ( res = vk.ImportSemaphoreFdKHR( m_device, &importFdInfo ) ) != VK_SUCCESS ) - { - vk_errorf( res, "vkImportSemaphoreFdKHR failed" ); - return nullptr; - } + // + // Thus, we must dup. + + VkImportSemaphoreFdInfoKHR importFdInfo = { + .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, + .pNext = nullptr, + .semaphore = pSemaphore->pVkSemaphore, + .flags = 0, // not temporary + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, + .fd = dup( pTimeline->GetSyncobjFd( ) ), + }; + if ( ( res = vk.ImportSemaphoreFdKHR( m_device, &importFdInfo ) ) != + VK_SUCCESS ) + { + vk_errorf( res, "vkImportSemaphoreFdKHR failed" ); + return nullptr; + } - return pSemaphore; + return pSemaphore; } -void CVulkanCmdBuffer::AddDependency( std::shared_ptr pTimelineSemaphore, uint64_t ulPoint ) +void CVulkanCmdBuffer::AddDependency( + std::shared_ptr pTimelineSemaphore, + uint64_t ulPoint ) { - m_ExternalDependencies.emplace_back( std::move( pTimelineSemaphore ), ulPoint ); + m_ExternalDependencies.emplace_back( + std::move( pTimelineSemaphore ), ulPoint ); } -void CVulkanCmdBuffer::AddSignal( std::shared_ptr pTimelineSemaphore, uint64_t ulPoint ) -{ - m_ExternalSignals.emplace_back( std::move( pTimelineSemaphore ), ulPoint ); -} +void CVulkanCmdBuffer::AddSignal( + std::shared_ptr pTimelineSemaphore, + uint64_t ulPoint ) +{ m_ExternalSignals.emplace_back( std::move( pTimelineSemaphore ), ulPoint ); } -void CVulkanDevice::wait(uint64_t sequence, bool reset) +void CVulkanDevice::wait( uint64_t sequence, bool reset ) { - if (m_submissionSeqNo == sequence) - m_uploadBufferOffset = 0; + if ( m_submissionSeqNo == sequence ) m_uploadBufferOffset = 0; - VkSemaphoreWaitInfo waitInfo = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, - .semaphoreCount = 1, - .pSemaphores = &m_scratchTimelineSemaphore, - .pValues = &sequence, - } ; + VkSemaphoreWaitInfo waitInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, + .semaphoreCount = 1, + .pSemaphores = &m_scratchTimelineSemaphore, + .pValues = &sequence, + }; - vk_check( vk.WaitSemaphores( device(), &waitInfo, ~0ull ) ); + vk_check( vk.WaitSemaphores( device( ), &waitInfo, ~0ull ) ); - if (reset) - resetCmdBuffers(sequence); + if ( reset ) resetCmdBuffers( sequence ); } -void CVulkanDevice::waitIdle(bool reset) -{ - wait(m_submissionSeqNo, reset); -} +void CVulkanDevice::waitIdle( bool reset ) { wait( m_submissionSeqNo, reset ); } -void CVulkanDevice::resetCmdBuffers(uint64_t sequence) +void CVulkanDevice::resetCmdBuffers( uint64_t sequence ) { - auto last = m_pendingCmdBufs.find(sequence); - if (last == m_pendingCmdBufs.end()) - return; - - for (auto it = m_pendingCmdBufs.begin(); ; it++) - { - it->second->reset(); - m_unusedCmdBufs.push_back(std::move(it->second)); - if (it == last) - break; - } - - m_pendingCmdBufs.erase(m_pendingCmdBufs.begin(), ++last); -} + auto last = m_pendingCmdBufs.find( sequence ); + if ( last == m_pendingCmdBufs.end( ) ) return; -CVulkanCmdBuffer::CVulkanCmdBuffer(CVulkanDevice *parent, VkCommandBuffer cmdBuffer, VkQueue queue, uint32_t queueFamily) - : m_cmdBuffer(cmdBuffer), m_device(parent), m_queue(queue), m_queueFamily(queueFamily) -{ + for ( auto it = m_pendingCmdBufs.begin( );; it++ ) + { + it->second->reset( ); + m_unusedCmdBufs.push_back( std::move( it->second ) ); + if ( it == last ) break; + } + + m_pendingCmdBufs.erase( m_pendingCmdBufs.begin( ), ++last ); } -CVulkanCmdBuffer::~CVulkanCmdBuffer() +CVulkanCmdBuffer::CVulkanCmdBuffer( + CVulkanDevice *parent, + VkCommandBuffer cmdBuffer, + VkQueue queue, + uint32_t queueFamily ) : + m_cmdBuffer( cmdBuffer ), + m_device( parent ), + m_queue( queue ), + m_queueFamily( queueFamily ) +{} + +CVulkanCmdBuffer::~CVulkanCmdBuffer( ) { - m_device->vk.FreeCommandBuffers(m_device->device(), m_device->commandPool(), 1, &m_cmdBuffer); + m_device->vk.FreeCommandBuffers( + m_device->device( ), m_device->commandPool( ), 1, &m_cmdBuffer ); } -void CVulkanCmdBuffer::reset() +void CVulkanCmdBuffer::reset( ) { - vk_check( m_device->vk.ResetCommandBuffer(m_cmdBuffer, 0) ); - m_textureRefs.clear(); - m_textureState.clear(); + vk_check( m_device->vk.ResetCommandBuffer( m_cmdBuffer, 0 ) ); + m_textureRefs.clear( ); + m_textureState.clear( ); - m_ExternalDependencies.clear(); - m_ExternalSignals.clear(); + m_ExternalDependencies.clear( ); + m_ExternalSignals.clear( ); } -void CVulkanCmdBuffer::begin() +void CVulkanCmdBuffer::begin( ) { - VkCommandBufferBeginInfo commandBufferBeginInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT - }; + VkCommandBufferBeginInfo commandBufferBeginInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; - vk_check( m_device->vk.BeginCommandBuffer(m_cmdBuffer, &commandBufferBeginInfo) ); + vk_check( m_device->vk.BeginCommandBuffer( + m_cmdBuffer, &commandBufferBeginInfo ) ); - clearState(); + clearState( ); } -void CVulkanCmdBuffer::end() +void CVulkanCmdBuffer::end( ) { - insertBarrier(true); - vk_check( m_device->vk.EndCommandBuffer(m_cmdBuffer) ); + insertBarrier( true ); + vk_check( m_device->vk.EndCommandBuffer( m_cmdBuffer ) ); } -void CVulkanCmdBuffer::bindTexture(uint32_t slot, gamescope::Rc texture) +void CVulkanCmdBuffer::bindTexture( + uint32_t slot, gamescope::Rc texture ) { - m_boundTextures[slot] = texture.get(); - if (texture) - m_textureRefs.emplace_back(std::move(texture)); + m_boundTextures[ slot ] = texture.get( ); + if ( texture ) m_textureRefs.emplace_back( std::move( texture ) ); } -void CVulkanCmdBuffer::bindColorMgmtLuts(uint32_t slot, gamescope::Rc lut1d, gamescope::Rc lut3d) +void CVulkanCmdBuffer::bindColorMgmtLuts( + uint32_t slot, + gamescope::Rc lut1d, + gamescope::Rc lut3d ) { - m_shaperLut[slot] = lut1d.get(); - m_lut3D[slot] = lut3d.get(); + m_shaperLut[ slot ] = lut1d.get( ); + m_lut3D[ slot ] = lut3d.get( ); - if (lut1d != nullptr) - m_textureRefs.emplace_back(std::move(lut1d)); - if (lut3d != nullptr) - m_textureRefs.emplace_back(std::move(lut3d)); + if ( lut1d != nullptr ) m_textureRefs.emplace_back( std::move( lut1d ) ); + if ( lut3d != nullptr ) m_textureRefs.emplace_back( std::move( lut3d ) ); } -void CVulkanCmdBuffer::setTextureSrgb(uint32_t slot, bool srgb) -{ - m_useSrgb[slot] = srgb; -} +void CVulkanCmdBuffer::setTextureSrgb( uint32_t slot, bool srgb ) +{ m_useSrgb[ slot ] = srgb; } -void CVulkanCmdBuffer::setSamplerNearest(uint32_t slot, bool nearest) -{ - m_samplerState[slot].bNearest = nearest; -} +void CVulkanCmdBuffer::setSamplerNearest( uint32_t slot, bool nearest ) +{ m_samplerState[ slot ].bNearest = nearest; } -void CVulkanCmdBuffer::setSamplerUnnormalized(uint32_t slot, bool unnormalized) -{ - m_samplerState[slot].bUnnormalized = unnormalized; -} +void CVulkanCmdBuffer::setSamplerUnnormalized( + uint32_t slot, bool unnormalized ) +{ m_samplerState[ slot ].bUnnormalized = unnormalized; } -void CVulkanCmdBuffer::bindTarget(gamescope::Rc target) +void CVulkanCmdBuffer::bindTarget( gamescope::Rc target ) { - m_target = target.get(); - if (target) - m_textureRefs.emplace_back(std::move(target)); + m_target = target.get( ); + if ( target ) m_textureRefs.emplace_back( std::move( target ) ); } -void CVulkanCmdBuffer::clearState() +void CVulkanCmdBuffer::clearState( ) { - for (auto& texture : m_boundTextures) - texture = nullptr; + for ( auto &texture : m_boundTextures ) + texture = nullptr; - for (auto& sampler : m_samplerState) - sampler = {}; + for ( auto &sampler : m_samplerState ) + sampler = {}; - m_target = nullptr; - m_useSrgb.reset(); + m_target = nullptr; + m_useSrgb.reset( ); } template -void CVulkanCmdBuffer::uploadConstants(Args&&... args) +void CVulkanCmdBuffer::uploadConstants( Args &&...args ) { - PushData data(std::forward(args)...); + PushData data( std::forward( args )... ); - auto [ptr, offset] = m_device->uploadBufferData(sizeof(data)); - m_renderBufferOffset = offset; - memcpy(ptr, &data, sizeof(data)); + auto [ ptr, offset ] = m_device->uploadBufferData( sizeof( data ) ); + m_renderBufferOffset = offset; + memcpy( ptr, &data, sizeof( data ) ); } -void CVulkanCmdBuffer::bindPipeline(VkPipeline pipeline) +void CVulkanCmdBuffer::bindPipeline( VkPipeline pipeline ) { - m_device->vk.CmdBindPipeline(m_cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + m_device->vk.CmdBindPipeline( + m_cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline ); } -void CVulkanCmdBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z) +void CVulkanCmdBuffer::dispatch( uint32_t x, uint32_t y, uint32_t z ) { - for (auto src : m_boundTextures) - { - if (src) - prepareSrcImage(src); - } - assert(m_target != nullptr); - prepareDestImage(m_target); - insertBarrier(); - - VkDescriptorSet descriptorSet = m_device->descriptorSet(); - - std::array writeDescriptorSets; - std::array imageDescriptors = {}; - std::array ycbcrImageDescriptors = {}; - std::array targetDescriptors = {}; - std::array shaperLutDescriptor = {}; - std::array lut3DDescriptor = {}; - VkDescriptorBufferInfo scratchDescriptor = {}; - - writeDescriptorSets[0] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = descriptorSet, - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .pBufferInfo = &scratchDescriptor, - }; - - writeDescriptorSets[1] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = descriptorSet, - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .pImageInfo = &targetDescriptors[0], - }; - - writeDescriptorSets[2] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = descriptorSet, - .dstBinding = 2, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .pImageInfo = &targetDescriptors[1], - }; - - writeDescriptorSets[3] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = descriptorSet, - .dstBinding = 3, - .dstArrayElement = 0, - .descriptorCount = imageDescriptors.size(), - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .pImageInfo = imageDescriptors.data(), - }; + for ( auto src : m_boundTextures ) + { + if ( src ) prepareSrcImage( src ); + } + assert( m_target != nullptr ); + prepareDestImage( m_target ); + insertBarrier( ); + + VkDescriptorSet descriptorSet = m_device->descriptorSet( ); + + std::array writeDescriptorSets; + std::array imageDescriptors = {}; + std::array + ycbcrImageDescriptors = {}; + std::array targetDescriptors = {}; + std::array shaperLutDescriptor = {}; + std::array lut3DDescriptor = {}; + VkDescriptorBufferInfo scratchDescriptor = {}; + + writeDescriptorSets[ 0 ] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = &scratchDescriptor, + }; + + writeDescriptorSets[ 1 ] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet, + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .pImageInfo = &targetDescriptors[ 0 ], + }; + + writeDescriptorSets[ 2 ] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet, + .dstBinding = 2, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .pImageInfo = &targetDescriptors[ 1 ], + }; + + writeDescriptorSets[ 3 ] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet, + .dstBinding = 3, + .dstArrayElement = 0, + .descriptorCount = imageDescriptors.size( ), + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = imageDescriptors.data( ), + }; + + writeDescriptorSets[ 4 ] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet, + .dstBinding = 4, + .dstArrayElement = 0, + .descriptorCount = ycbcrImageDescriptors.size( ), + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = ycbcrImageDescriptors.data( ), + }; + + writeDescriptorSets[ 5 ] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet, + .dstBinding = 5, + .dstArrayElement = 0, + .descriptorCount = shaperLutDescriptor.size( ), + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = shaperLutDescriptor.data( ), + }; + + writeDescriptorSets[ 6 ] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet, + .dstBinding = 6, + .dstArrayElement = 0, + .descriptorCount = lut3DDescriptor.size( ), + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = lut3DDescriptor.data( ), + }; + + scratchDescriptor.buffer = m_device->m_uploadBuffer; + scratchDescriptor.offset = m_renderBufferOffset; + scratchDescriptor.range = VK_WHOLE_SIZE; + + for ( uint32_t i = 0; i < VKR_SAMPLER_SLOTS; i++ ) + { + imageDescriptors[ i ].sampler = + m_device->sampler( m_samplerState[ i ] ); + imageDescriptors[ i ].imageLayout = VK_IMAGE_LAYOUT_GENERAL; + ycbcrImageDescriptors[ i ].imageLayout = VK_IMAGE_LAYOUT_GENERAL; + if ( m_boundTextures[ i ] == nullptr ) continue; + + VkImageView view = m_useSrgb[ i ] ? m_boundTextures[ i ]->srgbView( ) + : m_boundTextures[ i ]->linearView( ); + + if ( m_boundTextures[ i ]->format( ) == + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM ) + ycbcrImageDescriptors[ i ].imageView = view; + else + imageDescriptors[ i ].imageView = view; + } - writeDescriptorSets[4] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = descriptorSet, - .dstBinding = 4, - .dstArrayElement = 0, - .descriptorCount = ycbcrImageDescriptors.size(), - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .pImageInfo = ycbcrImageDescriptors.data(), - }; + for ( uint32_t i = 0; i < VKR_LUT3D_COUNT; i++ ) + { + SamplerState linearState; + linearState.bNearest = false; + linearState.bUnnormalized = false; + SamplerState nearestState; // TODO(Josh): Probably want to do this when + // I bring in tetrahedral interpolation. + nearestState.bNearest = true; + nearestState.bUnnormalized = false; + + shaperLutDescriptor[ i ].sampler = m_device->sampler( linearState ); + shaperLutDescriptor[ i ].imageLayout = + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + // TODO(Josh): I hate the fact that srgbView = view *as* raw srgb and + // treat as linear. I need to change this, it's so utterly stupid and + // confusing. + shaperLutDescriptor[ i ].imageView = + m_shaperLut[ i ] ? m_shaperLut[ i ]->srgbView( ) : VK_NULL_HANDLE; + + lut3DDescriptor[ i ].sampler = m_device->sampler( nearestState ); + lut3DDescriptor[ i ].imageLayout = + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + lut3DDescriptor[ i ].imageView = + m_lut3D[ i ] ? m_lut3D[ i ]->srgbView( ) : VK_NULL_HANDLE; + } - writeDescriptorSets[5] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = descriptorSet, - .dstBinding = 5, - .dstArrayElement = 0, - .descriptorCount = shaperLutDescriptor.size(), - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .pImageInfo = shaperLutDescriptor.data(), - }; + if ( !m_target->isYcbcr( ) ) + { + targetDescriptors[ 0 ].imageView = m_target->srgbView( ); + targetDescriptors[ 0 ].imageLayout = VK_IMAGE_LAYOUT_GENERAL; + } + else + { + targetDescriptors[ 0 ].imageView = m_target->lumaView( ); + targetDescriptors[ 0 ].imageLayout = VK_IMAGE_LAYOUT_GENERAL; - writeDescriptorSets[6] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = descriptorSet, - .dstBinding = 6, - .dstArrayElement = 0, - .descriptorCount = lut3DDescriptor.size(), - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .pImageInfo = lut3DDescriptor.data(), - }; + targetDescriptors[ 1 ].imageView = m_target->chromaView( ); + targetDescriptors[ 1 ].imageLayout = VK_IMAGE_LAYOUT_GENERAL; + } - scratchDescriptor.buffer = m_device->m_uploadBuffer; - scratchDescriptor.offset = m_renderBufferOffset; - scratchDescriptor.range = VK_WHOLE_SIZE; - - for (uint32_t i = 0; i < VKR_SAMPLER_SLOTS; i++) - { - imageDescriptors[i].sampler = m_device->sampler(m_samplerState[i]); - imageDescriptors[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL; - ycbcrImageDescriptors[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL; - if (m_boundTextures[i] == nullptr) - continue; - - VkImageView view = m_useSrgb[i] ? m_boundTextures[i]->srgbView() : m_boundTextures[i]->linearView(); - - if (m_boundTextures[i]->format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) - ycbcrImageDescriptors[i].imageView = view; - else - imageDescriptors[i].imageView = view; - } - - for (uint32_t i = 0; i < VKR_LUT3D_COUNT; i++) - { - SamplerState linearState; - linearState.bNearest = false; - linearState.bUnnormalized = false; - SamplerState nearestState; // TODO(Josh): Probably want to do this when I bring in tetrahedral interpolation. - nearestState.bNearest = true; - nearestState.bUnnormalized = false; - - shaperLutDescriptor[i].sampler = m_device->sampler(linearState); - shaperLutDescriptor[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - // TODO(Josh): I hate the fact that srgbView = view *as* raw srgb and treat as linear. - // I need to change this, it's so utterly stupid and confusing. - shaperLutDescriptor[i].imageView = m_shaperLut[i] ? m_shaperLut[i]->srgbView() : VK_NULL_HANDLE; - - lut3DDescriptor[i].sampler = m_device->sampler(nearestState); - lut3DDescriptor[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - lut3DDescriptor[i].imageView = m_lut3D[i] ? m_lut3D[i]->srgbView() : VK_NULL_HANDLE; - } - - if (!m_target->isYcbcr()) - { - targetDescriptors[0].imageView = m_target->srgbView(); - targetDescriptors[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; - } - else - { - targetDescriptors[0].imageView = m_target->lumaView(); - targetDescriptors[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; - - targetDescriptors[1].imageView = m_target->chromaView(); - targetDescriptors[1].imageLayout = VK_IMAGE_LAYOUT_GENERAL; - } - - m_device->vk.UpdateDescriptorSets(m_device->device(), writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr); - - m_device->vk.CmdBindDescriptorSets(m_cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, m_device->pipelineLayout(), 0, 1, &descriptorSet, 0, nullptr); - - m_device->vk.CmdDispatch(m_cmdBuffer, x, y, z); - - markDirty(m_target); + m_device->vk.UpdateDescriptorSets( + m_device->device( ), + writeDescriptorSets.size( ), + writeDescriptorSets.data( ), + 0, + nullptr ); + + m_device->vk.CmdBindDescriptorSets( + m_cmdBuffer, + VK_PIPELINE_BIND_POINT_COMPUTE, + m_device->pipelineLayout( ), + 0, + 1, + &descriptorSet, + 0, + nullptr ); + + m_device->vk.CmdDispatch( m_cmdBuffer, x, y, z ); + + markDirty( m_target ); } -void CVulkanCmdBuffer::copyImage(gamescope::Rc src, gamescope::Rc dst) +void CVulkanCmdBuffer::copyImage( + gamescope::Rc src, gamescope::Rc dst ) { - assert(src->width() == dst->width()); - assert(src->height() == dst->height()); - prepareSrcImage(src.get()); - prepareDestImage(dst.get()); - insertBarrier(); - - VkImageCopy region = { - .srcSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1 - }, - .dstSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1 - }, - .extent = { - .width = src->width(), - .height = src->height(), - .depth = 1 - }, - }; - - m_device->vk.CmdCopyImage(m_cmdBuffer, src->vkImage(), VK_IMAGE_LAYOUT_GENERAL, dst->vkImage(), VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); - - markDirty(dst.get()); - m_textureRefs.emplace_back(std::move(src)); - m_textureRefs.emplace_back(std::move(dst)); + assert( src->width( ) == dst->width( ) ); + assert( src->height( ) == dst->height( ) ); + prepareSrcImage( src.get( ) ); + prepareDestImage( dst.get( ) ); + insertBarrier( ); + + VkImageCopy region = { + .srcSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 }, + .dstSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 }, + .extent = { .width = src->width( ), + .height = src->height( ), + .depth = 1 }, + }; + + m_device->vk.CmdCopyImage( + m_cmdBuffer, + src->vkImage( ), + VK_IMAGE_LAYOUT_GENERAL, + dst->vkImage( ), + VK_IMAGE_LAYOUT_GENERAL, + 1, + ®ion ); + + markDirty( dst.get( ) ); + m_textureRefs.emplace_back( std::move( src ) ); + m_textureRefs.emplace_back( std::move( dst ) ); } -void CVulkanCmdBuffer::copyBufferToImage(VkBuffer buffer, VkDeviceSize offset, uint32_t stride, gamescope::Rc dst) +void CVulkanCmdBuffer::copyBufferToImage( + VkBuffer buffer, + VkDeviceSize offset, + uint32_t stride, + gamescope::Rc dst ) { - prepareDestImage(dst.get()); - insertBarrier(); - VkBufferImageCopy region = { + prepareDestImage( dst.get( ) ); + insertBarrier( ); + VkBufferImageCopy region = { .bufferOffset = offset, .bufferRowLength = stride, .imageSubresource = { @@ -1821,252 +2145,284 @@ void CVulkanCmdBuffer::copyBufferToImage(VkBuffer buffer, VkDeviceSize offset, u }, }; - m_device->vk.CmdCopyBufferToImage(m_cmdBuffer, buffer, dst->vkImage(), VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); + m_device->vk.CmdCopyBufferToImage( + m_cmdBuffer, + buffer, + dst->vkImage( ), + VK_IMAGE_LAYOUT_GENERAL, + 1, + ®ion ); - markDirty(dst.get()); + markDirty( dst.get( ) ); - m_textureRefs.emplace_back(std::move(dst)); + m_textureRefs.emplace_back( std::move( dst ) ); } -void CVulkanCmdBuffer::prepareSrcImage(CVulkanTexture *image) +void CVulkanCmdBuffer::prepareSrcImage( CVulkanTexture *image ) { - auto result = m_textureState.emplace(image, TextureState()); - // no need to reimport if the image didn't change - if (!result.second) - return; - // using the swapchain image as a source without writing to it doesn't make any sense - assert(image->outputImage() == false); - result.first->second.needsImport = image->externalImage(); - result.first->second.needsExport = image->externalImage(); + auto result = m_textureState.emplace( image, TextureState( ) ); + // no need to reimport if the image didn't change + if ( !result.second ) return; + // using the swapchain image as a source without writing to it doesn't make + // any sense + assert( image->outputImage( ) == false ); + result.first->second.needsImport = image->externalImage( ); + result.first->second.needsExport = image->externalImage( ); } -void CVulkanCmdBuffer::prepareDestImage(CVulkanTexture *image) +void CVulkanCmdBuffer::prepareDestImage( CVulkanTexture *image ) { - auto result = m_textureState.emplace(image, TextureState()); - // no need to discard if the image is already image/in the correct layout - if (!result.second) - return; - result.first->second.discarded = true; - result.first->second.needsExport = image->externalImage(); - result.first->second.needsPresentLayout = image->outputImage(); + auto result = m_textureState.emplace( image, TextureState( ) ); + // no need to discard if the image is already image/in the correct layout + if ( !result.second ) return; + result.first->second.discarded = true; + result.first->second.needsExport = image->externalImage( ); + result.first->second.needsPresentLayout = image->outputImage( ); } -void CVulkanCmdBuffer::discardImage(CVulkanTexture *image) +void CVulkanCmdBuffer::discardImage( CVulkanTexture *image ) { - auto result = m_textureState.emplace(image, TextureState()); - if (!result.second) - return; - result.first->second.discarded = true; + auto result = m_textureState.emplace( image, TextureState( ) ); + if ( !result.second ) return; + result.first->second.discarded = true; } -void CVulkanCmdBuffer::markDirty(CVulkanTexture *image) +void CVulkanCmdBuffer::markDirty( CVulkanTexture *image ) { - auto result = m_textureState.find(image); - // image should have been prepared already - assert(result != m_textureState.end()); - result->second.dirty = true; + auto result = m_textureState.find( image ); + // image should have been prepared already + assert( result != m_textureState.end( ) ); + result->second.dirty = true; } -void CVulkanCmdBuffer::insertBarrier(bool flush) +void CVulkanCmdBuffer::insertBarrier( bool flush ) { - std::vector barriers; + std::vector barriers; - uint32_t externalQueue = m_device->supportsModifiers() ? VK_QUEUE_FAMILY_FOREIGN_EXT : VK_QUEUE_FAMILY_EXTERNAL_KHR; + uint32_t externalQueue = m_device->supportsModifiers( ) + ? VK_QUEUE_FAMILY_FOREIGN_EXT + : VK_QUEUE_FAMILY_EXTERNAL_KHR; - VkImageSubresourceRange subResRange = - { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - }; + VkImageSubresourceRange subResRange = { .aspectMask = + VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 }; - for (auto& pair : m_textureState) - { - CVulkanTexture *image = pair.first; - TextureState& state = pair.second; - assert(!flush || !state.needsImport); - - bool isExport = flush && state.needsExport; - bool isPresent = flush && state.needsPresentLayout; - - if (!state.discarded && !state.dirty && !state.needsImport && !isExport && !isPresent) - continue; - - const VkAccessFlags write_bits = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - const VkAccessFlags read_bits = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT; - - if (image->queueFamily == VK_QUEUE_FAMILY_IGNORED) - image->queueFamily = m_queueFamily; - - VkImageMemoryBarrier memoryBarrier = - { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = state.dirty ? write_bits : 0u, - .dstAccessMask = flush ? 0u : read_bits | write_bits, - .oldLayout = state.discarded ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL, - .newLayout = isPresent ? GetBackend()->GetPresentLayout() : VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = isExport ? image->queueFamily : state.needsImport ? externalQueue : image->queueFamily, - .dstQueueFamilyIndex = isExport ? externalQueue : state.needsImport ? m_queueFamily : m_queueFamily, - .image = image->vkImage(), - .subresourceRange = subResRange - }; - - barriers.push_back(memoryBarrier); - - state.discarded = false; - state.dirty = false; - state.needsImport = false; - } + for ( auto &pair : m_textureState ) + { + CVulkanTexture *image = pair.first; + TextureState &state = pair.second; + assert( !flush || !state.needsImport ); + + bool isExport = flush && state.needsExport; + bool isPresent = flush && state.needsPresentLayout; + + if ( !state.discarded && !state.dirty && !state.needsImport && + !isExport && !isPresent ) + continue; + + const VkAccessFlags write_bits = + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + const VkAccessFlags read_bits = + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT; + + if ( image->queueFamily == VK_QUEUE_FAMILY_IGNORED ) + image->queueFamily = m_queueFamily; + + VkImageMemoryBarrier memoryBarrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = state.dirty ? write_bits : 0u, + .dstAccessMask = flush ? 0u : read_bits | write_bits, + .oldLayout = state.discarded ? VK_IMAGE_LAYOUT_UNDEFINED + : VK_IMAGE_LAYOUT_GENERAL, + .newLayout = isPresent ? GetBackend( )->GetPresentLayout( ) + : VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = isExport ? image->queueFamily + : state.needsImport ? externalQueue + : image->queueFamily, + .dstQueueFamilyIndex = isExport ? externalQueue + : state.needsImport ? m_queueFamily + : m_queueFamily, + .image = image->vkImage( ), + .subresourceRange = subResRange + }; + + barriers.push_back( memoryBarrier ); + + state.discarded = false; + state.dirty = false; + state.needsImport = false; + } - // TODO replace VK_PIPELINE_STAGE_ALL_COMMANDS_BIT - m_device->vk.CmdPipelineBarrier(m_cmdBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, 0, nullptr, 0, nullptr, barriers.size(), barriers.data()); + // TODO replace VK_PIPELINE_STAGE_ALL_COMMANDS_BIT + m_device->vk.CmdPipelineBarrier( + m_cmdBuffer, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, + 0, + nullptr, + 0, + nullptr, + barriers.size( ), + barriers.data( ) ); } CVulkanDevice g_device; static bool allDMABUFsEqual( wlr_dmabuf_attributes *pDMA ) { - if ( pDMA->n_planes == 1 ) - return true; - - struct stat first_stat; - if ( fstat( pDMA->fd[0], &first_stat ) != 0 ) - { - vk_log.errorf_errno( "fstat failed" ); - return false; - } - - for ( int i = 1; i < pDMA->n_planes; ++i ) - { - struct stat plane_stat; - if ( fstat( pDMA->fd[i], &plane_stat ) != 0 ) - { - vk_log.errorf_errno( "fstat failed" ); - return false; - } - if ( plane_stat.st_ino != first_stat.st_ino ) - return false; - } - - return true; -} + if ( pDMA->n_planes == 1 ) return true; -static VkResult getModifierProps( const VkImageCreateInfo *imageInfo, uint64_t modifier, VkExternalImageFormatProperties *externalFormatProps) -{ - VkPhysicalDeviceImageDrmFormatModifierInfoEXT modifierFormatInfo = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, - .drmFormatModifier = modifier, - .sharingMode = imageInfo->sharingMode, - }; + struct stat first_stat; + if ( fstat( pDMA->fd[ 0 ], &first_stat ) != 0 ) + { + vk_log.errorf_errno( "fstat failed" ); + return false; + } - VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, - .pNext = &modifierFormatInfo, - .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - }; + for ( int i = 1; i < pDMA->n_planes; ++i ) + { + struct stat plane_stat; + if ( fstat( pDMA->fd[ i ], &plane_stat ) != 0 ) + { + vk_log.errorf_errno( "fstat failed" ); + return false; + } + if ( plane_stat.st_ino != first_stat.st_ino ) return false; + } - VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .pNext = &externalImageFormatInfo, - .format = imageInfo->format, - .type = imageInfo->imageType, - .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, - .usage = imageInfo->usage, - .flags = imageInfo->flags, - }; + return true; +} - const VkImageFormatListCreateInfo *readonlyList = pNextFind(imageInfo, VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO); - VkImageFormatListCreateInfo formatList = {}; - if ( readonlyList != nullptr ) - { - formatList = *readonlyList; - formatList.pNext = std::exchange(imageFormatInfo.pNext, &formatList); - } - - VkImageFormatProperties2 imageProps = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - .pNext = externalFormatProps, - }; +static VkResult getModifierProps( + const VkImageCreateInfo *imageInfo, + uint64_t modifier, + VkExternalImageFormatProperties *externalFormatProps ) +{ + VkPhysicalDeviceImageDrmFormatModifierInfoEXT modifierFormatInfo = { + .sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, + .drmFormatModifier = modifier, + .sharingMode = imageInfo->sharingMode, + }; + + VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, + .pNext = &modifierFormatInfo, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + + VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + .pNext = &externalImageFormatInfo, + .format = imageInfo->format, + .type = imageInfo->imageType, + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + .usage = imageInfo->usage, + .flags = imageInfo->flags, + }; + + const VkImageFormatListCreateInfo *readonlyList = + pNextFind( + imageInfo, VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO ); + VkImageFormatListCreateInfo formatList = {}; + if ( readonlyList != nullptr ) + { + formatList = *readonlyList; + formatList.pNext = std::exchange( imageFormatInfo.pNext, &formatList ); + } - return g_device.vk.GetPhysicalDeviceImageFormatProperties2(g_device.physDev(), &imageFormatInfo, &imageProps); + VkImageFormatProperties2 imageProps = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + .pNext = externalFormatProps, + }; + + return g_device.vk.GetPhysicalDeviceImageFormatProperties2( + g_device.physDev( ), &imageFormatInfo, &imageProps ); } -static VkImageViewType VulkanImageTypeToViewType(VkImageType type) +static VkImageViewType VulkanImageTypeToViewType( VkImageType type ) { - switch (type) - { - case VK_IMAGE_TYPE_1D: return VK_IMAGE_VIEW_TYPE_1D; - case VK_IMAGE_TYPE_2D: return VK_IMAGE_VIEW_TYPE_2D; - case VK_IMAGE_TYPE_3D: return VK_IMAGE_VIEW_TYPE_3D; - default: abort(); - } + switch ( type ) + { + case VK_IMAGE_TYPE_1D: + return VK_IMAGE_VIEW_TYPE_1D; + case VK_IMAGE_TYPE_2D: + return VK_IMAGE_VIEW_TYPE_2D; + case VK_IMAGE_TYPE_3D: + return VK_IMAGE_VIEW_TYPE_3D; + default: + abort( ); + } } -bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uint32_t drmFormat, createFlags flags, wlr_dmabuf_attributes *pDMA /* = nullptr */, uint32_t contentWidth /* = 0 */, uint32_t contentHeight /* = 0 */, CVulkanTexture *pExistingImageToReuseMemory, gamescope::OwningRc pBackendFb ) +bool CVulkanTexture::BInit( + uint32_t width, + uint32_t height, + uint32_t depth, + uint32_t drmFormat, + createFlags flags, + wlr_dmabuf_attributes *pDMA /* = nullptr */, + uint32_t contentWidth /* = 0 */, + uint32_t contentHeight /* = 0 */, + CVulkanTexture *pExistingImageToReuseMemory, + gamescope::OwningRc pBackendFb ) { - m_pBackendFb = std::move( pBackendFb ); - m_drmFormat = drmFormat; - VkResult res = VK_ERROR_INITIALIZATION_FAILED; - - VkImageTiling tiling = (flags.bMappable || flags.bLinear) ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL; - VkImageUsageFlags usage = 0; - VkMemoryPropertyFlags properties; - - if ( flags.bSampled == true ) - { - usage |= VK_IMAGE_USAGE_SAMPLED_BIT; - } - - if ( flags.bStorage == true ) - { - usage |= VK_IMAGE_USAGE_STORAGE_BIT; - } - - if ( flags.bColorAttachment == true ) - { - usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - } - - if ( flags.bFlippable == true ) - { - flags.bExportable = true; - } - - if ( flags.bTransferSrc == true ) - { - usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - } - - if ( flags.bTransferDst == true ) - { - usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; - } - - if ( flags.bMappable == true ) - { - properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; - } - else - { - properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - } - - if ( flags.bOutputImage == true ) - { - m_bOutputImage = true; - } - - m_bExternal = pDMA || flags.bExportable == true; - - // Possible extensions for below - wsi_image_create_info wsiImageCreateInfo = {}; - VkExternalMemoryImageCreateInfo externalImageCreateInfo = {}; - VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {}; - VkSubresourceLayout modifierPlaneLayouts[4] = {}; - VkImageDrmFormatModifierListCreateInfoEXT modifierListInfo = {}; - - VkImageCreateInfo imageInfo = { + m_pBackendFb = std::move( pBackendFb ); + m_drmFormat = drmFormat; + VkResult res = VK_ERROR_INITIALIZATION_FAILED; + + VkImageTiling tiling = ( flags.bMappable || flags.bLinear ) + ? VK_IMAGE_TILING_LINEAR + : VK_IMAGE_TILING_OPTIMAL; + VkImageUsageFlags usage = 0; + VkMemoryPropertyFlags properties; + + if ( flags.bSampled == true ) { usage |= VK_IMAGE_USAGE_SAMPLED_BIT; } + + if ( flags.bStorage == true ) { usage |= VK_IMAGE_USAGE_STORAGE_BIT; } + + if ( flags.bColorAttachment == true ) + { + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + + if ( flags.bFlippable == true ) { flags.bExportable = true; } + + if ( flags.bTransferSrc == true ) + { + usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + + if ( flags.bTransferDst == true ) + { + usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + + if ( flags.bMappable == true ) + { + properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + } + else + { + properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + + if ( flags.bOutputImage == true ) { m_bOutputImage = true; } + + m_bExternal = pDMA || flags.bExportable == true; + + // Possible extensions for below + wsi_image_create_info wsiImageCreateInfo = {}; + VkExternalMemoryImageCreateInfo externalImageCreateInfo = {}; + VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {}; + VkSubresourceLayout modifierPlaneLayouts[ 4 ] = {}; + VkImageDrmFormatModifierListCreateInfoEXT modifierListInfo = {}; + + VkImageCreateInfo imageInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = flags.imageType, .format = DRMFormatToVulkan(drmFormat, false), @@ -2083,404 +2439,447 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; - assert( imageInfo.format != VK_FORMAT_UNDEFINED ); + assert( imageInfo.format != VK_FORMAT_UNDEFINED ); - std::array formats = { - DRMFormatToVulkan(drmFormat, false), - DRMFormatToVulkan(drmFormat, true), - }; + std::array formats = { + DRMFormatToVulkan( drmFormat, false ), + DRMFormatToVulkan( drmFormat, true ), + }; - VkImageFormatListCreateInfo formatList = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, - .viewFormatCount = (uint32_t)formats.size(), - .pViewFormats = formats.data(), - }; + VkImageFormatListCreateInfo formatList = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, + .viewFormatCount = ( uint32_t )formats.size( ), + .pViewFormats = formats.data( ), + }; - if ( formats[0] != formats[1] ) - { - formatList.pNext = std::exchange(imageInfo.pNext, &formatList); - imageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } - - if ( pDMA != nullptr ) - { - assert( drmFormat == pDMA->format ); - } - - if ( g_device.supportsModifiers() && pDMA && pDMA->modifier != DRM_FORMAT_MOD_INVALID ) - { - VkExternalImageFormatProperties externalImageProperties = { - .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, - }; + if ( formats[ 0 ] != formats[ 1 ] ) + { + formatList.pNext = std::exchange( imageInfo.pNext, &formatList ); + imageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + } - res = getModifierProps( &imageInfo, pDMA->modifier, &externalImageProperties ); - if ( res != VK_SUCCESS && res != VK_ERROR_FORMAT_NOT_SUPPORTED ) { - vk_errorf( res, "getModifierProps failed" ); - return false; - } - - if ( res == VK_SUCCESS && - ( externalImageProperties.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT ) ) - { - modifierInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, - .pNext = std::exchange(imageInfo.pNext, &modifierInfo), - .drmFormatModifier = pDMA->modifier, - .drmFormatModifierPlaneCount = uint32_t(pDMA->n_planes), - .pPlaneLayouts = modifierPlaneLayouts, - }; - - for ( int i = 0; i < pDMA->n_planes; ++i ) - { - modifierPlaneLayouts[i].offset = pDMA->offset[i]; - modifierPlaneLayouts[i].rowPitch = pDMA->stride[i]; - } - - imageInfo.tiling = tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; - } - } - - std::vector modifiers = {}; - // TODO(JoshA): Move this code to backend for making flippable image. - if ( GetBackend()->UsesModifiers() && flags.bFlippable && g_device.supportsModifiers() && !pDMA ) - { - assert( drmFormat != DRM_FORMAT_INVALID ); - - uint64_t linear = DRM_FORMAT_MOD_LINEAR; - - const uint64_t *possibleModifiers; - size_t numPossibleModifiers; - if ( flags.bLinear ) - { - possibleModifiers = &linear; - numPossibleModifiers = 1; - } - else - { - std::span modifiers = GetBackend()->GetSupportedModifiers( drmFormat ); - assert( !modifiers.empty() ); - possibleModifiers = modifiers.data(); - numPossibleModifiers = modifiers.size(); - } - - for ( size_t i = 0; i < numPossibleModifiers; i++ ) - { - uint64_t modifier = possibleModifiers[i]; - - VkExternalImageFormatProperties externalFormatProps = { - .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, - }; - res = getModifierProps( &imageInfo, modifier, &externalFormatProps ); - if ( res == VK_ERROR_FORMAT_NOT_SUPPORTED ) - continue; - else if ( res != VK_SUCCESS ) { - vk_errorf( res, "getModifierProps failed" ); - return false; - } - - if ( !( externalFormatProps.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT ) ) - continue; - - modifiers.push_back( modifier ); - } - - assert( modifiers.size() > 0 ); - - modifierListInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT, - .pNext = std::exchange(imageInfo.pNext, &modifierListInfo), - .drmFormatModifierCount = uint32_t(modifiers.size()), - .pDrmFormatModifiers = modifiers.data(), - }; + if ( pDMA != nullptr ) { assert( drmFormat == pDMA->format ); } - externalImageCreateInfo = { - .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, - .pNext = std::exchange(imageInfo.pNext, &externalImageCreateInfo), - .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - }; + if ( g_device.supportsModifiers( ) && pDMA && + pDMA->modifier != DRM_FORMAT_MOD_INVALID ) + { + VkExternalImageFormatProperties externalImageProperties = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, + }; + + res = getModifierProps( + &imageInfo, pDMA->modifier, &externalImageProperties ); + if ( res != VK_SUCCESS && res != VK_ERROR_FORMAT_NOT_SUPPORTED ) + { + vk_errorf( res, "getModifierProps failed" ); + return false; + } + + if ( res == VK_SUCCESS && + ( externalImageProperties.externalMemoryProperties + .externalMemoryFeatures & + VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT ) ) + { + modifierInfo = { + .sType = + VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, + .pNext = std::exchange( imageInfo.pNext, &modifierInfo ), + .drmFormatModifier = pDMA->modifier, + .drmFormatModifierPlaneCount = uint32_t( pDMA->n_planes ), + .pPlaneLayouts = modifierPlaneLayouts, + }; + + for ( int i = 0; i < pDMA->n_planes; ++i ) + { + modifierPlaneLayouts[ i ].offset = pDMA->offset[ i ]; + modifierPlaneLayouts[ i ].rowPitch = pDMA->stride[ i ]; + } + + imageInfo.tiling = tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + } + } + + std::vector modifiers = {}; + // TODO(JoshA): Move this code to backend for making flippable image. + if ( GetBackend( )->UsesModifiers( ) && flags.bFlippable && + g_device.supportsModifiers( ) && !pDMA ) + { + assert( drmFormat != DRM_FORMAT_INVALID ); + + uint64_t linear = DRM_FORMAT_MOD_LINEAR; + + const uint64_t *possibleModifiers; + size_t numPossibleModifiers; + if ( flags.bLinear ) + { + possibleModifiers = &linear; + numPossibleModifiers = 1; + } + else + { + std::span modifiers = + GetBackend( )->GetSupportedModifiers( drmFormat ); + assert( !modifiers.empty( ) ); + possibleModifiers = modifiers.data( ); + numPossibleModifiers = modifiers.size( ); + } + + for ( size_t i = 0; i < numPossibleModifiers; i++ ) + { + uint64_t modifier = possibleModifiers[ i ]; + + VkExternalImageFormatProperties externalFormatProps = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, + }; + res = + getModifierProps( &imageInfo, modifier, &externalFormatProps ); + if ( res == VK_ERROR_FORMAT_NOT_SUPPORTED ) continue; + else if ( res != VK_SUCCESS ) + { + vk_errorf( res, "getModifierProps failed" ); + return false; + } + + if ( !( externalFormatProps.externalMemoryProperties + .externalMemoryFeatures & + VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT ) ) + continue; + + modifiers.push_back( modifier ); + } + + assert( modifiers.size( ) > 0 ); + + modifierListInfo = { + .sType = + VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT, + .pNext = std::exchange( imageInfo.pNext, &modifierListInfo ), + .drmFormatModifierCount = uint32_t( modifiers.size( ) ), + .pDrmFormatModifiers = modifiers.data( ), + }; + + externalImageCreateInfo = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .pNext = std::exchange( imageInfo.pNext, &externalImageCreateInfo ), + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + + imageInfo.tiling = tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + } + + if ( flags.bFlippable == true && + tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) + { + // We want to scan-out the image + wsiImageCreateInfo = { + .sType = VK_STRUCTURE_TYPE_WSI_IMAGE_CREATE_INFO_MESA, + .pNext = std::exchange( imageInfo.pNext, &wsiImageCreateInfo ), + .scanout = VK_TRUE, + }; + } + + if ( pDMA != nullptr ) + { + externalImageCreateInfo = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .pNext = std::exchange( imageInfo.pNext, &externalImageCreateInfo ), + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + } + + m_width = width; + m_height = height; + m_depth = depth; + + if ( contentWidth && contentHeight ) + { + m_contentWidth = contentWidth; + m_contentHeight = contentHeight; + } + else + { + m_contentWidth = width; + m_contentHeight = height; + } + + m_format = imageInfo.format; + + res = g_device.vk.CreateImage( + g_device.device( ), &imageInfo, nullptr, &m_vkImage ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateImage failed" ); + return false; + } + + VkMemoryRequirements memRequirements; + g_device.vk.GetImageMemoryRequirements( + g_device.device( ), m_vkImage, &memRequirements ); + + VkMemoryAllocateInfo allocInfo = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memRequirements.size, + .memoryTypeIndex = uint32_t( g_device.findMemoryType( + properties, memRequirements.memoryTypeBits ) ), + }; + + m_size = allocInfo.allocationSize; + + VkDeviceMemory memoryHandle = VK_NULL_HANDLE; + + if ( pExistingImageToReuseMemory == nullptr ) + { + // Possible pNexts + VkImportMemoryFdInfoKHR importMemoryInfo = {}; + VkExportMemoryAllocateInfo memory_export_info = {}; + VkMemoryDedicatedAllocateInfo memory_dedicated_info = {}; + struct wsi_memory_allocate_info memory_wsi_info = {}; + + if ( flags.bFlippable == true ) + { + memory_wsi_info = { + .sType = VK_STRUCTURE_TYPE_WSI_MEMORY_ALLOCATE_INFO_MESA, + .pNext = std::exchange( allocInfo.pNext, &memory_wsi_info ), + }; + } + + if ( flags.bExportable == true || pDMA != nullptr ) + { + memory_dedicated_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .pNext = + std::exchange( allocInfo.pNext, &memory_dedicated_info ), + .image = m_vkImage, + }; + } + + if ( flags.bExportable == true && pDMA == nullptr ) + { + // We'll export it to DRM + memory_export_info = { + .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, + .pNext = std::exchange( allocInfo.pNext, &memory_export_info ), + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + } + + if ( pDMA != nullptr ) + { + // TODO: multi-planar DISTINCT DMA-BUFs support (see + // vkBindImageMemory2 and VkBindImagePlaneMemoryInfo) + assert( allDMABUFsEqual( pDMA ) ); + + // Importing memory from a FD transfers ownership of the FD + int fd = dup( pDMA->fd[ 0 ] ); + if ( fd < 0 ) + { + vk_log.errorf_errno( "dup failed" ); + return false; + } + + // Memory already provided by pDMA + importMemoryInfo = { + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .pNext = std::exchange( allocInfo.pNext, &importMemoryInfo ), + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .fd = fd, + }; + } + + res = g_device.vk.AllocateMemory( + g_device.device( ), &allocInfo, nullptr, &memoryHandle ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkAllocateMemory failed" ); + return false; + } + + m_vkImageMemory = memoryHandle; + } + else + { + vk_log.infof( + "%d vs %d!", + ( int )pExistingImageToReuseMemory->m_size, + ( int )m_size ); + assert( pExistingImageToReuseMemory->m_size >= m_size ); + + memoryHandle = pExistingImageToReuseMemory->m_vkImageMemory; + m_vkImageMemory = VK_NULL_HANDLE; + } + + res = g_device.vk.BindImageMemory( + g_device.device( ), m_vkImage, memoryHandle, 0 ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkBindImageMemory failed" ); + return false; + } + + if ( flags.bMappable == true ) + { + assert( tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ); + const VkImageSubresource image_subresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + }; + VkSubresourceLayout image_layout; + g_device.vk.GetImageSubresourceLayout( + g_device.device( ), m_vkImage, &image_subresource, &image_layout ); + + m_unRowPitch = image_layout.rowPitch; + + if ( isYcbcr( ) ) + { + const VkImageSubresource lumaSubresource = { + .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT, + }; + VkSubresourceLayout lumaLayout; + g_device.vk.GetImageSubresourceLayout( + g_device.device( ), m_vkImage, &lumaSubresource, &lumaLayout ); + + m_lumaOffset = lumaLayout.offset; + m_lumaPitch = lumaLayout.rowPitch; + + const VkImageSubresource chromaSubresource = { + .aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT, + }; + VkSubresourceLayout chromaLayout; + g_device.vk.GetImageSubresourceLayout( + g_device.device( ), + m_vkImage, + &chromaSubresource, + &chromaLayout ); + + m_chromaOffset = chromaLayout.offset; + m_chromaPitch = chromaLayout.rowPitch; + } + } + + if ( flags.bExportable == true ) + { + // We assume we own the memory when doing this right now. + // We could support the import scenario as well if needed (but we + // already have a DMA-BUF in that case). + assert( pDMA == nullptr ); + + struct wlr_dmabuf_attributes dmabuf = { + .width = int( width ), + .height = int( height ), + .format = drmFormat, + }; + assert( dmabuf.format != DRM_FORMAT_INVALID ); + + // TODO: disjoint planes support + const VkMemoryGetFdInfoKHR memory_get_fd_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, + .memory = memoryHandle, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + res = g_device.vk.GetMemoryFdKHR( + g_device.device( ), &memory_get_fd_info, &dmabuf.fd[ 0 ] ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkGetMemoryFdKHR failed" ); + return false; + } + + if ( tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) + { + assert( + g_device.vk.GetImageDrmFormatModifierPropertiesEXT != nullptr ); + + VkImageDrmFormatModifierPropertiesEXT imgModifierProps = { + .sType = + VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT, + }; + + res = g_device.vk.GetImageDrmFormatModifierPropertiesEXT( + g_device.device( ), m_vkImage, &imgModifierProps ); + if ( res != VK_SUCCESS ) + { + vk_errorf( + res, "vkGetImageDrmFormatModifierPropertiesEXT failed" ); + return false; + } + dmabuf.modifier = imgModifierProps.drmFormatModifier; + + assert( DRMModifierProps.count( m_format ) > 0 ); + assert( DRMModifierProps[ m_format ].count( dmabuf.modifier ) > 0 ); + + dmabuf.n_planes = DRMModifierProps[ m_format ][ dmabuf.modifier ] + .drmFormatModifierPlaneCount; + + const VkImageAspectFlagBits planeAspects[] = { + VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT, + VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT, + VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT, + VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT, + }; + assert( dmabuf.n_planes <= 4 ); + + for ( int i = 0; i < dmabuf.n_planes; i++ ) + { + const VkImageSubresource subresource = { + .aspectMask = planeAspects[ i ], + }; + VkSubresourceLayout subresourceLayout = {}; + g_device.vk.GetImageSubresourceLayout( + g_device.device( ), + m_vkImage, + &subresource, + &subresourceLayout ); + dmabuf.offset[ i ] = subresourceLayout.offset; + dmabuf.stride[ i ] = subresourceLayout.rowPitch; + } + + // Copy the first FD to all other planes + for ( int i = 1; i < dmabuf.n_planes; i++ ) + { + dmabuf.fd[ i ] = dup( dmabuf.fd[ 0 ] ); + if ( dmabuf.fd[ i ] < 0 ) + { + vk_log.errorf_errno( "dup failed" ); + return false; + } + } + } + else + { + const VkImageSubresource subresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + }; + VkSubresourceLayout subresourceLayout = {}; + g_device.vk.GetImageSubresourceLayout( + g_device.device( ), + m_vkImage, + &subresource, + &subresourceLayout ); + + dmabuf.n_planes = 1; + dmabuf.modifier = DRM_FORMAT_MOD_INVALID; + dmabuf.offset[ 0 ] = 0; + dmabuf.stride[ 0 ] = subresourceLayout.rowPitch; + } + + m_dmabuf = dmabuf; + } - imageInfo.tiling = tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; - } + if ( flags.bFlippable == true ) + { + m_pBackendFb = GetBackend( )->ImportDmabufToBackend( &m_dmabuf ); + } - if ( flags.bFlippable == true && tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) - { - // We want to scan-out the image - wsiImageCreateInfo = { - .sType = VK_STRUCTURE_TYPE_WSI_IMAGE_CREATE_INFO_MESA, - .pNext = std::exchange(imageInfo.pNext, &wsiImageCreateInfo), - .scanout = VK_TRUE, - }; - } - - if ( pDMA != nullptr ) - { - externalImageCreateInfo = { - .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, - .pNext = std::exchange(imageInfo.pNext, &externalImageCreateInfo), - .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - }; - } - - m_width = width; - m_height = height; - m_depth = depth; - - if (contentWidth && contentHeight) - { - m_contentWidth = contentWidth; - m_contentHeight = contentHeight; - } - else - { - m_contentWidth = width; - m_contentHeight = height; - } - - m_format = imageInfo.format; - - res = g_device.vk.CreateImage(g_device.device(), &imageInfo, nullptr, &m_vkImage); - if (res != VK_SUCCESS) { - vk_errorf( res, "vkCreateImage failed" ); - return false; - } - - VkMemoryRequirements memRequirements; - g_device.vk.GetImageMemoryRequirements(g_device.device(), m_vkImage, &memRequirements); - - VkMemoryAllocateInfo allocInfo = { - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .allocationSize = memRequirements.size, - .memoryTypeIndex = uint32_t(g_device.findMemoryType(properties, memRequirements.memoryTypeBits)), - }; + bool bHasAlpha = pDMA ? DRMFormatHasAlpha( pDMA->format ) : true; - m_size = allocInfo.allocationSize; - - VkDeviceMemory memoryHandle = VK_NULL_HANDLE; - - if ( pExistingImageToReuseMemory == nullptr ) - { - // Possible pNexts - VkImportMemoryFdInfoKHR importMemoryInfo = {}; - VkExportMemoryAllocateInfo memory_export_info = {}; - VkMemoryDedicatedAllocateInfo memory_dedicated_info = {}; - struct wsi_memory_allocate_info memory_wsi_info = {}; - - if ( flags.bFlippable == true ) - { - memory_wsi_info = { - .sType = VK_STRUCTURE_TYPE_WSI_MEMORY_ALLOCATE_INFO_MESA, - .pNext = std::exchange(allocInfo.pNext, &memory_wsi_info), - }; - } - - if ( flags.bExportable == true || pDMA != nullptr ) - { - memory_dedicated_info = { - .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, - .pNext = std::exchange(allocInfo.pNext, &memory_dedicated_info), - .image = m_vkImage, - }; - } - - if ( flags.bExportable == true && pDMA == nullptr ) - { - // We'll export it to DRM - memory_export_info = { - .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, - .pNext = std::exchange(allocInfo.pNext, &memory_export_info), - .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - }; - } - - if ( pDMA != nullptr ) - { - // TODO: multi-planar DISTINCT DMA-BUFs support (see vkBindImageMemory2 - // and VkBindImagePlaneMemoryInfo) - assert( allDMABUFsEqual( pDMA ) ); - - // Importing memory from a FD transfers ownership of the FD - int fd = dup( pDMA->fd[0] ); - if ( fd < 0 ) - { - vk_log.errorf_errno( "dup failed" ); - return false; - } - - // Memory already provided by pDMA - importMemoryInfo = { - .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, - .pNext = std::exchange(allocInfo.pNext, &importMemoryInfo), - .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - .fd = fd, - }; - } - - res = g_device.vk.AllocateMemory( g_device.device(), &allocInfo, nullptr, &memoryHandle ); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkAllocateMemory failed" ); - return false; - } - - m_vkImageMemory = memoryHandle; - } - else - { - vk_log.infof("%d vs %d!", (int)pExistingImageToReuseMemory->m_size, (int)m_size); - assert(pExistingImageToReuseMemory->m_size >= m_size); - - memoryHandle = pExistingImageToReuseMemory->m_vkImageMemory; - m_vkImageMemory = VK_NULL_HANDLE; - } - - res = g_device.vk.BindImageMemory( g_device.device(), m_vkImage, memoryHandle, 0 ); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkBindImageMemory failed" ); - return false; - } - - if ( flags.bMappable == true ) - { - assert( tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ); - const VkImageSubresource image_subresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - }; - VkSubresourceLayout image_layout; - g_device.vk.GetImageSubresourceLayout(g_device.device(), m_vkImage, &image_subresource, &image_layout); - - m_unRowPitch = image_layout.rowPitch; - - if (isYcbcr()) - { - const VkImageSubresource lumaSubresource = { - .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT, - }; - VkSubresourceLayout lumaLayout; - g_device.vk.GetImageSubresourceLayout(g_device.device(), m_vkImage, &lumaSubresource, &lumaLayout); - - m_lumaOffset = lumaLayout.offset; - m_lumaPitch = lumaLayout.rowPitch; - - const VkImageSubresource chromaSubresource = { - .aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT, - }; - VkSubresourceLayout chromaLayout; - g_device.vk.GetImageSubresourceLayout(g_device.device(), m_vkImage, &chromaSubresource, &chromaLayout); - - m_chromaOffset = chromaLayout.offset; - m_chromaPitch = chromaLayout.rowPitch; - } - } - - if ( flags.bExportable == true ) - { - // We assume we own the memory when doing this right now. - // We could support the import scenario as well if needed (but we - // already have a DMA-BUF in that case). - assert( pDMA == nullptr ); - - struct wlr_dmabuf_attributes dmabuf = { - .width = int(width), - .height = int(height), - .format = drmFormat, - }; - assert( dmabuf.format != DRM_FORMAT_INVALID ); + if ( !bHasAlpha ) + { + // not compatible with with swizzles + assert( flags.bStorage == false ); + } - // TODO: disjoint planes support - const VkMemoryGetFdInfoKHR memory_get_fd_info = { - .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, - .memory = memoryHandle, - .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - }; - res = g_device.vk.GetMemoryFdKHR(g_device.device(), &memory_get_fd_info, &dmabuf.fd[0]); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkGetMemoryFdKHR failed" ); - return false; - } - - if ( tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) - { - assert( g_device.vk.GetImageDrmFormatModifierPropertiesEXT != nullptr ); - - VkImageDrmFormatModifierPropertiesEXT imgModifierProps = { - .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT, - }; - - res = g_device.vk.GetImageDrmFormatModifierPropertiesEXT( g_device.device(), m_vkImage, &imgModifierProps ); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkGetImageDrmFormatModifierPropertiesEXT failed" ); - return false; - } - dmabuf.modifier = imgModifierProps.drmFormatModifier; - - assert( DRMModifierProps.count( m_format ) > 0); - assert( DRMModifierProps[ m_format ].count( dmabuf.modifier ) > 0); - - dmabuf.n_planes = DRMModifierProps[ m_format ][ dmabuf.modifier ].drmFormatModifierPlaneCount; - - const VkImageAspectFlagBits planeAspects[] = { - VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT, - VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT, - VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT, - VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT, - }; - assert( dmabuf.n_planes <= 4 ); - - for ( int i = 0; i < dmabuf.n_planes; i++ ) - { - const VkImageSubresource subresource = { - .aspectMask = planeAspects[i], - }; - VkSubresourceLayout subresourceLayout = {}; - g_device.vk.GetImageSubresourceLayout( g_device.device(), m_vkImage, &subresource, &subresourceLayout ); - dmabuf.offset[i] = subresourceLayout.offset; - dmabuf.stride[i] = subresourceLayout.rowPitch; - } - - // Copy the first FD to all other planes - for ( int i = 1; i < dmabuf.n_planes; i++ ) - { - dmabuf.fd[i] = dup( dmabuf.fd[0] ); - if ( dmabuf.fd[i] < 0 ) { - vk_log.errorf_errno( "dup failed" ); - return false; - } - } - } - else - { - const VkImageSubresource subresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - }; - VkSubresourceLayout subresourceLayout = {}; - g_device.vk.GetImageSubresourceLayout( g_device.device(), m_vkImage, &subresource, &subresourceLayout ); - - dmabuf.n_planes = 1; - dmabuf.modifier = DRM_FORMAT_MOD_INVALID; - dmabuf.offset[0] = 0; - dmabuf.stride[0] = subresourceLayout.rowPitch; - } - - m_dmabuf = dmabuf; - } - - if ( flags.bFlippable == true ) - { - m_pBackendFb = GetBackend()->ImportDmabufToBackend( &m_dmabuf ); - } - - bool bHasAlpha = pDMA ? DRMFormatHasAlpha( pDMA->format ) : true; - - if (!bHasAlpha ) - { - // not compatible with with swizzles - assert ( flags.bStorage == false ); - } - - if ( flags.bStorage || flags.bSampled || flags.bColorAttachment ) - { - VkImageViewCreateInfo createInfo = { + if ( flags.bStorage || flags.bSampled || flags.bColorAttachment ) + { + VkImageViewCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = m_vkImage, .viewType = VulkanImageTypeToViewType(flags.imageType), @@ -2498,91 +2897,102 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin }, }; - res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_srgbView); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkCreateImageView failed" ); - return false; - } - - if ( flags.bSampled ) - { - VkImageViewUsageCreateInfo viewUsageInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, - .usage = usage & ~VK_IMAGE_USAGE_STORAGE_BIT, - }; - createInfo.pNext = &viewUsageInfo; - createInfo.format = DRMFormatToVulkan(drmFormat, true); - res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_linearView); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkCreateImageView failed" ); - return false; - } - } - - - if ( isYcbcr() ) - { - createInfo.pNext = NULL; - createInfo.format = VK_FORMAT_R8_UNORM; - - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT; - res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_lumaView); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkCreateImageView failed" ); - return false; - } - - createInfo.pNext = NULL; - createInfo.format = VK_FORMAT_R8G8_UNORM; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; - res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_chromaView); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkCreateImageView failed" ); - return false; - } - - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - } - } - - if ( flags.bMappable ) - { - if (pExistingImageToReuseMemory) - { - m_pMappedData = pExistingImageToReuseMemory->m_pMappedData; - } - else - { - void *pData = nullptr; - res = g_device.vk.MapMemory( g_device.device(), memoryHandle, 0, VK_WHOLE_SIZE, 0, &pData ); - if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkMapMemory failed" ); - return false; - } - m_pMappedData = (uint8_t*)pData; - } - } - - m_bInitialized = true; - - return true; + res = g_device.vk.CreateImageView( + g_device.device( ), &createInfo, nullptr, &m_srgbView ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateImageView failed" ); + return false; + } + + if ( flags.bSampled ) + { + VkImageViewUsageCreateInfo viewUsageInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, + .usage = usage & ~VK_IMAGE_USAGE_STORAGE_BIT, + }; + createInfo.pNext = &viewUsageInfo; + createInfo.format = DRMFormatToVulkan( drmFormat, true ); + res = g_device.vk.CreateImageView( + g_device.device( ), &createInfo, nullptr, &m_linearView ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateImageView failed" ); + return false; + } + } + + if ( isYcbcr( ) ) + { + createInfo.pNext = NULL; + createInfo.format = VK_FORMAT_R8_UNORM; + + createInfo.subresourceRange.aspectMask = + VK_IMAGE_ASPECT_PLANE_0_BIT; + res = g_device.vk.CreateImageView( + g_device.device( ), &createInfo, nullptr, &m_lumaView ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateImageView failed" ); + return false; + } + + createInfo.pNext = NULL; + createInfo.format = VK_FORMAT_R8G8_UNORM; + createInfo.subresourceRange.aspectMask = + VK_IMAGE_ASPECT_PLANE_1_BIT; + res = g_device.vk.CreateImageView( + g_device.device( ), &createInfo, nullptr, &m_chromaView ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateImageView failed" ); + return false; + } + + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + } + + if ( flags.bMappable ) + { + if ( pExistingImageToReuseMemory ) + { + m_pMappedData = pExistingImageToReuseMemory->m_pMappedData; + } + else + { + void *pData = nullptr; + res = g_device.vk.MapMemory( + g_device.device( ), memoryHandle, 0, VK_WHOLE_SIZE, 0, &pData ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkMapMemory failed" ); + return false; + } + m_pMappedData = ( uint8_t * )pData; + } + } + + m_bInitialized = true; + + return true; } -bool CVulkanTexture::BInitFromSwapchain( VkImage image, uint32_t width, uint32_t height, VkFormat format ) +bool CVulkanTexture::BInitFromSwapchain( + VkImage image, uint32_t width, uint32_t height, VkFormat format ) { - m_drmFormat = VulkanFormatToDRM( format ); - m_vkImage = image; - m_vkImageMemory = VK_NULL_HANDLE; - m_width = width; - m_height = height; - m_depth = 1; - m_format = format; - m_contentWidth = width; - m_contentHeight = height; - m_bOutputImage = true; - - VkImageViewCreateInfo createInfo = { + m_drmFormat = VulkanFormatToDRM( format ); + m_vkImage = image; + m_vkImageMemory = VK_NULL_HANDLE; + m_width = width; + m_height = height; + m_depth = 1; + m_format = format; + m_contentWidth = width; + m_contentHeight = height; + m_bOutputImage = true; + + VkImageViewCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = image, .viewType = VK_IMAGE_VIEW_TYPE_2D, @@ -2600,590 +3010,663 @@ bool CVulkanTexture::BInitFromSwapchain( VkImage image, uint32_t width, uint32_t }, }; - VkResult res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_srgbView); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkCreateImageView failed" ); - return false; - } + VkResult res = g_device.vk.CreateImageView( + g_device.device( ), &createInfo, nullptr, &m_srgbView ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateImageView failed" ); + return false; + } - VkImageViewUsageCreateInfo viewUsageInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, - .usage = VK_IMAGE_USAGE_SAMPLED_BIT, - }; + VkImageViewUsageCreateInfo viewUsageInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT, + }; - createInfo.pNext = &viewUsageInfo; - createInfo.format = ToSrgbVulkanFormat( format ); + createInfo.pNext = &viewUsageInfo; + createInfo.format = ToSrgbVulkanFormat( format ); - res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_linearView); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkCreateImageView failed" ); - return false; - } + res = g_device.vk.CreateImageView( + g_device.device( ), &createInfo, nullptr, &m_linearView ); + if ( res != VK_SUCCESS ) + { + vk_errorf( res, "vkCreateImageView failed" ); + return false; + } - m_bInitialized = true; + m_bInitialized = true; - return true; + return true; } -uint32_t CVulkanTexture::IncRef() +uint32_t CVulkanTexture::IncRef( ) { - uint32_t uRefCount = gamescope::RcObject::IncRef(); - if ( m_pBackendFb && !uRefCount ) - { - m_pBackendFb->IncRef(); - } - return uRefCount; + uint32_t uRefCount = gamescope::RcObject::IncRef( ); + if ( m_pBackendFb && !uRefCount ) { m_pBackendFb->IncRef( ); } + return uRefCount; } -uint32_t CVulkanTexture::DecRef() +uint32_t CVulkanTexture::DecRef( ) { - // Need to pull it out as we could be destroyed in DecRef. - gamescope::IBackendFb *pBackendFb = m_pBackendFb.get(); - - uint32_t uRefCount = gamescope::RcObject::DecRef(); - if ( pBackendFb && !uRefCount ) - { - pBackendFb->DecRef(); - } - return uRefCount; + // Need to pull it out as we could be destroyed in DecRef. + gamescope::IBackendFb *pBackendFb = m_pBackendFb.get( ); + + uint32_t uRefCount = gamescope::RcObject::DecRef( ); + if ( pBackendFb && !uRefCount ) { pBackendFb->DecRef( ); } + return uRefCount; } -bool CVulkanTexture::IsInUse() +bool CVulkanTexture::IsInUse( ) { - if ( m_pBackendFb && m_pBackendFb->GetRefCount() != 0 ) - return true; + if ( m_pBackendFb && m_pBackendFb->GetRefCount( ) != 0 ) return true; - return GetRefCount() != 0; + return GetRefCount( ) != 0; } -CVulkanTexture::CVulkanTexture( void ) -{ -} +CVulkanTexture::CVulkanTexture( void ) {} CVulkanTexture::~CVulkanTexture( void ) { - wlr_dmabuf_attributes_finish( &m_dmabuf ); - - if ( m_pMappedData != nullptr && m_vkImageMemory ) - { - g_device.vk.UnmapMemory( g_device.device(), m_vkImageMemory ); - m_pMappedData = nullptr; - } - - if ( m_srgbView != VK_NULL_HANDLE ) - { - g_device.vk.DestroyImageView( g_device.device(), m_srgbView, nullptr ); - m_srgbView = VK_NULL_HANDLE; - } - - if ( m_linearView != VK_NULL_HANDLE ) - { - g_device.vk.DestroyImageView( g_device.device(), m_linearView, nullptr ); - m_linearView = VK_NULL_HANDLE; - } - - if ( m_pBackendFb != nullptr ) - m_pBackendFb = nullptr; - - if ( m_vkImageMemory != VK_NULL_HANDLE ) - { - if ( m_vkImage != VK_NULL_HANDLE ) - { - g_device.vk.DestroyImage( g_device.device(), m_vkImage, nullptr ); - m_vkImage = VK_NULL_HANDLE; - } - - g_device.vk.FreeMemory( g_device.device(), m_vkImageMemory, nullptr ); - m_vkImageMemory = VK_NULL_HANDLE; - } - - m_bInitialized = false; -} - -int CVulkanTexture::memoryFence() -{ - const VkMemoryGetFdInfoKHR memory_get_fd_info = { - .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, - .memory = m_vkImageMemory, - .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - }; - int fence = -1; - VkResult res = g_device.vk.GetMemoryFdKHR(g_device.device(), &memory_get_fd_info, &fence); - if ( res != VK_SUCCESS ) { - fprintf( stderr, "vkGetMemoryFdKHR failed\n" ); - } + wlr_dmabuf_attributes_finish( &m_dmabuf ); - return fence; -} + if ( m_pMappedData != nullptr && m_vkImageMemory ) + { + g_device.vk.UnmapMemory( g_device.device( ), m_vkImageMemory ); + m_pMappedData = nullptr; + } -static bool is_image_format_modifier_supported(VkFormat format, uint32_t drmFormat, uint64_t modifier) -{ - VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .format = format, - .type = VK_IMAGE_TYPE_2D, - .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, - .usage = VK_IMAGE_USAGE_SAMPLED_BIT, - }; - - std::array formats = { - DRMFormatToVulkan(drmFormat, false), - DRMFormatToVulkan(drmFormat, true), - }; - - VkImageFormatListCreateInfo formatList = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, - .viewFormatCount = (uint32_t)formats.size(), - .pViewFormats = formats.data(), - }; - - if ( formats[0] != formats[1] ) - { - formatList.pNext = std::exchange(imageFormatInfo.pNext, - &formatList); - imageFormatInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } - - VkPhysicalDeviceImageDrmFormatModifierInfoEXT modifierInfo = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, - .pNext = nullptr, - .drmFormatModifier = modifier, - }; - - modifierInfo.pNext = std::exchange(imageFormatInfo.pNext, &modifierInfo); - - VkImageFormatProperties2 imageFormatProps = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - }; - - VkResult res = g_device.vk.GetPhysicalDeviceImageFormatProperties2( g_device.physDev(), &imageFormatInfo, &imageFormatProps ); - return res == VK_SUCCESS; -} + if ( m_srgbView != VK_NULL_HANDLE ) + { + g_device.vk.DestroyImageView( g_device.device( ), m_srgbView, nullptr ); + m_srgbView = VK_NULL_HANDLE; + } -bool vulkan_init_format(VkFormat format, uint32_t drmFormat) -{ - // First, check whether the Vulkan format is supported - VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, - .format = format, - .type = VK_IMAGE_TYPE_2D, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_SAMPLED_BIT, - }; + if ( m_linearView != VK_NULL_HANDLE ) + { + g_device.vk.DestroyImageView( + g_device.device( ), m_linearView, nullptr ); + m_linearView = VK_NULL_HANDLE; + } - std::array formats = { - DRMFormatToVulkan(drmFormat, false), - DRMFormatToVulkan(drmFormat, true), - }; + if ( m_pBackendFb != nullptr ) m_pBackendFb = nullptr; - VkImageFormatListCreateInfo formatList = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, - .viewFormatCount = (uint32_t)formats.size(), - .pViewFormats = formats.data(), - }; + if ( m_vkImageMemory != VK_NULL_HANDLE ) + { + if ( m_vkImage != VK_NULL_HANDLE ) + { + g_device.vk.DestroyImage( g_device.device( ), m_vkImage, nullptr ); + m_vkImage = VK_NULL_HANDLE; + } + + g_device.vk.FreeMemory( g_device.device( ), m_vkImageMemory, nullptr ); + m_vkImageMemory = VK_NULL_HANDLE; + } - if ( formats[0] != formats[1] ) - { - formatList.pNext = std::exchange(imageFormatInfo.pNext, - &formatList); - imageFormatInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; - } + m_bInitialized = false; +} +int CVulkanTexture::memoryFence( ) +{ + const VkMemoryGetFdInfoKHR memory_get_fd_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, + .memory = m_vkImageMemory, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + int fence = -1; + VkResult res = g_device.vk.GetMemoryFdKHR( + g_device.device( ), &memory_get_fd_info, &fence ); + if ( res != VK_SUCCESS ) { fprintf( stderr, "vkGetMemoryFdKHR failed\n" ); } + + return fence; +} - VkImageFormatProperties2 imageFormatProps = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - }; +static bool is_image_format_modifier_supported( + VkFormat format, uint32_t drmFormat, uint64_t modifier ) +{ + VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + .format = format, + .type = VK_IMAGE_TYPE_2D, + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT, + }; + + std::array formats = { + DRMFormatToVulkan( drmFormat, false ), + DRMFormatToVulkan( drmFormat, true ), + }; + + VkImageFormatListCreateInfo formatList = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, + .viewFormatCount = ( uint32_t )formats.size( ), + .pViewFormats = formats.data( ), + }; + + if ( formats[ 0 ] != formats[ 1 ] ) + { + formatList.pNext = std::exchange( imageFormatInfo.pNext, &formatList ); + imageFormatInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + } - VkResult res = g_device.vk.GetPhysicalDeviceImageFormatProperties2( g_device.physDev(), &imageFormatInfo, &imageFormatProps ); - if ( res == VK_ERROR_FORMAT_NOT_SUPPORTED ) - { - return false; - } - else if ( res != VK_SUCCESS ) - { - vk_errorf( res, "vkGetPhysicalDeviceImageFormatProperties2 failed for DRM format 0x%" PRIX32, drmFormat ); - return false; - } - - wlr_drm_format_set_add( &sampledShmFormats, drmFormat, DRM_FORMAT_MOD_LINEAR ); - - if ( g_device.supportsModifiers() ) - { - // Then, collect the list of modifiers supported for sampled usage - VkDrmFormatModifierPropertiesListEXT modifierPropList = { - .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT, - }; - VkFormatProperties2 formatProps = { - .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, - .pNext = &modifierPropList, - }; + VkPhysicalDeviceImageDrmFormatModifierInfoEXT modifierInfo = { + .sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, + .pNext = nullptr, + .drmFormatModifier = modifier, + }; - g_device.vk.GetPhysicalDeviceFormatProperties2( g_device.physDev(), format, &formatProps ); + modifierInfo.pNext = std::exchange( imageFormatInfo.pNext, &modifierInfo ); - if ( modifierPropList.drmFormatModifierCount == 0 ) - { - vk_errorf( res, "vkGetPhysicalDeviceFormatProperties2 returned zero modifiers for DRM format 0x%" PRIX32, drmFormat ); - return false; - } + VkImageFormatProperties2 imageFormatProps = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + }; - std::vector modifierProps(modifierPropList.drmFormatModifierCount); - modifierPropList.pDrmFormatModifierProperties = modifierProps.data(); - g_device.vk.GetPhysicalDeviceFormatProperties2( g_device.physDev(), format, &formatProps ); + VkResult res = g_device.vk.GetPhysicalDeviceImageFormatProperties2( + g_device.physDev( ), &imageFormatInfo, &imageFormatProps ); + return res == VK_SUCCESS; +} - std::map< uint64_t, VkDrmFormatModifierPropertiesEXT > map = {}; +bool vulkan_init_format( VkFormat format, uint32_t drmFormat ) +{ + // First, check whether the Vulkan format is supported + VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, + .format = format, + .type = VK_IMAGE_TYPE_2D, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT, + }; + + std::array formats = { + DRMFormatToVulkan( drmFormat, false ), + DRMFormatToVulkan( drmFormat, true ), + }; + + VkImageFormatListCreateInfo formatList = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, + .viewFormatCount = ( uint32_t )formats.size( ), + .pViewFormats = formats.data( ), + }; + + if ( formats[ 0 ] != formats[ 1 ] ) + { + formatList.pNext = std::exchange( imageFormatInfo.pNext, &formatList ); + imageFormatInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + } - for ( size_t j = 0; j < modifierProps.size(); j++ ) - { - map[ modifierProps[j].drmFormatModifier ] = modifierProps[j]; + VkImageFormatProperties2 imageFormatProps = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + }; - uint64_t modifier = modifierProps[j].drmFormatModifier; + VkResult res = g_device.vk.GetPhysicalDeviceImageFormatProperties2( + g_device.physDev( ), &imageFormatInfo, &imageFormatProps ); + if ( res == VK_ERROR_FORMAT_NOT_SUPPORTED ) { return false; } + else if ( res != VK_SUCCESS ) + { + vk_errorf( + res, + "vkGetPhysicalDeviceImageFormatProperties2 failed for DRM format " + "0x%" PRIX32, + drmFormat ); + return false; + } - if ( !is_image_format_modifier_supported( format, drmFormat, modifier ) ) - continue; + wlr_drm_format_set_add( + &sampledShmFormats, drmFormat, DRM_FORMAT_MOD_LINEAR ); - if ( ( modifierProps[j].drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT ) == 0 ) - { - continue; - } + if ( g_device.supportsModifiers( ) ) + { + // Then, collect the list of modifiers supported for sampled usage + VkDrmFormatModifierPropertiesListEXT modifierPropList = { + .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT, + }; + VkFormatProperties2 formatProps = { + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, + .pNext = &modifierPropList, + }; + + g_device.vk.GetPhysicalDeviceFormatProperties2( + g_device.physDev( ), format, &formatProps ); + + if ( modifierPropList.drmFormatModifierCount == 0 ) + { + vk_errorf( + res, + "vkGetPhysicalDeviceFormatProperties2 returned zero modifiers " + "for DRM format 0x%" PRIX32, + drmFormat ); + return false; + } + + std::vector modifierProps( + modifierPropList.drmFormatModifierCount ); + modifierPropList.pDrmFormatModifierProperties = modifierProps.data( ); + g_device.vk.GetPhysicalDeviceFormatProperties2( + g_device.physDev( ), format, &formatProps ); + + std::map map = {}; + + for ( size_t j = 0; j < modifierProps.size( ); j++ ) + { + map[ modifierProps[ j ].drmFormatModifier ] = modifierProps[ j ]; + + uint64_t modifier = modifierProps[ j ].drmFormatModifier; + + if ( !is_image_format_modifier_supported( + format, drmFormat, modifier ) ) + continue; + + if ( ( modifierProps[ j ].drmFormatModifierTilingFeatures & + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT ) == 0 ) + { + continue; + } + + // The deferred backend exposes all sample-able formats as supported + // modifiers. + if ( !g_bAllowDeferredBackend ) + { + if ( GetBackend( )->UsesModifiers( ) && + !gamescope::Algorithm::Contains( + GetBackend( )->GetSupportedModifiers( drmFormat ), + modifier ) ) + continue; + } + + wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, modifier ); + s_SampledModifierFormats[ drmFormat ].emplace_back( modifier ); + } + + DRMModifierProps[ format ] = map; + return true; + } + else + { + if ( GetBackend( )->UsesModifiers( ) && + !GetBackend( )->SupportsInvalidModifier( drmFormat ) ) + return false; - // The deferred backend exposes all sample-able formats as supported modifiers. - if ( !g_bAllowDeferredBackend ) - { - if ( GetBackend()->UsesModifiers() && !gamescope::Algorithm::Contains( GetBackend()->GetSupportedModifiers( drmFormat ), modifier ) ) - continue; - } + wlr_drm_format_set_add( + &sampledDRMFormats, drmFormat, DRM_FORMAT_MOD_INVALID ); + return false; + } +} - wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, modifier ); - s_SampledModifierFormats[ drmFormat ].emplace_back( modifier ); - } +bool vulkan_init_formats( ) +{ + for ( size_t i = 0; s_DRMVKFormatTable[ i ].DRMFormat != DRM_FORMAT_INVALID; + i++ ) + { + if ( s_DRMVKFormatTable[ i ].internal ) continue; - DRMModifierProps[ format ] = map; - return true; - } - else - { - if ( GetBackend()->UsesModifiers() && !GetBackend()->SupportsInvalidModifier( drmFormat ) ) - return false; + VkFormat format = s_DRMVKFormatTable[ i ].vkFormat; + VkFormat srgbFormat = s_DRMVKFormatTable[ i ].vkFormatSrgb; + uint32_t drmFormat = s_DRMVKFormatTable[ i ].DRMFormat; - wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, DRM_FORMAT_MOD_INVALID ); - return false; - } -} + vulkan_init_format( format, drmFormat ); + if ( format != srgbFormat ) vulkan_init_format( srgbFormat, drmFormat ); + } -bool vulkan_init_formats() -{ - for ( size_t i = 0; s_DRMVKFormatTable[i].DRMFormat != DRM_FORMAT_INVALID; i++ ) - { - if (s_DRMVKFormatTable[i].internal) - continue; - - VkFormat format = s_DRMVKFormatTable[i].vkFormat; - VkFormat srgbFormat = s_DRMVKFormatTable[i].vkFormatSrgb; - uint32_t drmFormat = s_DRMVKFormatTable[i].DRMFormat; - - vulkan_init_format(format, drmFormat); - if (format != srgbFormat) - vulkan_init_format(srgbFormat, drmFormat); - } - - vk_log.infof( "supported DRM formats for sampling usage:" ); - for ( size_t i = 0; i < sampledDRMFormats.len; i++ ) - { - uint32_t fmt = sampledDRMFormats.formats[ i ].format; + vk_log.infof( "supported DRM formats for sampling usage:" ); + for ( size_t i = 0; i < sampledDRMFormats.len; i++ ) + { + uint32_t fmt = sampledDRMFormats.formats[ i ].format; #if HAVE_DRM - char *name = drmGetFormatName(fmt); - vk_log.infof( " %s (0x%" PRIX32 ")", name, fmt ); - free(name); + char *name = drmGetFormatName( fmt ); + vk_log.infof( " %s (0x%" PRIX32 ")", name, fmt ); + free( name ); #endif - } + } - return true; + return true; } bool acquire_next_image( void ) { - VkResult res = g_device.vk.AcquireNextImageKHR( g_device.device(), g_output.swapChain, UINT64_MAX, VK_NULL_HANDLE, g_output.acquireFence, &g_output.nOutImage ); - if ( res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR ) - return false; - if ( g_device.vk.WaitForFences( g_device.device(), 1, &g_output.acquireFence, false, UINT64_MAX ) != VK_SUCCESS ) - return false; - return g_device.vk.ResetFences( g_device.device(), 1, &g_output.acquireFence ) == VK_SUCCESS; + VkResult res = g_device.vk.AcquireNextImageKHR( + g_device.device( ), + g_output.swapChain, + UINT64_MAX, + VK_NULL_HANDLE, + g_output.acquireFence, + &g_output.nOutImage ); + if ( res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR ) return false; + if ( g_device.vk.WaitForFences( + g_device.device( ), + 1, + &g_output.acquireFence, + false, + UINT64_MAX ) != VK_SUCCESS ) + return false; + return g_device.vk.ResetFences( + g_device.device( ), 1, &g_output.acquireFence ) == VK_SUCCESS; } - -static std::atomic g_currentPresentWaitId = {0u}; -static std::mutex present_wait_lock; +static std::atomic g_currentPresentWaitId = { 0u }; +static std::mutex present_wait_lock; extern void mangoapp_output_update( uint64_t vblanktime ); static void present_wait_thread_func( void ) { - uint64_t present_wait_id = 0; - - while (true) - { - g_currentPresentWaitId.wait(present_wait_id); - - // Lock to make sure swapchain destruction is waited on and that - // it's for this swapchain. - { - std::unique_lock lock(present_wait_lock); - present_wait_id = g_currentPresentWaitId.load(); - - if (present_wait_id != 0) - { - g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu ); - uint64_t vblanktime = get_time_in_nanos(); - GetVBlankTimer().MarkVBlank( vblanktime, true ); - mangoapp_output_update( vblanktime ); - } - } - } + uint64_t present_wait_id = 0; + + while ( true ) + { + g_currentPresentWaitId.wait( present_wait_id ); + + // Lock to make sure swapchain destruction is waited on and that + // it's for this swapchain. + { + std::unique_lock lock( present_wait_lock ); + present_wait_id = g_currentPresentWaitId.load( ); + + if ( present_wait_id != 0 ) + { + g_device.vk.WaitForPresentKHR( + g_device.device( ), + g_output.swapChain, + present_wait_id, + 1'000'000'000lu ); + uint64_t vblanktime = get_time_in_nanos( ); + GetVBlankTimer( ).MarkVBlank( vblanktime, true ); + mangoapp_output_update( vblanktime ); + } + } + } } void vulkan_update_swapchain_hdr_metadata( VulkanOutput_t *pOutput ) { - if (!g_output.swapchainHDRMetadata) - return; - - if ( !g_device.vk.SetHdrMetadataEXT ) - { - static bool s_bWarned = false; - if (!s_bWarned) - { - vk_log.errorf("Unable to forward HDR metadata with Vulkan as vkSetMetadataEXT is not supported."); - s_bWarned = true; - } - return; - } - - const hdr_metadata_infoframe &infoframe = g_output.swapchainHDRMetadata->View().hdmi_metadata_type1; - VkHdrMetadataEXT metadata = - { - .sType = VK_STRUCTURE_TYPE_HDR_METADATA_EXT, - .displayPrimaryRed = VkXYColorEXT { color_xy_from_u16(infoframe.display_primaries[0].x), color_xy_from_u16(infoframe.display_primaries[0].y) }, - .displayPrimaryGreen = VkXYColorEXT { color_xy_from_u16(infoframe.display_primaries[1].x), color_xy_from_u16(infoframe.display_primaries[1].y), }, - .displayPrimaryBlue = VkXYColorEXT { color_xy_from_u16(infoframe.display_primaries[2].x), color_xy_from_u16(infoframe.display_primaries[2].y), }, - .whitePoint = VkXYColorEXT { color_xy_from_u16(infoframe.white_point.x), color_xy_from_u16(infoframe.white_point.y), }, - .maxLuminance = nits_from_u16(infoframe.max_display_mastering_luminance), - .minLuminance = nits_from_u16_dark(infoframe.min_display_mastering_luminance), - .maxContentLightLevel = nits_from_u16(infoframe.max_cll), - .maxFrameAverageLightLevel = nits_from_u16(infoframe.max_fall), - }; - g_device.vk.SetHdrMetadataEXT(g_device.device(), 1, &g_output.swapChain, &metadata); + if ( !g_output.swapchainHDRMetadata ) return; + + if ( !g_device.vk.SetHdrMetadataEXT ) + { + static bool s_bWarned = false; + if ( !s_bWarned ) + { + vk_log.errorf( + "Unable to forward HDR metadata with Vulkan as " + "vkSetMetadataEXT is not supported." ); + s_bWarned = true; + } + return; + } + + const hdr_metadata_infoframe &infoframe = + g_output.swapchainHDRMetadata->View( ) + .hdmi_metadata_type1; + VkHdrMetadataEXT metadata = { + .sType = VK_STRUCTURE_TYPE_HDR_METADATA_EXT, + .displayPrimaryRed = + VkXYColorEXT{ + color_xy_from_u16( infoframe.display_primaries[ 0 ].x ), + color_xy_from_u16( infoframe.display_primaries[ 0 ].y ) }, + .displayPrimaryGreen = + VkXYColorEXT{ + color_xy_from_u16( infoframe.display_primaries[ 1 ].x ), + color_xy_from_u16( infoframe.display_primaries[ 1 ].y ), + }, + .displayPrimaryBlue = + VkXYColorEXT{ + color_xy_from_u16( infoframe.display_primaries[ 2 ].x ), + color_xy_from_u16( infoframe.display_primaries[ 2 ].y ), + }, + .whitePoint = + VkXYColorEXT{ + color_xy_from_u16( infoframe.white_point.x ), + color_xy_from_u16( infoframe.white_point.y ), + }, + .maxLuminance = + nits_from_u16( infoframe.max_display_mastering_luminance ), + .minLuminance = + nits_from_u16_dark( infoframe.min_display_mastering_luminance ), + .maxContentLightLevel = nits_from_u16( infoframe.max_cll ), + .maxFrameAverageLightLevel = nits_from_u16( infoframe.max_fall ), + }; + g_device.vk.SetHdrMetadataEXT( + g_device.device( ), 1, &g_output.swapChain, &metadata ); } void vulkan_present_to_window( void ) { - static uint64_t s_lastPresentId = 0; - - uint64_t presentId = ++s_lastPresentId; - - auto feedback = steamcompmgr_get_base_layer_swapchain_feedback(); - if (feedback && feedback->hdr_metadata_blob) - { - if ( feedback->hdr_metadata_blob != g_output.swapchainHDRMetadata ) - { - g_output.swapchainHDRMetadata = feedback->hdr_metadata_blob; - vulkan_update_swapchain_hdr_metadata( &g_output ); - } - } - else if ( g_output.swapchainHDRMetadata != nullptr ) - { - // Only way to clear hdr metadata for a swapchain in Vulkan - // is to recreate the swapchain. - g_output.swapchainHDRMetadata = nullptr; - vulkan_remake_swapchain(); - } - - - VkPresentIdKHR presentIdInfo = { - .sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR, - .swapchainCount = 1, - .pPresentIds = &presentId, - }; - - VkPresentInfoKHR presentInfo = { - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .pNext = &presentIdInfo, - .swapchainCount = 1, - .pSwapchains = &g_output.swapChain, - .pImageIndices = &g_output.nOutImage, - }; + static uint64_t s_lastPresentId = 0; - if ( g_device.vk.QueuePresentKHR( g_device.queue(), &presentInfo ) == VK_SUCCESS ) - { - g_currentPresentWaitId = presentId; - g_currentPresentWaitId.notify_all(); - } - else - vulkan_remake_swapchain(); + uint64_t presentId = ++s_lastPresentId; - while ( !acquire_next_image() ) - vulkan_remake_swapchain(); -} - -gamescope::Rc vulkan_create_1d_lut(uint32_t size) -{ - CVulkanTexture::createFlags flags; - flags.bSampled = true; - flags.bTransferDst = true; - flags.imageType = VK_IMAGE_TYPE_1D; + auto feedback = steamcompmgr_get_base_layer_swapchain_feedback( ); + if ( feedback && feedback->hdr_metadata_blob ) + { + if ( feedback->hdr_metadata_blob != g_output.swapchainHDRMetadata ) + { + g_output.swapchainHDRMetadata = feedback->hdr_metadata_blob; + vulkan_update_swapchain_hdr_metadata( &g_output ); + } + } + else if ( g_output.swapchainHDRMetadata != nullptr ) + { + // Only way to clear hdr metadata for a swapchain in Vulkan + // is to recreate the swapchain. + g_output.swapchainHDRMetadata = nullptr; + vulkan_remake_swapchain( ); + } - auto texture = new CVulkanTexture(); - auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM ); - bool bRes = texture->BInit( size, 1u, 1u, drmFormat, flags ); - assert( bRes ); + VkPresentIdKHR presentIdInfo = { + .sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR, + .swapchainCount = 1, + .pPresentIds = &presentId, + }; + + VkPresentInfoKHR presentInfo = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = &presentIdInfo, + .swapchainCount = 1, + .pSwapchains = &g_output.swapChain, + .pImageIndices = &g_output.nOutImage, + }; + + if ( g_device.vk.QueuePresentKHR( g_device.queue( ), &presentInfo ) == + VK_SUCCESS ) + { + g_currentPresentWaitId = presentId; + g_currentPresentWaitId.notify_all( ); + } + else + vulkan_remake_swapchain( ); - return texture; + while ( !acquire_next_image( ) ) + vulkan_remake_swapchain( ); } -gamescope::Rc vulkan_create_3d_lut(uint32_t width, uint32_t height, uint32_t depth) +gamescope::Rc vulkan_create_1d_lut( uint32_t size ) { - CVulkanTexture::createFlags flags; - flags.bSampled = true; - flags.bTransferDst = true; - flags.imageType = VK_IMAGE_TYPE_3D; + CVulkanTexture::createFlags flags; + flags.bSampled = true; + flags.bTransferDst = true; + flags.imageType = VK_IMAGE_TYPE_1D; - auto texture = new CVulkanTexture(); - auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM ); - bool bRes = texture->BInit( width, height, depth, drmFormat, flags ); - assert( bRes ); + auto texture = new CVulkanTexture( ); + auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM ); + bool bRes = texture->BInit( size, 1u, 1u, drmFormat, flags ); + assert( bRes ); - return texture; + return texture; } -void vulkan_update_luts(const gamescope::Rc& lut1d, const gamescope::Rc& lut3d, void* lut1d_data, void* lut3d_data) +gamescope::Rc +vulkan_create_3d_lut( uint32_t width, uint32_t height, uint32_t depth ) { - size_t lut1d_size = lut1d->width() * sizeof(uint16_t) * 4; - size_t lut3d_size = lut3d->width() * lut3d->height() * lut3d->depth() * sizeof(uint16_t) * 4; - - auto [base_dst, base_offset] = g_device.uploadBufferData(lut1d_size + lut3d_size); + CVulkanTexture::createFlags flags; + flags.bSampled = true; + flags.bTransferDst = true; + flags.imageType = VK_IMAGE_TYPE_3D; - void* lut1d_dst = base_dst; - void *lut3d_dst = ((uint8_t*)base_dst) + lut1d_size; - memcpy(lut1d_dst, lut1d_data, lut1d_size); - memcpy(lut3d_dst, lut3d_data, lut3d_size); + auto texture = new CVulkanTexture( ); + auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM ); + bool bRes = texture->BInit( width, height, depth, drmFormat, flags ); + assert( bRes ); - auto cmdBuffer = g_device.commandBuffer(); - cmdBuffer->copyBufferToImage(g_device.uploadBuffer(), base_offset, 0, lut1d); - cmdBuffer->copyBufferToImage(g_device.uploadBuffer(), base_offset + lut1d_size, 0, lut3d); - g_device.submit(std::move(cmdBuffer)); - g_device.waitIdle(); // TODO: Sync this better + return texture; } -gamescope::Rc vulkan_get_hacky_blank_texture() +void vulkan_update_luts( + const gamescope::Rc &lut1d, + const gamescope::Rc &lut3d, + void *lut1d_data, + void *lut3d_data ) { - return g_output.temporaryHackyBlankImage.get(); + size_t lut1d_size = lut1d->width( ) * sizeof( uint16_t ) * 4; + size_t lut3d_size = lut3d->width( ) * lut3d->height( ) * lut3d->depth( ) * + sizeof( uint16_t ) * 4; + + auto [ base_dst, base_offset ] = + g_device.uploadBufferData( lut1d_size + lut3d_size ); + + void *lut1d_dst = base_dst; + void *lut3d_dst = ( ( uint8_t * )base_dst ) + lut1d_size; + memcpy( lut1d_dst, lut1d_data, lut1d_size ); + memcpy( lut3d_dst, lut3d_data, lut3d_size ); + + auto cmdBuffer = g_device.commandBuffer( ); + cmdBuffer->copyBufferToImage( + g_device.uploadBuffer( ), base_offset, 0, lut1d ); + cmdBuffer->copyBufferToImage( + g_device.uploadBuffer( ), base_offset + lut1d_size, 0, lut3d ); + g_device.submit( std::move( cmdBuffer ) ); + g_device.waitIdle( ); // TODO: Sync this better } -gamescope::OwningRc vulkan_create_flat_texture( uint32_t width, uint32_t height, uint8_t r, uint8_t g, uint8_t b, uint8_t a ) +gamescope::Rc vulkan_get_hacky_blank_texture( ) +{ return g_output.temporaryHackyBlankImage.get( ); } + +gamescope::OwningRc vulkan_create_flat_texture( + uint32_t width, + uint32_t height, + uint8_t r, + uint8_t g, + uint8_t b, + uint8_t a ) { - CVulkanTexture::createFlags flags; - flags.bFlippable = true; - flags.bSampled = true; - flags.bTransferDst = true; - - gamescope::OwningRc texture = new CVulkanTexture(); - bool bRes = texture->BInit( width, height, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ); - assert( bRes ); - - auto [_dst, offset] = g_device.uploadBufferData( width * height * 4 ); - uint8_t *dst = (uint8_t *)_dst; - for ( uint32_t i = 0; i < width * height * 4; i += 4 ) - { - dst[i + 0] = b; - dst[i + 1] = g; - dst[i + 2] = r; - dst[i + 3] = a; - } - - auto cmdBuffer = g_device.commandBuffer(); - cmdBuffer->copyBufferToImage(g_device.uploadBuffer(), offset, 0, texture.get()); - g_device.submit(std::move(cmdBuffer)); - g_device.waitIdle(); - - return texture; + CVulkanTexture::createFlags flags; + flags.bFlippable = true; + flags.bSampled = true; + flags.bTransferDst = true; + + gamescope::OwningRc texture = new CVulkanTexture( ); + bool bRes = texture->BInit( + width, + height, + 1u, + VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), + flags ); + assert( bRes ); + + auto [ _dst, offset ] = g_device.uploadBufferData( width * height * 4 ); + uint8_t *dst = ( uint8_t * )_dst; + for ( uint32_t i = 0; i < width * height * 4; i += 4 ) + { + dst[ i + 0 ] = b; + dst[ i + 1 ] = g; + dst[ i + 2 ] = r; + dst[ i + 3 ] = a; + } + + auto cmdBuffer = g_device.commandBuffer( ); + cmdBuffer->copyBufferToImage( + g_device.uploadBuffer( ), offset, 0, texture.get( ) ); + g_device.submit( std::move( cmdBuffer ) ); + g_device.waitIdle( ); + + return texture; } -gamescope::OwningRc vulkan_create_debug_blank_texture() +gamescope::OwningRc vulkan_create_debug_blank_texture( ) { - // To match Steam's scaling, which is capped at 1080p - int width = std::min( g_nOutputWidth, 1920 ); - int height = std::min( g_nOutputHeight, 1080 ); + // To match Steam's scaling, which is capped at 1080p + int width = std::min( g_nOutputWidth, 1920 ); + int height = std::min( g_nOutputHeight, 1080 ); - return vulkan_create_flat_texture( width, height, 0, 0, 0, 0 ); + return vulkan_create_flat_texture( width, height, 0, 0, 0, 0 ); } -bool vulkan_supports_hdr10() +bool vulkan_supports_hdr10( ) { - for ( auto& format : g_output.surfaceFormats ) - { - if ( format.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT ) - return true; - } + for ( auto &format : g_output.surfaceFormats ) + { + if ( format.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT ) return true; + } - return false; + return false; } extern bool g_bOutputHDREnabled; bool vulkan_make_swapchain( VulkanOutput_t *pOutput ) { - uint32_t imageCount = pOutput->surfaceCaps.minImageCount + 1; - uint32_t formatCount = pOutput->surfaceFormats.size(); - uint32_t surfaceFormat = formatCount; - VkColorSpaceKHR preferredColorSpace = g_bOutputHDREnabled ? VK_COLOR_SPACE_HDR10_ST2084_EXT : VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - - if ( surfaceFormat == formatCount ) - { - for ( surfaceFormat = 0; surfaceFormat < formatCount; surfaceFormat++ ) - { - if ( pOutput->surfaceFormats[ surfaceFormat ].format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 && - pOutput->surfaceFormats[ surfaceFormat ].colorSpace == preferredColorSpace ) - break; - } - } - - if ( surfaceFormat == formatCount ) - { - for ( surfaceFormat = 0; surfaceFormat < formatCount; surfaceFormat++ ) - { - if ( pOutput->surfaceFormats[ surfaceFormat ].format == VK_FORMAT_A2R10G10B10_UNORM_PACK32 && - pOutput->surfaceFormats[ surfaceFormat ].colorSpace == preferredColorSpace ) - break; - } - } - - if ( surfaceFormat == formatCount ) - { - for ( surfaceFormat = 0; surfaceFormat < formatCount; surfaceFormat++ ) - { - if ( pOutput->surfaceFormats[ surfaceFormat ].format == VK_FORMAT_B8G8R8A8_UNORM && - pOutput->surfaceFormats[ surfaceFormat ].colorSpace == preferredColorSpace ) - break; - } - } - - if ( surfaceFormat == formatCount ) - return false; - - VkFormat eVkFormat = pOutput->surfaceFormats[ surfaceFormat ].format; - pOutput->uOutputFormat = VulkanFormatToDRM( pOutput->surfaceFormats[ surfaceFormat ].format ); - - VkFormat formats[2] = - { - ToSrgbVulkanFormat( eVkFormat ), - ToLinearVulkanFormat( eVkFormat ), - }; + uint32_t imageCount = pOutput->surfaceCaps.minImageCount + 1; + uint32_t formatCount = pOutput->surfaceFormats.size( ); + uint32_t surfaceFormat = formatCount; + VkColorSpaceKHR preferredColorSpace = + g_bOutputHDREnabled ? VK_COLOR_SPACE_HDR10_ST2084_EXT + : VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + + if ( surfaceFormat == formatCount ) + { + for ( surfaceFormat = 0; surfaceFormat < formatCount; surfaceFormat++ ) + { + if ( pOutput->surfaceFormats[ surfaceFormat ].format == + VK_FORMAT_A2B10G10R10_UNORM_PACK32 && + pOutput->surfaceFormats[ surfaceFormat ].colorSpace == + preferredColorSpace ) + break; + } + } - VkImageFormatListCreateInfo usageListInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, - .viewFormatCount = 2, - .pViewFormats = formats, - }; + if ( surfaceFormat == formatCount ) + { + for ( surfaceFormat = 0; surfaceFormat < formatCount; surfaceFormat++ ) + { + if ( pOutput->surfaceFormats[ surfaceFormat ].format == + VK_FORMAT_A2R10G10B10_UNORM_PACK32 && + pOutput->surfaceFormats[ surfaceFormat ].colorSpace == + preferredColorSpace ) + break; + } + } + + if ( surfaceFormat == formatCount ) + { + for ( surfaceFormat = 0; surfaceFormat < formatCount; surfaceFormat++ ) + { + if ( pOutput->surfaceFormats[ surfaceFormat ].format == + VK_FORMAT_B8G8R8A8_UNORM && + pOutput->surfaceFormats[ surfaceFormat ].colorSpace == + preferredColorSpace ) + break; + } + } + + if ( surfaceFormat == formatCount ) return false; + + VkFormat eVkFormat = pOutput->surfaceFormats[ surfaceFormat ].format; + pOutput->uOutputFormat = + VulkanFormatToDRM( pOutput->surfaceFormats[ surfaceFormat ].format ); - vk_log.infof("Creating Gamescope nested swapchain with format %u and colorspace %u", eVkFormat, pOutput->surfaceFormats[surfaceFormat].colorSpace); + VkFormat formats[ 2 ] = { + ToSrgbVulkanFormat( eVkFormat ), + ToLinearVulkanFormat( eVkFormat ), + }; - VkSwapchainCreateInfoKHR createInfo = { + VkImageFormatListCreateInfo usageListInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, + .viewFormatCount = 2, + .pViewFormats = formats, + }; + + vk_log.infof( + "Creating Gamescope nested swapchain with format %u and colorspace %u", + eVkFormat, + pOutput->surfaceFormats[ surfaceFormat ].colorSpace ); + + VkSwapchainCreateInfoKHR createInfo = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .pNext = formats[0] != formats[1] ? &usageListInfo : nullptr, .flags = formats[0] != formats[1] ? VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR : (VkSwapchainCreateFlagBitsKHR )0, @@ -3204,1188 +3687,1439 @@ bool vulkan_make_swapchain( VulkanOutput_t *pOutput ) .clipped = VK_TRUE, }; - if (g_device.vk.CreateSwapchainKHR( g_device.device(), &createInfo, nullptr, &pOutput->swapChain) != VK_SUCCESS ) { - return false; - } - - g_device.vk.GetSwapchainImagesKHR( g_device.device(), pOutput->swapChain, &imageCount, nullptr ); - std::vector swapchainImages( imageCount ); - g_device.vk.GetSwapchainImagesKHR( g_device.device(), pOutput->swapChain, &imageCount, swapchainImages.data() ); + if ( g_device.vk.CreateSwapchainKHR( + g_device.device( ), &createInfo, nullptr, &pOutput->swapChain ) != + VK_SUCCESS ) + { + return false; + } - pOutput->outputImages.resize(imageCount); + g_device.vk.GetSwapchainImagesKHR( + g_device.device( ), pOutput->swapChain, &imageCount, nullptr ); + std::vector swapchainImages( imageCount ); + g_device.vk.GetSwapchainImagesKHR( + g_device.device( ), + pOutput->swapChain, + &imageCount, + swapchainImages.data( ) ); - for ( uint32_t i = 0; i < pOutput->outputImages.size(); i++ ) - { - pOutput->outputImages[i] = new CVulkanTexture(); + pOutput->outputImages.resize( imageCount ); - if ( !pOutput->outputImages[i]->BInitFromSwapchain(swapchainImages[i], g_nOutputWidth, g_nOutputHeight, eVkFormat)) - return false; - } + for ( uint32_t i = 0; i < pOutput->outputImages.size( ); i++ ) + { + pOutput->outputImages[ i ] = new CVulkanTexture( ); + + if ( !pOutput->outputImages[ i ]->BInitFromSwapchain( + swapchainImages[ i ], + g_nOutputWidth, + g_nOutputHeight, + eVkFormat ) ) + return false; + } - VkFenceCreateInfo fenceInfo = { - .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - }; + VkFenceCreateInfo fenceInfo = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + }; - g_device.vk.CreateFence( g_device.device(), &fenceInfo, nullptr, &pOutput->acquireFence ); + g_device.vk.CreateFence( + g_device.device( ), &fenceInfo, nullptr, &pOutput->acquireFence ); - vulkan_update_swapchain_hdr_metadata(pOutput); + vulkan_update_swapchain_hdr_metadata( pOutput ); - return true; + return true; } bool vulkan_remake_swapchain( void ) { - std::unique_lock lock(present_wait_lock); - g_currentPresentWaitId = 0; - g_currentPresentWaitId.notify_all(); + std::unique_lock lock( present_wait_lock ); + g_currentPresentWaitId = 0; + g_currentPresentWaitId.notify_all( ); - VulkanOutput_t *pOutput = &g_output; - g_device.waitIdle(); - g_device.vk.QueueWaitIdle( g_device.queue() ); + VulkanOutput_t *pOutput = &g_output; + g_device.waitIdle( ); + g_device.vk.QueueWaitIdle( g_device.queue( ) ); - pOutput->outputImages.clear(); + pOutput->outputImages.clear( ); - g_device.vk.DestroySwapchainKHR( g_device.device(), pOutput->swapChain, nullptr ); + g_device.vk.DestroySwapchainKHR( + g_device.device( ), pOutput->swapChain, nullptr ); - // Delete screenshot image to be remade if needed - for (auto& pScreenshotImage : pOutput->pScreenshotImages) - pScreenshotImage = nullptr; + // Delete screenshot image to be remade if needed + for ( auto &pScreenshotImage : pOutput->pScreenshotImages ) + pScreenshotImage = nullptr; - bool bRet = vulkan_make_swapchain( pOutput ); - assert( bRet ); // Something has gone horribly wrong! - return bRet; + bool bRet = vulkan_make_swapchain( pOutput ); + assert( bRet ); // Something has gone horribly wrong! + return bRet; } static bool vulkan_make_output_images( VulkanOutput_t *pOutput ) { - CVulkanTexture::createFlags outputImageflags; - outputImageflags.bFlippable = true; - outputImageflags.bStorage = true; - outputImageflags.bTransferSrc = true; // for screenshots - outputImageflags.bSampled = true; // for pipewire blits - outputImageflags.bOutputImage = true; - - pOutput->outputImages.resize(3); // extra image for partial composition. - pOutput->outputImagesPartialOverlay.resize(3); - - pOutput->outputImages[0] = nullptr; - pOutput->outputImages[1] = nullptr; - pOutput->outputImages[2] = nullptr; - pOutput->outputImagesPartialOverlay[0] = nullptr; - pOutput->outputImagesPartialOverlay[1] = nullptr; - pOutput->outputImagesPartialOverlay[2] = nullptr; - - uint32_t uDRMFormat = pOutput->uOutputFormat; - - pOutput->outputImages[0] = new CVulkanTexture(); - bool bSuccess = pOutput->outputImages[0]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags ); - if ( bSuccess != true ) - { - vk_log.errorf( "failed to allocate buffer for KMS" ); - return false; - } - - pOutput->outputImages[1] = new CVulkanTexture(); - bSuccess = pOutput->outputImages[1]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags ); - if ( bSuccess != true ) - { - vk_log.errorf( "failed to allocate buffer for KMS" ); - return false; - } - - pOutput->outputImages[2] = new CVulkanTexture(); - bSuccess = pOutput->outputImages[2]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags ); - if ( bSuccess != true ) - { - vk_log.errorf( "failed to allocate buffer for KMS" ); - return false; - } - - // Oh no. - pOutput->temporaryHackyBlankImage = vulkan_create_debug_blank_texture(); - - if ( pOutput->uOutputFormatOverlay != VK_FORMAT_UNDEFINED && !kDisablePartialComposition ) - { - uint32_t uPartialDRMFormat = pOutput->uOutputFormatOverlay; - - pOutput->outputImagesPartialOverlay[0] = new CVulkanTexture(); - bool bSuccess = pOutput->outputImagesPartialOverlay[0]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[0].get() ); - if ( bSuccess != true ) - { - vk_log.errorf( "failed to allocate buffer for KMS" ); - return false; - } - - pOutput->outputImagesPartialOverlay[1] = new CVulkanTexture(); - bSuccess = pOutput->outputImagesPartialOverlay[1]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[1].get() ); - if ( bSuccess != true ) - { - vk_log.errorf( "failed to allocate buffer for KMS" ); - return false; - } - - pOutput->outputImagesPartialOverlay[2] = new CVulkanTexture(); - bSuccess = pOutput->outputImagesPartialOverlay[2]->BInit( g_nOutputWidth, g_nOutputHeight, 1u, uPartialDRMFormat, outputImageflags, nullptr, 0, 0, pOutput->outputImages[2].get() ); - if ( bSuccess != true ) - { - vk_log.errorf( "failed to allocate buffer for KMS" ); - return false; - } - } - - return true; + CVulkanTexture::createFlags outputImageflags; + outputImageflags.bFlippable = true; + outputImageflags.bStorage = true; + outputImageflags.bTransferSrc = true; // for screenshots + outputImageflags.bSampled = true; // for pipewire blits + outputImageflags.bOutputImage = true; + + pOutput->outputImages.resize( 3 ); // extra image for partial composition. + pOutput->outputImagesPartialOverlay.resize( 3 ); + + pOutput->outputImages[ 0 ] = nullptr; + pOutput->outputImages[ 1 ] = nullptr; + pOutput->outputImages[ 2 ] = nullptr; + pOutput->outputImagesPartialOverlay[ 0 ] = nullptr; + pOutput->outputImagesPartialOverlay[ 1 ] = nullptr; + pOutput->outputImagesPartialOverlay[ 2 ] = nullptr; + + uint32_t uDRMFormat = pOutput->uOutputFormat; + + pOutput->outputImages[ 0 ] = new CVulkanTexture( ); + bool bSuccess = pOutput->outputImages[ 0 ]->BInit( + g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags ); + if ( bSuccess != true ) + { + vk_log.errorf( "failed to allocate buffer for KMS" ); + return false; + } + + pOutput->outputImages[ 1 ] = new CVulkanTexture( ); + bSuccess = pOutput->outputImages[ 1 ]->BInit( + g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags ); + if ( bSuccess != true ) + { + vk_log.errorf( "failed to allocate buffer for KMS" ); + return false; + } + + pOutput->outputImages[ 2 ] = new CVulkanTexture( ); + bSuccess = pOutput->outputImages[ 2 ]->BInit( + g_nOutputWidth, g_nOutputHeight, 1u, uDRMFormat, outputImageflags ); + if ( bSuccess != true ) + { + vk_log.errorf( "failed to allocate buffer for KMS" ); + return false; + } + + // Oh no. + pOutput->temporaryHackyBlankImage = vulkan_create_debug_blank_texture( ); + + if ( pOutput->uOutputFormatOverlay != VK_FORMAT_UNDEFINED && + !kDisablePartialComposition ) + { + uint32_t uPartialDRMFormat = pOutput->uOutputFormatOverlay; + + pOutput->outputImagesPartialOverlay[ 0 ] = new CVulkanTexture( ); + bool bSuccess = pOutput->outputImagesPartialOverlay[ 0 ]->BInit( + g_nOutputWidth, + g_nOutputHeight, + 1u, + uPartialDRMFormat, + outputImageflags, + nullptr, + 0, + 0, + pOutput->outputImages[ 0 ].get( ) ); + if ( bSuccess != true ) + { + vk_log.errorf( "failed to allocate buffer for KMS" ); + return false; + } + + pOutput->outputImagesPartialOverlay[ 1 ] = new CVulkanTexture( ); + bSuccess = pOutput->outputImagesPartialOverlay[ 1 ]->BInit( + g_nOutputWidth, + g_nOutputHeight, + 1u, + uPartialDRMFormat, + outputImageflags, + nullptr, + 0, + 0, + pOutput->outputImages[ 1 ].get( ) ); + if ( bSuccess != true ) + { + vk_log.errorf( "failed to allocate buffer for KMS" ); + return false; + } + + pOutput->outputImagesPartialOverlay[ 2 ] = new CVulkanTexture( ); + bSuccess = pOutput->outputImagesPartialOverlay[ 2 ]->BInit( + g_nOutputWidth, + g_nOutputHeight, + 1u, + uPartialDRMFormat, + outputImageflags, + nullptr, + 0, + 0, + pOutput->outputImages[ 2 ].get( ) ); + if ( bSuccess != true ) + { + vk_log.errorf( "failed to allocate buffer for KMS" ); + return false; + } + } + + return true; } -bool vulkan_remake_output_images() +bool vulkan_remake_output_images( ) { - VulkanOutput_t *pOutput = &g_output; - g_device.waitIdle(); + VulkanOutput_t *pOutput = &g_output; + g_device.waitIdle( ); - pOutput->nOutImage = 0; + pOutput->nOutImage = 0; - // Delete screenshot image to be remade if needed - for (auto& pScreenshotImage : pOutput->pScreenshotImages) - pScreenshotImage = nullptr; + // Delete screenshot image to be remade if needed + for ( auto &pScreenshotImage : pOutput->pScreenshotImages ) + pScreenshotImage = nullptr; - bool bRet = vulkan_make_output_images( pOutput ); - assert( bRet ); - return bRet; + bool bRet = vulkan_make_output_images( pOutput ); + assert( bRet ); + return bRet; } -bool vulkan_make_output() +bool vulkan_make_output( ) { - VulkanOutput_t *pOutput = &g_output; - - VkResult result; - - if ( GetBackend()->UsesVulkanSwapchain() ) - { - result = g_device.vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( g_device.physDev(), pOutput->surface, &pOutput->surfaceCaps ); - if ( result != VK_SUCCESS ) - { - vk_errorf( result, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed" ); - return false; - } - - uint32_t formatCount = 0; - result = g_device.vk.GetPhysicalDeviceSurfaceFormatsKHR( g_device.physDev(), pOutput->surface, &formatCount, nullptr ); - if ( result != VK_SUCCESS ) - { - vk_errorf( result, "vkGetPhysicalDeviceSurfaceFormatsKHR failed" ); - return false; - } - - if ( formatCount != 0 ) { - pOutput->surfaceFormats.resize( formatCount ); - g_device.vk.GetPhysicalDeviceSurfaceFormatsKHR( g_device.physDev(), pOutput->surface, &formatCount, pOutput->surfaceFormats.data() ); - if ( result != VK_SUCCESS ) - { - vk_errorf( result, "vkGetPhysicalDeviceSurfaceFormatsKHR failed" ); - return false; - } - } - - uint32_t presentModeCount = false; - result = g_device.vk.GetPhysicalDeviceSurfacePresentModesKHR(g_device.physDev(), pOutput->surface, &presentModeCount, nullptr ); - if ( result != VK_SUCCESS ) - { - vk_errorf( result, "vkGetPhysicalDeviceSurfacePresentModesKHR failed" ); - return false; - } - - if ( presentModeCount != 0 ) { - pOutput->presentModes.resize(presentModeCount); - result = g_device.vk.GetPhysicalDeviceSurfacePresentModesKHR( g_device.physDev(), pOutput->surface, &presentModeCount, pOutput->presentModes.data() ); - if ( result != VK_SUCCESS ) - { - vk_errorf( result, "vkGetPhysicalDeviceSurfacePresentModesKHR failed" ); - return false; - } - } - - if ( !vulkan_make_swapchain( pOutput ) ) - return false; - - while ( !acquire_next_image() ) - vulkan_remake_swapchain(); - } - else - { - GetBackend()->GetPreferredOutputFormat( &pOutput->uOutputFormat, &pOutput->uOutputFormatOverlay ); - - if ( pOutput->uOutputFormat == DRM_FORMAT_INVALID ) - { - vk_log.errorf( "failed to find Vulkan format suitable for KMS" ); - return false; - } - - if ( pOutput->uOutputFormatOverlay == DRM_FORMAT_INVALID ) - { - vk_log.errorf( "failed to find Vulkan format suitable for KMS partial overlays" ); - return false; - } - - if ( !vulkan_make_output_images( pOutput ) ) - return false; - } - - return true; + VulkanOutput_t *pOutput = &g_output; + + VkResult result; + + if ( GetBackend( )->UsesVulkanSwapchain( ) ) + { + result = g_device.vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( + g_device.physDev( ), pOutput->surface, &pOutput->surfaceCaps ); + if ( result != VK_SUCCESS ) + { + vk_errorf( + result, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed" ); + return false; + } + + uint32_t formatCount = 0; + result = g_device.vk.GetPhysicalDeviceSurfaceFormatsKHR( + g_device.physDev( ), pOutput->surface, &formatCount, nullptr ); + if ( result != VK_SUCCESS ) + { + vk_errorf( result, "vkGetPhysicalDeviceSurfaceFormatsKHR failed" ); + return false; + } + + if ( formatCount != 0 ) + { + pOutput->surfaceFormats.resize( formatCount ); + g_device.vk.GetPhysicalDeviceSurfaceFormatsKHR( + g_device.physDev( ), + pOutput->surface, + &formatCount, + pOutput->surfaceFormats.data( ) ); + if ( result != VK_SUCCESS ) + { + vk_errorf( + result, "vkGetPhysicalDeviceSurfaceFormatsKHR failed" ); + return false; + } + } + + uint32_t presentModeCount = false; + result = g_device.vk.GetPhysicalDeviceSurfacePresentModesKHR( + g_device.physDev( ), pOutput->surface, &presentModeCount, nullptr ); + if ( result != VK_SUCCESS ) + { + vk_errorf( + result, "vkGetPhysicalDeviceSurfacePresentModesKHR failed" ); + return false; + } + + if ( presentModeCount != 0 ) + { + pOutput->presentModes.resize( presentModeCount ); + result = g_device.vk.GetPhysicalDeviceSurfacePresentModesKHR( + g_device.physDev( ), + pOutput->surface, + &presentModeCount, + pOutput->presentModes.data( ) ); + if ( result != VK_SUCCESS ) + { + vk_errorf( + result, + "vkGetPhysicalDeviceSurfacePresentModesKHR failed" ); + return false; + } + } + + if ( !vulkan_make_swapchain( pOutput ) ) return false; + + while ( !acquire_next_image( ) ) + vulkan_remake_swapchain( ); + } + else + { + GetBackend( )->GetPreferredOutputFormat( + &pOutput->uOutputFormat, &pOutput->uOutputFormatOverlay ); + + if ( pOutput->uOutputFormat == DRM_FORMAT_INVALID ) + { + vk_log.errorf( "failed to find Vulkan format suitable for KMS" ); + return false; + } + + if ( pOutput->uOutputFormatOverlay == DRM_FORMAT_INVALID ) + { + vk_log.errorf( + "failed to find Vulkan format suitable for KMS partial " + "overlays" ); + return false; + } + + if ( !vulkan_make_output_images( pOutput ) ) return false; + } + + return true; } static void update_tmp_images( uint32_t width, uint32_t height ) { - if ( g_output.tmpOutput != nullptr - && width == g_output.tmpOutput->width() - && height == g_output.tmpOutput->height() ) - { - return; - } - - CVulkanTexture::createFlags createFlags; - createFlags.bSampled = true; - createFlags.bStorage = true; - - g_output.tmpOutput = new CVulkanTexture(); - bool bSuccess = g_output.tmpOutput->BInit( width, height, 1u, DRM_FORMAT_ARGB8888, createFlags, nullptr ); - - if ( !bSuccess ) - { - vk_log.errorf( "failed to create fsr output" ); - return; - } -} + if ( g_output.tmpOutput != nullptr && + width == g_output.tmpOutput->width( ) && + height == g_output.tmpOutput->height( ) ) + { + return; + } + + CVulkanTexture::createFlags createFlags; + createFlags.bSampled = true; + createFlags.bStorage = true; + g_output.tmpOutput = new CVulkanTexture( ); + bool bSuccess = g_output.tmpOutput->BInit( + width, height, 1u, DRM_FORMAT_ARGB8888, createFlags, nullptr ); -static bool init_nis_data() + if ( !bSuccess ) + { + vk_log.errorf( "failed to create fsr output" ); + return; + } +} + +static bool init_nis_data( ) { - // Create the NIS images - // Select between the FP16 or FP32 coefficients + // Create the NIS images + // Select between the FP16 or FP32 coefficients - void* coefScaleData = g_device.supportsFp16() ? (void*) coef_scale_fp16 : (void*) coef_scale; + void *coefScaleData = g_device.supportsFp16( ) ? ( void * )coef_scale_fp16 + : ( void * )coef_scale; - void* coefUsmData = g_device.supportsFp16() ? (void*) coef_usm_fp16 : (void*) coef_usm; + void *coefUsmData = + g_device.supportsFp16( ) ? ( void * )coef_usm_fp16 : ( void * )coef_usm; - uint32_t nisFormat = g_device.supportsFp16() ? DRM_FORMAT_ABGR16161616F : DRM_FORMAT_ABGR32323232F; + uint32_t nisFormat = g_device.supportsFp16( ) ? DRM_FORMAT_ABGR16161616F + : DRM_FORMAT_ABGR32323232F; - uint32_t width = kFilterSize / 4; - uint32_t height = kPhaseCount; + uint32_t width = kFilterSize / 4; + uint32_t height = kPhaseCount; - g_output.nisScalerImage = vulkan_create_texture_from_bits( width, height, width, height, nisFormat, {}, coefScaleData ); - g_output.nisUsmImage = vulkan_create_texture_from_bits( width, height, width, height, nisFormat, {}, coefUsmData ); + g_output.nisScalerImage = vulkan_create_texture_from_bits( + width, height, width, height, nisFormat, {}, coefScaleData ); + g_output.nisUsmImage = vulkan_create_texture_from_bits( + width, height, width, height, nisFormat, {}, coefUsmData ); - return true; + return true; } VkInstance vulkan_get_instance( void ) { - static VkInstance s_pVkInstance = []() -> VkInstance - { - VkResult result = VK_ERROR_INITIALIZATION_FAILED; - - if ( ( result = vulkan_load_module() ) != VK_SUCCESS ) - { - vk_errorf( result, "Failed to load vulkan module." ); - return nullptr; - } - - auto instanceExtensions = GetBackend()->GetInstanceExtensions(); - - const VkApplicationInfo appInfo = { - .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pApplicationName = "gamescope", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "hopefully not just some code", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = VK_API_VERSION_1_3, - }; - - const VkInstanceCreateInfo createInfo = { - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pApplicationInfo = &appInfo, - .enabledExtensionCount = (uint32_t)instanceExtensions.size(), - .ppEnabledExtensionNames = instanceExtensions.data(), - }; - - VkInstance instance = nullptr; - result = g_pfn_vkCreateInstance(&createInfo, 0, &instance); - if ( result != VK_SUCCESS ) - { - vk_errorf( result, "vkCreateInstance failed" ); - } - - return instance; - }(); - - return s_pVkInstance; + static VkInstance s_pVkInstance = []( ) -> VkInstance + { + VkResult result = VK_ERROR_INITIALIZATION_FAILED; + + if ( ( result = vulkan_load_module( ) ) != VK_SUCCESS ) + { + vk_errorf( result, "Failed to load vulkan module." ); + return nullptr; + } + + auto instanceExtensions = GetBackend( )->GetInstanceExtensions( ); + + const VkApplicationInfo appInfo = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "gamescope", + .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), + .pEngineName = "hopefully not just some code", + .engineVersion = VK_MAKE_VERSION( 1, 0, 0 ), + .apiVersion = VK_API_VERSION_1_3, + }; + + const VkInstanceCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo, + .enabledExtensionCount = ( uint32_t )instanceExtensions.size( ), + .ppEnabledExtensionNames = instanceExtensions.data( ), + }; + + VkInstance instance = nullptr; + result = g_pfn_vkCreateInstance( &createInfo, 0, &instance ); + if ( result != VK_SUCCESS ) + { + vk_errorf( result, "vkCreateInstance failed" ); + } + + return instance; + }( ); + + return s_pVkInstance; } bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ) { - static bool s_bInitted = false; - if ( s_bInitted ) - { - g_output.surface = surface; - return true; - } + static bool s_bInitted = false; + if ( s_bInitted ) + { + g_output.surface = surface; + return true; + } - if (!g_device.BInit(instance, surface)) - return false; + if ( !g_device.BInit( instance, surface ) ) return false; - if (!init_nis_data()) - return false; + if ( !init_nis_data( ) ) return false; - if ( GetBackend()->UsesVulkanSwapchain() ) - { - std::thread present_wait_thread( present_wait_thread_func ); - present_wait_thread.detach(); - } + if ( GetBackend( )->UsesVulkanSwapchain( ) ) + { + std::thread present_wait_thread( present_wait_thread_func ); + present_wait_thread.detach( ); + } - s_bInitted = true; + s_bInitted = true; - return true; + return true; } -gamescope::OwningRc vulkan_create_texture_from_dmabuf( struct wlr_dmabuf_attributes *pDMA, gamescope::OwningRc pBackendFb ) +gamescope::OwningRc vulkan_create_texture_from_dmabuf( + struct wlr_dmabuf_attributes *pDMA, + gamescope::OwningRc pBackendFb ) { - gamescope::OwningRc pTex = new CVulkanTexture(); - - CVulkanTexture::createFlags texCreateFlags; - texCreateFlags.bSampled = true; - - //fprintf(stderr, "pDMA->width: %d pDMA->height: %d pDMA->format: 0x%x pDMA->modifier: 0x%lx pDMA->n_planes: %d\n", - // pDMA->width, pDMA->height, pDMA->format, pDMA->modifier, pDMA->n_planes); - - if ( pTex->BInit( pDMA->width, pDMA->height, 1u, pDMA->format, texCreateFlags, pDMA, 0, 0, nullptr, pBackendFb ) == false ) - return nullptr; - - return pTex; + gamescope::OwningRc pTex = new CVulkanTexture( ); + + CVulkanTexture::createFlags texCreateFlags; + texCreateFlags.bSampled = true; + + // fprintf(stderr, "pDMA->width: %d pDMA->height: %d pDMA->format: 0x%x + // pDMA->modifier: 0x%lx pDMA->n_planes: %d\n", pDMA->width, + // pDMA->height, + // pDMA->format, pDMA->modifier, pDMA->n_planes); + + if ( pTex->BInit( + pDMA->width, + pDMA->height, + 1u, + pDMA->format, + texCreateFlags, + pDMA, + 0, + 0, + nullptr, + pBackendFb ) == false ) + return nullptr; + + return pTex; } -gamescope::OwningRc vulkan_create_texture_from_bits( uint32_t width, uint32_t height, uint32_t contentWidth, uint32_t contentHeight, uint32_t drmFormat, CVulkanTexture::createFlags texCreateFlags, void *bits ) +gamescope::OwningRc vulkan_create_texture_from_bits( + uint32_t width, + uint32_t height, + uint32_t contentWidth, + uint32_t contentHeight, + uint32_t drmFormat, + CVulkanTexture::createFlags texCreateFlags, + void *bits ) { - gamescope::OwningRc pTex = new CVulkanTexture(); + gamescope::OwningRc pTex = new CVulkanTexture( ); - texCreateFlags.bSampled = true; - texCreateFlags.bTransferDst = true; + texCreateFlags.bSampled = true; + texCreateFlags.bTransferDst = true; - if ( pTex->BInit( width, height, 1u, drmFormat, texCreateFlags, nullptr, contentWidth, contentHeight) == false ) - return nullptr; + if ( pTex->BInit( + width, + height, + 1u, + drmFormat, + texCreateFlags, + nullptr, + contentWidth, + contentHeight ) == false ) + return nullptr; - size_t size = width * height * DRMFormatGetBPP(drmFormat); - auto [ dst, offset ] = g_device.uploadBufferData(size); - memcpy( dst, bits, size ); + size_t size = width * height * DRMFormatGetBPP( drmFormat ); + auto [ dst, offset ] = g_device.uploadBufferData( size ); + memcpy( dst, bits, size ); - auto cmdBuffer = g_device.commandBuffer(); + auto cmdBuffer = g_device.commandBuffer( ); - cmdBuffer->copyBufferToImage(g_device.uploadBuffer(), offset, 0, pTex.get()); - // TODO: Sync this copyBufferToImage. + cmdBuffer->copyBufferToImage( + g_device.uploadBuffer( ), offset, 0, pTex.get( ) ); + // TODO: Sync this copyBufferToImage. - g_device.submit(std::move(cmdBuffer)); - g_device.waitIdle(); + g_device.submit( std::move( cmdBuffer ) ); + g_device.waitIdle( ); - return pTex; + return pTex; } static uint32_t s_frameId = 0; -void vulkan_garbage_collect( void ) -{ - g_device.garbageCollect(); -} +void vulkan_garbage_collect( void ) { g_device.garbageCollect( ); } -gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace) +gamescope::Rc vulkan_acquire_screenshot_texture( + uint32_t width, + uint32_t height, + bool exportable, + uint32_t drmFormat, + EStreamColorspace colorspace ) { - for (auto& pScreenshotImage : g_output.pScreenshotImages) - { - if (pScreenshotImage == nullptr) - { - pScreenshotImage = new CVulkanTexture(); - - CVulkanTexture::createFlags screenshotImageFlags; - screenshotImageFlags.bMappable = true; - screenshotImageFlags.bTransferDst = true; - screenshotImageFlags.bStorage = true; - if (exportable || drmFormat == DRM_FORMAT_NV12) { - screenshotImageFlags.bExportable = true; - screenshotImageFlags.bLinear = true; // TODO: support multi-planar DMA-BUF export via PipeWire - } - - bool bSuccess = pScreenshotImage->BInit( width, height, 1u, drmFormat, screenshotImageFlags ); - pScreenshotImage->setStreamColorspace(colorspace); - - assert( bSuccess ); - } - - if (pScreenshotImage->GetRefCount() != 0 || - width != pScreenshotImage->width() || - height != pScreenshotImage->height() || - drmFormat != pScreenshotImage->drmFormat()) - continue; - - return pScreenshotImage.get(); - } - - vk_log.errorf("Unable to acquire screenshot texture. Out of textures."); - return nullptr; + for ( auto &pScreenshotImage : g_output.pScreenshotImages ) + { + if ( pScreenshotImage == nullptr ) + { + pScreenshotImage = new CVulkanTexture( ); + + CVulkanTexture::createFlags screenshotImageFlags; + screenshotImageFlags.bMappable = true; + screenshotImageFlags.bTransferDst = true; + screenshotImageFlags.bStorage = true; + if ( exportable || drmFormat == DRM_FORMAT_NV12 ) + { + screenshotImageFlags.bExportable = true; + screenshotImageFlags.bLinear = + true; // TODO: support multi-planar DMA-BUF export via + // PipeWire + } + + bool bSuccess = pScreenshotImage->BInit( + width, height, 1u, drmFormat, screenshotImageFlags ); + pScreenshotImage->setStreamColorspace( colorspace ); + + assert( bSuccess ); + } + + if ( pScreenshotImage->GetRefCount( ) != 0 || + width != pScreenshotImage->width( ) || + height != pScreenshotImage->height( ) || + drmFormat != pScreenshotImage->drmFormat( ) ) + continue; + + return pScreenshotImage.get( ); + } + + vk_log.errorf( "Unable to acquire screenshot texture. Out of textures." ); + return nullptr; } // Internal display's native brightness. float g_flInternalDisplayBrightnessNits = 500.0f; -float g_flHDRItmSdrNits = 100.f; +float g_flHDRItmSdrNits = 100.f; float g_flHDRItmTargetNits = 1000.f; -#pragma pack(push, 1) +#pragma pack( push, 1 ) struct BlitPushData_t { - vec2_t scale[k_nMaxLayers]; - vec2_t offset[k_nMaxLayers]; - float opacity[k_nMaxLayers]; - glm::mat3x4 ctm[k_nMaxLayers]; - uint32_t borderMask; - uint32_t frameId; - uint32_t blurRadius; - - uint32_t u_shaderFilter; - uint32_t u_alphaMode; - - float u_linearToNits; // unset - float u_nitsToLinear; // unset - float u_itmSdrNits; // unset + vec2_t scale[ k_nMaxLayers ]; + vec2_t offset[ k_nMaxLayers ]; + float opacity[ k_nMaxLayers ]; + glm::mat3x4 ctm[ k_nMaxLayers ]; + uint32_t borderMask; + uint32_t frameId; + uint32_t blurRadius; + + uint32_t u_shaderFilter; + uint32_t u_alphaMode; + + float u_linearToNits; // unset + float u_nitsToLinear; // unset + float u_itmSdrNits; // unset float u_itmTargetNits; // unset - explicit BlitPushData_t(const struct FrameInfo_t *frameInfo) - { - u_shaderFilter = 0; - u_alphaMode = 0; - - for (int i = 0; i < frameInfo->layerCount; i++) { - const FrameInfo_t::Layer_t *layer = &frameInfo->layers[i]; - scale[i] = layer->scale; - offset[i] = layer->offsetPixelCenter(); - opacity[i] = layer->opacity; - if (layer->isScreenSize() || (layer->filter == GamescopeUpscaleFilter::LINEAR && layer->viewConvertsToLinearAutomatically())) - u_shaderFilter |= ((uint32_t)GamescopeUpscaleFilter::FROM_VIEW) << (i * 4); + explicit BlitPushData_t( const struct FrameInfo_t *frameInfo ) + { + u_shaderFilter = 0; + u_alphaMode = 0; + + for ( int i = 0; i < frameInfo->layerCount; i++ ) + { + const FrameInfo_t::Layer_t *layer = &frameInfo->layers[ i ]; + scale[ i ] = layer->scale; + offset[ i ] = layer->offsetPixelCenter( ); + opacity[ i ] = layer->opacity; + if ( layer->isScreenSize( ) || + ( layer->filter == GamescopeUpscaleFilter::LINEAR && + layer->viewConvertsToLinearAutomatically( ) ) ) + u_shaderFilter |= + ( ( uint32_t )GamescopeUpscaleFilter::FROM_VIEW ) + << ( i * 4 ); else - u_shaderFilter |= ((uint32_t)layer->filter) << (i * 4); - - u_alphaMode |= ((uint32_t)layer->eAlphaBlendingMode) << ( i * 4 ); - - if (layer->ctm) - { - ctm[i] = layer->ctm->View(); - } - else - { - ctm[i] = glm::mat3x4 - { - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0 - }; - } - } - - borderMask = frameInfo->borderMask(); - frameId = s_frameId++; - blurRadius = frameInfo->blurRadius ? ( frameInfo->blurRadius * 2 ) - 1 : 0; - - u_linearToNits = g_flInternalDisplayBrightnessNits; - u_nitsToLinear = 1.0f / g_flInternalDisplayBrightnessNits; - u_itmSdrNits = g_flHDRItmSdrNits; - u_itmTargetNits = g_flHDRItmTargetNits; - } - - explicit BlitPushData_t(float blit_scale) { - scale[0] = { blit_scale, blit_scale }; - offset[0] = { 0.5f, 0.5f }; - opacity[0] = 1.0f; - u_shaderFilter = (uint32_t)GamescopeUpscaleFilter::LINEAR; - u_alphaMode = 0; - ctm[0] = glm::mat3x4 - { - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0 - }; - borderMask = 0; - frameId = s_frameId; - - u_linearToNits = g_flInternalDisplayBrightnessNits; - u_nitsToLinear = 1.0f / g_flInternalDisplayBrightnessNits; - u_itmSdrNits = g_flHDRItmSdrNits; - u_itmTargetNits = g_flHDRItmTargetNits; - } + u_shaderFilter |= ( ( uint32_t )layer->filter ) << ( i * 4 ); + + u_alphaMode |= ( ( uint32_t )layer->eAlphaBlendingMode ) + << ( i * 4 ); + + if ( layer->ctm ) { ctm[ i ] = layer->ctm->View( ); } + else + { + ctm[ i ] = glm::mat3x4{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 }; + } + } + + borderMask = frameInfo->borderMask( ); + frameId = s_frameId++; + blurRadius = + frameInfo->blurRadius ? ( frameInfo->blurRadius * 2 ) - 1 : 0; + + u_linearToNits = g_flInternalDisplayBrightnessNits; + u_nitsToLinear = 1.0f / g_flInternalDisplayBrightnessNits; + u_itmSdrNits = g_flHDRItmSdrNits; + u_itmTargetNits = g_flHDRItmTargetNits; + } + + explicit BlitPushData_t( float blit_scale ) + { + scale[ 0 ] = { blit_scale, blit_scale }; + offset[ 0 ] = { 0.5f, 0.5f }; + opacity[ 0 ] = 1.0f; + u_shaderFilter = ( uint32_t )GamescopeUpscaleFilter::LINEAR; + u_alphaMode = 0; + ctm[ 0 ] = glm::mat3x4{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 }; + borderMask = 0; + frameId = s_frameId; + + u_linearToNits = g_flInternalDisplayBrightnessNits; + u_nitsToLinear = 1.0f / g_flInternalDisplayBrightnessNits; + u_itmSdrNits = g_flHDRItmSdrNits; + u_itmTargetNits = g_flHDRItmTargetNits; + } }; struct CaptureConvertBlitData_t { - vec2_t scale[1]; - vec2_t offset[1]; - float opacity[1]; - glm::mat3x4 ctm[1]; - mat3x4 outputCTM; - uint32_t borderMask; - uint32_t halfExtent[2]; - - explicit CaptureConvertBlitData_t(float blit_scale, const mat3x4 &color_matrix) { - scale[0] = { blit_scale, blit_scale }; - offset[0] = { 0.0f, 0.0f }; - opacity[0] = 1.0f; - borderMask = 0; - ctm[0] = glm::mat3x4 - { - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0 - }; - outputCTM = color_matrix; - } + vec2_t scale[ 1 ]; + vec2_t offset[ 1 ]; + float opacity[ 1 ]; + glm::mat3x4 ctm[ 1 ]; + mat3x4 outputCTM; + uint32_t borderMask; + uint32_t halfExtent[ 2 ]; + + explicit CaptureConvertBlitData_t( + float blit_scale, const mat3x4 &color_matrix ) + { + scale[ 0 ] = { blit_scale, blit_scale }; + offset[ 0 ] = { 0.0f, 0.0f }; + opacity[ 0 ] = 1.0f; + borderMask = 0; + ctm[ 0 ] = glm::mat3x4{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 }; + outputCTM = color_matrix; + } }; struct uvec4_t { - uint32_t x; - uint32_t y; - uint32_t z; - uint32_t w; + uint32_t x; + uint32_t y; + uint32_t z; + uint32_t w; }; struct uvec2_t { - uint32_t x; - uint32_t y; + uint32_t x; + uint32_t y; }; struct EasuPushData_t { - uvec4_t Const0; - uvec4_t Const1; - uvec4_t Const2; - uvec4_t Const3; - - EasuPushData_t(uint32_t inputX, uint32_t inputY, uint32_t tempX, uint32_t tempY) - { - FsrEasuCon(&Const0.x, &Const1.x, &Const2.x, &Const3.x, inputX, inputY, inputX, inputY, tempX, tempY); - } + uvec4_t Const0; + uvec4_t Const1; + uvec4_t Const2; + uvec4_t Const3; + + EasuPushData_t( + uint32_t inputX, uint32_t inputY, uint32_t tempX, uint32_t tempY ) + { + FsrEasuCon( + &Const0.x, + &Const1.x, + &Const2.x, + &Const3.x, + inputX, + inputY, + inputX, + inputY, + tempX, + tempY ); + } }; struct RcasPushData_t { - uvec2_t u_layer0Offset; - vec2_t u_scale[k_nMaxLayers - 1]; - vec2_t u_offset[k_nMaxLayers - 1]; - float u_opacity[k_nMaxLayers]; - glm::mat3x4 ctm[k_nMaxLayers]; - uint32_t u_borderMask; - uint32_t u_frameId; - uint32_t u_c1; - - uint32_t u_shaderFilter; - uint32_t u_alphaMode; - - float u_linearToNits; // unset - float u_nitsToLinear; // unset - float u_itmSdrNits; // unset + uvec2_t u_layer0Offset; + vec2_t u_scale[ k_nMaxLayers - 1 ]; + vec2_t u_offset[ k_nMaxLayers - 1 ]; + float u_opacity[ k_nMaxLayers ]; + glm::mat3x4 ctm[ k_nMaxLayers ]; + uint32_t u_borderMask; + uint32_t u_frameId; + uint32_t u_c1; + + uint32_t u_shaderFilter; + uint32_t u_alphaMode; + + float u_linearToNits; // unset + float u_nitsToLinear; // unset + float u_itmSdrNits; // unset float u_itmTargetNits; // unset - RcasPushData_t(const struct FrameInfo_t *frameInfo, float sharpness) - { - uvec4_t tmp; - FsrRcasCon(&tmp.x, sharpness); - u_layer0Offset.x = uint32_t(int32_t(frameInfo->layers[0].offset.x)); - u_layer0Offset.y = uint32_t(int32_t(frameInfo->layers[0].offset.y)); - u_borderMask = frameInfo->borderMask() >> 1u; - u_frameId = s_frameId++; - u_c1 = tmp.x; - u_shaderFilter = 0; - u_alphaMode = 0; - - for (int i = 0; i < frameInfo->layerCount; i++) - { - const FrameInfo_t::Layer_t *layer = &frameInfo->layers[i]; - - if (i == 0 || layer->isScreenSize() || (layer->filter == GamescopeUpscaleFilter::LINEAR && layer->viewConvertsToLinearAutomatically())) - u_shaderFilter |= ((uint32_t)GamescopeUpscaleFilter::FROM_VIEW) << (i * 4); + RcasPushData_t( const struct FrameInfo_t *frameInfo, float sharpness ) + { + uvec4_t tmp; + FsrRcasCon( &tmp.x, sharpness ); + u_layer0Offset.x = + uint32_t( int32_t( frameInfo->layers[ 0 ].offset.x ) ); + u_layer0Offset.y = + uint32_t( int32_t( frameInfo->layers[ 0 ].offset.y ) ); + u_borderMask = frameInfo->borderMask( ) >> 1u; + u_frameId = s_frameId++; + u_c1 = tmp.x; + u_shaderFilter = 0; + u_alphaMode = 0; + + for ( int i = 0; i < frameInfo->layerCount; i++ ) + { + const FrameInfo_t::Layer_t *layer = &frameInfo->layers[ i ]; + + if ( i == 0 || layer->isScreenSize( ) || + ( layer->filter == GamescopeUpscaleFilter::LINEAR && + layer->viewConvertsToLinearAutomatically( ) ) ) + u_shaderFilter |= + ( ( uint32_t )GamescopeUpscaleFilter::FROM_VIEW ) + << ( i * 4 ); + else + u_shaderFilter |= ( ( uint32_t )layer->filter ) << ( i * 4 ); + + u_alphaMode |= ( ( uint32_t )layer->eAlphaBlendingMode ) + << ( i * 4 ); + + if ( layer->ctm ) { ctm[ i ] = layer->ctm->View( ); } else - u_shaderFilter |= ((uint32_t)layer->filter) << (i * 4); - - u_alphaMode |= ((uint32_t)layer->eAlphaBlendingMode) << ( i * 4 ); - - if (layer->ctm) - { - ctm[i] = layer->ctm->View(); - } - else - { - ctm[i] = glm::mat3x4 - { - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0 - }; - } - - u_opacity[i] = frameInfo->layers[i].opacity; - } - - u_linearToNits = g_flInternalDisplayBrightnessNits; - u_nitsToLinear = 1.0f / g_flInternalDisplayBrightnessNits; - u_itmSdrNits = g_flHDRItmSdrNits; - u_itmTargetNits = g_flHDRItmTargetNits; - - for (uint32_t i = 1; i < k_nMaxLayers; i++) - { - u_scale[i - 1] = frameInfo->layers[i].scale; - u_offset[i - 1] = frameInfo->layers[i].offsetPixelCenter(); - } - } + { + ctm[ i ] = glm::mat3x4{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 }; + } + + u_opacity[ i ] = frameInfo->layers[ i ].opacity; + } + + u_linearToNits = g_flInternalDisplayBrightnessNits; + u_nitsToLinear = 1.0f / g_flInternalDisplayBrightnessNits; + u_itmSdrNits = g_flHDRItmSdrNits; + u_itmTargetNits = g_flHDRItmTargetNits; + + for ( uint32_t i = 1; i < k_nMaxLayers; i++ ) + { + u_scale[ i - 1 ] = frameInfo->layers[ i ].scale; + u_offset[ i - 1 ] = frameInfo->layers[ i ].offsetPixelCenter( ); + } + } }; struct NisPushData_t { - NISConfig nisConfig; - - NisPushData_t(uint32_t inputX, uint32_t inputY, uint32_t tempX, uint32_t tempY, float sharpness) - { - NVScalerUpdateConfig( - nisConfig, sharpness, - 0, 0, - inputX, inputY, - inputX, inputY, - 0, 0, - tempX, tempY, - tempX, tempY); - } + NISConfig nisConfig; + + NisPushData_t( + uint32_t inputX, + uint32_t inputY, + uint32_t tempX, + uint32_t tempY, + float sharpness ) + { + NVScalerUpdateConfig( + nisConfig, + sharpness, + 0, + 0, + inputX, + inputY, + inputX, + inputY, + 0, + 0, + tempX, + tempY, + tempX, + tempY ); + } }; -#pragma pack(pop) +#pragma pack( pop ) -void bind_all_layers(CVulkanCmdBuffer* cmdBuffer, const struct FrameInfo_t *frameInfo) +void bind_all_layers( + CVulkanCmdBuffer *cmdBuffer, const struct FrameInfo_t *frameInfo ) { - for ( int i = 0; i < frameInfo->layerCount; i++ ) - { - const FrameInfo_t::Layer_t *layer = &frameInfo->layers[i]; - - bool nearest = layer->isScreenSize() - || layer->filter == GamescopeUpscaleFilter::NEAREST - || (layer->filter == GamescopeUpscaleFilter::LINEAR && !layer->viewConvertsToLinearAutomatically()); - - cmdBuffer->bindTexture(i, layer->tex); - cmdBuffer->setTextureSrgb(i, layer->colorspace != GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR); - cmdBuffer->setSamplerNearest(i, nearest); - cmdBuffer->setSamplerUnnormalized(i, true); - } - for (uint32_t i = frameInfo->layerCount; i < VKR_SAMPLER_SLOTS; i++) - { - cmdBuffer->bindTexture(i, nullptr); - } + for ( int i = 0; i < frameInfo->layerCount; i++ ) + { + const FrameInfo_t::Layer_t *layer = &frameInfo->layers[ i ]; + + bool nearest = layer->isScreenSize( ) || + layer->filter == GamescopeUpscaleFilter::NEAREST || + ( layer->filter == GamescopeUpscaleFilter::LINEAR && + !layer->viewConvertsToLinearAutomatically( ) ); + + cmdBuffer->bindTexture( i, layer->tex ); + cmdBuffer->setTextureSrgb( + i, layer->colorspace != GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR ); + cmdBuffer->setSamplerNearest( i, nearest ); + cmdBuffer->setSamplerUnnormalized( i, true ); + } + for ( uint32_t i = frameInfo->layerCount; i < VKR_SAMPLER_SLOTS; i++ ) + { + cmdBuffer->bindTexture( i, nullptr ); + } } -std::optional vulkan_screenshot( const struct FrameInfo_t *frameInfo, gamescope::Rc pScreenshotTexture, gamescope::Rc pYUVOutTexture ) +std::optional vulkan_screenshot( + const struct FrameInfo_t *frameInfo, + gamescope::Rc pScreenshotTexture, + gamescope::Rc pYUVOutTexture ) { - EOTF outputTF = frameInfo->outputEncodingEOTF; - if (!frameInfo->applyOutputColorMgmt) - outputTF = EOTF_Count; //Disable blending stuff. - - auto cmdBuffer = g_device.commandBuffer(); - - for (uint32_t i = 0; i < EOTF_Count; i++) - cmdBuffer->bindColorMgmtLuts(i, frameInfo->shaperLut[i], frameInfo->lut3D[i]); + EOTF outputTF = frameInfo->outputEncodingEOTF; + if ( !frameInfo->applyOutputColorMgmt ) + outputTF = EOTF_Count; // Disable blending stuff. + + auto cmdBuffer = g_device.commandBuffer( ); + + for ( uint32_t i = 0; i < EOTF_Count; i++ ) + cmdBuffer->bindColorMgmtLuts( + i, frameInfo->shaperLut[ i ], frameInfo->lut3D[ i ] ); + + cmdBuffer->bindPipeline( g_device.pipeline( + SHADER_TYPE_BLIT, + frameInfo->layerCount, + frameInfo->ycbcrMask( ), + 0u, + frameInfo->colorspaceMask( ), + outputTF ) ); + bind_all_layers( cmdBuffer.get( ), frameInfo ); + cmdBuffer->bindTarget( pScreenshotTexture ); + cmdBuffer->uploadConstants( frameInfo ); + + const int pixelsPerGroup = 8; + + cmdBuffer->dispatch( + div_roundup( currentOutputWidth, pixelsPerGroup ), + div_roundup( currentOutputHeight, pixelsPerGroup ) ); + + if ( pYUVOutTexture != nullptr ) + { + float scale = + ( float )pScreenshotTexture->width( ) / pYUVOutTexture->width( ); + + CaptureConvertBlitData_t constants( + scale, + colorspace_to_conversion_from_srgb_matrix( + pYUVOutTexture->streamColorspace( ) ) ); + constants.halfExtent[ 0 ] = pYUVOutTexture->width( ) / 2.0f; + constants.halfExtent[ 1 ] = pYUVOutTexture->height( ) / 2.0f; + cmdBuffer->uploadConstants( constants ); + + for ( uint32_t i = 0; i < EOTF_Count; i++ ) + cmdBuffer->bindColorMgmtLuts( i, nullptr, nullptr ); + + cmdBuffer->bindPipeline( g_device.pipeline( + SHADER_TYPE_RGB_TO_NV12, + 1, + 0, + 0, + GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, + EOTF_Count ) ); + cmdBuffer->bindTexture( 0, pScreenshotTexture ); + cmdBuffer->setTextureSrgb( 0, true ); + cmdBuffer->setSamplerNearest( 0, false ); + cmdBuffer->setSamplerUnnormalized( 0, true ); + for ( uint32_t i = 1; i < VKR_SAMPLER_SLOTS; i++ ) + { + cmdBuffer->bindTexture( i, nullptr ); + } + cmdBuffer->bindTarget( pYUVOutTexture ); + + const int pixelsPerGroup = 8; + + // For ycbcr, we operate on 2 pixels at a time, so use the half-extent. + const int dispatchSize = pixelsPerGroup * 2; + + cmdBuffer->dispatch( + div_roundup( pYUVOutTexture->width( ), dispatchSize ), + div_roundup( pYUVOutTexture->height( ), dispatchSize ) ); + } - cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), outputTF )); - bind_all_layers(cmdBuffer.get(), frameInfo); - cmdBuffer->bindTarget(pScreenshotTexture); - cmdBuffer->uploadConstants(frameInfo); + uint64_t sequence = g_device.submit( std::move( cmdBuffer ) ); + return sequence; +} - const int pixelsPerGroup = 8; +extern std::string g_reshade_effect; +extern uint32_t g_reshade_technique_idx; - cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); +ReshadeEffectPipeline *g_pLastReshadeEffect = nullptr; - if ( pYUVOutTexture != nullptr ) - { - float scale = (float)pScreenshotTexture->width() / pYUVOutTexture->width(); +std::optional vulkan_composite( + struct FrameInfo_t *frameInfo, + gamescope::Rc pPipewireTexture, + bool partial, + gamescope::Rc pOutputOverride, + bool increment, + std::unique_ptr pInCommandBuffer ) +{ + EOTF outputTF = frameInfo->outputEncodingEOTF; + if ( !frameInfo->applyOutputColorMgmt ) + outputTF = EOTF_Count; // Disable blending stuff. - CaptureConvertBlitData_t constants( scale, colorspace_to_conversion_from_srgb_matrix( pYUVOutTexture->streamColorspace() ) ); - constants.halfExtent[0] = pYUVOutTexture->width() / 2.0f; - constants.halfExtent[1] = pYUVOutTexture->height() / 2.0f; - cmdBuffer->uploadConstants(constants); + g_pLastReshadeEffect = nullptr; + if ( !g_reshade_effect.empty( ) ) + { + if ( frameInfo->layers[ 0 ].tex ) + { + ReshadeEffectKey key{ + .path = g_reshade_effect, + .bufferWidth = frameInfo->layers[ 0 ].tex->width( ), + .bufferHeight = frameInfo->layers[ 0 ].tex->height( ), + .bufferColorSpace = frameInfo->layers[ 0 ].colorspace, + .bufferFormat = frameInfo->layers[ 0 ].tex->format( ), + .techniqueIdx = g_reshade_technique_idx, + }; + + ReshadeEffectPipeline *pipeline = g_reshadeManager.pipeline( key ); + g_pLastReshadeEffect = pipeline; + + if ( pipeline != nullptr ) + { + uint64_t seq = pipeline->execute( + frameInfo->layers[ 0 ].tex, &frameInfo->layers[ 0 ].tex ); + g_device.wait( seq ); + } + } + } + else + { + g_reshadeManager.clear( ); + } - for (uint32_t i = 0; i < EOTF_Count; i++) - cmdBuffer->bindColorMgmtLuts(i, nullptr, nullptr); + gamescope::Rc compositeImage; + if ( pOutputOverride ) compositeImage = pOutputOverride; + else + compositeImage = + partial ? g_output.outputImagesPartialOverlay[ g_output.nOutImage ] + : g_output.outputImages[ g_output.nOutImage ]; - cmdBuffer->bindPipeline(g_device.pipeline( SHADER_TYPE_RGB_TO_NV12, 1, 0, 0, GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, EOTF_Count )); - cmdBuffer->bindTexture(0, pScreenshotTexture); - cmdBuffer->setTextureSrgb(0, true); - cmdBuffer->setSamplerNearest(0, false); - cmdBuffer->setSamplerUnnormalized(0, true); - for (uint32_t i = 1; i < VKR_SAMPLER_SLOTS; i++) - { - cmdBuffer->bindTexture(i, nullptr); - } - cmdBuffer->bindTarget(pYUVOutTexture); + auto cmdBuffer = pInCommandBuffer ? std::move( pInCommandBuffer ) + : g_device.commandBuffer( ); - const int pixelsPerGroup = 8; + for ( uint32_t i = 0; i < EOTF_Count; i++ ) + cmdBuffer->bindColorMgmtLuts( + i, frameInfo->shaperLut[ i ], frameInfo->lut3D[ i ] ); - // For ycbcr, we operate on 2 pixels at a time, so use the half-extent. - const int dispatchSize = pixelsPerGroup * 2; + if ( frameInfo->useFSRLayer0 ) + { + uint32_t inputX = frameInfo->layers[ 0 ].tex->width( ); + uint32_t inputY = frameInfo->layers[ 0 ].tex->height( ); + + uint32_t tempX = frameInfo->layers[ 0 ].integerWidth( ); + uint32_t tempY = frameInfo->layers[ 0 ].integerHeight( ); + + update_tmp_images( tempX, tempY ); + + cmdBuffer->bindPipeline( g_device.pipeline( SHADER_TYPE_EASU ) ); + cmdBuffer->bindTarget( g_output.tmpOutput ); + cmdBuffer->bindTexture( 0, frameInfo->layers[ 0 ].tex ); + cmdBuffer->setTextureSrgb( 0, true ); + cmdBuffer->setSamplerUnnormalized( 0, false ); + cmdBuffer->setSamplerNearest( 0, false ); + cmdBuffer->uploadConstants( + inputX, inputY, tempX, tempY ); + + int pixelsPerGroup = 16; + + cmdBuffer->dispatch( + div_roundup( tempX, pixelsPerGroup ), + div_roundup( tempY, pixelsPerGroup ) ); + + cmdBuffer->bindPipeline( g_device.pipeline( + SHADER_TYPE_RCAS, + frameInfo->layerCount, + frameInfo->ycbcrMask( ) & ~1, + 0u, + frameInfo->colorspaceMask( ), + outputTF ) ); + bind_all_layers( cmdBuffer.get( ), frameInfo ); + cmdBuffer->bindTexture( 0, g_output.tmpOutput ); + cmdBuffer->setTextureSrgb( 0, true ); + cmdBuffer->setSamplerUnnormalized( 0, false ); + cmdBuffer->setSamplerNearest( 0, false ); + cmdBuffer->bindTarget( compositeImage ); + cmdBuffer->uploadConstants( + frameInfo, g_upscaleFilterSharpness / 10.0f ); + + cmdBuffer->dispatch( + div_roundup( currentOutputWidth, pixelsPerGroup ), + div_roundup( currentOutputHeight, pixelsPerGroup ) ); + } + else if ( frameInfo->useNISLayer0 ) + { + uint32_t inputX = frameInfo->layers[ 0 ].tex->width( ); + uint32_t inputY = frameInfo->layers[ 0 ].tex->height( ); + + uint32_t tempX = frameInfo->layers[ 0 ].integerWidth( ); + uint32_t tempY = frameInfo->layers[ 0 ].integerHeight( ); + + update_tmp_images( tempX, tempY ); + + float nisSharpness = ( 20 - g_upscaleFilterSharpness ) / 20.0f; + + cmdBuffer->bindPipeline( g_device.pipeline( SHADER_TYPE_NIS ) ); + cmdBuffer->bindTarget( g_output.tmpOutput ); + cmdBuffer->bindTexture( 0, frameInfo->layers[ 0 ].tex ); + cmdBuffer->setTextureSrgb( 0, true ); + cmdBuffer->setSamplerUnnormalized( 0, false ); + cmdBuffer->setSamplerNearest( 0, false ); + cmdBuffer->bindTexture( + VKR_NIS_COEF_SCALER_SLOT, g_output.nisScalerImage ); + cmdBuffer->setSamplerUnnormalized( VKR_NIS_COEF_SCALER_SLOT, false ); + cmdBuffer->setSamplerNearest( VKR_NIS_COEF_SCALER_SLOT, false ); + cmdBuffer->bindTexture( VKR_NIS_COEF_USM_SLOT, g_output.nisUsmImage ); + cmdBuffer->setSamplerUnnormalized( VKR_NIS_COEF_USM_SLOT, false ); + cmdBuffer->setSamplerNearest( VKR_NIS_COEF_USM_SLOT, false ); + cmdBuffer->uploadConstants( + inputX, inputY, tempX, tempY, nisSharpness ); + + int pixelsPerGroupX = 32; + int pixelsPerGroupY = 24; + + cmdBuffer->dispatch( + div_roundup( tempX, pixelsPerGroupX ), + div_roundup( tempY, pixelsPerGroupY ) ); + + struct FrameInfo_t nisFrameInfo = *frameInfo; + nisFrameInfo.layers[ 0 ].tex = g_output.tmpOutput; + nisFrameInfo.layers[ 0 ].scale.x = 1.0f; + nisFrameInfo.layers[ 0 ].scale.y = 1.0f; + + cmdBuffer->bindPipeline( g_device.pipeline( + SHADER_TYPE_BLIT, + nisFrameInfo.layerCount, + nisFrameInfo.ycbcrMask( ), + 0u, + nisFrameInfo.colorspaceMask( ), + outputTF ) ); + bind_all_layers( cmdBuffer.get( ), &nisFrameInfo ); + cmdBuffer->bindTarget( compositeImage ); + cmdBuffer->uploadConstants( &nisFrameInfo ); + + int pixelsPerGroup = 8; + + cmdBuffer->dispatch( + div_roundup( currentOutputWidth, pixelsPerGroup ), + div_roundup( currentOutputHeight, pixelsPerGroup ) ); + } + else if ( frameInfo->blurLayer0 ) + { + update_tmp_images( currentOutputWidth, currentOutputHeight ); + + ShaderType type = SHADER_TYPE_BLUR_FIRST_PASS; + + uint32_t blur_layer_count = 1; + // Also blur the override on top if we have one. + if ( frameInfo->layerCount >= 2 && + frameInfo->layers[ 1 ].zpos == g_zposOverride ) + blur_layer_count++; + + cmdBuffer->bindPipeline( g_device.pipeline( + type, + blur_layer_count, + frameInfo->ycbcrMask( ) & 0x3u, + 0, + frameInfo->colorspaceMask( ), + outputTF ) ); + cmdBuffer->bindTarget( g_output.tmpOutput ); + for ( uint32_t i = 0; i < blur_layer_count; i++ ) + { + cmdBuffer->bindTexture( i, frameInfo->layers[ i ].tex ); + cmdBuffer->setTextureSrgb( i, false ); + cmdBuffer->setSamplerUnnormalized( i, true ); + cmdBuffer->setSamplerNearest( i, false ); + } + cmdBuffer->uploadConstants( frameInfo ); + + int pixelsPerGroup = 8; + + cmdBuffer->dispatch( + div_roundup( currentOutputWidth, pixelsPerGroup ), + div_roundup( currentOutputHeight, pixelsPerGroup ) ); + + bool useSrgbView = frameInfo->layers[ 0 ].colorspace == + GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; + + type = frameInfo->blurLayer0 == BLUR_MODE_COND ? SHADER_TYPE_BLUR_COND + : SHADER_TYPE_BLUR; + cmdBuffer->bindPipeline( g_device.pipeline( + type, + frameInfo->layerCount, + frameInfo->ycbcrMask( ), + blur_layer_count, + frameInfo->colorspaceMask( ), + outputTF ) ); + bind_all_layers( cmdBuffer.get( ), frameInfo ); + cmdBuffer->bindTarget( compositeImage ); + cmdBuffer->bindTexture( VKR_BLUR_EXTRA_SLOT, g_output.tmpOutput ); + cmdBuffer->setTextureSrgb( + VKR_BLUR_EXTRA_SLOT, + !useSrgbView ); // Inverted because it chooses whether to view as + // linear (sRGB view) or sRGB (raw view). It's + // horrible. I need to change it. + cmdBuffer->setSamplerUnnormalized( VKR_BLUR_EXTRA_SLOT, true ); + cmdBuffer->setSamplerNearest( VKR_BLUR_EXTRA_SLOT, false ); + + cmdBuffer->dispatch( + div_roundup( currentOutputWidth, pixelsPerGroup ), + div_roundup( currentOutputHeight, pixelsPerGroup ) ); + } + else + { + cmdBuffer->bindPipeline( g_device.pipeline( + SHADER_TYPE_BLIT, + frameInfo->layerCount, + frameInfo->ycbcrMask( ), + 0u, + frameInfo->colorspaceMask( ), + outputTF ) ); + bind_all_layers( cmdBuffer.get( ), frameInfo ); + cmdBuffer->bindTarget( compositeImage ); + cmdBuffer->uploadConstants( frameInfo ); + + const int pixelsPerGroup = 8; + + cmdBuffer->dispatch( + div_roundup( currentOutputWidth, pixelsPerGroup ), + div_roundup( currentOutputHeight, pixelsPerGroup ) ); + } - cmdBuffer->dispatch(div_roundup(pYUVOutTexture->width(), dispatchSize), div_roundup(pYUVOutTexture->height(), dispatchSize)); - } + if ( pPipewireTexture != nullptr ) + { - uint64_t sequence = g_device.submit(std::move(cmdBuffer)); - return sequence; -} + if ( compositeImage->format( ) == pPipewireTexture->format( ) && + compositeImage->width( ) == pPipewireTexture->width( ) && + compositeImage->height( ) == pPipewireTexture->height( ) ) + { + cmdBuffer->copyImage( compositeImage, pPipewireTexture ); + } + else + { + const bool ycbcr = pPipewireTexture->isYcbcr( ); + + float scale = + ( float )compositeImage->width( ) / pPipewireTexture->width( ); + if ( ycbcr ) + { + CaptureConvertBlitData_t constants( + scale, + colorspace_to_conversion_from_srgb_matrix( + pPipewireTexture->streamColorspace( ) ) ); + constants.halfExtent[ 0 ] = pPipewireTexture->width( ) / 2.0f; + constants.halfExtent[ 1 ] = pPipewireTexture->height( ) / 2.0f; + cmdBuffer->uploadConstants( + constants ); + } + else + { + BlitPushData_t constants( scale ); + cmdBuffer->uploadConstants( constants ); + } + + for ( uint32_t i = 0; i < EOTF_Count; i++ ) + cmdBuffer->bindColorMgmtLuts( i, nullptr, nullptr ); + + cmdBuffer->bindPipeline( g_device.pipeline( + ycbcr ? SHADER_TYPE_RGB_TO_NV12 : SHADER_TYPE_BLIT, + 1, + 0, + 0, + GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, + EOTF_Count ) ); + cmdBuffer->bindTexture( 0, compositeImage ); + cmdBuffer->setTextureSrgb( 0, true ); + cmdBuffer->setSamplerNearest( 0, false ); + cmdBuffer->setSamplerUnnormalized( 0, true ); + for ( uint32_t i = 1; i < VKR_SAMPLER_SLOTS; i++ ) + { + cmdBuffer->bindTexture( i, nullptr ); + } + cmdBuffer->bindTarget( pPipewireTexture ); + + const int pixelsPerGroup = 8; + + // For ycbcr, we operate on 2 pixels at a time, so use the + // half-extent. + const int dispatchSize = + ycbcr ? pixelsPerGroup * 2 : pixelsPerGroup; + + cmdBuffer->dispatch( + div_roundup( pPipewireTexture->width( ), dispatchSize ), + div_roundup( pPipewireTexture->height( ), dispatchSize ) ); + } + } -extern std::string g_reshade_effect; -extern uint32_t g_reshade_technique_idx; + uint64_t sequence = g_device.submit( std::move( cmdBuffer ) ); -ReshadeEffectPipeline *g_pLastReshadeEffect = nullptr; + if ( !GetBackend( )->UsesVulkanSwapchain( ) && pOutputOverride == nullptr && + increment ) + { + g_output.nOutImage = ( g_output.nOutImage + 1 ) % 3; + } -std::optional vulkan_composite( struct FrameInfo_t *frameInfo, gamescope::Rc pPipewireTexture, bool partial, gamescope::Rc pOutputOverride, bool increment, std::unique_ptr pInCommandBuffer ) -{ - EOTF outputTF = frameInfo->outputEncodingEOTF; - if (!frameInfo->applyOutputColorMgmt) - outputTF = EOTF_Count; //Disable blending stuff. - - g_pLastReshadeEffect = nullptr; - if (!g_reshade_effect.empty()) - { - if (frameInfo->layers[0].tex) - { - ReshadeEffectKey key - { - .path = g_reshade_effect, - .bufferWidth = frameInfo->layers[0].tex->width(), - .bufferHeight = frameInfo->layers[0].tex->height(), - .bufferColorSpace = frameInfo->layers[0].colorspace, - .bufferFormat = frameInfo->layers[0].tex->format(), - .techniqueIdx = g_reshade_technique_idx, - }; - - ReshadeEffectPipeline* pipeline = g_reshadeManager.pipeline(key); - g_pLastReshadeEffect = pipeline; - - if (pipeline != nullptr) - { - uint64_t seq = pipeline->execute(frameInfo->layers[0].tex, &frameInfo->layers[0].tex); - g_device.wait(seq); - } - } - } - else - { - g_reshadeManager.clear(); - } - - gamescope::Rc compositeImage; - if ( pOutputOverride ) - compositeImage = pOutputOverride; - else - compositeImage = partial ? g_output.outputImagesPartialOverlay[ g_output.nOutImage ] : g_output.outputImages[ g_output.nOutImage ]; - - auto cmdBuffer = pInCommandBuffer ? std::move( pInCommandBuffer ) : g_device.commandBuffer(); - - for (uint32_t i = 0; i < EOTF_Count; i++) - cmdBuffer->bindColorMgmtLuts(i, frameInfo->shaperLut[i], frameInfo->lut3D[i]); - - if ( frameInfo->useFSRLayer0 ) - { - uint32_t inputX = frameInfo->layers[0].tex->width(); - uint32_t inputY = frameInfo->layers[0].tex->height(); - - uint32_t tempX = frameInfo->layers[0].integerWidth(); - uint32_t tempY = frameInfo->layers[0].integerHeight(); - - update_tmp_images(tempX, tempY); - - cmdBuffer->bindPipeline(g_device.pipeline(SHADER_TYPE_EASU)); - cmdBuffer->bindTarget(g_output.tmpOutput); - cmdBuffer->bindTexture(0, frameInfo->layers[0].tex); - cmdBuffer->setTextureSrgb(0, true); - cmdBuffer->setSamplerUnnormalized(0, false); - cmdBuffer->setSamplerNearest(0, false); - cmdBuffer->uploadConstants(inputX, inputY, tempX, tempY); - - int pixelsPerGroup = 16; - - cmdBuffer->dispatch(div_roundup(tempX, pixelsPerGroup), div_roundup(tempY, pixelsPerGroup)); - - cmdBuffer->bindPipeline(g_device.pipeline(SHADER_TYPE_RCAS, frameInfo->layerCount, frameInfo->ycbcrMask() & ~1, 0u, frameInfo->colorspaceMask(), outputTF )); - bind_all_layers(cmdBuffer.get(), frameInfo); - cmdBuffer->bindTexture(0, g_output.tmpOutput); - cmdBuffer->setTextureSrgb(0, true); - cmdBuffer->setSamplerUnnormalized(0, false); - cmdBuffer->setSamplerNearest(0, false); - cmdBuffer->bindTarget(compositeImage); - cmdBuffer->uploadConstants(frameInfo, g_upscaleFilterSharpness / 10.0f); - - cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); - } - else if ( frameInfo->useNISLayer0 ) - { - uint32_t inputX = frameInfo->layers[0].tex->width(); - uint32_t inputY = frameInfo->layers[0].tex->height(); - - uint32_t tempX = frameInfo->layers[0].integerWidth(); - uint32_t tempY = frameInfo->layers[0].integerHeight(); - - update_tmp_images(tempX, tempY); - - float nisSharpness = (20 - g_upscaleFilterSharpness) / 20.0f; - - cmdBuffer->bindPipeline(g_device.pipeline(SHADER_TYPE_NIS)); - cmdBuffer->bindTarget(g_output.tmpOutput); - cmdBuffer->bindTexture(0, frameInfo->layers[0].tex); - cmdBuffer->setTextureSrgb(0, true); - cmdBuffer->setSamplerUnnormalized(0, false); - cmdBuffer->setSamplerNearest(0, false); - cmdBuffer->bindTexture(VKR_NIS_COEF_SCALER_SLOT, g_output.nisScalerImage); - cmdBuffer->setSamplerUnnormalized(VKR_NIS_COEF_SCALER_SLOT, false); - cmdBuffer->setSamplerNearest(VKR_NIS_COEF_SCALER_SLOT, false); - cmdBuffer->bindTexture(VKR_NIS_COEF_USM_SLOT, g_output.nisUsmImage); - cmdBuffer->setSamplerUnnormalized(VKR_NIS_COEF_USM_SLOT, false); - cmdBuffer->setSamplerNearest(VKR_NIS_COEF_USM_SLOT, false); - cmdBuffer->uploadConstants(inputX, inputY, tempX, tempY, nisSharpness); - - int pixelsPerGroupX = 32; - int pixelsPerGroupY = 24; - - cmdBuffer->dispatch(div_roundup(tempX, pixelsPerGroupX), div_roundup(tempY, pixelsPerGroupY)); - - struct FrameInfo_t nisFrameInfo = *frameInfo; - nisFrameInfo.layers[0].tex = g_output.tmpOutput; - nisFrameInfo.layers[0].scale.x = 1.0f; - nisFrameInfo.layers[0].scale.y = 1.0f; - - cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, nisFrameInfo.layerCount, nisFrameInfo.ycbcrMask(), 0u, nisFrameInfo.colorspaceMask(), outputTF )); - bind_all_layers(cmdBuffer.get(), &nisFrameInfo); - cmdBuffer->bindTarget(compositeImage); - cmdBuffer->uploadConstants(&nisFrameInfo); - - int pixelsPerGroup = 8; - - cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); - } - else if ( frameInfo->blurLayer0 ) - { - update_tmp_images(currentOutputWidth, currentOutputHeight); - - ShaderType type = SHADER_TYPE_BLUR_FIRST_PASS; - - uint32_t blur_layer_count = 1; - // Also blur the override on top if we have one. - if (frameInfo->layerCount >= 2 && frameInfo->layers[1].zpos == g_zposOverride) - blur_layer_count++; - - cmdBuffer->bindPipeline(g_device.pipeline(type, blur_layer_count, frameInfo->ycbcrMask() & 0x3u, 0, frameInfo->colorspaceMask(), outputTF )); - cmdBuffer->bindTarget(g_output.tmpOutput); - for (uint32_t i = 0; i < blur_layer_count; i++) - { - cmdBuffer->bindTexture(i, frameInfo->layers[i].tex); - cmdBuffer->setTextureSrgb(i, false); - cmdBuffer->setSamplerUnnormalized(i, true); - cmdBuffer->setSamplerNearest(i, false); - } - cmdBuffer->uploadConstants(frameInfo); - - int pixelsPerGroup = 8; - - cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); - - bool useSrgbView = frameInfo->layers[0].colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; - - type = frameInfo->blurLayer0 == BLUR_MODE_COND ? SHADER_TYPE_BLUR_COND : SHADER_TYPE_BLUR; - cmdBuffer->bindPipeline(g_device.pipeline(type, frameInfo->layerCount, frameInfo->ycbcrMask(), blur_layer_count, frameInfo->colorspaceMask(), outputTF )); - bind_all_layers(cmdBuffer.get(), frameInfo); - cmdBuffer->bindTarget(compositeImage); - cmdBuffer->bindTexture(VKR_BLUR_EXTRA_SLOT, g_output.tmpOutput); - cmdBuffer->setTextureSrgb(VKR_BLUR_EXTRA_SLOT, !useSrgbView); // Inverted because it chooses whether to view as linear (sRGB view) or sRGB (raw view). It's horrible. I need to change it. - cmdBuffer->setSamplerUnnormalized(VKR_BLUR_EXTRA_SLOT, true); - cmdBuffer->setSamplerNearest(VKR_BLUR_EXTRA_SLOT, false); - - cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); - } - else - { - cmdBuffer->bindPipeline( g_device.pipeline(SHADER_TYPE_BLIT, frameInfo->layerCount, frameInfo->ycbcrMask(), 0u, frameInfo->colorspaceMask(), outputTF )); - bind_all_layers(cmdBuffer.get(), frameInfo); - cmdBuffer->bindTarget(compositeImage); - cmdBuffer->uploadConstants(frameInfo); - - const int pixelsPerGroup = 8; - - cmdBuffer->dispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); - } - - if ( pPipewireTexture != nullptr ) - { - - if (compositeImage->format() == pPipewireTexture->format() && - compositeImage->width() == pPipewireTexture->width() && - compositeImage->height() == pPipewireTexture->height()) { - cmdBuffer->copyImage(compositeImage, pPipewireTexture); - } else { - const bool ycbcr = pPipewireTexture->isYcbcr(); - - float scale = (float)compositeImage->width() / pPipewireTexture->width(); - if ( ycbcr ) - { - CaptureConvertBlitData_t constants( scale, colorspace_to_conversion_from_srgb_matrix( pPipewireTexture->streamColorspace() ) ); - constants.halfExtent[0] = pPipewireTexture->width() / 2.0f; - constants.halfExtent[1] = pPipewireTexture->height() / 2.0f; - cmdBuffer->uploadConstants(constants); - } - else - { - BlitPushData_t constants( scale ); - cmdBuffer->uploadConstants(constants); - } - - for (uint32_t i = 0; i < EOTF_Count; i++) - cmdBuffer->bindColorMgmtLuts(i, nullptr, nullptr); - - cmdBuffer->bindPipeline(g_device.pipeline( ycbcr ? SHADER_TYPE_RGB_TO_NV12 : SHADER_TYPE_BLIT, 1, 0, 0, GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, EOTF_Count )); - cmdBuffer->bindTexture(0, compositeImage); - cmdBuffer->setTextureSrgb(0, true); - cmdBuffer->setSamplerNearest(0, false); - cmdBuffer->setSamplerUnnormalized(0, true); - for (uint32_t i = 1; i < VKR_SAMPLER_SLOTS; i++) - { - cmdBuffer->bindTexture(i, nullptr); - } - cmdBuffer->bindTarget(pPipewireTexture); - - const int pixelsPerGroup = 8; - - // For ycbcr, we operate on 2 pixels at a time, so use the half-extent. - const int dispatchSize = ycbcr ? pixelsPerGroup * 2 : pixelsPerGroup; - - cmdBuffer->dispatch(div_roundup(pPipewireTexture->width(), dispatchSize), div_roundup(pPipewireTexture->height(), dispatchSize)); - } - } - - uint64_t sequence = g_device.submit(std::move(cmdBuffer)); - - if ( !GetBackend()->UsesVulkanSwapchain() && pOutputOverride == nullptr && increment ) - { - g_output.nOutImage = ( g_output.nOutImage + 1 ) % 3; - } - - return sequence; + return sequence; } void vulkan_wait( uint64_t ulSeqNo, bool bReset ) -{ - return g_device.wait( ulSeqNo, bReset ); -} +{ return g_device.wait( ulSeqNo, bReset ); } -bool vulkan_has_drm_props() +bool vulkan_has_drm_props( ) { - for (const auto& ext : g_device.supportedExtensions()) { - if ( strcmp(ext.extensionName, VK_EXT_PHYSICAL_DEVICE_DRM_EXTENSION_NAME) == 0 ) - return true; - } + for ( const auto &ext : g_device.supportedExtensions( ) ) + { + if ( strcmp( + ext.extensionName, + VK_EXT_PHYSICAL_DEVICE_DRM_EXTENSION_NAME ) == 0 ) + return true; + } - return false; + return false; } -gamescope::Rc vulkan_get_last_output_image( bool partial, bool defer ) +gamescope::Rc +vulkan_get_last_output_image( bool partial, bool defer ) { - // Get previous image ( +2 ) - // 1 2 3 - // | - // | - uint32_t nRegularImage = ( g_output.nOutImage + 2 ) % 3; - - // Get previous previous image ( +1 ) - // 1 2 3 - // | - // | - uint32_t nDeferredImage = ( g_output.nOutImage + 1 ) % 3; + // Get previous image ( +2 ) + // 1 2 3 + // | + // | + uint32_t nRegularImage = ( g_output.nOutImage + 2 ) % 3; - uint32_t nOutImage = defer ? nDeferredImage : nRegularImage; + // Get previous previous image ( +1 ) + // 1 2 3 + // | + // | + uint32_t nDeferredImage = ( g_output.nOutImage + 1 ) % 3; - if ( partial ) - { + uint32_t nOutImage = defer ? nDeferredImage : nRegularImage; - //vk_log.infof( "Partial overlay frame: %d", nDeferredImage ); - return g_output.outputImagesPartialOverlay[ nOutImage ]; - } + if ( partial ) + { + // vk_log.infof( "Partial overlay frame: %d", nDeferredImage ); + return g_output.outputImagesPartialOverlay[ nOutImage ]; + } - return g_output.outputImages[ nOutImage ]; + return g_output.outputImages[ nOutImage ]; } -bool vulkan_primary_dev_id(dev_t *id) +bool vulkan_primary_dev_id( dev_t *id ) { - *id = g_device.primaryDevId(); - return g_device.hasDrmPrimaryDevId(); + *id = g_device.primaryDevId( ); + return g_device.hasDrmPrimaryDevId( ); } -bool vulkan_supports_modifiers(void) -{ - return g_device.supportsModifiers(); -} +bool vulkan_supports_modifiers( void ) { return g_device.supportsModifiers( ); } static void texture_destroy( struct wlr_texture *wlr_texture ) { - VulkanWlrTexture_t *tex = (VulkanWlrTexture_t *)wlr_texture; - wlr_buffer_unlock( tex->buf ); - delete tex; + VulkanWlrTexture_t *tex = ( VulkanWlrTexture_t * )wlr_texture; + wlr_buffer_unlock( tex->buf ); + delete tex; } static const struct wlr_texture_impl texture_impl = { - .destroy = texture_destroy, + .destroy = texture_destroy, }; -static const struct wlr_drm_format_set *renderer_get_texture_formats( struct wlr_renderer *wlr_renderer, uint32_t buffer_caps ) +static const struct wlr_drm_format_set *renderer_get_texture_formats( + struct wlr_renderer *wlr_renderer, uint32_t buffer_caps ) { - if (buffer_caps & WLR_BUFFER_CAP_DMABUF) - { - return &sampledDRMFormats; - } - else if (buffer_caps & WLR_BUFFER_CAP_DATA_PTR) - { - return &sampledShmFormats; - } - else - { - return nullptr; - } + if ( buffer_caps & WLR_BUFFER_CAP_DMABUF ) { return &sampledDRMFormats; } + else if ( buffer_caps & WLR_BUFFER_CAP_DATA_PTR ) + { + return &sampledShmFormats; + } + else + { + return nullptr; + } } static int renderer_get_drm_fd( struct wlr_renderer *wlr_renderer ) -{ - return g_device.drmRenderFd(); -} +{ return g_device.drmRenderFd( ); } -static struct wlr_texture *renderer_texture_from_buffer( struct wlr_renderer *wlr_renderer, struct wlr_buffer *buf ) +static struct wlr_texture *renderer_texture_from_buffer( + struct wlr_renderer *wlr_renderer, struct wlr_buffer *buf ) { - VulkanWlrTexture_t *tex = new VulkanWlrTexture_t(); - wlr_texture_init( &tex->base, wlr_renderer, &texture_impl, buf->width, buf->height ); - tex->buf = wlr_buffer_lock( buf ); - // TODO: check format/modifier - // TODO: if DMA-BUF, try importing it into Vulkan - return &tex->base; + VulkanWlrTexture_t *tex = new VulkanWlrTexture_t( ); + wlr_texture_init( + &tex->base, wlr_renderer, &texture_impl, buf->width, buf->height ); + tex->buf = wlr_buffer_lock( buf ); + // TODO: check format/modifier + // TODO: if DMA-BUF, try importing it into Vulkan + return &tex->base; } -static struct wlr_render_pass *renderer_begin_buffer_pass( struct wlr_renderer *renderer, struct wlr_buffer *buffer, const struct wlr_buffer_pass_options *options ) +static struct wlr_render_pass *renderer_begin_buffer_pass( + struct wlr_renderer *renderer, + struct wlr_buffer *buffer, + const struct wlr_buffer_pass_options *options ) { - abort(); // unreachable + abort( ); // unreachable } static const struct wlr_renderer_impl renderer_impl = { - .get_texture_formats = renderer_get_texture_formats, - .get_drm_fd = renderer_get_drm_fd, - .texture_from_buffer = renderer_texture_from_buffer, - .begin_buffer_pass = renderer_begin_buffer_pass, + .get_texture_formats = renderer_get_texture_formats, + .get_drm_fd = renderer_get_drm_fd, + .texture_from_buffer = renderer_texture_from_buffer, + .begin_buffer_pass = renderer_begin_buffer_pass, }; struct wlr_renderer *vulkan_renderer_create( void ) { - VulkanRenderer_t *renderer = new VulkanRenderer_t(); - wlr_renderer_init(&renderer->base, &renderer_impl, WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR); - return &renderer->base; + VulkanRenderer_t *renderer = new VulkanRenderer_t( ); + wlr_renderer_init( + &renderer->base, + &renderer_impl, + WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_DATA_PTR ); + return &renderer->base; } -gamescope::OwningRc vulkan_create_texture_from_wlr_buffer( struct wlr_buffer *buf, gamescope::OwningRc pBackendFb ) +gamescope::OwningRc vulkan_create_texture_from_wlr_buffer( + struct wlr_buffer *buf, + gamescope::OwningRc pBackendFb ) { - struct wlr_dmabuf_attributes dmabuf = {0}; - if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) - { - return vulkan_create_texture_from_dmabuf( &dmabuf, pBackendFb ); - } - - VkResult result; - - void *src; - uint32_t drmFormat; - size_t stride; - if ( !wlr_buffer_begin_data_ptr_access( buf, WLR_BUFFER_DATA_PTR_ACCESS_READ, &src, &drmFormat, &stride ) ) - { - return nullptr; - } - - uint32_t width = buf->width; - uint32_t height = buf->height; - - VkBufferCreateInfo bufferCreateInfo = { - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .size = stride * height, - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - }; - VkBuffer buffer; - result = g_device.vk.CreateBuffer( g_device.device(), &bufferCreateInfo, nullptr, &buffer ); - if ( result != VK_SUCCESS ) - { - wlr_buffer_end_data_ptr_access( buf ); - return nullptr; - } - - VkMemoryRequirements memRequirements; - g_device.vk.GetBufferMemoryRequirements(g_device.device(), buffer, &memRequirements); - - uint32_t memTypeIndex = g_device.findMemoryType(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT|VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, memRequirements.memoryTypeBits ); - if ( memTypeIndex == ~0u ) - { - wlr_buffer_end_data_ptr_access( buf ); - return nullptr; - } - - VkMemoryAllocateInfo allocInfo = { - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .allocationSize = memRequirements.size, - .memoryTypeIndex = memTypeIndex, - }; + struct wlr_dmabuf_attributes dmabuf = { 0 }; + if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) + { + return vulkan_create_texture_from_dmabuf( &dmabuf, pBackendFb ); + } + + VkResult result; + + void *src; + uint32_t drmFormat; + size_t stride; + if ( !wlr_buffer_begin_data_ptr_access( + buf, WLR_BUFFER_DATA_PTR_ACCESS_READ, &src, &drmFormat, &stride ) ) + { + return nullptr; + } + + uint32_t width = buf->width; + uint32_t height = buf->height; + + VkBufferCreateInfo bufferCreateInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = stride * height, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + }; + VkBuffer buffer; + result = g_device.vk.CreateBuffer( + g_device.device( ), &bufferCreateInfo, nullptr, &buffer ); + if ( result != VK_SUCCESS ) + { + wlr_buffer_end_data_ptr_access( buf ); + return nullptr; + } + + VkMemoryRequirements memRequirements; + g_device.vk.GetBufferMemoryRequirements( + g_device.device( ), buffer, &memRequirements ); + + uint32_t memTypeIndex = g_device.findMemoryType( + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + memRequirements.memoryTypeBits ); + if ( memTypeIndex == ~0u ) + { + wlr_buffer_end_data_ptr_access( buf ); + return nullptr; + } + + VkMemoryAllocateInfo allocInfo = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memRequirements.size, + .memoryTypeIndex = memTypeIndex, + }; - VkDeviceMemory bufferMemory; - result = g_device.vk.AllocateMemory( g_device.device(), &allocInfo, nullptr, &bufferMemory); - if ( result != VK_SUCCESS ) - { - wlr_buffer_end_data_ptr_access( buf ); - return nullptr; - } + VkDeviceMemory bufferMemory; + result = g_device.vk.AllocateMemory( + g_device.device( ), &allocInfo, nullptr, &bufferMemory ); + if ( result != VK_SUCCESS ) + { + wlr_buffer_end_data_ptr_access( buf ); + return nullptr; + } - result = g_device.vk.BindBufferMemory( g_device.device(), buffer, bufferMemory, 0 ); - if ( result != VK_SUCCESS ) - { - wlr_buffer_end_data_ptr_access( buf ); - return nullptr; - } + result = g_device.vk.BindBufferMemory( + g_device.device( ), buffer, bufferMemory, 0 ); + if ( result != VK_SUCCESS ) + { + wlr_buffer_end_data_ptr_access( buf ); + return nullptr; + } - void *dst; - result = g_device.vk.MapMemory( g_device.device(), bufferMemory, 0, VK_WHOLE_SIZE, 0, &dst ); - if ( result != VK_SUCCESS ) - { - wlr_buffer_end_data_ptr_access( buf ); - return nullptr; - } + void *dst; + result = g_device.vk.MapMemory( + g_device.device( ), bufferMemory, 0, VK_WHOLE_SIZE, 0, &dst ); + if ( result != VK_SUCCESS ) + { + wlr_buffer_end_data_ptr_access( buf ); + return nullptr; + } - memcpy( dst, src, stride * height ); + memcpy( dst, src, stride * height ); - g_device.vk.UnmapMemory( g_device.device(), bufferMemory ); + g_device.vk.UnmapMemory( g_device.device( ), bufferMemory ); - wlr_buffer_end_data_ptr_access( buf ); + wlr_buffer_end_data_ptr_access( buf ); - gamescope::OwningRc pTex = new CVulkanTexture(); - CVulkanTexture::createFlags texCreateFlags; - texCreateFlags.bSampled = true; - texCreateFlags.bTransferDst = true; - texCreateFlags.bFlippable = true; - if ( pTex->BInit( width, height, 1u, drmFormat, texCreateFlags, nullptr, 0, 0, nullptr, pBackendFb ) == false ) - return nullptr; + gamescope::OwningRc pTex = new CVulkanTexture( ); + CVulkanTexture::createFlags texCreateFlags; + texCreateFlags.bSampled = true; + texCreateFlags.bTransferDst = true; + texCreateFlags.bFlippable = true; + if ( pTex->BInit( + width, + height, + 1u, + drmFormat, + texCreateFlags, + nullptr, + 0, + 0, + nullptr, + pBackendFb ) == false ) + return nullptr; - auto cmdBuffer = g_device.commandBuffer(); + auto cmdBuffer = g_device.commandBuffer( ); - cmdBuffer->copyBufferToImage( buffer, 0, stride / DRMFormatGetBPP(drmFormat), pTex); - // TODO: Sync this copyBufferToImage + cmdBuffer->copyBufferToImage( + buffer, 0, stride / DRMFormatGetBPP( drmFormat ), pTex ); + // TODO: Sync this copyBufferToImage - uint64_t sequence = g_device.submit(std::move(cmdBuffer)); + uint64_t sequence = g_device.submit( std::move( cmdBuffer ) ); - g_device.wait(sequence); + g_device.wait( sequence ); - g_device.vk.DestroyBuffer(g_device.device(), buffer, nullptr); - g_device.vk.FreeMemory(g_device.device(), bufferMemory, nullptr); + g_device.vk.DestroyBuffer( g_device.device( ), buffer, nullptr ); + g_device.vk.FreeMemory( g_device.device( ), bufferMemory, nullptr ); - return pTex; + return pTex; } diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp index 4b65f9317f..4ae94ac778 100644 --- a/src/rendervulkan.hpp +++ b/src/rendervulkan.hpp @@ -2,20 +2,20 @@ #pragma once -#include -#include -#include -#include -#include #include +#include #include +#include +#include #include #include +#include +#include #include "main.hpp" -#include "gamescope_shared.h" #include "backend.h" +#include "gamescope_shared.h" #include "shaders/descriptor_set_constants.h" @@ -42,376 +42,420 @@ class CVulkanCmdBuffer; #define k_nMaxBlurLayers 2 -#define kMaxBlurRadius (37u / 2 + 1) +#define kMaxBlurRadius ( 37u / 2 + 1 ) -enum BlurMode { - BLUR_MODE_OFF = 0, - BLUR_MODE_COND = 1, +enum BlurMode +{ + BLUR_MODE_OFF = 0, + BLUR_MODE_COND = 1, BLUR_MODE_ALWAYS = 2, }; enum EStreamColorspace : int { - k_EStreamColorspace_Unknown = 0, - k_EStreamColorspace_BT601 = 1, - k_EStreamColorspace_BT601_Full = 2, - k_EStreamColorspace_BT709 = 3, - k_EStreamColorspace_BT709_Full = 4 + k_EStreamColorspace_Unknown = 0, + k_EStreamColorspace_BT601 = 1, + k_EStreamColorspace_BT601_Full = 2, + k_EStreamColorspace_BT709 = 3, + k_EStreamColorspace_BT709_Full = 4 }; +#include #include #include #include -#include #include -#include "wlr_begin.hpp" #include #include +#include "wlr_begin.hpp" #include "wlr_end.hpp" #define VK_NO_PROTOTYPES -#include #include +#include struct VulkanRenderer_t { - struct wlr_renderer base; + struct wlr_renderer base; }; struct VulkanWlrTexture_t { - struct wlr_texture base; - struct wlr_buffer *buf; + struct wlr_texture base; + struct wlr_buffer *buf; }; inline VkFormat ToSrgbVulkanFormat( VkFormat format ) { - switch ( format ) - { - case VK_FORMAT_B8G8R8A8_UNORM: return VK_FORMAT_B8G8R8A8_SRGB; - case VK_FORMAT_R8G8B8A8_UNORM: return VK_FORMAT_R8G8B8A8_SRGB; - default: return format; - } + switch ( format ) + { + case VK_FORMAT_B8G8R8A8_UNORM: + return VK_FORMAT_B8G8R8A8_SRGB; + case VK_FORMAT_R8G8B8A8_UNORM: + return VK_FORMAT_R8G8B8A8_SRGB; + default: + return format; + } } inline VkFormat ToLinearVulkanFormat( VkFormat format ) { - switch ( format ) - { - case VK_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_UNORM; - case VK_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_UNORM; - default: return format; - } + switch ( format ) + { + case VK_FORMAT_B8G8R8A8_SRGB: + return VK_FORMAT_B8G8R8A8_UNORM; + case VK_FORMAT_R8G8B8A8_SRGB: + return VK_FORMAT_R8G8B8A8_UNORM; + default: + return format; + } } -inline GamescopeAppTextureColorspace VkColorSpaceToGamescopeAppTextureColorSpace(VkFormat format, VkColorSpaceKHR colorspace) +inline GamescopeAppTextureColorspace +VkColorSpaceToGamescopeAppTextureColorSpace( + VkFormat format, VkColorSpaceKHR colorspace ) { - switch (colorspace) - { - default: - case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: - // We will use image view conversions for these 8888 formats. - if (ToSrgbVulkanFormat(format) != ToLinearVulkanFormat(format)) - return GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; - return GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - - case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: - return GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB; - - case VK_COLOR_SPACE_HDR10_ST2084_EXT: - return GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; - } + switch ( colorspace ) + { + default: + case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: + // We will use image view conversions for these 8888 formats. + if ( ToSrgbVulkanFormat( format ) != + ToLinearVulkanFormat( format ) ) + return GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; + return GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + + case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: + return GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB; + + case VK_COLOR_SPACE_HDR10_ST2084_EXT: + return GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; + } } class CVulkanTexture : public gamescope::RcObject { public: - struct createFlags { - - createFlags( void ) - { - bFlippable = false; - bMappable = false; - bSampled = false; - bStorage = false; - bTransferSrc = false; - bTransferDst = false; - bLinear = false; - bExportable = false; - bOutputImage = false; - bColorAttachment = false; - imageType = VK_IMAGE_TYPE_2D; - } - - bool bFlippable : 1; - bool bMappable : 1; - bool bSampled : 1; - bool bStorage : 1; - bool bTransferSrc : 1; - bool bTransferDst : 1; - bool bLinear : 1; - bool bExportable : 1; - bool bOutputImage : 1; - bool bColorAttachment : 1; - VkImageType imageType; - }; - - bool BInit( uint32_t width, uint32_t height, uint32_t depth, uint32_t drmFormat, createFlags flags, wlr_dmabuf_attributes *pDMA = nullptr, uint32_t contentWidth = 0, uint32_t contentHeight = 0, CVulkanTexture *pExistingImageToReuseMemory = nullptr, gamescope::OwningRc pBackendFb = nullptr ); - bool BInitFromSwapchain( VkImage image, uint32_t width, uint32_t height, VkFormat format ); - - uint32_t IncRef(); - uint32_t DecRef(); - - bool IsInUse(); - - inline VkImageView view( bool linear ) { return linear ? m_linearView : m_srgbView; } - inline VkImageView linearView() { return m_linearView; } - inline VkImageView srgbView() { return m_srgbView; } - inline VkImageView lumaView() { return m_lumaView; } - inline VkImageView chromaView() { return m_chromaView; } - inline uint32_t width() { return m_width; } - inline uint32_t height() { return m_height; } - inline uint32_t depth() { return m_depth; } - inline uint32_t contentWidth() {return m_contentWidth; } - inline uint32_t contentHeight() {return m_contentHeight; } - inline uint32_t rowPitch() { return m_unRowPitch; } - inline gamescope::IBackendFb* GetBackendFb() { return m_pBackendFb.get(); } - inline uint8_t *mappedData() { return m_pMappedData; } - inline VkFormat format() const { return m_format; } - inline const struct wlr_dmabuf_attributes& dmabuf() { return m_dmabuf; } - inline VkImage vkImage() { return m_vkImage; } - inline bool outputImage() { return m_bOutputImage; } - inline bool externalImage() { return m_bExternal; } - inline VkDeviceSize totalSize() const { return m_size; } - inline uint32_t drmFormat() const { return m_drmFormat; } - - inline uint32_t lumaOffset() const { return m_lumaOffset; } - inline uint32_t lumaRowPitch() const { return m_lumaPitch; } - inline uint32_t chromaOffset() const { return m_chromaOffset; } - inline uint32_t chromaRowPitch() const { return m_chromaPitch; } - - inline EStreamColorspace streamColorspace() const { return m_streamColorspace; } - inline void setStreamColorspace(EStreamColorspace colorspace) { m_streamColorspace = colorspace; } - - inline bool isYcbcr() const - { - return format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; - } - - int memoryFence(); - - CVulkanTexture( void ); - ~CVulkanTexture( void ); - - uint32_t queueFamily = VK_QUEUE_FAMILY_IGNORED; + struct createFlags + { + + createFlags( void ) + { + bFlippable = false; + bMappable = false; + bSampled = false; + bStorage = false; + bTransferSrc = false; + bTransferDst = false; + bLinear = false; + bExportable = false; + bOutputImage = false; + bColorAttachment = false; + imageType = VK_IMAGE_TYPE_2D; + } + + bool bFlippable : 1; + bool bMappable : 1; + bool bSampled : 1; + bool bStorage : 1; + bool bTransferSrc : 1; + bool bTransferDst : 1; + bool bLinear : 1; + bool bExportable : 1; + bool bOutputImage : 1; + bool bColorAttachment : 1; + VkImageType imageType; + }; + + bool BInit( + uint32_t width, + uint32_t height, + uint32_t depth, + uint32_t drmFormat, + createFlags flags, + wlr_dmabuf_attributes *pDMA = nullptr, + uint32_t contentWidth = 0, + uint32_t contentHeight = 0, + CVulkanTexture *pExistingImageToReuseMemory = nullptr, + gamescope::OwningRc pBackendFb = nullptr ); + bool BInitFromSwapchain( + VkImage image, uint32_t width, uint32_t height, VkFormat format ); + + uint32_t IncRef( ); + uint32_t DecRef( ); + + bool IsInUse( ); + + inline VkImageView view( bool linear ) + { return linear ? m_linearView : m_srgbView; } + inline VkImageView linearView( ) { return m_linearView; } + inline VkImageView srgbView( ) { return m_srgbView; } + inline VkImageView lumaView( ) { return m_lumaView; } + inline VkImageView chromaView( ) { return m_chromaView; } + inline uint32_t width( ) { return m_width; } + inline uint32_t height( ) { return m_height; } + inline uint32_t depth( ) { return m_depth; } + inline uint32_t contentWidth( ) { return m_contentWidth; } + inline uint32_t contentHeight( ) { return m_contentHeight; } + inline uint32_t rowPitch( ) { return m_unRowPitch; } + inline gamescope::IBackendFb *GetBackendFb( ) + { return m_pBackendFb.get( ); } + inline uint8_t *mappedData( ) { return m_pMappedData; } + inline VkFormat format( ) const { return m_format; } + inline const struct wlr_dmabuf_attributes &dmabuf( ) { return m_dmabuf; } + inline VkImage vkImage( ) { return m_vkImage; } + inline bool outputImage( ) { return m_bOutputImage; } + inline bool externalImage( ) { return m_bExternal; } + inline VkDeviceSize totalSize( ) const { return m_size; } + inline uint32_t drmFormat( ) const { return m_drmFormat; } + + inline uint32_t lumaOffset( ) const { return m_lumaOffset; } + inline uint32_t lumaRowPitch( ) const { return m_lumaPitch; } + inline uint32_t chromaOffset( ) const { return m_chromaOffset; } + inline uint32_t chromaRowPitch( ) const { return m_chromaPitch; } + + inline EStreamColorspace streamColorspace( ) const + { return m_streamColorspace; } + inline void setStreamColorspace( EStreamColorspace colorspace ) + { m_streamColorspace = colorspace; } + + inline bool isYcbcr( ) const + { return format( ) == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; } + + int memoryFence( ); + + CVulkanTexture( void ); + ~CVulkanTexture( void ); + + uint32_t queueFamily = VK_QUEUE_FAMILY_IGNORED; private: - bool m_bInitialized = false; - bool m_bExternal = false; - bool m_bOutputImage = false; + bool m_bInitialized = false; + bool m_bExternal = false; + bool m_bOutputImage = false; + + uint32_t m_drmFormat = DRM_FORMAT_INVALID; - uint32_t m_drmFormat = DRM_FORMAT_INVALID; + VkImage m_vkImage = VK_NULL_HANDLE; + VkDeviceMemory m_vkImageMemory = VK_NULL_HANDLE; - VkImage m_vkImage = VK_NULL_HANDLE; - VkDeviceMemory m_vkImageMemory = VK_NULL_HANDLE; - - VkImageView m_srgbView = VK_NULL_HANDLE; - VkImageView m_linearView = VK_NULL_HANDLE; + VkImageView m_srgbView = VK_NULL_HANDLE; + VkImageView m_linearView = VK_NULL_HANDLE; - VkImageView m_lumaView = VK_NULL_HANDLE; - VkImageView m_chromaView = VK_NULL_HANDLE; + VkImageView m_lumaView = VK_NULL_HANDLE; + VkImageView m_chromaView = VK_NULL_HANDLE; - uint32_t m_width = 0; - uint32_t m_height = 0; - uint32_t m_depth = 0; + uint32_t m_width = 0; + uint32_t m_height = 0; + uint32_t m_depth = 0; - uint32_t m_contentWidth = 0; - uint32_t m_contentHeight = 0; + uint32_t m_contentWidth = 0; + uint32_t m_contentHeight = 0; - uint32_t m_unRowPitch = 0; - VkDeviceSize m_size = 0; + uint32_t m_unRowPitch = 0; + VkDeviceSize m_size = 0; - uint32_t m_lumaOffset = 0; - uint32_t m_lumaPitch = 0; - uint32_t m_chromaOffset = 0; - uint32_t m_chromaPitch = 0; - - // If this texture owns the backend Fb (ie. it's an internal texture) - gamescope::OwningRc m_pBackendFb; + uint32_t m_lumaOffset = 0; + uint32_t m_lumaPitch = 0; + uint32_t m_chromaOffset = 0; + uint32_t m_chromaPitch = 0; - uint8_t *m_pMappedData = nullptr; + // If this texture owns the backend Fb (ie. it's an internal texture) + gamescope::OwningRc m_pBackendFb; - VkFormat m_format = VK_FORMAT_UNDEFINED; + uint8_t *m_pMappedData = nullptr; - EStreamColorspace m_streamColorspace = k_EStreamColorspace_Unknown; + VkFormat m_format = VK_FORMAT_UNDEFINED; - struct wlr_dmabuf_attributes m_dmabuf = {}; + EStreamColorspace m_streamColorspace = k_EStreamColorspace_Unknown; + + struct wlr_dmabuf_attributes m_dmabuf = {}; }; struct vec2_t { - float x, y; + float x, y; }; -static inline bool float_is_integer(float x) -{ - return fabsf(ceilf(x) - x) <= 0.001f; -} +static inline bool float_is_integer( float x ) +{ return fabsf( ceilf( x ) - x ) <= 0.001f; } -inline bool close_enough(float a, float b, float epsilon = 0.001f) -{ - return fabsf(a - b) <= epsilon; -} +inline bool close_enough( float a, float b, float epsilon = 0.001f ) +{ return fabsf( a - b ) <= epsilon; } bool DRMFormatHasAlpha( uint32_t nDRMFormat ); enum AlphaBlendingMode_t { - ALPHA_BLENDING_MODE_PREMULTIPLIED, - ALPHA_BLENDING_MODE_COVERAGE, - ALPHA_BLENDING_MODE_NONE, + ALPHA_BLENDING_MODE_PREMULTIPLIED, + ALPHA_BLENDING_MODE_COVERAGE, + ALPHA_BLENDING_MODE_NONE, }; -//#define DRM_MODE_BLEND_PREMULTI 0 -//#define DRM_MODE_BLEND_COVERAGE 1 -//#define DRM_MODE_BLEND_PIXEL_NONE 2 +// #define DRM_MODE_BLEND_PREMULTI 0 +// #define DRM_MODE_BLEND_COVERAGE 1 +// #define DRM_MODE_BLEND_PIXEL_NONE 2 struct FrameInfo_t { - bool useFSRLayer0; - bool useNISLayer0; - bool bFadingOut; - BlurMode blurLayer0; - int blurRadius; - - gamescope::Rc shaperLut[EOTF_Count]; - gamescope::Rc lut3D[EOTF_Count]; - - bool allowVRR; - bool applyOutputColorMgmt; // drm only - EOTF outputEncodingEOTF; - - int layerCount; - struct Layer_t - { - gamescope::Rc tex; - int zpos; - - vec2_t offset; - vec2_t scale; - - float opacity; - - GamescopeUpscaleFilter filter = GamescopeUpscaleFilter::LINEAR; - - bool blackBorder; - bool applyColorMgmt; // drm only - - AlphaBlendingMode_t eAlphaBlendingMode = ALPHA_BLENDING_MODE_PREMULTIPLIED; - - std::shared_ptr ctm; - std::shared_ptr hdr_metadata_blob; - - GamescopeAppTextureColorspace colorspace; - - bool isYcbcr() const - { - if ( !tex ) - return false; - - return tex->isYcbcr(); - } - - bool hasAlpha() const - { - if ( !tex ) - return false; - - return DRMFormatHasAlpha( tex->drmFormat() ); - } - - bool isScreenSize() const { - return close_enough(scale.x, 1.0f) && - close_enough(scale.y, 1.0f) && - float_is_integer(offset.x) && - float_is_integer(offset.y); - } - - bool viewConvertsToLinearAutomatically() const { - if (isYcbcr()) - return true; - - return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR || - colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB || - colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU; - } - - uint32_t integerWidth() const { return tex->width() / scale.x; } - uint32_t integerHeight() const { return tex->height() / scale.y; } - vec2_t offsetPixelCenter() const - { - float x = offset.x + 0.5f / scale.x; - float y = offset.y + 0.5f / scale.y; - return { x, y }; - } - } layers[ k_nMaxLayers ]; - - uint32_t borderMask() const { - uint32_t result = 0; - for (int i = 0; i < layerCount; i++) - { - if (layers[ i ].blackBorder) - result |= 1 << i; - } - return result; - } - uint32_t ycbcrMask() const { - uint32_t result = 0; - for (int i = 0; i < layerCount; i++) - { - if (layers[ i ].isYcbcr()) - result |= 1 << i; - } - return result; - } - uint32_t colorspaceMask() const { - uint32_t result = 0; - for (int i = 0; i < layerCount; i++) - { -result |= layers[ i ].colorspace << (i * GamescopeAppTextureColorspace_Bits); - } - return result; - } + bool useFSRLayer0; + bool useNISLayer0; + bool bFadingOut; + BlurMode blurLayer0; + int blurRadius; + + gamescope::Rc shaperLut[ EOTF_Count ]; + gamescope::Rc lut3D[ EOTF_Count ]; + + bool allowVRR; + bool applyOutputColorMgmt; // drm only + EOTF outputEncodingEOTF; + + int layerCount; + struct Layer_t + { + gamescope::Rc tex; + int zpos; + + vec2_t offset; + vec2_t scale; + + float opacity; + + GamescopeUpscaleFilter filter = GamescopeUpscaleFilter::LINEAR; + + bool blackBorder; + bool applyColorMgmt; // drm only + + AlphaBlendingMode_t eAlphaBlendingMode = + ALPHA_BLENDING_MODE_PREMULTIPLIED; + + std::shared_ptr ctm; + std::shared_ptr hdr_metadata_blob; + + GamescopeAppTextureColorspace colorspace; + + bool isYcbcr( ) const + { + if ( !tex ) return false; + + return tex->isYcbcr( ); + } + + bool hasAlpha( ) const + { + if ( !tex ) return false; + + return DRMFormatHasAlpha( tex->drmFormat( ) ); + } + + bool isScreenSize( ) const + { + return close_enough( scale.x, 1.0f ) && + close_enough( scale.y, 1.0f ) && + float_is_integer( offset.x ) && float_is_integer( offset.y ); + } + + bool viewConvertsToLinearAutomatically( ) const + { + if ( isYcbcr( ) ) return true; + + return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR || + colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB || + colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU; + } + + uint32_t integerWidth( ) const { return tex->width( ) / scale.x; } + uint32_t integerHeight( ) const { return tex->height( ) / scale.y; } + vec2_t offsetPixelCenter( ) const + { + float x = offset.x + 0.5f / scale.x; + float y = offset.y + 0.5f / scale.y; + return { x, y }; + } + } layers[ k_nMaxLayers ]; + + uint32_t borderMask( ) const + { + uint32_t result = 0; + for ( int i = 0; i < layerCount; i++ ) + { + if ( layers[ i ].blackBorder ) result |= 1 << i; + } + return result; + } + uint32_t ycbcrMask( ) const + { + uint32_t result = 0; + for ( int i = 0; i < layerCount; i++ ) + { + if ( layers[ i ].isYcbcr( ) ) result |= 1 << i; + } + return result; + } + uint32_t colorspaceMask( ) const + { + uint32_t result = 0; + for ( int i = 0; i < layerCount; i++ ) + { + result |= layers[ i ].colorspace + << ( i * GamescopeAppTextureColorspace_Bits ); + } + return result; + } }; -extern uint32_t g_uCompositeDebug; +extern uint32_t g_uCompositeDebug; extern gamescope::ConVar cv_composite_debug; namespace CompositeDebugFlag { - static constexpr uint32_t Markers = 1u << 0; - static constexpr uint32_t PlaneBorders = 1u << 1; - static constexpr uint32_t Heatmap = 1u << 2; - static constexpr uint32_t Heatmap_MSWCG = 1u << 3; - static constexpr uint32_t Heatmap_Hard = 1u << 4; - static constexpr uint32_t Markers_Partial = 1u << 5; - static constexpr uint32_t Tonemap_Reinhard = 1u << 7; -}; - -VkInstance vulkan_get_instance(void); -bool vulkan_init(VkInstance instance, VkSurfaceKHR surface); -bool vulkan_init_formats(void); -bool vulkan_make_output(); - -gamescope::OwningRc vulkan_create_texture_from_dmabuf( struct wlr_dmabuf_attributes *pDMA, gamescope::OwningRc pBackendFb ); -gamescope::OwningRc vulkan_create_texture_from_bits( uint32_t width, uint32_t height, uint32_t contentWidth, uint32_t contentHeight, uint32_t drmFormat, CVulkanTexture::createFlags texCreateFlags, void *bits ); -gamescope::OwningRc vulkan_create_texture_from_wlr_buffer( struct wlr_buffer *buf, gamescope::OwningRc pBackendFb ); - -std::optional vulkan_composite( struct FrameInfo_t *frameInfo, gamescope::Rc pScreenshotTexture, bool partial, gamescope::Rc pOutputOverride = nullptr, bool increment = true, std::unique_ptr pInCommandBuffer = nullptr ); + static constexpr uint32_t Markers = 1u << 0; + static constexpr uint32_t PlaneBorders = 1u << 1; + static constexpr uint32_t Heatmap = 1u << 2; + static constexpr uint32_t Heatmap_MSWCG = 1u << 3; + static constexpr uint32_t Heatmap_Hard = 1u << 4; + static constexpr uint32_t Markers_Partial = 1u << 5; + static constexpr uint32_t Tonemap_Reinhard = 1u << 7; +}; // namespace CompositeDebugFlag + +VkInstance vulkan_get_instance( void ); +bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ); +bool vulkan_init_formats( void ); +bool vulkan_make_output( ); + +gamescope::OwningRc vulkan_create_texture_from_dmabuf( + struct wlr_dmabuf_attributes *pDMA, + gamescope::OwningRc pBackendFb ); +gamescope::OwningRc vulkan_create_texture_from_bits( + uint32_t width, + uint32_t height, + uint32_t contentWidth, + uint32_t contentHeight, + uint32_t drmFormat, + CVulkanTexture::createFlags texCreateFlags, + void *bits ); +gamescope::OwningRc vulkan_create_texture_from_wlr_buffer( + struct wlr_buffer *buf, + gamescope::OwningRc pBackendFb ); + +std::optional vulkan_composite( + struct FrameInfo_t *frameInfo, + gamescope::Rc pScreenshotTexture, + bool partial, + gamescope::Rc pOutputOverride = nullptr, + bool increment = true, + std::unique_ptr pInCommandBuffer = nullptr ); void vulkan_wait( uint64_t ulSeqNo, bool bReset ); -gamescope::Rc vulkan_get_last_output_image( bool partial, bool defer ); -gamescope::Rc vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); +gamescope::Rc +vulkan_get_last_output_image( bool partial, bool defer ); +gamescope::Rc vulkan_acquire_screenshot_texture( + uint32_t width, + uint32_t height, + bool exportable, + uint32_t drmFormat, + EStreamColorspace colorspace = k_EStreamColorspace_Unknown ); void vulkan_present_to_window( void ); @@ -420,16 +464,24 @@ bool vulkan_remake_swapchain( void ); bool vulkan_remake_output_images( void ); bool acquire_next_image( void ); -bool vulkan_primary_dev_id(dev_t *id); -bool vulkan_supports_modifiers(void); +bool vulkan_primary_dev_id( dev_t *id ); +bool vulkan_supports_modifiers( void ); -gamescope::Rc vulkan_create_1d_lut(uint32_t size); -gamescope::Rc vulkan_create_3d_lut(uint32_t width, uint32_t height, uint32_t depth); -void vulkan_update_luts(const gamescope::Rc& lut1d, const gamescope::Rc& lut3d, void* lut1d_data, void* lut3d_data); +gamescope::Rc vulkan_create_1d_lut( uint32_t size ); +gamescope::Rc +vulkan_create_3d_lut( uint32_t width, uint32_t height, uint32_t depth ); +void vulkan_update_luts( + const gamescope::Rc &lut1d, + const gamescope::Rc &lut3d, + void *lut1d_data, + void *lut3d_data ); -gamescope::Rc vulkan_get_hacky_blank_texture(); +gamescope::Rc vulkan_get_hacky_blank_texture( ); -std::optional vulkan_screenshot( const struct FrameInfo_t *frameInfo, gamescope::Rc pScreenshotTexture, gamescope::Rc pYUVOutTexture ); +std::optional vulkan_screenshot( + const struct FrameInfo_t *frameInfo, + gamescope::Rc pScreenshotTexture, + gamescope::Rc pYUVOutTexture ); struct wlr_renderer *vulkan_renderer_create( void ); @@ -439,568 +491,601 @@ using mat3x4 = std::array, 3>; struct gamescope_color_mgmt_t { - bool enabled; - uint32_t externalDirtyCtr; - nightmode_t nightmode; - float sdrGamutWideness = -1; // user property to widen gamut - float flInternalDisplayBrightness = 500.f; - float flSDROnHDRBrightness = 203.f; - float flHDRInputGain = 1.f; - float flSDRInputGain = 1.f; - - // HDR Display Metadata Override & Tonemapping - ETonemapOperator hdrTonemapOperator = ETonemapOperator_None; - tonemap_info_t hdrTonemapDisplayMetadata = { 0 }; - tonemap_info_t hdrTonemapSourceMetadata = { 0 }; - - // the native colorimetry capabilities of the display - displaycolorimetry_t displayColorimetry; - EOTF displayEOTF; - - // the output encoding colorimetry - // ie. for HDR displays we send an explicit 2020 colorimetry packet. - // on SDR displays this is the same as displayColorimetry. - displaycolorimetry_t outputEncodingColorimetry; - EOTF outputEncodingEOTF; - - // If non-zero, use this as the emulated "virtual" white point for the output - glm::vec2 outputVirtualWhite = { 0.f, 0.f }; - EChromaticAdaptationMethod chromaticAdaptationMode = k_EChromaticAdapatationMethod_Bradford; - - std::shared_ptr appHDRMetadata; - - bool operator == (const gamescope_color_mgmt_t&) const = default; - bool operator != (const gamescope_color_mgmt_t&) const = default; + bool enabled; + uint32_t externalDirtyCtr; + nightmode_t nightmode; + float sdrGamutWideness = -1; // user property to widen gamut + float flInternalDisplayBrightness = 500.f; + float flSDROnHDRBrightness = 203.f; + float flHDRInputGain = 1.f; + float flSDRInputGain = 1.f; + + // HDR Display Metadata Override & Tonemapping + ETonemapOperator hdrTonemapOperator = ETonemapOperator_None; + tonemap_info_t hdrTonemapDisplayMetadata = { 0 }; + tonemap_info_t hdrTonemapSourceMetadata = { 0 }; + + // the native colorimetry capabilities of the display + displaycolorimetry_t displayColorimetry; + EOTF displayEOTF; + + // the output encoding colorimetry + // ie. for HDR displays we send an explicit 2020 colorimetry packet. + // on SDR displays this is the same as displayColorimetry. + displaycolorimetry_t outputEncodingColorimetry; + EOTF outputEncodingEOTF; + + // If non-zero, use this as the emulated "virtual" white point for the + // output + glm::vec2 outputVirtualWhite = { 0.f, 0.f }; + EChromaticAdaptationMethod chromaticAdaptationMode = + k_EChromaticAdapatationMethod_Bradford; + + std::shared_ptr appHDRMetadata; + + bool operator==( const gamescope_color_mgmt_t & ) const = default; + bool operator!=( const gamescope_color_mgmt_t & ) const = default; }; -//namespace members from "color_helpers_impl.h": +// namespace members from "color_helpers_impl.h": using rendervulkan::s_nLutEdgeSize3d; using rendervulkan::s_nLutSize1d; struct gamescope_color_mgmt_luts { - bool bHasLut3D = false; - bool bHasLut1D = false; - uint16_t lut3d[s_nLutEdgeSize3d*s_nLutEdgeSize3d*s_nLutEdgeSize3d*4]; - uint16_t lut1d[s_nLutSize1d*4]; - - gamescope::Rc vk_lut3d; - gamescope::Rc vk_lut1d; - - bool HasLuts() const - { - return bHasLut3D && bHasLut1D; - } - - void shutdown() - { - bHasLut1D = false; - bHasLut3D = false; - vk_lut1d = nullptr; - vk_lut3d = nullptr; - } - - void reset() - { - bHasLut1D = false; - bHasLut3D = false; - } + bool bHasLut3D = false; + bool bHasLut1D = false; + uint16_t + lut3d[ s_nLutEdgeSize3d * s_nLutEdgeSize3d * s_nLutEdgeSize3d * 4 ]; + uint16_t lut1d[ s_nLutSize1d * 4 ]; + + gamescope::Rc vk_lut3d; + gamescope::Rc vk_lut1d; + + bool HasLuts( ) const { return bHasLut3D && bHasLut1D; } + + void shutdown( ) + { + bHasLut1D = false; + bHasLut3D = false; + vk_lut1d = nullptr; + vk_lut3d = nullptr; + } + + void reset( ) + { + bHasLut1D = false; + bHasLut3D = false; + } }; struct gamescope_color_mgmt_tracker_t { - gamescope_color_mgmt_t pending{}; - gamescope_color_mgmt_t current{}; - uint32_t serial{}; + gamescope_color_mgmt_t pending{}; + gamescope_color_mgmt_t current{}; + uint32_t serial{}; }; extern gamescope_color_mgmt_tracker_t g_ColorMgmt; -extern gamescope_color_mgmt_luts g_ColorMgmtLuts[ EOTF_Count ]; +extern gamescope_color_mgmt_luts g_ColorMgmtLuts[ EOTF_Count ]; struct VulkanOutput_t { - VkSurfaceKHR surface; - VkSurfaceCapabilitiesKHR surfaceCaps; - std::vector< VkSurfaceFormatKHR > surfaceFormats; - std::vector< VkPresentModeKHR > presentModes; - + VkSurfaceKHR surface; + VkSurfaceCapabilitiesKHR surfaceCaps; + std::vector surfaceFormats; + std::vector presentModes; - std::shared_ptr swapchainHDRMetadata; - VkSwapchainKHR swapChain; - VkFence acquireFence; + std::shared_ptr swapchainHDRMetadata; + VkSwapchainKHR swapChain; + VkFence acquireFence; - uint32_t nOutImage; // swapchain index in nested mode, or ping/pong between two RTs - std::vector> outputImages; - std::vector> outputImagesPartialOverlay; - gamescope::OwningRc temporaryHackyBlankImage; + uint32_t nOutImage; // swapchain index in nested mode, or ping/pong between + // two RTs + std::vector> outputImages; + std::vector> outputImagesPartialOverlay; + gamescope::OwningRc temporaryHackyBlankImage; - uint32_t uOutputFormat = DRM_FORMAT_INVALID; - uint32_t uOutputFormatOverlay = DRM_FORMAT_INVALID; + uint32_t uOutputFormat = DRM_FORMAT_INVALID; + uint32_t uOutputFormatOverlay = DRM_FORMAT_INVALID; - std::array, 2> pScreenshotImages; + std::array, 2> pScreenshotImages; - // NIS and FSR - gamescope::OwningRc tmpOutput; + // NIS and FSR + gamescope::OwningRc tmpOutput; - // NIS - gamescope::OwningRc nisScalerImage; - gamescope::OwningRc nisUsmImage; + // NIS + gamescope::OwningRc nisScalerImage; + gamescope::OwningRc nisUsmImage; }; - -enum ShaderType { - SHADER_TYPE_BLIT = 0, - SHADER_TYPE_BLUR, - SHADER_TYPE_BLUR_COND, - SHADER_TYPE_BLUR_FIRST_PASS, - SHADER_TYPE_EASU, - SHADER_TYPE_RCAS, - SHADER_TYPE_NIS, - SHADER_TYPE_RGB_TO_NV12, - - SHADER_TYPE_COUNT +enum ShaderType +{ + SHADER_TYPE_BLIT = 0, + SHADER_TYPE_BLUR, + SHADER_TYPE_BLUR_COND, + SHADER_TYPE_BLUR_FIRST_PASS, + SHADER_TYPE_EASU, + SHADER_TYPE_RCAS, + SHADER_TYPE_NIS, + SHADER_TYPE_RGB_TO_NV12, + + SHADER_TYPE_COUNT }; extern VulkanOutput_t g_output; struct SamplerState { - bool bNearest : 1; - bool bUnnormalized : 1; - - SamplerState( void ) - { - bNearest = false; - bUnnormalized = false; - } - - bool operator==( const SamplerState& other ) const - { - return this->bNearest == other.bNearest - && this->bUnnormalized == other.bUnnormalized; - } + bool bNearest : 1; + bool bUnnormalized : 1; + + SamplerState( void ) + { + bNearest = false; + bUnnormalized = false; + } + + bool operator==( const SamplerState &other ) const + { + return this->bNearest == other.bNearest && + this->bUnnormalized == other.bUnnormalized; + } }; namespace std { - template <> - struct hash - { - size_t operator()( const SamplerState& k ) const - { - return k.bNearest | (k.bUnnormalized << 1); - } - }; -} + template<> struct hash + { + size_t operator( )( const SamplerState &k ) const + { return k.bNearest | ( k.bUnnormalized << 1 ); } + }; +} // namespace std struct PipelineInfo_t { - ShaderType shaderType; - - uint32_t layerCount; - uint32_t ycbcrMask; - uint32_t blurLayerCount; - - uint32_t compositeDebug; - - uint32_t colorspaceMask; - uint32_t outputEOTF; - bool itmEnable; - - bool operator==(const PipelineInfo_t& o) const { - return - shaderType == o.shaderType && - layerCount == o.layerCount && - ycbcrMask == o.ycbcrMask && - blurLayerCount == o.blurLayerCount && - compositeDebug == o.compositeDebug && - colorspaceMask == o.colorspaceMask && - outputEOTF == o.outputEOTF && - itmEnable == o.itmEnable; - } + ShaderType shaderType; + + uint32_t layerCount; + uint32_t ycbcrMask; + uint32_t blurLayerCount; + + uint32_t compositeDebug; + + uint32_t colorspaceMask; + uint32_t outputEOTF; + bool itmEnable; + + bool operator==( const PipelineInfo_t &o ) const + { + return shaderType == o.shaderType && layerCount == o.layerCount && + ycbcrMask == o.ycbcrMask && blurLayerCount == o.blurLayerCount && + compositeDebug == o.compositeDebug && + colorspaceMask == o.colorspaceMask && + outputEOTF == o.outputEOTF && itmEnable == o.itmEnable; + } }; - -static inline uint32_t hash_combine(uint32_t old_hash, uint32_t new_hash) { - return old_hash ^ (new_hash + 0x9e3779b9 + (old_hash << 6) + (old_hash >> 2)); -} - -namespace std +static inline uint32_t hash_combine( uint32_t old_hash, uint32_t new_hash ) { - template <> - struct hash - { - size_t operator()( const PipelineInfo_t& k ) const - { - uint32_t hash = k.shaderType; - hash = hash_combine(hash, k.layerCount); - hash = hash_combine(hash, k.ycbcrMask); - hash = hash_combine(hash, k.blurLayerCount); - hash = hash_combine(hash, k.compositeDebug); - hash = hash_combine(hash, k.colorspaceMask); - hash = hash_combine(hash, k.outputEOTF); - hash = hash_combine(hash, k.itmEnable); - return hash; - } - }; + return old_hash ^ + ( new_hash + 0x9e3779b9 + ( old_hash << 6 ) + ( old_hash >> 2 ) ); } -static inline uint32_t div_roundup(uint32_t x, uint32_t y) +namespace std { - return (x + (y - 1)) / y; -} - -#define VULKAN_INSTANCE_FUNCTIONS \ - VK_FUNC(CreateDevice) \ - VK_FUNC(EnumerateDeviceExtensionProperties) \ - VK_FUNC(EnumeratePhysicalDevices) \ - VK_FUNC(GetDeviceProcAddr) \ - VK_FUNC(GetPhysicalDeviceFeatures2) \ - VK_FUNC(GetPhysicalDeviceFormatProperties) \ - VK_FUNC(GetPhysicalDeviceFormatProperties2) \ - VK_FUNC(GetPhysicalDeviceImageFormatProperties2) \ - VK_FUNC(GetPhysicalDeviceMemoryProperties) \ - VK_FUNC(GetPhysicalDeviceQueueFamilyProperties) \ - VK_FUNC(GetPhysicalDeviceProperties) \ - VK_FUNC(GetPhysicalDeviceProperties2) \ - VK_FUNC(GetPhysicalDeviceSurfaceCapabilitiesKHR) \ - VK_FUNC(GetPhysicalDeviceSurfaceFormatsKHR) \ - VK_FUNC(GetPhysicalDeviceSurfacePresentModesKHR) \ - VK_FUNC(GetPhysicalDeviceSurfaceSupportKHR) - -#define VULKAN_DEVICE_FUNCTIONS \ - VK_FUNC(AcquireNextImageKHR) \ - VK_FUNC(AllocateCommandBuffers) \ - VK_FUNC(AllocateDescriptorSets) \ - VK_FUNC(AllocateMemory) \ - VK_FUNC(BeginCommandBuffer) \ - VK_FUNC(BindBufferMemory) \ - VK_FUNC(BindImageMemory) \ - VK_FUNC(CmdBeginRendering) \ - VK_FUNC(CmdBindDescriptorSets) \ - VK_FUNC(CmdBindPipeline) \ - VK_FUNC(CmdClearColorImage) \ - VK_FUNC(CmdCopyBufferToImage) \ - VK_FUNC(CmdCopyImage) \ - VK_FUNC(CmdDispatch) \ - VK_FUNC(CmdDraw) \ - VK_FUNC(CmdEndRendering) \ - VK_FUNC(CmdPipelineBarrier) \ - VK_FUNC(CmdPushConstants) \ - VK_FUNC(CreateBuffer) \ - VK_FUNC(CreateCommandPool) \ - VK_FUNC(CreateComputePipelines) \ - VK_FUNC(CreateDescriptorPool) \ - VK_FUNC(CreateDescriptorSetLayout) \ - VK_FUNC(CreateFence) \ - VK_FUNC(CreateGraphicsPipelines) \ - VK_FUNC(CreateImage) \ - VK_FUNC(CreateImageView) \ - VK_FUNC(CreatePipelineLayout) \ - VK_FUNC(CreateSampler) \ - VK_FUNC(CreateSamplerYcbcrConversion) \ - VK_FUNC(CreateSemaphore) \ - VK_FUNC(GetSemaphoreFdKHR) \ - VK_FUNC(ImportSemaphoreFdKHR) \ - VK_FUNC(CreateShaderModule) \ - VK_FUNC(CreateSwapchainKHR) \ - VK_FUNC(DestroyBuffer) \ - VK_FUNC(DestroyDescriptorPool) \ - VK_FUNC(DestroyDescriptorSetLayout) \ - VK_FUNC(DestroyImage) \ - VK_FUNC(DestroyImageView) \ - VK_FUNC(DestroyPipeline) \ - VK_FUNC(DestroySemaphore) \ - VK_FUNC(DestroyPipelineLayout) \ - VK_FUNC(DestroySampler) \ - VK_FUNC(DestroySwapchainKHR) \ - VK_FUNC(EndCommandBuffer) \ - VK_FUNC(FreeCommandBuffers) \ - VK_FUNC(FreeDescriptorSets) \ - VK_FUNC(FreeMemory) \ - VK_FUNC(GetBufferMemoryRequirements) \ - VK_FUNC(GetDeviceQueue) \ - VK_FUNC(GetImageDrmFormatModifierPropertiesEXT) \ - VK_FUNC(GetImageMemoryRequirements) \ - VK_FUNC(GetImageSubresourceLayout) \ - VK_FUNC(GetMemoryFdKHR) \ - VK_FUNC(GetSemaphoreCounterValue) \ - VK_FUNC(GetSwapchainImagesKHR) \ - VK_FUNC(MapMemory) \ - VK_FUNC(QueuePresentKHR) \ - VK_FUNC(QueueSubmit) \ - VK_FUNC(QueueWaitIdle) \ - VK_FUNC(ResetCommandBuffer) \ - VK_FUNC(ResetFences) \ - VK_FUNC(UnmapMemory) \ - VK_FUNC(UpdateDescriptorSets) \ - VK_FUNC(WaitForFences) \ - VK_FUNC(WaitForPresentKHR) \ - VK_FUNC(WaitSemaphores) \ - VK_FUNC(SetHdrMetadataEXT) - -template -constexpr T align(T what, U to) { -return (what + to - 1) & ~(to - 1); -} + template<> struct hash + { + size_t operator( )( const PipelineInfo_t &k ) const + { + uint32_t hash = k.shaderType; + hash = hash_combine( hash, k.layerCount ); + hash = hash_combine( hash, k.ycbcrMask ); + hash = hash_combine( hash, k.blurLayerCount ); + hash = hash_combine( hash, k.compositeDebug ); + hash = hash_combine( hash, k.colorspaceMask ); + hash = hash_combine( hash, k.outputEOTF ); + hash = hash_combine( hash, k.itmEnable ); + return hash; + } + }; +} // namespace std + +static inline uint32_t div_roundup( uint32_t x, uint32_t y ) +{ return ( x + ( y - 1 ) ) / y; } + +#define VULKAN_INSTANCE_FUNCTIONS \ + VK_FUNC( CreateDevice ) \ + VK_FUNC( EnumerateDeviceExtensionProperties ) \ + VK_FUNC( EnumeratePhysicalDevices ) \ + VK_FUNC( GetDeviceProcAddr ) \ + VK_FUNC( GetPhysicalDeviceFeatures2 ) \ + VK_FUNC( GetPhysicalDeviceFormatProperties ) \ + VK_FUNC( GetPhysicalDeviceFormatProperties2 ) \ + VK_FUNC( GetPhysicalDeviceImageFormatProperties2 ) \ + VK_FUNC( GetPhysicalDeviceMemoryProperties ) \ + VK_FUNC( GetPhysicalDeviceQueueFamilyProperties ) \ + VK_FUNC( GetPhysicalDeviceProperties ) \ + VK_FUNC( GetPhysicalDeviceProperties2 ) \ + VK_FUNC( GetPhysicalDeviceSurfaceCapabilitiesKHR ) \ + VK_FUNC( GetPhysicalDeviceSurfaceFormatsKHR ) \ + VK_FUNC( GetPhysicalDeviceSurfacePresentModesKHR ) \ + VK_FUNC( GetPhysicalDeviceSurfaceSupportKHR ) + +#define VULKAN_DEVICE_FUNCTIONS \ + VK_FUNC( AcquireNextImageKHR ) \ + VK_FUNC( AllocateCommandBuffers ) \ + VK_FUNC( AllocateDescriptorSets ) \ + VK_FUNC( AllocateMemory ) \ + VK_FUNC( BeginCommandBuffer ) \ + VK_FUNC( BindBufferMemory ) \ + VK_FUNC( BindImageMemory ) \ + VK_FUNC( CmdBeginRendering ) \ + VK_FUNC( CmdBindDescriptorSets ) \ + VK_FUNC( CmdBindPipeline ) \ + VK_FUNC( CmdClearColorImage ) \ + VK_FUNC( CmdCopyBufferToImage ) \ + VK_FUNC( CmdCopyImage ) \ + VK_FUNC( CmdDispatch ) \ + VK_FUNC( CmdDraw ) \ + VK_FUNC( CmdEndRendering ) \ + VK_FUNC( CmdPipelineBarrier ) \ + VK_FUNC( CmdPushConstants ) \ + VK_FUNC( CreateBuffer ) \ + VK_FUNC( CreateCommandPool ) \ + VK_FUNC( CreateComputePipelines ) \ + VK_FUNC( CreateDescriptorPool ) \ + VK_FUNC( CreateDescriptorSetLayout ) \ + VK_FUNC( CreateFence ) \ + VK_FUNC( CreateGraphicsPipelines ) \ + VK_FUNC( CreateImage ) \ + VK_FUNC( CreateImageView ) \ + VK_FUNC( CreatePipelineLayout ) \ + VK_FUNC( CreateSampler ) \ + VK_FUNC( CreateSamplerYcbcrConversion ) \ + VK_FUNC( CreateSemaphore ) \ + VK_FUNC( GetSemaphoreFdKHR ) \ + VK_FUNC( ImportSemaphoreFdKHR ) \ + VK_FUNC( CreateShaderModule ) \ + VK_FUNC( CreateSwapchainKHR ) \ + VK_FUNC( DestroyBuffer ) \ + VK_FUNC( DestroyDescriptorPool ) \ + VK_FUNC( DestroyDescriptorSetLayout ) \ + VK_FUNC( DestroyImage ) \ + VK_FUNC( DestroyImageView ) \ + VK_FUNC( DestroyPipeline ) \ + VK_FUNC( DestroySemaphore ) \ + VK_FUNC( DestroyPipelineLayout ) \ + VK_FUNC( DestroySampler ) \ + VK_FUNC( DestroySwapchainKHR ) \ + VK_FUNC( EndCommandBuffer ) \ + VK_FUNC( FreeCommandBuffers ) \ + VK_FUNC( FreeDescriptorSets ) \ + VK_FUNC( FreeMemory ) \ + VK_FUNC( GetBufferMemoryRequirements ) \ + VK_FUNC( GetDeviceQueue ) \ + VK_FUNC( GetImageDrmFormatModifierPropertiesEXT ) \ + VK_FUNC( GetImageMemoryRequirements ) \ + VK_FUNC( GetImageSubresourceLayout ) \ + VK_FUNC( GetMemoryFdKHR ) \ + VK_FUNC( GetSemaphoreCounterValue ) \ + VK_FUNC( GetSwapchainImagesKHR ) \ + VK_FUNC( MapMemory ) \ + VK_FUNC( QueuePresentKHR ) \ + VK_FUNC( QueueSubmit ) \ + VK_FUNC( QueueWaitIdle ) \ + VK_FUNC( ResetCommandBuffer ) \ + VK_FUNC( ResetFences ) \ + VK_FUNC( UnmapMemory ) \ + VK_FUNC( UpdateDescriptorSets ) \ + VK_FUNC( WaitForFences ) \ + VK_FUNC( WaitForPresentKHR ) \ + VK_FUNC( WaitSemaphores ) \ + VK_FUNC( SetHdrMetadataEXT ) + +template constexpr T align( T what, U to ) +{ return ( what + to - 1 ) & ~( to - 1 ); } class CVulkanDevice; struct VulkanTimelineSemaphore_t { - ~VulkanTimelineSemaphore_t(); + ~VulkanTimelineSemaphore_t( ); - CVulkanDevice *pDevice = nullptr; - VkSemaphore pVkSemaphore = VK_NULL_HANDLE; + CVulkanDevice *pDevice = nullptr; + VkSemaphore pVkSemaphore = VK_NULL_HANDLE; - int GetFd() const; + int GetFd( ) const; }; struct VulkanTimelinePoint_t { - std::shared_ptr pTimelineSemaphore; - uint64_t ulPoint; + std::shared_ptr pTimelineSemaphore; + uint64_t ulPoint; }; class CVulkanDevice { public: - bool BInit(VkInstance instance, VkSurfaceKHR surface); - - VkSampler sampler(SamplerState key); - VkPipeline pipeline(ShaderType type, uint32_t layerCount = 1, uint32_t ycbcrMask = 0, uint32_t blur_layers = 0, uint32_t colorspace_mask = 0, uint32_t output_eotf = EOTF_Gamma22, bool itm_enable = false); - int32_t findMemoryType( VkMemoryPropertyFlags properties, uint32_t requiredTypeBits ); - std::unique_ptr commandBuffer(); - uint64_t submit( std::unique_ptr cmdBuf); - uint64_t submitInternal( CVulkanCmdBuffer* cmdBuf ); - void wait(uint64_t sequence, bool reset = true); - void waitIdle(bool reset = true); - void garbageCollect(); - inline VkDescriptorSet descriptorSet() - { - VkDescriptorSet ret = m_descriptorSets[m_currentDescriptorSet]; - m_currentDescriptorSet = (m_currentDescriptorSet + 1) % m_descriptorSets.size(); - return ret; - } - - std::shared_ptr CreateTimelineSemaphore( uint64_t ulStartingPoint, bool bShared = false ); - std::shared_ptr ImportTimelineSemaphore( gamescope::CTimeline *pTimeline ); - - static const uint32_t upload_buffer_size = 1920 * 1080 * 4; - - inline VkDevice device() { return m_device; } - inline VkPhysicalDevice physDev() {return m_physDev; } - inline VkInstance instance() { return m_instance; } - inline VkQueue queue() {return m_queue;} - inline VkQueue generalQueue() {return m_generalQueue;} - inline VkCommandPool commandPool() {return m_commandPool;} - inline VkCommandPool generalCommandPool() {return m_generalCommandPool;} - inline uint32_t queueFamily() {return m_queueFamily;} - inline uint32_t generalQueueFamily() {return m_generalQueueFamily;} - inline VkBuffer uploadBuffer() {return m_uploadBuffer;} - inline VkPipelineLayout pipelineLayout() {return m_pipelineLayout;} - inline int drmRenderFd() {return m_drmRendererFd;} - inline bool supportsModifiers() {return m_bSupportsModifiers;} - inline bool hasDrmPrimaryDevId() {return m_bHasDrmPrimaryDevId;} - inline dev_t primaryDevId() {return m_drmPrimaryDevId;} - inline bool supportsFp16() {return m_bSupportsFp16;} - inline std::vector& supportedExtensions() {return m_supportedExts;} - - inline std::pair uploadBufferData(uint32_t size) - { - assert(size <= upload_buffer_size); - - m_uploadBufferOffset = align(m_uploadBufferOffset, 16); - if (m_uploadBufferOffset + size > upload_buffer_size) - { - fprintf(stderr, "Exceeded uploadBufferData\n"); - waitIdle(false); - } - - uint32_t uOffset = m_uploadBufferOffset; - - uint8_t *ptr = ((uint8_t*)m_uploadBufferData) + uOffset; - m_uploadBufferOffset += size; - return std::make_pair( ptr, uOffset ); - } - - #define VK_FUNC(x) PFN_vk##x x = nullptr; - struct - { - VULKAN_INSTANCE_FUNCTIONS - VULKAN_DEVICE_FUNCTIONS - } vk; - #undef VK_FUNC - - void resetCmdBuffers(uint64_t sequence); + bool BInit( VkInstance instance, VkSurfaceKHR surface ); + + VkSampler sampler( SamplerState key ); + VkPipeline pipeline( + ShaderType type, + uint32_t layerCount = 1, + uint32_t ycbcrMask = 0, + uint32_t blur_layers = 0, + uint32_t colorspace_mask = 0, + uint32_t output_eotf = EOTF_Gamma22, + bool itm_enable = false ); + int32_t findMemoryType( + VkMemoryPropertyFlags properties, uint32_t requiredTypeBits ); + std::unique_ptr commandBuffer( ); + uint64_t submit( std::unique_ptr cmdBuf ); + uint64_t submitInternal( CVulkanCmdBuffer *cmdBuf ); + void wait( uint64_t sequence, bool reset = true ); + void waitIdle( bool reset = true ); + void garbageCollect( ); + inline VkDescriptorSet descriptorSet( ) + { + VkDescriptorSet ret = m_descriptorSets[ m_currentDescriptorSet ]; + m_currentDescriptorSet = + ( m_currentDescriptorSet + 1 ) % m_descriptorSets.size( ); + return ret; + } + + std::shared_ptr + CreateTimelineSemaphore( uint64_t ulStartingPoint, bool bShared = false ); + std::shared_ptr + ImportTimelineSemaphore( gamescope::CTimeline *pTimeline ); + + static const uint32_t upload_buffer_size = 1920 * 1080 * 4; + + inline VkDevice device( ) { return m_device; } + inline VkPhysicalDevice physDev( ) { return m_physDev; } + inline VkInstance instance( ) { return m_instance; } + inline VkQueue queue( ) { return m_queue; } + inline VkQueue generalQueue( ) { return m_generalQueue; } + inline VkCommandPool commandPool( ) { return m_commandPool; } + inline VkCommandPool generalCommandPool( ) { return m_generalCommandPool; } + inline uint32_t queueFamily( ) { return m_queueFamily; } + inline uint32_t generalQueueFamily( ) { return m_generalQueueFamily; } + inline VkBuffer uploadBuffer( ) { return m_uploadBuffer; } + inline VkPipelineLayout pipelineLayout( ) { return m_pipelineLayout; } + inline int drmRenderFd( ) { return m_drmRendererFd; } + inline bool supportsModifiers( ) { return m_bSupportsModifiers; } + inline bool hasDrmPrimaryDevId( ) { return m_bHasDrmPrimaryDevId; } + inline dev_t primaryDevId( ) { return m_drmPrimaryDevId; } + inline bool supportsFp16( ) { return m_bSupportsFp16; } + inline std::vector &supportedExtensions( ) + { return m_supportedExts; } + + inline std::pair uploadBufferData( uint32_t size ) + { + assert( size <= upload_buffer_size ); + + m_uploadBufferOffset = align( m_uploadBufferOffset, 16 ); + if ( m_uploadBufferOffset + size > upload_buffer_size ) + { + fprintf( stderr, "Exceeded uploadBufferData\n" ); + waitIdle( false ); + } + + uint32_t uOffset = m_uploadBufferOffset; + + uint8_t *ptr = ( ( uint8_t * )m_uploadBufferData ) + uOffset; + m_uploadBufferOffset += size; + return std::make_pair( ptr, uOffset ); + } + +#define VK_FUNC( x ) PFN_vk##x x = nullptr; + struct + { + VULKAN_INSTANCE_FUNCTIONS + VULKAN_DEVICE_FUNCTIONS + } vk; +#undef VK_FUNC + + void resetCmdBuffers( uint64_t sequence ); protected: - friend class CVulkanCmdBuffer; - - bool selectPhysDev(VkSurfaceKHR surface); - bool createDevice(); - bool createLayouts(); - bool createPools(); - bool createShaders(); - bool createScratchResources(); - VkPipeline compilePipeline(uint32_t layerCount, uint32_t ycbcrMask, ShaderType type, uint32_t blur_layer_count, uint32_t composite_debug, uint32_t colorspace_mask, uint32_t output_eotf, bool itm_enable); - void compileAllPipelines(); - - VkDevice m_device = nullptr; - VkPhysicalDevice m_physDev = nullptr; - VkInstance m_instance = nullptr; - VkQueue m_queue = nullptr; - VkQueue m_generalQueue = nullptr; - VkSamplerYcbcrConversion m_ycbcrConversion = VK_NULL_HANDLE; - VkSampler m_ycbcrSampler = VK_NULL_HANDLE; - VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE; - VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; - VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; - VkCommandPool m_commandPool = VK_NULL_HANDLE; - VkCommandPool m_generalCommandPool = VK_NULL_HANDLE; - - uint32_t m_queueFamily = -1; - uint32_t m_generalQueueFamily = -1; - - int m_drmRendererFd = -1; - dev_t m_drmPrimaryDevId = 0; - - bool m_bSupportsFp16 = false; - bool m_bHasDrmPrimaryDevId = false; - bool m_bSupportsModifiers = false; - bool m_bInitialized = false; - - - VkPhysicalDeviceMemoryProperties m_memoryProperties; - - std::unordered_map< SamplerState, VkSampler > m_samplerCache; - std::array m_shaderModules; - std::unordered_map m_pipelineMap; - std::mutex m_pipelineMutex; - - static constexpr uint32_t k_uMaxConcurrentSubmits = 8; - - // currently just one set, no need to double buffer because we - // vkQueueWaitIdle after each submit. - // should be moved to the output if we are going to support multiple outputs - std::array m_descriptorSets; - uint32_t m_currentDescriptorSet = 0; - - VkBuffer m_uploadBuffer; - VkDeviceMemory m_uploadBufferMemory; - void *m_uploadBufferData; - uint32_t m_uploadBufferOffset = 0; - - VkSemaphore m_scratchTimelineSemaphore; - std::atomic m_submissionSeqNo = { 0 }; - std::vector> m_unusedCmdBufs; - std::map> m_pendingCmdBufs; + friend class CVulkanCmdBuffer; + + bool selectPhysDev( VkSurfaceKHR surface ); + bool createDevice( ); + bool createLayouts( ); + bool createPools( ); + bool createShaders( ); + bool createScratchResources( ); + VkPipeline compilePipeline( + uint32_t layerCount, + uint32_t ycbcrMask, + ShaderType type, + uint32_t blur_layer_count, + uint32_t composite_debug, + uint32_t colorspace_mask, + uint32_t output_eotf, + bool itm_enable ); + void compileAllPipelines( ); + + VkDevice m_device = nullptr; + VkPhysicalDevice m_physDev = nullptr; + VkInstance m_instance = nullptr; + VkQueue m_queue = nullptr; + VkQueue m_generalQueue = nullptr; + VkSamplerYcbcrConversion m_ycbcrConversion = VK_NULL_HANDLE; + VkSampler m_ycbcrSampler = VK_NULL_HANDLE; + VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE; + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; + VkCommandPool m_commandPool = VK_NULL_HANDLE; + VkCommandPool m_generalCommandPool = VK_NULL_HANDLE; + + uint32_t m_queueFamily = -1; + uint32_t m_generalQueueFamily = -1; + + int m_drmRendererFd = -1; + dev_t m_drmPrimaryDevId = 0; + + bool m_bSupportsFp16 = false; + bool m_bHasDrmPrimaryDevId = false; + bool m_bSupportsModifiers = false; + bool m_bInitialized = false; + + VkPhysicalDeviceMemoryProperties m_memoryProperties; + + std::unordered_map m_samplerCache; + std::array m_shaderModules; + std::unordered_map m_pipelineMap; + std::mutex m_pipelineMutex; + + static constexpr uint32_t k_uMaxConcurrentSubmits = 8; + + // currently just one set, no need to double buffer because we + // vkQueueWaitIdle after each submit. + // should be moved to the output if we are going to support multiple outputs + std::array m_descriptorSets; + uint32_t m_currentDescriptorSet = 0; + + VkBuffer m_uploadBuffer; + VkDeviceMemory m_uploadBufferMemory; + void *m_uploadBufferData; + uint32_t m_uploadBufferOffset = 0; + + VkSemaphore m_scratchTimelineSemaphore; + std::atomic m_submissionSeqNo = { 0 }; + std::vector> m_unusedCmdBufs; + std::map> m_pendingCmdBufs; private: - std::vector m_supportedExts; + std::vector m_supportedExts; }; struct TextureState { - bool discarded : 1; - bool dirty : 1; - bool needsPresentLayout : 1; - bool needsExport : 1; - bool needsImport : 1; - - TextureState() - { - discarded = false; - dirty = false; - needsPresentLayout = false; - needsExport = false; - needsImport = false; - } + bool discarded : 1; + bool dirty : 1; + bool needsPresentLayout : 1; + bool needsExport : 1; + bool needsImport : 1; + + TextureState( ) + { + discarded = false; + dirty = false; + needsPresentLayout = false; + needsExport = false; + needsImport = false; + } }; class CVulkanCmdBuffer { public: - CVulkanCmdBuffer(CVulkanDevice *parent, VkCommandBuffer cmdBuffer, VkQueue queue, uint32_t queueFamily); - ~CVulkanCmdBuffer(); - CVulkanCmdBuffer(const CVulkanCmdBuffer& other) = delete; - CVulkanCmdBuffer(CVulkanCmdBuffer&& other) = delete; - CVulkanCmdBuffer& operator=(const CVulkanCmdBuffer& other) = delete; - CVulkanCmdBuffer& operator=(CVulkanCmdBuffer&& other) = delete; - - inline VkCommandBuffer rawBuffer() {return m_cmdBuffer;} - void reset(); - void begin(); - void end(); - void bindTexture(uint32_t slot, gamescope::Rc texture); - void bindColorMgmtLuts(uint32_t slot, gamescope::Rc lut1d, gamescope::Rc lut3d); - void setTextureStorage(bool storage); - void setTextureSrgb(uint32_t slot, bool srgb); - void setSamplerNearest(uint32_t slot, bool nearest); - void setSamplerUnnormalized(uint32_t slot, bool unnormalized); - void bindTarget(gamescope::Rc target); - void clearState(); - template - void uploadConstants(Args&&... args); - void bindPipeline(VkPipeline pipeline); - void dispatch(uint32_t x, uint32_t y = 1, uint32_t z = 1); - void copyImage(gamescope::Rc src, gamescope::Rc dst); - void copyBufferToImage(VkBuffer buffer, VkDeviceSize offset, uint32_t stride, gamescope::Rc dst); - - - void prepareSrcImage(CVulkanTexture *image); - void prepareDestImage(CVulkanTexture *image); - void discardImage(CVulkanTexture *image); - void markDirty(CVulkanTexture *image); - void insertBarrier(bool flush = false); - - VkQueue queue() { return m_queue; } - uint32_t queueFamily() { return m_queueFamily; } - - void AddDependency( std::shared_ptr pTimelineSemaphore, uint64_t ulPoint ); - void AddSignal( std::shared_ptr pTimelineSemaphore, uint64_t ulPoint ); - - const std::vector &GetExternalDependencies() const { return m_ExternalDependencies; } - const std::vector &GetExternalSignals() const { return m_ExternalSignals; } + CVulkanCmdBuffer( + CVulkanDevice *parent, + VkCommandBuffer cmdBuffer, + VkQueue queue, + uint32_t queueFamily ); + ~CVulkanCmdBuffer( ); + CVulkanCmdBuffer( const CVulkanCmdBuffer &other ) = delete; + CVulkanCmdBuffer( CVulkanCmdBuffer &&other ) = delete; + CVulkanCmdBuffer &operator=( const CVulkanCmdBuffer &other ) = delete; + CVulkanCmdBuffer &operator=( CVulkanCmdBuffer &&other ) = delete; + + inline VkCommandBuffer rawBuffer( ) { return m_cmdBuffer; } + void reset( ); + void begin( ); + void end( ); + void bindTexture( uint32_t slot, gamescope::Rc texture ); + void bindColorMgmtLuts( + uint32_t slot, + gamescope::Rc lut1d, + gamescope::Rc lut3d ); + void setTextureStorage( bool storage ); + void setTextureSrgb( uint32_t slot, bool srgb ); + void setSamplerNearest( uint32_t slot, bool nearest ); + void setSamplerUnnormalized( uint32_t slot, bool unnormalized ); + void bindTarget( gamescope::Rc target ); + void clearState( ); + template + void uploadConstants( Args &&...args ); + void bindPipeline( VkPipeline pipeline ); + void dispatch( uint32_t x, uint32_t y = 1, uint32_t z = 1 ); + void copyImage( + gamescope::Rc src, gamescope::Rc dst ); + void copyBufferToImage( + VkBuffer buffer, + VkDeviceSize offset, + uint32_t stride, + gamescope::Rc dst ); + + void prepareSrcImage( CVulkanTexture *image ); + void prepareDestImage( CVulkanTexture *image ); + void discardImage( CVulkanTexture *image ); + void markDirty( CVulkanTexture *image ); + void insertBarrier( bool flush = false ); + + VkQueue queue( ) { return m_queue; } + uint32_t queueFamily( ) { return m_queueFamily; } + + void AddDependency( + std::shared_ptr pTimelineSemaphore, + uint64_t ulPoint ); + void AddSignal( + std::shared_ptr pTimelineSemaphore, + uint64_t ulPoint ); + + const std::vector &GetExternalDependencies( ) const + { return m_ExternalDependencies; } + const std::vector &GetExternalSignals( ) const + { return m_ExternalSignals; } private: - VkCommandBuffer m_cmdBuffer; - CVulkanDevice *m_device; + VkCommandBuffer m_cmdBuffer; + CVulkanDevice *m_device; - VkQueue m_queue; - uint32_t m_queueFamily; + VkQueue m_queue; + uint32_t m_queueFamily; - // Per Use State - std::vector> m_textureRefs; - std::unordered_map m_textureState; + // Per Use State + std::vector> m_textureRefs; + std::unordered_map m_textureState; - // Draw State - std::array m_boundTextures; - std::bitset m_useSrgb; - std::array m_samplerState; - CVulkanTexture *m_target; + // Draw State + std::array m_boundTextures; + std::bitset m_useSrgb; + std::array m_samplerState; + CVulkanTexture *m_target; - std::array m_shaperLut; - std::array m_lut3D; + std::array m_shaperLut; + std::array m_lut3D; - std::vector m_ExternalDependencies; - std::vector m_ExternalSignals; + std::vector m_ExternalDependencies; + std::vector m_ExternalSignals; - uint32_t m_renderBufferOffset = 0; + uint32_t m_renderBufferOffset = 0; }; -uint32_t VulkanFormatToDRM( VkFormat vkFormat, std::optional obHasAlphaOverride = std::nullopt ); +uint32_t VulkanFormatToDRM( + VkFormat vkFormat, std::optional obHasAlphaOverride = std::nullopt ); VkFormat DRMFormatToVulkan( uint32_t nDRMFormat, bool bSrgb ); -bool DRMFormatHasAlpha( uint32_t nDRMFormat ); +bool DRMFormatHasAlpha( uint32_t nDRMFormat ); uint32_t DRMFormatGetBPP( uint32_t nDRMFormat ); -gamescope::OwningRc vulkan_create_flat_texture( uint32_t width, uint32_t height, uint8_t r, uint8_t g, uint8_t b, uint8_t a ); +gamescope::OwningRc vulkan_create_flat_texture( + uint32_t width, + uint32_t height, + uint8_t r, + uint8_t g, + uint8_t b, + uint8_t a ); -bool vulkan_supports_hdr10(); +bool vulkan_supports_hdr10( ); -void vulkan_wait_idle(); +void vulkan_wait_idle( ); // Whether the driver implements VK_EXT_physical_device_drm -bool vulkan_has_drm_props(); +bool vulkan_has_drm_props( ); extern CVulkanDevice g_device; diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp index 959a973738..6ba3baea76 100644 --- a/src/reshade_effect_manager.cpp +++ b/src/reshade_effect_manager.cpp @@ -86,7 +86,7 @@ class RefreshRateUniform : public ReshadeUniform virtual ~RefreshRateUniform(); private: - int32_t count = 0; + [[maybe_unused]] int32_t count = 0; }; class DateUniform : public ReshadeUniform @@ -1935,7 +1935,7 @@ ReshadeEffectPipeline* ReshadeEffectManager::pipeline(const ReshadeEffectKey &ke ReshadeEffectManager g_reshadeManager; -void reshade_effect_manager_set_uniform_variable(const char *key, uint8_t* value) +void reshade_effect_manager_set_uniform_variable(const char *key, uint8_t* value) { std::lock_guard lock(g_runtimeUniformsMutex); @@ -1943,7 +1943,7 @@ void reshade_effect_manager_set_uniform_variable(const char *key, uint8_t* value if (it != g_runtimeUniforms.end()) { delete[] it->second; } - + g_runtimeUniforms[std::string(key)] = value; force_repaint(); } @@ -1956,12 +1956,12 @@ void reshade_effect_manager_set_effect(const char *path, std::function -#include #include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include +#include #include -#include +#include +#include +#include #include -#include -#include +#include +#include #include +#include +#include +#include "backend.h" +#include "gamescope_shared.h" +#include "xwayland_ctx.hpp" #include -#include +#include #include +#include #include -#include #include #include -#include #include -#if defined(__linux__) -#include -#elif defined(__DragonFly__) || defined(__FreeBSD__) -#include +#include +#if defined( __linux__ ) + #include +#elif defined( __DragonFly__ ) || defined( __FreeBSD__ ) + #include #endif -#include +#include +#include +#include +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include -#include #include "waitable.h" -#include -#include "main.hpp" -#include "wlserver.hpp" -#include "rendervulkan.hpp" -#include "steamcompmgr.hpp" -#include "vblankmanager.hpp" -#include "log.hpp" +#include "BufferMemo.h" +#include "Script/Script.h" +#include "Utils/Algorithm.h" #include "Utils/Defer.h" -#include "win32_styles.h" +#include "Utils/Process.h" +#include "commit.h" +#include "convar.h" #include "edid.h" #include "hdmi.h" -#include "convar.h" -#include "Script/Script.h" +#include "log.hpp" +#include "main.hpp" #include "refresh_rate.h" -#include "commit.h" +#include "rendervulkan.hpp" #include "reshade_effect_manager.hpp" -#include "BufferMemo.h" -#include "Utils/Process.h" -#include "Utils/Algorithm.h" +#include "steamcompmgr.hpp" +#include "vblankmanager.hpp" +#include "win32_styles.h" +#include "wlserver.hpp" -#include "wlr_begin.hpp" #include "wlr/types/wlr_pointer_constraints_v1.h" +#include "wlr_begin.hpp" #include "wlr_end.hpp" #if HAVE_AVIF -#include "avif/avif.h" + #include "avif/avif.h" #endif static const int g_nBaseCursorScale = 36; #if HAVE_PIPEWIRE -#include "pipewire.hpp" + #include "pipewire.hpp" #endif #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION #include -#include #include +#include #define GPUVIS_TRACE_IMPLEMENTATION #include "gpuvis_trace_utils.h" -LogScope xwm_log("xwm"); -LogScope focus_log("focus"); -LogScope g_WaitableLog("waitable"); +LogScope xwm_log( "xwm" ); +LogScope focus_log( "focus" ); +LogScope g_WaitableLog( "waitable" ); -gamescope::ConVar cv_overlay_unmultiplied_alpha{ "overlay_unmultiplied_alpha", false }; +gamescope::ConVar cv_overlay_unmultiplied_alpha{ + "overlay_unmultiplied_alpha", false +}; -gamescope::ConVar cv_vr_show_forwarded_overlays{ "vr_show_forwarded_overlays", false }; +gamescope::ConVar cv_vr_show_forwarded_overlays{ + "vr_show_forwarded_overlays", false +}; -std::string *g_pVROverlayKey = nullptr; -bool g_bWasPartialComposite = false; +std::string *g_pVROverlayKey = nullptr; +bool g_bWasPartialComposite = false; -bool ShouldDrawCursor(); +bool ShouldDrawCursor( ); std::atomic g_unCurrentVRSceneAppId; std::atomic g_FocusedVROverlayMouse; @@ -145,9 +148,8 @@ std::atomic g_FocusedVROverlayKeyboard; gamescope_color_mgmt_tracker_t g_ColorMgmt{}; -static gamescope_color_mgmt_luts g_ColorMgmtLutsOverride[ EOTF_Count ]; -std::atomic> g_ColorMgmtLooks[EOTF_Count]; - +static gamescope_color_mgmt_luts g_ColorMgmtLutsOverride[ EOTF_Count ]; +std::atomic> g_ColorMgmtLooks[ EOTF_Count ]; gamescope_color_mgmt_luts g_ColorMgmtLuts[ EOTF_Count ]; @@ -159,7 +161,7 @@ static lut3d_t g_tmpLut3d; extern int g_nDynamicRefreshHz; -bool g_bForceHDRSupportDebug = false; +bool g_bForceHDRSupportDebug = false; extern float g_flInternalDisplayBrightnessNits; extern float g_flHDRItmSdrNits; extern float g_flHDRItmTargetNits; @@ -171,950 +173,1019 @@ static std::shared_ptr s_scRGB709To2020Matrix; std::string clipboard; std::string primarySelection; -std::string g_reshade_effect{}; +std::string g_reshade_effect{}; extern ReshadeEffectPipeline *g_pLastReshadeEffect; -uint32_t g_reshade_technique_idx = 0; +uint32_t g_reshade_technique_idx = 0; bool g_bSteamIsActiveWindow = false; -bool g_bForceInternal = false; +bool g_bForceInternal = false; namespace gamescope { - extern std::shared_ptr GetX11HostCursor(); + extern std::shared_ptr GetX11HostCursor( ); } static std::unordered_map g_vramCapacities; -static const char *cgroupPathFromPID(pid_t pid) { - if (!pid) - return NULL; - - char procfsPath[512]; - if (snprintf(procfsPath, 512, "/proc/%ld/cgroup", (long)pid) < 0) - return NULL; - - FILE *procfsFile = fopen(procfsPath, "r"); - if (!procfsFile) - return NULL; - - char *retPath = NULL; - - char *line = NULL; - size_t lineSize; - while (getline(&line, &lineSize, procfsFile) > 0) { - /* cgroup v2 pathsi are always in the format "0::$PATH" */ - const char *pathHeader = "0::"; - /* Does the line start with "0::"? */ - if (strncmp(line, pathHeader, strlen(pathHeader))) - continue; - char *path = line; - /* Skip over the header */ - path = path + strlen(pathHeader); - - /* According to https://docs.kernel.org/admin-guide/cgroup-v2.html, - * (deleted) is added to the path if pid refers to a zombie process, - * and the process's cgroup has been deleted. - * It's nonsensical for us to try setting cgroup limits in such a case, - * so bail out. - */ - if (strstr(path, "(deleted)")) - break; - - /* Remove trailing newlines. */ - char *delim = strstr(path, "\n"); - if (delim) - *delim = '\0'; - - retPath = strdup(path); - break; - } - - free(line); - fclose(procfsFile); - return retPath; -} - -static int setDMemMemoryLow(const char *cgroupPath, bool focused) { - char *limitPath; - - int r = asprintf(&limitPath, "/sys/fs/cgroup%s/dmem.low", cgroupPath); - if (r < 0) - return r; +static const char *cgroupPathFromPID( pid_t pid ) +{ + if ( !pid ) return NULL; - FILE *limitFile = fopen(limitPath, "w"); + char procfsPath[ 512 ]; + if ( snprintf( procfsPath, 512, "/proc/%ld/cgroup", ( long )pid ) < 0 ) + return NULL; - free(limitPath); - if (!limitFile) - return -1; + FILE *procfsFile = fopen( procfsPath, "r" ); + if ( !procfsFile ) return NULL; - for (const auto& cap : g_vramCapacities) { - fprintf(limitFile, "%s %" PRIu64 "\n", cap.first.c_str(), focused ? cap.second : 0); - } + char *retPath = NULL; - fclose(limitFile); - return 0; -} + char *line = NULL; + size_t lineSize; + while ( getline( &line, &lineSize, procfsFile ) > 0 ) + { + /* cgroup v2 pathsi are always in the format "0::$PATH" */ + const char *pathHeader = "0::"; + /* Does the line start with "0::"? */ + if ( strncmp( line, pathHeader, strlen( pathHeader ) ) ) continue; + char *path = line; + /* Skip over the header */ + path = path + strlen( pathHeader ); -static std::vector< steamcompmgr_win_t* > GetGlobalPossibleFocusWindows(); -static bool -pick_primary_focus_and_override( - focus_t *out, - Window focusControlWindow, - const std::vector& vecPossibleFocusWindows, - bool globalFocus, - const std::vector& ctxFocusControlAppIDs, - uint64_t ulVirtualFocusKey = 0, - gamescope::VirtualConnectorStrategy eStrategy = gamescope::VirtualConnectorStrategies::PerWindow); + /* According to https://docs.kernel.org/admin-guide/cgroup-v2.html, + * (deleted) is added to the path if pid refers to a zombie process, + * and the process's cgroup has been deleted. + * It's nonsensical for us to try setting cgroup limits in such a case, + * so bail out. + */ + if ( strstr( path, "(deleted)" ) ) break; -bool env_to_bool(const char *env) -{ - if (!env || !*env) - return false; + /* Remove trailing newlines. */ + char *delim = strstr( path, "\n" ); + if ( delim ) *delim = '\0'; - return !!atoi(env); -} + retPath = strdup( path ); + break; + } -uint64_t timespec_to_nanos(struct timespec& spec) -{ - return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec; + free( line ); + fclose( procfsFile ); + return retPath; } -timespec nanos_to_timespec( uint64_t ulNanos ) +static int setDMemMemoryLow( const char *cgroupPath, bool focused ) { - timespec ts = - { - .tv_sec = time_t( ulNanos / 1'000'000'000ul ), - .tv_nsec = long( ulNanos % 1'000'000'000ul ), - }; - return ts; -} - -static void -update_runtime_info(); + char *limitPath; -gamescope::ConVar cv_adaptive_sync( "adaptive_sync", false, "Whether or not adaptive sync is enabled if available." ); -gamescope::ConVar cv_adaptive_sync_ignore_overlay( "adaptive_sync_ignore_overlay", false, "Whether or not to ignore overlay planes for pushing commits with adaptive sync." ); -gamescope::ConVar cv_adaptive_sync_overlay_cycles( "adaptive_sync_overlay_cycles", 1, "Number of vblank cycles to ignore overlay repaints before forcing a commit with adaptive sync." ); + int r = asprintf( &limitPath, "/sys/fs/cgroup%s/dmem.low", cgroupPath ); + if ( r < 0 ) return r; -gamescope::ConVar cv_upscale_preemptive( "upscale_preemptive", true, "Allow pre-emptive upscaling" ); -gamescope::ConVar cv_upscale_preemptive_debug_force_sync( "upscale_preemptive_debug_force_sync", false, "Force synchronize pre-emptive upscaling" ); + FILE *limitFile = fopen( limitPath, "w" ); -uint64_t g_SteamCompMgrLimitedAppRefreshCycle = 16'666'666; -uint64_t g_SteamCompMgrAppRefreshCycle = 16'666'666; + free( limitPath ); + if ( !limitFile ) return -1; -static const gamescope_color_mgmt_t k_ScreenshotColorMgmt = -{ - .enabled = true, - .displayColorimetry = displaycolorimetry_709, - .displayEOTF = EOTF_Gamma22, - .outputEncodingColorimetry = displaycolorimetry_709, - .outputEncodingEOTF = EOTF_Gamma22, -}; + for ( const auto &cap : g_vramCapacities ) + { + fprintf( + limitFile, + "%s %" PRIu64 "\n", + cap.first.c_str( ), + focused ? cap.second : 0 ); + } -static const gamescope_color_mgmt_t k_ScreenshotColorMgmtHDR = -{ - .enabled = true, - .displayColorimetry = displaycolorimetry_2020, - .displayEOTF = EOTF_PQ, - .outputEncodingColorimetry = displaycolorimetry_2020, - .outputEncodingEOTF = EOTF_PQ, -}; + fclose( limitFile ); + return 0; +} -//#define COLOR_MGMT_MICROBENCH -// sudo cpupower frequency-set --governor performance +static std::vector GetGlobalPossibleFocusWindows( ); +static bool pick_primary_focus_and_override( + focus_t *out, + Window focusControlWindow, + const std::vector &vecPossibleFocusWindows, + bool globalFocus, + const std::vector &ctxFocusControlAppIDs, + uint64_t ulVirtualFocusKey = 0, + gamescope::VirtualConnectorStrategy eStrategy = + gamescope::VirtualConnectorStrategies::PerWindow ); -static void -create_color_mgmt_luts(const gamescope_color_mgmt_t& newColorMgmt, gamescope_color_mgmt_luts outColorMgmtLuts[ EOTF_Count ]) +bool env_to_bool( const char *env ) { - const displaycolorimetry_t& displayColorimetry = newColorMgmt.displayColorimetry; - const displaycolorimetry_t& outputEncodingColorimetry = newColorMgmt.outputEncodingColorimetry; - - for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) - { - if (!outColorMgmtLuts[nInputEOTF].vk_lut1d) - outColorMgmtLuts[nInputEOTF].vk_lut1d = vulkan_create_1d_lut(s_nLutSize1d); + if ( !env || !*env ) return false; - if (!outColorMgmtLuts[nInputEOTF].vk_lut3d) - outColorMgmtLuts[nInputEOTF].vk_lut3d = vulkan_create_3d_lut(s_nLutEdgeSize3d, s_nLutEdgeSize3d, s_nLutEdgeSize3d); - - if ( g_ColorMgmtLutsOverride[nInputEOTF].HasLuts() ) - { - memcpy(g_ColorMgmtLuts[nInputEOTF].lut1d, g_ColorMgmtLutsOverride[nInputEOTF].lut1d, sizeof(g_ColorMgmtLutsOverride[nInputEOTF].lut1d)); - memcpy(g_ColorMgmtLuts[nInputEOTF].lut3d, g_ColorMgmtLutsOverride[nInputEOTF].lut3d, sizeof(g_ColorMgmtLutsOverride[nInputEOTF].lut3d)); - } - else - { - displaycolorimetry_t inputColorimetry{}; - colormapping_t colorMapping{}; - - tonemapping_t tonemapping{}; - tonemapping.bUseShaper = true; - - EOTF inputEOTF = static_cast( nInputEOTF ); - float flGain = 1.f; - std::shared_ptr pSharedLook = g_ColorMgmtLooks[ nInputEOTF ]; - lut3d_t * pLook = pSharedLook && pSharedLook->lutEdgeSize > 0 ? pSharedLook.get() : nullptr; - - if ( inputEOTF == EOTF_Gamma22 ) - { - flGain = newColorMgmt.flSDRInputGain; - if ( newColorMgmt.outputEncodingEOTF == EOTF_Gamma22 ) - { - // G22 -> G22. Does not matter what the g22 mult is - tonemapping.g22_luminance = 1.f; - // xwm_log.infof("G22 -> G22"); - } - else if ( newColorMgmt.outputEncodingEOTF == EOTF_PQ ) - { - // G22 -> PQ. SDR content going on an HDR output - tonemapping.g22_luminance = newColorMgmt.flSDROnHDRBrightness; - // xwm_log.infof("G22 -> PQ"); - } - - // The final display colorimetry is used to build the output mapping, as we want a gamut-aware handling - // for sdrGamutWideness indepdendent of the output encoding (for SDR data), and when mapping SDR -> PQ output - // we only want to utilize a portion of the gamut the actual display can reproduce - buildSDRColorimetry( &inputColorimetry, &colorMapping, newColorMgmt.sdrGamutWideness, displayColorimetry ); - } - else if ( inputEOTF == EOTF_PQ ) - { - flGain = newColorMgmt.flHDRInputGain; - if ( newColorMgmt.outputEncodingEOTF == EOTF_Gamma22 ) - { - // PQ -> G22 Leverage the display's native brightness - tonemapping.g22_luminance = newColorMgmt.flInternalDisplayBrightness; - - // Determine the tonemapping parameters - // Use the external atoms if provided - tonemap_info_t source = newColorMgmt.hdrTonemapSourceMetadata; - tonemap_info_t dest = newColorMgmt.hdrTonemapDisplayMetadata; - // Otherwise, rely on the Vulkan source info and the EDID - // TODO: If source is invalid, use the provided metadata. - // TODO: If hdrTonemapDisplayMetadata is invalid, use the one provided by the display - - // Adjust the source brightness range by the requested HDR input gain - dest.flBlackPointNits /= flGain; - dest.flWhitePointNits /= flGain; - - if ( source.BIsValid() && dest.BIsValid() ) - { - tonemapping.eOperator = newColorMgmt.hdrTonemapOperator; - tonemapping.eetf2390.init( source, newColorMgmt.hdrTonemapDisplayMetadata ); - } - else - { - tonemapping.eOperator = ETonemapOperator_None; - } - /* - xwm_log.infof("PQ -> 2.2 - g22_luminance %f gain %f", tonemapping.g22_luminance, flGain ); - xwm_log.infof("source %f %f", source.flBlackPointNits, source.flWhitePointNits ); - xwm_log.infof("dest %f %f", dest.flBlackPointNits, dest.flWhitePointNits ); - xwm_log.infof("operator %d", (int) tonemapping.eOperator );*/ - } - else if ( newColorMgmt.outputEncodingEOTF == EOTF_PQ ) - { - // PQ -> PQ passthrough (though this does apply gain) - // TODO: should we manipulate the output static metadata to reflect the gain factor? - tonemapping.g22_luminance = 1.f; - // xwm_log.infof("PQ -> PQ"); - } - - buildPQColorimetry( &inputColorimetry, &colorMapping, displayColorimetry ); - } - - calcColorTransform( &g_tmpLut1d, s_nLutSize1d, &g_tmpLut3d, inputColorimetry, inputEOTF, - outputEncodingColorimetry, newColorMgmt.outputEncodingEOTF, - newColorMgmt.outputVirtualWhite, newColorMgmt.chromaticAdaptationMode, - colorMapping, newColorMgmt.nightmode, tonemapping, pLook, flGain ); + return !!atoi( env ); +} - // Create quantized output luts - for ( size_t i=0, end = g_tmpLut1d.dataR.size(); i cv_adaptive_sync( + "adaptive_sync", + false, + "Whether or not adaptive sync is enabled if available." ); +gamescope::ConVar cv_adaptive_sync_ignore_overlay( + "adaptive_sync_ignore_overlay", + false, + "Whether or not to ignore overlay planes for pushing commits with adaptive " + "sync." ); +gamescope::ConVar cv_adaptive_sync_overlay_cycles( + "adaptive_sync_overlay_cycles", + 1, + "Number of vblank cycles to ignore overlay repaints before forcing a " + "commit with adaptive sync." ); + +gamescope::ConVar cv_upscale_preemptive( + "upscale_preemptive", true, "Allow pre-emptive upscaling" ); +gamescope::ConVar cv_upscale_preemptive_debug_force_sync( + "upscale_preemptive_debug_force_sync", + false, + "Force synchronize pre-emptive upscaling" ); - outColorMgmtLuts[nInputEOTF].bHasLut1D = true; - outColorMgmtLuts[nInputEOTF].bHasLut3D = true; +uint64_t g_SteamCompMgrLimitedAppRefreshCycle = 16'666'666; +uint64_t g_SteamCompMgrAppRefreshCycle = 16'666'666; + +static const gamescope_color_mgmt_t k_ScreenshotColorMgmt = { + .enabled = true, + .displayColorimetry = displaycolorimetry_709, + .displayEOTF = EOTF_Gamma22, + .outputEncodingColorimetry = displaycolorimetry_709, + .outputEncodingEOTF = EOTF_Gamma22, +}; - vulkan_update_luts(outColorMgmtLuts[nInputEOTF].vk_lut1d, outColorMgmtLuts[nInputEOTF].vk_lut3d, outColorMgmtLuts[nInputEOTF].lut1d, outColorMgmtLuts[nInputEOTF].lut3d); - } -} +static const gamescope_color_mgmt_t k_ScreenshotColorMgmtHDR = { + .enabled = true, + .displayColorimetry = displaycolorimetry_2020, + .displayEOTF = EOTF_PQ, + .outputEncodingColorimetry = displaycolorimetry_2020, + .outputEncodingEOTF = EOTF_PQ, +}; -gamescope::ConVar cv_tearing_enabled{ "tearing_enabled", false, "Whether or not tearing is enabled." }; -int g_nSteamMaxHeight = 0; -bool g_bVRRCapable_CachedValue = false; -bool g_bVRRInUse_CachedValue = false; -bool g_bSupportsHDR_CachedValue = false; -bool g_bForceHDR10OutputDebug = false; -gamescope::ConVar cv_hdr_enabled{ "hdr_enabled", false, "Whether or not HDR is enabled if it is available." }; -bool g_bHDRItmEnable = false; -int g_nCurrentRefreshRate_CachedValue = 0; +// #define COLOR_MGMT_MICROBENCH +// sudo cpupower frequency-set --governor performance + +static void create_color_mgmt_luts( + const gamescope_color_mgmt_t &newColorMgmt, + gamescope_color_mgmt_luts outColorMgmtLuts[ EOTF_Count ] ) +{ + const displaycolorimetry_t &displayColorimetry = + newColorMgmt.displayColorimetry; + const displaycolorimetry_t &outputEncodingColorimetry = + newColorMgmt.outputEncodingColorimetry; + + for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) + { + if ( !outColorMgmtLuts[ nInputEOTF ].vk_lut1d ) + outColorMgmtLuts[ nInputEOTF ].vk_lut1d = + vulkan_create_1d_lut( s_nLutSize1d ); + + if ( !outColorMgmtLuts[ nInputEOTF ].vk_lut3d ) + outColorMgmtLuts[ nInputEOTF ].vk_lut3d = vulkan_create_3d_lut( + s_nLutEdgeSize3d, s_nLutEdgeSize3d, s_nLutEdgeSize3d ); + + if ( g_ColorMgmtLutsOverride[ nInputEOTF ].HasLuts( ) ) + { + memcpy( + g_ColorMgmtLuts[ nInputEOTF ].lut1d, + g_ColorMgmtLutsOverride[ nInputEOTF ].lut1d, + sizeof( g_ColorMgmtLutsOverride[ nInputEOTF ].lut1d ) ); + memcpy( + g_ColorMgmtLuts[ nInputEOTF ].lut3d, + g_ColorMgmtLutsOverride[ nInputEOTF ].lut3d, + sizeof( g_ColorMgmtLutsOverride[ nInputEOTF ].lut3d ) ); + } + else + { + displaycolorimetry_t inputColorimetry{}; + colormapping_t colorMapping{}; + + tonemapping_t tonemapping{}; + tonemapping.bUseShaper = true; + + EOTF inputEOTF = static_cast( nInputEOTF ); + float flGain = 1.f; + std::shared_ptr pSharedLook = + g_ColorMgmtLooks[ nInputEOTF ]; + lut3d_t *pLook = pSharedLook && pSharedLook->lutEdgeSize > 0 + ? pSharedLook.get( ) + : nullptr; + + if ( inputEOTF == EOTF_Gamma22 ) + { + flGain = newColorMgmt.flSDRInputGain; + if ( newColorMgmt.outputEncodingEOTF == EOTF_Gamma22 ) + { + // G22 -> G22. Does not matter what the g22 mult is + tonemapping.g22_luminance = 1.f; + // xwm_log.infof("G22 -> G22"); + } + else if ( newColorMgmt.outputEncodingEOTF == EOTF_PQ ) + { + // G22 -> PQ. SDR content going on an HDR output + tonemapping.g22_luminance = + newColorMgmt.flSDROnHDRBrightness; + // xwm_log.infof("G22 -> PQ"); + } + + // The final display colorimetry is used to build the output + // mapping, as we want a gamut-aware handling for + // sdrGamutWideness indepdendent of the output encoding (for SDR + // data), and when mapping SDR -> PQ output we only want to + // utilize a portion of the gamut the actual display can + // reproduce + buildSDRColorimetry( + &inputColorimetry, + &colorMapping, + newColorMgmt.sdrGamutWideness, + displayColorimetry ); + } + else if ( inputEOTF == EOTF_PQ ) + { + flGain = newColorMgmt.flHDRInputGain; + if ( newColorMgmt.outputEncodingEOTF == EOTF_Gamma22 ) + { + // PQ -> G22 Leverage the display's native brightness + tonemapping.g22_luminance = + newColorMgmt.flInternalDisplayBrightness; + + // Determine the tonemapping parameters + // Use the external atoms if provided + tonemap_info_t source = + newColorMgmt.hdrTonemapSourceMetadata; + tonemap_info_t dest = + newColorMgmt.hdrTonemapDisplayMetadata; + // Otherwise, rely on the Vulkan source info and the EDID + // TODO: If source is invalid, use the provided metadata. + // TODO: If hdrTonemapDisplayMetadata is invalid, use the + // one provided by the display + + // Adjust the source brightness range by the requested HDR + // input gain + dest.flBlackPointNits /= flGain; + dest.flWhitePointNits /= flGain; + + if ( source.BIsValid( ) && dest.BIsValid( ) ) + { + tonemapping.eOperator = newColorMgmt.hdrTonemapOperator; + tonemapping.eetf2390.init( + source, newColorMgmt.hdrTonemapDisplayMetadata ); + } + else + { + tonemapping.eOperator = ETonemapOperator_None; + } + /* + xwm_log.infof("PQ -> 2.2 - g22_luminance %f gain %f", + tonemapping.g22_luminance, flGain ); xwm_log.infof("source + %f %f", source.flBlackPointNits, source.flWhitePointNits ); + xwm_log.infof("dest %f %f", dest.flBlackPointNits, + dest.flWhitePointNits ); xwm_log.infof("operator %d", (int) + tonemapping.eOperator );*/ + } + else if ( newColorMgmt.outputEncodingEOTF == EOTF_PQ ) + { + // PQ -> PQ passthrough (though this does apply gain) + // TODO: should we manipulate the output static metadata to + // reflect the gain factor? + tonemapping.g22_luminance = 1.f; + // xwm_log.infof("PQ -> PQ"); + } + + buildPQColorimetry( + &inputColorimetry, &colorMapping, displayColorimetry ); + } + + calcColorTransform( + &g_tmpLut1d, + s_nLutSize1d, + &g_tmpLut3d, + inputColorimetry, + inputEOTF, + outputEncodingColorimetry, + newColorMgmt.outputEncodingEOTF, + newColorMgmt.outputVirtualWhite, + newColorMgmt.chromaticAdaptationMode, + colorMapping, + newColorMgmt.nightmode, + tonemapping, + pLook, + flGain ); + + // Create quantized output luts + for ( size_t i = 0, end = g_tmpLut1d.dataR.size( ); i < end; ++i ) + { + outColorMgmtLuts[ nInputEOTF ].lut1d[ 4 * i + 0 ] = + quantize_lut_value_16bit( g_tmpLut1d.dataR[ i ] ); + outColorMgmtLuts[ nInputEOTF ].lut1d[ 4 * i + 1 ] = + quantize_lut_value_16bit( g_tmpLut1d.dataG[ i ] ); + outColorMgmtLuts[ nInputEOTF ].lut1d[ 4 * i + 2 ] = + quantize_lut_value_16bit( g_tmpLut1d.dataB[ i ] ); + outColorMgmtLuts[ nInputEOTF ].lut1d[ 4 * i + 3 ] = 0; + } + + for ( size_t i = 0, end = g_tmpLut3d.data.size( ); i < end; ++i ) + { + outColorMgmtLuts[ nInputEOTF ].lut3d[ 4 * i + 0 ] = + quantize_lut_value_16bit( g_tmpLut3d.data[ i ].r ); + outColorMgmtLuts[ nInputEOTF ].lut3d[ 4 * i + 1 ] = + quantize_lut_value_16bit( g_tmpLut3d.data[ i ].g ); + outColorMgmtLuts[ nInputEOTF ].lut3d[ 4 * i + 2 ] = + quantize_lut_value_16bit( g_tmpLut3d.data[ i ].b ); + outColorMgmtLuts[ nInputEOTF ].lut3d[ 4 * i + 3 ] = 0; + } + } + + outColorMgmtLuts[ nInputEOTF ].bHasLut1D = true; + outColorMgmtLuts[ nInputEOTF ].bHasLut3D = true; + + vulkan_update_luts( + outColorMgmtLuts[ nInputEOTF ].vk_lut1d, + outColorMgmtLuts[ nInputEOTF ].vk_lut3d, + outColorMgmtLuts[ nInputEOTF ].lut1d, + outColorMgmtLuts[ nInputEOTF ].lut3d ); + } +} + +gamescope::ConVar cv_tearing_enabled{ + "tearing_enabled", false, "Whether or not tearing is enabled." +}; +int g_nSteamMaxHeight = 0; +bool g_bVRRCapable_CachedValue = false; +bool g_bVRRInUse_CachedValue = false; +bool g_bSupportsHDR_CachedValue = false; +bool g_bForceHDR10OutputDebug = false; +gamescope::ConVar cv_hdr_enabled{ + "hdr_enabled", false, "Whether or not HDR is enabled if it is available." +}; +bool g_bHDRItmEnable = false; +int g_nCurrentRefreshRate_CachedValue = 0; -static void -update_color_mgmt() +static void update_color_mgmt( ) { - if ( !GetBackend()->GetCurrentConnector() ) - return; + if ( !GetBackend( )->GetCurrentConnector( ) ) return; - GetBackend()->GetCurrentConnector()->GetNativeColorimetry( - g_bOutputHDREnabled, - &g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF, - &g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF ); + GetBackend( )->GetCurrentConnector( )->GetNativeColorimetry( + g_bOutputHDREnabled, + &g_ColorMgmt.pending.displayColorimetry, + &g_ColorMgmt.pending.displayEOTF, + &g_ColorMgmt.pending.outputEncodingColorimetry, + &g_ColorMgmt.pending.outputEncodingEOTF ); - g_ColorMgmt.pending.flInternalDisplayBrightness = - GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxContentLightLevel; + g_ColorMgmt.pending.flInternalDisplayBrightness = + GetBackend( ) + ->GetCurrentConnector( ) + ->GetHDRInfo( ) + .uMaxContentLightLevel; #ifdef COLOR_MGMT_MICROBENCH - struct timespec t0, t1; + struct timespec t0, t1; #else - // check if any part of our color mgmt stack is dirty - if ( g_ColorMgmt.pending == g_ColorMgmt.current && g_ColorMgmt.serial != 0 ) - return; + // check if any part of our color mgmt stack is dirty + if ( g_ColorMgmt.pending == g_ColorMgmt.current && g_ColorMgmt.serial != 0 ) + return; #endif #ifdef COLOR_MGMT_MICROBENCH - clock_gettime(CLOCK_MONOTONIC_RAW, &t0); + clock_gettime( CLOCK_MONOTONIC_RAW, &t0 ); #endif - if (g_ColorMgmt.pending.enabled) - { - create_color_mgmt_luts(g_ColorMgmt.pending, g_ColorMgmtLuts); - } - else - { - for ( uint32_t i = 0; i < EOTF_Count; i++ ) - g_ColorMgmtLuts[i].reset(); - } + if ( g_ColorMgmt.pending.enabled ) + { + create_color_mgmt_luts( g_ColorMgmt.pending, g_ColorMgmtLuts ); + } + else + { + for ( uint32_t i = 0; i < EOTF_Count; i++ ) + g_ColorMgmtLuts[ i ].reset( ); + } #ifdef COLOR_MGMT_MICROBENCH - clock_gettime(CLOCK_MONOTONIC_RAW, &t1); + clock_gettime( CLOCK_MONOTONIC_RAW, &t1 ); #endif #ifdef COLOR_MGMT_MICROBENCH - double delta = (timespec_to_nanos(t1) - timespec_to_nanos(t0)) / 1000000.0; + double delta = + ( timespec_to_nanos( t1 ) - timespec_to_nanos( t0 ) ) / 1000000.0; - static uint32_t iter = 0; - static const uint32_t iter_count = 120; - static double accum = 0; + static uint32_t iter = 0; + static const uint32_t iter_count = 120; + static double accum = 0; - accum += delta; + accum += delta; - if (iter++ == iter_count) - { - printf("update_color_mgmt: %.3fms\n", accum / iter_count); + if ( iter++ == iter_count ) + { + printf( "update_color_mgmt: %.3fms\n", accum / iter_count ); - iter = 0; - accum = 0; - } + iter = 0; + accum = 0; + } #endif - static uint32_t s_NextColorMgmtSerial = 0; + static uint32_t s_NextColorMgmtSerial = 0; - g_ColorMgmt.serial = ++s_NextColorMgmtSerial; - g_ColorMgmt.current = g_ColorMgmt.pending; + g_ColorMgmt.serial = ++s_NextColorMgmtSerial; + g_ColorMgmt.current = g_ColorMgmt.pending; } -static void -update_screenshot_color_mgmt() +static void update_screenshot_color_mgmt( ) { - create_color_mgmt_luts(k_ScreenshotColorMgmt, g_ScreenshotColorMgmtLuts); - create_color_mgmt_luts(k_ScreenshotColorMgmtHDR, g_ScreenshotColorMgmtLutsHDR); + create_color_mgmt_luts( k_ScreenshotColorMgmt, g_ScreenshotColorMgmtLuts ); + create_color_mgmt_luts( + k_ScreenshotColorMgmtHDR, g_ScreenshotColorMgmtLutsHDR ); } bool set_color_sdr_gamut_wideness( float flVal ) { - if ( g_ColorMgmt.pending.sdrGamutWideness == flVal ) - return false; + if ( g_ColorMgmt.pending.sdrGamutWideness == flVal ) return false; - g_ColorMgmt.pending.sdrGamutWideness = flVal; + g_ColorMgmt.pending.sdrGamutWideness = flVal; - return g_ColorMgmt.pending.enabled; + return g_ColorMgmt.pending.enabled; } bool set_internal_display_brightness( float flVal ) { - if ( flVal < 1.f ) - { - flVal = 500.f; - } + if ( flVal < 1.f ) { flVal = 500.f; } - if ( g_ColorMgmt.pending.flInternalDisplayBrightness == flVal ) - return false; + if ( g_ColorMgmt.pending.flInternalDisplayBrightness == flVal ) + return false; - g_ColorMgmt.pending.flInternalDisplayBrightness = flVal; - g_flInternalDisplayBrightnessNits = flVal; + g_ColorMgmt.pending.flInternalDisplayBrightness = flVal; + g_flInternalDisplayBrightnessNits = flVal; - return g_ColorMgmt.pending.enabled; + return g_ColorMgmt.pending.enabled; } bool set_sdr_on_hdr_brightness( float flVal ) { - if ( flVal < 1.f ) - { - flVal = 203.f; - } + if ( flVal < 1.f ) { flVal = 203.f; } - if ( g_ColorMgmt.pending.flSDROnHDRBrightness == flVal ) - return false; + if ( g_ColorMgmt.pending.flSDROnHDRBrightness == flVal ) return false; - g_ColorMgmt.pending.flSDROnHDRBrightness = flVal; + g_ColorMgmt.pending.flSDROnHDRBrightness = flVal; - return g_ColorMgmt.pending.enabled; + return g_ColorMgmt.pending.enabled; } bool set_hdr_input_gain( float flVal ) { - if ( flVal < 0.f ) - { - flVal = 1.f; - } + if ( flVal < 0.f ) { flVal = 1.f; } - if ( g_ColorMgmt.pending.flHDRInputGain == flVal ) - return false; + if ( g_ColorMgmt.pending.flHDRInputGain == flVal ) return false; - g_ColorMgmt.pending.flHDRInputGain = flVal; + g_ColorMgmt.pending.flHDRInputGain = flVal; - return g_ColorMgmt.pending.enabled; + return g_ColorMgmt.pending.enabled; } bool set_sdr_input_gain( float flVal ) { - if ( flVal < 0.f ) - { - flVal = 1.f; - } + if ( flVal < 0.f ) { flVal = 1.f; } - if ( g_ColorMgmt.pending.flSDRInputGain == flVal ) - return false; + if ( g_ColorMgmt.pending.flSDRInputGain == flVal ) return false; - g_ColorMgmt.pending.flSDRInputGain = flVal; - return g_ColorMgmt.pending.enabled; + g_ColorMgmt.pending.flSDRInputGain = flVal; + return g_ColorMgmt.pending.enabled; } bool set_color_nightmode( const nightmode_t &nightmode ) { - if ( g_ColorMgmt.pending.nightmode == nightmode ) - return false; + if ( g_ColorMgmt.pending.nightmode == nightmode ) return false; - g_ColorMgmt.pending.nightmode = nightmode; + g_ColorMgmt.pending.nightmode = nightmode; - return g_ColorMgmt.pending.enabled; + return g_ColorMgmt.pending.enabled; } bool set_color_mgmt_enabled( bool bEnabled ) { - if ( g_ColorMgmt.pending.enabled == bEnabled ) - return false; + if ( g_ColorMgmt.pending.enabled == bEnabled ) return false; - g_ColorMgmt.pending.enabled = bEnabled; + g_ColorMgmt.pending.enabled = bEnabled; - return true; + return true; } -static gamescope::OwningRc s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; -static std::shared_ptr s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; -static float g_flMuraScale = 1.0f; -static bool g_bMuraCompensationDisabled = false; +static gamescope::OwningRc + s_MuraCorrectionImage[ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ]; +static std::shared_ptr + s_MuraCTMBlob[ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ]; +static float g_flMuraScale = 1.0f; +static bool g_bMuraCompensationDisabled = false; -bool is_mura_correction_enabled() +bool is_mura_correction_enabled( ) { - if ( !GetBackend()->GetCurrentConnector() ) - return false; + if ( !GetBackend( )->GetCurrentConnector( ) ) return false; - return s_MuraCorrectionImage[GetBackend()->GetCurrentConnector()->GetScreenType()] != nullptr && !g_bMuraCompensationDisabled; + return s_MuraCorrectionImage[ GetBackend( ) + ->GetCurrentConnector( ) + ->GetScreenType( ) ] != nullptr && + !g_bMuraCompensationDisabled; } -void update_mura_ctm() +void update_mura_ctm( ) { - s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = nullptr; - if (s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] == nullptr) - return; + s_MuraCTMBlob[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = nullptr; + if ( s_MuraCorrectionImage[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] == + nullptr ) + return; - static constexpr float kMuraMapScale = 0.0625f; - static constexpr float kMuraOffset = -127.0f / 255.0f; + static constexpr float kMuraMapScale = 0.0625f; + static constexpr float kMuraOffset = -127.0f / 255.0f; - // Mura's influence scales non-linearly with brightness, so we have an additional scale - // on top of the scale factor for the underlying mura map. - const float flScale = g_flMuraScale * kMuraMapScale; - glm::mat3x4 mura_scale_offset = glm::mat3x4 - { - flScale, 0, 0, kMuraOffset * flScale, - 0, flScale, 0, kMuraOffset * flScale, - 0, 0, 0, 0, // No mura comp for blue channel. - }; - s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = GetBackend()->CreateBackendBlob( mura_scale_offset ); + // Mura's influence scales non-linearly with brightness, so we have an + // additional scale on top of the scale factor for the underlying mura map. + const float flScale = g_flMuraScale * kMuraMapScale; + glm::mat3x4 mura_scale_offset = glm::mat3x4{ + flScale, 0, 0, kMuraOffset * flScale, + 0, flScale, 0, kMuraOffset * flScale, + 0, 0, 0, 0, // No mura comp for blue channel. + }; + s_MuraCTMBlob[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = + GetBackend( )->CreateBackendBlob( mura_scale_offset ); } bool g_bMuraDebugFullColor = false; bool set_mura_overlay( const char *path ) { - xwm_log.infof("[josh mura correction] Setting mura correction image to: %s", path); - s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = nullptr; - update_mura_ctm(); + xwm_log.infof( + "[josh mura correction] Setting mura correction image to: %s", path ); + s_MuraCorrectionImage[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = + nullptr; + update_mura_ctm( ); - std::string red_path = std::string(path) + "_red.png"; - std::string green_path = std::string(path) + "_green.png"; + std::string red_path = std::string( path ) + "_red.png"; + std::string green_path = std::string( path ) + "_green.png"; - int red_w, red_h, red_comp; - unsigned char *red_data = stbi_load(red_path.c_str(), &red_w, &red_h, &red_comp, 1); - int green_w, green_h, green_comp; - unsigned char *green_data = stbi_load(green_path.c_str(), &green_w, &green_h, &green_comp, 1); - if (!red_data || !green_data || red_w != green_w || red_h != green_h || red_comp != green_comp || red_comp != 1 || green_comp != 1) - { - xwm_log.infof("[josh mura correction] Couldn't load mura correction image, disabling mura correction."); - return true; - } + int red_w, red_h, red_comp; + unsigned char *red_data = + stbi_load( red_path.c_str( ), &red_w, &red_h, &red_comp, 1 ); + int green_w, green_h, green_comp; + unsigned char *green_data = + stbi_load( green_path.c_str( ), &green_w, &green_h, &green_comp, 1 ); + if ( !red_data || !green_data || red_w != green_w || red_h != green_h || + red_comp != green_comp || red_comp != 1 || green_comp != 1 ) + { + xwm_log.infof( + "[josh mura correction] Couldn't load mura correction image, " + "disabling mura correction." ); + return true; + } - int w = red_w; - int h = red_h; - unsigned char *data = (unsigned char*)malloc(red_w * red_h * 4); + int w = red_w; + int h = red_h; + unsigned char *data = ( unsigned char * )malloc( red_w * red_h * 4 ); - for (int y = 0; y < h; y++) - { - for (int x = 0; x < w; x++) - { - data[(y * w * 4) + (x * 4) + 0] = g_bMuraDebugFullColor ? 255 : red_data[y * w + x]; - data[(y * w * 4) + (x * 4) + 1] = g_bMuraDebugFullColor ? 255 : green_data[y * w + x]; - data[(y * w * 4) + (x * 4) + 2] = 127; // offset of 0. - data[(y * w * 4) + (x * 4) + 3] = 0; // Make alpha = 0 so we act as addtive. - } - } - free(red_data); - free(green_data); + for ( int y = 0; y < h; y++ ) + { + for ( int x = 0; x < w; x++ ) + { + data[ ( y * w * 4 ) + ( x * 4 ) + 0 ] = + g_bMuraDebugFullColor ? 255 : red_data[ y * w + x ]; + data[ ( y * w * 4 ) + ( x * 4 ) + 1 ] = + g_bMuraDebugFullColor ? 255 : green_data[ y * w + x ]; + data[ ( y * w * 4 ) + ( x * 4 ) + 2 ] = 127; // offset of 0. + data[ ( y * w * 4 ) + ( x * 4 ) + 3 ] = + 0; // Make alpha = 0 so we act as addtive. + } + } + free( red_data ); + free( green_data ); - CVulkanTexture::createFlags texCreateFlags; - texCreateFlags.bFlippable = true; - texCreateFlags.bSampled = true; - s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = vulkan_create_texture_from_bits(w, h, w, h, DRM_FORMAT_ABGR8888, texCreateFlags, (void*)data); - free(data); + CVulkanTexture::createFlags texCreateFlags; + texCreateFlags.bFlippable = true; + texCreateFlags.bSampled = true; + s_MuraCorrectionImage[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = + vulkan_create_texture_from_bits( + w, h, w, h, DRM_FORMAT_ABGR8888, texCreateFlags, ( void * )data ); + free( data ); - xwm_log.infof("[josh mura correction] Loaded new mura correction image!"); + xwm_log.infof( "[josh mura correction] Loaded new mura correction image!" ); - update_mura_ctm(); + update_mura_ctm( ); - return true; + return true; } -bool set_mura_scale(float new_scale) +bool set_mura_scale( float new_scale ) { - bool diff = g_flMuraScale != new_scale; - g_flMuraScale = new_scale; - update_mura_ctm(); - return diff; + bool diff = g_flMuraScale != new_scale; + g_flMuraScale = new_scale; + update_mura_ctm( ); + return diff; } -bool set_color_3dlut_override(const char *path) +bool set_color_3dlut_override( const char *path ) { - int nLutIndex = EOTF_Gamma22; - g_ColorMgmt.pending.externalDirtyCtr++; - g_ColorMgmtLutsOverride[nLutIndex].bHasLut3D = false; + int nLutIndex = EOTF_Gamma22; + g_ColorMgmt.pending.externalDirtyCtr++; + g_ColorMgmtLutsOverride[ nLutIndex ].bHasLut3D = false; - FILE *f = fopen(path, "rb"); - if (!f) { - return true; - } - defer( fclose(f) ); + FILE *f = fopen( path, "rb" ); + if ( !f ) { return true; } + defer( fclose( f ) ); - fseek(f, 0, SEEK_END); - long fsize = ftell(f); - fseek(f, 0, SEEK_SET); + fseek( f, 0, SEEK_END ); + long fsize = ftell( f ); + fseek( f, 0, SEEK_SET ); - size_t elems = fsize / sizeof(uint16_t); + size_t elems = fsize / sizeof( uint16_t ); - if (elems == 0) { - return true; - } + if ( elems == 0 ) { return true; } - fread(g_ColorMgmtLutsOverride[nLutIndex].lut3d, elems, sizeof(uint16_t), f); - g_ColorMgmtLutsOverride[nLutIndex].bHasLut3D = true; + fread( + g_ColorMgmtLutsOverride[ nLutIndex ].lut3d, + elems, + sizeof( uint16_t ), + f ); + g_ColorMgmtLutsOverride[ nLutIndex ].bHasLut3D = true; - return true; + return true; } -bool set_color_shaperlut_override(const char *path) +bool set_color_shaperlut_override( const char *path ) { - int nLutIndex = EOTF_Gamma22; - g_ColorMgmt.pending.externalDirtyCtr++; - g_ColorMgmtLutsOverride[nLutIndex].bHasLut1D = false; + int nLutIndex = EOTF_Gamma22; + g_ColorMgmt.pending.externalDirtyCtr++; + g_ColorMgmtLutsOverride[ nLutIndex ].bHasLut1D = false; - FILE *f = fopen(path, "rb"); - if (!f) { - return true; - } - defer( fclose(f) ); + FILE *f = fopen( path, "rb" ); + if ( !f ) { return true; } + defer( fclose( f ) ); - fseek(f, 0, SEEK_END); - long fsize = ftell(f); - fseek(f, 0, SEEK_SET); + fseek( f, 0, SEEK_END ); + long fsize = ftell( f ); + fseek( f, 0, SEEK_SET ); - size_t elems = fsize / sizeof(uint16_t); + size_t elems = fsize / sizeof( uint16_t ); - if (elems == 0) { - return true; - } + if ( elems == 0 ) { return true; } - fread(g_ColorMgmtLutsOverride[nLutIndex].lut1d, elems, sizeof(uint16_t), f); - g_ColorMgmtLutsOverride[nLutIndex].bHasLut1D = true; + fread( + g_ColorMgmtLutsOverride[ nLutIndex ].lut1d, + elems, + sizeof( uint16_t ), + f ); + g_ColorMgmtLutsOverride[ nLutIndex ].bHasLut1D = true; - return true; + return true; } -bool set_color_look_pq(const char *path) +bool set_color_look_pq( const char *path ) { - bool bRaisesBlackLevelFloor = false; - g_ColorMgmtLooks[EOTF_PQ] = LoadCubeLut( path, bRaisesBlackLevelFloor ); - cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; - g_ColorMgmt.pending.externalDirtyCtr++; - return true; + bool bRaisesBlackLevelFloor = false; + g_ColorMgmtLooks[ EOTF_PQ ] = LoadCubeLut( path, bRaisesBlackLevelFloor ); + cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; + g_ColorMgmt.pending.externalDirtyCtr++; + return true; } -bool set_color_look_g22(const char *path) +bool set_color_look_g22( const char *path ) { - bool bRaisesBlackLevelFloor = false; - g_ColorMgmtLooks[EOTF_Gamma22] = LoadCubeLut( path, bRaisesBlackLevelFloor ); - cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; - g_ColorMgmt.pending.externalDirtyCtr++; - return true; + bool bRaisesBlackLevelFloor = false; + g_ColorMgmtLooks[ EOTF_Gamma22 ] = + LoadCubeLut( path, bRaisesBlackLevelFloor ); + cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; + g_ColorMgmt.pending.externalDirtyCtr++; + return true; } bool g_bColorSliderInUse = false; -template< typename T > -constexpr const T& clamp( const T& x, const T& min, const T& max ) -{ - return x < min ? min : max < x ? max : x; -} +template +constexpr const T &clamp( const T &x, const T &min, const T &max ) +{ return x < min ? min : max < x ? max : x; } extern bool g_bForceRelativeMouse; CommitDoneList_t g_steamcompmgr_xdg_done_commits; - -static std::mutex s_KeysToCloseMutex; +static std::mutex s_KeysToCloseMutex; static std::vector s_KeysToClose; -void close_virtual_connector_key(gamescope::VirtualConnectorKey_t eKey) +void close_virtual_connector_key( gamescope::VirtualConnectorKey_t eKey ) { - std::unique_lock lock{ s_KeysToCloseMutex }; - s_KeysToClose.push_back( eKey ); + std::unique_lock lock{ s_KeysToCloseMutex }; + s_KeysToClose.push_back( eKey ); } -struct ignore { - struct ignore *next; - unsigned long sequence; +struct ignore +{ + struct ignore *next; + unsigned long sequence; }; -gamescope::CAsyncWaiter> g_ImageWaiter{ "gamescope_img" }; +gamescope::CAsyncWaiter> g_ImageWaiter{ + "gamescope_img" +}; gamescope::CWaiter g_SteamCompMgrWaiter; -Window x11_win(steamcompmgr_win_t *w) { - if (w == nullptr) - return None; - if (w->type != steamcompmgr_win_type_t::XWAYLAND) - return None; - return w->xwayland().id; +Window x11_win( steamcompmgr_win_t *w ) +{ + if ( w == nullptr ) return None; + if ( w->type != steamcompmgr_win_type_t::XWAYLAND ) return None; + return w->xwayland( ).id; } static std::atomic s_ulFocusSerial = 0ul; -void MakeFocusDirty() -{ - s_ulFocusSerial++; -} +void MakeFocusDirty( ) { s_ulFocusSerial++; } -static inline uint64_t GetFocusSerial() -{ - return s_ulFocusSerial; -} +static inline uint64_t GetFocusSerial( ) { return s_ulFocusSerial; } -bool focus_t::IsDirty() -{ - return ulCurrentFocusSerial != GetFocusSerial(); -} +bool focus_t::IsDirty( ) { return ulCurrentFocusSerial != GetFocusSerial( ); } struct global_focus_t : public focus_t { - steamcompmgr_win_t *keyboardFocusWindow; - steamcompmgr_win_t *fadeWindow; - MouseCursor *cursor; + steamcompmgr_win_t *keyboardFocusWindow; + steamcompmgr_win_t *fadeWindow; + MouseCursor *cursor; - gamescope::VirtualConnectorKey_t ulVirtualFocusKey = 0; - std::shared_ptr pVirtualConnector; + gamescope::VirtualConnectorKey_t ulVirtualFocusKey = 0; + std::shared_ptr pVirtualConnector; - gamescope::INestedHints *GetNestedHints() - { - gamescope::IBackendConnector *pConnector = this->pVirtualConnector.get(); - if ( !pConnector ) - pConnector = GetBackend()->GetCurrentConnector(); + gamescope::INestedHints *GetNestedHints( ) + { + gamescope::IBackendConnector *pConnector = + this->pVirtualConnector.get( ); + if ( !pConnector ) pConnector = GetBackend( )->GetCurrentConnector( ); - if ( pConnector ) - { - return pConnector->GetNestedHints(); - } + if ( pConnector ) { return pConnector->GetNestedHints( ); } - return nullptr; - } + return nullptr; + } }; -std::unordered_map g_VirtualConnectorFocuses; -global_focus_t *GetCurrentFocus() +std::unordered_map + g_VirtualConnectorFocuses; +global_focus_t *GetCurrentFocus( ) { - uint64_t ulKey = GetBackend()->GetCurrentConnector() ? GetBackend()->GetCurrentConnector()->GetVirtualConnectorKey() : 0; + uint64_t ulKey = + GetBackend( )->GetCurrentConnector( ) + ? GetBackend( )->GetCurrentConnector( )->GetVirtualConnectorKey( ) + : 0; - auto iter = g_VirtualConnectorFocuses.find( ulKey ); - if ( iter != g_VirtualConnectorFocuses.end() ) - return &iter->second; + auto iter = g_VirtualConnectorFocuses.find( ulKey ); + if ( iter != g_VirtualConnectorFocuses.end( ) ) return &iter->second; - return nullptr; + return nullptr; } -global_focus_t *GetCurrentMouseFocus() +global_focus_t *GetCurrentMouseFocus( ) { - uint64_t ulKey = GetBackend()->GetCurrentMouseConnector() ? GetBackend()->GetCurrentMouseConnector()->GetVirtualConnectorKey() : 0; + uint64_t ulKey = GetBackend( )->GetCurrentMouseConnector( ) + ? GetBackend( ) + ->GetCurrentMouseConnector( ) + ->GetVirtualConnectorKey( ) + : 0; - auto iter = g_VirtualConnectorFocuses.find( ulKey ); - if ( iter != g_VirtualConnectorFocuses.end() ) - return &iter->second; + auto iter = g_VirtualConnectorFocuses.find( ulKey ); + if ( iter != g_VirtualConnectorFocuses.end( ) ) return &iter->second; - return GetCurrentFocus(); + return GetCurrentFocus( ); } -uint32_t currentOutputWidth, currentOutputHeight; -int currentOutputRefresh; -bool currentHDROutput = false; -bool currentHDRForce = false; +uint32_t currentOutputWidth, currentOutputHeight; +int currentOutputRefresh; +bool currentHDROutput = false; +bool currentHDRForce = false; -std::vector< uint32_t > vecFocuscontrolAppIDs; +std::vector vecFocuscontrolAppIDs; -bool gameFocused; +bool gameFocused; -unsigned int gamesRunningCount; +unsigned int gamesRunningCount; -float overscanScaleRatio = 1.0; -float zoomScaleRatio = 1.0; -float globalScaleRatio = 1.0f; +float overscanScaleRatio = 1.0; +float zoomScaleRatio = 1.0; +float globalScaleRatio = 1.0f; -float focusedWindowScaleX = 1.0f; -float focusedWindowScaleY = 1.0f; -float focusedWindowOffsetX = 0.0f; -float focusedWindowOffsetY = 0.0f; +float focusedWindowScaleX = 1.0f; +float focusedWindowScaleY = 1.0f; +float focusedWindowOffsetX = 0.0f; +float focusedWindowOffsetY = 0.0f; -uint32_t inputCounter; -uint32_t lastPublishedInputCounter; +uint32_t inputCounter; +uint32_t lastPublishedInputCounter; -std::atomic hasRepaint = false; -bool hasRepaintNonBasePlane = false; +std::atomic hasRepaint = false; +bool hasRepaintNonBasePlane = false; -bool g_bUpdateForwardedVROverlays = false; +bool g_bUpdateForwardedVROverlays = false; -static gamescope::ConCommand cc_debug_force_repaint( "debug_force_repaint", "Force a repaint", -[]( std::span args ) -{ - hasRepaint = true; -}); +static gamescope::ConCommand cc_debug_force_repaint( + "debug_force_repaint", + "Force a repaint", + []( std::span args ) { hasRepaint = true; } ); -unsigned long damageSequence = 0; +unsigned long damageSequence = 0; -uint64_t cursorHideTime = 10'000ul * 1'000'000ul; +uint64_t cursorHideTime = 10'000ul * 1'000'000ul; -bool gotXError = false; +bool gotXError = false; -unsigned int fadeOutStartTime = 0; +unsigned int fadeOutStartTime = 0; -unsigned int g_FadeOutDuration = 0; +unsigned int g_FadeOutDuration = 0; extern float g_flMaxWindowScale; -bool synchronize; +bool synchronize; std::mutex g_SteamCompMgrXWaylandServerMutex; gamescope::VBlankTime g_SteamCompMgrVBlankTime = {}; uint64_t g_uCurrentBasePlaneCommitID = 0; -uint32_t g_uCurrentBasePlaneAppID = 0; -bool g_bCurrentBasePlaneIsFifo = false; +uint32_t g_uCurrentBasePlaneAppID = 0; +bool g_bCurrentBasePlaneIsFifo = false; -static int g_nSteamCompMgrTargetFPS = 0; -static uint64_t g_uDynamicRefreshEqualityTime = 0; -static int g_nDynamicRefreshRate[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT] = { 0, 0 }; +static int g_nSteamCompMgrTargetFPS = 0; +static uint64_t g_uDynamicRefreshEqualityTime = 0; +static int g_nDynamicRefreshRate[ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ] = { + 0, 0 +}; // Delay to stop modes flickering back and forth. static const uint64_t g_uDynamicRefreshDelay = 600'000'000; // 600ms -static int g_nCombinedAppRefreshCycleOverride[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT] = { 0, 0 }; -bool g_nCombinedAppRefreshCycleChangeRefresh[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT] = { true, true }; -bool g_nCombinedAppRefreshCycleChangeFPS[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT] = { true, true }; +static int g_nCombinedAppRefreshCycleOverride + [ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ] = { 0, 0 }; +bool g_nCombinedAppRefreshCycleChangeRefresh + [ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ] = { true, true }; +bool g_nCombinedAppRefreshCycleChangeFPS + [ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ] = { true, true }; -static void _update_app_target_refresh_cycle() +static void _update_app_target_refresh_cycle( ) { - if ( !GetBackend()->GetCurrentConnector() ) - return; + if ( !GetBackend( )->GetCurrentConnector( ) ) return; - gamescope::GamescopeScreenType type = GetBackend()->GetCurrentConnector()->GetScreenType(); + gamescope::GamescopeScreenType type = + GetBackend( )->GetCurrentConnector( )->GetScreenType( ); - int target_fps = g_nCombinedAppRefreshCycleOverride[type]; + int target_fps = g_nCombinedAppRefreshCycleOverride[ type ]; - g_nDynamicRefreshRate[ type ] = 0; - g_nSteamCompMgrTargetFPS = 0; + g_nDynamicRefreshRate[ type ] = 0; + g_nSteamCompMgrTargetFPS = 0; - if ( !target_fps ) - { - return; - } + if ( !target_fps ) { return; } - if ( g_nCombinedAppRefreshCycleChangeFPS[ type ] ) - { - g_nSteamCompMgrTargetFPS = target_fps; - } + if ( g_nCombinedAppRefreshCycleChangeFPS[ type ] ) + { + g_nSteamCompMgrTargetFPS = target_fps; + } - if ( g_nCombinedAppRefreshCycleChangeRefresh[ type ] ) - { - auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates(); + if ( g_nCombinedAppRefreshCycleChangeRefresh[ type ] ) + { + auto rates = GetBackend( ) + ->GetCurrentConnector( ) + ->GetValidDynamicRefreshRates( ); - // Find highest mode to do refresh doubling with. - for ( auto rate = rates.rbegin(); rate != rates.rend(); rate++ ) - { - if (*rate % target_fps == 0) - { - g_nDynamicRefreshRate[ type ] = *rate; - return; - } - } - } + // Find highest mode to do refresh doubling with. + for ( auto rate = rates.rbegin( ); rate != rates.rend( ); rate++ ) + { + if ( *rate % target_fps == 0 ) + { + g_nDynamicRefreshRate[ type ] = *rate; + return; + } + } + } } -static void update_app_target_refresh_cycle() +static void update_app_target_refresh_cycle( ) { - int nPrevFPSLimit = g_nSteamCompMgrTargetFPS; - _update_app_target_refresh_cycle(); - if ( !!g_nSteamCompMgrTargetFPS != !!nPrevFPSLimit ) - update_runtime_info(); + int nPrevFPSLimit = g_nSteamCompMgrTargetFPS; + _update_app_target_refresh_cycle( ); + if ( !!g_nSteamCompMgrTargetFPS != !!nPrevFPSLimit ) update_runtime_info( ); } -void steamcompmgr_set_app_refresh_cycle_override( gamescope::GamescopeScreenType type, int override_fps, bool change_refresh, bool change_fps_cap ) +void steamcompmgr_set_app_refresh_cycle_override( + gamescope::GamescopeScreenType type, + int override_fps, + bool change_refresh, + bool change_fps_cap ) { - g_nCombinedAppRefreshCycleOverride[ type ] = override_fps; - g_nCombinedAppRefreshCycleChangeRefresh[ type ] = change_refresh; - g_nCombinedAppRefreshCycleChangeFPS[ type ] = change_fps_cap; - update_app_target_refresh_cycle(); + g_nCombinedAppRefreshCycleOverride[ type ] = override_fps; + g_nCombinedAppRefreshCycleChangeRefresh[ type ] = change_refresh; + g_nCombinedAppRefreshCycleChangeFPS[ type ] = change_fps_cap; + update_app_target_refresh_cycle( ); } -gamescope::ConCommand cc_debug_set_fps_limit( "debug_set_fps_limit", "Set refresh cycle (debug)", -[](std::span svArgs) -{ - if ( svArgs.size() < 2 ) - return; +gamescope::ConCommand cc_debug_set_fps_limit( + "debug_set_fps_limit", + "Set refresh cycle (debug)", + []( std::span svArgs ) + { + if ( svArgs.size( ) < 2 ) return; - // TODO: Expose all facets as args. - std::optional onFps = gamescope::Parse( svArgs[1] ); - if ( !onFps ) - { - console_log.errorf( "Failed to parse FPS." ); - return; - } + // TODO: Expose all facets as args. + std::optional onFps = gamescope::Parse( svArgs[ 1 ] ); + if ( !onFps ) + { + console_log.errorf( "Failed to parse FPS." ); + return; + } - int32_t nFps = *onFps; + int32_t nFps = *onFps; - steamcompmgr_set_app_refresh_cycle_override( GetBackend()->GetScreenType(), nFps, true, true ); -}); + steamcompmgr_set_app_refresh_cycle_override( + GetBackend( )->GetScreenType( ), nFps, true, true ); + } ); static int g_nRuntimeInfoFd = -1; bool g_bFSRActive = false; -BlurMode g_BlurMode = BLUR_MODE_OFF; -BlurMode g_BlurModeOld = BLUR_MODE_OFF; -unsigned int g_BlurFadeDuration = 0; -int g_BlurRadius = 5; +BlurMode g_BlurMode = BLUR_MODE_OFF; +BlurMode g_BlurModeOld = BLUR_MODE_OFF; +unsigned int g_BlurFadeDuration = 0; +int g_BlurRadius = 5; unsigned int g_BlurFadeStartTime = 0; -pid_t focusWindow_pid, sdFocusWindow_pid; +pid_t focusWindow_pid, sdFocusWindow_pid; std::atomic> focusWindow_engine = nullptr; -focus_t g_steamcompmgr_xdg_focus; +focus_t g_steamcompmgr_xdg_focus; std::vector> g_steamcompmgr_xdg_wins; -static bool -window_is_steam( steamcompmgr_win_t *w ) -{ - return w && ( w->isSteamLegacyBigPicture || w->appID == 769 ); -} +static bool window_is_steam( steamcompmgr_win_t *w ) +{ return w && ( w->isSteamLegacyBigPicture || w->appID == 769 ); } -static bool -window_is_vr_scene_app( steamcompmgr_win_t *w ) +static bool window_is_vr_scene_app( steamcompmgr_win_t *w ) { - return w && w->appID && w->appID == g_unCurrentVRSceneAppId.load( std::memory_order_relaxed ); + return w && w->appID && + w->appID == + g_unCurrentVRSceneAppId.load( std::memory_order_relaxed ); } bool g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = false; bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w ) { - return w && !window_is_steam( w ) && !window_is_vr_scene_app( w ) && !w->isOverlay && !w->isExternalOverlay; + return w && !window_is_steam( w ) && !window_is_vr_scene_app( w ) && + !w->isOverlay && !w->isExternalOverlay; } -static bool -steamcompmgr_user_has_any_game_open() +static bool steamcompmgr_user_has_any_game_open( ) { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - if (!server->ctx) - continue; + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); i++ ) + { + if ( !server->ctx ) continue; - if (steamcompmgr_window_should_limit_fps( server->ctx->focus.focusWindow )) - return true; - } + if ( steamcompmgr_window_should_limit_fps( + server->ctx->focus.focusWindow ) ) + return true; + } - return false; + return false; } bool steamcompmgr_window_should_refresh_switch( steamcompmgr_win_t *w ) { - if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() ) - return false; + if ( GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->IsVRRActive( ) ) + return false; - if ( g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive ) - return steamcompmgr_user_has_any_game_open(); + if ( g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive ) + return steamcompmgr_user_has_any_game_open( ); - return steamcompmgr_window_should_limit_fps( w ); + return steamcompmgr_window_should_limit_fps( w ); } - enum HeldCommitTypes_t { - HELD_COMMIT_BASE, - HELD_COMMIT_FADE, + HELD_COMMIT_BASE, + HELD_COMMIT_FADE, - HELD_COMMIT_COUNT, + HELD_COMMIT_COUNT, }; std::array, HELD_COMMIT_COUNT> g_HeldCommits; -bool g_bPendingFade = false; +bool g_bPendingFade = false; /* opacity property name; sometime soon I'll write up an EWMH spec for it */ -#define OPACITY_PROP "_NET_WM_WINDOW_OPACITY" -#define GAME_PROP "STEAM_GAME" -#define STEAM_PROP "STEAM_BIGPICTURE" -#define OVERLAY_PROP "STEAM_OVERLAY" -#define EXTERNAL_OVERLAY_PROP "GAMESCOPE_EXTERNAL_OVERLAY" -#define GAMES_RUNNING_PROP "STEAM_GAMES_RUNNING" -#define SCREEN_SCALE_PROP "STEAM_SCREEN_SCALE" -#define SCREEN_MAGNIFICATION_PROP "STEAM_SCREEN_MAGNIFICATION" - -#define TRANSLUCENT 0x00000000 -#define OPAQUE 0xffffffff +#define OPACITY_PROP "_NET_WM_WINDOW_OPACITY" +#define GAME_PROP "STEAM_GAME" +#define STEAM_PROP "STEAM_BIGPICTURE" +#define OVERLAY_PROP "STEAM_OVERLAY" +#define EXTERNAL_OVERLAY_PROP "GAMESCOPE_EXTERNAL_OVERLAY" +#define GAMES_RUNNING_PROP "STEAM_GAMES_RUNNING" +#define SCREEN_SCALE_PROP "STEAM_SCREEN_SCALE" +#define SCREEN_MAGNIFICATION_PROP "STEAM_SCREEN_MAGNIFICATION" + +#define TRANSLUCENT 0x00000000 +#define OPAQUE 0xffffffff #define ICCCM_WITHDRAWN_STATE 0 #define ICCCM_NORMAL_STATE 1 @@ -1128,55 +1199,60 @@ bool g_bPendingFade = false; #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 -#define FRAME_RATE_SAMPLING_PERIOD 160 +#define FRAME_RATE_SAMPLING_PERIOD 160 -unsigned int frameCounter; -unsigned int lastSampledFrameTime; -float currentFrameRate; +unsigned int frameCounter; +unsigned int lastSampledFrameTime; +float currentFrameRate; -static bool debugFocus = false; -static bool drawDebugInfo = false; -static bool debugEvents = false; -extern bool steamMode; +static bool debugFocus = false; +static bool drawDebugInfo = false; +static bool debugEvents = false; +extern bool steamMode; -gamescope::ConVar cv_composite_force{ "composite_force", false, "Force composition always, never use scanout" }; -static bool useXRes = true; +gamescope::ConVar cv_composite_force{ + "composite_force", false, "Force composition always, never use scanout" +}; +static bool useXRes = true; namespace gamescope { - CScreenshotManager &CScreenshotManager::Get() - { - static CScreenshotManager s_Instance; - return s_Instance; - } - - static ConCommand cc_screenshot( "screenshot", "Take a screenshot to a given path.", - []( std::span args ) - { - std::string_view szPath = "/tmp/gamescope.png"; - if ( args.size() > 1 ) - szPath = args[1]; - - gamescope_control_screenshot_type eScreenshotType = GAMESCOPE_CONTROL_SCREENSHOT_TYPE_BASE_PLANE_ONLY; - if ( args.size() > 2 ) - { - std::optional oType = Parse( args[2] ); - if ( oType ) - eScreenshotType = static_cast( *oType ); - } - - gamescope::CScreenshotManager::Get().TakeScreenshot( gamescope::GamescopeScreenshotInfo - { - .szScreenshotPath = std::string( szPath ), - .eScreenshotType = eScreenshotType, - .uScreenshotFlags = 0, - .bX11PropertyRequested = false, - } ); - }); -} - - -static std::atomic g_bForceRepaint{false}; + CScreenshotManager &CScreenshotManager::Get( ) + { + static CScreenshotManager s_Instance; + return s_Instance; + } + + static ConCommand cc_screenshot( + "screenshot", + "Take a screenshot to a given path.", + []( std::span args ) + { + std::string_view szPath = "/tmp/gamescope.png"; + if ( args.size( ) > 1 ) szPath = args[ 1 ]; + + gamescope_control_screenshot_type eScreenshotType = + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_BASE_PLANE_ONLY; + if ( args.size( ) > 2 ) + { + std::optional oType = Parse( args[ 2 ] ); + if ( oType ) + eScreenshotType = + static_cast( + *oType ); + } + + gamescope::CScreenshotManager::Get( ).TakeScreenshot( + gamescope::GamescopeScreenshotInfo{ + .szScreenshotPath = std::string( szPath ), + .eScreenshotType = eScreenshotType, + .uScreenshotFlags = 0, + .bX11PropertyRequested = false, + } ); + } ); +} // namespace gamescope + +static std::atomic g_bForceRepaint{ false }; extern int g_nCursorScaleHeight; @@ -1184,7389 +1260,8807 @@ extern int g_nCursorScaleHeight; class sem { public: - void wait( void ) - { - std::unique_lock lock(mtx); - - while(count == 0){ - cv.wait(lock); - } - count--; - } - - void signal( void ) - { - std::unique_lock lock(mtx); - count++; - cv.notify_one(); - } + void wait( void ) + { + std::unique_lock lock( mtx ); + + while ( count == 0 ) + { + cv.wait( lock ); + } + count--; + } + + void signal( void ) + { + std::unique_lock lock( mtx ); + count++; + cv.notify_one( ); + } private: - std::mutex mtx; - std::condition_variable cv; - int count = 0; + std::mutex mtx; + std::condition_variable cv; + int count = 0; }; -sem statsThreadSem; -std::mutex statsEventQueueLock; -std::vector< std::string > statsEventQueue; +sem statsThreadSem; +std::mutex statsEventQueueLock; +std::vector statsEventQueue; std::string statsThreadPath; -int statsPipeFD = -1; +int statsPipeFD = -1; bool statsThreadRun; void statsThreadMain( void ) { - pthread_setname_np( pthread_self(), "gamescope-stats" ); - signal(SIGPIPE, SIG_IGN); + pthread_setname_np( pthread_self( ), "gamescope-stats" ); + signal( SIGPIPE, SIG_IGN ); - while ( statsPipeFD == -1 ) - { - statsPipeFD = open( statsThreadPath.c_str(), O_WRONLY | O_CLOEXEC ); + while ( statsPipeFD == -1 ) + { + statsPipeFD = open( statsThreadPath.c_str( ), O_WRONLY | O_CLOEXEC ); - if ( statsPipeFD == -1 ) - { - sleep( 10 ); - } - } + if ( statsPipeFD == -1 ) { sleep( 10 ); } + } wait: - statsThreadSem.wait(); + statsThreadSem.wait( ); - if ( statsThreadRun == false ) - { - return; - } + if ( statsThreadRun == false ) { return; } - std::string event; + std::string event; retry: - { - std::unique_lock< std::mutex > lock( statsEventQueueLock ); +{ + std::unique_lock lock( statsEventQueueLock ); - if( statsEventQueue.empty() ) - { - goto wait; - } + if ( statsEventQueue.empty( ) ) { goto wait; } - event = statsEventQueue[ 0 ]; - statsEventQueue.erase( statsEventQueue.begin() ); - } + event = statsEventQueue[ 0 ]; + statsEventQueue.erase( statsEventQueue.begin( ) ); +} - dprintf( statsPipeFD, "%s", event.c_str() ); + dprintf( statsPipeFD, "%s", event.c_str( ) ); - goto retry; + goto retry; } -static inline void stats_printf( const char* format, ...) +static inline void stats_printf( const char *format, ... ) { - static char buffer[256]; - static std::string eventstr; + static char buffer[ 256 ]; + static std::string eventstr; - va_list args; - va_start(args, format); - vsprintf(buffer,format, args); - va_end(args); + va_list args; + va_start( args, format ); + vsprintf( buffer, format, args ); + va_end( args ); - eventstr = buffer; + eventstr = buffer; - { - { - std::unique_lock< std::mutex > lock( statsEventQueueLock ); + { + { + std::unique_lock lock( statsEventQueueLock ); - if( statsEventQueue.size() > 50 ) - { - // overflow, drop event - return; - } + if ( statsEventQueue.size( ) > 50 ) + { + // overflow, drop event + return; + } - statsEventQueue.push_back( eventstr ); + statsEventQueue.push_back( eventstr ); - statsThreadSem.signal(); - } - } + statsThreadSem.signal( ); + } + } } -uint64_t get_time_in_nanos() +uint64_t get_time_in_nanos( ) { - timespec ts; - // Kernel reports page flips with CLOCK_MONOTONIC. - clock_gettime(CLOCK_MONOTONIC, &ts); - return timespec_to_nanos(ts); + timespec ts; + // Kernel reports page flips with CLOCK_MONOTONIC. + clock_gettime( CLOCK_MONOTONIC, &ts ); + return timespec_to_nanos( ts ); } -void sleep_for_nanos(uint64_t nanos) +void sleep_for_nanos( uint64_t nanos ) { - timespec ts = nanos_to_timespec( nanos ); - nanosleep(&ts, nullptr); + timespec ts = nanos_to_timespec( nanos ); + nanosleep( &ts, nullptr ); } -void sleep_until_nanos(uint64_t nanos) +void sleep_until_nanos( uint64_t nanos ) { - uint64_t now = get_time_in_nanos(); - if (now >= nanos) - return; - sleep_for_nanos(nanos - now); + uint64_t now = get_time_in_nanos( ); + if ( now >= nanos ) return; + sleep_for_nanos( nanos - now ); } -unsigned int -get_time_in_milliseconds(void) -{ - return (unsigned int)(get_time_in_nanos() / 1'000'000ul); -} +unsigned int get_time_in_milliseconds( void ) +{ return ( unsigned int )( get_time_in_nanos( ) / 1'000'000ul ); } -bool xwayland_ctx_t::HasQueuedEvents() +bool xwayland_ctx_t::HasQueuedEvents( ) { - // If mode is QueuedAlready, XEventsQueued() returns the number of - // events already in the event queue (and never performs a system call). - return XEventsQueued( dpy, QueuedAlready ) != 0; + // If mode is QueuedAlready, XEventsQueued() returns the number of + // events already in the event queue (and never performs a system call). + return XEventsQueued( dpy, QueuedAlready ) != 0; } static steamcompmgr_win_t * -find_win(xwayland_ctx_t *ctx, Window id, bool find_children = true) +find_win( xwayland_ctx_t *ctx, Window id, bool find_children = true ) { - steamcompmgr_win_t *w; + steamcompmgr_win_t *w; - if (id == None) - { - return NULL; - } + if ( id == None ) { return NULL; } - for (w = ctx->list; w; w = w->xwayland().next) - { - if (w->xwayland().id == id) - { - return w; - } - } + for ( w = ctx->list; w; w = w->xwayland( ).next ) + { + if ( w->xwayland( ).id == id ) { return w; } + } - if ( !find_children ) - return nullptr; + if ( !find_children ) return nullptr; - // Didn't find, must be a children somewhere; try again with parent. - Window root = None; - Window parent = None; - Window *children = NULL; - unsigned int childrenCount; - XQueryTree(ctx->dpy, id, &root, &parent, &children, &childrenCount); - if (children) - XFree(children); + // Didn't find, must be a children somewhere; try again with parent. + Window root = None; + Window parent = None; + Window *children = NULL; + unsigned int childrenCount; + XQueryTree( ctx->dpy, id, &root, &parent, &children, &childrenCount ); + if ( children ) XFree( children ); - if (root == parent || parent == None) - { - return NULL; - } + if ( root == parent || parent == None ) { return NULL; } - return find_win(ctx, parent); + return find_win( ctx, parent ); } -static steamcompmgr_win_t * find_win( xwayland_ctx_t *ctx, struct wlr_surface *surf ) +static steamcompmgr_win_t * +find_win( xwayland_ctx_t *ctx, struct wlr_surface *surf ) { - steamcompmgr_win_t *w = nullptr; + steamcompmgr_win_t *w = nullptr; - for (w = ctx->list; w; w = w->xwayland().next) - { - if ( w->xwayland().surface.main_surface == surf ) - return w; + for ( w = ctx->list; w; w = w->xwayland( ).next ) + { + if ( w->xwayland( ).surface.main_surface == surf ) return w; - if ( w->xwayland().surface.override_surface == surf ) - return w; - } + if ( w->xwayland( ).surface.override_surface == surf ) return w; + } - return nullptr; + return nullptr; } static gamescope::CBufferMemoizer s_BufferMemos; // This really needs cleanup, this function is so silly... -static gamescope::Rc -import_commit ( - steamcompmgr_win_t *w, - struct wlr_surface *surf, - struct wlr_buffer *buf, - bool async, - std::shared_ptr swapchain_feedback, - std::vector presentation_feedbacks, - std::optional present_id, - uint64_t desired_present_time, - bool fifo ) -{ - gamescope::Rc commit = new commit_t; - - commit->win_seq = w->seq; - commit->surf = surf; - commit->buf = buf; - commit->async = async; - commit->fifo = fifo; - commit->is_steam = window_is_steam( w ); - commit->appID = w->appID; - commit->presentation_feedbacks = std::move(presentation_feedbacks); - if (swapchain_feedback) - commit->feedback = *swapchain_feedback; - commit->present_id = present_id; - commit->desired_present_time = desired_present_time; - if (window_is_vr_scene_app( w )) { - commit->async = true; - commit->fifo = false; - } +static gamescope::Rc import_commit( + steamcompmgr_win_t *w, + struct wlr_surface *surf, + struct wlr_buffer *buf, + bool async, + std::shared_ptr swapchain_feedback, + std::vector presentation_feedbacks, + std::optional present_id, + uint64_t desired_present_time, + bool fifo ) +{ + gamescope::Rc commit = new commit_t; - if ( gamescope::OwningRc pTexture = s_BufferMemos.LookupVulkanTexture( buf ) ) - { - // Going from OwningRc -> Rc now. - commit->vulkanTex = pTexture; - return commit; - } + commit->win_seq = w->seq; + commit->surf = surf; + commit->buf = buf; + commit->async = async; + commit->fifo = fifo; + commit->is_steam = window_is_steam( w ); + commit->appID = w->appID; + commit->presentation_feedbacks = std::move( presentation_feedbacks ); + if ( swapchain_feedback ) + commit->feedback.emplace( + std::make_shared( + *swapchain_feedback ) ); + commit->present_id = present_id; + commit->desired_present_time = desired_present_time; + if ( window_is_vr_scene_app( w ) ) + { + commit->async = true; + commit->fifo = false; + } - struct wlr_dmabuf_attributes dmabuf = {0}; - gamescope::OwningRc pBackendFb; - if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) - { - pBackendFb = GetBackend()->ImportDmabufToBackend( &dmabuf ); - } + if ( gamescope::OwningRc pTexture = + s_BufferMemos.LookupVulkanTexture( buf ) ) + { + // Going from OwningRc -> Rc now. + commit->vulkanTex = pTexture; + return commit; + } - gamescope::OwningRc pOwnedTexture = vulkan_create_texture_from_wlr_buffer( buf, std::move( pBackendFb ) ); - commit->vulkanTex = pOwnedTexture; + struct wlr_dmabuf_attributes dmabuf = { 0 }; + gamescope::OwningRc pBackendFb; + if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) + { + pBackendFb = GetBackend( )->ImportDmabufToBackend( &dmabuf ); + } - s_BufferMemos.MemoizeBuffer( buf, std::move( pOwnedTexture ) ); + gamescope::OwningRc pOwnedTexture = + vulkan_create_texture_from_wlr_buffer( buf, std::move( pBackendFb ) ); + commit->vulkanTex = pOwnedTexture; - return commit; + s_BufferMemos.MemoizeBuffer( buf, std::move( pOwnedTexture ) ); + + return commit; } -static int32_t -window_last_done_commit_index( steamcompmgr_win_t *w ) +static int32_t window_last_done_commit_index( steamcompmgr_win_t *w ) { - int32_t lastCommit = -1; - for ( uint32_t i = 0; i < w->commit_queue.size(); i++ ) - { - if ( w->commit_queue[ i ]->done ) - { - lastCommit = i; - } - } + int32_t lastCommit = -1; + for ( uint32_t i = 0; i < w->commit_queue.size( ); i++ ) + { + if ( w->commit_queue[ i ]->done ) { lastCommit = i; } + } - return lastCommit; + return lastCommit; } -static bool -window_has_commits( steamcompmgr_win_t *w ) -{ - return window_last_done_commit_index( w ) != -1; -} +static bool window_has_commits( steamcompmgr_win_t *w ) +{ return window_last_done_commit_index( w ) != -1; } -static void -get_window_last_done_commit( steamcompmgr_win_t *w, gamescope::Rc &commit ) +static void get_window_last_done_commit( + steamcompmgr_win_t *w, gamescope::Rc &commit ) { - int32_t lastCommit = window_last_done_commit_index( w ); + int32_t lastCommit = window_last_done_commit_index( w ); - if ( lastCommit == -1 ) - { - return; - } + if ( lastCommit == -1 ) { return; } - if ( commit != w->commit_queue[ lastCommit ] ) - commit = w->commit_queue[ lastCommit ]; + if ( commit != w->commit_queue[ lastCommit ] ) + commit = w->commit_queue[ lastCommit ]; } -static commit_t* -get_window_last_done_commit_peek( steamcompmgr_win_t *w ) +static commit_t *get_window_last_done_commit_peek( steamcompmgr_win_t *w ) { - int32_t lastCommit = window_last_done_commit_index( w ); + int32_t lastCommit = window_last_done_commit_index( w ); - if ( lastCommit == -1 ) - { - return nullptr; - } + if ( lastCommit == -1 ) { return nullptr; } - return w->commit_queue[ lastCommit ].get(); + return w->commit_queue[ lastCommit ].get( ); } -static int64_t -window_last_done_commit_id( steamcompmgr_win_t *w ) +static int64_t window_last_done_commit_id( steamcompmgr_win_t *w ) { - if ( !w ) - return 0; + if ( !w ) return 0; - commit_t *pCommit = get_window_last_done_commit_peek( w ); - if ( !pCommit ) - return 0; + commit_t *pCommit = get_window_last_done_commit_peek( w ); + if ( !pCommit ) return 0; - return pCommit->commitID; + return pCommit->commitID; } // For Steam, etc. -static bool -window_wants_no_focus_when_mouse_hidden( steamcompmgr_win_t *w ) -{ - return window_is_steam( w ); +static bool window_wants_no_focus_when_mouse_hidden( steamcompmgr_win_t *w ) +{ return window_is_steam( w ); } + +static bool window_is_fullscreen( steamcompmgr_win_t *w ) +{ return w && ( window_is_steam( w ) || w->isFullscreen ); } + +void calc_scale_factor_scaler( + float &out_scale_x, + float &out_scale_y, + float sourceWidth, + float sourceHeight ) +{ + float XOutputRatio = currentOutputWidth / ( float )g_nNestedWidth; + float YOutputRatio = currentOutputHeight / ( float )g_nNestedHeight; + float outputScaleRatio = std::min( XOutputRatio, YOutputRatio ); + + float XRatio = ( float )g_nNestedWidth / sourceWidth; + float YRatio = ( float )g_nNestedHeight / sourceHeight; + + if ( g_upscaleScaler == GamescopeUpscaleScaler::STRETCH ) + { + out_scale_x = XRatio * XOutputRatio; + out_scale_y = YRatio * YOutputRatio; + return; + } + + if ( g_upscaleScaler != GamescopeUpscaleScaler::FILL ) + { + out_scale_x = std::min( XRatio, YRatio ); + out_scale_y = std::min( XRatio, YRatio ); + } + else + { + out_scale_x = std::max( XRatio, YRatio ); + out_scale_y = std::max( XRatio, YRatio ); + } + + if ( g_upscaleScaler == GamescopeUpscaleScaler::AUTO ) + { + out_scale_x = std::min( g_flMaxWindowScale, out_scale_x ); + out_scale_y = std::min( g_flMaxWindowScale, out_scale_y ); + } + + out_scale_x *= outputScaleRatio; + out_scale_y *= outputScaleRatio; + + if ( g_upscaleScaler == GamescopeUpscaleScaler::INTEGER ) + { + if ( out_scale_x > 1.0f ) + { + // x == y here always. + out_scale_x = out_scale_y = floor( out_scale_x ); + } + } +} + +void calc_scale_factor( + float &out_scale_x, + float &out_scale_y, + float sourceWidth, + float sourceHeight ) +{ + calc_scale_factor_scaler( + out_scale_x, out_scale_y, sourceWidth, sourceHeight ); + + out_scale_x *= globalScaleRatio; + out_scale_y *= globalScaleRatio; } -static bool -window_is_fullscreen( steamcompmgr_win_t *w ) +/** + * Constructor for a cursor. It is hidden in the beginning (normally until moved + * by user). + */ +MouseCursor::MouseCursor( xwayland_ctx_t *ctx ) : + m_texture( nullptr ), m_dirty( true ), m_imageEmpty( false ), m_ctx( ctx ) +{ updateCursorFeedback( true ); } + +void MouseCursor::UpdatePosition( ) +{ + wlserver_lock( ); + struct wlr_pointer_constraint_v1 *pConstraint = + wlserver.GetCursorConstraint( ); + m_bConstrained = !!pConstraint; + if ( pConstraint && pConstraint->current.cursor_hint.enabled ) + { + m_x = pConstraint->current.cursor_hint.x; + m_y = pConstraint->current.cursor_hint.y; + } + else + { + m_x = wlserver.mouse_surface_cursorx; + m_y = wlserver.mouse_surface_cursory; + } + wlserver_unlock( ); +} + +void MouseCursor::checkSuspension( ) +{ + getTexture( ); + + if ( ShouldDrawCursor( ) ) + { + const bool suspended = int64_t( get_time_in_nanos( ) ) - + int64_t( wlserver.ulLastMovedCursorTime ) > + int64_t( cursorHideTime ); + if ( !wlserver.bCursorHidden && suspended ) + { + wlserver.bCursorHidden = true; + + steamcompmgr_win_t *window = m_ctx->focus.inputFocusWindow; + // Rearm warp count + if ( window ) + { + // Move the cursor to the bottom right corner, just off screen + // if we can if the window (ie. Steam) doesn't want hover/focus + // events. + if ( window_wants_no_focus_when_mouse_hidden( window ) ) + { + wlserver_lock( ); + wlserver_fake_mouse_pos( + window->GetGeometry( ).nWidth - 1, + window->GetGeometry( ).nHeight - 1 ); + wlserver_mousehide( ); + wlserver_unlock( ); + } + } + + // We're hiding the cursor, force redraw if we were showing it + if ( window && !m_imageEmpty ) + { + hasRepaintNonBasePlane = true; + nudge_steamcompmgr( ); + } + } + } + else + { + wlserver.bCursorHidden = false; + } + + wlserver.bCursorHasImage = !m_imageEmpty; + + updateCursorFeedback( ); +} + +void MouseCursor::setDirty( ) +{ + // We can't prove it's empty until checking again + m_imageEmpty = false; + m_dirty = true; +} + +bool MouseCursor::setCursorImage( char *data, int w, int h, int hx, int hy ) +{ + XRenderPictFormat *pictformat; + Picture picture; + XImage *ximage; + Pixmap pixmap; + Cursor cursor; + GC gc; + + if ( !( ximage = XCreateImage( + m_ctx->dpy, + DefaultVisual( m_ctx->dpy, DefaultScreen( m_ctx->dpy ) ), + 32, + ZPixmap, + 0, + data, + w, + h, + 32, + 0 ) ) ) + { + xwm_log.errorf( "Failed to make ximage for cursor" ); + goto error_image; + } + + if ( !( pixmap = XCreatePixmap( + m_ctx->dpy, DefaultRootWindow( m_ctx->dpy ), w, h, 32 ) ) ) + { + xwm_log.errorf( "Failed to make pixmap for cursor" ); + goto error_pixmap; + } + + if ( !( gc = XCreateGC( m_ctx->dpy, pixmap, 0, NULL ) ) ) + { + xwm_log.errorf( "Failed to make gc for cursor" ); + goto error_gc; + } + + XPutImage( m_ctx->dpy, pixmap, gc, ximage, 0, 0, 0, 0, w, h ); + + if ( !( pictformat = + XRenderFindStandardFormat( m_ctx->dpy, PictStandardARGB32 ) ) ) + { + xwm_log.errorf( "Failed to create pictformat for cursor" ); + goto error_pictformat; + } + + if ( !( picture = XRenderCreatePicture( + m_ctx->dpy, pixmap, pictformat, 0, NULL ) ) ) + { + xwm_log.errorf( "Failed to create picture for cursor" ); + goto error_picture; + } + + if ( !( cursor = XRenderCreateCursor( m_ctx->dpy, picture, hx, hy ) ) ) + { + xwm_log.errorf( "Failed to create cursor" ); + goto error_cursor; + } + + XDefineCursor( m_ctx->dpy, DefaultRootWindow( m_ctx->dpy ), cursor ); + XFlush( m_ctx->dpy ); + setDirty( ); + return true; + +error_cursor: + XRenderFreePicture( m_ctx->dpy, picture ); +error_picture: +error_pictformat: + XFreeGC( m_ctx->dpy, gc ); +error_gc: + XFreePixmap( m_ctx->dpy, pixmap ); +error_pixmap: + // XDestroyImage frees the data. + XDestroyImage( ximage ); +error_image: + return false; +} + +bool MouseCursor::setCursorImageByName( const char *name ) +{ + int index = XmuCursorNameToIndex( name ); + if ( index < 0 ) return false; + + Cursor cursor = XcursorShapeLoadCursor( m_ctx->dpy, index ); + + XDefineCursor( m_ctx->dpy, DefaultRootWindow( m_ctx->dpy ), cursor ); + XFlush( m_ctx->dpy ); + setDirty( ); + return true; +} + +int MouseCursor::x( ) const { return m_x; } + +int MouseCursor::y( ) const { return m_y; } + +bool MouseCursor::getTexture( ) +{ + uint64_t ulConnectorId = 0; + if ( GetBackend( )->GetCurrentConnector( ) ) + ulConnectorId = + GetBackend( )->GetCurrentConnector( )->GetConnectorID( ); + + if ( ulConnectorId != m_ulLastConnectorId ) + { + m_ulLastConnectorId = ulConnectorId; + m_dirty = true; + } + + if ( !m_dirty ) { return !m_imageEmpty; } + + auto *image = XFixesGetCursorImage( m_ctx->dpy ); + + if ( !image ) { return false; } + + m_hotspotX = image->xhot; + m_hotspotY = image->yhot; + + int nDesiredWidth = image->width; + int nDesiredHeight = image->height; + if ( g_nCursorScaleHeight > 0 ) + { + GetDesiredSize( nDesiredWidth, nDesiredHeight ); + } + + uint32_t surfaceWidth; + uint32_t surfaceHeight; + glm::uvec2 surfaceSize = GetBackend( )->CursorSurfaceSize( + glm::uvec2{ ( uint32_t )nDesiredWidth, ( uint32_t )nDesiredHeight } ); + surfaceWidth = surfaceSize.x; + surfaceHeight = surfaceSize.y; + + m_texture = nullptr; + + // Assume the cursor is fully translucent unless proven otherwise. + bool bNoCursor = true; + + std::vector cursorBuffer; + + int nContentWidth = image->width; + int nContentHeight = image->height; + + if ( image->width && image->height ) + { + if ( nDesiredWidth < image->width || nDesiredHeight < image->height ) + { + std::vector pixels( image->width * image->height ); + for ( int i = 0; i < image->height; i++ ) + { + for ( int j = 0; j < image->width; j++ ) + { + pixels[ i * image->width + j ] = + image->pixels[ i * image->width + j ]; + } + } + std::vector resizeBuffer( + nDesiredWidth * nDesiredHeight ); + stbir_resize_uint8_srgb( + ( unsigned char * )pixels.data( ), + image->width, + image->height, + 0, + ( unsigned char * )resizeBuffer.data( ), + nDesiredWidth, + nDesiredHeight, + 0, + 4, + 3, + STBIR_FLAG_ALPHA_PREMULTIPLIED ); + + cursorBuffer = + std::vector( surfaceWidth * surfaceHeight ); + for ( int i = 0; i < nDesiredHeight; i++ ) + { + for ( int j = 0; j < nDesiredWidth; j++ ) + { + cursorBuffer[ i * surfaceWidth + j ] = + resizeBuffer[ i * nDesiredWidth + j ]; + } + } + + m_hotspotX = ( m_hotspotX * nDesiredWidth ) / image->width; + m_hotspotY = ( m_hotspotY * nDesiredHeight ) / image->height; + + nContentWidth = nDesiredWidth; + nContentHeight = nDesiredHeight; + } + else + { + cursorBuffer = + std::vector( surfaceWidth * surfaceHeight ); + for ( int i = 0; i < image->height; i++ ) + { + for ( int j = 0; j < image->width; j++ ) + { + cursorBuffer[ i * surfaceWidth + j ] = + image->pixels[ i * image->width + j ]; + } + } + } + } + + for ( uint32_t i = 0; i < surfaceHeight; i++ ) + { + for ( uint32_t j = 0; j < surfaceWidth; j++ ) + { + if ( cursorBuffer[ i * surfaceWidth + j ] & 0xff000000 ) + { + bNoCursor = false; + break; + } + } + } + + if ( bNoCursor ) cursorBuffer.clear( ); + + m_imageEmpty = bNoCursor; + + m_dirty = false; + updateCursorFeedback( ); + + if ( m_imageEmpty ) + { + if ( GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->GetNestedHints( ) ) + GetBackend( ) + ->GetCurrentConnector( ) + ->GetNestedHints( ) + ->SetCursorImage( nullptr ); + return false; + } + + CVulkanTexture::createFlags texCreateFlags; + texCreateFlags.bFlippable = true; + if ( GetBackend( )->SupportsPlaneHardwareCursor( ) ) + { + texCreateFlags.bLinear = true; // cursor buffer needs to be linear + // TODO: choose format & modifiers from cursor plane + } + + m_texture = vulkan_create_texture_from_bits( + surfaceWidth, + surfaceHeight, + nContentWidth, + nContentHeight, + DRM_FORMAT_ARGB8888, + texCreateFlags, + cursorBuffer.data( ) ); + + if ( GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->GetNestedHints( ) ) + { + auto info = std::make_shared( + gamescope::INestedHints::CursorInfo{ + .pPixels = std::move( cursorBuffer ), + .uWidth = ( uint32_t )nDesiredWidth, + .uHeight = ( uint32_t )nDesiredHeight, + .uXHotspot = image->xhot, + .uYHotspot = image->yhot, + } ); + GetBackend( ) + ->GetCurrentConnector( ) + ->GetNestedHints( ) + ->SetCursorImage( std::move( info ) ); + } + + assert( m_texture ); + XFree( image ); + + return true; +} + +void MouseCursor::GetDesiredSize( int &nWidth, int &nHeight ) +{ + int nSize = g_nBaseCursorScale; + if ( g_nCursorScaleHeight > 0 ) + { + nSize = + nSize * floor( g_nOutputHeight / ( float )g_nCursorScaleHeight ); + nSize = std::clamp( nSize, g_nBaseCursorScale, 256 ); + } + + nSize = std::min( + nSize, + glm::compMin( + GetBackend( )->CursorSurfaceSize( + glm::uvec2{ ( uint32_t )nSize, ( uint32_t )nSize } ) ) ); + + nWidth = nSize; + nHeight = nSize; +} + +void MouseCursor::paint( + steamcompmgr_win_t *window, + steamcompmgr_win_t *fit, + struct FrameInfo_t *frameInfo ) +{ + if ( m_imageEmpty || wlserver.bCursorHidden ) return; + + int winX = x( ); + int winY = y( ); + + // Also need new texture + if ( !getTexture( ) ) { return; } + + int32_t sourceWidth = window->GetGeometry( ).nWidth; + int32_t sourceHeight = window->GetGeometry( ).nHeight; + + if ( fit ) + { + // If we have an override window, try to fit it in as long as it won't + // make our scale go below 1.0. + sourceWidth = std::max( + sourceWidth, + clamp( + fit->GetGeometry( ).nX + fit->GetGeometry( ).nWidth, + 0, + currentOutputWidth ) ); + sourceHeight = std::max( + sourceHeight, + clamp( + fit->GetGeometry( ).nY + fit->GetGeometry( ).nHeight, + 0, + currentOutputHeight ) ); + } + + float cursor_scale = 1.0f; + if ( g_nCursorScaleHeight > 0 ) + { + int nDesiredWidth, nDesiredHeight; + GetDesiredSize( nDesiredWidth, nDesiredHeight ); + cursor_scale = nDesiredHeight / ( float )m_texture->contentHeight( ); + } + cursor_scale = std::max( cursor_scale, 1.0f ); + + float scaledX, scaledY; + float currentScaleRatio_x = 1.0; + float currentScaleRatio_y = 1.0; + int cursorOffsetX, cursorOffsetY; + + calc_scale_factor( + currentScaleRatio_x, currentScaleRatio_y, sourceWidth, sourceHeight ); + + cursorOffsetX = + ( currentOutputWidth - sourceWidth * currentScaleRatio_x ) / 2.0f; + cursorOffsetY = + ( currentOutputHeight - sourceHeight * currentScaleRatio_y ) / 2.0f; + + // Actual point on scaled screen where the cursor hotspot should be + scaledX = ( winX - window->GetGeometry( ).nX ) * currentScaleRatio_x + + cursorOffsetX; + scaledY = ( winY - window->GetGeometry( ).nY ) * currentScaleRatio_y + + cursorOffsetY; + + if ( zoomScaleRatio != 1.0 ) + { + scaledX += ( ( sourceWidth / 2 ) - winX ) * currentScaleRatio_x; + scaledY += ( ( sourceHeight / 2 ) - winY ) * currentScaleRatio_y; + } + + // Apply the cursor offset inside the texture using the display scale + scaledX = scaledX - ( m_hotspotX * cursor_scale ); + scaledY = scaledY - ( m_hotspotY * cursor_scale ); + + int curLayer = frameInfo->layerCount++; + + FrameInfo_t::Layer_t *layer = &frameInfo->layers[ curLayer ]; + + layer->opacity = 1.0; + + layer->scale.x = 1.0f / cursor_scale; + layer->scale.y = 1.0f / cursor_scale; + + layer->offset.x = -scaledX; + layer->offset.y = -scaledY; + + layer->zpos = g_zposCursor; // cursor, on top of both bottom layers + layer->applyColorMgmt = false; + + layer->tex = m_texture; + + layer->filter = cursor_scale != 1.0f ? GamescopeUpscaleFilter::LINEAR + : GamescopeUpscaleFilter::NEAREST; + layer->blackBorder = false; + layer->ctm = nullptr; + layer->hdr_metadata_blob = nullptr; + layer->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + + layer->eAlphaBlendingMode = cv_overlay_unmultiplied_alpha + ? ALPHA_BLENDING_MODE_COVERAGE + : ALPHA_BLENDING_MODE_PREMULTIPLIED; +} + +void MouseCursor::updateCursorFeedback( bool bForce ) { - return w && ( window_is_steam( w ) || w->isFullscreen ); + // Can't resolve this until cursor is un-dirtied. + if ( m_dirty && !bForce ) return; + + bool bVisible = !isHidden( ); + + if ( m_bCursorVisibleFeedback == bVisible && !bForce ) return; + + uint32_t value = bVisible ? 1 : 0; + + XChangeProperty( + m_ctx->dpy, + m_ctx->root, + m_ctx->atoms.gamescopeCursorVisibleFeedback, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&value, + 1 ); + + m_bCursorVisibleFeedback = bVisible; + m_needs_server_flush = true; } -void calc_scale_factor_scaler(float &out_scale_x, float &out_scale_y, float sourceWidth, float sourceHeight) +struct BaseLayerInfo_t { - float XOutputRatio = currentOutputWidth / (float)g_nNestedWidth; - float YOutputRatio = currentOutputHeight / (float)g_nNestedHeight; - float outputScaleRatio = std::min(XOutputRatio, YOutputRatio); + float scale[ 2 ]; + float offset[ 2 ]; + float opacity; + GamescopeUpscaleFilter filter; + AlphaBlendingMode_t eAlphaBlendingMode = ALPHA_BLENDING_MODE_PREMULTIPLIED; +}; - float XRatio = (float)g_nNestedWidth / sourceWidth; - float YRatio = (float)g_nNestedHeight / sourceHeight; +std::array g_CachedPlanes = {}; - if (g_upscaleScaler == GamescopeUpscaleScaler::STRETCH) - { - out_scale_x = XRatio * XOutputRatio; - out_scale_y = YRatio * YOutputRatio; - return; - } +static void paint_cached_base_layer( + const gamescope::Rc &commit, + const BaseLayerInfo_t &base, + struct FrameInfo_t *frameInfo, + float flOpacityScale, + bool bOverrideOpacity ) +{ + int curLayer = frameInfo->layerCount++; - if (g_upscaleScaler != GamescopeUpscaleScaler::FILL) - { - out_scale_x = std::min(XRatio, YRatio); - out_scale_y = std::min(XRatio, YRatio); - } - else - { - out_scale_x = std::max(XRatio, YRatio); - out_scale_y = std::max(XRatio, YRatio); - } + FrameInfo_t::Layer_t *layer = &frameInfo->layers[ curLayer ]; - if (g_upscaleScaler == GamescopeUpscaleScaler::AUTO) - { - out_scale_x = std::min(g_flMaxWindowScale, out_scale_x); - out_scale_y = std::min(g_flMaxWindowScale, out_scale_y); - } + layer->scale.x = base.scale[ 0 ]; + layer->scale.y = base.scale[ 1 ]; + layer->offset.x = base.offset[ 0 ]; + layer->offset.y = base.offset[ 1 ]; + layer->opacity = + bOverrideOpacity ? flOpacityScale : base.opacity * flOpacityScale; - out_scale_x *= outputScaleRatio; - out_scale_y *= outputScaleRatio; + layer->colorspace = commit->colorspace( ); + layer->hdr_metadata_blob = nullptr; + if ( commit->feedback ) + { + layer->hdr_metadata_blob = commit->feedback.value( )->hdr_metadata_blob; + } + layer->ctm = nullptr; + if ( layer->colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ) + layer->ctm = s_scRGB709To2020Matrix; + layer->tex = commit->vulkanTex; - if (g_upscaleScaler == GamescopeUpscaleScaler::INTEGER) - { - if (out_scale_x > 1.0f) - { - // x == y here always. - out_scale_x = out_scale_y = floor(out_scale_x); - } - } + layer->filter = base.filter; + layer->eAlphaBlendingMode = base.eAlphaBlendingMode; + layer->blackBorder = true; } -void calc_scale_factor(float &out_scale_x, float &out_scale_y, float sourceWidth, float sourceHeight) +namespace PaintWindowFlag { - calc_scale_factor_scaler(out_scale_x, out_scale_y, sourceWidth, sourceHeight); + static const uint32_t BasePlane = 1u << 0; + static const uint32_t FadeTarget = 1u << 1; + static const uint32_t NotificationMode = 1u << 2; + static const uint32_t DrawBorders = 1u << 3; + static const uint32_t NoScale = 1u << 4; + static const uint32_t NoFilter = 1u << 5; + static const uint32_t CoverageMode = 1u << 6; +} // namespace PaintWindowFlag +using PaintWindowFlags = uint32_t; - out_scale_x *= globalScaleRatio; - out_scale_y *= globalScaleRatio; +wlserver_vk_swapchain_feedback * +steamcompmgr_get_base_layer_swapchain_feedback( ) +{ + if ( g_HeldCommits[ HELD_COMMIT_BASE ] == nullptr ) return nullptr; + + if ( !g_HeldCommits[ HELD_COMMIT_BASE ]->feedback ) return nullptr; + + return &( *g_HeldCommits[ HELD_COMMIT_BASE ]->feedback.value( ) ); +} + +gamescope::ConVar cv_paint_debug_pause_base_plane( + "paint_debug_pause_base_plane", false, "Pause updates to the base plane." ); + +static FrameInfo_t::Layer_t *paint_window_commit( + const gamescope::Rc &lastCommit, + steamcompmgr_win_t *w, + steamcompmgr_win_t *scaleW, + struct FrameInfo_t *frameInfo, + MouseCursor *cursor, + PaintWindowFlags flags = 0, + float flOpacityScale = 1.0f, + steamcompmgr_win_t *fit = nullptr ) + +{ + int32_t sourceWidth, sourceHeight; + int32_t baseWidth, baseHeight; + int drawXOffset = 0, drawYOffset = 0; + float currentScaleRatio_x = 1.0; + float currentScaleRatio_y = 1.0; + float baseScaleRatio_x = 1.0; + float baseScaleRatio_y = 1.0; + + if ( !GetBackend( )->ShouldFitWindows( ) ) fit = nullptr; + + // Exit out if we have no window or + // no commit. + // + // We may have no commit if we're an overlay, + // in which case, we don't want to add it, + // or in the case of the base plane, this is our + // first ever frame so we have no cached base layer + // to hold on to, so we should not add a layer in that + // instance either. + if ( !w || lastCommit == nullptr ) return nullptr; + + // Base plane will stay as tex=0 if we don't have contents yet, which will + // make us fall back to compositing and use the Vulkan null texture + + int curLayer = frameInfo->layerCount++; + + FrameInfo_t::Layer_t *layer = &frameInfo->layers[ curLayer ]; + + layer->filter = ( flags & PaintWindowFlag::NoFilter ) + ? GamescopeUpscaleFilter::LINEAR + : g_upscaleFilter; + + layer->tex = lastCommit->GetTexture( + layer->filter, g_upscaleScaler, layer->colorspace ); + + if ( flags & PaintWindowFlag::NoScale ) + { + sourceWidth = currentOutputWidth; + sourceHeight = currentOutputHeight; + } + else + { + // If w == scaleW, then scale the window by the committed buffer size + // instead of the window size. + // + // Some games like Halo Infinite still make swapchains that are eg. + // 3840x2160 on a 720p window if you do borderless fullscreen. + // + // Typically XWayland would do a blit here to avoid that, but when we + // are using the bypass layer, we don't get that, so we need to handle + // this case explicitly. + if ( w == scaleW ) + { + sourceWidth = layer->tex->width( ); + sourceHeight = layer->tex->height( ); + + baseWidth = lastCommit->vulkanTex->width( ); + baseHeight = lastCommit->vulkanTex->height( ); + } + else + { + sourceWidth = scaleW->GetGeometry( ).nWidth; + sourceHeight = scaleW->GetGeometry( ).nHeight; + + baseWidth = sourceWidth; + baseHeight = sourceHeight; + } + + if ( fit ) + { + // If we have an override window, try to fit it in as long as it + // won't make our scale go below 1.0. + sourceWidth = std::max( + sourceWidth, + clamp( + fit->GetGeometry( ).nX + fit->GetGeometry( ).nWidth, + 0, + currentOutputWidth ) ); + sourceHeight = std::max( + sourceHeight, + clamp( + fit->GetGeometry( ).nY + fit->GetGeometry( ).nHeight, + 0, + currentOutputHeight ) ); + + baseWidth = std::max( + baseWidth, + clamp( + fit->GetGeometry( ).nX + fit->GetGeometry( ).nWidth, + 0, + currentOutputWidth ) ); + baseHeight = std::max( + baseHeight, + clamp( + fit->GetGeometry( ).nY + fit->GetGeometry( ).nHeight, + 0, + currentOutputHeight ) ); + } + } + + bool offset = + ( ( w->GetGeometry( ).nX || w->GetGeometry( ).nY ) && w != scaleW ); + + if ( sourceWidth != ( int32_t )currentOutputWidth || + sourceHeight != ( int32_t )currentOutputHeight || offset || + globalScaleRatio != 1.0f ) + { + calc_scale_factor( + currentScaleRatio_x, + currentScaleRatio_y, + sourceWidth, + sourceHeight ); + + drawXOffset = ( ( int )currentOutputWidth - + ( int )sourceWidth * currentScaleRatio_x ) / + 2.0f; + drawYOffset = ( ( int )currentOutputHeight - + ( int )sourceHeight * currentScaleRatio_y ) / + 2.0f; + + if ( w != scaleW ) + { + drawXOffset += w->GetGeometry( ).nX * currentScaleRatio_x; + drawYOffset += w->GetGeometry( ).nY * currentScaleRatio_y; + } + + calc_scale_factor( + baseScaleRatio_x, baseScaleRatio_y, baseWidth, baseHeight ); + if ( zoomScaleRatio != 1.0 ) + { + drawXOffset += + ( ( ( int )baseWidth / 2 ) - ( cursor ? cursor->x( ) : 0 ) ) * + baseScaleRatio_x; + drawYOffset += + ( ( ( int )baseHeight / 2 ) - ( cursor ? cursor->y( ) : 0 ) ) * + baseScaleRatio_y; + } + } + + layer->opacity = ( ( w->isOverlay || w->isExternalOverlay ) + ? w->opacity / ( float )OPAQUE + : 1.0f ) * + flOpacityScale; + + layer->scale.x = 1.0 / currentScaleRatio_x; + layer->scale.y = 1.0 / currentScaleRatio_y; + + if ( w != scaleW ) + { + layer->offset.x = -drawXOffset; + layer->offset.y = -drawYOffset; + } + else + { + layer->offset.x = -drawXOffset; + layer->offset.y = -drawYOffset; + } + + layer->blackBorder = flags & PaintWindowFlag::DrawBorders; + + layer->applyColorMgmt = g_ColorMgmt.pending.enabled; + layer->zpos = g_zposBase; + + if ( w != scaleW ) { layer->zpos = g_zposOverride; } + + if ( w->isOverlay || w->isSteamStreamingClient ) + { + layer->zpos = g_zposOverlay; + } + if ( w->isExternalOverlay ) { layer->zpos = g_zposExternalOverlay; } + + layer->hdr_metadata_blob = nullptr; + if ( lastCommit->feedback ) + { + layer->hdr_metadata_blob = + lastCommit->feedback.value( )->hdr_metadata_blob; + } + layer->ctm = nullptr; + if ( layer->colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB ) + layer->ctm = s_scRGB709To2020Matrix; + + if ( layer->filter == GamescopeUpscaleFilter::PIXEL ) + { + // Don't bother doing more expensive filtering if we are sharp + + // integer. + if ( float_is_integer( currentScaleRatio_x ) && + float_is_integer( currentScaleRatio_y ) ) + layer->filter = GamescopeUpscaleFilter::NEAREST; + } + + layer->eAlphaBlendingMode = ( flags & PaintWindowFlag::CoverageMode ) + ? ALPHA_BLENDING_MODE_COVERAGE + : ALPHA_BLENDING_MODE_PREMULTIPLIED; + + return layer; +} + +static void paint_window( + steamcompmgr_win_t *w, + steamcompmgr_win_t *scaleW, + struct FrameInfo_t *frameInfo, + MouseCursor *cursor, + PaintWindowFlags flags = 0, + float flOpacityScale = 1.0f, + steamcompmgr_win_t *fit = nullptr ) +{ + gamescope::Rc lastCommit; + if ( w ) get_window_last_done_commit( w, lastCommit ); + + if ( flags & PaintWindowFlag::BasePlane ) + { + if ( lastCommit == nullptr || cv_paint_debug_pause_base_plane ) + { + // If we're the base plane and have no valid contents + // pick up that buffer we've been holding onto if we have one. + if ( g_HeldCommits[ HELD_COMMIT_BASE ] != nullptr ) + { + paint_cached_base_layer( + g_HeldCommits[ HELD_COMMIT_BASE ], + g_CachedPlanes[ HELD_COMMIT_BASE ], + frameInfo, + flOpacityScale, + true ); + return; + } + } + else + { + if ( g_bPendingFade ) + { + fadeOutStartTime = get_time_in_milliseconds( ); + g_bPendingFade = false; + } + } + } + + FrameInfo_t::Layer_t *layer = paint_window_commit( + lastCommit, w, scaleW, frameInfo, cursor, flags, flOpacityScale, fit ); + + if ( layer && ( flags & PaintWindowFlag::BasePlane ) ) + { + BaseLayerInfo_t basePlane = {}; + basePlane.scale[ 0 ] = layer->scale.x; + basePlane.scale[ 1 ] = layer->scale.y; + basePlane.offset[ 0 ] = layer->offset.x; + basePlane.offset[ 1 ] = layer->offset.y; + basePlane.opacity = layer->opacity; + basePlane.filter = layer->filter; + basePlane.eAlphaBlendingMode = layer->eAlphaBlendingMode; + + g_CachedPlanes[ HELD_COMMIT_BASE ] = basePlane; + if ( !( flags & PaintWindowFlag::FadeTarget ) ) + g_CachedPlanes[ HELD_COMMIT_FADE ] = basePlane; + + g_uCurrentBasePlaneCommitID = lastCommit->commitID; + g_uCurrentBasePlaneAppID = lastCommit->appID; + g_bCurrentBasePlaneIsFifo = lastCommit->IsPerfOverlayFIFO( ); + } } -/** - * Constructor for a cursor. It is hidden in the beginning (normally until moved by user). - */ -MouseCursor::MouseCursor(xwayland_ctx_t *ctx) - : m_texture(nullptr) - , m_dirty(true) - , m_imageEmpty(false) - , m_ctx(ctx) +bool g_bFirstFrame = true; + +static bool is_fading_out( ) { return fadeOutStartTime || g_bPendingFade; } + +static void update_touch_scaling( const struct FrameInfo_t *frameInfo ) { - updateCursorFeedback( true ); + if ( !frameInfo->layerCount ) return; + + focusedWindowScaleX = + frameInfo->layers[ frameInfo->layerCount - 1 ].scale.x; + focusedWindowScaleY = + frameInfo->layers[ frameInfo->layerCount - 1 ].scale.y; + focusedWindowOffsetX = + frameInfo->layers[ frameInfo->layerCount - 1 ].offset.x; + focusedWindowOffsetY = + frameInfo->layers[ frameInfo->layerCount - 1 ].offset.y; } -void MouseCursor::UpdatePosition() -{ - wlserver_lock(); - struct wlr_pointer_constraint_v1 *pConstraint = wlserver.GetCursorConstraint(); - m_bConstrained = !!pConstraint; - if ( pConstraint && pConstraint->current.cursor_hint.enabled ) - { - m_x = pConstraint->current.cursor_hint.x; - m_y = pConstraint->current.cursor_hint.y; - } - else - { - m_x = wlserver.mouse_surface_cursorx; - m_y = wlserver.mouse_surface_cursory; - } - wlserver_unlock(); +#if HAVE_PIPEWIRE +static void paint_pipewire( ) +{ + static struct pipewire_buffer *s_pPipewireBuffer = nullptr; + + // If the stream stopped/changed, and the underlying pw_buffer was thus + // destroyed, then destroy this buffer and grab a new one. + if ( s_pPipewireBuffer && s_pPipewireBuffer->IsStale( ) ) + { + pipewire_destroy_buffer( s_pPipewireBuffer ); + s_pPipewireBuffer = nullptr; + } + + // Queue up a buffer with some metadata. + if ( !s_pPipewireBuffer ) s_pPipewireBuffer = dequeue_pipewire_buffer( ); + + if ( !s_pPipewireBuffer || !s_pPipewireBuffer->texture ) return; + + struct FrameInfo_t frameInfo = {}; + frameInfo.applyOutputColorMgmt = true; + frameInfo.outputEncodingEOTF = EOTF_Gamma22; + frameInfo.allowVRR = false; + frameInfo.bFadingOut = false; + + // Apply screenshot-style color management. + for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) + { + frameInfo.lut3D[ nInputEOTF ] = + g_ScreenshotColorMgmtLuts[ nInputEOTF ].vk_lut3d; + frameInfo.shaperLut[ nInputEOTF ] = + g_ScreenshotColorMgmtLuts[ nInputEOTF ].vk_lut1d; + } + + const uint64_t ulFocusAppId = s_pPipewireBuffer->gamescope_info.focus_appid; + + focus_t *pFocus = nullptr; + if ( ulFocusAppId ) + { + static uint64_t s_ulLastFocusAppId = 0; + + bool bAppIdChange = ulFocusAppId != s_ulLastFocusAppId; + if ( bAppIdChange ) + { + xwm_log.infof( + "Exposing appid %lu (%u 32-bit) focus-wise on pipewire stream.", + ulFocusAppId, + uint32_t( ulFocusAppId ) ); + s_ulLastFocusAppId = ulFocusAppId; + } + + static focus_t s_PipewireFocus{}; + if ( s_PipewireFocus.IsDirty( ) || bAppIdChange ) + { + std::vector vecPossibleFocusWindows = + GetGlobalPossibleFocusWindows( ); + + std::vector vecAppIds{ uint32_t( ulFocusAppId ) }; + pick_primary_focus_and_override( + &s_PipewireFocus, + None, + vecPossibleFocusWindows, + false, + vecAppIds, + 0, + gamescope::VirtualConnectorStrategies::SteamControlled ); + } + pFocus = &s_PipewireFocus; + } + else + { + pFocus = GetCurrentFocus( ); + } + + if ( !pFocus->focusWindow ) return; + + const bool bAppIdMatches = + !ulFocusAppId || pFocus->focusWindow->appID == ulFocusAppId; + if ( !bAppIdMatches ) return; + + // If the commits are the same as they were last time, don't repaint and + // don't push a new buffer on the stream. + static uint64_t s_ulLastFocusCommitId = 0; + static uint64_t s_ulLastOverrideCommitId = 0; + + uint64_t ulFocusCommitId = + window_last_done_commit_id( pFocus->focusWindow ); + uint64_t ulOverrideCommitId = + window_last_done_commit_id( pFocus->overrideWindow ); + + if ( ulFocusCommitId == s_ulLastFocusCommitId && + ulOverrideCommitId == s_ulLastOverrideCommitId ) + return; + + s_ulLastFocusCommitId = ulFocusCommitId; + s_ulLastOverrideCommitId = ulOverrideCommitId; + + uint32_t uWidth = s_pPipewireBuffer->texture->width( ); + uint32_t uHeight = s_pPipewireBuffer->texture->height( ); + + const uint32_t uCompositeDebugBackup = g_uCompositeDebug; + const uint32_t uBackupWidth = currentOutputWidth; + const uint32_t uBackupHeight = currentOutputHeight; + + g_uCompositeDebug = 0; + currentOutputWidth = uWidth; + currentOutputHeight = uHeight; + + // Paint the windows we have onto the Pipewire stream. + paint_window( + pFocus->focusWindow, + pFocus->focusWindow, + &frameInfo, + nullptr, + 0, + 1.0f, + pFocus->overrideWindow ); + + if ( pFocus->overrideWindow && + !pFocus->focusWindow->isSteamStreamingClient ) + paint_window( + pFocus->overrideWindow, + pFocus->focusWindow, + &frameInfo, + nullptr, + PaintWindowFlag::NoFilter, + 1.0f, + pFocus->overrideWindow ); + + if ( !ulFocusAppId && pFocus->overlayWindow && + pFocus->overlayWindow->opacity ) + { + paint_window( + pFocus->overlayWindow, + pFocus->overlayWindow, + &frameInfo, + nullptr, + PaintWindowFlag::DrawBorders | PaintWindowFlag::NoFilter | + ( cv_overlay_unmultiplied_alpha ? PaintWindowFlag::CoverageMode + : 0 ) ); + } + + gamescope::Rc pRGBTexture = + s_pPipewireBuffer->texture->isYcbcr( ) + ? vulkan_acquire_screenshot_texture( + uWidth, uHeight, false, DRM_FORMAT_XRGB2101010 ) + : gamescope::Rc{ s_pPipewireBuffer->texture }; + + gamescope::Rc pYUVTexture = + s_pPipewireBuffer->texture->isYcbcr( ) ? s_pPipewireBuffer->texture + : nullptr; + + std::optional oPipewireSequence = + vulkan_screenshot( &frameInfo, pRGBTexture, pYUVTexture ); + // If we ever want the fat compositing path, use this. + // std::optional oPipewireSequence = vulkan_composite( &frameInfo, + // s_pPipewireBuffer->texture, false, pRGBTexture, false ); + + g_uCompositeDebug = uCompositeDebugBackup; + + currentOutputWidth = uBackupWidth; + currentOutputHeight = uBackupHeight; + + if ( oPipewireSequence ) + { + vulkan_wait( *oPipewireSequence, true ); + + push_pipewire_buffer( s_pPipewireBuffer ); + s_pPipewireBuffer = nullptr; + } } +#endif -void MouseCursor::checkSuspension() +gamescope::ConVar cv_cursor_composite{ + "cursor_composite", + 1, + "0 = Never composite a cursor. 1 = Composite cursor when not nested. 2 = " + "Always composite a cursor manually" +}; +bool ShouldDrawCursor( ) { - getTexture(); + if ( cv_cursor_composite == 2 ) return true; - if ( ShouldDrawCursor() ) - { - const bool suspended = int64_t( get_time_in_nanos() ) - int64_t( wlserver.ulLastMovedCursorTime ) > int64_t( cursorHideTime ); - if (!wlserver.bCursorHidden && suspended) { - wlserver.bCursorHidden = true; + if ( cv_cursor_composite == 0 ) return false; - steamcompmgr_win_t *window = m_ctx->focus.inputFocusWindow; - // Rearm warp count - if (window) - { - // Move the cursor to the bottom right corner, just off screen if we can - // if the window (ie. Steam) doesn't want hover/focus events. - if ( window_wants_no_focus_when_mouse_hidden(window) ) - { - wlserver_lock(); - wlserver_fake_mouse_pos( window->GetGeometry().nWidth - 1, window->GetGeometry().nHeight - 1 ); - wlserver_mousehide(); - wlserver_unlock(); - } - } + if ( g_bForceRelativeMouse ) return true; - // We're hiding the cursor, force redraw if we were showing it - if (window && !m_imageEmpty ) { - hasRepaintNonBasePlane = true; - nudge_steamcompmgr(); - } - } - } - else - { - wlserver.bCursorHidden = false; - } + global_focus_t *pFocus = GetCurrentFocus( ); + if ( !pFocus ) return false; - wlserver.bCursorHasImage = !m_imageEmpty; + if ( !pFocus->GetNestedHints( ) ) return true; - updateCursorFeedback(); + return pFocus->GetNestedHints( )->ShouldPaintCursor( ); } -void MouseCursor::setDirty() +static void ForwardVROverlayTargets( ) { - // We can't prove it's empty until checking again - m_imageEmpty = false; - m_dirty = true; -} + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); i++ ) + { + for ( steamcompmgr_win_t *w = server->ctx->list; w; + w = w->xwayland( ).next ) + { + if ( w->oulTargetVROverlay && w->bNeedsForwarding ) + { + gamescope::Rc lastCommit; + get_window_last_done_commit( w, lastCommit ); + if ( !lastCommit ) continue; -bool MouseCursor::setCursorImage(char *data, int w, int h, int hx, int hy) -{ - XRenderPictFormat *pictformat; - Picture picture; - XImage* ximage; - Pixmap pixmap; - Cursor cursor; - GC gc; + gamescope::IBackendFb *pFb = + lastCommit->vulkanTex->GetBackendFb( ); + if ( !pFb ) continue; - if (!(ximage = XCreateImage( - m_ctx->dpy, - DefaultVisual(m_ctx->dpy, DefaultScreen(m_ctx->dpy)), - 32, ZPixmap, - 0, - data, - w, h, - 32, 0))) - { - xwm_log.errorf("Failed to make ximage for cursor"); - goto error_image; - } + const uint64_t ulOverlayHandle = *w->oulTargetVROverlay; + GetBackend( )->ForwardFramebuffer( + w->pForwarderPlane, pFb, &ulOverlayHandle ); - if (!(pixmap = XCreatePixmap(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), w, h, 32))) - { - xwm_log.errorf("Failed to make pixmap for cursor"); - goto error_pixmap; - } + w->bNeedsForwarding = false; + } + } + } - if (!(gc = XCreateGC(m_ctx->dpy, pixmap, 0, NULL))) - { - xwm_log.errorf("Failed to make gc for cursor"); - goto error_gc; - } + gpuvis_trace_printf( "Forward VR Overlays" ); +} - XPutImage(m_ctx->dpy, pixmap, gc, ximage, 0, 0, 0, 0, w, h); +gamescope::ConVar cv_paint_primary_plane{ "paint_primary_plane", true }; +gamescope::ConVar cv_paint_override_redirect_plane{ + "paint_override_redirect_plane", true +}; +gamescope::ConVar cv_paint_steam_overlay_plane{ + "paint_steam_overlay_plane", true +}; +gamescope::ConVar cv_paint_external_overlay_plane{ + "paint_external_overlay_plane", true +}; +gamescope::ConVar cv_paint_cursor_plane{ "paint_cursor_plane", true }; +gamescope::ConVar cv_paint_mura_plane{ "paint_mura_plane", true }; - if (!(pictformat = XRenderFindStandardFormat(m_ctx->dpy, PictStandardARGB32))) - { - xwm_log.errorf("Failed to create pictformat for cursor"); - goto error_pictformat; - } +static void paint_all( global_focus_t *pFocus, bool async ) +{ + if ( !pFocus ) return; + + gamescope::IBackendConnector *pConnector = pFocus->pVirtualConnector.get( ); + if ( !pConnector ) pConnector = GetBackend( )->GetCurrentConnector( ); + + gamescope_xwayland_server_t *root_server = + wlserver_get_xwayland_server( 0 ); + xwayland_ctx_t *root_ctx = root_server->ctx.get( ); + + static long long int paintID = 0; + + update_color_mgmt( ); + + paintID++; + gpuvis_trace_begin_ctx_printf( paintID, "paint_all" ); + steamcompmgr_win_t *w; + steamcompmgr_win_t *overlay; + steamcompmgr_win_t *externalOverlay; + steamcompmgr_win_t *notification; + steamcompmgr_win_t *override; + steamcompmgr_win_t *input; + + unsigned int currentTime = get_time_in_milliseconds( ); + bool fadingOut = ( currentTime - fadeOutStartTime < g_FadeOutDuration || + g_bPendingFade ) && + g_HeldCommits[ HELD_COMMIT_FADE ] != nullptr; + + w = pFocus->focusWindow; + overlay = pFocus->overlayWindow; + externalOverlay = pFocus->externalOverlayWindow; + notification = pFocus->notificationWindow; + override = pFocus->overrideWindow; + input = pFocus->inputFocusWindow; + + if ( ++frameCounter == 300 ) + { + currentFrameRate = + 300 * 1000.0f / ( currentTime - lastSampledFrameTime ); + lastSampledFrameTime = currentTime; + frameCounter = 0; + + stats_printf( "fps=%f\n", currentFrameRate ); + + if ( window_is_steam( w ) ) { stats_printf( "focus=steam\n" ); } + else + { + stats_printf( "focus=%i\n", w ? w->appID : 0 ); + } + } + + struct FrameInfo_t frameInfo = {}; + frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; + frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; + frameInfo.allowVRR = cv_adaptive_sync; + frameInfo.bFadingOut = fadingOut; + + // If the window we'd paint as the base layer is the streaming client, + // find the video underlay and put it up first in the scenegraph + if ( cv_paint_primary_plane ) + { + if ( w ) + { + if ( w->isSteamStreamingClient == true ) + { + steamcompmgr_win_t *videow = NULL; + bool bHasVideoUnderlay = false; + + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; + ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + { + for ( videow = server->ctx->list; videow; + videow = videow->xwayland( ).next ) + { + if ( videow->isSteamStreamingClientVideo == true ) + { + // TODO: also check matching AppID so we can have + // several pairs + paint_window( + videow, + videow, + &frameInfo, + pFocus->cursor, + PaintWindowFlag::BasePlane | + PaintWindowFlag::DrawBorders ); + bHasVideoUnderlay = true; + break; + } + } + } + + int nOldLayerCount = frameInfo.layerCount; + + uint32_t flags = 0; + if ( !bHasVideoUnderlay ) flags |= PaintWindowFlag::BasePlane; + paint_window( w, w, &frameInfo, pFocus->cursor, flags ); + if ( pFocus == GetCurrentFocus( ) ) + update_touch_scaling( &frameInfo ); + + // paint UI unless it's fully hidden, which it communicates to + // us through opacity=0 we paint it to extract scaling + // coefficients above, then remove the layer if one was added + if ( w->opacity == TRANSLUCENT && bHasVideoUnderlay && + nOldLayerCount < frameInfo.layerCount ) + frameInfo.layerCount--; + } + else + { + if ( fadingOut ) + { + float opacityScale = + g_bPendingFade ? 0.0f + : ( ( currentTime - fadeOutStartTime ) / + ( float )g_FadeOutDuration ); + + paint_cached_base_layer( + g_HeldCommits[ HELD_COMMIT_FADE ], + g_CachedPlanes[ HELD_COMMIT_FADE ], + &frameInfo, + 1.0f - opacityScale, + false ); + paint_window( + w, + w, + &frameInfo, + pFocus->cursor, + PaintWindowFlag::BasePlane | + PaintWindowFlag::FadeTarget | + PaintWindowFlag::DrawBorders, + opacityScale, + override ); + } + else + { + { + if ( g_HeldCommits[ HELD_COMMIT_FADE ] != nullptr ) + { + g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; + g_bPendingFade = false; + fadeOutStartTime = 0; + pFocus->fadeWindow = None; + } + } + // Just draw focused window as normal, be it Steam or the + // game + paint_window( + w, + w, + &frameInfo, + pFocus->cursor, + PaintWindowFlag::BasePlane | + PaintWindowFlag::DrawBorders, + 1.0f, + override ); + + bool needsScaling = + frameInfo.layers[ 0 ].scale.x < 0.999f && + frameInfo.layers[ 0 ].scale.y < 0.999f; + frameInfo.useFSRLayer0 = + g_upscaleFilter == GamescopeUpscaleFilter::FSR && + needsScaling; + frameInfo.useNISLayer0 = + g_upscaleFilter == GamescopeUpscaleFilter::NIS && + needsScaling; + } + if ( pFocus == GetCurrentFocus( ) ) + update_touch_scaling( &frameInfo ); + } + } + else + { + if ( g_HeldCommits[ HELD_COMMIT_BASE ] != nullptr ) + { + float opacityScale = 1.0f; + if ( fadingOut ) + { + opacityScale = g_bPendingFade + ? 0.0f + : ( ( currentTime - fadeOutStartTime ) / + ( float )g_FadeOutDuration ); + } + + paint_cached_base_layer( + g_HeldCommits[ HELD_COMMIT_BASE ], + g_CachedPlanes[ HELD_COMMIT_BASE ], + &frameInfo, + opacityScale, + true ); + } + } + } + + // TODO: We want to paint this at the same scale as the normal window and + // probably with an offset. Josh: No override if we're streaming video as we + // will have too many layers. Better to be safe than sorry. + if ( override && w && !w->isSteamStreamingClient && + cv_paint_override_redirect_plane ) + { + paint_window( + override, + w, + &frameInfo, + pFocus->cursor, + PaintWindowFlag::NoFilter, + 1.0f, + override ); + // Don't update touch scaling for frameInfo. We don't ever make it our + // wlserver_mousefocus window. + // update_touch_scaling( &frameInfo ); + } + + // If we have any layers that aren't a cursor or overlay, then we have valid + // contents for presentation. + const bool bValidContents = frameInfo.layerCount > 0; + + if ( externalOverlay && cv_paint_external_overlay_plane ) + { + if ( externalOverlay->opacity ) + { + paint_window( + externalOverlay, + externalOverlay, + &frameInfo, + pFocus->cursor, + PaintWindowFlag::NoScale | PaintWindowFlag::NoFilter | + ( cv_overlay_unmultiplied_alpha + ? PaintWindowFlag::CoverageMode + : 0 ) ); + + if ( externalOverlay == pFocus->inputFocusWindow && + pFocus == GetCurrentFocus( ) ) + update_touch_scaling( &frameInfo ); + } + } + + if ( cv_paint_steam_overlay_plane ) + { + if ( overlay && overlay->opacity ) + { + paint_window( + overlay, + overlay, + &frameInfo, + pFocus->cursor, + PaintWindowFlag::DrawBorders | PaintWindowFlag::NoFilter | + ( cv_overlay_unmultiplied_alpha + ? PaintWindowFlag::CoverageMode + : 0 ) ); + + if ( overlay == pFocus->inputFocusWindow && + pFocus == GetCurrentFocus( ) ) + update_touch_scaling( &frameInfo ); + } + else if ( + !GetBackend( )->UsesVulkanSwapchain( ) && + GetBackend( )->IsSessionBased( ) ) + { + auto tex = vulkan_get_hacky_blank_texture( ); + if ( tex != nullptr ) + { + // HACK! HACK HACK HACK + // To avoid stutter when toggling the overlay on + int curLayer = frameInfo.layerCount++; + + FrameInfo_t::Layer_t *layer = &frameInfo.layers[ curLayer ]; + + layer->scale.x = g_nOutputWidth == tex->width( ) + ? 1.0f + : tex->width( ) / ( float )g_nOutputWidth; + layer->scale.y = + g_nOutputHeight == tex->height( ) + ? 1.0f + : tex->height( ) / ( float )g_nOutputHeight; + layer->offset.x = 0.0f; + layer->offset.y = 0.0f; + layer->opacity = 1.0f; // BLAH + layer->zpos = g_zposOverlay; + layer->applyColorMgmt = g_ColorMgmt.pending.enabled; + layer->eAlphaBlendingMode = + cv_overlay_unmultiplied_alpha + ? ALPHA_BLENDING_MODE_COVERAGE + : ALPHA_BLENDING_MODE_PREMULTIPLIED; + + layer->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; + layer->hdr_metadata_blob = nullptr; + layer->ctm = nullptr; + layer->tex = tex; + + layer->filter = GamescopeUpscaleFilter::NEAREST; + layer->blackBorder = true; + } + } + } + + if ( notification ) + { + if ( notification->opacity ) + { + paint_window( + notification, + notification, + &frameInfo, + pFocus->cursor, + PaintWindowFlag::NotificationMode | PaintWindowFlag::NoFilter ); + } + } + + if ( input ) + { + // Make sure to un-dirty the texture before we do any painting logic. + // We determine whether we are grabbed etc this way. + pFocus->cursor->undirty( ); + } + + // Draw cursor if we need to + if ( input && ShouldDrawCursor( ) && cv_paint_cursor_plane ) + { + pFocus->cursor->paint( + input, w == input ? override : nullptr, &frameInfo ); + } + + if ( !bValidContents || GetBackend( )->IsPaused( ) ) { return; } + + unsigned int blurFadeTime = + get_time_in_milliseconds( ) - g_BlurFadeStartTime; + bool blurFading = blurFadeTime < g_BlurFadeDuration; + BlurMode currentBlurMode = + blurFading ? std::max( g_BlurMode, g_BlurModeOld ) : g_BlurMode; + + if ( currentBlurMode && + !( frameInfo.layerCount <= 1 && currentBlurMode == BLUR_MODE_COND ) ) + { + frameInfo.blurLayer0 = currentBlurMode; + frameInfo.blurRadius = g_BlurRadius; + + if ( blurFading ) + { + float ratio = blurFadeTime / ( float )g_BlurFadeDuration; + bool fadingIn = g_BlurMode > g_BlurModeOld; + + if ( !fadingIn ) ratio = 1.0 - ratio; + + frameInfo.blurRadius = ratio * g_BlurRadius; + } + + frameInfo.useFSRLayer0 = false; + frameInfo.useNISLayer0 = false; + } + + g_bFSRActive = frameInfo.useFSRLayer0; + if ( const auto &heldCommit = g_HeldCommits[ HELD_COMMIT_BASE ]; + heldCommit && heldCommit->upscaledTexture ) + { + g_bFSRActive = + ( heldCommit->upscaledTexture->eFilter == + GamescopeUpscaleFilter::FSR ); + } + + g_bFirstFrame = false; + + update_app_target_refresh_cycle( ); + + const bool bSupportsDynamicRefresh = + pConnector && !pConnector->GetValidDynamicRefreshRates( ).empty( ); + if ( bSupportsDynamicRefresh ) + { + auto rates = pConnector->GetValidDynamicRefreshRates( ); + + int nDynamicRefreshHz = + g_nDynamicRefreshRate[ GetBackend( )->GetScreenType( ) ]; + + int nTargetRefreshHz = + nDynamicRefreshHz && steamcompmgr_window_should_refresh_switch( + pFocus->focusWindow ) + ? nDynamicRefreshHz + : int( rates[ rates.size( ) - 1 ] ); + + uint64_t now = get_time_in_nanos( ); + + // The actual output hz generated by the mode, could be off by one + // either side, due to rounding. + // + // Check what we cached for the current dynamic refresh Hz we put in -- + // otherwise, convert g_nOutputRefresh -> Hz rounded. + int32_t nCurrentDynamicOutputHz = + g_nDynamicRefreshHz ? g_nDynamicRefreshHz + : gamescope::ConvertmHzToHz( g_nOutputRefresh ); + + if ( nCurrentDynamicOutputHz == nTargetRefreshHz ) + { + g_uDynamicRefreshEqualityTime = now; + } + else if ( g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now ) + { + GetBackend( )->HackTemporarySetDynamicRefresh( nTargetRefreshHz ); + } + } + + bool bDoMuraCompensation = is_mura_correction_enabled( ) && + frameInfo.layerCount && cv_paint_mura_plane; + if ( bDoMuraCompensation ) + { + auto &MuraCorrectionImage = + s_MuraCorrectionImage[ GetBackend( )->GetScreenType( ) ]; + int curLayer = frameInfo.layerCount++; + + FrameInfo_t::Layer_t *layer = &frameInfo.layers[ curLayer ]; + + layer->applyColorMgmt = false; + layer->scale = vec2_t{ 1.0f, 1.0f }; + layer->blackBorder = true; + layer->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU; + layer->hdr_metadata_blob = nullptr; + layer->opacity = 1.0f; + layer->zpos = g_zposMuraCorrection; + layer->filter = GamescopeUpscaleFilter::NEAREST; + layer->tex = MuraCorrectionImage; + layer->ctm = s_MuraCTMBlob[ GetBackend( )->GetScreenType( ) ]; + + // Blending needs to be done in Gamma 2.2 space for mura correction to + // work. + frameInfo.applyOutputColorMgmt = false; + } + + for ( uint32_t i = 0; i < EOTF_Count; i++ ) + { + if ( g_ColorMgmtLuts[ i ].HasLuts( ) ) + { + frameInfo.shaperLut[ i ] = g_ColorMgmtLuts[ i ].vk_lut1d; + frameInfo.lut3D[ i ] = g_ColorMgmtLuts[ i ].vk_lut3d; + } + } + + if ( pConnector && pConnector->Present( &frameInfo, async ) != 0 ) + { + return; + } + + std::optional oScreenshotInfo = + gamescope::CScreenshotManager::Get( ).ProcessPendingScreenshot( ); + + if ( oScreenshotInfo ) + { + std::filesystem::path path = + std::filesystem::path{ oScreenshotInfo->szScreenshotPath }; + + uint32_t drmCaptureFormat = DRM_FORMAT_INVALID; + + if ( path.extension( ) == ".avif" ) + drmCaptureFormat = DRM_FORMAT_XRGB2101010; + else if ( path.extension( ) == ".png" ) + drmCaptureFormat = DRM_FORMAT_XRGB8888; + else if ( path.extension( ) == ".nv12.bin" ) + drmCaptureFormat = DRM_FORMAT_NV12; + + gamescope::Rc pScreenshotTexture; + if ( drmCaptureFormat != DRM_FORMAT_INVALID ) + pScreenshotTexture = vulkan_acquire_screenshot_texture( + g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat ); + + if ( pScreenshotTexture ) + { + bool bHDRScreenshot = + path.extension( ) == ".avif" && frameInfo.layerCount > 0 && + ColorspaceIsHDR( frameInfo.layers[ 0 ].colorspace ) && + oScreenshotInfo->eScreenshotType != + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER; + + if ( drmCaptureFormat == DRM_FORMAT_NV12 || + oScreenshotInfo->eScreenshotType != + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) + { + // Basically no color mgmt applied for screenshots. (aside from + // being able to handle HDR content with LUTs) + for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; + nInputEOTF++ ) + { + auto &luts = bHDRScreenshot ? g_ScreenshotColorMgmtLutsHDR + : g_ScreenshotColorMgmtLuts; + frameInfo.lut3D[ nInputEOTF ] = luts[ nInputEOTF ].vk_lut3d; + frameInfo.shaperLut[ nInputEOTF ] = + luts[ nInputEOTF ].vk_lut1d; + } + + if ( oScreenshotInfo->eScreenshotType == + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_BASE_PLANE_ONLY ) + { + // Remove everything but base planes from the screenshot. + for ( int i = 0; i < frameInfo.layerCount; i++ ) + { + if ( frameInfo.layers[ i ].zpos >= + ( int )g_zposExternalOverlay ) + { + frameInfo.layerCount = i; + break; + } + } + } + else + { + if ( is_mura_correction_enabled( ) ) + { + // Remove the last layer which is for mura... + for ( int i = 0; i < frameInfo.layerCount; i++ ) + { + if ( frameInfo.layers[ i ].zpos >= + ( int )g_zposMuraCorrection ) + { + frameInfo.layerCount = i; + break; + } + } + } + } + + // Re-enable output color management (blending) if it was + // disabled by mura. + frameInfo.applyOutputColorMgmt = true; + } + + frameInfo.outputEncodingEOTF = + bHDRScreenshot ? EOTF_PQ : EOTF_Gamma22; + + uint32_t uCompositeDebugBackup = g_uCompositeDebug; + + if ( oScreenshotInfo->eScreenshotType != + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) + { + g_uCompositeDebug = 0; + } + + std::optional oScreenshotSeq; + if ( drmCaptureFormat == DRM_FORMAT_NV12 ) + oScreenshotSeq = vulkan_composite( + &frameInfo, pScreenshotTexture, false, nullptr ); + else if ( + oScreenshotInfo->eScreenshotType == + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_FULL_COMPOSITION || + oScreenshotInfo->eScreenshotType == + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) + oScreenshotSeq = vulkan_composite( + &frameInfo, nullptr, false, pScreenshotTexture ); + else + oScreenshotSeq = vulkan_screenshot( + &frameInfo, pScreenshotTexture, nullptr ); + + if ( oScreenshotInfo->eScreenshotType != + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) + { + g_uCompositeDebug = uCompositeDebugBackup; + } + + if ( !oScreenshotSeq ) + { + xwm_log.errorf( "vulkan_screenshot failed" ); + return; + } + + vulkan_wait( *oScreenshotSeq, false ); + + uint16_t maxCLLNits = 0; + uint16_t maxFALLNits = 0; + + if ( bHDRScreenshot ) + { + // Unfortunately games give us very bogus values here. + // Thus we don't really use them. + // Instead rely on the display it was initially tonemapped for. + // if ( g_ColorMgmt.current.appHDRMetadata ) + //{ + // maxCLLNits = + // g_ColorMgmt.current.appHDRMetadata->metadata.hdmi_metadata_type1.max_cll; + // maxFALLNits = + // g_ColorMgmt.current.appHDRMetadata->metadata.hdmi_metadata_type1.max_fall; + //} + + if ( !maxCLLNits && !maxFALLNits ) + { + if ( pConnector ) + { + maxCLLNits = + pConnector->GetHDRInfo( ).uMaxContentLightLevel; + maxFALLNits = + pConnector->GetHDRInfo( ).uMaxFrameAverageLuminance; + } + } + + if ( !maxCLLNits && !maxFALLNits ) + { + maxCLLNits = + g_ColorMgmt.pending.flInternalDisplayBrightness; + maxFALLNits = + g_ColorMgmt.pending.flInternalDisplayBrightness * 0.8f; + } + } + + std::thread screenshotThread = std::thread( + [ = ] + { + pthread_setname_np( pthread_self( ), "gamescope-scrsh" ); + + const uint8_t *mappedData = + pScreenshotTexture->mappedData( ); + + bool bScreenshotSuccess = false; + + if ( pScreenshotTexture->format( ) == + VK_FORMAT_A2R10G10B10_UNORM_PACK32 ) + { + // Make our own copy of the image to remove the alpha + // channel. + constexpr uint32_t kCompCnt = 3; + auto imageData = std::vector( + g_nOutputWidth * g_nOutputHeight * kCompCnt ); + + for ( uint32_t y = 0; y < g_nOutputHeight; y++ ) + { + for ( uint32_t x = 0; x < g_nOutputWidth; x++ ) + { + uint32_t *pInPixel = ( uint32_t * )&mappedData + [ ( y * pScreenshotTexture->rowPitch( ) ) + + x * ( 32 / 8 ) ]; + uint32_t uInPixel = *pInPixel; + + imageData + [ y * g_nOutputWidth * kCompCnt + + x * kCompCnt + 0 ] = + ( uInPixel & ( 0b1111111111 << 20 ) ) >> + 20; + imageData + [ y * g_nOutputWidth * kCompCnt + + x * kCompCnt + 1 ] = + ( uInPixel & ( 0b1111111111 << 10 ) ) >> + 10; + imageData + [ y * g_nOutputWidth * kCompCnt + + x * kCompCnt + 2 ] = + ( uInPixel & ( 0b1111111111 << 0 ) ) >> + 0; + } + } + + assert( HAVE_AVIF ); +#if HAVE_AVIF + avifResult avifResult = AVIF_RESULT_OK; + + avifImage *pAvifImage = avifImageCreate( + g_nOutputWidth, + g_nOutputHeight, + 10, + AVIF_PIXEL_FORMAT_YUV444 ); + defer( avifImageDestroy( pAvifImage ) ); + pAvifImage->yuvRange = AVIF_RANGE_FULL; + pAvifImage->colorPrimaries = + bHDRScreenshot ? AVIF_COLOR_PRIMARIES_BT2020 + : AVIF_COLOR_PRIMARIES_BT709; + pAvifImage->transferCharacteristics = + bHDRScreenshot + ? AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084 + : AVIF_TRANSFER_CHARACTERISTICS_SRGB; + // We are not actually using YUV, but storing raw GBR + // (yes not RGB) data This does not compress as well, + // but is always lossless! + pAvifImage->matrixCoefficients = + AVIF_MATRIX_COEFFICIENTS_IDENTITY; + + if ( oScreenshotInfo->eScreenshotType == + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) + { + // When dumping the screen output buffer for + // debugging, mark the primaries as UNKNOWN as stuff + // has likely been transformed to native if HDR on + // Deck OLED etc. We want everything to be seen + // unadulterated by a viewer/image editor. + pAvifImage->colorPrimaries = + AVIF_COLOR_PRIMARIES_UNKNOWN; + } + + if ( bHDRScreenshot ) + { + pAvifImage->clli.maxCLL = maxCLLNits; + pAvifImage->clli.maxPALL = maxFALLNits; + } + + avifRGBImage rgbAvifImage{}; + avifRGBImageSetDefaults( &rgbAvifImage, pAvifImage ); + rgbAvifImage.format = AVIF_RGB_FORMAT_RGB; + rgbAvifImage.ignoreAlpha = AVIF_TRUE; + + rgbAvifImage.pixels = ( uint8_t * )imageData.data( ); + rgbAvifImage.rowBytes = + g_nOutputWidth * kCompCnt * sizeof( uint16_t ); + + if ( ( avifResult = avifImageRGBToYUV( + pAvifImage, &rgbAvifImage ) ) != + AVIF_RESULT_OK ) // Not really! See Matrix + // Coefficients IDENTITY above. + { + xwm_log.errorf( + "Failed to convert RGB to YUV: %u", + avifResult ); + return; + } + + avifEncoder *pEncoder = avifEncoderCreate( ); + defer( avifEncoderDestroy( pEncoder ) ); + pEncoder->quality = AVIF_QUALITY_LOSSLESS; + pEncoder->qualityAlpha = AVIF_QUALITY_LOSSLESS; + pEncoder->speed = AVIF_SPEED_FASTEST; + + if ( ( avifResult = avifEncoderAddImage( + pEncoder, + pAvifImage, + 1, + AVIF_ADD_IMAGE_FLAG_SINGLE ) ) != + AVIF_RESULT_OK ) + { + xwm_log.errorf( + "Failed to add image to avif encoder: %u", + avifResult ); + return; + } + + avifRWData avifOutput = AVIF_DATA_EMPTY; + defer( avifRWDataFree( &avifOutput ) ); + if ( ( avifResult = avifEncoderFinish( + pEncoder, &avifOutput ) ) != AVIF_RESULT_OK ) + { + xwm_log.errorf( + "Failed to finish encoder: %u", avifResult ); + return; + } + + FILE *pScreenshotFile = nullptr; + if ( ( pScreenshotFile = fopen( + oScreenshotInfo->szScreenshotPath.c_str( ), + "wb" ) ) == nullptr ) + { + xwm_log.errorf( + "Failed to fopen file: %s", + oScreenshotInfo->szScreenshotPath.c_str( ) ); + return; + } + + fwrite( + avifOutput.data, + 1, + avifOutput.size, + pScreenshotFile ); + fclose( pScreenshotFile ); + + xwm_log.infof( + "Screenshot saved to %s", + oScreenshotInfo->szScreenshotPath.c_str( ) ); + bScreenshotSuccess = true; +#endif + } + else if ( + pScreenshotTexture->format( ) == + VK_FORMAT_B8G8R8A8_UNORM ) + { + // Make our own copy of the image to remove the alpha + // channel. + auto imageData = std::vector( + currentOutputWidth * currentOutputHeight * 4 ); + const uint32_t comp = 4; + const uint32_t pitch = currentOutputWidth * comp; + for ( uint32_t y = 0; y < currentOutputHeight; y++ ) + { + for ( uint32_t x = 0; x < currentOutputWidth; x++ ) + { + // BGR... + imageData[ y * pitch + x * comp + 0 ] = + mappedData + [ y * pScreenshotTexture->rowPitch( ) + + x * comp + 2 ]; + imageData[ y * pitch + x * comp + 1 ] = + mappedData + [ y * pScreenshotTexture->rowPitch( ) + + x * comp + 1 ]; + imageData[ y * pitch + x * comp + 2 ] = + mappedData + [ y * pScreenshotTexture->rowPitch( ) + + x * comp + 0 ]; + imageData[ y * pitch + x * comp + 3 ] = 255; + } + } + if ( stbi_write_png( + oScreenshotInfo->szScreenshotPath.c_str( ), + currentOutputWidth, + currentOutputHeight, + 4, + imageData.data( ), + pitch ) ) + { + xwm_log.infof( + "Screenshot saved to %s", + oScreenshotInfo->szScreenshotPath.c_str( ) ); + bScreenshotSuccess = true; + } + else + { + xwm_log.errorf( + "Failed to save screenshot to %s", + oScreenshotInfo->szScreenshotPath.c_str( ) ); + } + } + else if ( + pScreenshotTexture->format( ) == + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM ) + { + FILE *file = fopen( + oScreenshotInfo->szScreenshotPath.c_str( ), "wb" ); + if ( file ) + { + fwrite( + mappedData, + 1, + pScreenshotTexture->totalSize( ), + file ); + fclose( file ); + + bScreenshotSuccess = true; + xwm_log.infof( + "Screenshot saved to %s", + oScreenshotInfo->szScreenshotPath.c_str( ) ); - if (!(picture = XRenderCreatePicture(m_ctx->dpy, pixmap, pictformat, 0, NULL))) - { - xwm_log.errorf("Failed to create picture for cursor"); - goto error_picture; - } +#if 0 + char cmd[4096]; + sprintf(cmd, "ffmpeg -f rawvideo -pixel_format nv12 -video_size %dx%d -i %s %s_encoded.png", pScreenshotTexture->width(), pScreenshotTexture->height(), oScreenshotInfo->szScreenshotPath.c_str(), oScreenshotInfo->szScreenshotPath.c_str() ); - if (!(cursor = XRenderCreateCursor(m_ctx->dpy, picture, hx, hy))) - { - xwm_log.errorf("Failed to create cursor"); - goto error_cursor; - } + int ret = system(cmd); - XDefineCursor(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), cursor); - XFlush(m_ctx->dpy); - setDirty(); - return true; + /* Above call may fail, ffmpeg returns 0 on success */ + if (ret) { + xwm_log.infof("Ffmpeg call return status %i", ret); + xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); + } else { + xwm_log.infof("Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str()); + } +#endif + } + else + { + xwm_log.errorf( + "Failed to save screenshot to %s", + oScreenshotInfo->szScreenshotPath.c_str( ) ); + } + } + + if ( oScreenshotInfo->bX11PropertyRequested ) + { + XDeleteProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeScreenShotAtom ); + XDeleteProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeDebugScreenShotAtom ); + } + + if ( bScreenshotSuccess && + oScreenshotInfo->bWaylandRequested ) + { + wlserver_lock( ); + for ( const auto &control : + wlserver.gamescope_controls ) + { + gamescope_control_send_screenshot_taken( + control, + oScreenshotInfo->szScreenshotPath.c_str( ) ); + } + wlserver_unlock( ); + } + } ); + + screenshotThread.detach( ); + } + else + { + xwm_log.errorf( + "Oh no, we ran out of screenshot images. Not actually writing " + "a screenshot." ); + if ( oScreenshotInfo->bX11PropertyRequested ) + { + XDeleteProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeScreenShotAtom ); + XDeleteProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeDebugScreenShotAtom ); + } + } + } + + gpuvis_trace_end_ctx_printf( paintID, "paint_all" ); + gpuvis_trace_printf( "paint_all %i layers", ( int )frameInfo.layerCount ); +} -error_cursor: - XRenderFreePicture(m_ctx->dpy, picture); -error_picture: -error_pictformat: - XFreeGC(m_ctx->dpy, gc); -error_gc: - XFreePixmap(m_ctx->dpy, pixmap); -error_pixmap: - // XDestroyImage frees the data. - XDestroyImage(ximage); -error_image: - return false; +/* Get prop from window + * not found: default + * otherwise the value + */ +__attribute__(( + __no_sanitize_address__ )) // x11 broken, returns format 32 even when it + // only malloc'ed one byte. :( +static unsigned int get_prop( + xwayland_ctx_t *ctx, + Window win, + Atom prop, + unsigned int def, + bool *found = nullptr ) +{ + Atom actual; + int format; + unsigned long n, left; + + unsigned char *data; + int result = XGetWindowProperty( + ctx->dpy, + win, + prop, + 0L, + 1L, + false, + XA_CARDINAL, + &actual, + &format, + &n, + &left, + &data ); + if ( result == Success && data != NULL ) + { + unsigned int i; + memcpy( &i, data, sizeof( unsigned int ) ); + XFree( ( void * )data ); + if ( found != nullptr ) { *found = true; } + return i; + } + if ( found != nullptr ) { *found = false; } + return def; +} + +// vectored version, return value is whether anything was found +__attribute__(( __no_sanitize_address__ )) // x11 broken :( +bool get_prop( + xwayland_ctx_t *ctx, + Window win, + Atom prop, + std::vector &vecResult ) +{ + Atom actual; + int format; + unsigned long n, left; + + vecResult.clear( ); + uint64_t *data; + int result = XGetWindowProperty( + ctx->dpy, + win, + prop, + 0L, + ~0UL, + false, + XA_CARDINAL, + &actual, + &format, + &n, + &left, + ( unsigned char ** )&data ); + if ( result == Success && data != NULL ) + { + for ( uint32_t i = 0; i < n; i++ ) + { + vecResult.push_back( data[ i ] ); + } + XFree( ( void * )data ); + return true; + } + return false; +} + +std::optional +get_u64_prop( xwayland_ctx_t *ctx, Window win, Atom prop ) +{ + std::vector values; + get_prop( ctx, win, prop, values ); + + if ( values.size( ) != 2 ) return std::nullopt; + + return ( ( uint64_t )( values[ 0 ] ) ) | + ( ( ( uint64_t )values[ 1 ] ) << 32ul ); } -bool MouseCursor::setCursorImageByName(const char *name) +std::string get_string_prop( xwayland_ctx_t *ctx, Window win, Atom prop ) { - int index = XmuCursorNameToIndex(name); - if (index < 0) - return false; + XTextProperty tp; + if ( !XGetTextProperty( ctx->dpy, win, &tp, prop ) ) return ""; - Cursor cursor = XcursorShapeLoadCursor( m_ctx->dpy, index ); + std::string value = reinterpret_cast( tp.value ); + XFree( tp.value ); + return value; +} - XDefineCursor(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), cursor); - XFlush(m_ctx->dpy); - setDirty(); - return true; +void set_string_prop( xwayland_ctx_t *ctx, Atom prop, const std::string &value ) +{ + XTextProperty text_property = { .value = ( unsigned char * )value.c_str( ), + .encoding = ctx->atoms.utf8StringAtom, + .format = 8, + .nitems = strlen( value.c_str( ) ) }; + XSetTextProperty( ctx->dpy, ctx->root, &text_property, prop ); + XFlush( ctx->dpy ); } -int MouseCursor::x() const +void clear_prop( xwayland_ctx_t *ctx, Atom prop ) { - return m_x; + XDeleteProperty( ctx->dpy, ctx->root, prop ); + XFlush( ctx->dpy ); +} + +static bool win_has_game_id( steamcompmgr_win_t *w ) { return w->appID != 0; } + +static bool win_is_useless( steamcompmgr_win_t *w ) +{ + if ( w->isSysTrayIcon ) return true; + + // Windows that are 1x1 are pretty useless for override redirects. + // Just ignore them. + // Fixes the Xbox Login in Age of Empires 2: DE. + return w->GetGeometry( ).nWidth == 1 && w->GetGeometry( ).nHeight == 1; +} + +static bool win_is_override_redirect( steamcompmgr_win_t *w ) +{ + if ( w->type != steamcompmgr_win_type_t::XWAYLAND ) return false; + + return w->xwayland( ).a.override_redirect && !w->ignoreOverrideRedirect && + !win_is_useless( w ); +} + +static bool win_skip_taskbar_and_pager( steamcompmgr_win_t *w ) +{ return w->skipTaskbar && w->skipPager; } + +static bool win_skip_and_not_fullscreen( steamcompmgr_win_t *w ) +{ return win_skip_taskbar_and_pager( w ) && !w->isFullscreen; } + +static bool win_maybe_a_dropdown( steamcompmgr_win_t *w ) +{ + if ( w->type != steamcompmgr_win_type_t::XWAYLAND ) return false; + + // Josh: + // Right now we don't get enough info from Wine + // about the true nature of windows to distringuish + // something like the Fallout 4 Options menu from the + // Warframe language dropdown. Until we get more stuff + // exposed for that, there is this workaround to let that work. + if ( w->appID == 230410 && w->maybe_a_dropdown && + w->xwayland( ).transientFor && ( w->skipPager || w->skipTaskbar ) ) + return !win_is_useless( w ); + + // Work around Antichamber splash screen until we hook up + // the Proton window style deduction. + if ( w->appID == 219890 ) return false; + + // The Launcher in Witcher 2 (20920) has a clear window with WS_EX_LAYERED + // on top of it. + // + // The Age of Empires 2 Launcher also has a WS_EX_LAYERED window to separate + // controls from its backing, which this seems to handle, although we + // seemingly don't handle it's transparency yet, which I do not understand. + // + // Layered windows are windows that are meant to be transparent + // with alpha blending + visual fx. + // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features + // + // TODO: Come back to me for original Age of Empires HD launcher. + // Does that use it? It wants blending! + // + // Only do this if we have CONTROLPARENT right now. Some other apps, such as + // the Street Fighter V (310950) Splash Screen also use LAYERED and + // TOOLWINDOW, and we don't want that to be overlayed. Ignore LAYERED if + // it's marked as top-level with WS_EX_APPWINDOW. + // TODO: Find more apps using LAYERED. + const uint32_t validLayered = WS_EX_CONTROLPARENT | WS_EX_LAYERED; + const uint32_t invalidLayered = WS_EX_APPWINDOW; + if ( w->hasHwndStyleEx && + ( ( w->hwndStyleEx & validLayered ) == validLayered ) && + ( ( w->hwndStyleEx & invalidLayered ) == 0 ) ) + return true; + + // Forza Horizon 4 & 5 create a black background window that might + // incorrectly be considered a valid dropdown and steal focus. + if ( ( w->appID == 1293830 || w->appID == 1551360 ) && + w->maybe_a_dropdown && w->requestedWidth == 0 && + w->requestedHeight == 0 ) + return false; + + // Josh: + // The logic here is as follows. The window will be treated as a dropdown + // if: + // + // If this window has a fixed position on the screen + static gravity: + // - If the window has either skipPage or skipTaskbar + // - If the window isn't a dialog, always treat it as a dropdown, as it's + // probably meant to be some form of popup. + // - If the window is a dialog + // - If the window has transient for, disregard it, as it is trying to + // redirecting us elsewhere + // ie. a settings menu dialog popup or something. + // - If the window has both skip taskbar and pager, treat it as a + // dialog. + bool valid_maybe_a_dropdown = + w->maybe_a_dropdown && + ( ( !w->is_dialog || ( !w->xwayland( ).transientFor && + win_skip_and_not_fullscreen( w ) ) ) && + ( w->skipPager || w->skipTaskbar ) ); + return ( valid_maybe_a_dropdown || win_is_override_redirect( w ) ) && + !win_is_useless( w ); +} + +static bool win_is_disabled( steamcompmgr_win_t *w ) +{ + if ( !w->hasHwndStyle ) return false; + + return !!( w->hwndStyle & WS_DISABLED ); } -int MouseCursor::y() const +/* Returns true if a's focus priority > b's. + * + * This function establishes a list of criteria to decide which window should + * have focus. The first criteria has higher priority. If the first criteria + * is a tie, fallback to the second one, then the third, and so on. + * + * The general workflow is: + * + * if ( windows don't have the same criteria value ) + * return true if a should be focused; + * // This is a tie, fallback to the next criteria + */ +static bool +is_focus_priority_greater( steamcompmgr_win_t *a, steamcompmgr_win_t *b ) { - return m_y; + if ( win_has_game_id( a ) != win_has_game_id( b ) ) + return win_has_game_id( a ); + + // We allow using an override redirect window in some cases, but if we have + // a choice between two windows we always prefer the non-override redirect + // one. + if ( win_is_override_redirect( a ) != win_is_override_redirect( b ) ) + return !win_is_override_redirect( a ); + + // If the window is 1x1 then prefer anything else we have. + if ( win_is_useless( a ) != win_is_useless( b ) ) + return !win_is_useless( a ); + + if ( win_maybe_a_dropdown( a ) != win_maybe_a_dropdown( b ) ) + return !win_maybe_a_dropdown( a ); + + if ( win_is_disabled( a ) != win_is_disabled( b ) ) + return !win_is_disabled( a ); + + // Wine sets SKIP_TASKBAR and SKIP_PAGER hints for WS_EX_NOACTIVATE windows. + // See https://github.com/Plagman/gamescope/issues/87 + if ( win_skip_and_not_fullscreen( a ) != win_skip_and_not_fullscreen( b ) ) + return !win_skip_and_not_fullscreen( a ); + + // Prefer normal windows over dialogs + // if we are an override redirect/dropdown window. + if ( win_maybe_a_dropdown( a ) && win_maybe_a_dropdown( b ) && + a->is_dialog != b->is_dialog ) + return !a->is_dialog; + + if ( a->type != steamcompmgr_win_type_t::XWAYLAND ) { return true; } + + // Attempt to tie-break dropdowns by transient-for. + if ( win_maybe_a_dropdown( a ) && win_maybe_a_dropdown( b ) && + !a->xwayland( ).transientFor != !b->xwayland( ).transientFor ) + return !a->xwayland( ).transientFor; + + if ( win_has_game_id( a ) && + a->xwayland( ).map_sequence != b->xwayland( ).map_sequence ) + return a->xwayland( ).map_sequence > b->xwayland( ).map_sequence; + + // The damage sequences are only relevant for game windows. + if ( win_has_game_id( a ) && + a->xwayland( ).damage_sequence != b->xwayland( ).damage_sequence ) + return a->xwayland( ).damage_sequence > b->xwayland( ).damage_sequence; + + return false; +} + +static bool is_good_override_candidate( + steamcompmgr_win_t *override, steamcompmgr_win_t *focus ) +{ + // Some Chrome/Edge dropdowns (ie. FH5 xbox login) will automatically close + // themselves if you focus them while they are meant to be offscreen (-1,-1 + // and 1x1) so check that the override's position is on-screen. + if ( !focus ) return false; + + // The pids should probably match for a dropdown to be a good candidate for + // this window. + if ( override->pid != focus->pid ) return false; + + auto rect = override->GetGeometry( ); + return override != focus && ( rect.nX + rect.nWidth ) > 0 && + ( rect.nY + rect.nHeight ) > 0; +} + +static void handle_desktop_window( steamcompmgr_win_t *w ); + +static bool pick_primary_focus_and_override( + focus_t *out, + Window focusControlWindow, + const std::vector &vecPossibleFocusWindows, + bool globalFocus, + const std::vector &ctxFocusControlAppIDs, + uint64_t ulVirtualFocusKey, + gamescope::VirtualConnectorStrategy eStrategy ) +{ + bool localGameFocused = false; + steamcompmgr_win_t *focus = NULL, *override_focus = NULL; + + bool controlledFocus = + eStrategy != gamescope::VirtualConnectorStrategies::SingleApplication || + focusControlWindow != None || !ctxFocusControlAppIDs.empty( ); + if ( controlledFocus ) + { + if ( eStrategy == + gamescope::VirtualConnectorStrategies::SteamControlled ) + { + if ( focusControlWindow != None ) + { + for ( steamcompmgr_win_t *focusable_window : + vecPossibleFocusWindows ) + { + if ( focusable_window->type != + steamcompmgr_win_type_t::XWAYLAND ) + continue; + + if ( focusable_window->xwayland( ).id == + focusControlWindow ) + { + focus = focusable_window; + localGameFocused = true; + goto found; + } + } + } + + for ( auto focusable_appid : ctxFocusControlAppIDs ) + { + for ( steamcompmgr_win_t *focusable_window : + vecPossibleFocusWindows ) + { + if ( focusable_window->appID == focusable_appid ) + { + focus = focusable_window; + localGameFocused = true; + goto found; + } + } + } + } + else + { + for ( steamcompmgr_win_t *focusable_window : + vecPossibleFocusWindows ) + { + if ( focusable_window->GetVirtualConnectorKey( eStrategy ) == + ulVirtualFocusKey ) + { + focus = focusable_window; + localGameFocused = true; + goto found; + } + } + } + + found:; + } + + if ( !focus && ( !globalFocus || !controlledFocus ) ) + { + if ( !vecPossibleFocusWindows.empty( ) ) + { + focus = vecPossibleFocusWindows[ 0 ]; + localGameFocused = focus->appID != 0; + } + } + + auto resolveTransientOverrides = [ & ]( bool maybe ) + { + if ( !focus || focus->type != steamcompmgr_win_type_t::XWAYLAND ) + return; + + // Do some searches to find transient links to override redirects too. + while ( true ) + { + bool bFoundTransient = false; + + for ( steamcompmgr_win_t *candidate : vecPossibleFocusWindows ) + { + if ( candidate->type != steamcompmgr_win_type_t::XWAYLAND ) + continue; + + bool is_dropdown = maybe + ? win_maybe_a_dropdown( candidate ) + : win_is_override_redirect( candidate ); + if ( ( !override_focus || candidate != override_focus ) && + candidate != focus && + ( ( !override_focus && + candidate->xwayland( ).transientFor == + focus->xwayland( ).id ) || + ( override_focus && + candidate->xwayland( ).transientFor == + override_focus->xwayland( ).id ) ) && + is_dropdown ) + { + bFoundTransient = true; + override_focus = candidate; + break; + } + } + + // Hopefully we can't have transient cycles or we'll have to + // maintain a list of visited windows here + if ( bFoundTransient == false ) break; + } + }; + + if ( focus && focus->type == steamcompmgr_win_type_t::XWAYLAND ) + { + if ( !focusControlWindow ) + { + // Do some searches through game windows to follow transient links + // if needed + while ( true ) + { + bool bFoundTransient = false; + + for ( steamcompmgr_win_t *candidate : vecPossibleFocusWindows ) + { + if ( candidate->type != steamcompmgr_win_type_t::XWAYLAND ) + continue; + + if ( candidate != focus && + candidate->xwayland( ).transientFor == + focus->xwayland( ).id && + !win_maybe_a_dropdown( candidate ) ) + { + bFoundTransient = true; + focus = candidate; + break; + } + } + + // Hopefully we can't have transient cycles or we'll have to + // maintain a list of visited windows here + if ( bFoundTransient == false ) break; + } + } + + if ( !override_focus ) + { + if ( !ctxFocusControlAppIDs.empty( ) ) + { + for ( steamcompmgr_win_t *override : vecPossibleFocusWindows ) + { + if ( win_is_override_redirect( override ) && + is_good_override_candidate( override, focus ) && + override->appID == focus->appID ) + { + override_focus = override; + break; + } + } + } + else if ( !vecPossibleFocusWindows.empty( ) ) + { + for ( steamcompmgr_win_t *override : vecPossibleFocusWindows ) + { + if ( win_is_override_redirect( override ) && + is_good_override_candidate( override, focus ) ) + { + override_focus = override; + break; + } + } + } + + resolveTransientOverrides( false ); + } + } + + if ( focus ) + { + if ( window_has_commits( focus ) ) out->focusWindow = focus; + else + focus->outdatedInteractiveFocus = true; + + // Always update X's idea of focus, but still dirty + // the it being outdated so we can resolve that globally later. + // + // Only affecting X and not the WL idea of focus here, + // we always want to think the window is focused. + // but our real presenting focus and input focus can be elsewhere. + if ( !globalFocus ) out->focusWindow = focus; + } + + if ( !override_focus && focus ) + { + if ( controlledFocus ) + { + for ( auto focusable_appid : ctxFocusControlAppIDs ) + { + for ( steamcompmgr_win_t *fake_override : + vecPossibleFocusWindows ) + { + if ( fake_override->appID == focusable_appid ) + { + if ( win_maybe_a_dropdown( fake_override ) && + is_good_override_candidate( + fake_override, focus ) && + fake_override->appID == focus->appID ) + { + override_focus = fake_override; + goto found2; + } + } + } + } + } + else + { + for ( steamcompmgr_win_t *fake_override : vecPossibleFocusWindows ) + { + if ( win_maybe_a_dropdown( fake_override ) && + is_good_override_candidate( fake_override, focus ) ) + { + override_focus = fake_override; + goto found2; + } + } + } + + found2:; + resolveTransientOverrides( true ); + } + + out->overrideWindow = override_focus; + + return localGameFocused; +} + +std::vector xwayland_ctx_t::GetPossibleFocusWindows( ) +{ + std::vector vecPossibleFocusWindows; + + for ( steamcompmgr_win_t *w = this->list; w; w = w->xwayland( ).next ) + { + // Always skip system tray icons and overlays + if ( w->isSysTrayIcon || w->isOverlay || w->isExternalOverlay ) + { + continue; + } + + // Skip overlay targets + if ( w->oulTargetVROverlay && !cv_vr_show_forwarded_overlays ) + { + continue; + } + + // Skip streaming client video window + if ( w->isSteamStreamingClientVideo ) { continue; } + + if ( gamescope::cv_backend_virtual_connector_strategy == + gamescope::VirtualConnectorStrategies::SteamControlled ) + { + if ( !( win_has_game_id( w ) || window_is_steam( w ) || + w->isSteamStreamingClient ) ) + continue; + } + + if ( w->xwayland( ).a.map_state == IsViewable && + w->xwayland( ).a.c_class == InputOutput && + ( w->opacity > TRANSLUCENT || w->isSteamStreamingClient ) ) + { + vecPossibleFocusWindows.push_back( w ); + } + } + + std::stable_sort( + vecPossibleFocusWindows.begin( ), + vecPossibleFocusWindows.end( ), + is_focus_priority_greater ); + + return vecPossibleFocusWindows; } -bool MouseCursor::getTexture() +static void set_wm_state( xwayland_ctx_t *ctx, Window win, uint32_t state ) { - uint64_t ulConnectorId = 0; - if ( GetBackend()->GetCurrentConnector() ) - ulConnectorId = GetBackend()->GetCurrentConnector()->GetConnectorID(); + uint32_t wmState[] = { state, None }; + XChangeProperty( + ctx->dpy, + win, + ctx->atoms.WMStateAtom, + ctx->atoms.WMStateAtom, + 32, + PropModeReplace, + ( unsigned char * )wmState, + sizeof( wmState ) / sizeof( wmState[ 0 ] ) ); +} + +void xwayland_ctx_t::DetermineAndApplyFocus( + const std::vector &vecPossibleFocusWindows ) +{ + xwayland_ctx_t *ctx = this; + + steamcompmgr_win_t *inputFocus = NULL; + + steamcompmgr_win_t *prevFocusWindow = ctx->focus.focusWindow; + ctx->focus.overlayWindow = nullptr; + ctx->focus.notificationWindow = nullptr; + ctx->focus.overrideWindow = nullptr; + ctx->focus.externalOverlayWindow = nullptr; + + unsigned int maxOpacity = 0; + unsigned int maxOpacityExternal = 0; + for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland( ).next ) + { + if ( w->isOverlay ) + { + if ( w->GetGeometry( ).nWidth > 1200 && w->opacity >= maxOpacity ) + { + ctx->focus.overlayWindow = w; + maxOpacity = w->opacity; + } + else + { + ctx->focus.notificationWindow = w; + } + } + + if ( w->isExternalOverlay ) + { + if ( w->opacity > maxOpacityExternal ) + { + ctx->focus.externalOverlayWindow = w; + maxOpacityExternal = w->opacity; + } + } + + if ( gamescope::VirtualConnectorIsSingleOutput( ) ) + { + if ( w->isOverlay && w->inputFocusMode ) { inputFocus = w; } + } + } + + gamescope::VirtualConnectorStrategy eStrategy = + gamescope::VirtualConnectorStrategies::PerWindow; + gamescope::VirtualConnectorKey_t ulKey = 0; + + if ( ctx->xwayland_server->get_index( ) != 0 ) + { + eStrategy = gamescope::VirtualConnectorStrategies::SteamControlled; + ulKey = 0; + } + else if ( GetBackend( ) && GetBackend( )->GetCurrentConnector( ) ) + { + eStrategy = gamescope::cv_backend_virtual_connector_strategy; + ulKey = + GetBackend( )->GetCurrentConnector( )->GetVirtualConnectorKey( ); + } + + pick_primary_focus_and_override( + &ctx->focus, + ctx->focusControlWindow, + vecPossibleFocusWindows, + false, + vecFocuscontrolAppIDs, + ulKey, + eStrategy ); + + if ( !ctx->focus.overrideWindowMouse ) + { + ctx->focus.overrideWindowMouse = ctx->focus.overrideWindow; + } + + if ( inputFocus == NULL ) { inputFocus = ctx->focus.focusWindow; } + + if ( ctx->focus.focusWindow ) + { + if ( prevFocusWindow != ctx->focus.focusWindow ) + { + /* Some games (e.g. DOOM Eternal) don't react well to being put back + * as iconic, so never do that. Only take them out of iconic. */ + set_wm_state( + ctx, + ctx->focus.focusWindow->xwayland( ).id, + ICCCM_NORMAL_STATE ); + + gpuvis_trace_printf( + "determine_and_apply_focus focus %lu", + ctx->focus.focusWindow->xwayland( ).id ); + + if ( debugFocus == true ) + { + xwm_log.debugf( + "determine_and_apply_focus focus %lu", + ctx->focus.focusWindow->xwayland( ).id ); + char buf[ 512 ]; + sprintf( + buf, + "xwininfo -id 0x%lx; xprop -id 0x%lx; xwininfo -root -tree", + ctx->focus.focusWindow->xwayland( ).id, + ctx->focus.focusWindow->xwayland( ).id ); + system( buf ); + } + } + } + + steamcompmgr_win_t *keyboardFocusWin = inputFocus; + + if ( gamescope::VirtualConnectorIsSingleOutput( ) ) + { + if ( inputFocus && inputFocus->inputFocusMode == 2 ) + keyboardFocusWin = ctx->focus.focusWindow; + } + else + { + // Handle mouse focus being totally disjoint from KB in VR. + if ( GetBackend( ) && GetBackend( )->GetCurrentMouseConnector( ) ) + { + gamescope::VirtualConnectorKey_t ulMouseKey = + GetBackend( ) + ->GetCurrentMouseConnector( ) + ->GetVirtualConnectorKey( ); + + focus_t mouse_focus{}; + pick_primary_focus_and_override( + &mouse_focus, + ctx->focusControlWindow, + vecPossibleFocusWindows, + false, + vecFocuscontrolAppIDs, + ulMouseKey, + eStrategy ); + + if ( mouse_focus.overrideWindow || mouse_focus.focusWindow ) + { + inputFocus = mouse_focus.overrideWindow + ? mouse_focus.overrideWindow + : mouse_focus.focusWindow; + ctx->focus.overrideWindowMouse = mouse_focus.overrideWindow; + } + } + + uint64_t ulFocusedKeyboardOverlayVR = g_FocusedVROverlayKeyboard; + uint64_t ulFocusedMouseOverlayVR = g_FocusedVROverlayMouse; + + if ( ulFocusedKeyboardOverlayVR || ulFocusedMouseOverlayVR ) + { + for ( steamcompmgr_win_t *queryWindow = ctx->list; queryWindow; + queryWindow = queryWindow->xwayland( ).next ) + { + if ( queryWindow->oulTargetVROverlay && + *queryWindow->oulTargetVROverlay == + ulFocusedKeyboardOverlayVR ) + { + focus_log.debugf( + "[XWL] Overriding keyboard focus window with VR " + "forwarder overlay! Overlay: 0x%lx XWindow: 0x%x " + "Title: %s", + ulFocusedKeyboardOverlayVR, + queryWindow->id( ), + queryWindow->debug_name( ) ); + + keyboardFocusWin = queryWindow; + ctx->focus.focusWindow = queryWindow; + + // No support for overrides with this VR path! + ctx->focus.overrideWindow = nullptr; + ctx->focus.overrideWindowMouse = nullptr; + } + + if ( queryWindow->oulTargetVROverlay && + *queryWindow->oulTargetVROverlay == + ulFocusedMouseOverlayVR ) + { + // We don't want to do any mouse input for target VR + // overlays right now. SteamWebHelper is handling this. + + if ( !inputFocus ) inputFocus = queryWindow; + + // No support for overrides with this VR path! + ctx->focus.overrideWindow = nullptr; + ctx->focus.overrideWindowMouse = nullptr; + } + } + } + } + + if ( !ctx->focus.focusWindow ) { return; } + + if ( !inputFocus ) { inputFocus = ctx->focus.focusWindow; } + + Window keyboardFocusWindow = + keyboardFocusWin ? keyboardFocusWin->xwayland( ).id : None; + + // If the top level parent of our current keyboard window is the same as our + // target (top level) input focus window then keep focus on that and don't + // yank it away to the top level input focus window. Fixes dropdowns in + // Steam CEF. + if ( keyboardFocusWindow && ctx->currentKeyboardFocusWindow && + find_win( ctx, ctx->currentKeyboardFocusWindow ) == keyboardFocusWin ) + keyboardFocusWindow = ctx->currentKeyboardFocusWindow; + + if ( ctx->focus.inputFocusWindow != inputFocus || + ctx->focus.inputFocusMode != inputFocus->inputFocusMode || + ctx->currentKeyboardFocusWindow != keyboardFocusWindow ) + { + if ( debugFocus == true ) + { + xwm_log.debugf( + "determine_and_apply_focus inputFocus %lu", + inputFocus->xwayland( ).id ); + } + + if ( !ctx->focus.overrideWindow || + ctx->focus.overrideWindow != keyboardFocusWin ) + XSetInputFocus( + ctx->dpy, + keyboardFocusWin->xwayland( ).id, + RevertToNone, + CurrentTime ); + + if ( ctx->focus.inputFocusWindow != inputFocus || + ctx->focus.inputFocusMode != inputFocus->inputFocusMode ) + { + // If the window doesn't want focus when hidden, move it away + // as we are going to hide it straight after. + // otherwise, if we switch from wanting it to not + // (steam -> game) + // put us back in the centre of the screen. + if ( window_wants_no_focus_when_mouse_hidden( inputFocus ) ) + ctx->focus.bResetToCorner = true; + else if ( + window_wants_no_focus_when_mouse_hidden( inputFocus ) != + window_wants_no_focus_when_mouse_hidden( + ctx->focus.inputFocusWindow ) ) + ctx->focus.bResetToCenter = true; + + // cursor is likely not interactable anymore in its original + // context, hide don't care if we change kb focus window due to that + // happening when going from override -> focus and we don't want to + // hide then as it's probably a dropdown. + ctx->cursor->hide( ); + } + + ctx->focus.inputFocusWindow = inputFocus; + ctx->focus.inputFocusMode = inputFocus->inputFocusMode; + ctx->currentKeyboardFocusWindow = keyboardFocusWindow; + } + + steamcompmgr_win_t *w; + w = ctx->focus.focusWindow; + + if ( inputFocus == ctx->focus.focusWindow && + ctx->focus.overrideWindowMouse ) + { + if ( ctx->list[ 0 ].xwayland( ).id != + ctx->focus.overrideWindowMouse->xwayland( ).id ) + { + XRaiseWindow( + ctx->dpy, ctx->focus.overrideWindowMouse->xwayland( ).id ); + } + } + else + { + if ( ctx->list[ 0 ].xwayland( ).id != inputFocus->xwayland( ).id ) + { + XRaiseWindow( ctx->dpy, inputFocus->xwayland( ).id ); + } + } + + if ( !ctx->focus.focusWindow->nudged ) + { + XMoveWindow( ctx->dpy, ctx->focus.focusWindow->xwayland( ).id, 1, 1 ); + ctx->focus.focusWindow->nudged = true; + } + + if ( w->GetGeometry( ).nX != 0 || w->GetGeometry( ).nY != 0 ) + XMoveWindow( ctx->dpy, ctx->focus.focusWindow->xwayland( ).id, 0, 0 ); + + if ( win_has_game_id( w ) ) + { + if ( window_is_fullscreen( ctx->focus.focusWindow ) || + ctx->force_windows_fullscreen ) + { + bool bIsSteam = window_is_steam( ctx->focus.focusWindow ); + int fs_width = ctx->root_width; + int fs_height = ctx->root_height; + if ( bIsSteam && g_nSteamMaxHeight && + ctx->root_height > g_nSteamMaxHeight ) + { + float steam_height_scale = + g_nSteamMaxHeight / ( float )ctx->root_height; + fs_height = g_nSteamMaxHeight; + fs_width = ctx->root_width * steam_height_scale; + } + + if ( w->GetGeometry( ).nWidth != fs_width || + w->GetGeometry( ).nHeight != fs_height || + globalScaleRatio != 1.0f ) + XResizeWindow( + ctx->dpy, + ctx->focus.focusWindow->xwayland( ).id, + fs_width, + fs_height ); + } + else + { + if ( ctx->focus.focusWindow->sizeHintsSpecified && + ( ( unsigned )ctx->focus.focusWindow->GetGeometry( ).nWidth != + ctx->focus.focusWindow->requestedWidth || + ( unsigned )ctx->focus.focusWindow->GetGeometry( ).nHeight != + ctx->focus.focusWindow->requestedHeight ) ) + { + XResizeWindow( + ctx->dpy, + ctx->focus.focusWindow->xwayland( ).id, + ctx->focus.focusWindow->requestedWidth, + ctx->focus.focusWindow->requestedHeight ); + } + } + } + else + { + handle_desktop_window( w ); + } + + Window root_return = None, parent_return = None; + Window *children = NULL; + unsigned int nchildren = 0; + unsigned int i = 0; + + XQueryTree( + ctx->dpy, + w->xwayland( ).id, + &root_return, + &parent_return, + &children, + &nchildren ); + + while ( i < nchildren ) + { + XSelectInput( ctx->dpy, children[ i ], FocusChangeMask ); + i++; + } + + XFree( children ); + + ctx->focus.ulCurrentFocusSerial = GetFocusSerial( ); +} + +wlr_surface *win_surface( steamcompmgr_win_t *window ) +{ + if ( !window ) return nullptr; + + return window->main_surface( ); +} + +const char *get_win_display_name( steamcompmgr_win_t *window ) +{ + if ( window->type == steamcompmgr_win_type_t::XWAYLAND ) + return window->xwayland( ) + .ctx->xwayland_server->get_nested_display_name( ); + else if ( window->type == steamcompmgr_win_type_t::XDG ) + return wlserver_get_wl_display_name( ); + else + return ""; +} + +static std::vector +steamcompmgr_xdg_get_possible_focus_windows( ) +{ + std::vector windows; + for ( auto &win : g_steamcompmgr_xdg_wins ) + { + // Always skip system tray icons and overlays + if ( win->isSysTrayIcon || win->isOverlay || win->isExternalOverlay ) + { + continue; + } + + windows.emplace_back( win.get( ) ); + } + return windows; +} + +static std::vector GetGlobalPossibleFocusWindows( ) +{ + std::vector vecPossibleFocusWindows; + + { + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + { + std::vector vecLocalPossibleFocusWindows = + server->ctx->GetPossibleFocusWindows( ); + vecPossibleFocusWindows.insert( + vecPossibleFocusWindows.end( ), + vecLocalPossibleFocusWindows.begin( ), + vecLocalPossibleFocusWindows.end( ) ); + } + } + + { + std::vector vecLocalPossibleFocusWindows = + steamcompmgr_xdg_get_possible_focus_windows( ); + vecPossibleFocusWindows.insert( + vecPossibleFocusWindows.end( ), + vecLocalPossibleFocusWindows.begin( ), + vecLocalPossibleFocusWindows.end( ) ); + } + + // Determine global primary focus + std::stable_sort( + vecPossibleFocusWindows.begin( ), + vecPossibleFocusWindows.end( ), + is_focus_priority_greater ); + + return vecPossibleFocusWindows; +} + +static void steamcompmgr_xdg_determine_and_apply_focus( + const std::vector &vecPossibleFocusWindows ) +{ + for ( auto &window : g_steamcompmgr_xdg_wins ) + { + if ( window->isOverlay ) + g_steamcompmgr_xdg_focus.overlayWindow = window.get( ); - if ( ulConnectorId != m_ulLastConnectorId ) - { - m_ulLastConnectorId = ulConnectorId; - m_dirty = true; - } + if ( window->isExternalOverlay ) + g_steamcompmgr_xdg_focus.externalOverlayWindow = window.get( ); + } - if (!m_dirty) { - return !m_imageEmpty; - } + gamescope::VirtualConnectorStrategy eStrategy = + gamescope::cv_backend_virtual_connector_strategy == + gamescope::VirtualConnectorStrategies::SteamControlled + ? gamescope::VirtualConnectorStrategies::SteamControlled + : gamescope::VirtualConnectorStrategies::PerWindow; - auto *image = XFixesGetCursorImage(m_ctx->dpy); + pick_primary_focus_and_override( + &g_steamcompmgr_xdg_focus, + None, + vecPossibleFocusWindows, + false, + vecFocuscontrolAppIDs, + 0, + eStrategy ); +} - if (!image) { - return false; - } +uint32_t g_focusedBaseAppId = 0; + +static void determine_and_apply_focus( global_focus_t *pFocus ) +{ + gamescope_xwayland_server_t *root_server = + wlserver_get_xwayland_server( 0 ); + xwayland_ctx_t *root_ctx = root_server->ctx.get( ); + global_focus_t previousLocalFocus = *pFocus; + *pFocus = global_focus_t{}; + pFocus->focusWindow = previousLocalFocus.focusWindow; + pFocus->cursor = root_ctx->cursor.get( ); + pFocus->ulVirtualFocusKey = previousLocalFocus.ulVirtualFocusKey; + pFocus->pVirtualConnector = previousLocalFocus.pVirtualConnector; + gameFocused = false; + + focus_log.debugf( "Rerolling global focus..." ); + + std::vector focusable_appids; + std::vector focusable_windows; + + // Apply focus to the XWayland contexts. + { + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + { + std::vector vecLocalPossibleFocusWindows = + server->ctx->GetPossibleFocusWindows( ); + if ( server->ctx->focus.IsDirty( ) ) + server->ctx->DetermineAndApplyFocus( + vecLocalPossibleFocusWindows ); + } + } + + // Apply focus to XDG contexts (TODO merge me with some nice abstraction of + // "environments") + { + std::vector vecLocalPossibleFocusWindows = + steamcompmgr_xdg_get_possible_focus_windows( ); + if ( g_steamcompmgr_xdg_focus.IsDirty( ) ) + steamcompmgr_xdg_determine_and_apply_focus( + vecLocalPossibleFocusWindows ); + } + + // Determine local context focuses + std::vector vecPossibleFocusWindows = + GetGlobalPossibleFocusWindows( ); + + for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) + { + if ( focusable_window->type != steamcompmgr_win_type_t::XWAYLAND ) + continue; + + // Exclude windows that are useless (1x1), skip taskbar + pager or + // override redirect windows from the reported focusable windows to + // Steam. + if ( win_is_useless( focusable_window ) || + win_skip_and_not_fullscreen( focusable_window ) || + focusable_window->xwayland( ).a.override_redirect ) + continue; + + unsigned int unAppID = focusable_window->appID; + if ( unAppID != 0 ) + { + unsigned long j; + for ( j = 0; j < focusable_appids.size( ); j++ ) + { + if ( focusable_appids[ j ] == unAppID ) { break; } + } + if ( j == focusable_appids.size( ) ) + { + focusable_appids.push_back( unAppID ); + } + } + + // list of [window, appid, pid] triplets + focusable_windows.push_back( focusable_window->xwayland( ).id ); + focusable_windows.push_back( focusable_window->appID ); + focusable_windows.push_back( focusable_window->pid ); + } + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeFocusableAppsAtom, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )focusable_appids.data( ), + focusable_appids.size( ) ); + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeFocusableWindowsAtom, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )focusable_windows.data( ), + focusable_windows.size( ) ); + + gameFocused = pick_primary_focus_and_override( + pFocus, + root_ctx->focusControlWindow, + vecPossibleFocusWindows, + true, + vecFocuscontrolAppIDs, + pFocus->ulVirtualFocusKey, + gamescope::cv_backend_virtual_connector_strategy ); + + // Pick overlay/notifications from root ctx + pFocus->overlayWindow = root_ctx->focus.overlayWindow; + pFocus->externalOverlayWindow = root_ctx->focus.externalOverlayWindow; + pFocus->notificationWindow = root_ctx->focus.notificationWindow; + + if ( !pFocus->overlayWindow ) + { + pFocus->overlayWindow = g_steamcompmgr_xdg_focus.overlayWindow; + } + + if ( !pFocus->externalOverlayWindow ) + { + pFocus->externalOverlayWindow = + g_steamcompmgr_xdg_focus.externalOverlayWindow; + } + + bool bUseOverlay = + gamescope::VirtualConnectorIsSingleOutput( ) || + gamescope::VirtualConnectorKeyIsSteam( pFocus->ulVirtualFocusKey ); + if ( !bUseOverlay ) + { + pFocus->overlayWindow = nullptr; + pFocus->notificationWindow = nullptr; + } + + // Pick inputFocusWindow + if ( gamescope::VirtualConnectorIsSingleOutput( ) && + pFocus->overlayWindow && pFocus->overlayWindow->inputFocusMode ) + { + pFocus->inputFocusWindow = pFocus->overlayWindow; + pFocus->keyboardFocusWindow = pFocus->overlayWindow; + } + else + { + pFocus->inputFocusWindow = pFocus->focusWindow; + pFocus->keyboardFocusWindow = pFocus->overrideWindow + ? pFocus->overrideWindow + : pFocus->focusWindow; + } + + if ( !gamescope::VirtualConnectorIsSingleOutput( ) ) + { + uint64_t ulFocusedKeyboardOverlayVR = g_FocusedVROverlayKeyboard; + uint64_t ulFocusedMouseOverlayVR = g_FocusedVROverlayMouse; + + focus_log.debugf( + "Current focus VR overlays: keyboard 0x%lx | mouse 0x%lx", + ulFocusedKeyboardOverlayVR, + ulFocusedMouseOverlayVR ); + + if ( ulFocusedKeyboardOverlayVR || ulFocusedMouseOverlayVR ) + { + for ( steamcompmgr_win_t *queryWindow = root_ctx->list; queryWindow; + queryWindow = queryWindow->xwayland( ).next ) + { + if ( queryWindow->oulTargetVROverlay && + *queryWindow->oulTargetVROverlay == + ulFocusedKeyboardOverlayVR ) + { + focus_log.debugf( + "[WL GLOBAL] Overriding keyboard focus window with VR " + "forwarder overlay! Overlay: 0x%lx XWindow: 0x%x " + "Title: %s", + ulFocusedKeyboardOverlayVR, + queryWindow->id( ), + queryWindow->debug_name( ) ); + pFocus->keyboardFocusWindow = queryWindow; + + pFocus->overrideWindow = nullptr; + } + + if ( queryWindow->oulTargetVROverlay && + *queryWindow->oulTargetVROverlay == + ulFocusedMouseOverlayVR ) + { + // We don't want to do any mouse input for target VR + // overlays right now. SteamWebHelper is handling this. + + // pFocus->inputFocusWindow = queryWindow; + + pFocus->overrideWindow = nullptr; + } + } + } + } + + // Pick cursor from our input focus window + + // Initially pick cursor from the ctx of our input focus. + if ( pFocus->inputFocusWindow ) + { + if ( pFocus->inputFocusWindow->type == + steamcompmgr_win_type_t::XWAYLAND ) + pFocus->cursor = + pFocus->inputFocusWindow->xwayland( ).ctx->cursor.get( ); + else + { + // TODO XDG: + // Implement cursor support. + // Probably want some form of abstraction here for + // wl cursor vs x11 cursor given we have virtual cursors. + // wlserver should update wl cursor pos xy directly. + static bool s_once = false; + if ( !s_once ) + { + xwm_log.errorf( "NO CURSOR IMPL XDG" ); + s_once = true; + } + } + } + + if ( pFocus->inputFocusWindow ) + pFocus->inputFocusMode = pFocus->inputFocusWindow->inputFocusMode; + + if ( gamescope::VirtualConnectorIsSingleOutput( ) && + pFocus->inputFocusMode == 2 ) + { + pFocus->keyboardFocusWindow = pFocus->overrideWindow + ? pFocus->overrideWindow + : pFocus->focusWindow; + } + + // TODO(strategy): multi-seat on Wayland side + if ( pFocus == GetCurrentFocus( ) ) + { + static gamescope::VirtualConnectorKey_t s_ulPreviousGlobalFocusKey; + + // Tell wlserver about our keyboard/mouse focus. + if ( pFocus->keyboardFocusWindow != + previousLocalFocus.keyboardFocusWindow || + pFocus->ulVirtualFocusKey != s_ulPreviousGlobalFocusKey ) + { + if ( win_surface( pFocus->inputFocusWindow ) != nullptr || + win_surface( pFocus->keyboardFocusWindow ) != nullptr ) + { + wlserver_lock( ); + + if ( win_surface( pFocus->keyboardFocusWindow ) != nullptr ) + { + wlserver_keyboardfocus( + pFocus->keyboardFocusWindow->main_surface( ) ); + focus_log.debugf( + "Setting keyboard focus to: %s (%x)", + pFocus->keyboardFocusWindow->debug_name( ), + pFocus->keyboardFocusWindow->id( ) ); + } + + wlserver_unlock( ); + } + } + + if ( pFocus->inputFocusWindow ) + { + // Cannot simply XWarpPointer here as we immediately go on to + // do wlserver_mousefocus and need to update m_x and m_y of the + // cursor. + if ( pFocus->inputFocusWindow->GetFocus( )->bResetToCorner ) + { + wlserver_lock( ); + wlserver_mousewarp( + pFocus->inputFocusWindow->GetGeometry( ).nWidth / 2, + pFocus->inputFocusWindow->GetGeometry( ).nHeight / 2, + 0, + true ); + wlserver_fake_mouse_pos( + pFocus->inputFocusWindow->GetGeometry( ).nWidth - 1, + pFocus->inputFocusWindow->GetGeometry( ).nHeight - 1 ); + wlserver_unlock( ); + } + else if ( pFocus->inputFocusWindow->GetFocus( )->bResetToCenter ) + { + wlserver_lock( ); + wlserver_mousewarp( + pFocus->inputFocusWindow->GetGeometry( ).nWidth / 2, + pFocus->inputFocusWindow->GetGeometry( ).nHeight / 2, + 0, + true ); + wlserver_unlock( ); + } + + pFocus->inputFocusWindow->GetFocus( )->bResetToCorner = false; + pFocus->inputFocusWindow->GetFocus( )->bResetToCenter = false; + } + + s_ulPreviousGlobalFocusKey = pFocus->ulVirtualFocusKey; + } + + if ( pFocus == GetCurrentMouseFocus( ) ) + { + static gamescope::VirtualConnectorKey_t s_ulPreviousGlobalFocusKey; + + // Tell wlserver about our keyboard/mouse focus. + if ( pFocus->inputFocusWindow != previousLocalFocus.inputFocusWindow || + pFocus->overrideWindow != previousLocalFocus.overrideWindow || + pFocus->ulVirtualFocusKey != s_ulPreviousGlobalFocusKey ) + { + if ( win_surface( pFocus->inputFocusWindow ) != nullptr ) + { + wlserver_lock( ); + + wlserver_clear_dropdowns( ); + if ( win_surface( pFocus->overrideWindow ) != nullptr ) + wlserver_notify_dropdown( + pFocus->overrideWindow->main_surface( ), + pFocus->overrideWindow->xwayland( ).a.x, + pFocus->overrideWindow->xwayland( ).a.y ); + + if ( gamescope::VirtualConnectorInSteamPerAppState( ) && + pFocus->inputFocusWindow ) + { + if ( pFocus->inputFocusWindow->type == + steamcompmgr_win_type_t::XWAYLAND ) + { + xwayland_ctx_t *ctx = + pFocus->inputFocusWindow->xwayland( ).ctx; + bool bTouchPointerEmulation = + gamescope::VirtualConnectorKeyIsNonSteamWindow( + pFocus->ulVirtualFocusKey ); + + if ( ctx->bTouchPointerEmulation != + bTouchPointerEmulation ) + { + xwm_log.infof( + "Changing touch pointer emulation for display " + "%u to %s\n", + ctx->xwayland_server->get_index( ), + bTouchPointerEmulation ? "true" : "false" ); + + uint32_t uValue = bTouchPointerEmulation ? 1 : 0; + XChangeProperty( + ctx->dpy, + ctx->root, + ctx->atoms.steamosTouchPointerEmulation, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&uValue, + 1 ); + ctx->bTouchPointerEmulation = + bTouchPointerEmulation; + } + } + } + + if ( win_surface( pFocus->inputFocusWindow ) != nullptr && + pFocus->cursor ) + { + wlserver_mousefocus( + pFocus->inputFocusWindow->main_surface( ), + pFocus->cursor->x( ), + pFocus->cursor->y( ) ); + focus_log.debugf( + "Setting mouse focus to: %s (%x)", + pFocus->inputFocusWindow->debug_name( ), + pFocus->inputFocusWindow->id( ) ); + } + + wlserver_unlock( ); + } + + // Hide cursor on transitioning between xwaylands + // We already do this when transitioning input focus inside of an + // xwayland ctx. + // don't care if we change kb focus window due to that happening + // when going from override -> focus and we don't want to hide then + // as it's probably a dropdown. + if ( pFocus->cursor && pFocus->inputFocusWindow != + previousLocalFocus.inputFocusWindow ) + pFocus->cursor->hide( ); + } + + if ( pFocus->inputFocusWindow ) + { + // Cannot simply XWarpPointer here as we immediately go on to + // do wlserver_mousefocus and need to update m_x and m_y of the + // cursor. + if ( pFocus->inputFocusWindow->GetFocus( )->bResetToCorner ) + { + wlserver_lock( ); + wlserver_mousewarp( + pFocus->inputFocusWindow->GetGeometry( ).nWidth / 2, + pFocus->inputFocusWindow->GetGeometry( ).nHeight / 2, + 0, + true ); + wlserver_fake_mouse_pos( + pFocus->inputFocusWindow->GetGeometry( ).nWidth - 1, + pFocus->inputFocusWindow->GetGeometry( ).nHeight - 1 ); + wlserver_unlock( ); + } + else if ( pFocus->inputFocusWindow->GetFocus( )->bResetToCenter ) + { + wlserver_lock( ); + wlserver_mousewarp( + pFocus->inputFocusWindow->GetGeometry( ).nWidth / 2, + pFocus->inputFocusWindow->GetGeometry( ).nHeight / 2, + 0, + true ); + wlserver_unlock( ); + } + + pFocus->inputFocusWindow->GetFocus( )->bResetToCorner = false; + pFocus->inputFocusWindow->GetFocus( )->bResetToCenter = false; + } + + s_ulPreviousGlobalFocusKey = pFocus->ulVirtualFocusKey; + } + + pid_t newFocusedWindowPID = + pFocus->focusWindow ? pFocus->focusWindow->pid : 0; + if ( sdFocusWindow_pid != newFocusedWindowPID ) + { + const char *unfocusedWindowCGroup = + cgroupPathFromPID( sdFocusWindow_pid ); + const char *focusedWindowCGroup = + cgroupPathFromPID( newFocusedWindowPID ); + bool sameUnit = unfocusedWindowCGroup && focusedWindowCGroup && + !strcmp( unfocusedWindowCGroup, focusedWindowCGroup ); + + if ( unfocusedWindowCGroup && !sameUnit ) + setDMemMemoryLow( unfocusedWindowCGroup, false ); + if ( focusedWindowCGroup && !sameUnit ) + setDMemMemoryLow( focusedWindowCGroup, true ); + } + + if ( pFocus->pVirtualConnector && pFocus->inputFocusWindow ) + { + pFocus->pVirtualConnector->SetProperty( + gamescope::ConnectorProperty::IsFileBrowser, + pFocus->inputFocusWindow->bIsDolphin ); + } + + // Backchannel to Steam + unsigned long focusedWindow = 0; + unsigned long focusedAppId = 0; + unsigned long focusedBaseAppId = 0; + const char *focused_display = + root_ctx->xwayland_server->get_nested_display_name( ); + const char *focused_keyboard_display = + root_ctx->xwayland_server->get_nested_display_name( ); + const char *focused_mouse_display = + root_ctx->xwayland_server->get_nested_display_name( ); + + if ( pFocus->focusWindow ) + { + focusedWindow = ( unsigned long )pFocus->focusWindow->id( ); + focusedBaseAppId = pFocus->focusWindow->appID; + focusedAppId = pFocus->inputFocusWindow->appID; + focused_display = get_win_display_name( pFocus->focusWindow ); + sdFocusWindow_pid = pFocus->focusWindow->pid; + } + + g_focusedBaseAppId = ( uint32_t )focusedAppId; + + if ( pFocus->inputFocusWindow ) + { + focused_mouse_display = + get_win_display_name( pFocus->inputFocusWindow ); + } + + if ( pFocus->keyboardFocusWindow ) + { + focused_keyboard_display = + get_win_display_name( pFocus->keyboardFocusWindow ); + } + + if ( pFocus == GetCurrentFocus( ) ) + { + if ( steamMode ) + { + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeFocusedAppAtom, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&focusedAppId, + focusedAppId != 0 ? 1 : 0 ); + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeFocusedAppGfxAtom, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&focusedBaseAppId, + focusedBaseAppId != 0 ? 1 : 0 ); + } + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeFocusedWindowAtom, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&focusedWindow, + focusedWindow != 0 ? 1 : 0 ); + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeFocusDisplay, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )focused_display, + strlen( focused_display ) + 1 ); + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeMouseFocusDisplay, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )focused_mouse_display, + strlen( focused_mouse_display ) + 1 ); + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeKeyboardFocusDisplay, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )focused_keyboard_display, + strlen( focused_keyboard_display ) + 1 ); + + XFlush( root_ctx->dpy ); + } + + // Sort out fading. + if ( pFocus->focusWindow && + previousLocalFocus.focusWindow != pFocus->focusWindow ) + { + bool bDoFade = win_has_game_id( pFocus->focusWindow ); + + if ( g_FadeOutDuration != 0 && !g_bFirstFrame && bDoFade ) + { + if ( g_HeldCommits[ HELD_COMMIT_FADE ] == nullptr ) + { + pFocus->fadeWindow = previousLocalFocus.focusWindow; + g_HeldCommits[ HELD_COMMIT_FADE ] = + g_HeldCommits[ HELD_COMMIT_BASE ]; + g_bPendingFade = true; + } + else + { + // If we end up fading back to what we were going to fade to, + // cancel the fade. + if ( pFocus->fadeWindow != nullptr && + pFocus->focusWindow == pFocus->fadeWindow ) + { + g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; + g_bPendingFade = false; + fadeOutStartTime = 0; + pFocus->fadeWindow = nullptr; + } + } + } + } + + if ( !cv_paint_debug_pause_base_plane ) + { + // Update last focus commit + if ( pFocus->focusWindow && + previousLocalFocus.focusWindow != pFocus->focusWindow && + !pFocus->focusWindow->isSteamStreamingClient ) + { + get_window_last_done_commit( + pFocus->focusWindow, g_HeldCommits[ HELD_COMMIT_BASE ] ); + } + } + + // Set SDL window title + if ( pFocus->GetNestedHints( ) ) + { + if ( pFocus->focusWindow ) + { + pFocus->GetNestedHints( )->SetVisible( true ); + if ( previousLocalFocus.focusWindow != pFocus->focusWindow ) + { + pFocus->GetNestedHints( )->SetTitle( + pFocus->focusWindow->title ); + pFocus->GetNestedHints( )->SetIcon( pFocus->focusWindow->icon ); + } + } + else + { + pFocus->GetNestedHints( )->SetVisible( false ); + } + } + + // Some games such as Disgaea PC (405900) don't take controller input until + // the window is first clicked on despite it having focus. + if ( pFocus->inputFocusWindow && pFocus->inputFocusWindow->appID == 405900 ) + { + auto now = get_time_in_milliseconds( ); + + wlserver_lock( ); + wlserver_touchdown( 0.5, 0.5, 0, now ); + wlserver_touchup( 0, now + 1 ); + wlserver_mousehide( ); + wlserver_unlock( ); + } + + pFocus->ulCurrentFocusSerial = GetFocusSerial( ); +} + +static void get_win_type( xwayland_ctx_t *ctx, steamcompmgr_win_t *w ) +{ + w->is_dialog = !!w->xwayland( ).transientFor; + + std::vector atoms; + if ( get_prop( ctx, w->xwayland( ).id, ctx->atoms.winTypeAtom, atoms ) ) + { + for ( unsigned int atom : atoms ) + { + if ( atom == ctx->atoms.winDialogAtom ) { w->is_dialog = true; } + if ( atom == ctx->atoms.winNormalAtom ) { w->is_dialog = false; } + } + } +} + +static void get_size_hints( xwayland_ctx_t *ctx, steamcompmgr_win_t *w ) +{ + XSizeHints hints; + long hintsSpecified = 0; + + XGetWMNormalHints( ctx->dpy, w->xwayland( ).id, &hints, &hintsSpecified ); + + const bool bHasPositionAndGravityHints = + ( hintsSpecified & ( PPosition | PWinGravity ) ) == + ( PPosition | PWinGravity ); + if ( bHasPositionAndGravityHints && hints.x >= 0 && hints.y >= 0 && + hints.win_gravity == StaticGravity ) + { + w->maybe_a_dropdown = true; + } + else + { + w->maybe_a_dropdown = false; + } + + if ( hintsSpecified & ( PMaxSize | PMinSize ) && hints.max_width && + hints.max_height && hints.min_width && hints.min_height && + hints.max_width == hints.min_width && + hints.min_height == hints.max_height ) + { + w->requestedWidth = hints.max_width; + w->requestedHeight = hints.max_height; + + w->sizeHintsSpecified = true; + } + else + { + w->sizeHintsSpecified = false; + + // Below block checks for a pattern that matches old SDL fullscreen + // applications; SDL creates a fullscreen overrride-redirect window and + // reparents the game window under it, centered. We get rid of the + // modeswitch and also want that black border gone. + if ( w->xwayland( ).a.override_redirect ) + { + Window root_return = None, parent_return = None; + Window *children = NULL; + unsigned int nchildren = 0; + + XQueryTree( + ctx->dpy, + w->xwayland( ).id, + &root_return, + &parent_return, + &children, + &nchildren ); + + if ( nchildren == 1 ) + { + XWindowAttributes attribs; + + XGetWindowAttributes( ctx->dpy, children[ 0 ], &attribs ); + + // If we have a unique children that isn't override-reidrect + // that is contained inside this fullscreen window, it's + // probably it. + if ( attribs.override_redirect == false && + attribs.width <= w->GetGeometry( ).nWidth && + attribs.height <= w->GetGeometry( ).nHeight ) + { + w->sizeHintsSpecified = true; + + w->requestedWidth = attribs.width; + w->requestedHeight = attribs.height; + + XMoveWindow( ctx->dpy, children[ 0 ], 0, 0 ); + + w->ignoreOverrideRedirect = true; + } + } + + XFree( children ); + } + } +} - m_hotspotX = image->xhot; - m_hotspotY = image->yhot; +static void +get_win_title( xwayland_ctx_t *ctx, steamcompmgr_win_t *w, Atom atom ) +{ + assert( atom == XA_WM_NAME || atom == ctx->atoms.netWMNameAtom ); + + // Allocates a title we are meant to free, + // let's re-use this allocation for w->title :) + XTextProperty tp; + XGetTextProperty( ctx->dpy, w->xwayland( ).id, &tp, atom ); + + bool is_utf8; + if ( tp.encoding == ctx->atoms.utf8StringAtom ) { is_utf8 = true; } + else if ( tp.encoding == XA_STRING ) { is_utf8 = false; } + else + { + return; + } + + if ( !is_utf8 && w->utf8_title ) + { + /* Clients usually set both the non-UTF8 title and the UTF8 title + * properties. If the client has set the UTF8 title prop, ignore the + * non-UTF8 one. */ + return; + } + + if ( tp.nitems > 0 ) + { + // Ride off the allocation from XGetTextProperty. + w->title = std::make_shared( ( const char * )tp.value ); + } + else + { + w->title = NULL; + } + w->utf8_title = is_utf8; +} + +static void get_net_wm_state( xwayland_ctx_t *ctx, steamcompmgr_win_t *w ) +{ + Atom type; + int format; + unsigned long nitems; + unsigned long bytesAfter; + unsigned char *data; + if ( XGetWindowProperty( + ctx->dpy, + w->xwayland( ).id, + ctx->atoms.netWMStateAtom, + 0, + 2048, + false, + AnyPropertyType, + &type, + &format, + &nitems, + &bytesAfter, + &data ) != Success ) + { + return; + } + + Atom *props = ( Atom * )data; + for ( size_t i = 0; i < nitems; i++ ) + { + if ( props[ i ] == ctx->atoms.netWMStateFullscreenAtom ) + { + w->isFullscreen = true; + } + else if ( props[ i ] == ctx->atoms.netWMStateSkipTaskbarAtom ) + { + w->skipTaskbar = true; + } + else if ( props[ i ] == ctx->atoms.netWMStateSkipPagerAtom ) + { + w->skipPager = true; + } + else + { + xwm_log.debugf( + "Unhandled initial NET_WM_STATE property: %s", + XGetAtomName( ctx->dpy, props[ i ] ) ); + } + } + + XFree( data ); +} + +static void get_win_icon( xwayland_ctx_t *ctx, steamcompmgr_win_t *w ) +{ + w->icon = std::make_shared>( ); + get_prop( ctx, w->xwayland( ).id, ctx->atoms.netWMIcon, *w->icon.get( ) ); +} + +static void handle_desktop_window( steamcompmgr_win_t *w ) +{ + if ( !w ) return; + + if ( win_has_game_id( w ) || w->bIsSteamPid || w->bIsSteamWebHelperPid || + w->bIsVRWebHelperPid ) + return; + + if ( w->type != steamcompmgr_win_type_t::XWAYLAND ) return; + + if ( w->xwayland( ).a.override_redirect || + ( w->oulTargetVROverlay && !cv_vr_show_forwarded_overlays ) ) + return; + + if ( win_maybe_a_dropdown( w ) || win_is_useless( w ) ) return; + + xwayland_ctx_t *ctx = w->xwayland( ).ctx; + + if ( w->sizeHintsSpecified && + !( window_is_fullscreen( w ) || ctx->force_windows_fullscreen ) ) + { + if ( ( unsigned )w->GetGeometry( ).nWidth != w->requestedWidth || + ( unsigned )w->GetGeometry( ).nHeight != w->requestedHeight ) + { + XResizeWindow( + ctx->dpy, + w->xwayland( ).id, + w->requestedWidth, + w->requestedHeight ); + } + } + else + { + int fs_width = ctx->root_width; + int fs_height = ctx->root_height; + + if ( w->GetGeometry( ).nWidth != fs_width || + w->GetGeometry( ).nHeight != fs_height ) + { + XResizeWindow( ctx->dpy, w->xwayland( ).id, fs_width, fs_height ); + } + } +} + +static void map_win( xwayland_ctx_t *ctx, Window id, unsigned long sequence ) +{ + steamcompmgr_win_t *w = find_win( ctx, id ); + + if ( !w ) return; + + w->xwayland( ).a.map_state = IsViewable; + + /* This needs to be here or else we lose transparency messages */ + XSelectInput( + ctx->dpy, + id, + PropertyChangeMask | SubstructureNotifyMask | LeaveWindowMask | + FocusChangeMask ); + + XFlush( ctx->dpy ); + + /* This needs to be here since we don't get PropertyNotify when unmapped */ + w->opacity = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.opacityAtom, OPAQUE ); + + w->isSteamLegacyBigPicture = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.steamAtom, 0 ); + + /* First try to read the UTF8 title prop, then fallback to the non-UTF8 one + */ + get_win_title( ctx, w, ctx->atoms.netWMNameAtom ); + get_win_title( ctx, w, XA_WM_NAME ); + get_win_icon( ctx, w ); - int nDesiredWidth = image->width; - int nDesiredHeight = image->height; - if ( g_nCursorScaleHeight > 0 ) - { - GetDesiredSize( nDesiredWidth, nDesiredHeight ); - } + w->inputFocusMode = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.steamInputFocusAtom, 0 ); - uint32_t surfaceWidth; - uint32_t surfaceHeight; - glm::uvec2 surfaceSize = GetBackend()->CursorSurfaceSize( glm::uvec2{ (uint32_t)nDesiredWidth, (uint32_t)nDesiredHeight } ); - surfaceWidth = surfaceSize.x; - surfaceHeight = surfaceSize.y; + w->isSteamStreamingClient = get_prop( + ctx, w->xwayland( ).id, ctx->atoms.steamStreamingClientAtom, 0 ); + w->isSteamStreamingClientVideo = get_prop( + ctx, w->xwayland( ).id, ctx->atoms.steamStreamingClientVideoAtom, 0 ); - m_texture = nullptr; + if ( steamMode == true ) + { + uint32_t appID = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.gameAtom, 0 ); - // Assume the cursor is fully translucent unless proven otherwise. - bool bNoCursor = true; + if ( w->appID != 0 && appID != 0 && w->appID != appID ) + { + xwm_log.errorf( "appid clash was %u now %u", w->appID, appID ); + } + // Let the appID property be authoritative for now + if ( appID != 0 ) { w->appID = appID; } + } + else + { + w->appID = w->xwayland( ).id; + } - std::vector cursorBuffer; + if ( w->isSteamLegacyBigPicture ) w->appID = 769; - int nContentWidth = image->width; - int nContentHeight = image->height; + w->isOverlay = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.overlayAtom, 0 ); + w->isExternalOverlay = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.externalOverlayAtom, 0 ); - if (image->width && image->height) - { - if ( nDesiredWidth < image->width || nDesiredHeight < image->height ) - { - std::vector pixels(image->width * image->height); - for (int i = 0; i < image->height; i++) - { - for (int j = 0; j < image->width; j++) - { - pixels[i * image->width + j] = image->pixels[i * image->width + j]; - } - } - std::vector resizeBuffer( nDesiredWidth * nDesiredHeight ); - stbir_resize_uint8_srgb( (unsigned char *)pixels.data(), image->width, image->height, 0, - (unsigned char *)resizeBuffer.data(), nDesiredWidth, nDesiredHeight, 0, - 4, 3, STBIR_FLAG_ALPHA_PREMULTIPLIED ); - - cursorBuffer = std::vector(surfaceWidth * surfaceHeight); - for (int i = 0; i < nDesiredHeight; i++) - { - for (int j = 0; j < nDesiredWidth; j++) - { - cursorBuffer[i * surfaceWidth + j] = resizeBuffer[i * nDesiredWidth + j]; - } - } + // misyl: Disable appID for overlay types, as parts of the code don't expect + // that focus-wise. Fixes mangoapp usage when nested, and not in SteamOS. + if ( w->isExternalOverlay ) w->appID = 0; - m_hotspotX = ( m_hotspotX * nDesiredWidth ) / image->width; - m_hotspotY = ( m_hotspotY * nDesiredHeight ) / image->height; + w->oulTargetVROverlay = get_u64_prop( + ctx, w->xwayland( ).id, ctx->atoms.steamGamescopeVROverlayTarget ); + if ( w->oulTargetVROverlay ) + { + g_bUpdateForwardedVROverlays = true; + w->bNeedsForwarding = true; + } + w->pForwarderPlane = nullptr; - nContentWidth = nDesiredWidth; - nContentHeight = nDesiredHeight; - } - else - { - cursorBuffer = std::vector(surfaceWidth * surfaceHeight); - for (int i = 0; i < image->height; i++) - { - for (int j = 0; j < image->width; j++) - { - cursorBuffer[i * surfaceWidth + j] = image->pixels[i * image->width + j]; - } - } - } - } + get_size_hints( ctx, w ); - for (uint32_t i = 0; i < surfaceHeight; i++) - { - for (uint32_t j = 0; j < surfaceWidth; j++) - { - if ( cursorBuffer[i * surfaceWidth + j] & 0xff000000 ) - { - bNoCursor = false; - break; - } - } - } + get_net_wm_state( ctx, w ); - if (bNoCursor) - cursorBuffer.clear(); + XWMHints *wmHints = XGetWMHints( ctx->dpy, w->xwayland( ).id ); - m_imageEmpty = bNoCursor; + if ( wmHints != nullptr ) + { + if ( wmHints->flags & ( InputHint | StateHint ) && + wmHints->input == true && wmHints->initial_state == NormalState ) + { + XRaiseWindow( ctx->dpy, w->xwayland( ).id ); + } - m_dirty = false; - updateCursorFeedback(); + XFree( wmHints ); + } - if (m_imageEmpty) { - if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetNestedHints() ) - GetBackend()->GetCurrentConnector()->GetNestedHints()->SetCursorImage( nullptr ); - return false; - } + Window transientFor = None; + if ( XGetTransientForHint( ctx->dpy, w->xwayland( ).id, &transientFor ) ) + { + w->xwayland( ).transientFor = transientFor; + } + else + { + w->xwayland( ).transientFor = None; + } - CVulkanTexture::createFlags texCreateFlags; - texCreateFlags.bFlippable = true; - if ( GetBackend()->SupportsPlaneHardwareCursor() ) - { - texCreateFlags.bLinear = true; // cursor buffer needs to be linear - // TODO: choose format & modifiers from cursor plane - } + get_win_type( ctx, w ); - m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer.data()); + w->xwayland( ).damage_sequence = 0; + w->xwayland( ).map_sequence = sequence; - if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetNestedHints() ) - { - auto info = std::make_shared( - gamescope::INestedHints::CursorInfo - { - .pPixels = std::move( cursorBuffer ), - .uWidth = (uint32_t) nDesiredWidth, - .uHeight = (uint32_t) nDesiredHeight, - .uXHotspot = image->xhot, - .uYHotspot = image->yhot, - }); - GetBackend()->GetCurrentConnector()->GetNestedHints()->SetCursorImage( std::move( info ) ); - } + if ( w == ctx->focus.inputFocusWindow || + w->xwayland( ).id == ctx->currentKeyboardFocusWindow ) + { + XSetInputFocus( + ctx->dpy, w->xwayland( ).id, RevertToNone, CurrentTime ); + } - assert(m_texture); - XFree(image); + handle_desktop_window( w ); - return true; + MakeFocusDirty( ); + + set_wm_state( ctx, w->xwayland( ).id, ICCCM_NORMAL_STATE ); } -void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) +static void finish_unmap_win( xwayland_ctx_t *ctx, steamcompmgr_win_t *w ) { - int nSize = g_nBaseCursorScale; - if ( g_nCursorScaleHeight > 0 ) - { - nSize = nSize * floor(g_nOutputHeight / (float)g_nCursorScaleHeight); - nSize = std::clamp( nSize, g_nBaseCursorScale, 256 ); - } + // TODO clear done commits here? - nSize = std::min( nSize, glm::compMin( GetBackend()->CursorSurfaceSize( glm::uvec2{ (uint32_t)nSize, (uint32_t)nSize } ) ) ); + /* don't care about properties anymore */ + XSelectInput( ctx->dpy, w->xwayland( ).id, 0 ); - nWidth = nSize; - nHeight = nSize; + ctx->clipChanged = true; } -void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, struct FrameInfo_t *frameInfo) +static void unmap_win( xwayland_ctx_t *ctx, Window id, bool fade ) { - if ( m_imageEmpty || wlserver.bCursorHidden ) - return; + steamcompmgr_win_t *w = find_win( ctx, id ); + if ( !w ) return; + w->xwayland( ).a.map_state = IsUnmapped; - int winX = x(); - int winY = y(); + MakeFocusDirty( ); - // Also need new texture - if (!getTexture()) { - return; - } + finish_unmap_win( ctx, w ); + set_wm_state( ctx, w->xwayland( ).id, ICCCM_WITHDRAWN_STATE ); +} - int32_t sourceWidth = window->GetGeometry().nWidth; - int32_t sourceHeight = window->GetGeometry().nHeight; +std::string get_name_from_pid( pid_t pid ) +{ + std::string procNameStr; - if ( fit ) - { - // If we have an override window, try to fit it in as long as it won't make our scale go below 1.0. - sourceWidth = std::max( sourceWidth, clamp( fit->GetGeometry().nX + fit->GetGeometry().nWidth, 0, currentOutputWidth ) ); - sourceHeight = std::max( sourceHeight, clamp( fit->GetGeometry().nY + fit->GetGeometry().nHeight, 0, currentOutputHeight ) ); - } + char filename[ 256 ]; - float cursor_scale = 1.0f; - if ( g_nCursorScaleHeight > 0 ) - { - int nDesiredWidth, nDesiredHeight; - GetDesiredSize( nDesiredWidth, nDesiredHeight ); - cursor_scale = nDesiredHeight / (float)m_texture->contentHeight(); - } - cursor_scale = std::max(cursor_scale, 1.0f); + snprintf( filename, sizeof( filename ), "/proc/%i/stat", pid ); + std::ifstream proc_stat_file( filename ); - float scaledX, scaledY; - float currentScaleRatio_x = 1.0; - float currentScaleRatio_y = 1.0; - int cursorOffsetX, cursorOffsetY; + if ( !proc_stat_file.is_open( ) || proc_stat_file.bad( ) ) return ""; - calc_scale_factor(currentScaleRatio_x, currentScaleRatio_y, sourceWidth, sourceHeight); + std::string proc_stat; - cursorOffsetX = (currentOutputWidth - sourceWidth * currentScaleRatio_x) / 2.0f; - cursorOffsetY = (currentOutputHeight - sourceHeight * currentScaleRatio_y) / 2.0f; + std::getline( proc_stat_file, proc_stat ); - // Actual point on scaled screen where the cursor hotspot should be - scaledX = (winX - window->GetGeometry().nX) * currentScaleRatio_x + cursorOffsetX; - scaledY = (winY - window->GetGeometry().nY) * currentScaleRatio_y + cursorOffsetY; + char *procName = nullptr; + char *lastParens = nullptr; - if ( zoomScaleRatio != 1.0 ) - { - scaledX += ((sourceWidth / 2) - winX) * currentScaleRatio_x; - scaledY += ((sourceHeight / 2) - winY) * currentScaleRatio_y; - } + for ( uint32_t i = 0; i < proc_stat.length( ); i++ ) + { + if ( procName == nullptr && proc_stat[ i ] == '(' ) + { + procName = &proc_stat[ i + 1 ]; + } - // Apply the cursor offset inside the texture using the display scale - scaledX = scaledX - (m_hotspotX * cursor_scale); - scaledY = scaledY - (m_hotspotY * cursor_scale); + if ( proc_stat[ i ] == ')' ) { lastParens = &proc_stat[ i ]; } + } - int curLayer = frameInfo->layerCount++; + if ( !lastParens ) return ""; - FrameInfo_t::Layer_t *layer = &frameInfo->layers[ curLayer ]; + *lastParens = '\0'; - layer->opacity = 1.0; + if ( procName ) procNameStr = procName; - layer->scale.x = 1.0f / cursor_scale; - layer->scale.y = 1.0f / cursor_scale; + return procNameStr; +} - layer->offset.x = -scaledX; - layer->offset.y = -scaledY; +uint32_t get_appid_from_pid( pid_t pid ) +{ + uint32_t unFoundAppId = 0; - layer->zpos = g_zposCursor; // cursor, on top of both bottom layers - layer->applyColorMgmt = false; + char filename[ 256 ]; + pid_t next_pid = pid; - layer->tex = m_texture; + while ( 1 ) + { + snprintf( filename, sizeof( filename ), "/proc/%i/stat", next_pid ); + std::ifstream proc_stat_file( filename ); - layer->filter = cursor_scale != 1.0f ? GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NEAREST; - layer->blackBorder = false; - layer->ctm = nullptr; - layer->hdr_metadata_blob = nullptr; - layer->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + if ( !proc_stat_file.is_open( ) || proc_stat_file.bad( ) ) break; - layer->eAlphaBlendingMode = cv_overlay_unmultiplied_alpha ? ALPHA_BLENDING_MODE_COVERAGE : ALPHA_BLENDING_MODE_PREMULTIPLIED; -} + std::string proc_stat; -void MouseCursor::updateCursorFeedback( bool bForce ) -{ - // Can't resolve this until cursor is un-dirtied. - if ( m_dirty && !bForce ) - return; + std::getline( proc_stat_file, proc_stat ); - bool bVisible = !isHidden(); + char *procName = nullptr; + char *lastParens = nullptr; - if ( m_bCursorVisibleFeedback == bVisible && !bForce ) - return; + for ( uint32_t i = 0; i < proc_stat.length( ); i++ ) + { + if ( procName == nullptr && proc_stat[ i ] == '(' ) + { + procName = &proc_stat[ i + 1 ]; + } - uint32_t value = bVisible ? 1 : 0; + if ( proc_stat[ i ] == ')' ) { lastParens = &proc_stat[ i ]; } + } - XChangeProperty(m_ctx->dpy, m_ctx->root, m_ctx->atoms.gamescopeCursorVisibleFeedback, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&value, 1 ); + if ( !lastParens ) break; - m_bCursorVisibleFeedback = bVisible; - m_needs_server_flush = true; -} + *lastParens = '\0'; + char state; + int parent_pid = -1; -struct BaseLayerInfo_t -{ - float scale[2]; - float offset[2]; - float opacity; - GamescopeUpscaleFilter filter; - AlphaBlendingMode_t eAlphaBlendingMode = ALPHA_BLENDING_MODE_PREMULTIPLIED; -}; + sscanf( lastParens + 1, " %c %d", &state, &parent_pid ); -std::array< BaseLayerInfo_t, HELD_COMMIT_COUNT > g_CachedPlanes = {}; + if ( strcmp( "reaper", procName ) == 0 ) + { + snprintf( + filename, sizeof( filename ), "/proc/%i/cmdline", next_pid ); + std::ifstream proc_cmdline_file( filename ); + std::string proc_cmdline; -static void -paint_cached_base_layer(const gamescope::Rc& commit, const BaseLayerInfo_t& base, struct FrameInfo_t *frameInfo, float flOpacityScale, bool bOverrideOpacity ) -{ - int curLayer = frameInfo->layerCount++; + bool bSteamLaunch = false; + uint32_t unAppId = 0; - FrameInfo_t::Layer_t *layer = &frameInfo->layers[ curLayer ]; + std::getline( proc_cmdline_file, proc_cmdline ); - layer->scale.x = base.scale[0]; - layer->scale.y = base.scale[1]; - layer->offset.x = base.offset[0]; - layer->offset.y = base.offset[1]; - layer->opacity = bOverrideOpacity ? flOpacityScale : base.opacity * flOpacityScale; + for ( uint32_t j = 0; j < proc_cmdline.length( ); j++ ) + { + if ( proc_cmdline[ j ] == '\0' && + j + 1 < proc_cmdline.length( ) ) + { + if ( strcmp( "SteamLaunch", &proc_cmdline[ j + 1 ] ) == 0 ) + { + bSteamLaunch = true; + } + else if ( + sscanf( + &proc_cmdline[ j + 1 ], "AppId=%u", &unAppId ) == + 1 && + unAppId != 0 ) + { + if ( bSteamLaunch == true ) { unFoundAppId = unAppId; } + } + else if ( strcmp( "--", &proc_cmdline[ j + 1 ] ) == 0 ) + { + break; + } + } + } + } - layer->colorspace = commit->colorspace(); - layer->hdr_metadata_blob = nullptr; - if (commit->feedback) - { - layer->hdr_metadata_blob = commit->feedback->hdr_metadata_blob; - } - layer->ctm = nullptr; - if (layer->colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB) - layer->ctm = s_scRGB709To2020Matrix; - layer->tex = commit->vulkanTex; + if ( parent_pid == -1 || parent_pid == 0 ) { break; } + else + { + next_pid = parent_pid; + } + } - layer->filter = base.filter; - layer->eAlphaBlendingMode = base.eAlphaBlendingMode; - layer->blackBorder = true; + return unFoundAppId; } -namespace PaintWindowFlag +static pid_t get_win_pid( xwayland_ctx_t *ctx, Window id ) { - static const uint32_t BasePlane = 1u << 0; - static const uint32_t FadeTarget = 1u << 1; - static const uint32_t NotificationMode = 1u << 2; - static const uint32_t DrawBorders = 1u << 3; - static const uint32_t NoScale = 1u << 4; - static const uint32_t NoFilter = 1u << 5; - static const uint32_t CoverageMode = 1u << 6; + XResClientIdSpec client_spec = { + .client = id, + .mask = XRES_CLIENT_ID_PID_MASK, + }; + long num_ids = 0; + XResClientIdValue *client_ids = NULL; + XResQueryClientIds( ctx->dpy, 1, &client_spec, &num_ids, &client_ids ); + + pid_t pid = -1; + for ( long i = 0; i < num_ids; i++ ) + { + pid = XResGetClientPid( &client_ids[ i ] ); + if ( pid > 0 ) break; + } + XResClientIdsDestroy( num_ids, client_ids ); + if ( pid <= 0 ) xwm_log.errorf( "Failed to find PID for window 0x%lx", id ); + return pid; } -using PaintWindowFlags = uint32_t; -wlserver_vk_swapchain_feedback* steamcompmgr_get_base_layer_swapchain_feedback() -{ - if ( g_HeldCommits[ HELD_COMMIT_BASE ] == nullptr ) - return nullptr; +static void +add_win( xwayland_ctx_t *ctx, Window id, Window prev, unsigned long sequence ) +{ + steamcompmgr_win_t *new_win = new steamcompmgr_win_t{}; + steamcompmgr_win_t **p; + + if ( !new_win ) return; + + new_win->seq = ++g_lastWinSeq; + new_win->type = steamcompmgr_win_type_t::XWAYLAND; + new_win->_window_types.emplace( ); + + if ( prev ) + { + for ( p = &ctx->list; *p; p = &( *p )->xwayland( ).next ) + if ( ( *p )->xwayland( ).id == prev ) break; + } + else + p = &ctx->list; + new_win->xwayland( ).id = id; + if ( !XGetWindowAttributes( ctx->dpy, id, &new_win->xwayland( ).a ) ) + { + delete new_win; + return; + } + + new_win->xwayland( ).ctx = ctx; + new_win->xwayland( ).damage_sequence = 0; + new_win->xwayland( ).map_sequence = 0; + if ( new_win->xwayland( ).a.c_class == InputOnly ) + new_win->xwayland( ).damage = None; + else + { + new_win->xwayland( ).damage = + XDamageCreate( ctx->dpy, id, XDamageReportRawRectangles ); + } + new_win->opacity = OPAQUE; + + if ( useXRes == true ) { new_win->pid = get_win_pid( ctx, id ); } + else + { + new_win->pid = -1; + } + + new_win->isOverlay = false; + new_win->isExternalOverlay = false; + new_win->isSteamLegacyBigPicture = false; + new_win->isSteamStreamingClient = false; + new_win->isSteamStreamingClientVideo = false; + new_win->inputFocusMode = 0; + new_win->is_dialog = false; + new_win->maybe_a_dropdown = false; + + new_win->hasHwndStyle = false; + new_win->hwndStyle = 0; + new_win->hasHwndStyleEx = false; + new_win->hwndStyleEx = 0; + + if ( steamMode == true ) + { + if ( new_win->pid != -1 ) + { + new_win->appID = get_appid_from_pid( new_win->pid ); + } + else + { + new_win->appID = 0; + } + } + else + { + new_win->appID = id; + } + + if ( new_win->isExternalOverlay ) new_win->appID = 0; + + std::string pid_name = get_name_from_pid( new_win->pid ); + new_win->pid_name = pid_name; + if ( pid_name == "steam" ) new_win->bIsSteamPid = true; + if ( pid_name == "steamwebhelper" ) new_win->bIsSteamWebHelperPid = true; + if ( pid_name == "vrwebhelper" ) new_win->bIsVRWebHelperPid = true; + if ( pid_name == "dolphin" ) new_win->bIsDolphin = true; + + Window transientFor = None; + if ( XGetTransientForHint( ctx->dpy, id, &transientFor ) ) + { + new_win->xwayland( ).transientFor = transientFor; + } + else + { + new_win->xwayland( ).transientFor = None; + } + + get_win_type( ctx, new_win ); + + new_win->title = NULL; + new_win->utf8_title = false; + + new_win->isFullscreen = false; + new_win->isSysTrayIcon = false; + new_win->sizeHintsSpecified = false; + new_win->skipTaskbar = false; + new_win->skipPager = false; + new_win->requestedWidth = 0; + new_win->requestedHeight = 0; + new_win->nudged = false; + new_win->ignoreOverrideRedirect = false; + + wlserver_x11_surface_info_init( + &new_win->xwayland( ).surface, ctx->xwayland_server, id ); + + { + std::unique_lock lock( ctx->list_mutex ); + new_win->xwayland( ).next = *p; + *p = new_win; + } + if ( new_win->xwayland( ).a.map_state == IsViewable ) + map_win( ctx, id, sequence ); + + MakeFocusDirty( ); +} - if ( !g_HeldCommits[ HELD_COMMIT_BASE ]->feedback ) - return nullptr; +static void +restack_win( xwayland_ctx_t *ctx, steamcompmgr_win_t *w, Window new_above ) +{ + Window old_above; + + if ( w->xwayland( ).next ) old_above = w->xwayland( ).next->xwayland( ).id; + else + old_above = None; + if ( old_above != new_above ) + { + std::unique_lock lock( ctx->list_mutex ); - return &(*g_HeldCommits[ HELD_COMMIT_BASE ]->feedback); -} + steamcompmgr_win_t **prev; -gamescope::ConVar cv_paint_debug_pause_base_plane( "paint_debug_pause_base_plane", false, "Pause updates to the base plane." ); + /* unhook */ + for ( prev = &ctx->list; *prev; prev = &( *prev )->xwayland( ).next ) + { + if ( ( *prev ) == w ) break; + } + *prev = w->xwayland( ).next; -static FrameInfo_t::Layer_t * -paint_window_commit( const gamescope::Rc &lastCommit, steamcompmgr_win_t *w, steamcompmgr_win_t *scaleW, struct FrameInfo_t *frameInfo, - MouseCursor *cursor, PaintWindowFlags flags = 0, float flOpacityScale = 1.0f, steamcompmgr_win_t *fit = nullptr ) + /* rehook */ + for ( prev = &ctx->list; *prev; prev = &( *prev )->xwayland( ).next ) + { + if ( ( *prev )->xwayland( ).id == new_above ) break; + } + w->xwayland( ).next = *prev; + *prev = w; + MakeFocusDirty( ); + } +} + +static void configure_win( xwayland_ctx_t *ctx, XConfigureEvent *ce ) { - int32_t sourceWidth, sourceHeight; - int32_t baseWidth, baseHeight; - int drawXOffset = 0, drawYOffset = 0; - float currentScaleRatio_x = 1.0; - float currentScaleRatio_y = 1.0; - float baseScaleRatio_x = 1.0; - float baseScaleRatio_y = 1.0; + steamcompmgr_win_t *w = find_win( ctx, ce->window ); + + if ( !w || w->xwayland( ).id != ce->window ) + { + if ( ce->window == ctx->root ) + { + ctx->root_width = ce->width; + ctx->root_height = ce->height; + MakeFocusDirty( ); - if ( !GetBackend()->ShouldFitWindows() ) - fit = nullptr; + gamescope_xwayland_server_t *root_server = + wlserver_get_xwayland_server( 0 ); + xwayland_ctx_t *root_ctx = root_server->ctx.get( ); + XDeleteProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeXWaylandModeControl ); + XFlush( root_ctx->dpy ); + } + return; + } + + w->xwayland( ).a.x = ce->x; + w->xwayland( ).a.y = ce->y; + w->xwayland( ).a.width = ce->width; + w->xwayland( ).a.height = ce->height; + w->xwayland( ).a.border_width = ce->border_width; + w->xwayland( ).a.override_redirect = ce->override_redirect; + restack_win( ctx, w, ce->above ); + + MakeFocusDirty( ); +} + +static void circulate_win( xwayland_ctx_t *ctx, XCirculateEvent *ce ) +{ + steamcompmgr_win_t *w = find_win( ctx, ce->window ); + Window new_above; + + if ( !w || w->xwayland( ).id != ce->window ) return; + + if ( ce->place == PlaceOnTop ) new_above = ctx->list->xwayland( ).id; + else + new_above = None; + restack_win( ctx, w, new_above ); + ctx->clipChanged = true; +} + +static void map_request( xwayland_ctx_t *ctx, XMapRequestEvent *mapRequest ) +{ XMapWindow( ctx->dpy, mapRequest->window ); } + +static void configure_request( + xwayland_ctx_t *ctx, XConfigureRequestEvent *configureRequest ) +{ + XWindowChanges changes = { .x = configureRequest->x, + .y = configureRequest->y, + .width = configureRequest->width, + .height = configureRequest->height, + .border_width = configureRequest->border_width, + .sibling = configureRequest->above, + .stack_mode = configureRequest->detail }; + + XConfigureWindow( + ctx->dpy, + configureRequest->window, + configureRequest->value_mask, + &changes ); +} + +static void circulate_request( + xwayland_ctx_t *ctx, XCirculateRequestEvent *circulateRequest ) +{ + XCirculateSubwindows( + ctx->dpy, circulateRequest->window, circulateRequest->place ); +} + +static void finish_destroy_win( xwayland_ctx_t *ctx, Window id, bool gone ) +{ + steamcompmgr_win_t **prev, *w; + + for ( prev = &ctx->list; ( w = *prev ); prev = &w->xwayland( ).next ) + if ( w->xwayland( ).id == id ) + { + if ( gone ) finish_unmap_win( ctx, w ); + + { + std::unique_lock lock( ctx->list_mutex ); + *prev = w->xwayland( ).next; + } + if ( w->xwayland( ).damage != None ) + { + XDamageDestroy( ctx->dpy, w->xwayland( ).damage ); + w->xwayland( ).damage = None; + } + + if ( gone ) + { + // release all commits now we are closed. + w->commit_queue.clear( ); + } + + wlserver_lock( ); + wlserver_x11_surface_info_finish( &w->xwayland( ).surface ); + wlserver_unlock( ); + delete w; + break; + } +} + +static void destroy_win( xwayland_ctx_t *ctx, Window id, bool gone, bool fade ) +{ + // Context + if ( x11_win( ctx->focus.focusWindow ) == id && gone ) + ctx->focus.focusWindow = nullptr; + if ( x11_win( ctx->focus.inputFocusWindow ) == id && gone ) + ctx->focus.inputFocusWindow = nullptr; + if ( x11_win( ctx->focus.overlayWindow ) == id && gone ) + ctx->focus.overlayWindow = nullptr; + if ( x11_win( ctx->focus.externalOverlayWindow ) == id && gone ) + ctx->focus.externalOverlayWindow = nullptr; + if ( x11_win( ctx->focus.notificationWindow ) == id && gone ) + ctx->focus.notificationWindow = nullptr; + if ( x11_win( ctx->focus.overrideWindow ) == id && gone ) + ctx->focus.overrideWindow = nullptr; + if ( ctx->currentKeyboardFocusWindow == id && gone ) + ctx->currentKeyboardFocusWindow = None; + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + // Global Focus + if ( x11_win( pFocus->focusWindow ) == id && gone ) + pFocus->focusWindow = nullptr; + if ( x11_win( pFocus->inputFocusWindow ) == id && gone ) + pFocus->inputFocusWindow = nullptr; + if ( x11_win( pFocus->overlayWindow ) == id && gone ) + pFocus->overlayWindow = nullptr; + if ( x11_win( pFocus->notificationWindow ) == id && gone ) + pFocus->notificationWindow = nullptr; + if ( x11_win( pFocus->overrideWindow ) == id && gone ) + pFocus->overrideWindow = nullptr; + if ( x11_win( pFocus->fadeWindow ) == id && gone ) + pFocus->fadeWindow = nullptr; + } - // Exit out if we have no window or - // no commit. - // - // We may have no commit if we're an overlay, - // in which case, we don't want to add it, - // or in the case of the base plane, this is our - // first ever frame so we have no cached base layer - // to hold on to, so we should not add a layer in that - // instance either. - if (!w || lastCommit == nullptr) - return nullptr; + MakeFocusDirty( ); - // Base plane will stay as tex=0 if we don't have contents yet, which will - // make us fall back to compositing and use the Vulkan null texture - - int curLayer = frameInfo->layerCount++; + finish_destroy_win( ctx, id, gone ); +} + +static void damage_win( xwayland_ctx_t *ctx, XDamageNotifyEvent *de ) +{ + steamcompmgr_win_t *w = find_win( ctx, de->drawable ); + steamcompmgr_win_t *focus = ctx->focus.focusWindow; + + if ( !w ) return; + + if ( w->IsAnyOverlay( ) && !w->opacity ) return; + + bool bHasAppID = w->appID != 0; + + if ( gamescope::cv_backend_virtual_connector_strategy != + gamescope::VirtualConnectorStrategies::SteamControlled ) + { + bHasAppID = true; + } + + bool bCareAboutWindow = true; + + if ( win_is_useless( w ) || w->IsAnyOverlay( ) || + ( w->oulTargetVROverlay && !cv_vr_show_forwarded_overlays ) || + w->isSysTrayIcon || w->xwayland( ).a.map_state != IsViewable ) + { + bCareAboutWindow = false; + } + + // First damage event we get, compute focus; we only want to focus damaged + // windows to have meaningful frames. + /// FIXME APPID FOCUS STRATERGY FOR + if ( bHasAppID && bCareAboutWindow && w->xwayland( ).damage_sequence == 0 ) + MakeFocusDirty( ); + + w->xwayland( ).damage_sequence = damageSequence++; + + // If we just passed the focused window, we might be eliglible to take over + if ( focus && focus != w && bHasAppID && bCareAboutWindow && + w->xwayland( ).damage_sequence > focus->xwayland( ).damage_sequence ) + MakeFocusDirty( ); + + // Josh: This will sometimes cause a BadDamage error. + // I looked around at different compositors to see what + // they do here and they just seem to ignore it. + if ( w->xwayland( ).damage ) + { + XDamageSubtract( ctx->dpy, w->xwayland( ).damage, None, None ); + } + + gpuvis_trace_printf( + "damage_win win %lx appID %u", w->xwayland( ).id, w->appID ); +} + +static void handle_wl_surface_id( + xwayland_ctx_t *ctx, steamcompmgr_win_t *w, uint32_t surfaceID ) +{ + struct wlr_surface *current_surface = NULL; + struct wlr_surface *main_surface = NULL; + + wlserver_lock( ); + + ctx->xwayland_server->set_wl_id( &w->xwayland( ).surface, surfaceID ); + + current_surface = w->xwayland( ).surface.current_surface( ); + main_surface = w->xwayland( ).surface.main_surface; + if ( current_surface == NULL ) + { + wlserver_unlock( ); + return; + } + + global_focus_t *pCurrentFocus = GetCurrentFocus( ); + if ( pCurrentFocus ) + { + // If we already focused on our side and are handling this late, + // let wayland know now. + if ( w == pCurrentFocus->inputFocusWindow ) + wlserver_mousefocus( main_surface, INT32_MAX, INT32_MAX ); + + steamcompmgr_win_t *keyboardFocusWindow = + pCurrentFocus->inputFocusWindow; + + if ( gamescope::VirtualConnectorIsSingleOutput( ) && + keyboardFocusWindow && keyboardFocusWindow->inputFocusMode == 2 ) + keyboardFocusWindow = pCurrentFocus->focusWindow; + + if ( w == keyboardFocusWindow ) wlserver_keyboardfocus( main_surface ); + } + + // Pull the first buffer out of that window, if needed + xwayland_surface_commit( current_surface ); + + wlserver_unlock( ); +} + +static void update_net_wm_state( uint32_t action, bool *value ) +{ + switch ( action ) + { + case NET_WM_STATE_REMOVE: + *value = false; + break; + case NET_WM_STATE_ADD: + *value = true; + break; + case NET_WM_STATE_TOGGLE: + *value = !*value; + break; + default: + xwm_log.debugf( "Unknown NET_WM_STATE action: %" PRIu32, action ); + } +} + +static void handle_net_wm_state( + xwayland_ctx_t *ctx, steamcompmgr_win_t *w, XClientMessageEvent *ev ) +{ + uint32_t action = ( uint32_t )ev->data.l[ 0 ]; + Atom *props = ( Atom * )&ev->data.l[ 1 ]; + for ( size_t i = 0; i < 2; i++ ) + { + if ( props[ i ] == ctx->atoms.netWMStateFullscreenAtom ) + { + update_net_wm_state( action, &w->isFullscreen ); + MakeFocusDirty( ); + } + else if ( props[ i ] == ctx->atoms.netWMStateSkipTaskbarAtom ) + { + update_net_wm_state( action, &w->skipTaskbar ); + MakeFocusDirty( ); + } + else if ( props[ i ] == ctx->atoms.netWMStateSkipPagerAtom ) + { + update_net_wm_state( action, &w->skipPager ); + MakeFocusDirty( ); + } + else if ( props[ i ] != None ) + { + xwm_log.debugf( + "Unhandled NET_WM_STATE property change: %s", + XGetAtomName( ctx->dpy, props[ i ] ) ); + } + } +} - FrameInfo_t::Layer_t *layer = &frameInfo->layers[ curLayer ]; +bool g_bLowLatency = false; - layer->filter = ( flags & PaintWindowFlag::NoFilter ) ? GamescopeUpscaleFilter::LINEAR : g_upscaleFilter; +static void +handle_system_tray_opcode( xwayland_ctx_t *ctx, XClientMessageEvent *ev ) +{ + long opcode = ev->data.l[ 1 ]; - layer->tex = lastCommit->GetTexture( layer->filter, g_upscaleScaler, layer->colorspace ); + switch ( opcode ) + { + case SYSTEM_TRAY_REQUEST_DOCK: + { + Window embed_id = ev->data.l[ 2 ]; - if ( flags & PaintWindowFlag::NoScale ) - { - sourceWidth = currentOutputWidth; - sourceHeight = currentOutputHeight; - } - else - { - // If w == scaleW, then scale the window by the committed buffer size - // instead of the window size. - // - // Some games like Halo Infinite still make swapchains that are eg. - // 3840x2160 on a 720p window if you do borderless fullscreen. - // - // Typically XWayland would do a blit here to avoid that, but when we - // are using the bypass layer, we don't get that, so we need to handle - // this case explicitly. - if (w == scaleW) { - sourceWidth = layer->tex->width(); - sourceHeight = layer->tex->height(); - - baseWidth = lastCommit->vulkanTex->width(); - baseHeight = lastCommit->vulkanTex->height(); - } else { - sourceWidth = scaleW->GetGeometry().nWidth; - sourceHeight = scaleW->GetGeometry().nHeight; - - baseWidth = sourceWidth; - baseHeight = sourceHeight; - } + /* At this point we're supposed to initiate the XEmbed lifecycle by + * sending XEMBED_EMBEDDED_NOTIFY. However we don't actually need to + * render the systray, we just want to recognize and blacklist these + * icons. So for now do nothing. */ - if ( fit ) - { - // If we have an override window, try to fit it in as long as it won't make our scale go below 1.0. - sourceWidth = std::max( sourceWidth, clamp( fit->GetGeometry().nX + fit->GetGeometry().nWidth, 0, currentOutputWidth ) ); - sourceHeight = std::max( sourceHeight, clamp( fit->GetGeometry().nY + fit->GetGeometry().nHeight, 0, currentOutputHeight ) ); + steamcompmgr_win_t *w = find_win( ctx, embed_id ); + if ( w ) { w->isSysTrayIcon = true; } + break; + } + default: + xwm_log.debugf( "Unhandled _NET_SYSTEM_TRAY_OPCODE %ld", opcode ); + } +} - baseWidth = std::max( baseWidth, clamp( fit->GetGeometry().nX + fit->GetGeometry().nWidth, 0, currentOutputWidth ) ); - baseHeight = std::max( baseHeight, clamp( fit->GetGeometry().nY + fit->GetGeometry().nHeight, 0, currentOutputHeight ) ); - } - } +/* See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */ +static void handle_wm_change_state( + xwayland_ctx_t *ctx, steamcompmgr_win_t *w, XClientMessageEvent *ev ) +{ + long state = ev->data.l[ 0 ]; + + if ( state == ICCCM_ICONIC_STATE ) + { + xwm_log.debugf( + "Faking WM_CHANGE_STATE to ICONIC for window 0x%lx", + w->xwayland( ).id ); + set_wm_state( ctx, w->xwayland( ).id, ICCCM_ICONIC_STATE ); + } + else + { + xwm_log.debugf( + "Unhandled WM_CHANGE_STATE to %ld for window 0x%lx", + state, + w->xwayland( ).id ); + } +} - bool offset = ( ( w->GetGeometry().nX || w->GetGeometry().nY ) && w != scaleW ); +static void +handle_client_message( xwayland_ctx_t *ctx, XClientMessageEvent *ev ) +{ + if ( ev->window == ctx->ourWindow && + ev->message_type == ctx->atoms.netSystemTrayOpcodeAtom ) + { + handle_system_tray_opcode( ctx, ev ); + return; + } + + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + if ( ev->message_type == ctx->atoms.WLSurfaceIDAtom ) + { + handle_wl_surface_id( ctx, w, uint32_t( ev->data.l[ 0 ] ) ); + } + else if ( ev->message_type == ctx->atoms.activeWindowAtom ) + { + XRaiseWindow( ctx->dpy, w->xwayland( ).id ); + } + else if ( ev->message_type == ctx->atoms.netWMStateAtom ) + { + handle_net_wm_state( ctx, w, ev ); + } + else if ( ev->message_type == ctx->atoms.WMChangeStateAtom ) + { + handle_wm_change_state( ctx, w, ev ); + } + else if ( ev->message_type != 0 ) + { + xwm_log.debugf( + "Unhandled client message: %s", + XGetAtomName( ctx->dpy, ev->message_type ) ); + } + } +} + +static void x11_set_selection_owner( + xwayland_ctx_t *ctx, + std::string contents, + GamescopeSelection eSelectionTarget ) +{ + Atom target; + if ( eSelectionTarget == GAMESCOPE_SELECTION_CLIPBOARD ) + { + target = ctx->atoms.clipboard; + } + else if ( eSelectionTarget == GAMESCOPE_SELECTION_PRIMARY ) + { + target = ctx->atoms.primarySelection; + } + else + { + return; + } + + XSetSelectionOwner( ctx->dpy, target, ctx->ourWindow, CurrentTime ); +} + +void gamescope_set_selection( + std::string contents, GamescopeSelection eSelection ) +{ + if ( eSelection == GAMESCOPE_SELECTION_CLIPBOARD ) { clipboard = contents; } + else if ( eSelection == GAMESCOPE_SELECTION_PRIMARY ) + { + primarySelection = contents; + } + + gamescope_xwayland_server_t *server = NULL; + for ( int i = 0; ( server = wlserver_get_xwayland_server( i ) ); i++ ) + { + xwayland_ctx_t *ctx = server->ctx.get( ); + + if ( ctx ) x11_set_selection_owner( ctx, contents, eSelection ); + } +} + +void gamescope_set_reshade_effect( std::string effect_path ) +{ + gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( 0 ); + set_string_prop( + server->ctx.get( ), + server->ctx->atoms.gamescopeReshadeEffect, + effect_path ); +} + +void gamescope_clear_reshade_effect( ) +{ + gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( 0 ); + clear_prop( server->ctx.get( ), server->ctx->atoms.gamescopeReshadeEffect ); +} - if (sourceWidth != (int32_t)currentOutputWidth || sourceHeight != (int32_t)currentOutputHeight || offset || globalScaleRatio != 1.0f) - { - calc_scale_factor(currentScaleRatio_x, currentScaleRatio_y, sourceWidth, sourceHeight); +static void +handle_selection_request( xwayland_ctx_t *ctx, XSelectionRequestEvent *ev ) +{ + std::string *selection = ev->selection == ctx->atoms.primarySelection + ? &primarySelection + : &clipboard; + + const char *targetString = XGetAtomName( ctx->dpy, ev->target ); + + XEvent response; + response.xselection.type = SelectionNotify; + response.xselection.selection = ev->selection; + response.xselection.requestor = ev->requestor; + response.xselection.time = ev->time; + response.xselection.property = None; + response.xselection.target = None; + + if ( ev->requestor == ctx->ourWindow ) { return; } + + if ( ev->target == ctx->atoms.targets ) + { + Atom targetList[] = { + ctx->atoms.targets, + ctx->atoms.utf8StringAtom, + }; + + XChangeProperty( + ctx->dpy, + ev->requestor, + ev->property, + XA_ATOM, + 32, + PropModeReplace, + ( unsigned char * )&targetList, + sizeof( targetList ) / sizeof( targetList[ 0 ] ) ); + response.xselection.property = ev->property; + response.xselection.target = ev->target; + } + else if ( + !strcmp( targetString, "text/plain;charset=utf-8" ) || + !strcmp( targetString, "text/plain" ) || + !strcmp( targetString, "TEXT" ) || + !strcmp( targetString, "UTF8_STRING" ) || + !strcmp( targetString, "STRING" ) ) + { + + XChangeProperty( + ctx->dpy, + ev->requestor, + ev->property, + ev->target, + 8, + PropModeReplace, + ( unsigned char * )selection->c_str( ), + selection->length( ) ); + response.xselection.property = ev->property; + response.xselection.target = ev->target; + } + else + { + xwm_log.debugf( + "Unsupported clipboard type: %s. Ignoring", targetString ); + } + + XSendEvent( ctx->dpy, ev->requestor, False, NoEventMask, &response ); + XFlush( ctx->dpy ); +} + +static void handle_selection_notify( xwayland_ctx_t *ctx, XSelectionEvent *ev ) +{ + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char *data = NULL; + + XGetWindowProperty( + ctx->dpy, + ev->requestor, + ev->property, + 0, + 0, + False, + AnyPropertyType, + &actual_type, + &actual_format, + &nitems, + &bytes_after, + &data ); + if ( data ) { XFree( data ); } + + if ( actual_type == ctx->atoms.utf8StringAtom && actual_format == 8 ) + { + XGetWindowProperty( + ctx->dpy, + ev->requestor, + ev->property, + 0, + bytes_after, + False, + AnyPropertyType, + &actual_type, + &actual_format, + &nitems, + &bytes_after, + &data ); + if ( data ) + { + const char *contents = ( const char * )data; + auto szContents = std::make_shared( contents ); + defer( XFree( data ); ); + + gamescope::INestedHints *hints = nullptr; + if ( auto connector = GetBackend( )->GetCurrentConnector( ) ) + hints = connector->GetNestedHints( ); + + if ( ev->selection == ctx->atoms.clipboard ) + { + if ( hints ) + { + hints->SetSelection( + szContents, GAMESCOPE_SELECTION_CLIPBOARD ); + } + else + { + gamescope_set_selection( + contents, GAMESCOPE_SELECTION_CLIPBOARD ); + } + } + else if ( ev->selection == ctx->atoms.primarySelection ) + { + if ( hints ) + { + hints->SetSelection( + szContents, GAMESCOPE_SELECTION_PRIMARY ); + } + else + { + gamescope_set_selection( + contents, GAMESCOPE_SELECTION_PRIMARY ); + } + } + else + { + xwm_log.errorf( + "Selection '%s' not supported. Ignoring", + XGetAtomName( ctx->dpy, ev->selection ) ); + } + } + } +} + +template T bit_cast( const J &src ) +{ + T dst; + memcpy( &dst, &src, sizeof( T ) ); + return dst; +} + +static void update_runtime_info( ) +{ + if ( g_nRuntimeInfoFd < 0 ) return; + + uint32_t limiter_enabled = g_nSteamCompMgrTargetFPS != 0 ? 1 : 0; + pwrite( g_nRuntimeInfoFd, &limiter_enabled, sizeof( limiter_enabled ), 0 ); +} + +static void init_runtime_info( ) +{ + const char *path = getenv( "GAMESCOPE_LIMITER_FILE" ); + if ( !path ) return; + + g_nRuntimeInfoFd = open( path, O_CREAT | O_RDWR, 0644 ); + update_runtime_info( ); +} + +static void steamcompmgr_flush_frame_done( steamcompmgr_win_t *w ) +{ + wlr_surface *current_surface = w->current_surface( ); + if ( current_surface && w->unlockedForFrameCallback && + w->receivedDoneCommit ) + { + // TODO: Look into making this _RAW + // wlroots, seems to just use normal MONOTONIC + // all over so this may be problematic to just change. + struct timespec now; + clock_gettime( CLOCK_MONOTONIC, &now ); + + wlr_surface *main_surface = w->main_surface( ); + w->unlockedForFrameCallback = false; + w->receivedDoneCommit = false; + + w->last_commit_first_latch_time = timespec_to_nanos( now ); + + // Acknowledge commit once. + wlserver_lock( ); + + if ( main_surface != nullptr ) + { + wlserver_send_frame_done( main_surface, &now ); + } + + if ( current_surface != nullptr && main_surface != current_surface ) + { + wlserver_send_frame_done( current_surface, &now ); + } + + wlserver_unlock( ); + } +} - drawXOffset = ((int)currentOutputWidth - (int)sourceWidth * currentScaleRatio_x) / 2.0f; - drawYOffset = ((int)currentOutputHeight - (int)sourceHeight * currentScaleRatio_y) / 2.0f; +static std::optional s_oLowestFPSLimitScheduleVRR; - if ( w != scaleW ) - { - drawXOffset += w->GetGeometry().nX * currentScaleRatio_x; - drawYOffset += w->GetGeometry().nY * currentScaleRatio_y; - } +static bool steamcompmgr_should_vblank_window( + bool bShouldLimitFPS, + uint64_t vblank_idx, + steamcompmgr_win_t *w = nullptr, + uint64_t now = 0 ) +{ + bool bSendCallback = true; - calc_scale_factor(baseScaleRatio_x, baseScaleRatio_y, baseWidth, baseHeight); - if ( zoomScaleRatio != 1.0 ) - { - drawXOffset += (((int)baseWidth / 2) - (cursor ? cursor->x() : 0)) * baseScaleRatio_x; - drawYOffset += (((int)baseHeight / 2) - (cursor ? cursor->y() : 0)) * baseScaleRatio_y; - } - } + int nRefreshHz = gamescope::ConvertmHzToHz( + g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh ); + int nTargetFPS = g_nSteamCompMgrTargetFPS; - layer->opacity = ( (w->isOverlay || w->isExternalOverlay) ? w->opacity / (float)OPAQUE : 1.0f ) * flOpacityScale; + if ( GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->IsVRRActive( ) ) + { + bool bCloseEnough = + std::abs( g_nSteamCompMgrTargetFPS - nRefreshHz ) < 2; - layer->scale.x = 1.0 / currentScaleRatio_x; - layer->scale.y = 1.0 / currentScaleRatio_y; + if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && w && !bCloseEnough ) + { + uint64_t schedule = w->last_commit_first_latch_time + + g_SteamCompMgrLimitedAppRefreshCycle; - if ( w != scaleW ) - { - layer->offset.x = -drawXOffset; - layer->offset.y = -drawYOffset; - } - else - { - layer->offset.x = -drawXOffset; - layer->offset.y = -drawYOffset; - } + static constexpr uint64_t k_ulVRRScheduleFudge = 200'000; // 0.2ms + if ( now + k_ulVRRScheduleFudge < schedule ) + { + bSendCallback = false; - layer->blackBorder = flags & PaintWindowFlag::DrawBorders; + if ( !s_oLowestFPSLimitScheduleVRR ) + s_oLowestFPSLimitScheduleVRR = schedule; + else + s_oLowestFPSLimitScheduleVRR = + std::min( *s_oLowestFPSLimitScheduleVRR, schedule ); + } + } + } + else + { + if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && + nRefreshHz > nTargetFPS ) + { + int nVblankDivisor = nRefreshHz / nTargetFPS; - layer->applyColorMgmt = g_ColorMgmt.pending.enabled; - layer->zpos = g_zposBase; + if ( vblank_idx % nVblankDivisor != 0 ) bSendCallback = false; + } + } - if ( w != scaleW ) - { - layer->zpos = g_zposOverride; - } + return bSendCallback; +} - if ( w->isOverlay || w->isSteamStreamingClient ) - { - layer->zpos = g_zposOverlay; - } - if ( w->isExternalOverlay ) - { - layer->zpos = g_zposExternalOverlay; - } - - layer->hdr_metadata_blob = nullptr; - if (lastCommit->feedback) - { - layer->hdr_metadata_blob = lastCommit->feedback->hdr_metadata_blob; - } - layer->ctm = nullptr; - if (layer->colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB) - layer->ctm = s_scRGB709To2020Matrix; - - if (layer->filter == GamescopeUpscaleFilter::PIXEL) - { - // Don't bother doing more expensive filtering if we are sharp + integer. - if (float_is_integer(currentScaleRatio_x) && float_is_integer(currentScaleRatio_y)) - layer->filter = GamescopeUpscaleFilter::NEAREST; - } - - layer->eAlphaBlendingMode = ( flags & PaintWindowFlag::CoverageMode ) ? ALPHA_BLENDING_MODE_COVERAGE : ALPHA_BLENDING_MODE_PREMULTIPLIED; - - return layer; -} - -static void -paint_window(steamcompmgr_win_t *w, steamcompmgr_win_t *scaleW, struct FrameInfo_t *frameInfo, - MouseCursor *cursor, PaintWindowFlags flags = 0, float flOpacityScale = 1.0f, steamcompmgr_win_t *fit = nullptr ) -{ - gamescope::Rc lastCommit; - if ( w ) - get_window_last_done_commit( w, lastCommit ); - - if ( flags & PaintWindowFlag::BasePlane ) - { - if ( lastCommit == nullptr || cv_paint_debug_pause_base_plane ) - { - // If we're the base plane and have no valid contents - // pick up that buffer we've been holding onto if we have one. - if ( g_HeldCommits[ HELD_COMMIT_BASE ] != nullptr ) - { - paint_cached_base_layer( g_HeldCommits[ HELD_COMMIT_BASE ], g_CachedPlanes[ HELD_COMMIT_BASE ], frameInfo, flOpacityScale, true ); - return; - } - } - else - { - if ( g_bPendingFade ) - { - fadeOutStartTime = get_time_in_milliseconds(); - g_bPendingFade = false; - } - } - } - - FrameInfo_t::Layer_t *layer = paint_window_commit( lastCommit, w, scaleW, frameInfo, cursor, flags, flOpacityScale, fit ); - - if ( layer && ( flags & PaintWindowFlag::BasePlane ) ) - { - BaseLayerInfo_t basePlane = {}; - basePlane.scale[0] = layer->scale.x; - basePlane.scale[1] = layer->scale.y; - basePlane.offset[0] = layer->offset.x; - basePlane.offset[1] = layer->offset.y; - basePlane.opacity = layer->opacity; - basePlane.filter = layer->filter; - basePlane.eAlphaBlendingMode = layer->eAlphaBlendingMode; - - g_CachedPlanes[ HELD_COMMIT_BASE ] = basePlane; - if ( !(flags & PaintWindowFlag::FadeTarget) ) - g_CachedPlanes[ HELD_COMMIT_FADE ] = basePlane; - - g_uCurrentBasePlaneCommitID = lastCommit->commitID; - g_uCurrentBasePlaneAppID = lastCommit->appID; - g_bCurrentBasePlaneIsFifo = lastCommit->IsPerfOverlayFIFO(); - } -} - -bool g_bFirstFrame = true; - -static bool is_fading_out() -{ - return fadeOutStartTime || g_bPendingFade; -} - -static void update_touch_scaling( const struct FrameInfo_t *frameInfo ) -{ - if ( !frameInfo->layerCount ) - return; - - focusedWindowScaleX = frameInfo->layers[ frameInfo->layerCount - 1 ].scale.x; - focusedWindowScaleY = frameInfo->layers[ frameInfo->layerCount - 1 ].scale.y; - focusedWindowOffsetX = frameInfo->layers[ frameInfo->layerCount - 1 ].offset.x; - focusedWindowOffsetY = frameInfo->layers[ frameInfo->layerCount - 1 ].offset.y; -} - -#if HAVE_PIPEWIRE -static void paint_pipewire() -{ - static struct pipewire_buffer *s_pPipewireBuffer = nullptr; - - // If the stream stopped/changed, and the underlying pw_buffer was thus - // destroyed, then destroy this buffer and grab a new one. - if ( s_pPipewireBuffer && s_pPipewireBuffer->IsStale() ) - { - pipewire_destroy_buffer( s_pPipewireBuffer ); - s_pPipewireBuffer = nullptr; - } - - // Queue up a buffer with some metadata. - if ( !s_pPipewireBuffer ) - s_pPipewireBuffer = dequeue_pipewire_buffer(); - - if ( !s_pPipewireBuffer || !s_pPipewireBuffer->texture ) - return; - - struct FrameInfo_t frameInfo = {}; - frameInfo.applyOutputColorMgmt = true; - frameInfo.outputEncodingEOTF = EOTF_Gamma22; - frameInfo.allowVRR = false; - frameInfo.bFadingOut = false; - - // Apply screenshot-style color management. - for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) - { - frameInfo.lut3D[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut3d; - frameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; - } - - const uint64_t ulFocusAppId = s_pPipewireBuffer->gamescope_info.focus_appid; - - focus_t *pFocus = nullptr; - if ( ulFocusAppId ) - { - static uint64_t s_ulLastFocusAppId = 0; - - bool bAppIdChange = ulFocusAppId != s_ulLastFocusAppId; - if ( bAppIdChange ) - { - xwm_log.infof( "Exposing appid %lu (%u 32-bit) focus-wise on pipewire stream.", ulFocusAppId, uint32_t( ulFocusAppId ) ); - s_ulLastFocusAppId = ulFocusAppId; - } - - static focus_t s_PipewireFocus{}; - if ( s_PipewireFocus.IsDirty() || bAppIdChange ) - { - std::vector vecPossibleFocusWindows = GetGlobalPossibleFocusWindows(); - - std::vector vecAppIds{ uint32_t( ulFocusAppId ) }; - pick_primary_focus_and_override( &s_PipewireFocus, None, vecPossibleFocusWindows, false, vecAppIds, 0, gamescope::VirtualConnectorStrategies::SteamControlled ); - } - pFocus = &s_PipewireFocus; - } - else - { - pFocus = GetCurrentFocus(); - } - - if ( !pFocus->focusWindow ) - return; - - const bool bAppIdMatches = !ulFocusAppId || pFocus->focusWindow->appID == ulFocusAppId; - if ( !bAppIdMatches ) - return; - - // If the commits are the same as they were last time, don't repaint and don't push a new buffer on the stream. - static uint64_t s_ulLastFocusCommitId = 0; - static uint64_t s_ulLastOverrideCommitId = 0; - - uint64_t ulFocusCommitId = window_last_done_commit_id( pFocus->focusWindow ); - uint64_t ulOverrideCommitId = window_last_done_commit_id( pFocus->overrideWindow ); - - if ( ulFocusCommitId == s_ulLastFocusCommitId && - ulOverrideCommitId == s_ulLastOverrideCommitId ) - return; - - s_ulLastFocusCommitId = ulFocusCommitId; - s_ulLastOverrideCommitId = ulOverrideCommitId; - - uint32_t uWidth = s_pPipewireBuffer->texture->width(); - uint32_t uHeight = s_pPipewireBuffer->texture->height(); - - const uint32_t uCompositeDebugBackup = g_uCompositeDebug; - const uint32_t uBackupWidth = currentOutputWidth; - const uint32_t uBackupHeight = currentOutputHeight; - - g_uCompositeDebug = 0; - currentOutputWidth = uWidth; - currentOutputHeight = uHeight; - - // Paint the windows we have onto the Pipewire stream. - paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, 0, 1.0f, pFocus->overrideWindow ); - - if ( pFocus->overrideWindow && !pFocus->focusWindow->isSteamStreamingClient ) - paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow ); - - if ( !ulFocusAppId && pFocus->overlayWindow && pFocus->overlayWindow->opacity ) - { - paint_window( pFocus->overlayWindow, pFocus->overlayWindow, &frameInfo, nullptr, PaintWindowFlag::DrawBorders | PaintWindowFlag::NoFilter | - ( cv_overlay_unmultiplied_alpha ? PaintWindowFlag::CoverageMode : 0 ) ); - } - - gamescope::Rc pRGBTexture = s_pPipewireBuffer->texture->isYcbcr() - ? vulkan_acquire_screenshot_texture( uWidth, uHeight, false, DRM_FORMAT_XRGB2101010 ) - : gamescope::Rc{ s_pPipewireBuffer->texture }; - - gamescope::Rc pYUVTexture = s_pPipewireBuffer->texture->isYcbcr() ? s_pPipewireBuffer->texture : nullptr; - - - std::optional oPipewireSequence = vulkan_screenshot( &frameInfo, pRGBTexture, pYUVTexture ); - // If we ever want the fat compositing path, use this. - //std::optional oPipewireSequence = vulkan_composite( &frameInfo, s_pPipewireBuffer->texture, false, pRGBTexture, false ); - - g_uCompositeDebug = uCompositeDebugBackup; - - currentOutputWidth = uBackupWidth; - currentOutputHeight = uBackupHeight; - - if ( oPipewireSequence ) - { - vulkan_wait( *oPipewireSequence, true ); - - push_pipewire_buffer( s_pPipewireBuffer ); - s_pPipewireBuffer = nullptr; - } -} -#endif - -gamescope::ConVar cv_cursor_composite{ "cursor_composite", 1, "0 = Never composite a cursor. 1 = Composite cursor when not nested. 2 = Always composite a cursor manually" }; -bool ShouldDrawCursor() -{ - if ( cv_cursor_composite == 2 ) - return true; - - if ( cv_cursor_composite == 0 ) - return false; - - if ( g_bForceRelativeMouse ) - return true; - - global_focus_t *pFocus = GetCurrentFocus(); - if ( !pFocus ) - return false; - - if ( !pFocus->GetNestedHints() ) - return true; - - return pFocus->GetNestedHints()->ShouldPaintCursor(); -} - -static void ForwardVROverlayTargets() -{ - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - for ( steamcompmgr_win_t *w = server->ctx->list; w; w = w->xwayland().next ) - { - if ( w->oulTargetVROverlay && w->bNeedsForwarding ) - { - gamescope::Rc lastCommit; - get_window_last_done_commit( w, lastCommit ); - if ( !lastCommit ) - continue; - - gamescope::IBackendFb* pFb = lastCommit->vulkanTex->GetBackendFb(); - if ( !pFb ) - continue; - - const uint64_t ulOverlayHandle = *w->oulTargetVROverlay; - GetBackend()->ForwardFramebuffer( w->pForwarderPlane, pFb, &ulOverlayHandle ); - - w->bNeedsForwarding = false; - } - } - } - - gpuvis_trace_printf( "Forward VR Overlays" ); -} - -gamescope::ConVar cv_paint_primary_plane{ "paint_primary_plane", true }; -gamescope::ConVar cv_paint_override_redirect_plane{ "paint_override_redirect_plane", true }; -gamescope::ConVar cv_paint_steam_overlay_plane{ "paint_steam_overlay_plane", true }; -gamescope::ConVar cv_paint_external_overlay_plane{ "paint_external_overlay_plane", true }; -gamescope::ConVar cv_paint_cursor_plane{ "paint_cursor_plane", true }; -gamescope::ConVar cv_paint_mura_plane{ "paint_mura_plane", true }; - -static void -paint_all( global_focus_t *pFocus, bool async ) -{ - if ( !pFocus ) - return; - - gamescope::IBackendConnector *pConnector = pFocus->pVirtualConnector.get(); - if ( !pConnector ) - pConnector = GetBackend()->GetCurrentConnector(); - - gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0); - xwayland_ctx_t *root_ctx = root_server->ctx.get(); - - static long long int paintID = 0; - - update_color_mgmt(); - - paintID++; - gpuvis_trace_begin_ctx_printf( paintID, "paint_all" ); - steamcompmgr_win_t *w; - steamcompmgr_win_t *overlay; - steamcompmgr_win_t *externalOverlay; - steamcompmgr_win_t *notification; - steamcompmgr_win_t *override; - steamcompmgr_win_t *input; - - unsigned int currentTime = get_time_in_milliseconds(); - bool fadingOut = ( currentTime - fadeOutStartTime < g_FadeOutDuration || g_bPendingFade ) && g_HeldCommits[HELD_COMMIT_FADE] != nullptr; - - w = pFocus->focusWindow; - overlay = pFocus->overlayWindow; - externalOverlay = pFocus->externalOverlayWindow; - notification = pFocus->notificationWindow; - override = pFocus->overrideWindow; - input = pFocus->inputFocusWindow; - - if (++frameCounter == 300) - { - currentFrameRate = 300 * 1000.0f / (currentTime - lastSampledFrameTime); - lastSampledFrameTime = currentTime; - frameCounter = 0; - - stats_printf( "fps=%f\n", currentFrameRate ); - - if ( window_is_steam( w ) ) - { - stats_printf( "focus=steam\n" ); - } - else - { - stats_printf( "focus=%i\n", w ? w->appID : 0 ); - } - } - - struct FrameInfo_t frameInfo = {}; - frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; - frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; - frameInfo.allowVRR = cv_adaptive_sync; - frameInfo.bFadingOut = fadingOut; - - // If the window we'd paint as the base layer is the streaming client, - // find the video underlay and put it up first in the scenegraph - if ( cv_paint_primary_plane ) - { - if ( w ) - { - if ( w->isSteamStreamingClient == true ) - { - steamcompmgr_win_t *videow = NULL; - bool bHasVideoUnderlay = false; - - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - for ( videow = server->ctx->list; videow; videow = videow->xwayland().next ) - { - if ( videow->isSteamStreamingClientVideo == true ) - { - // TODO: also check matching AppID so we can have several pairs - paint_window(videow, videow, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders); - bHasVideoUnderlay = true; - break; - } - } - } - - int nOldLayerCount = frameInfo.layerCount; - - uint32_t flags = 0; - if ( !bHasVideoUnderlay ) - flags |= PaintWindowFlag::BasePlane; - paint_window(w, w, &frameInfo, pFocus->cursor, flags); - if ( pFocus == GetCurrentFocus() ) - update_touch_scaling( &frameInfo ); - - // paint UI unless it's fully hidden, which it communicates to us through opacity=0 - // we paint it to extract scaling coefficients above, then remove the layer if one was added - if ( w->opacity == TRANSLUCENT && bHasVideoUnderlay && nOldLayerCount < frameInfo.layerCount ) - frameInfo.layerCount--; - } - else - { - if ( fadingOut ) - { - float opacityScale = g_bPendingFade - ? 0.0f - : ((currentTime - fadeOutStartTime) / (float)g_FadeOutDuration); - - paint_cached_base_layer(g_HeldCommits[HELD_COMMIT_FADE], g_CachedPlanes[HELD_COMMIT_FADE], &frameInfo, 1.0f - opacityScale, false); - paint_window(w, w, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::FadeTarget | PaintWindowFlag::DrawBorders, opacityScale, override); - } - else - { - { - if ( g_HeldCommits[HELD_COMMIT_FADE] != nullptr ) - { - g_HeldCommits[HELD_COMMIT_FADE] = nullptr; - g_bPendingFade = false; - fadeOutStartTime = 0; - pFocus->fadeWindow = None; - } - } - // Just draw focused window as normal, be it Steam or the game - paint_window(w, w, &frameInfo, pFocus->cursor, PaintWindowFlag::BasePlane | PaintWindowFlag::DrawBorders, 1.0f, override); - - bool needsScaling = frameInfo.layers[0].scale.x < 0.999f && frameInfo.layers[0].scale.y < 0.999f; - frameInfo.useFSRLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::FSR && needsScaling; - frameInfo.useNISLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::NIS && needsScaling; - } - if ( pFocus == GetCurrentFocus() ) - update_touch_scaling( &frameInfo ); - } - } - else - { - if ( g_HeldCommits[HELD_COMMIT_BASE] != nullptr ) - { - float opacityScale = 1.0f; - if ( fadingOut ) - { - opacityScale = g_bPendingFade - ? 0.0f - : ((currentTime - fadeOutStartTime) / (float)g_FadeOutDuration); - } - - paint_cached_base_layer( g_HeldCommits[HELD_COMMIT_BASE], g_CachedPlanes[HELD_COMMIT_BASE], &frameInfo, opacityScale, true ); - } - } - } - - // TODO: We want to paint this at the same scale as the normal window and probably - // with an offset. - // Josh: No override if we're streaming video - // as we will have too many layers. Better to be safe than sorry. - if ( override && w && !w->isSteamStreamingClient && cv_paint_override_redirect_plane ) - { - paint_window(override, w, &frameInfo, pFocus->cursor, PaintWindowFlag::NoFilter, 1.0f, override); - // Don't update touch scaling for frameInfo. We don't ever make it our - // wlserver_mousefocus window. - //update_touch_scaling( &frameInfo ); - } - - // If we have any layers that aren't a cursor or overlay, then we have valid contents for presentation. - const bool bValidContents = frameInfo.layerCount > 0; - - if (externalOverlay && cv_paint_external_overlay_plane ) - { - if (externalOverlay->opacity) - { - paint_window(externalOverlay, externalOverlay, &frameInfo, pFocus->cursor, PaintWindowFlag::NoScale | PaintWindowFlag::NoFilter | - ( cv_overlay_unmultiplied_alpha ? PaintWindowFlag::CoverageMode : 0 ) ); - - if ( externalOverlay == pFocus->inputFocusWindow && pFocus == GetCurrentFocus() ) - update_touch_scaling( &frameInfo ); - } - } - - if ( cv_paint_steam_overlay_plane ) - { - if (overlay && overlay->opacity ) - { - paint_window(overlay, overlay, &frameInfo, pFocus->cursor, PaintWindowFlag::DrawBorders | PaintWindowFlag::NoFilter | - ( cv_overlay_unmultiplied_alpha ? PaintWindowFlag::CoverageMode : 0 ) ); - - if ( overlay == pFocus->inputFocusWindow && pFocus == GetCurrentFocus() ) - update_touch_scaling( &frameInfo ); - } - else if ( !GetBackend()->UsesVulkanSwapchain() && GetBackend()->IsSessionBased() ) - { - auto tex = vulkan_get_hacky_blank_texture(); - if ( tex != nullptr ) - { - // HACK! HACK HACK HACK - // To avoid stutter when toggling the overlay on - int curLayer = frameInfo.layerCount++; - - FrameInfo_t::Layer_t *layer = &frameInfo.layers[ curLayer ]; - - - layer->scale.x = g_nOutputWidth == tex->width() ? 1.0f : tex->width() / (float)g_nOutputWidth; - layer->scale.y = g_nOutputHeight == tex->height() ? 1.0f : tex->height() / (float)g_nOutputHeight; - layer->offset.x = 0.0f; - layer->offset.y = 0.0f; - layer->opacity = 1.0f; // BLAH - layer->zpos = g_zposOverlay; - layer->applyColorMgmt = g_ColorMgmt.pending.enabled; - layer->eAlphaBlendingMode = cv_overlay_unmultiplied_alpha ? ALPHA_BLENDING_MODE_COVERAGE : ALPHA_BLENDING_MODE_PREMULTIPLIED; - - layer->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; - layer->hdr_metadata_blob = nullptr; - layer->ctm = nullptr; - layer->tex = tex; - - layer->filter = GamescopeUpscaleFilter::NEAREST; - layer->blackBorder = true; - } - } - } - - if (notification) - { - if (notification->opacity) - { - paint_window(notification, notification, &frameInfo, pFocus->cursor, PaintWindowFlag::NotificationMode | PaintWindowFlag::NoFilter); - } - } - - if (input) - { - // Make sure to un-dirty the texture before we do any painting logic. - // We determine whether we are grabbed etc this way. - pFocus->cursor->undirty(); - } - - // Draw cursor if we need to - if (input && ShouldDrawCursor() && cv_paint_cursor_plane) { - pFocus->cursor->paint( - input, w == input ? override : nullptr, - &frameInfo); - } - - if ( !bValidContents || GetBackend()->IsPaused() ) - { - return; - } - - unsigned int blurFadeTime = get_time_in_milliseconds() - g_BlurFadeStartTime; - bool blurFading = blurFadeTime < g_BlurFadeDuration; - BlurMode currentBlurMode = blurFading ? std::max(g_BlurMode, g_BlurModeOld) : g_BlurMode; - - if (currentBlurMode && !(frameInfo.layerCount <= 1 && currentBlurMode == BLUR_MODE_COND)) - { - frameInfo.blurLayer0 = currentBlurMode; - frameInfo.blurRadius = g_BlurRadius; - - if (blurFading) - { - float ratio = blurFadeTime / (float) g_BlurFadeDuration; - bool fadingIn = g_BlurMode > g_BlurModeOld; - - if (!fadingIn) - ratio = 1.0 - ratio; - - frameInfo.blurRadius = ratio * g_BlurRadius; - } - - frameInfo.useFSRLayer0 = false; - frameInfo.useNISLayer0 = false; - } - - g_bFSRActive = frameInfo.useFSRLayer0; - if ( const auto& heldCommit = g_HeldCommits[HELD_COMMIT_BASE]; heldCommit && heldCommit->upscaledTexture ) { - g_bFSRActive = ( heldCommit->upscaledTexture->eFilter == GamescopeUpscaleFilter::FSR ); - } - - g_bFirstFrame = false; - - update_app_target_refresh_cycle(); - - const bool bSupportsDynamicRefresh = pConnector && !pConnector->GetValidDynamicRefreshRates().empty(); - if ( bSupportsDynamicRefresh ) - { - auto rates = pConnector->GetValidDynamicRefreshRates(); - - int nDynamicRefreshHz = g_nDynamicRefreshRate[GetBackend()->GetScreenType()]; - - int nTargetRefreshHz = nDynamicRefreshHz && steamcompmgr_window_should_refresh_switch( pFocus->focusWindow ) - ? nDynamicRefreshHz - : int( rates[ rates.size() - 1 ] ); - - uint64_t now = get_time_in_nanos(); - - // The actual output hz generated by the mode, could be off by one either side, due to rounding. - // - // Check what we cached for the current dynamic refresh Hz we put in -- otherwise, convert - // g_nOutputRefresh -> Hz rounded. - int32_t nCurrentDynamicOutputHz = g_nDynamicRefreshHz - ? g_nDynamicRefreshHz - : gamescope::ConvertmHzToHz( g_nOutputRefresh ); - - if ( nCurrentDynamicOutputHz == nTargetRefreshHz ) - { - g_uDynamicRefreshEqualityTime = now; - } - else if ( g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now ) - { - GetBackend()->HackTemporarySetDynamicRefresh( nTargetRefreshHz ); - } - } - - bool bDoMuraCompensation = is_mura_correction_enabled() && frameInfo.layerCount && cv_paint_mura_plane; - if ( bDoMuraCompensation ) - { - auto& MuraCorrectionImage = s_MuraCorrectionImage[GetBackend()->GetScreenType()]; - int curLayer = frameInfo.layerCount++; - - FrameInfo_t::Layer_t *layer = &frameInfo.layers[ curLayer ]; - - layer->applyColorMgmt = false; - layer->scale = vec2_t{ 1.0f, 1.0f }; - layer->blackBorder = true; - layer->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU; - layer->hdr_metadata_blob = nullptr; - layer->opacity = 1.0f; - layer->zpos = g_zposMuraCorrection; - layer->filter = GamescopeUpscaleFilter::NEAREST; - layer->tex = MuraCorrectionImage; - layer->ctm = s_MuraCTMBlob[GetBackend()->GetScreenType()]; - - // Blending needs to be done in Gamma 2.2 space for mura correction to work. - frameInfo.applyOutputColorMgmt = false; - } - - for (uint32_t i = 0; i < EOTF_Count; i++) - { - if ( g_ColorMgmtLuts[i].HasLuts() ) - { - frameInfo.shaperLut[i] = g_ColorMgmtLuts[i].vk_lut1d; - frameInfo.lut3D[i] = g_ColorMgmtLuts[i].vk_lut3d; - } - } - - if ( pConnector && pConnector->Present( &frameInfo, async ) != 0 ) - { - return; - } - - std::optional oScreenshotInfo = - gamescope::CScreenshotManager::Get().ProcessPendingScreenshot(); - - if ( oScreenshotInfo ) - { - std::filesystem::path path = std::filesystem::path{ oScreenshotInfo->szScreenshotPath }; - - uint32_t drmCaptureFormat = DRM_FORMAT_INVALID; - - if ( path.extension() == ".avif" ) - drmCaptureFormat = DRM_FORMAT_XRGB2101010; - else if ( path.extension() == ".png" ) - drmCaptureFormat = DRM_FORMAT_XRGB8888; - else if ( path.extension() == ".nv12.bin" ) - drmCaptureFormat = DRM_FORMAT_NV12; - - gamescope::Rc pScreenshotTexture; - if ( drmCaptureFormat != DRM_FORMAT_INVALID ) - pScreenshotTexture = vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat ); - - if ( pScreenshotTexture ) - { - bool bHDRScreenshot = path.extension() == ".avif" && - frameInfo.layerCount > 0 && - ColorspaceIsHDR( frameInfo.layers[0].colorspace ) && - oScreenshotInfo->eScreenshotType != GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER; - - if ( drmCaptureFormat == DRM_FORMAT_NV12 || oScreenshotInfo->eScreenshotType != GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) - { - // Basically no color mgmt applied for screenshots. (aside from being able to handle HDR content with LUTs) - for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) - { - auto& luts = bHDRScreenshot ? g_ScreenshotColorMgmtLutsHDR : g_ScreenshotColorMgmtLuts; - frameInfo.lut3D[nInputEOTF] = luts[nInputEOTF].vk_lut3d; - frameInfo.shaperLut[nInputEOTF] = luts[nInputEOTF].vk_lut1d; - } - - if ( oScreenshotInfo->eScreenshotType == GAMESCOPE_CONTROL_SCREENSHOT_TYPE_BASE_PLANE_ONLY ) - { - // Remove everything but base planes from the screenshot. - for (int i = 0; i < frameInfo.layerCount; i++) - { - if (frameInfo.layers[i].zpos >= (int)g_zposExternalOverlay) - { - frameInfo.layerCount = i; - break; - } - } - } - else - { - if ( is_mura_correction_enabled() ) - { - // Remove the last layer which is for mura... - for (int i = 0; i < frameInfo.layerCount; i++) - { - if (frameInfo.layers[i].zpos >= (int)g_zposMuraCorrection) - { - frameInfo.layerCount = i; - break; - } - } - } - } - - // Re-enable output color management (blending) if it was disabled by mura. - frameInfo.applyOutputColorMgmt = true; - } - - frameInfo.outputEncodingEOTF = bHDRScreenshot ? EOTF_PQ : EOTF_Gamma22; - - uint32_t uCompositeDebugBackup = g_uCompositeDebug; - - if ( oScreenshotInfo->eScreenshotType != GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) - { - g_uCompositeDebug = 0; - } - - std::optional oScreenshotSeq; - if ( drmCaptureFormat == DRM_FORMAT_NV12 ) - oScreenshotSeq = vulkan_composite( &frameInfo, pScreenshotTexture, false, nullptr ); - else if ( oScreenshotInfo->eScreenshotType == GAMESCOPE_CONTROL_SCREENSHOT_TYPE_FULL_COMPOSITION || - oScreenshotInfo->eScreenshotType == GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) - oScreenshotSeq = vulkan_composite( &frameInfo, nullptr, false, pScreenshotTexture ); - else - oScreenshotSeq = vulkan_screenshot( &frameInfo, pScreenshotTexture, nullptr ); - - if ( oScreenshotInfo->eScreenshotType != GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) - { - g_uCompositeDebug = uCompositeDebugBackup; - } - - if ( !oScreenshotSeq ) - { - xwm_log.errorf("vulkan_screenshot failed"); - return; - } - - vulkan_wait( *oScreenshotSeq, false ); - - uint16_t maxCLLNits = 0; - uint16_t maxFALLNits = 0; - - if ( bHDRScreenshot ) - { - // Unfortunately games give us very bogus values here. - // Thus we don't really use them. - // Instead rely on the display it was initially tonemapped for. - //if ( g_ColorMgmt.current.appHDRMetadata ) - //{ - // maxCLLNits = g_ColorMgmt.current.appHDRMetadata->metadata.hdmi_metadata_type1.max_cll; - // maxFALLNits = g_ColorMgmt.current.appHDRMetadata->metadata.hdmi_metadata_type1.max_fall; - //} - - if ( !maxCLLNits && !maxFALLNits ) - { - if ( pConnector ) - { - maxCLLNits = pConnector->GetHDRInfo().uMaxContentLightLevel; - maxFALLNits = pConnector->GetHDRInfo().uMaxFrameAverageLuminance; - } - } - - if ( !maxCLLNits && !maxFALLNits ) - { - maxCLLNits = g_ColorMgmt.pending.flInternalDisplayBrightness; - maxFALLNits = g_ColorMgmt.pending.flInternalDisplayBrightness * 0.8f; - } - } - - std::thread screenshotThread = std::thread([=] { - pthread_setname_np( pthread_self(), "gamescope-scrsh" ); - - const uint8_t *mappedData = pScreenshotTexture->mappedData(); - - bool bScreenshotSuccess = false; - - if ( pScreenshotTexture->format() == VK_FORMAT_A2R10G10B10_UNORM_PACK32 ) - { - // Make our own copy of the image to remove the alpha channel. - constexpr uint32_t kCompCnt = 3; - auto imageData = std::vector( g_nOutputWidth * g_nOutputHeight * kCompCnt ); - - for (uint32_t y = 0; y < g_nOutputHeight; y++) - { - for (uint32_t x = 0; x < g_nOutputWidth; x++) - { - uint32_t *pInPixel = (uint32_t *)&mappedData[(y * pScreenshotTexture->rowPitch()) + x * (32 / 8)]; - uint32_t uInPixel = *pInPixel; - - imageData[y * g_nOutputWidth * kCompCnt + x * kCompCnt + 0] = (uInPixel & (0b1111111111 << 20)) >> 20; - imageData[y * g_nOutputWidth * kCompCnt + x * kCompCnt + 1] = (uInPixel & (0b1111111111 << 10)) >> 10; - imageData[y * g_nOutputWidth * kCompCnt + x * kCompCnt + 2] = (uInPixel & (0b1111111111 << 0)) >> 0; - } - } - - assert( HAVE_AVIF ); -#if HAVE_AVIF - avifResult avifResult = AVIF_RESULT_OK; - - avifImage *pAvifImage = avifImageCreate( g_nOutputWidth, g_nOutputHeight, 10, AVIF_PIXEL_FORMAT_YUV444 ); - defer( avifImageDestroy( pAvifImage ) ); - pAvifImage->yuvRange = AVIF_RANGE_FULL; - pAvifImage->colorPrimaries = bHDRScreenshot ? AVIF_COLOR_PRIMARIES_BT2020 : AVIF_COLOR_PRIMARIES_BT709; - pAvifImage->transferCharacteristics = bHDRScreenshot ? AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084 : AVIF_TRANSFER_CHARACTERISTICS_SRGB; - // We are not actually using YUV, but storing raw GBR (yes not RGB) data - // This does not compress as well, but is always lossless! - pAvifImage->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; - - if ( oScreenshotInfo->eScreenshotType == GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER ) - { - // When dumping the screen output buffer for debugging, - // mark the primaries as UNKNOWN as stuff has likely been transformed - // to native if HDR on Deck OLED etc. - // We want everything to be seen unadulterated by a viewer/image editor. - pAvifImage->colorPrimaries = AVIF_COLOR_PRIMARIES_UNKNOWN; - } - - if ( bHDRScreenshot ) - { - pAvifImage->clli.maxCLL = maxCLLNits; - pAvifImage->clli.maxPALL = maxFALLNits; - } - - avifRGBImage rgbAvifImage{}; - avifRGBImageSetDefaults( &rgbAvifImage, pAvifImage ); - rgbAvifImage.format = AVIF_RGB_FORMAT_RGB; - rgbAvifImage.ignoreAlpha = AVIF_TRUE; - - rgbAvifImage.pixels = (uint8_t *)imageData.data(); - rgbAvifImage.rowBytes = g_nOutputWidth * kCompCnt * sizeof( uint16_t ); - - if ( ( avifResult = avifImageRGBToYUV( pAvifImage, &rgbAvifImage ) ) != AVIF_RESULT_OK ) // Not really! See Matrix Coefficients IDENTITY above. - { - xwm_log.errorf( "Failed to convert RGB to YUV: %u", avifResult ); - return; - } - - avifEncoder *pEncoder = avifEncoderCreate(); - defer( avifEncoderDestroy( pEncoder ) ); - pEncoder->quality = AVIF_QUALITY_LOSSLESS; - pEncoder->qualityAlpha = AVIF_QUALITY_LOSSLESS; - pEncoder->speed = AVIF_SPEED_FASTEST; - - if ( ( avifResult = avifEncoderAddImage( pEncoder, pAvifImage, 1, AVIF_ADD_IMAGE_FLAG_SINGLE ) ) != AVIF_RESULT_OK ) - { - xwm_log.errorf( "Failed to add image to avif encoder: %u", avifResult ); - return; - } - - avifRWData avifOutput = AVIF_DATA_EMPTY; - defer( avifRWDataFree( &avifOutput ) ); - if ( ( avifResult = avifEncoderFinish( pEncoder, &avifOutput ) ) != AVIF_RESULT_OK ) - { - xwm_log.errorf( "Failed to finish encoder: %u", avifResult ); - return; - } - - FILE *pScreenshotFile = nullptr; - if ( ( pScreenshotFile = fopen( oScreenshotInfo->szScreenshotPath.c_str(), "wb" ) ) == nullptr ) - { - xwm_log.errorf( "Failed to fopen file: %s", oScreenshotInfo->szScreenshotPath.c_str() ); - return; - } - - fwrite( avifOutput.data, 1, avifOutput.size, pScreenshotFile ); - fclose( pScreenshotFile ); - - xwm_log.infof( "Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - bScreenshotSuccess = true; -#endif - } - else if (pScreenshotTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) - { - // Make our own copy of the image to remove the alpha channel. - auto imageData = std::vector(currentOutputWidth * currentOutputHeight * 4); - const uint32_t comp = 4; - const uint32_t pitch = currentOutputWidth * comp; - for (uint32_t y = 0; y < currentOutputHeight; y++) - { - for (uint32_t x = 0; x < currentOutputWidth; x++) - { - // BGR... - imageData[y * pitch + x * comp + 0] = mappedData[y * pScreenshotTexture->rowPitch() + x * comp + 2]; - imageData[y * pitch + x * comp + 1] = mappedData[y * pScreenshotTexture->rowPitch() + x * comp + 1]; - imageData[y * pitch + x * comp + 2] = mappedData[y * pScreenshotTexture->rowPitch() + x * comp + 0]; - imageData[y * pitch + x * comp + 3] = 255; - } - } - if ( stbi_write_png( oScreenshotInfo->szScreenshotPath.c_str(), currentOutputWidth, currentOutputHeight, 4, imageData.data(), pitch ) ) - { - xwm_log.infof( "Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - bScreenshotSuccess = true; - } - else - { - xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - } - } - else if (pScreenshotTexture->format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) - { - FILE *file = fopen( oScreenshotInfo->szScreenshotPath.c_str(), "wb" ); - if (file) - { - fwrite(mappedData, 1, pScreenshotTexture->totalSize(), file ); - fclose(file); - - bScreenshotSuccess = true; - xwm_log.infof("Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str()); - -#if 0 - char cmd[4096]; - sprintf(cmd, "ffmpeg -f rawvideo -pixel_format nv12 -video_size %dx%d -i %s %s_encoded.png", pScreenshotTexture->width(), pScreenshotTexture->height(), oScreenshotInfo->szScreenshotPath.c_str(), oScreenshotInfo->szScreenshotPath.c_str() ); - - int ret = system(cmd); - - /* Above call may fail, ffmpeg returns 0 on success */ - if (ret) { - xwm_log.infof("Ffmpeg call return status %i", ret); - xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - } else { - xwm_log.infof("Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str()); - } -#endif - } - else - { - xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - } - } - - if ( oScreenshotInfo->bX11PropertyRequested ) - { - XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); - XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDebugScreenShotAtom ); - } - - if ( bScreenshotSuccess && oScreenshotInfo->bWaylandRequested ) - { - wlserver_lock(); - for ( const auto &control : wlserver.gamescope_controls ) - { - gamescope_control_send_screenshot_taken( control, oScreenshotInfo->szScreenshotPath.c_str() ); - } - wlserver_unlock(); - } - }); - - screenshotThread.detach(); - } - else - { - xwm_log.errorf( "Oh no, we ran out of screenshot images. Not actually writing a screenshot." ); - if ( oScreenshotInfo->bX11PropertyRequested ) - { - XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); - XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDebugScreenShotAtom ); - } - } - } - - - gpuvis_trace_end_ctx_printf( paintID, "paint_all" ); - gpuvis_trace_printf( "paint_all %i layers", (int)frameInfo.layerCount ); -} - -/* Get prop from window - * not found: default - * otherwise the value - */ -__attribute__((__no_sanitize_address__)) // x11 broken, returns format 32 even when it only malloc'ed one byte. :( -static unsigned int -get_prop(xwayland_ctx_t *ctx, Window win, Atom prop, unsigned int def, bool *found = nullptr ) -{ - Atom actual; - int format; - unsigned long n, left; - - unsigned char *data; - int result = XGetWindowProperty(ctx->dpy, win, prop, 0L, 1L, false, - XA_CARDINAL, &actual, &format, - &n, &left, &data); - if (result == Success && data != NULL) - { - unsigned int i; - memcpy(&i, data, sizeof(unsigned int)); - XFree((void *) data); - if ( found != nullptr ) - { - *found = true; - } - return i; - } - if ( found != nullptr ) - { - *found = false; - } - return def; -} - -// vectored version, return value is whether anything was found -__attribute__((__no_sanitize_address__)) // x11 broken :( -bool get_prop( xwayland_ctx_t *ctx, Window win, Atom prop, std::vector< uint32_t > &vecResult ) -{ - Atom actual; - int format; - unsigned long n, left; - - vecResult.clear(); - uint64_t *data; - int result = XGetWindowProperty(ctx->dpy, win, prop, 0L, ~0UL, false, - XA_CARDINAL, &actual, &format, - &n, &left, ( unsigned char** )&data); - if (result == Success && data != NULL) - { - for ( uint32_t i = 0; i < n; i++ ) - { - vecResult.push_back( data[ i ] ); - } - XFree((void *) data); - return true; - } - return false; -} - -std::optional get_u64_prop( xwayland_ctx_t *ctx, Window win, Atom prop ) -{ - std::vector< uint32_t > values; - get_prop( ctx, win, prop, values ); - - if ( values.size() != 2 ) - return std::nullopt; - - return ((uint64_t)(values[0])) | (((uint64_t)values[1]) << 32ul ); -} - -std::string get_string_prop( xwayland_ctx_t *ctx, Window win, Atom prop ) -{ - XTextProperty tp; - if ( !XGetTextProperty( ctx->dpy, win, &tp, prop ) ) - return ""; - - std::string value = reinterpret_cast( tp.value ); - XFree( tp.value ); - return value; -} - -void set_string_prop( xwayland_ctx_t *ctx, Atom prop, const std::string &value ) -{ - XTextProperty text_property = - { - .value = ( unsigned char * )value.c_str(), - .encoding = ctx->atoms.utf8StringAtom, - .format = 8, - .nitems = strlen( value.c_str() ) - }; - XSetTextProperty( ctx->dpy, ctx->root, &text_property, prop); - XFlush( ctx->dpy ); -} - -void clear_prop( xwayland_ctx_t *ctx, Atom prop ) -{ - XDeleteProperty( ctx->dpy, ctx->root, prop ); - XFlush( ctx->dpy ); -} - -static bool -win_has_game_id( steamcompmgr_win_t *w ) -{ - return w->appID != 0; -} - -static bool -win_is_useless( steamcompmgr_win_t *w ) -{ - if ( w->isSysTrayIcon ) - return true; - - // Windows that are 1x1 are pretty useless for override redirects. - // Just ignore them. - // Fixes the Xbox Login in Age of Empires 2: DE. - return w->GetGeometry().nWidth == 1 && w->GetGeometry().nHeight == 1; -} - -static bool -win_is_override_redirect( steamcompmgr_win_t *w ) -{ - if (w->type != steamcompmgr_win_type_t::XWAYLAND) - return false; - - return w->xwayland().a.override_redirect && !w->ignoreOverrideRedirect && !win_is_useless( w ); -} - -static bool -win_skip_taskbar_and_pager( steamcompmgr_win_t *w ) -{ - return w->skipTaskbar && w->skipPager; -} - -static bool -win_skip_and_not_fullscreen( steamcompmgr_win_t *w ) -{ - return win_skip_taskbar_and_pager( w ) && !w->isFullscreen; -} - -static bool -win_maybe_a_dropdown( steamcompmgr_win_t *w ) -{ - if ( w->type != steamcompmgr_win_type_t::XWAYLAND ) - return false; - - // Josh: - // Right now we don't get enough info from Wine - // about the true nature of windows to distringuish - // something like the Fallout 4 Options menu from the - // Warframe language dropdown. Until we get more stuff - // exposed for that, there is this workaround to let that work. - if ( w->appID == 230410 && w->maybe_a_dropdown && w->xwayland().transientFor && ( w->skipPager || w->skipTaskbar ) ) - return !win_is_useless( w ); - - // Work around Antichamber splash screen until we hook up - // the Proton window style deduction. - if ( w->appID == 219890 ) - return false; - - // The Launcher in Witcher 2 (20920) has a clear window with WS_EX_LAYERED on top of it. - // - // The Age of Empires 2 Launcher also has a WS_EX_LAYERED window to separate controls - // from its backing, which this seems to handle, although we seemingly don't handle - // it's transparency yet, which I do not understand. - // - // Layered windows are windows that are meant to be transparent - // with alpha blending + visual fx. - // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features - // - // TODO: Come back to me for original Age of Empires HD launcher. - // Does that use it? It wants blending! - // - // Only do this if we have CONTROLPARENT right now. Some other apps, such as the - // Street Fighter V (310950) Splash Screen also use LAYERED and TOOLWINDOW, and we don't - // want that to be overlayed. - // Ignore LAYERED if it's marked as top-level with WS_EX_APPWINDOW. - // TODO: Find more apps using LAYERED. - const uint32_t validLayered = WS_EX_CONTROLPARENT | WS_EX_LAYERED; - const uint32_t invalidLayered = WS_EX_APPWINDOW; - if ( w->hasHwndStyleEx && - ( ( w->hwndStyleEx & validLayered ) == validLayered ) && - ( ( w->hwndStyleEx & invalidLayered ) == 0 ) ) - return true; - - // Forza Horizon 4 & 5 create a black background window that might incorrectly - // be considered a valid dropdown and steal focus. - if ( ( w->appID == 1293830 || w->appID == 1551360 ) && - w->maybe_a_dropdown && w->requestedWidth == 0 && w->requestedHeight == 0 ) - return false; - - // Josh: - // The logic here is as follows. The window will be treated as a dropdown if: - // - // If this window has a fixed position on the screen + static gravity: - // - If the window has either skipPage or skipTaskbar - // - If the window isn't a dialog, always treat it as a dropdown, as it's - // probably meant to be some form of popup. - // - If the window is a dialog - // - If the window has transient for, disregard it, as it is trying to redirecting us elsewhere - // ie. a settings menu dialog popup or something. - // - If the window has both skip taskbar and pager, treat it as a dialog. - bool valid_maybe_a_dropdown = - w->maybe_a_dropdown && ( ( !w->is_dialog || ( !w->xwayland().transientFor && win_skip_and_not_fullscreen( w ) ) ) && ( w->skipPager || w->skipTaskbar ) ); - return ( valid_maybe_a_dropdown || win_is_override_redirect( w ) ) && !win_is_useless( w ); -} - -static bool -win_is_disabled( steamcompmgr_win_t *w ) -{ - if ( !w->hasHwndStyle ) - return false; - - return !!(w->hwndStyle & WS_DISABLED); -} - -/* Returns true if a's focus priority > b's. - * - * This function establishes a list of criteria to decide which window should - * have focus. The first criteria has higher priority. If the first criteria - * is a tie, fallback to the second one, then the third, and so on. - * - * The general workflow is: - * - * if ( windows don't have the same criteria value ) - * return true if a should be focused; - * // This is a tie, fallback to the next criteria - */ -static bool -is_focus_priority_greater( steamcompmgr_win_t *a, steamcompmgr_win_t *b ) -{ - if ( win_has_game_id( a ) != win_has_game_id( b ) ) - return win_has_game_id( a ); - - // We allow using an override redirect window in some cases, but if we have - // a choice between two windows we always prefer the non-override redirect - // one. - if ( win_is_override_redirect( a ) != win_is_override_redirect( b ) ) - return !win_is_override_redirect( a ); - - // If the window is 1x1 then prefer anything else we have. - if ( win_is_useless( a ) != win_is_useless( b ) ) - return !win_is_useless( a ); - - if ( win_maybe_a_dropdown( a ) != win_maybe_a_dropdown( b ) ) - return !win_maybe_a_dropdown( a ); - - if ( win_is_disabled( a ) != win_is_disabled( b ) ) - return !win_is_disabled( a ); - - // Wine sets SKIP_TASKBAR and SKIP_PAGER hints for WS_EX_NOACTIVATE windows. - // See https://github.com/Plagman/gamescope/issues/87 - if ( win_skip_and_not_fullscreen( a ) != win_skip_and_not_fullscreen( b ) ) - return !win_skip_and_not_fullscreen( a ); - - // Prefer normal windows over dialogs - // if we are an override redirect/dropdown window. - if ( win_maybe_a_dropdown( a ) && win_maybe_a_dropdown( b ) && - a->is_dialog != b->is_dialog ) - return !a->is_dialog; - - if (a->type != steamcompmgr_win_type_t::XWAYLAND) - { - return true; - } - - // Attempt to tie-break dropdowns by transient-for. - if ( win_maybe_a_dropdown( a ) && win_maybe_a_dropdown( b ) && - !a->xwayland().transientFor != !b->xwayland().transientFor ) - return !a->xwayland().transientFor; - - if ( win_has_game_id( a ) && a->xwayland().map_sequence != b->xwayland().map_sequence ) - return a->xwayland().map_sequence > b->xwayland().map_sequence; - - // The damage sequences are only relevant for game windows. - if ( win_has_game_id( a ) && a->xwayland().damage_sequence != b->xwayland().damage_sequence ) - return a->xwayland().damage_sequence > b->xwayland().damage_sequence; - - return false; -} - -static bool is_good_override_candidate( steamcompmgr_win_t *override, steamcompmgr_win_t* focus ) -{ - // Some Chrome/Edge dropdowns (ie. FH5 xbox login) will automatically close themselves if you - // focus them while they are meant to be offscreen (-1,-1 and 1x1) so check that the - // override's position is on-screen. - if ( !focus ) - return false; - - // The pids should probably match for a dropdown to be a good candidate for this window. - if (override->pid != focus->pid) - return false; - - auto rect = override->GetGeometry(); - return override != focus && (rect.nX + rect.nWidth) > 0 && (rect.nY + rect.nHeight) > 0; -} - -static void -handle_desktop_window(steamcompmgr_win_t *w); - -static bool -pick_primary_focus_and_override( - focus_t *out, - Window focusControlWindow, - const std::vector& vecPossibleFocusWindows, - bool globalFocus, - const std::vector& ctxFocusControlAppIDs, - uint64_t ulVirtualFocusKey, - gamescope::VirtualConnectorStrategy eStrategy ) -{ - bool localGameFocused = false; - steamcompmgr_win_t *focus = NULL, *override_focus = NULL; - - bool controlledFocus = eStrategy != gamescope::VirtualConnectorStrategies::SingleApplication || focusControlWindow != None || !ctxFocusControlAppIDs.empty(); - if ( controlledFocus ) - { - if ( eStrategy == gamescope::VirtualConnectorStrategies::SteamControlled ) - { - if ( focusControlWindow != None ) - { - for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) - { - if ( focusable_window->type != steamcompmgr_win_type_t::XWAYLAND ) - continue; - - if ( focusable_window->xwayland().id == focusControlWindow ) - { - focus = focusable_window; - localGameFocused = true; - goto found; - } - } - } - - for ( auto focusable_appid : ctxFocusControlAppIDs ) - { - for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) - { - if ( focusable_window->appID == focusable_appid ) - { - focus = focusable_window; - localGameFocused = true; - goto found; - } - } - } - } - else - { - for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) - { - if ( focusable_window->GetVirtualConnectorKey( eStrategy ) == ulVirtualFocusKey ) - { - focus = focusable_window; - localGameFocused = true; - goto found; - } - } - } - -found:; - } - - if ( !focus && ( !globalFocus || !controlledFocus ) ) - { - if ( !vecPossibleFocusWindows.empty() ) - { - focus = vecPossibleFocusWindows[ 0 ]; - localGameFocused = focus->appID != 0; - } - } - - auto resolveTransientOverrides = [&](bool maybe) - { - if ( !focus || focus->type != steamcompmgr_win_type_t::XWAYLAND ) - return; - - // Do some searches to find transient links to override redirects too. - while ( true ) - { - bool bFoundTransient = false; - - for ( steamcompmgr_win_t *candidate : vecPossibleFocusWindows ) - { - if ( candidate->type != steamcompmgr_win_type_t::XWAYLAND ) - continue; - - bool is_dropdown = maybe ? win_maybe_a_dropdown( candidate ) : win_is_override_redirect( candidate ); - if ( ( !override_focus || candidate != override_focus ) && candidate != focus && - ( ( !override_focus && candidate->xwayland().transientFor == focus->xwayland().id ) || ( override_focus && candidate->xwayland().transientFor == override_focus->xwayland().id ) ) && - is_dropdown) - { - bFoundTransient = true; - override_focus = candidate; - break; - } - } - - // Hopefully we can't have transient cycles or we'll have to maintain a list of visited windows here - if ( bFoundTransient == false ) - break; - } - }; - - if ( focus && focus->type == steamcompmgr_win_type_t::XWAYLAND ) - { - if ( !focusControlWindow ) - { - // Do some searches through game windows to follow transient links if needed - while ( true ) - { - bool bFoundTransient = false; - - for ( steamcompmgr_win_t *candidate : vecPossibleFocusWindows ) - { - if ( candidate->type != steamcompmgr_win_type_t::XWAYLAND ) - continue; - - if ( candidate != focus && candidate->xwayland().transientFor == focus->xwayland().id && !win_maybe_a_dropdown( candidate ) ) - { - bFoundTransient = true; - focus = candidate; - break; - } - } - - // Hopefully we can't have transient cycles or we'll have to maintain a list of visited windows here - if ( bFoundTransient == false ) - break; - } - } - - if ( !override_focus ) - { - if ( !ctxFocusControlAppIDs.empty() ) - { - for ( steamcompmgr_win_t *override : vecPossibleFocusWindows ) - { - if ( win_is_override_redirect(override) && is_good_override_candidate(override, focus) && override->appID == focus->appID ) { - override_focus = override; - break; - } - } - } - else if ( !vecPossibleFocusWindows.empty() ) - { - for ( steamcompmgr_win_t *override : vecPossibleFocusWindows ) - { - if ( win_is_override_redirect(override) && is_good_override_candidate(override, focus) ) { - override_focus = override; - break; - } - } - } - - resolveTransientOverrides( false ); - } - } - - if ( focus ) - { - if ( window_has_commits( focus ) ) - out->focusWindow = focus; - else - focus->outdatedInteractiveFocus = true; - - // Always update X's idea of focus, but still dirty - // the it being outdated so we can resolve that globally later. - // - // Only affecting X and not the WL idea of focus here, - // we always want to think the window is focused. - // but our real presenting focus and input focus can be elsewhere. - if ( !globalFocus ) - out->focusWindow = focus; - } - - if ( !override_focus && focus ) - { - if ( controlledFocus ) - { - for ( auto focusable_appid : ctxFocusControlAppIDs ) - { - for ( steamcompmgr_win_t *fake_override : vecPossibleFocusWindows ) - { - if ( fake_override->appID == focusable_appid ) - { - if ( win_maybe_a_dropdown( fake_override ) && is_good_override_candidate( fake_override, focus ) && fake_override->appID == focus->appID ) - { - override_focus = fake_override; - goto found2; - } - } - } - } - } - else - { - for ( steamcompmgr_win_t *fake_override : vecPossibleFocusWindows ) - { - if ( win_maybe_a_dropdown( fake_override ) && is_good_override_candidate( fake_override, focus ) ) - { - override_focus = fake_override; - goto found2; - } - } - } - - found2:; - resolveTransientOverrides( true ); - } - - out->overrideWindow = override_focus; - - return localGameFocused; -} - - std::vector< steamcompmgr_win_t* > xwayland_ctx_t::GetPossibleFocusWindows() - { - std::vector vecPossibleFocusWindows; - - for (steamcompmgr_win_t *w = this->list; w; w = w->xwayland().next) - { - // Always skip system tray icons and overlays - if ( w->isSysTrayIcon || w->isOverlay || w->isExternalOverlay ) - { - continue; - } - - // Skip overlay targets - if ( w->oulTargetVROverlay && !cv_vr_show_forwarded_overlays ) - { - continue; - } - - // Skip streaming client video window - if ( w->isSteamStreamingClientVideo ) - { - continue; - } - - if ( gamescope::cv_backend_virtual_connector_strategy == gamescope::VirtualConnectorStrategies::SteamControlled ) - { - if ( !( win_has_game_id( w ) || window_is_steam( w ) || w->isSteamStreamingClient ) ) - continue; - } - - if ( w->xwayland().a.map_state == IsViewable && w->xwayland().a.c_class == InputOutput && - (w->opacity > TRANSLUCENT || w->isSteamStreamingClient ) ) - { - vecPossibleFocusWindows.push_back( w ); - } - } - - std::stable_sort( vecPossibleFocusWindows.begin(), vecPossibleFocusWindows.end(), is_focus_priority_greater ); - - return vecPossibleFocusWindows; - } - -static void set_wm_state( xwayland_ctx_t *ctx, Window win, uint32_t state ) -{ - uint32_t wmState[] = { state, None }; - XChangeProperty(ctx->dpy, win, ctx->atoms.WMStateAtom, ctx->atoms.WMStateAtom, 32, - PropModeReplace, (unsigned char *)wmState, - sizeof(wmState) / sizeof(wmState[0])); -} - -void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win_t* > &vecPossibleFocusWindows ) -{ - xwayland_ctx_t *ctx = this; - - steamcompmgr_win_t *inputFocus = NULL; - - steamcompmgr_win_t *prevFocusWindow = ctx->focus.focusWindow; - ctx->focus.overlayWindow = nullptr; - ctx->focus.notificationWindow = nullptr; - ctx->focus.overrideWindow = nullptr; - ctx->focus.externalOverlayWindow = nullptr; - - unsigned int maxOpacity = 0; - unsigned int maxOpacityExternal = 0; - for (steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next) - { - if (w->isOverlay) - { - if (w->GetGeometry().nWidth > 1200 && w->opacity >= maxOpacity) - { - ctx->focus.overlayWindow = w; - maxOpacity = w->opacity; - } - else - { - ctx->focus.notificationWindow = w; - } - } - - if (w->isExternalOverlay) - { - if (w->opacity > maxOpacityExternal) - { - ctx->focus.externalOverlayWindow = w; - maxOpacityExternal = w->opacity; - } - } - - if ( gamescope::VirtualConnectorIsSingleOutput() ) - { - if ( w->isOverlay && w->inputFocusMode ) - { - inputFocus = w; - } - } - } - - gamescope::VirtualConnectorStrategy eStrategy = gamescope::VirtualConnectorStrategies::PerWindow; - gamescope::VirtualConnectorKey_t ulKey = 0; - - if ( ctx->xwayland_server->get_index() != 0 ) - { - eStrategy = gamescope::VirtualConnectorStrategies::SteamControlled; - ulKey = 0; - } - else if ( GetBackend() && GetBackend()->GetCurrentConnector() ) - { - eStrategy = gamescope::cv_backend_virtual_connector_strategy; - ulKey = GetBackend()->GetCurrentConnector()->GetVirtualConnectorKey(); - } - - pick_primary_focus_and_override( &ctx->focus, ctx->focusControlWindow, vecPossibleFocusWindows, false, vecFocuscontrolAppIDs, ulKey, eStrategy ); - - if ( !ctx->focus.overrideWindowMouse ) - { - ctx->focus.overrideWindowMouse = ctx->focus.overrideWindow; - } - - if ( inputFocus == NULL ) - { - inputFocus = ctx->focus.focusWindow; - } - - if ( ctx->focus.focusWindow ) - { - if ( prevFocusWindow != ctx->focus.focusWindow ) - { - /* Some games (e.g. DOOM Eternal) don't react well to being put back as - * iconic, so never do that. Only take them out of iconic. */ - set_wm_state( ctx, ctx->focus.focusWindow->xwayland().id, ICCCM_NORMAL_STATE ); - - gpuvis_trace_printf( "determine_and_apply_focus focus %lu", ctx->focus.focusWindow->xwayland().id ); - - if ( debugFocus == true ) - { - xwm_log.debugf( "determine_and_apply_focus focus %lu", ctx->focus.focusWindow->xwayland().id ); - char buf[512]; - sprintf( buf, "xwininfo -id 0x%lx; xprop -id 0x%lx; xwininfo -root -tree", ctx->focus.focusWindow->xwayland().id, ctx->focus.focusWindow->xwayland().id ); - system( buf ); - } - } - } - - steamcompmgr_win_t *keyboardFocusWin = inputFocus; - - if ( gamescope::VirtualConnectorIsSingleOutput() ) - { - if ( inputFocus && inputFocus->inputFocusMode == 2 ) - keyboardFocusWin = ctx->focus.focusWindow; - } - else - { - // Handle mouse focus being totally disjoint from KB in VR. - if ( GetBackend() && GetBackend()->GetCurrentMouseConnector() ) - { - gamescope::VirtualConnectorKey_t ulMouseKey = GetBackend()->GetCurrentMouseConnector()->GetVirtualConnectorKey(); - - focus_t mouse_focus{}; - pick_primary_focus_and_override( &mouse_focus, ctx->focusControlWindow, vecPossibleFocusWindows, false, vecFocuscontrolAppIDs, ulMouseKey, eStrategy ); - - if ( mouse_focus.overrideWindow || mouse_focus.focusWindow ) - { - inputFocus = mouse_focus.overrideWindow ? mouse_focus.overrideWindow : mouse_focus.focusWindow; - ctx->focus.overrideWindowMouse = mouse_focus.overrideWindow; - } - } - - uint64_t ulFocusedKeyboardOverlayVR = g_FocusedVROverlayKeyboard; - uint64_t ulFocusedMouseOverlayVR = g_FocusedVROverlayMouse; - - if ( ulFocusedKeyboardOverlayVR || ulFocusedMouseOverlayVR ) - { - for ( steamcompmgr_win_t *queryWindow = ctx->list; queryWindow; queryWindow = queryWindow->xwayland().next ) - { - if ( queryWindow->oulTargetVROverlay && *queryWindow->oulTargetVROverlay == ulFocusedKeyboardOverlayVR ) - { - focus_log.debugf( "[XWL] Overriding keyboard focus window with VR forwarder overlay! Overlay: 0x%lx XWindow: 0x%x Title: %s", ulFocusedKeyboardOverlayVR, queryWindow->id(), queryWindow->debug_name() ); - - keyboardFocusWin = queryWindow; - ctx->focus.focusWindow = queryWindow; - - // No support for overrides with this VR path! - ctx->focus.overrideWindow = nullptr; - ctx->focus.overrideWindowMouse = nullptr; - } - - if ( queryWindow->oulTargetVROverlay && *queryWindow->oulTargetVROverlay == ulFocusedMouseOverlayVR ) - { - // We don't want to do any mouse input for target VR overlays right now. - // SteamWebHelper is handling this. - - if ( !inputFocus ) - inputFocus = queryWindow; - - // No support for overrides with this VR path! - ctx->focus.overrideWindow = nullptr; - ctx->focus.overrideWindowMouse = nullptr; - } - } - } - } - - if ( !ctx->focus.focusWindow ) - { - return; - } - - if ( !inputFocus ) - { - inputFocus = ctx->focus.focusWindow; - } - - Window keyboardFocusWindow = keyboardFocusWin ? keyboardFocusWin->xwayland().id : None; - - // If the top level parent of our current keyboard window is the same as our target (top level) input focus window - // then keep focus on that and don't yank it away to the top level input focus window. - // Fixes dropdowns in Steam CEF. - if ( keyboardFocusWindow && ctx->currentKeyboardFocusWindow && find_win( ctx, ctx->currentKeyboardFocusWindow ) == keyboardFocusWin ) - keyboardFocusWindow = ctx->currentKeyboardFocusWindow; - - if ( ctx->focus.inputFocusWindow != inputFocus || - ctx->focus.inputFocusMode != inputFocus->inputFocusMode || - ctx->currentKeyboardFocusWindow != keyboardFocusWindow ) - { - if ( debugFocus == true ) - { - xwm_log.debugf( "determine_and_apply_focus inputFocus %lu", inputFocus->xwayland().id ); - } - - if ( !ctx->focus.overrideWindow || ctx->focus.overrideWindow != keyboardFocusWin ) - XSetInputFocus(ctx->dpy, keyboardFocusWin->xwayland().id, RevertToNone, CurrentTime); - - if ( ctx->focus.inputFocusWindow != inputFocus || - ctx->focus.inputFocusMode != inputFocus->inputFocusMode ) - { - // If the window doesn't want focus when hidden, move it away - // as we are going to hide it straight after. - // otherwise, if we switch from wanting it to not - // (steam -> game) - // put us back in the centre of the screen. - if (window_wants_no_focus_when_mouse_hidden(inputFocus)) - ctx->focus.bResetToCorner = true; - else if ( window_wants_no_focus_when_mouse_hidden(inputFocus) != window_wants_no_focus_when_mouse_hidden(ctx->focus.inputFocusWindow) ) - ctx->focus.bResetToCenter = true; - - // cursor is likely not interactable anymore in its original context, hide - // don't care if we change kb focus window due to that happening when - // going from override -> focus and we don't want to hide then as it's probably a dropdown. - ctx->cursor->hide(); - } - - ctx->focus.inputFocusWindow = inputFocus; - ctx->focus.inputFocusMode = inputFocus->inputFocusMode; - ctx->currentKeyboardFocusWindow = keyboardFocusWindow; - } - - steamcompmgr_win_t *w; - w = ctx->focus.focusWindow; - - if ( inputFocus == ctx->focus.focusWindow && ctx->focus.overrideWindowMouse ) - { - if ( ctx->list[0].xwayland().id != ctx->focus.overrideWindowMouse->xwayland().id ) - { - XRaiseWindow(ctx->dpy, ctx->focus.overrideWindowMouse->xwayland().id); - } - } - else - { - if ( ctx->list[0].xwayland().id != inputFocus->xwayland().id ) - { - XRaiseWindow(ctx->dpy, inputFocus->xwayland().id); - } - } - - if (!ctx->focus.focusWindow->nudged) - { - XMoveWindow(ctx->dpy, ctx->focus.focusWindow->xwayland().id, 1, 1); - ctx->focus.focusWindow->nudged = true; - } - - if (w->GetGeometry().nX != 0 || w->GetGeometry().nY != 0) - XMoveWindow(ctx->dpy, ctx->focus.focusWindow->xwayland().id, 0, 0); - - if ( win_has_game_id( w ) ) - { - if ( window_is_fullscreen( ctx->focus.focusWindow ) || ctx->force_windows_fullscreen ) - { - bool bIsSteam = window_is_steam( ctx->focus.focusWindow ); - int fs_width = ctx->root_width; - int fs_height = ctx->root_height; - if ( bIsSteam && g_nSteamMaxHeight && ctx->root_height > g_nSteamMaxHeight ) - { - float steam_height_scale = g_nSteamMaxHeight / (float)ctx->root_height; - fs_height = g_nSteamMaxHeight; - fs_width = ctx->root_width * steam_height_scale; - } - - if ( w->GetGeometry().nWidth != fs_width || w->GetGeometry().nHeight != fs_height || globalScaleRatio != 1.0f ) - XResizeWindow(ctx->dpy, ctx->focus.focusWindow->xwayland().id, fs_width, fs_height); - } - else - { - if (ctx->focus.focusWindow->sizeHintsSpecified && - ((unsigned)ctx->focus.focusWindow->GetGeometry().nWidth != ctx->focus.focusWindow->requestedWidth || - (unsigned)ctx->focus.focusWindow->GetGeometry().nHeight != ctx->focus.focusWindow->requestedHeight)) - { - XResizeWindow(ctx->dpy, ctx->focus.focusWindow->xwayland().id, ctx->focus.focusWindow->requestedWidth, ctx->focus.focusWindow->requestedHeight); - } - } - } - else - { - handle_desktop_window( w ); - } - - Window root_return = None, parent_return = None; - Window *children = NULL; - unsigned int nchildren = 0; - unsigned int i = 0; - - XQueryTree(ctx->dpy, w->xwayland().id, &root_return, &parent_return, &children, &nchildren); - - while (i < nchildren) - { - XSelectInput( ctx->dpy, children[i], FocusChangeMask ); - i++; - } - - XFree(children); - - ctx->focus.ulCurrentFocusSerial = GetFocusSerial(); -} - -wlr_surface *win_surface(steamcompmgr_win_t *window) -{ - if (!window) - return nullptr; - - return window->main_surface(); -} - -const char *get_win_display_name(steamcompmgr_win_t *window) -{ - if ( window->type == steamcompmgr_win_type_t::XWAYLAND ) - return window->xwayland().ctx->xwayland_server->get_nested_display_name(); - else if ( window->type == steamcompmgr_win_type_t::XDG ) - return wlserver_get_wl_display_name(); - else - return ""; -} - - -static std::vector< steamcompmgr_win_t* > -steamcompmgr_xdg_get_possible_focus_windows() -{ - std::vector< steamcompmgr_win_t* > windows; - for ( auto &win : g_steamcompmgr_xdg_wins ) - { - // Always skip system tray icons and overlays - if ( win->isSysTrayIcon || win->isOverlay || win->isExternalOverlay ) - { - continue; - } - - windows.emplace_back( win.get() ); - } - return windows; -} - -static std::vector< steamcompmgr_win_t* > GetGlobalPossibleFocusWindows() -{ - std::vector< steamcompmgr_win_t* > vecPossibleFocusWindows; - - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - std::vector< steamcompmgr_win_t* > vecLocalPossibleFocusWindows = server->ctx->GetPossibleFocusWindows(); - vecPossibleFocusWindows.insert( vecPossibleFocusWindows.end(), vecLocalPossibleFocusWindows.begin(), vecLocalPossibleFocusWindows.end() ); - } - } - - { - std::vector< steamcompmgr_win_t* > vecLocalPossibleFocusWindows = steamcompmgr_xdg_get_possible_focus_windows(); - vecPossibleFocusWindows.insert( vecPossibleFocusWindows.end(), vecLocalPossibleFocusWindows.begin(), vecLocalPossibleFocusWindows.end() ); - } - - // Determine global primary focus - std::stable_sort( vecPossibleFocusWindows.begin(), vecPossibleFocusWindows.end(), is_focus_priority_greater ); - - return vecPossibleFocusWindows; -} - -static void -steamcompmgr_xdg_determine_and_apply_focus( const std::vector< steamcompmgr_win_t* > &vecPossibleFocusWindows ) -{ - for ( auto &window : g_steamcompmgr_xdg_wins ) - { - if (window->isOverlay) - g_steamcompmgr_xdg_focus.overlayWindow = window.get(); - - if (window->isExternalOverlay) - g_steamcompmgr_xdg_focus.externalOverlayWindow = window.get(); - } - - gamescope::VirtualConnectorStrategy eStrategy = gamescope::cv_backend_virtual_connector_strategy == gamescope::VirtualConnectorStrategies::SteamControlled - ? gamescope::VirtualConnectorStrategies::SteamControlled - : gamescope::VirtualConnectorStrategies::PerWindow; - - pick_primary_focus_and_override( &g_steamcompmgr_xdg_focus, None, vecPossibleFocusWindows, false, vecFocuscontrolAppIDs, 0, eStrategy ); -} - -uint32_t g_focusedBaseAppId = 0; - -static void -determine_and_apply_focus( global_focus_t *pFocus ) -{ - gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0); - xwayland_ctx_t *root_ctx = root_server->ctx.get(); - global_focus_t previousLocalFocus = *pFocus; - *pFocus = global_focus_t{}; - pFocus->focusWindow = previousLocalFocus.focusWindow; - pFocus->cursor = root_ctx->cursor.get(); - pFocus->ulVirtualFocusKey = previousLocalFocus.ulVirtualFocusKey; - pFocus->pVirtualConnector = previousLocalFocus.pVirtualConnector; - gameFocused = false; - - focus_log.debugf( "Rerolling global focus..." ); - - std::vector< unsigned long > focusable_appids; - std::vector< unsigned long > focusable_windows; - - // Apply focus to the XWayland contexts. - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - std::vector< steamcompmgr_win_t* > vecLocalPossibleFocusWindows = server->ctx->GetPossibleFocusWindows(); - if ( server->ctx->focus.IsDirty() ) - server->ctx->DetermineAndApplyFocus( vecLocalPossibleFocusWindows ); - } - } - - // Apply focus to XDG contexts (TODO merge me with some nice abstraction of "environments") - { - std::vector< steamcompmgr_win_t* > vecLocalPossibleFocusWindows = steamcompmgr_xdg_get_possible_focus_windows(); - if ( g_steamcompmgr_xdg_focus.IsDirty() ) - steamcompmgr_xdg_determine_and_apply_focus( vecLocalPossibleFocusWindows ); - } - - // Determine local context focuses - std::vector vecPossibleFocusWindows = GetGlobalPossibleFocusWindows(); - - for ( steamcompmgr_win_t *focusable_window : vecPossibleFocusWindows ) - { - if ( focusable_window->type != steamcompmgr_win_type_t::XWAYLAND ) - continue; - - // Exclude windows that are useless (1x1), skip taskbar + pager or override redirect windows - // from the reported focusable windows to Steam. - if ( win_is_useless( focusable_window ) || - win_skip_and_not_fullscreen( focusable_window ) || - focusable_window->xwayland().a.override_redirect ) - continue; - - unsigned int unAppID = focusable_window->appID; - if ( unAppID != 0 ) - { - unsigned long j; - for( j = 0; j < focusable_appids.size(); j++ ) - { - if ( focusable_appids[ j ] == unAppID ) - { - break; - } - } - if ( j == focusable_appids.size() ) - { - focusable_appids.push_back( unAppID ); - } - } - - // list of [window, appid, pid] triplets - focusable_windows.push_back( focusable_window->xwayland().id ); - focusable_windows.push_back( focusable_window->appID ); - focusable_windows.push_back( focusable_window->pid ); - } - - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusableAppsAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)focusable_appids.data(), focusable_appids.size() ); - - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusableWindowsAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)focusable_windows.data(), focusable_windows.size() ); - - gameFocused = pick_primary_focus_and_override( pFocus, root_ctx->focusControlWindow, vecPossibleFocusWindows, true, vecFocuscontrolAppIDs, - pFocus->ulVirtualFocusKey, - gamescope::cv_backend_virtual_connector_strategy ); - - // Pick overlay/notifications from root ctx - pFocus->overlayWindow = root_ctx->focus.overlayWindow; - pFocus->externalOverlayWindow = root_ctx->focus.externalOverlayWindow; - pFocus->notificationWindow = root_ctx->focus.notificationWindow; - - if ( !pFocus->overlayWindow ) - { - pFocus->overlayWindow = g_steamcompmgr_xdg_focus.overlayWindow; - } - - if ( !pFocus->externalOverlayWindow ) - { - pFocus->externalOverlayWindow = g_steamcompmgr_xdg_focus.externalOverlayWindow; - } - - bool bUseOverlay = gamescope::VirtualConnectorIsSingleOutput() || gamescope::VirtualConnectorKeyIsSteam( pFocus->ulVirtualFocusKey ); - if ( !bUseOverlay ) - { - pFocus->overlayWindow = nullptr; - pFocus->notificationWindow = nullptr; - } - - // Pick inputFocusWindow - if ( gamescope::VirtualConnectorIsSingleOutput() && - pFocus->overlayWindow && pFocus->overlayWindow->inputFocusMode ) - { - pFocus->inputFocusWindow = pFocus->overlayWindow; - pFocus->keyboardFocusWindow = pFocus->overlayWindow; - } - else - { - pFocus->inputFocusWindow = pFocus->focusWindow; - pFocus->keyboardFocusWindow = pFocus->overrideWindow ? pFocus->overrideWindow : pFocus->focusWindow; - } - - if ( !gamescope::VirtualConnectorIsSingleOutput() ) - { - uint64_t ulFocusedKeyboardOverlayVR = g_FocusedVROverlayKeyboard; - uint64_t ulFocusedMouseOverlayVR = g_FocusedVROverlayMouse; - - focus_log.debugf( "Current focus VR overlays: keyboard 0x%lx | mouse 0x%lx", ulFocusedKeyboardOverlayVR, ulFocusedMouseOverlayVR ); - - if ( ulFocusedKeyboardOverlayVR || ulFocusedMouseOverlayVR ) - { - for ( steamcompmgr_win_t *queryWindow = root_ctx->list; queryWindow; queryWindow = queryWindow->xwayland().next ) - { - if ( queryWindow->oulTargetVROverlay && *queryWindow->oulTargetVROverlay == ulFocusedKeyboardOverlayVR ) - { - focus_log.debugf( "[WL GLOBAL] Overriding keyboard focus window with VR forwarder overlay! Overlay: 0x%lx XWindow: 0x%x Title: %s", ulFocusedKeyboardOverlayVR, queryWindow->id(), queryWindow->debug_name() ); - pFocus->keyboardFocusWindow = queryWindow; - - pFocus->overrideWindow = nullptr; - } - - if ( queryWindow->oulTargetVROverlay && *queryWindow->oulTargetVROverlay == ulFocusedMouseOverlayVR ) - { - // We don't want to do any mouse input for target VR overlays right now. - // SteamWebHelper is handling this. - - //pFocus->inputFocusWindow = queryWindow; - - pFocus->overrideWindow = nullptr; - } - } - } - } - - // Pick cursor from our input focus window - - // Initially pick cursor from the ctx of our input focus. - if (pFocus->inputFocusWindow) - { - if (pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND) - pFocus->cursor = pFocus->inputFocusWindow->xwayland().ctx->cursor.get(); - else - { - // TODO XDG: - // Implement cursor support. - // Probably want some form of abstraction here for - // wl cursor vs x11 cursor given we have virtual cursors. - // wlserver should update wl cursor pos xy directly. - static bool s_once = false; - if (!s_once) - { - xwm_log.errorf("NO CURSOR IMPL XDG"); - s_once = true; - } - } - } - - if (pFocus->inputFocusWindow) - pFocus->inputFocusMode = pFocus->inputFocusWindow->inputFocusMode; - - if ( gamescope::VirtualConnectorIsSingleOutput() && - pFocus->inputFocusMode == 2 ) - { - pFocus->keyboardFocusWindow = pFocus->overrideWindow - ? pFocus->overrideWindow - : pFocus->focusWindow; - } - - // TODO(strategy): multi-seat on Wayland side - if ( pFocus == GetCurrentFocus() ) - { - static gamescope::VirtualConnectorKey_t s_ulPreviousGlobalFocusKey; - - // Tell wlserver about our keyboard/mouse focus. - if (pFocus->keyboardFocusWindow != previousLocalFocus.keyboardFocusWindow || - pFocus->ulVirtualFocusKey != s_ulPreviousGlobalFocusKey ) - { - if ( win_surface(pFocus->inputFocusWindow) != nullptr || - win_surface(pFocus->keyboardFocusWindow) != nullptr ) - { - wlserver_lock(); - - if ( win_surface(pFocus->keyboardFocusWindow) != nullptr ) - { - wlserver_keyboardfocus( pFocus->keyboardFocusWindow->main_surface() ); - focus_log.debugf( "Setting keyboard focus to: %s (%x)", pFocus->keyboardFocusWindow->debug_name(), pFocus->keyboardFocusWindow->id() ); - } - - wlserver_unlock(); - } - } - - if ( pFocus->inputFocusWindow ) - { - // Cannot simply XWarpPointer here as we immediately go on to - // do wlserver_mousefocus and need to update m_x and m_y of the cursor. - if ( pFocus->inputFocusWindow->GetFocus()->bResetToCorner ) - { - wlserver_lock(); - wlserver_mousewarp( pFocus->inputFocusWindow->GetGeometry().nWidth / 2, pFocus->inputFocusWindow->GetGeometry().nHeight / 2, 0, true ); - wlserver_fake_mouse_pos( pFocus->inputFocusWindow->GetGeometry().nWidth - 1, pFocus->inputFocusWindow->GetGeometry().nHeight - 1 ); - wlserver_unlock(); - } - else if ( pFocus->inputFocusWindow->GetFocus()->bResetToCenter ) - { - wlserver_lock(); - wlserver_mousewarp( pFocus->inputFocusWindow->GetGeometry().nWidth / 2, pFocus->inputFocusWindow->GetGeometry().nHeight / 2, 0, true ); - wlserver_unlock(); - } - - pFocus->inputFocusWindow->GetFocus()->bResetToCorner = false; - pFocus->inputFocusWindow->GetFocus()->bResetToCenter = false; - } - - s_ulPreviousGlobalFocusKey = pFocus->ulVirtualFocusKey; - } - - if ( pFocus == GetCurrentMouseFocus() ) - { - static gamescope::VirtualConnectorKey_t s_ulPreviousGlobalFocusKey; - - // Tell wlserver about our keyboard/mouse focus. - if ( pFocus->inputFocusWindow != previousLocalFocus.inputFocusWindow || - pFocus->overrideWindow != previousLocalFocus.overrideWindow || - pFocus->ulVirtualFocusKey != s_ulPreviousGlobalFocusKey ) - { - if ( win_surface(pFocus->inputFocusWindow) != nullptr ) - { - wlserver_lock(); - - wlserver_clear_dropdowns(); - if ( win_surface( pFocus->overrideWindow ) != nullptr ) - wlserver_notify_dropdown( pFocus->overrideWindow->main_surface(), pFocus->overrideWindow->xwayland().a.x, pFocus->overrideWindow->xwayland().a.y ); - - if ( gamescope::VirtualConnectorInSteamPerAppState() && pFocus->inputFocusWindow ) - { - if ( pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND ) - { - xwayland_ctx_t *ctx = pFocus->inputFocusWindow->xwayland().ctx; - bool bTouchPointerEmulation = gamescope::VirtualConnectorKeyIsNonSteamWindow( pFocus->ulVirtualFocusKey ); - - if ( ctx->bTouchPointerEmulation != bTouchPointerEmulation ) - { - xwm_log.infof( "Changing touch pointer emulation for display %u to %s\n", ctx->xwayland_server->get_index(), bTouchPointerEmulation ? "true" : "false" ); - - uint32_t uValue = bTouchPointerEmulation ? 1 : 0; - XChangeProperty( ctx->dpy, ctx->root, ctx->atoms.steamosTouchPointerEmulation, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&uValue, 1 ); - ctx->bTouchPointerEmulation = bTouchPointerEmulation; - } - } - } - - if ( win_surface(pFocus->inputFocusWindow) != nullptr && pFocus->cursor ) - { - wlserver_mousefocus( pFocus->inputFocusWindow->main_surface(), pFocus->cursor->x(), pFocus->cursor->y() ); - focus_log.debugf( "Setting mouse focus to: %s (%x)", pFocus->inputFocusWindow->debug_name(), pFocus->inputFocusWindow->id() ); - } - - wlserver_unlock(); - } - - // Hide cursor on transitioning between xwaylands - // We already do this when transitioning input focus inside of an - // xwayland ctx. - // don't care if we change kb focus window due to that happening when - // going from override -> focus and we don't want to hide then as it's probably a dropdown. - if ( pFocus->cursor && pFocus->inputFocusWindow != previousLocalFocus.inputFocusWindow ) - pFocus->cursor->hide(); - } - - if ( pFocus->inputFocusWindow ) - { - // Cannot simply XWarpPointer here as we immediately go on to - // do wlserver_mousefocus and need to update m_x and m_y of the cursor. - if ( pFocus->inputFocusWindow->GetFocus()->bResetToCorner ) - { - wlserver_lock(); - wlserver_mousewarp( pFocus->inputFocusWindow->GetGeometry().nWidth / 2, pFocus->inputFocusWindow->GetGeometry().nHeight / 2, 0, true ); - wlserver_fake_mouse_pos( pFocus->inputFocusWindow->GetGeometry().nWidth - 1, pFocus->inputFocusWindow->GetGeometry().nHeight - 1 ); - wlserver_unlock(); - } - else if ( pFocus->inputFocusWindow->GetFocus()->bResetToCenter ) - { - wlserver_lock(); - wlserver_mousewarp( pFocus->inputFocusWindow->GetGeometry().nWidth / 2, pFocus->inputFocusWindow->GetGeometry().nHeight / 2, 0, true ); - wlserver_unlock(); - } - - pFocus->inputFocusWindow->GetFocus()->bResetToCorner = false; - pFocus->inputFocusWindow->GetFocus()->bResetToCenter = false; - } - - s_ulPreviousGlobalFocusKey = pFocus->ulVirtualFocusKey; - } - - pid_t newFocusedWindowPID = pFocus->focusWindow ? pFocus->focusWindow->pid : 0; - if (sdFocusWindow_pid != newFocusedWindowPID) { - const char *unfocusedWindowCGroup = cgroupPathFromPID(sdFocusWindow_pid); - const char *focusedWindowCGroup = cgroupPathFromPID(newFocusedWindowPID); - bool sameUnit = unfocusedWindowCGroup && focusedWindowCGroup && !strcmp(unfocusedWindowCGroup, focusedWindowCGroup); - - if (unfocusedWindowCGroup && !sameUnit) - setDMemMemoryLow(unfocusedWindowCGroup, false); - if (focusedWindowCGroup && !sameUnit) - setDMemMemoryLow(focusedWindowCGroup, true); - } - - if ( pFocus->pVirtualConnector && pFocus->inputFocusWindow ) - { - pFocus->pVirtualConnector->SetProperty( gamescope::ConnectorProperty::IsFileBrowser, pFocus->inputFocusWindow->bIsDolphin ); - } - - // Backchannel to Steam - unsigned long focusedWindow = 0; - unsigned long focusedAppId = 0; - unsigned long focusedBaseAppId = 0; - const char *focused_display = root_ctx->xwayland_server->get_nested_display_name(); - const char *focused_keyboard_display = root_ctx->xwayland_server->get_nested_display_name(); - const char *focused_mouse_display = root_ctx->xwayland_server->get_nested_display_name(); - - if ( pFocus->focusWindow ) - { - focusedWindow = (unsigned long)pFocus->focusWindow->id(); - focusedBaseAppId = pFocus->focusWindow->appID; - focusedAppId = pFocus->inputFocusWindow->appID; - focused_display = get_win_display_name(pFocus->focusWindow); - sdFocusWindow_pid = pFocus->focusWindow->pid; - } - - g_focusedBaseAppId = (uint32_t)focusedAppId; - - if ( pFocus->inputFocusWindow ) - { - focused_mouse_display = get_win_display_name(pFocus->inputFocusWindow); - } - - if ( pFocus->keyboardFocusWindow ) - { - focused_keyboard_display = get_win_display_name(pFocus->keyboardFocusWindow); - } - - if ( pFocus == GetCurrentFocus() ) - { - if ( steamMode ) - { - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&focusedAppId, focusedAppId != 0 ? 1 : 0 ); - - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedAppGfxAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&focusedBaseAppId, focusedBaseAppId != 0 ? 1 : 0 ); - } - - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusedWindowAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&focusedWindow, focusedWindow != 0 ? 1 : 0 ); - - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFocusDisplay, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)focused_display, strlen(focused_display) + 1 ); - - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeMouseFocusDisplay, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)focused_mouse_display, strlen(focused_mouse_display) + 1 ); - - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeKeyboardFocusDisplay, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)focused_keyboard_display, strlen(focused_keyboard_display) + 1 ); - - XFlush( root_ctx->dpy ); - } - - // Sort out fading. - if (pFocus->focusWindow && previousLocalFocus.focusWindow != pFocus->focusWindow) - { - bool bDoFade = win_has_game_id( pFocus->focusWindow ); - - if ( g_FadeOutDuration != 0 && !g_bFirstFrame && bDoFade ) - { - if ( g_HeldCommits[ HELD_COMMIT_FADE ] == nullptr ) - { - pFocus->fadeWindow = previousLocalFocus.focusWindow; - g_HeldCommits[ HELD_COMMIT_FADE ] = g_HeldCommits[ HELD_COMMIT_BASE ]; - g_bPendingFade = true; - } - else - { - // If we end up fading back to what we were going to fade to, cancel the fade. - if ( pFocus->fadeWindow != nullptr && pFocus->focusWindow == pFocus->fadeWindow ) - { - g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; - g_bPendingFade = false; - fadeOutStartTime = 0; - pFocus->fadeWindow = nullptr; - } - } - } - } - - if ( !cv_paint_debug_pause_base_plane ) - { - // Update last focus commit - if ( pFocus->focusWindow && - previousLocalFocus.focusWindow != pFocus->focusWindow && - !pFocus->focusWindow->isSteamStreamingClient ) - { - get_window_last_done_commit( pFocus->focusWindow, g_HeldCommits[ HELD_COMMIT_BASE ] ); - } - } - - // Set SDL window title - if ( pFocus->GetNestedHints() ) - { - if ( pFocus->focusWindow ) - { - pFocus->GetNestedHints()->SetVisible( true ); - if ( previousLocalFocus.focusWindow != pFocus->focusWindow ) - { - pFocus->GetNestedHints()->SetTitle( pFocus->focusWindow->title ); - pFocus->GetNestedHints()->SetIcon( pFocus->focusWindow->icon ); - } - } - else - { - pFocus->GetNestedHints()->SetVisible( false ); - } - } - - // Some games such as Disgaea PC (405900) don't take controller input until - // the window is first clicked on despite it having focus. - if ( pFocus->inputFocusWindow && pFocus->inputFocusWindow->appID == 405900 ) - { - auto now = get_time_in_milliseconds(); - - wlserver_lock(); - wlserver_touchdown( 0.5, 0.5, 0, now ); - wlserver_touchup( 0, now + 1 ); - wlserver_mousehide(); - wlserver_unlock(); - } - - pFocus->ulCurrentFocusSerial = GetFocusSerial(); -} - -static void -get_win_type(xwayland_ctx_t *ctx, steamcompmgr_win_t *w) -{ - w->is_dialog = !!w->xwayland().transientFor; - - std::vector atoms; - if ( get_prop( ctx, w->xwayland().id, ctx->atoms.winTypeAtom, atoms ) ) - { - for ( unsigned int atom : atoms ) - { - if ( atom == ctx->atoms.winDialogAtom ) - { - w->is_dialog = true; - } - if ( atom == ctx->atoms.winNormalAtom ) - { - w->is_dialog = false; - } - } - } -} - -static void -get_size_hints(xwayland_ctx_t *ctx, steamcompmgr_win_t *w) -{ - XSizeHints hints; - long hintsSpecified = 0; - - XGetWMNormalHints(ctx->dpy, w->xwayland().id, &hints, &hintsSpecified); - - const bool bHasPositionAndGravityHints = ( hintsSpecified & ( PPosition | PWinGravity ) ) == ( PPosition | PWinGravity ); - if ( bHasPositionAndGravityHints && - hints.x >= 0 && hints.y >= 0 && hints.win_gravity == StaticGravity ) - { - w->maybe_a_dropdown = true; - } - else - { - w->maybe_a_dropdown = false; - } - - if (hintsSpecified & (PMaxSize | PMinSize) && - hints.max_width && hints.max_height && hints.min_width && hints.min_height && - hints.max_width == hints.min_width && hints.min_height == hints.max_height) - { - w->requestedWidth = hints.max_width; - w->requestedHeight = hints.max_height; - - w->sizeHintsSpecified = true; - } - else - { - w->sizeHintsSpecified = false; - - // Below block checks for a pattern that matches old SDL fullscreen applications; - // SDL creates a fullscreen overrride-redirect window and reparents the game - // window under it, centered. We get rid of the modeswitch and also want that - // black border gone. - if (w->xwayland().a.override_redirect) - { - Window root_return = None, parent_return = None; - Window *children = NULL; - unsigned int nchildren = 0; - - XQueryTree(ctx->dpy, w->xwayland().id, &root_return, &parent_return, &children, &nchildren); - - if (nchildren == 1) - { - XWindowAttributes attribs; - - XGetWindowAttributes(ctx->dpy, children[0], &attribs); - - // If we have a unique children that isn't override-reidrect that is - // contained inside this fullscreen window, it's probably it. - if (attribs.override_redirect == false && - attribs.width <= w->GetGeometry().nWidth && - attribs.height <= w->GetGeometry().nHeight) - { - w->sizeHintsSpecified = true; - - w->requestedWidth = attribs.width; - w->requestedHeight = attribs.height; - - XMoveWindow(ctx->dpy, children[0], 0, 0); - - w->ignoreOverrideRedirect = true; - } - } - - XFree(children); - } - } -} - -static void -get_win_title(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, Atom atom) -{ - assert(atom == XA_WM_NAME || atom == ctx->atoms.netWMNameAtom); - - // Allocates a title we are meant to free, - // let's re-use this allocation for w->title :) - XTextProperty tp; - XGetTextProperty( ctx->dpy, w->xwayland().id, &tp, atom ); - - bool is_utf8; - if (tp.encoding == ctx->atoms.utf8StringAtom) { - is_utf8 = true; - } else if (tp.encoding == XA_STRING) { - is_utf8 = false; - } else { - return; - } - - if (!is_utf8 && w->utf8_title) { - /* Clients usually set both the non-UTF8 title and the UTF8 title - * properties. If the client has set the UTF8 title prop, ignore the - * non-UTF8 one. */ - return; - } - - if (tp.nitems > 0) { - // Ride off the allocation from XGetTextProperty. - w->title = std::make_shared((const char *)tp.value); - } else { - w->title = NULL; - } - w->utf8_title = is_utf8; -} - -static void -get_net_wm_state(xwayland_ctx_t *ctx, steamcompmgr_win_t *w) -{ - Atom type; - int format; - unsigned long nitems; - unsigned long bytesAfter; - unsigned char *data; - if (XGetWindowProperty(ctx->dpy, w->xwayland().id, ctx->atoms.netWMStateAtom, 0, 2048, false, - AnyPropertyType, &type, &format, &nitems, &bytesAfter, &data) != Success) { - return; - } - - Atom *props = (Atom *)data; - for (size_t i = 0; i < nitems; i++) { - if (props[i] == ctx->atoms.netWMStateFullscreenAtom) { - w->isFullscreen = true; - } else if (props[i] == ctx->atoms.netWMStateSkipTaskbarAtom) { - w->skipTaskbar = true; - } else if (props[i] == ctx->atoms.netWMStateSkipPagerAtom) { - w->skipPager = true; - } else { - xwm_log.debugf("Unhandled initial NET_WM_STATE property: %s", XGetAtomName(ctx->dpy, props[i])); - } - } - - XFree(data); -} - -static void -get_win_icon(xwayland_ctx_t* ctx, steamcompmgr_win_t* w) -{ - w->icon = std::make_shared>(); - get_prop(ctx, w->xwayland().id, ctx->atoms.netWMIcon, *w->icon.get()); -} - -static void -handle_desktop_window(steamcompmgr_win_t *w) -{ - if ( !w ) - return; - - if ( win_has_game_id( w ) || w->bIsSteamPid || w->bIsSteamWebHelperPid || w->bIsVRWebHelperPid ) - return; - - if ( w->type != steamcompmgr_win_type_t::XWAYLAND ) - return; - - if ( w->xwayland().a.override_redirect || ( w->oulTargetVROverlay && !cv_vr_show_forwarded_overlays ) ) - return; - - if ( win_maybe_a_dropdown( w ) || win_is_useless( w ) ) - return; - - xwayland_ctx_t *ctx = w->xwayland().ctx; - - if ( w->sizeHintsSpecified && !(window_is_fullscreen( w ) || ctx->force_windows_fullscreen) ) - { - if ((unsigned)w->GetGeometry().nWidth != w->requestedWidth || (unsigned)w->GetGeometry().nHeight != w->requestedHeight) - { - XResizeWindow(ctx->dpy, w->xwayland().id, w->requestedWidth, w->requestedHeight); - } - } - else - { - int fs_width = ctx->root_width; - int fs_height = ctx->root_height; - - if ( w->GetGeometry().nWidth != fs_width || w->GetGeometry().nHeight != fs_height ) - { - XResizeWindow(ctx->dpy, w->xwayland().id, fs_width, fs_height); - } - } -} - -static void -map_win(xwayland_ctx_t* ctx, Window id, unsigned long sequence) -{ - steamcompmgr_win_t *w = find_win(ctx, id); - - if (!w) - return; - - w->xwayland().a.map_state = IsViewable; - - /* This needs to be here or else we lose transparency messages */ - XSelectInput(ctx->dpy, id, PropertyChangeMask | SubstructureNotifyMask | - LeaveWindowMask | FocusChangeMask); - - XFlush(ctx->dpy); - - /* This needs to be here since we don't get PropertyNotify when unmapped */ - w->opacity = get_prop(ctx, w->xwayland().id, ctx->atoms.opacityAtom, OPAQUE); - - w->isSteamLegacyBigPicture = get_prop(ctx, w->xwayland().id, ctx->atoms.steamAtom, 0); - - /* First try to read the UTF8 title prop, then fallback to the non-UTF8 one */ - get_win_title( ctx, w, ctx->atoms.netWMNameAtom ); - get_win_title( ctx, w, XA_WM_NAME ); - get_win_icon( ctx, w ); - - w->inputFocusMode = get_prop(ctx, w->xwayland().id, ctx->atoms.steamInputFocusAtom, 0); - - w->isSteamStreamingClient = get_prop(ctx, w->xwayland().id, ctx->atoms.steamStreamingClientAtom, 0); - w->isSteamStreamingClientVideo = get_prop(ctx, w->xwayland().id, ctx->atoms.steamStreamingClientVideoAtom, 0); - - if ( steamMode == true ) - { - uint32_t appID = get_prop(ctx, w->xwayland().id, ctx->atoms.gameAtom, 0); - - if ( w->appID != 0 && appID != 0 && w->appID != appID ) - { - xwm_log.errorf( "appid clash was %u now %u", w->appID, appID ); - } - // Let the appID property be authoritative for now - if ( appID != 0 ) - { - w->appID = appID; - } - } - else - { - w->appID = w->xwayland().id; - } - - if ( w->isSteamLegacyBigPicture ) - w->appID = 769; - - w->isOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.overlayAtom, 0); - w->isExternalOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.externalOverlayAtom, 0); - - // misyl: Disable appID for overlay types, as parts of the code don't expect that focus-wise. - // Fixes mangoapp usage when nested, and not in SteamOS. - if ( w->isExternalOverlay ) - w->appID = 0; - - w->oulTargetVROverlay = get_u64_prop(ctx, w->xwayland().id, ctx->atoms.steamGamescopeVROverlayTarget); - if ( w->oulTargetVROverlay ) - { - g_bUpdateForwardedVROverlays = true; - w->bNeedsForwarding = true; - } - w->pForwarderPlane = nullptr; - - get_size_hints(ctx, w); - - get_net_wm_state(ctx, w); - - XWMHints *wmHints = XGetWMHints( ctx->dpy, w->xwayland().id ); - - if ( wmHints != nullptr ) - { - if ( wmHints->flags & (InputHint | StateHint ) && wmHints->input == true && wmHints->initial_state == NormalState ) - { - XRaiseWindow( ctx->dpy, w->xwayland().id ); - } - - XFree( wmHints ); - } - - Window transientFor = None; - if ( XGetTransientForHint( ctx->dpy, w->xwayland().id, &transientFor ) ) - { - w->xwayland().transientFor = transientFor; - } - else - { - w->xwayland().transientFor = None; - } - - get_win_type( ctx, w ); - - w->xwayland().damage_sequence = 0; - w->xwayland().map_sequence = sequence; - - if ( w == ctx->focus.inputFocusWindow || w->xwayland().id == ctx->currentKeyboardFocusWindow ) - { - XSetInputFocus(ctx->dpy, w->xwayland().id, RevertToNone, CurrentTime); - } - - handle_desktop_window( w ); - - MakeFocusDirty(); - - set_wm_state( ctx, w->xwayland().id, ICCCM_NORMAL_STATE ); -} - -static void -finish_unmap_win(xwayland_ctx_t *ctx, steamcompmgr_win_t *w) -{ - // TODO clear done commits here? - - /* don't care about properties anymore */ - XSelectInput(ctx->dpy, w->xwayland().id, 0); - - ctx->clipChanged = true; -} - -static void -unmap_win(xwayland_ctx_t *ctx, Window id, bool fade) -{ - steamcompmgr_win_t *w = find_win(ctx, id); - if (!w) - return; - w->xwayland().a.map_state = IsUnmapped; - - MakeFocusDirty(); - - finish_unmap_win(ctx, w); - set_wm_state( ctx, w->xwayland().id, ICCCM_WITHDRAWN_STATE ); -} - -std::string -get_name_from_pid( pid_t pid ) -{ - std::string procNameStr; - - char filename[256]; - - snprintf( filename, sizeof( filename ), "/proc/%i/stat", pid ); - std::ifstream proc_stat_file( filename ); - - if (!proc_stat_file.is_open() || proc_stat_file.bad()) - return ""; - - std::string proc_stat; - - std::getline( proc_stat_file, proc_stat ); - - char *procName = nullptr; - char *lastParens = nullptr; - - for ( uint32_t i = 0; i < proc_stat.length(); i++ ) - { - if ( procName == nullptr && proc_stat[ i ] == '(' ) - { - procName = &proc_stat[ i + 1 ]; - } - - if ( proc_stat[ i ] == ')' ) - { - lastParens = &proc_stat[ i ]; - } - } - - if (!lastParens) - return ""; - - *lastParens = '\0'; - - if ( procName ) - procNameStr = procName; - - return procNameStr; -} - -uint32_t -get_appid_from_pid( pid_t pid ) -{ - uint32_t unFoundAppId = 0; - - char filename[256]; - pid_t next_pid = pid; - - while ( 1 ) - { - snprintf( filename, sizeof( filename ), "/proc/%i/stat", next_pid ); - std::ifstream proc_stat_file( filename ); - - if (!proc_stat_file.is_open() || proc_stat_file.bad()) - break; - - std::string proc_stat; - - std::getline( proc_stat_file, proc_stat ); - - char *procName = nullptr; - char *lastParens = nullptr; - - for ( uint32_t i = 0; i < proc_stat.length(); i++ ) - { - if ( procName == nullptr && proc_stat[ i ] == '(' ) - { - procName = &proc_stat[ i + 1 ]; - } - - if ( proc_stat[ i ] == ')' ) - { - lastParens = &proc_stat[ i ]; - } - } - - if (!lastParens) - break; - - *lastParens = '\0'; - char state; - int parent_pid = -1; - - sscanf( lastParens + 1, " %c %d", &state, &parent_pid ); - - if ( strcmp( "reaper", procName ) == 0 ) - { - snprintf( filename, sizeof( filename ), "/proc/%i/cmdline", next_pid ); - std::ifstream proc_cmdline_file( filename ); - std::string proc_cmdline; - - bool bSteamLaunch = false; - uint32_t unAppId = 0; - - std::getline( proc_cmdline_file, proc_cmdline ); - - for ( uint32_t j = 0; j < proc_cmdline.length(); j++ ) - { - if ( proc_cmdline[ j ] == '\0' && j + 1 < proc_cmdline.length() ) - { - if ( strcmp( "SteamLaunch", &proc_cmdline[ j + 1 ] ) == 0 ) - { - bSteamLaunch = true; - } - else if ( sscanf( &proc_cmdline[ j + 1 ], "AppId=%u", &unAppId ) == 1 && unAppId != 0 ) - { - if ( bSteamLaunch == true ) - { - unFoundAppId = unAppId; - } - } - else if ( strcmp( "--", &proc_cmdline[ j + 1 ] ) == 0 ) - { - break; - } - } - } - } - - if ( parent_pid == -1 || parent_pid == 0 ) - { - break; - } - else - { - next_pid = parent_pid; - } - } - - return unFoundAppId; -} - -static pid_t -get_win_pid(xwayland_ctx_t *ctx, Window id) -{ - XResClientIdSpec client_spec = { - .client = id, - .mask = XRES_CLIENT_ID_PID_MASK, - }; - long num_ids = 0; - XResClientIdValue *client_ids = NULL; - XResQueryClientIds(ctx->dpy, 1, &client_spec, &num_ids, &client_ids); - - pid_t pid = -1; - for (long i = 0; i < num_ids; i++) { - pid = XResGetClientPid(&client_ids[i]); - if (pid > 0) - break; - } - XResClientIdsDestroy(num_ids, client_ids); - if (pid <= 0) - xwm_log.errorf("Failed to find PID for window 0x%lx", id); - return pid; -} - -static void -add_win(xwayland_ctx_t *ctx, Window id, Window prev, unsigned long sequence) -{ - steamcompmgr_win_t *new_win = new steamcompmgr_win_t{}; - steamcompmgr_win_t **p; - - if (!new_win) - return; - - new_win->seq = ++g_lastWinSeq; - new_win->type = steamcompmgr_win_type_t::XWAYLAND; - new_win->_window_types.emplace(); - - if (prev) - { - for (p = &ctx->list; *p; p = &(*p)->xwayland().next) - if ((*p)->xwayland().id == prev) - break; - } - else - p = &ctx->list; - new_win->xwayland().id = id; - if (!XGetWindowAttributes(ctx->dpy, id, &new_win->xwayland().a)) - { - delete new_win; - return; - } - - new_win->xwayland().ctx = ctx; - new_win->xwayland().damage_sequence = 0; - new_win->xwayland().map_sequence = 0; - if (new_win->xwayland().a.c_class == InputOnly) - new_win->xwayland().damage = None; - else - { - new_win->xwayland().damage = XDamageCreate(ctx->dpy, id, XDamageReportRawRectangles); - } - new_win->opacity = OPAQUE; - - if ( useXRes == true ) - { - new_win->pid = get_win_pid(ctx, id); - } - else - { - new_win->pid = -1; - } - - new_win->isOverlay = false; - new_win->isExternalOverlay = false; - new_win->isSteamLegacyBigPicture = false; - new_win->isSteamStreamingClient = false; - new_win->isSteamStreamingClientVideo = false; - new_win->inputFocusMode = 0; - new_win->is_dialog = false; - new_win->maybe_a_dropdown = false; - - new_win->hasHwndStyle = false; - new_win->hwndStyle = 0; - new_win->hasHwndStyleEx = false; - new_win->hwndStyleEx = 0; - - if ( steamMode == true ) - { - if ( new_win->pid != -1 ) - { - new_win->appID = get_appid_from_pid( new_win->pid ); - } - else - { - new_win->appID = 0; - } - } - else - { - new_win->appID = id; - } - - if ( new_win->isExternalOverlay ) - new_win->appID = 0; - - std::string pid_name = get_name_from_pid( new_win->pid ); - new_win->pid_name = pid_name; - if ( pid_name == "steam" ) - new_win->bIsSteamPid = true; - if ( pid_name == "steamwebhelper" ) - new_win->bIsSteamWebHelperPid = true; - if ( pid_name == "vrwebhelper" ) - new_win->bIsVRWebHelperPid = true; - if ( pid_name == "dolphin" ) - new_win->bIsDolphin = true; - - Window transientFor = None; - if ( XGetTransientForHint( ctx->dpy, id, &transientFor ) ) - { - new_win->xwayland().transientFor = transientFor; - } - else - { - new_win->xwayland().transientFor = None; - } - - get_win_type( ctx, new_win ); - - new_win->title = NULL; - new_win->utf8_title = false; - - new_win->isFullscreen = false; - new_win->isSysTrayIcon = false; - new_win->sizeHintsSpecified = false; - new_win->skipTaskbar = false; - new_win->skipPager = false; - new_win->requestedWidth = 0; - new_win->requestedHeight = 0; - new_win->nudged = false; - new_win->ignoreOverrideRedirect = false; - - wlserver_x11_surface_info_init( &new_win->xwayland().surface, ctx->xwayland_server, id ); - - { - std::unique_lock lock( ctx->list_mutex ); - new_win->xwayland().next = *p; - *p = new_win; - } - if (new_win->xwayland().a.map_state == IsViewable) - map_win(ctx, id, sequence); - - MakeFocusDirty(); -} - -static void -restack_win(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, Window new_above) -{ - Window old_above; - - if (w->xwayland().next) - old_above = w->xwayland().next->xwayland().id; - else - old_above = None; - if (old_above != new_above) - { - std::unique_lock lock( ctx->list_mutex ); - - steamcompmgr_win_t **prev; - - /* unhook */ - for (prev = &ctx->list; *prev; prev = &(*prev)->xwayland().next) - { - if ((*prev) == w) - break; - } - *prev = w->xwayland().next; - - /* rehook */ - for (prev = &ctx->list; *prev; prev = &(*prev)->xwayland().next) - { - if ((*prev)->xwayland().id == new_above) - break; - } - - w->xwayland().next = *prev; - *prev = w; - MakeFocusDirty(); - } -} - -static void -configure_win(xwayland_ctx_t *ctx, XConfigureEvent *ce) -{ - steamcompmgr_win_t *w = find_win(ctx, ce->window); - - if (!w || w->xwayland().id != ce->window) - { - if (ce->window == ctx->root) - { - ctx->root_width = ce->width; - ctx->root_height = ce->height; - MakeFocusDirty(); - - gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0); - xwayland_ctx_t *root_ctx = root_server->ctx.get(); - XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeXWaylandModeControl ); - XFlush( root_ctx->dpy ); - } - return; - } - - w->xwayland().a.x = ce->x; - w->xwayland().a.y = ce->y; - w->xwayland().a.width = ce->width; - w->xwayland().a.height = ce->height; - w->xwayland().a.border_width = ce->border_width; - w->xwayland().a.override_redirect = ce->override_redirect; - restack_win(ctx, w, ce->above); - - MakeFocusDirty(); -} - -static void -circulate_win(xwayland_ctx_t *ctx, XCirculateEvent *ce) -{ - steamcompmgr_win_t *w = find_win(ctx, ce->window); - Window new_above; - - if (!w || w->xwayland().id != ce->window) - return; - - if (ce->place == PlaceOnTop) - new_above = ctx->list->xwayland().id; - else - new_above = None; - restack_win(ctx, w, new_above); - ctx->clipChanged = true; -} - -static void map_request(xwayland_ctx_t *ctx, XMapRequestEvent *mapRequest) -{ - XMapWindow( ctx->dpy, mapRequest->window ); -} - -static void configure_request(xwayland_ctx_t *ctx, XConfigureRequestEvent *configureRequest) -{ - XWindowChanges changes = - { - .x = configureRequest->x, - .y = configureRequest->y, - .width = configureRequest->width, - .height = configureRequest->height, - .border_width = configureRequest->border_width, - .sibling = configureRequest->above, - .stack_mode = configureRequest->detail - }; - - XConfigureWindow( ctx->dpy, configureRequest->window, configureRequest->value_mask, &changes ); -} - -static void circulate_request( xwayland_ctx_t *ctx, XCirculateRequestEvent *circulateRequest ) -{ - XCirculateSubwindows( ctx->dpy, circulateRequest->window, circulateRequest->place ); -} - -static void -finish_destroy_win(xwayland_ctx_t *ctx, Window id, bool gone) -{ - steamcompmgr_win_t **prev, *w; - - for (prev = &ctx->list; (w = *prev); prev = &w->xwayland().next) - if (w->xwayland().id == id) - { - if (gone) - finish_unmap_win (ctx, w); - - { - std::unique_lock lock( ctx->list_mutex ); - *prev = w->xwayland().next; - } - if (w->xwayland().damage != None) - { - XDamageDestroy(ctx->dpy, w->xwayland().damage); - w->xwayland().damage = None; - } - - if (gone) - { - // release all commits now we are closed. - w->commit_queue.clear(); - } - - wlserver_lock(); - wlserver_x11_surface_info_finish( &w->xwayland().surface ); - wlserver_unlock(); - delete w; - break; - } -} - -static void -destroy_win(xwayland_ctx_t *ctx, Window id, bool gone, bool fade) -{ - // Context - if (x11_win(ctx->focus.focusWindow) == id && gone) - ctx->focus.focusWindow = nullptr; - if (x11_win(ctx->focus.inputFocusWindow) == id && gone) - ctx->focus.inputFocusWindow = nullptr; - if (x11_win(ctx->focus.overlayWindow) == id && gone) - ctx->focus.overlayWindow = nullptr; - if (x11_win(ctx->focus.externalOverlayWindow) == id && gone) - ctx->focus.externalOverlayWindow = nullptr; - if (x11_win(ctx->focus.notificationWindow) == id && gone) - ctx->focus.notificationWindow = nullptr; - if (x11_win(ctx->focus.overrideWindow) == id && gone) - ctx->focus.overrideWindow = nullptr; - if (ctx->currentKeyboardFocusWindow == id && gone) - ctx->currentKeyboardFocusWindow = None; - - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - // Global Focus - if (x11_win(pFocus->focusWindow) == id && gone) - pFocus->focusWindow = nullptr; - if (x11_win(pFocus->inputFocusWindow) == id && gone) - pFocus->inputFocusWindow = nullptr; - if (x11_win(pFocus->overlayWindow) == id && gone) - pFocus->overlayWindow = nullptr; - if (x11_win(pFocus->notificationWindow) == id && gone) - pFocus->notificationWindow = nullptr; - if (x11_win(pFocus->overrideWindow) == id && gone) - pFocus->overrideWindow = nullptr; - if (x11_win(pFocus->fadeWindow) == id && gone) - pFocus->fadeWindow = nullptr; - } - - MakeFocusDirty(); - - finish_destroy_win(ctx, id, gone); -} - -static void -damage_win(xwayland_ctx_t *ctx, XDamageNotifyEvent *de) -{ - steamcompmgr_win_t *w = find_win(ctx, de->drawable); - steamcompmgr_win_t *focus = ctx->focus.focusWindow; - - if (!w) - return; - - if (w->IsAnyOverlay() && !w->opacity) - return; - - bool bHasAppID = w->appID != 0; - - if ( gamescope::cv_backend_virtual_connector_strategy != gamescope::VirtualConnectorStrategies::SteamControlled ) - { - bHasAppID = true; - } - - bool bCareAboutWindow = true; - - if ( win_is_useless( w ) || w->IsAnyOverlay() || - ( w->oulTargetVROverlay && !cv_vr_show_forwarded_overlays ) || w->isSysTrayIcon || - w->xwayland().a.map_state != IsViewable ) - { - bCareAboutWindow = false; - } - - // First damage event we get, compute focus; we only want to focus damaged - // windows to have meaningful frames. - /// FIXME APPID FOCUS STRATERGY FOR - if (bHasAppID && bCareAboutWindow && w->xwayland().damage_sequence == 0) - MakeFocusDirty(); - - w->xwayland().damage_sequence = damageSequence++; - - // If we just passed the focused window, we might be eliglible to take over - if ( focus && focus != w && bHasAppID && bCareAboutWindow && - w->xwayland().damage_sequence > focus->xwayland().damage_sequence) - MakeFocusDirty(); - - // Josh: This will sometimes cause a BadDamage error. - // I looked around at different compositors to see what - // they do here and they just seem to ignore it. - if (w->xwayland().damage) - { - XDamageSubtract(ctx->dpy, w->xwayland().damage, None, None); - } - - gpuvis_trace_printf( "damage_win win %lx appID %u", w->xwayland().id, w->appID ); -} - -static void -handle_wl_surface_id(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, uint32_t surfaceID) -{ - struct wlr_surface *current_surface = NULL; - struct wlr_surface *main_surface = NULL; - - wlserver_lock(); - - ctx->xwayland_server->set_wl_id( &w->xwayland().surface, surfaceID ); - - current_surface = w->xwayland().surface.current_surface(); - main_surface = w->xwayland().surface.main_surface; - if ( current_surface == NULL ) - { - wlserver_unlock(); - return; - } - - global_focus_t *pCurrentFocus = GetCurrentFocus(); - if ( pCurrentFocus ) - { - // If we already focused on our side and are handling this late, - // let wayland know now. - if ( w == pCurrentFocus->inputFocusWindow ) - wlserver_mousefocus( main_surface, INT32_MAX, INT32_MAX ); - - steamcompmgr_win_t *keyboardFocusWindow = pCurrentFocus->inputFocusWindow; - - if ( gamescope::VirtualConnectorIsSingleOutput() && - keyboardFocusWindow && keyboardFocusWindow->inputFocusMode == 2 ) - keyboardFocusWindow = pCurrentFocus->focusWindow; - - if ( w == keyboardFocusWindow ) - wlserver_keyboardfocus( main_surface ); - } - - // Pull the first buffer out of that window, if needed - xwayland_surface_commit( current_surface ); - - wlserver_unlock(); -} - -static void -update_net_wm_state(uint32_t action, bool *value) -{ - switch (action) { - case NET_WM_STATE_REMOVE: - *value = false; - break; - case NET_WM_STATE_ADD: - *value = true; - break; - case NET_WM_STATE_TOGGLE: - *value = !*value; - break; - default: - xwm_log.debugf("Unknown NET_WM_STATE action: %" PRIu32, action); - } -} - -static void -handle_net_wm_state(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, XClientMessageEvent *ev) -{ - uint32_t action = (uint32_t)ev->data.l[0]; - Atom *props = (Atom *)&ev->data.l[1]; - for (size_t i = 0; i < 2; i++) { - if (props[i] == ctx->atoms.netWMStateFullscreenAtom) { - update_net_wm_state(action, &w->isFullscreen); - MakeFocusDirty(); - } else if (props[i] == ctx->atoms.netWMStateSkipTaskbarAtom) { - update_net_wm_state(action, &w->skipTaskbar); - MakeFocusDirty(); - } else if (props[i] == ctx->atoms.netWMStateSkipPagerAtom) { - update_net_wm_state(action, &w->skipPager); - MakeFocusDirty(); - } else if (props[i] != None) { - xwm_log.debugf("Unhandled NET_WM_STATE property change: %s", XGetAtomName(ctx->dpy, props[i])); - } - } -} - -bool g_bLowLatency = false; - -static void -handle_system_tray_opcode(xwayland_ctx_t *ctx, XClientMessageEvent *ev) -{ - long opcode = ev->data.l[1]; - - switch (opcode) { - case SYSTEM_TRAY_REQUEST_DOCK: { - Window embed_id = ev->data.l[2]; - - /* At this point we're supposed to initiate the XEmbed lifecycle by - * sending XEMBED_EMBEDDED_NOTIFY. However we don't actually need to - * render the systray, we just want to recognize and blacklist these - * icons. So for now do nothing. */ - - steamcompmgr_win_t *w = find_win(ctx, embed_id); - if (w) { - w->isSysTrayIcon = true; - } - break; - } - default: - xwm_log.debugf("Unhandled _NET_SYSTEM_TRAY_OPCODE %ld", opcode); - } -} - -/* See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */ -static void -handle_wm_change_state(xwayland_ctx_t *ctx, steamcompmgr_win_t *w, XClientMessageEvent *ev) -{ - long state = ev->data.l[0]; - - if (state == ICCCM_ICONIC_STATE) { - xwm_log.debugf("Faking WM_CHANGE_STATE to ICONIC for window 0x%lx", w->xwayland().id); - set_wm_state( ctx, w->xwayland().id, ICCCM_ICONIC_STATE ); - } else { - xwm_log.debugf("Unhandled WM_CHANGE_STATE to %ld for window 0x%lx", state, w->xwayland().id); - } -} - -static void -handle_client_message(xwayland_ctx_t *ctx, XClientMessageEvent *ev) -{ - if (ev->window == ctx->ourWindow && ev->message_type == ctx->atoms.netSystemTrayOpcodeAtom) - { - handle_system_tray_opcode( ctx, ev ); - return; - } - - steamcompmgr_win_t *w = find_win(ctx, ev->window); - if (w) - { - if (ev->message_type == ctx->atoms.WLSurfaceIDAtom) - { - handle_wl_surface_id( ctx, w, uint32_t(ev->data.l[0])); - } - else if ( ev->message_type == ctx->atoms.activeWindowAtom ) - { - XRaiseWindow( ctx->dpy, w->xwayland().id ); - } - else if ( ev->message_type == ctx->atoms.netWMStateAtom ) - { - handle_net_wm_state( ctx, w, ev ); - } - else if ( ev->message_type == ctx->atoms.WMChangeStateAtom ) - { - handle_wm_change_state( ctx, w, ev ); - } - else if ( ev->message_type != 0 ) - { - xwm_log.debugf( "Unhandled client message: %s", XGetAtomName( ctx->dpy, ev->message_type ) ); - } - } -} - -static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, GamescopeSelection eSelectionTarget) -{ - Atom target; - if (eSelectionTarget == GAMESCOPE_SELECTION_CLIPBOARD) - { - target = ctx->atoms.clipboard; - } - else if (eSelectionTarget == GAMESCOPE_SELECTION_PRIMARY) - { - target = ctx->atoms.primarySelection; - } - else - { - return; - } - - XSetSelectionOwner(ctx->dpy, target, ctx->ourWindow, CurrentTime); -} - -void gamescope_set_selection(std::string contents, GamescopeSelection eSelection) -{ - if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD) - { - clipboard = contents; - } - else if (eSelection == GAMESCOPE_SELECTION_PRIMARY) - { - primarySelection = contents; - } - - gamescope_xwayland_server_t *server = NULL; - for (int i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - xwayland_ctx_t *ctx = server->ctx.get(); - - if (ctx) - x11_set_selection_owner(ctx, contents, eSelection); - } -} - -void gamescope_set_reshade_effect(std::string effect_path) -{ - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(0); - set_string_prop(server->ctx.get(), server->ctx->atoms.gamescopeReshadeEffect, effect_path); -} - -void gamescope_clear_reshade_effect() { - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(0); - clear_prop(server->ctx.get(), server->ctx->atoms.gamescopeReshadeEffect); -} - -static void -handle_selection_request(xwayland_ctx_t *ctx, XSelectionRequestEvent *ev) -{ - std::string *selection = ev->selection == ctx->atoms.primarySelection ? &primarySelection : &clipboard; - - const char *targetString = XGetAtomName(ctx->dpy, ev->target); - - XEvent response; - response.xselection.type = SelectionNotify; - response.xselection.selection = ev->selection; - response.xselection.requestor = ev->requestor; - response.xselection.time = ev->time; - response.xselection.property = None; - response.xselection.target = None; - - if (ev->requestor == ctx->ourWindow) - { - return; - } - - if (ev->target == ctx->atoms.targets) - { - Atom targetList[] = { - ctx->atoms.targets, - ctx->atoms.utf8StringAtom, - }; - - XChangeProperty(ctx->dpy, ev->requestor, ev->property, XA_ATOM, 32, PropModeReplace, - (unsigned char *)&targetList, sizeof(targetList) / sizeof(targetList[0])); - response.xselection.property = ev->property; - response.xselection.target = ev->target; - } - else if (!strcmp(targetString, "text/plain;charset=utf-8") || - !strcmp(targetString, "text/plain") || - !strcmp(targetString, "TEXT") || - !strcmp(targetString, "UTF8_STRING") || - !strcmp(targetString, "STRING")) - { - - XChangeProperty(ctx->dpy, ev->requestor, ev->property, ev->target, 8, PropModeReplace, - (unsigned char *)selection->c_str(), selection->length()); - response.xselection.property = ev->property; - response.xselection.target = ev->target; - } - else - { - xwm_log.debugf("Unsupported clipboard type: %s. Ignoring", targetString); - } - - XSendEvent(ctx->dpy, ev->requestor, False, NoEventMask, &response); - XFlush(ctx->dpy); -} - -static void -handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) -{ - Atom actual_type; - int actual_format; - unsigned long nitems; - unsigned long bytes_after; - unsigned char *data = NULL; - - XGetWindowProperty(ctx->dpy, ev->requestor, ev->property, 0, 0, False, AnyPropertyType, - &actual_type, &actual_format, &nitems, &bytes_after, &data); - if (data) { - XFree(data); - } - - if (actual_type == ctx->atoms.utf8StringAtom && actual_format == 8) { - XGetWindowProperty(ctx->dpy, ev->requestor, ev->property, 0, bytes_after, False, AnyPropertyType, - &actual_type, &actual_format, &nitems, &bytes_after, &data); - if (data) { - const char *contents = (const char *) data; - auto szContents = std::make_shared(contents); - defer( XFree( data ); ); - - gamescope::INestedHints *hints = nullptr; - if (auto connector = GetBackend()->GetCurrentConnector()) - hints = connector->GetNestedHints(); - - if (ev->selection == ctx->atoms.clipboard) - { - if ( hints ) - { - hints->SetSelection( szContents, GAMESCOPE_SELECTION_CLIPBOARD ); - } - else - { - gamescope_set_selection( contents, GAMESCOPE_SELECTION_CLIPBOARD ); - } - } - else if (ev->selection == ctx->atoms.primarySelection) - { - if ( hints ) - { - hints->SetSelection( szContents, GAMESCOPE_SELECTION_PRIMARY ); - } - else - { - gamescope_set_selection( contents, GAMESCOPE_SELECTION_PRIMARY ); - } - } - else - { - xwm_log.errorf( "Selection '%s' not supported. Ignoring", XGetAtomName(ctx->dpy, ev->selection) ); - } - } - } -} - -template -T bit_cast(const J& src) { - T dst; - memcpy(&dst, &src, sizeof(T)); - return dst; -} - -static void -update_runtime_info() -{ - if ( g_nRuntimeInfoFd < 0 ) - return; - - uint32_t limiter_enabled = g_nSteamCompMgrTargetFPS != 0 ? 1 : 0; - pwrite( g_nRuntimeInfoFd, &limiter_enabled, sizeof( limiter_enabled ), 0 ); -} - -static void -init_runtime_info() -{ - const char *path = getenv( "GAMESCOPE_LIMITER_FILE" ); - if ( !path ) - return; - - g_nRuntimeInfoFd = open( path, O_CREAT | O_RDWR , 0644 ); - update_runtime_info(); -} - -static void -steamcompmgr_flush_frame_done( steamcompmgr_win_t *w ) -{ - wlr_surface *current_surface = w->current_surface(); - if ( current_surface && w->unlockedForFrameCallback && w->receivedDoneCommit ) - { - // TODO: Look into making this _RAW - // wlroots, seems to just use normal MONOTONIC - // all over so this may be problematic to just change. - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - - wlr_surface *main_surface = w->main_surface(); - w->unlockedForFrameCallback = false; - w->receivedDoneCommit = false; - - w->last_commit_first_latch_time = timespec_to_nanos(now); - - // Acknowledge commit once. - wlserver_lock(); - - if ( main_surface != nullptr ) - { - wlserver_send_frame_done(main_surface, &now); - } - - if ( current_surface != nullptr && main_surface != current_surface ) - { - wlserver_send_frame_done(current_surface, &now); - } - - wlserver_unlock(); - } -} - -static std::optional s_oLowestFPSLimitScheduleVRR; - -static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vblank_idx, steamcompmgr_win_t *w = nullptr, uint64_t now = 0 ) -{ - bool bSendCallback = true; - - int nRefreshHz = gamescope::ConvertmHzToHz( g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh ); - int nTargetFPS = g_nSteamCompMgrTargetFPS; - - if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() ) - { - bool bCloseEnough = std::abs( g_nSteamCompMgrTargetFPS - nRefreshHz ) < 2; - - if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && w && !bCloseEnough ) - { - uint64_t schedule = w->last_commit_first_latch_time + g_SteamCompMgrLimitedAppRefreshCycle; - - static constexpr uint64_t k_ulVRRScheduleFudge = 200'000; // 0.2ms - if ( now + k_ulVRRScheduleFudge < schedule ) - { - bSendCallback = false; - - if ( !s_oLowestFPSLimitScheduleVRR ) - s_oLowestFPSLimitScheduleVRR = schedule; - else - s_oLowestFPSLimitScheduleVRR = std::min( *s_oLowestFPSLimitScheduleVRR, schedule ); - } - } - } - else - { - if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && nRefreshHz > nTargetFPS ) - { - int nVblankDivisor = nRefreshHz / nTargetFPS; - - if ( vblank_idx % nVblankDivisor != 0 ) - bSendCallback = false; - } - } - - return bSendCallback; -} - -static bool steamcompmgr_should_vblank_window( steamcompmgr_win_t *w, uint64_t vblank_idx, uint64_t now ) -{ - return steamcompmgr_should_vblank_window( steamcompmgr_window_should_limit_fps( w ), vblank_idx, w, now ); -} - -static void -steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx, uint64_t now ) -{ - if ( steamcompmgr_should_vblank_window( w, vblank_idx, now ) ) - { - w->unlockedForFrameCallback = true; - } -} - -static inline float santitize_float( float f ) -{ -#ifndef __FAST_MATH__ - return ( std::isfinite( f ) ? f : 0.f ); -#else - return f; -#endif -} - -static void -handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) -{ - /* check if Trans property was changed */ - if (ev->atom == ctx->atoms.opacityAtom) - { - /* reset mode and redraw window */ - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if ( w != nullptr ) - { - unsigned int newOpacity = get_prop(ctx, w->xwayland().id, ctx->atoms.opacityAtom, OPAQUE); - - if (newOpacity != w->opacity) - { - w->opacity = newOpacity; - - if ( gameFocused && ( w == ctx->focus.overlayWindow || w == ctx->focus.notificationWindow ) ) - { - hasRepaintNonBasePlane = true; - } - if ( w == ctx->focus.externalOverlayWindow ) - { - hasRepaint = true; - } - } - - unsigned int maxOpacity = 0; - unsigned int maxOpacityExternal = 0; - - for (w = ctx->list; w; w = w->xwayland().next) - { - if (w->isOverlay) - { - if (w->GetGeometry().nWidth > 1200 && w->opacity >= maxOpacity) - { - ctx->focus.overlayWindow = w; - maxOpacity = w->opacity; - } - } - if (w->isExternalOverlay) - { - if (w->opacity >= maxOpacityExternal) - { - ctx->focus.externalOverlayWindow = w; - maxOpacityExternal = w->opacity; - } - } - } - } - } - if (ev->atom == ctx->atoms.steamAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->isSteamLegacyBigPicture = get_prop(ctx, w->xwayland().id, ctx->atoms.steamAtom, 0); - if ( w->isSteamLegacyBigPicture ) - w->appID = 769; - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.steamInputFocusAtom ) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->inputFocusMode = get_prop(ctx, w->xwayland().id, ctx->atoms.steamInputFocusAtom, 0); - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.steamTouchClickModeAtom ) - { - gamescope::cv_touch_click_mode = (gamescope::TouchClickMode) get_prop(ctx, ctx->root, ctx->atoms.steamTouchClickModeAtom, 0u ); - } - if (ev->atom == ctx->atoms.steamStreamingClientAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->isSteamStreamingClient = get_prop(ctx, w->xwayland().id, ctx->atoms.steamStreamingClientAtom, 0); - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.steamStreamingClientVideoAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->isSteamStreamingClientVideo = get_prop(ctx, w->xwayland().id, ctx->atoms.steamStreamingClientVideoAtom, 0); - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.steamGamescopeVROverlayTarget) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->oulTargetVROverlay = get_u64_prop(ctx, w->xwayland().id, ctx->atoms.steamGamescopeVROverlayTarget); - w->pForwarderPlane = nullptr; - MakeFocusDirty(); - hasRepaint = true; - g_bUpdateForwardedVROverlays = true; - w->bNeedsForwarding = true; - } - } - if (ev->atom == ctx->atoms.gamescopeCtrlAppIDAtom ) - { - get_prop( ctx, ctx->root, ctx->atoms.gamescopeCtrlAppIDAtom, vecFocuscontrolAppIDs ); - MakeFocusDirty(); - } - if (ev->atom == ctx->atoms.gamescopeCtrlWindowAtom ) - { - ctx->focusControlWindow = get_prop( ctx, ctx->root, ctx->atoms.gamescopeCtrlWindowAtom, None ); - MakeFocusDirty(); - } - if ( ev->atom == ctx->atoms.gamescopeScreenShotAtom ) - { - if ( ev->state == PropertyNewValue ) - { - gamescope::CScreenshotManager::Get().TakeScreenshot( gamescope::GamescopeScreenshotInfo - { - .szScreenshotPath = "/tmp/gamescope.png", - .eScreenshotType = (gamescope_control_screenshot_type) get_prop( ctx, ctx->root, ctx->atoms.gamescopeScreenShotAtom, None ), - .uScreenshotFlags = 0, - .bX11PropertyRequested = true, - } ); - } - } - if ( ev->atom == ctx->atoms.gamescopeDebugScreenShotAtom ) - { - if ( ev->state == PropertyNewValue ) - { - gamescope::CScreenshotManager::Get().TakeScreenshot( gamescope::GamescopeScreenshotInfo - { - .szScreenshotPath = "/tmp/gamescope.png", - .eScreenshotType = (gamescope_control_screenshot_type) get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugScreenShotAtom, None ), - .uScreenshotFlags = 0, - .bX11PropertyRequested = true, - } ); - } - } - if (ev->atom == ctx->atoms.gameAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - uint32_t appID = get_prop(ctx, w->xwayland().id, ctx->atoms.gameAtom, 0); - - if ( w->appID != 0 && appID != 0 && w->appID != appID ) - { - xwm_log.errorf( "appid clash was %u now %u", w->appID, appID ); - } - w->appID = appID; - if ( w->isExternalOverlay ) - w->appID = 0; - - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.overlayAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->isOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.overlayAtom, 0); - if ( w->isExternalOverlay ) - w->appID = 0; - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.externalOverlayAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->isExternalOverlay = get_prop(ctx, w->xwayland().id, ctx->atoms.externalOverlayAtom, 0); - if ( w->isExternalOverlay ) - w->appID = 0; - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.winTypeAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - get_win_type(ctx, w); - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.sizeHintsAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - get_size_hints(ctx, w); - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.gamesRunningAtom) - { - gamesRunningCount = get_prop(ctx, ctx->root, ctx->atoms.gamesRunningAtom, 0); - - MakeFocusDirty(); - } - if (ev->atom == ctx->atoms.screenScaleAtom) - { - overscanScaleRatio = get_prop(ctx, ctx->root, ctx->atoms.screenScaleAtom, 0xFFFFFFFF) / (double)0xFFFFFFFF; - - globalScaleRatio = overscanScaleRatio * zoomScaleRatio; - - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - if (pFocus->focusWindow) - { - hasRepaint = true; - } - } - - MakeFocusDirty(); - } - if (ev->atom == ctx->atoms.screenZoomAtom) - { - zoomScaleRatio = get_prop(ctx, ctx->root, ctx->atoms.screenZoomAtom, 0xFFFF) / (double)0xFFFF; - - globalScaleRatio = overscanScaleRatio * zoomScaleRatio; - - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - if (pFocus->focusWindow) - { - hasRepaint = true; - } - } - - MakeFocusDirty(); - } - if (ev->atom == ctx->atoms.WMTransientForAtom) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - Window transientFor = None; - if ( XGetTransientForHint( ctx->dpy, ev->window, &transientFor ) ) - { - w->xwayland().transientFor = transientFor; - } - else - { - w->xwayland().transientFor = None; - } - get_win_type( ctx, w ); - - MakeFocusDirty(); - } - } - if (ev->atom == XA_WM_NAME || ev->atom == ctx->atoms.netWMNameAtom) - { - steamcompmgr_win_t *w = find_win(ctx, ev->window); - - if (w) - { - get_win_title(ctx, w, ev->atom); - - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - if (ev->window == x11_win(pFocus->focusWindow)) - { - if ( pFocus->GetNestedHints() ) - pFocus->GetNestedHints()->SetTitle( w->title ); - } - } - } - } - if (ev->atom == ctx->atoms.netWMIcon) - { - steamcompmgr_win_t *w = find_win(ctx, ev->window); - - if (w) - { - get_win_icon(ctx, w); - - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - if (ev->window == x11_win(pFocus->focusWindow)) - { - if ( pFocus->GetNestedHints() ) - pFocus->GetNestedHints()->SetIcon( w->icon ); - } - } - } - } -#if 0 - if ( ev->atom == ctx->atoms.gamescopeTuneableVBlankRedZone ) - { - g_uVblankDrawBufferRedZoneNS = (uint64_t)get_prop( ctx, ctx->root, ctx->atoms.gamescopeTuneableVBlankRedZone, g_uDefaultVBlankRedZone ); - } - if ( ev->atom == ctx->atoms.gamescopeTuneableRateOfDecay ) - { - g_uVBlankRateOfDecayPercentage = (uint64_t)get_prop( ctx, ctx->root, ctx->atoms.gamescopeTuneableRateOfDecay, g_uDefaultVBlankRateOfDecayPercentage ); - } -#endif - if ( ev->atom == ctx->atoms.gamescopeScalingFilter ) - { - int nScalingMode = get_prop( ctx, ctx->root, ctx->atoms.gamescopeScalingFilter, 0 ); - switch ( nScalingMode ) - { - default: - case 0: - g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; - g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; - break; - case 1: - g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; - g_wantedUpscaleFilter = GamescopeUpscaleFilter::NEAREST; - break; - case 2: - g_wantedUpscaleScaler = GamescopeUpscaleScaler::INTEGER; - g_wantedUpscaleFilter = GamescopeUpscaleFilter::NEAREST; - break; - case 3: - g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; - g_wantedUpscaleFilter = GamescopeUpscaleFilter::FSR; - break; - case 4: - g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; - g_wantedUpscaleFilter = GamescopeUpscaleFilter::NIS; - break; - } - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeFSRSharpness || ev->atom == ctx->atoms.gamescopeSharpness ) - { - g_upscaleFilterSharpness = (int)clamp( get_prop( ctx, ctx->root, ev->atom, 2 ), 0u, 20u ); - if ( g_upscaleFilter == GamescopeUpscaleFilter::FSR || g_upscaleFilter == GamescopeUpscaleFilter::NIS ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeXWaylandModeControl ) - { - std::vector< uint32_t > xwayland_mode_ctl; - bool hasModeCtrl = get_prop( ctx, ctx->root, ctx->atoms.gamescopeXWaylandModeControl, xwayland_mode_ctl ); - if ( hasModeCtrl && xwayland_mode_ctl.size() == 4 ) - { - size_t server_idx = size_t{ xwayland_mode_ctl[ 0 ] }; - int width = xwayland_mode_ctl[ 1 ]; - int height = xwayland_mode_ctl[ 2 ]; - bool allowSuperRes = !!xwayland_mode_ctl[ 3 ]; - - if ( !allowSuperRes ) - { - width = std::min(width, currentOutputWidth); - height = std::min(height, currentOutputHeight); - } - - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( server_idx ); - if ( server ) - { - bool root_size_identical = server->ctx->root_width == width && server->ctx->root_height == height; - - wlserver_lock(); - wlserver_set_xwayland_server_mode( server_idx, width, height, g_nOutputRefresh ); - wlserver_unlock(); - - if ( root_size_identical ) - { - gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0); - xwayland_ctx_t *root_ctx = root_server->ctx.get(); - XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeXWaylandModeControl ); - XFlush( root_ctx->dpy ); - } - } - } - } - if ( ev->atom == ctx->atoms.gamescopeFPSLimit ) - { - g_nSteamCompMgrTargetFPS = get_prop( ctx, ctx->root, ctx->atoms.gamescopeFPSLimit, 0 ); - update_runtime_info(); - } - for (int i = 0; i < gamescope::GAMESCOPE_SCREEN_TYPE_COUNT; i++) - { - if ( ev->atom == ctx->atoms.gamescopeDynamicRefresh[i] ) - { - g_nDynamicRefreshRate[i] = get_prop( ctx, ctx->root, ctx->atoms.gamescopeDynamicRefresh[i], 0 ); - } - } - if ( ev->atom == ctx->atoms.gamescopeLowLatency ) - { - g_bLowLatency = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeLowLatency, 0 ); - } - if ( ev->atom == ctx->atoms.gamescopeBlurMode ) - { - BlurMode newBlur = (BlurMode)get_prop( ctx, ctx->root, ctx->atoms.gamescopeBlurMode, 0 ); - if (newBlur < BLUR_MODE_OFF || newBlur > BLUR_MODE_ALWAYS) - newBlur = BLUR_MODE_OFF; - - if (newBlur != g_BlurMode) { - g_BlurFadeStartTime = get_time_in_milliseconds(); - g_BlurModeOld = g_BlurMode; - g_BlurMode = newBlur; - hasRepaint = true; - } - } - if ( ev->atom == ctx->atoms.gamescopeBlurRadius ) - { - unsigned int pixel = get_prop( ctx, ctx->root, ctx->atoms.gamescopeBlurRadius, 0 ); - g_BlurRadius = (int)clamp((pixel / 2) + 1, 1u, kMaxBlurRadius - 1); - if ( g_BlurMode ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeBlurFadeDuration ) - { - g_BlurFadeDuration = get_prop( ctx, ctx->root, ctx->atoms.gamescopeBlurFadeDuration, 0 ); - } - if ( ev->atom == ctx->atoms.gamescopeCompositeForce ) - { - cv_composite_force = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeCompositeForce, 0 ); - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeCompositeDebug ) - { - cv_composite_debug = get_prop( ctx, ctx->root, ctx->atoms.gamescopeCompositeDebug, 0 ); - - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeAllowTearing ) - { - cv_tearing_enabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeAllowTearing, 0 ); - } - if ( ev->atom == ctx->atoms.gamescopeSteamMaxHeight ) - { - g_nSteamMaxHeight = get_prop( ctx, ctx->root, ctx->atoms.gamescopeSteamMaxHeight, 0 ); - MakeFocusDirty(); - } - if ( ev->atom == ctx->atoms.gamescopeVRREnabled ) - { - bool enabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeVRREnabled, 0 ); - cv_adaptive_sync = enabled; - } - if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal ) - { - g_bForceInternal = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayForceInternal, 0 ); - GetBackend()->DirtyState(); - } - if ( ev->atom == ctx->atoms.gamescopeDisplayModeNudge ) - { - GetBackend()->DirtyState( true ); - XDeleteProperty( ctx->dpy, ctx->root, ctx->atoms.gamescopeDisplayModeNudge ); - } - if ( ev->atom == ctx->atoms.gamescopeNewScalingFilter ) - { - GamescopeUpscaleFilter nScalingFilter = ( GamescopeUpscaleFilter ) get_prop( ctx, ctx->root, ctx->atoms.gamescopeNewScalingFilter, 0 ); - if (g_wantedUpscaleFilter != nScalingFilter) - { - g_wantedUpscaleFilter = nScalingFilter; - hasRepaint = true; - } - } - if ( ev->atom == ctx->atoms.gamescopeNewScalingScaler ) - { - GamescopeUpscaleScaler nScalingScaler = ( GamescopeUpscaleScaler ) get_prop( ctx, ctx->root, ctx->atoms.gamescopeNewScalingScaler, 0 ); - if (g_wantedUpscaleScaler != nScalingScaler) - { - g_wantedUpscaleScaler = nScalingScaler; - hasRepaint = true; - } - } - if ( ev->atom == ctx->atoms.gamescopeDisplayHDREnabled ) - { - cv_hdr_enabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayHDREnabled, 0 ); - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeDebugForceHDR10Output ) - { - g_bForceHDR10OutputDebug = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugForceHDR10Output, 0 ); - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeDebugForceHDRSupport ) - { - g_bForceHDRSupportDebug = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugForceHDRSupport, 0 ); - GetBackend()->HackUpdatePatchedEdid(); - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeDebugHDRHeatmap ) - { - uint32_t heatmap = get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugHDRHeatmap, 0 ); - cv_composite_debug &= ~CompositeDebugFlag::Heatmap; - cv_composite_debug &= ~CompositeDebugFlag::Heatmap_MSWCG; - cv_composite_debug &= ~CompositeDebugFlag::Heatmap_Hard; - if (heatmap != 0) - cv_composite_debug |= CompositeDebugFlag::Heatmap; - if (heatmap == 2) - cv_composite_debug |= CompositeDebugFlag::Heatmap_MSWCG; - if (heatmap == 3) - cv_composite_debug |= CompositeDebugFlag::Heatmap_Hard; - hasRepaint = true; - } - - if ( ev->atom == ctx->atoms.gamescopeHDRTonemapOperator ) - { - g_ColorMgmt.pending.hdrTonemapOperator = (ETonemapOperator) get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRTonemapOperator, 0 ); - hasRepaint = true; - } - - if ( ev->atom == ctx->atoms.gamescopeHDRTonemapDisplayMetadata ) - { - std::vector< uint32_t > user_vec; - if ( get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRTonemapDisplayMetadata, user_vec ) && user_vec.size() >= 2 ) - { - g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flBlackPointNits = bit_cast( user_vec[0] ); - g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits = bit_cast( user_vec[1] ); - } - else - { - g_ColorMgmt.pending.hdrTonemapDisplayMetadata.reset(); - } - hasRepaint = true; - } - - if ( ev->atom == ctx->atoms.gamescopeHDRTonemapSourceMetadata ) - { - std::vector< uint32_t > user_vec; - if ( get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRTonemapSourceMetadata, user_vec ) && user_vec.size() >= 2 ) - { - g_ColorMgmt.pending.hdrTonemapSourceMetadata.flBlackPointNits = bit_cast( user_vec[0] ); - g_ColorMgmt.pending.hdrTonemapSourceMetadata.flWhitePointNits = bit_cast( user_vec[1] ); - } - else - { - g_ColorMgmt.pending.hdrTonemapSourceMetadata.reset(); - } - hasRepaint = true; - } - - if ( ev->atom == ctx->atoms.gamescopeSDROnHDRContentBrightness ) - { - uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeSDROnHDRContentBrightness, 0 ); - if ( set_sdr_on_hdr_brightness( bit_cast(val) ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeHDRItmEnable ) - { - g_bHDRItmEnable = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRItmEnable, 0 ); - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeHDRItmSDRNits ) - { - g_flHDRItmSdrNits = get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRItmSDRNits, 0 ); - if ( g_flHDRItmSdrNits < 1.f ) - g_flHDRItmSdrNits = 100.f; - else if ( g_flHDRItmSdrNits > 1000.f) - g_flHDRItmSdrNits = 1000.f; - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeHDRItmTargetNits ) - { - g_flHDRItmTargetNits = get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRItmTargetNits, 0 ); - if ( g_flHDRItmTargetNits < 1.f ) - g_flHDRItmTargetNits = 1000.f; - else if ( g_flHDRItmTargetNits > 10000.f) - g_flHDRItmTargetNits = 10000.f; - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeColorLookPQ ) - { - std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorLookPQ ); - if ( set_color_look_pq( path.c_str() ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeColorLookG22 ) - { - std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorLookG22 ); - if ( set_color_look_g22( path.c_str() ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeColorOutputVirtualWhite ) - { - std::vector< uint32_t > user_vec; - if ( get_prop( ctx, ctx->root, ctx->atoms.gamescopeColorOutputVirtualWhite, user_vec ) && user_vec.size() >= 2 ) - { - g_ColorMgmt.pending.outputVirtualWhite.x = santitize_float( bit_cast( user_vec[0] ) ); - g_ColorMgmt.pending.outputVirtualWhite.y = santitize_float( bit_cast( user_vec[1] ) ); - } - else - { - g_ColorMgmt.pending.outputVirtualWhite.x = 0.f; - g_ColorMgmt.pending.outputVirtualWhite.y = 0.f; - } - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeHDRInputGain ) - { - uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRInputGain, 0 ); - if ( set_hdr_input_gain( bit_cast(val) ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeSDRInputGain ) - { - uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeSDRInputGain, 0 ); - if ( set_sdr_input_gain( bit_cast(val) ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeForceWindowsFullscreen ) - { - ctx->force_windows_fullscreen = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeForceWindowsFullscreen, 0 ); - MakeFocusDirty(); - } - if ( ev->atom == ctx->atoms.gamescopeColorLut3DOverride ) - { - std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorLut3DOverride ); - if ( set_color_3dlut_override( path.c_str() ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeColorShaperLutOverride ) - { - std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorShaperLutOverride ); - if ( set_color_shaperlut_override( path.c_str() ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeColorSDRGamutWideness ) - { - uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorSDRGamutWideness, 0); - if ( set_color_sdr_gamut_wideness( bit_cast(val) ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeColorNightMode ) - { - std::vector< uint32_t > user_vec; - bool bHasVec = get_prop( ctx, ctx->root, ctx->atoms.gamescopeColorNightMode, user_vec ); - - // identity - float vec[3] = { 0.0f, 0.0f, 0.0f }; - if ( bHasVec && user_vec.size() == 3 ) - { - for (int i = 0; i < 3; i++) - vec[i] = bit_cast( user_vec[i] ); - } - - nightmode_t nightmode; - nightmode.amount = vec[0]; - nightmode.hue = vec[1]; - nightmode.saturation = vec[2]; - - if ( set_color_nightmode( nightmode ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeColorManagementDisable ) - { - uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorManagementDisable, 0); - if ( set_color_mgmt_enabled( !val ) ) - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeColorSliderInUse ) - { - uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorSliderInUse, 0); - g_bColorSliderInUse = !!val; - } - if ( ev->atom == ctx->atoms.gamescopeColorChromaticAdaptationMode ) - { - uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorChromaticAdaptationMode, 0); - g_ColorMgmt.pending.chromaticAdaptationMode = ( EChromaticAdaptationMethod ) val; - } - // TODO: Hook up gamescopeColorMuraCorrectionImage for external. - if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) - { - std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorMuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ); - if ( set_mura_overlay( path.c_str() ) ) - hasRepaint = true; - } - // TODO: Hook up gamescopeColorMuraScale for external. - if ( ev->atom == ctx->atoms.gamescopeColorMuraScale[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) - { - uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorMuraScale[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL], 0); - float new_scale = bit_cast(val); - if ( set_mura_scale( new_scale ) ) - hasRepaint = true; - } - // TODO: Hook up gamescopeColorMuraCorrectionDisabled for external. - if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionDisabled[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) - { - bool disabled = !!get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorMuraCorrectionDisabled[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL], 0); - if ( g_bMuraCompensationDisabled != disabled ) { - g_bMuraCompensationDisabled = disabled; - hasRepaint = true; - } - } - if (ev->atom == ctx->atoms.gamescopeCreateXWaylandServer) - { - uint32_t identifier = get_prop(ctx, ctx->root, ctx->atoms.gamescopeCreateXWaylandServer, 0); - if (identifier) - { - wlserver_lock(); - uint32_t server_id = (uint32_t)wlserver_make_new_xwayland_server(); - assert(server_id != ~0u); - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(server_id); - init_xwayland_ctx(server_id, server); - char propertyString[256]; - snprintf(propertyString, sizeof(propertyString), "%u %u %s", identifier, server_id, server->get_nested_display_name()); - XTextProperty text_property = - { - .value = (unsigned char *)propertyString, - .encoding = ctx->atoms.utf8StringAtom, - .format = 8, - .nitems = strlen(propertyString), - }; - g_SteamCompMgrWaiter.AddWaitable( server->ctx.get() ); - XSetTextProperty( ctx->dpy, ctx->root, &text_property, ctx->atoms.gamescopeCreateXWaylandServerFeedback ); - wlserver_unlock(); - } - } - if (ev->atom == ctx->atoms.gamescopeDestroyXWaylandServer) - { - uint32_t server_id = get_prop(ctx, ctx->root, ctx->atoms.gamescopeDestroyXWaylandServer, 0); - - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(server_id); - if (server) - { - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - if (pFocus->focusWindow && - pFocus->focusWindow->type == steamcompmgr_win_type_t::XWAYLAND && - pFocus->focusWindow->xwayland().ctx == server->ctx.get()) - pFocus->focusWindow = nullptr; - - if (pFocus->inputFocusWindow && - pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND && - pFocus->inputFocusWindow->xwayland().ctx == server->ctx.get()) - pFocus->inputFocusWindow = nullptr; - - if (pFocus->overlayWindow && - pFocus->overlayWindow->type == steamcompmgr_win_type_t::XWAYLAND && - pFocus->overlayWindow->xwayland().ctx == server->ctx.get()) - pFocus->overlayWindow = nullptr; - - if (pFocus->externalOverlayWindow && - pFocus->externalOverlayWindow->type == steamcompmgr_win_type_t::XWAYLAND && - pFocus->externalOverlayWindow->xwayland().ctx == server->ctx.get()) - pFocus->externalOverlayWindow = nullptr; - - if (pFocus->notificationWindow && - pFocus->notificationWindow->type == steamcompmgr_win_type_t::XWAYLAND && - pFocus->notificationWindow->xwayland().ctx == server->ctx.get()) - pFocus->notificationWindow = nullptr; - - if (pFocus->overrideWindow && - pFocus->overrideWindow->type == steamcompmgr_win_type_t::XWAYLAND && - pFocus->overrideWindow->xwayland().ctx == server->ctx.get()) - pFocus->overrideWindow = nullptr; - - if (pFocus->keyboardFocusWindow && - pFocus->keyboardFocusWindow->type == steamcompmgr_win_type_t::XWAYLAND && - pFocus->keyboardFocusWindow->xwayland().ctx == server->ctx.get()) - pFocus->keyboardFocusWindow = nullptr; - - if (pFocus->fadeWindow && - pFocus->fadeWindow->type == steamcompmgr_win_type_t::XWAYLAND && - pFocus->fadeWindow->xwayland().ctx == server->ctx.get()) - pFocus->fadeWindow = nullptr; - - if (pFocus->cursor && - pFocus->cursor->getCtx() == server->ctx.get()) - pFocus->cursor = nullptr; - } - - wlserver_lock(); - g_SteamCompMgrWaiter.RemoveWaitable( server->ctx.get() ); - wlserver_destroy_xwayland_server(server); - wlserver_unlock(); - - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.gamescopeReshadeTechniqueIdx) - { - uint32_t technique_idx = get_prop(ctx, ctx->root, ctx->atoms.gamescopeReshadeTechniqueIdx, 0); - g_reshade_technique_idx = technique_idx; - } - if (ev->atom == ctx->atoms.gamescopeReshadeEffect) - { - std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeReshadeEffect ); - g_reshade_effect = path; - } - if (ev->atom == ctx->atoms.gamescopeDisplayDynamicRefreshBasedOnGamePresence) - { - g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = !!get_prop(ctx, ctx->root, ctx->atoms.gamescopeDisplayDynamicRefreshBasedOnGamePresence, 0); - } - if (ev->atom == ctx->atoms.wineHwndStyle) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->hasHwndStyle = true; - w->hwndStyle = get_prop(ctx, w->xwayland().id, ctx->atoms.wineHwndStyle, 0); - MakeFocusDirty(); - } - } - if (ev->atom == ctx->atoms.wineHwndStyleEx) - { - steamcompmgr_win_t * w = find_win(ctx, ev->window); - if (w) - { - w->hasHwndStyleEx = true; - w->hwndStyleEx = get_prop(ctx, w->xwayland().id, ctx->atoms.wineHwndStyleEx, 0); - MakeFocusDirty(); - } - } -} - -static int -error(Display *dpy, XErrorEvent *ev) -{ - // Do nothing. XErrors are usually benign. - return 0; -} - -static void -steamcompmgr_exit(void) -{ - g_ImageWaiter.Shutdown(); - - // Clean up any commits. - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - for ( steamcompmgr_win_t *w = server->ctx->list; w; w = w->xwayland().next ) - w->commit_queue.clear(); - } - } - g_steamcompmgr_xdg_wins.clear(); - g_HeldCommits[ HELD_COMMIT_BASE ] = nullptr; - g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; - - for ( auto &lut : g_ColorMgmtLuts ) lut.shutdown(); - for ( auto &lut : g_ColorMgmtLutsOverride ) lut.shutdown(); - for ( auto &lut : g_ScreenshotColorMgmtLuts ) lut.shutdown(); - for ( auto &lut : g_ScreenshotColorMgmtLutsHDR ) lut.shutdown(); - - if ( statsThreadRun == true ) - { - statsThreadRun = false; - statsThreadSem.signal(); - } - - { - g_ColorMgmt.pending.appHDRMetadata = nullptr; - g_ColorMgmt.current.appHDRMetadata = nullptr; - - s_scRGB709To2020Matrix = nullptr; - for (int i = 0; i < gamescope::GAMESCOPE_SCREEN_TYPE_COUNT; i++) - { - s_MuraCorrectionImage[i] = nullptr; - s_MuraCTMBlob[i] = nullptr; - } - } - - g_VirtualConnectorFocuses.clear(); - - gamescope::IBackend::Set( nullptr ); - - wlserver_lock(); - wlserver_shutdown(); - wlserver_unlock(false); -} - -[[noreturn]] static int -handle_io_error(Display *dpy) -{ - xwm_log.errorf("X11 I/O error! This is fatal. Aborting..."); - abort(); -} - -static bool -register_cm(xwayland_ctx_t *ctx) -{ - Window w; - Atom a; - static char net_wm_cm[] = "_NET_WM_CM_Sxx"; - - snprintf(net_wm_cm, sizeof(net_wm_cm), "_NET_WM_CM_S%d", ctx->scr); - a = XInternAtom(ctx->dpy, net_wm_cm, false); - - w = XGetSelectionOwner(ctx->dpy, a); - if (w != None) - { - XTextProperty tp; - char **strs; - int count; - Atom winNameAtom = XInternAtom(ctx->dpy, "_NET_WM_NAME", false); - - if (!XGetTextProperty(ctx->dpy, w, &tp, winNameAtom) && - !XGetTextProperty(ctx->dpy, w, &tp, XA_WM_NAME)) - { - xwm_log.errorf("Another composite manager is already running (0x%lx)", (unsigned long) w); - return false; - } - if (XmbTextPropertyToTextList(ctx->dpy, &tp, &strs, &count) == Success) - { - xwm_log.errorf("Another composite manager is already running (%s)", strs[0]); - - XFreeStringList(strs); - } - - XFree(tp.value); - - return false; - } - - w = XCreateSimpleWindow(ctx->dpy, RootWindow(ctx->dpy, ctx->scr), 0, 0, 1, 1, 0, None, - None); - - Xutf8SetWMProperties(ctx->dpy, w, "steamcompmgr", "steamcompmgr", NULL, 0, NULL, NULL, - NULL); - - Atom atomWmCheck = XInternAtom(ctx->dpy, "_NET_SUPPORTING_WM_CHECK", false); - XChangeProperty(ctx->dpy, ctx->root, atomWmCheck, - XA_WINDOW, 32, PropModeReplace, (unsigned char *)&w, 1); - XChangeProperty(ctx->dpy, w, atomWmCheck, - XA_WINDOW, 32, PropModeReplace, (unsigned char *)&w, 1); - - - Atom supportedAtoms[] = { - XInternAtom(ctx->dpy, "_NET_WM_STATE", false), - XInternAtom(ctx->dpy, "_NET_WM_STATE_FULLSCREEN", false), - XInternAtom(ctx->dpy, "_NET_WM_STATE_SKIP_TASKBAR", false), - XInternAtom(ctx->dpy, "_NET_WM_STATE_SKIP_PAGER", false), - XInternAtom(ctx->dpy, "_NET_ACTIVE_WINDOW", false), - }; - - XChangeProperty(ctx->dpy, ctx->root, XInternAtom(ctx->dpy, "_NET_SUPPORTED", false), - XA_ATOM, 32, PropModeAppend, (unsigned char *)supportedAtoms, - sizeof(supportedAtoms) / sizeof(supportedAtoms[0])); - - XSetSelectionOwner(ctx->dpy, a, w, 0); - - ctx->ourWindow = w; - - return true; -} - -static void -register_systray(xwayland_ctx_t *ctx) -{ - static char net_system_tray_name[] = "_NET_SYSTEM_TRAY_Sxx"; - - snprintf(net_system_tray_name, sizeof(net_system_tray_name), - "_NET_SYSTEM_TRAY_S%d", ctx->scr); - Atom net_system_tray = XInternAtom(ctx->dpy, net_system_tray_name, false); - - XSetSelectionOwner(ctx->dpy, net_system_tray, ctx->ourWindow, 0); -} - -bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t commitID, uint64_t earliestPresentTime, uint64_t earliestLatchTime ) -{ - bool bFoundWindow = false; - uint32_t j; - for ( j = 0; j < w->commit_queue.size(); j++ ) - { - if (w->commit_queue[ j ]->feedback.has_value()) - w->engineName = w->commit_queue[ j ]->feedback->vk_engine_name; - - if ( w->commit_queue[ j ]->commitID == commitID ) - { - gpuvis_trace_printf( "commit %lu done", w->commit_queue[ j ]->commitID ); - w->commit_queue[ j ]->done = true; - w->commit_queue[ j ]->earliest_present_time = earliestPresentTime; - w->commit_queue[ j ]->present_margin = earliestPresentTime - earliestLatchTime; - bFoundWindow = true; - - // Window just got a new available commit, determine if that's worth a repaint - - // If this is a forwarded vr plane, repaint - if ( w->oulTargetVROverlay ) - { - g_bUpdateForwardedVROverlays = true; - w->bNeedsForwarding = true; - } - - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - - // If this is an overlay that we're presenting, repaint - if ( w == pFocus->overlayWindow && w->opacity != TRANSLUCENT ) - { - hasRepaintNonBasePlane = true; - } - - if ( w == pFocus->notificationWindow && w->opacity != TRANSLUCENT ) - { - hasRepaintNonBasePlane = true; - } - - // matt: the performance overlay in Steam will interfere with VRR if we let this repaint. - // This has been broken since the logic for external overlay repaints was moved out of - // outdatedInteractiveFocus. It can cause displays to jump between the focused app's - // refresh rate and the maximum panel refresh rate when presenting any type of overlay, - // creating noticeable VRR flicker in the process. - // TODO: fix this properly for all overlays, including Steam notifications and QAM - // HACK: If VRR is active, prevent external overlays, i.e. mangoapp, from repainting the base plane - if ( ( w == pFocus->externalOverlayWindow && w->opacity != TRANSLUCENT ) && - ( GetBackend()->GetCurrentConnector() && !GetBackend()->GetCurrentConnector()->IsVRRActive() ) ) - { - hasRepaintNonBasePlane = true; - } - - // If this is the main plane, repaint - if ( w == pFocus->focusWindow && !w->isSteamStreamingClient ) - { - if ( !cv_paint_debug_pause_base_plane ) - g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ]; - hasRepaint = true; - - focusWindow_engine = w->engineName; - focusWindow_pid = w->pid; - } - - if ( w == pFocus->overrideWindow ) - { - hasRepaintNonBasePlane = true; - } - - if ( w->isSteamStreamingClientVideo && pFocus->focusWindow && pFocus->focusWindow->isSteamStreamingClient ) - { - if ( !cv_paint_debug_pause_base_plane ) - g_HeldCommits[ HELD_COMMIT_BASE ] = w->commit_queue[ j ]; - hasRepaint = true; - } - } - - if ( w->outdatedInteractiveFocus ) - { - MakeFocusDirty(); - w->outdatedInteractiveFocus = false; - } - - break; - } - } - - if ( bFoundWindow == true ) - { - if ( j > 0 ) - w->commit_queue.erase( w->commit_queue.begin(), w->commit_queue.begin() + j ); - w->receivedDoneCommit = true; - return true; - } - - return false; -} - -// TODO: Merge these two functions. -void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vblank_idx ) -{ - std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); - - uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; - - // commits that were not ready to be presented based on their display timing. - static std::vector< CommitDoneEntry_t > commits_before_their_time; - commits_before_their_time.clear(); - commits_before_their_time.reserve( 32 ); - - // windows in FIFO mode we got a new frame to present for this vblank - static std::unordered_set< uint64_t > fifo_win_seqs; - fifo_win_seqs.clear(); - fifo_win_seqs.reserve( 32 ); - - uint64_t now = get_time_in_nanos(); - - // very fast loop yes - for ( auto& entry : ctx->doneCommits.listCommitsDone ) - { - bool entry_vblank = vblank; - - if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() ) - { - for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next ) - { - if (w->seq != entry.winSeq) - continue; - - entry_vblank = entry_vblank && steamcompmgr_should_vblank_window( true, vblank_idx, w, now ); - } - } - else - { - entry_vblank = entry_vblank && steamcompmgr_should_vblank_window( true, vblank_idx ); - } - - if (entry.fifo && (!entry_vblank || fifo_win_seqs.count(entry.winSeq) > 0)) - { - commits_before_their_time.push_back( entry ); - continue; - } - - if (!entry.earliestPresentTime) - { - entry.earliestPresentTime = next_refresh_time; - entry.earliestLatchTime = now; - } - - if ( entry.desiredPresentTime > next_refresh_time ) - { - commits_before_their_time.push_back( entry ); - continue; - } - - for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next ) - { - if (w->seq != entry.winSeq) - continue; - if (handle_done_commit(w, ctx, entry.commitID, entry.earliestPresentTime, entry.earliestLatchTime)) - { - if (entry.fifo) - fifo_win_seqs.insert(entry.winSeq); - break; - } - } - } - - ctx->doneCommits.listCommitsDone.swap( commits_before_their_time ); -} - -void handle_done_commits_xdg( bool vblank, uint64_t vblank_idx ) -{ - std::lock_guard lock( g_steamcompmgr_xdg_done_commits.listCommitsDoneLock ); - - uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; - - // commits that were not ready to be presented based on their display timing. - static std::vector< CommitDoneEntry_t > commits_before_their_time; - commits_before_their_time.clear(); - commits_before_their_time.reserve( 32 ); - - // windows in FIFO mode we got a new frame to present for this vblank - static std::unordered_set< uint64_t > fifo_win_seqs; - fifo_win_seqs.clear(); - fifo_win_seqs.reserve( 32 ); - - uint64_t now = get_time_in_nanos(); - - vblank = vblank && steamcompmgr_should_vblank_window( true, vblank_idx ); - - // very fast loop yes - for ( auto& entry : g_steamcompmgr_xdg_done_commits.listCommitsDone ) - { - if (entry.fifo && (!vblank || fifo_win_seqs.count(entry.winSeq) > 0)) - { - commits_before_their_time.push_back( entry ); - continue; - } - - if (!entry.earliestPresentTime) - { - entry.earliestPresentTime = next_refresh_time; - entry.earliestLatchTime = now; - } - - if ( entry.desiredPresentTime > next_refresh_time ) - { - commits_before_their_time.push_back( entry ); - break; - } - - for (const auto& xdg_win : g_steamcompmgr_xdg_wins) - { - if (xdg_win->seq != entry.winSeq) - continue; - if (handle_done_commit(xdg_win.get(), nullptr, entry.commitID, entry.earliestPresentTime, entry.earliestLatchTime)) - { - if (entry.fifo) - fifo_win_seqs.insert(entry.winSeq); - break; - } - } - } - - g_steamcompmgr_xdg_done_commits.listCommitsDone.swap( commits_before_their_time ); -} - -gamescope::ConVar cv_mangoapp_use_output_timing{ "mangoapp_use_output_timing", true }; - -void handle_presented_for_window( steamcompmgr_win_t* w ) -{ - // wlserver_lock is held. - - uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; - - uint64_t refresh_cycle = g_nSteamCompMgrTargetFPS && steamcompmgr_window_should_limit_fps( w ) - ? g_SteamCompMgrLimitedAppRefreshCycle - : g_SteamCompMgrAppRefreshCycle; - - commit_t *lastCommit = get_window_last_done_commit_peek(w); - if (lastCommit) - { - if ( !cv_mangoapp_use_output_timing ) - { - // We might present the same commit multiple times. In these cases - // there will be no frametime delta as the last frame was just re-used. - uint64_t frametime_ns = lastCommit->present_time - w->last_commit_present_time; - if ( frametime_ns > 0 ) - { - if ( w->appID > 0 ) - wlserver_app_presented( w->appID, frametime_ns ); - - w->last_commit_present_time = lastCommit->present_time; - } - } - - if (!lastCommit->presentation_feedbacks.empty() || lastCommit->present_id) - { - if (!lastCommit->presentation_feedbacks.empty()) - { - wlserver_presentation_feedback_presented( - lastCommit->surf, - lastCommit->presentation_feedbacks, - next_refresh_time, - refresh_cycle); - } - - if (lastCommit->present_id) - { - wlserver_past_present_timing( - lastCommit->surf, - *lastCommit->present_id, - lastCommit->desired_present_time, - next_refresh_time, - lastCommit->earliest_present_time, - lastCommit->present_margin); - lastCommit->present_id = std::nullopt; - } - } - } - - if (struct wlr_surface *surface = w->current_surface()) - { - auto info = get_wl_surface_info(surface); - if (info != nullptr && info->last_refresh_cycle != refresh_cycle) - { - // Could have got the override set in this bubble. - surface = w->current_surface(); - - if (info->last_refresh_cycle != refresh_cycle) - { - info->last_refresh_cycle = refresh_cycle; - wlserver_refresh_cycle(surface, refresh_cycle); - } - } - } -} - -void handle_presented_xwayland( xwayland_ctx_t *ctx ) -{ - for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next ) - { - handle_presented_for_window(w); - } -} - -void handle_presented_xdg() -{ - for (const auto& xdg_win : g_steamcompmgr_xdg_wins) - { - handle_presented_for_window(xdg_win.get()); - } -} - -void nudge_steamcompmgr( void ) -{ - g_SteamCompMgrWaiter.Nudge(); -} - -void force_repaint( void ) -{ - g_bForceRepaint = true; - nudge_steamcompmgr(); -} - -struct TempUpscaleImage_t -{ - gamescope::OwningRc pTexture; - // Timeline of upscale -> release, to be used as acquire for the commit. - std::shared_ptr pReleaseTimeline; - uint64_t ulLastPoint = 0ul; -}; - -static std::vector g_pUpscaleImages; -void ClearUpscaleImages() -{ - g_pUpscaleImages.clear(); -} - -static TempUpscaleImage_t *GetTempUpscaleImage( uint32_t uWidth, uint32_t uHeight, uint32_t uDrmFormat ) -{ - if ( g_pUpscaleImages.size() ) - { - // Mixing and matching sizes to only do the min required would be nice - // but massively complicates caching. - if ( g_pUpscaleImages[0].pTexture->width() != uWidth || - g_pUpscaleImages[0].pTexture->height() != uHeight || - g_pUpscaleImages[0].pTexture->drmFormat() != uDrmFormat ) - { - g_pUpscaleImages.clear(); - } - } - - for ( TempUpscaleImage_t &image : g_pUpscaleImages ) - { - if ( !image.pTexture->IsInUse() ) - return ℑ - } - - if ( g_pUpscaleImages.size() > 8 ) - { - xwm_log.warnf( "No upscale images free!\n" ); - return {}; - } - - gamescope::OwningRc pTexture = new CVulkanTexture(); - - std::shared_ptr pTimeline = gamescope::CTimeline::Create(); - if ( !pTimeline ) - return nullptr; - - CVulkanTexture::createFlags imageFlags; - imageFlags.bSampled = true; - imageFlags.bStorage = true; - imageFlags.bFlippable = true; - pTexture->BInit( g_nOutputWidth, g_nOutputHeight, 1, uDrmFormat, imageFlags ); - TempUpscaleImage_t &image = g_pUpscaleImages.emplace_back( std::move( pTexture ), std::move( pTimeline ) ); - - return ℑ -} - -gamescope::ConVar cv_surface_update_force_only_current_surface( "surface_update_force_only_current_surface", false, "Force updates to apply only to the current surface, ignoring commits for other surfaces." ); - -void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, ResListEntry_t& reslistentry) -{ - struct wlr_buffer *buf = reslistentry.buf; - - if ( w == nullptr ) - { - wlserver_lock(); - wlr_buffer_unlock( buf ); - wlserver_unlock(); - - // Make sure to send the discarded event if we hit this - // to ensure forward progress. - if (!reslistentry.presentation_feedbacks.empty()) - { - wlserver_presentation_feedback_discard( reslistentry.surf, reslistentry.presentation_feedbacks ); - // presentation_feedbacks cleared by wlserver_presentation_feedback_discard - } - - xwm_log.errorf( "waylandres but no win" ); - return; - } - - // If we ever use HDR on the surface, only ever accept flip commits from the WSI layer. - if ( reslistentry.feedback && reslistentry.feedback->vk_colorspace != VK_COLOR_SPACE_SRGB_NONLINEAR_KHR ) - { - w->bHasHadNonSRGBColorSpace = true; - } - - // If there are random commits that are really thin/small when we have the WSI layer active ever, - // let's just ignore these as they are probably bogus commits from glamor. - bool bPossiblyBogus = reslistentry.buf->width <= 2 || reslistentry.buf->height <= 2; - - // If the buffer has no damage, always prefer our override surface. - bool bHasDamage = ( reslistentry.surf->buffer_damage.extents.x2 - reslistentry.surf->buffer_damage.extents.x1 ) > 2 && - ( reslistentry.surf->buffer_damage.extents.y2 - reslistentry.surf->buffer_damage.extents.y1 ) > 2; - - // If we have an override surface, make sure this commit is for the current surface - // or if the commit is probably bogus. - bool bOnlyCurrentSurface = w->bHasHadNonSRGBColorSpace || bPossiblyBogus || !bHasDamage || cv_surface_update_force_only_current_surface; - - bool for_current_surface = !w->override_surface() || w->current_surface() == reslistentry.surf; - - if ( !for_current_surface ) - { - xwm_log.debugf( "Got commit not for current surface." ); - } - - if ( bOnlyCurrentSurface && !for_current_surface ) - { - wlserver_lock(); - wlr_buffer_unlock( buf ); - wlserver_unlock(); - w->receivedDoneCommit = true; - return; - } - - bool already_exists = false; - for ( const auto& existing_commit : w->commit_queue ) - { - if (existing_commit->buf == buf) - already_exists = true; - } - - if ( already_exists && !reslistentry.feedback && reslistentry.presentation_feedbacks.empty() ) - { - wlserver_lock(); - wlr_buffer_unlock( buf ); - wlserver_unlock(); - xwm_log.debugf( "got the same buffer committed twice, ignoring." ); - - // If we have a duplicated commit + frame callback, ensure that is signalled. - // This matches Mutter and Weston behavior, so it's plausible that some application relies on forward progress. - // We're essentially discarding the commit here, so consider it complete right away. - w->receivedDoneCommit = true; - return; - } - - gamescope::Rc newCommit = import_commit( - w, - reslistentry.surf, - buf, - reslistentry.async, - std::move(reslistentry.feedback), - std::move(reslistentry.presentation_feedbacks), - reslistentry.present_id, - reslistentry.desired_present_time, - reslistentry.fifo ); - - int fence = -1; - if ( newCommit != nullptr ) - { - global_focus_t *pCurrentFocus = GetCurrentFocus(); - - static bool bMangoappSocketDisable = env_to_bool( getenv( "GAMESCOPE_MANGOAPP_SOCKET_DISABLE" )); - - // Whether or not to nudge mango app when this commit is done. - const bool mango_nudge = pCurrentFocus && ( ( w == pCurrentFocus->focusWindow && !w->isSteamStreamingClient ) || - ( pCurrentFocus->focusWindow && pCurrentFocus->focusWindow->isSteamStreamingClient && w->isSteamStreamingClientVideo ) ) - && !bMangoappSocketDisable; - - bool bValidPreemptiveScale = reslistentry.pAcquirePoint && pCurrentFocus && w == pCurrentFocus->focusWindow && cv_upscale_preemptive; - bool bPreemptiveUpscale = bValidPreemptiveScale && newCommit->ShouldPreemptivelyUpscale(); - - bool bKnownReady = false; - - std::pair eventFd = gamescope::CAcquireTimelinePoint::k_InvalidEvent; - - if ( bPreemptiveUpscale ) - { - FrameInfo_t upscaledFrameInfo{}; - upscaledFrameInfo.applyOutputColorMgmt = true; - upscaledFrameInfo.outputEncodingEOTF = ( newCommit->colorspace() == GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR || newCommit->colorspace() == GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB ) - ? EOTF_Gamma22 - : EOTF_PQ; - - float flOldGlobalScale = globalScaleRatio; - float flOldZoomScale = zoomScaleRatio; - float flOldOverscanScale = overscanScaleRatio; - overscanScaleRatio = 1.0f; - zoomScaleRatio = 1.0f; - globalScaleRatio = 1.0f; - paint_window_commit( newCommit, w, w, &upscaledFrameInfo, nullptr ); - upscaledFrameInfo.useFSRLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::FSR; - upscaledFrameInfo.useNISLayer0 = g_upscaleFilter == GamescopeUpscaleFilter::NIS; - globalScaleRatio = flOldGlobalScale; - zoomScaleRatio = flOldZoomScale; - overscanScaleRatio = flOldOverscanScale; - - TempUpscaleImage_t *pTempImage = GetTempUpscaleImage( g_nOutputWidth, g_nOutputHeight, g_output.uOutputFormat ); - if ( pTempImage ) - { - const uint64_t ulNextReleasePoint = ++pTempImage->ulLastPoint; - - std::unique_ptr pCommandBuffer = g_device.commandBuffer(); - - pCommandBuffer->AddDependency( reslistentry.pAcquirePoint->GetTimeline()->ToVkSemaphore(), reslistentry.pAcquirePoint->GetPoint() ); - pCommandBuffer->AddSignal( pTempImage->pReleaseTimeline->ToVkSemaphore(), ulNextReleasePoint ); - - static std::optional s_ulLastPreemptiveUpscaleSeqNo; - - if ( s_ulLastPreemptiveUpscaleSeqNo ) - { - vulkan_wait( *s_ulLastPreemptiveUpscaleSeqNo, true ); - } - - std::optional seqNo = vulkan_composite( &upscaledFrameInfo, nullptr, false, pTempImage->pTexture, false, std::move( pCommandBuffer ) ); - - if ( cv_upscale_preemptive_debug_force_sync ) - { - vulkan_wait( *seqNo, true ); - } - - s_ulLastPreemptiveUpscaleSeqNo = seqNo; - - newCommit->upscaledTexture = std::optional - { - std::in_place_t{}, - g_upscaleFilter, - g_upscaleScaler, - g_nOutputWidth, - g_nOutputHeight, - pTempImage->pTexture, - upscaledFrameInfo.outputEncodingEOTF == EOTF_Gamma22 ? VK_COLOR_SPACE_SRGB_NONLINEAR_KHR : VK_COLOR_SPACE_HDR10_ST2084_EXT, - }; - - // Manifest a new acquire timeline point with this inline work. - eventFd = gamescope::CAcquireTimelinePoint( pTempImage->pReleaseTimeline, ulNextReleasePoint ).CreateEventFd(); - - //xwm_log.infof( "Pre-emptively upscaling!" ); - } - else - { - bPreemptiveUpscale = false; - } - } - - if ( !bPreemptiveUpscale ) - { - if ( bValidPreemptiveScale ) - { - ClearUpscaleImages(); - } - - if ( reslistentry.pAcquirePoint ) - { - eventFd = reslistentry.pAcquirePoint->CreateEventFd(); - } - } - - if ( gamescope::IBackendFb *pBackendFb = newCommit->vulkanTex->GetBackendFb() ) - { - if ( reslistentry.pReleasePoint ) - pBackendFb->SetReleasePoint( reslistentry.pReleasePoint ); - else - pBackendFb->SetBuffer( buf ); - } - - if ( eventFd != gamescope::CAcquireTimelinePoint::k_InvalidEvent ) - { - fence = eventFd.first; - bKnownReady = eventFd.second; - } - else - { - struct wlr_dmabuf_attributes dmabuf = {0}; - if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) - { - fence = dup( dmabuf.fd[0] ); - } - else - { - fence = newCommit->vulkanTex->memoryFence(); - } - } - - gpuvis_trace_printf( "pushing wait for commit %lu win %lx", newCommit->commitID, w->type == steamcompmgr_win_type_t::XWAYLAND ? w->xwayland().id : 0 ); - { - newCommit->SetFence( fence, mango_nudge, doneCommits ); - if ( bKnownReady ) - newCommit->Signal(); - else - g_ImageWaiter.AddWaitable( newCommit.get() ); - } - - w->commit_queue.push_back( std::move(newCommit) ); - } -} - -void check_new_xwayland_res(xwayland_ctx_t *ctx) -{ - // When importing buffer, we'll potentially need to perform operations with - // a wlserver lock (e.g. wlr_buffer_lock). We can't do this with a - // wayland_commit_queue lock because that causes deadlocks. - std::vector& tmp_queue = ctx->xwayland_server->retrieve_commits(); - - for ( uint32_t i = 0; i < tmp_queue.size(); i++ ) - { - steamcompmgr_win_t *w = find_win( ctx, tmp_queue[ i ].surf ); - update_wayland_res( &ctx->doneCommits, w, tmp_queue[ i ]); - } -} - -void check_new_xdg_res() -{ - std::vector tmp_queue = wlserver_xdg_commit_queue(); - for ( uint32_t i = 0; i < tmp_queue.size(); i++ ) - { - for ( const auto& xdg_win : g_steamcompmgr_xdg_wins ) - { - if ( xdg_win->xdg().surface.main_surface == tmp_queue[ i ].surf ) - { - update_wayland_res( &g_steamcompmgr_xdg_done_commits, xdg_win.get(), tmp_queue[ i ] ); - break; - } - } - } -} - - -static void -handle_xfixes_selection_notify( xwayland_ctx_t *ctx, XFixesSelectionNotifyEvent *event ) -{ - if (event->owner == ctx->ourWindow) - { - return; - } - - XConvertSelection(ctx->dpy, event->selection, ctx->atoms.utf8StringAtom, event->selection, ctx->ourWindow, CurrentTime); - XFlush(ctx->dpy); -} - -void xwayland_ctx_t::Dispatch() -{ - xwayland_ctx_t *ctx = this; - - MouseCursor *cursor = ctx->cursor.get(); - bool bSetFocus = false; - - while (XPending(ctx->dpy)) - { - XEvent ev; - int ret = XNextEvent(ctx->dpy, &ev); - if (ret != 0) - { - xwm_log.errorf("XNextEvent failed"); - break; - } - if (debugEvents) - { - gpuvis_trace_printf("event %d", ev.type); - printf("event %d\n", ev.type); - } - switch (ev.type) { - case CreateNotify: - if (ev.xcreatewindow.parent == ctx->root) - add_win(ctx, ev.xcreatewindow.window, 0, ev.xcreatewindow.serial); - break; - case ConfigureNotify: - configure_win(ctx, &ev.xconfigure); - break; - case DestroyNotify: - { - steamcompmgr_win_t * w = find_win(ctx, ev.xdestroywindow.window); - - if (w && w->xwayland().id == ev.xdestroywindow.window) - destroy_win(ctx, ev.xdestroywindow.window, true, true); - break; - } - case MapNotify: - { - steamcompmgr_win_t * w = find_win(ctx, ev.xmap.window); - - if (w && w->xwayland().id == ev.xmap.window) - map_win(ctx, ev.xmap.window, ev.xmap.serial); - break; - } - case UnmapNotify: - { - steamcompmgr_win_t * w = find_win(ctx, ev.xunmap.window); - - if (w && w->xwayland().id == ev.xunmap.window) - unmap_win(ctx, ev.xunmap.window, true); - break; - } - case FocusOut: - { - steamcompmgr_win_t * w = find_win( ctx, ev.xfocus.window ); - - // If focus escaped the current desired keyboard focus window, check where it went - if ( w && w->xwayland().id == ctx->currentKeyboardFocusWindow ) - { - Window newKeyboardFocus = None; - int nRevertMode = 0; - XGetInputFocus( ctx->dpy, &newKeyboardFocus, &nRevertMode ); - - // Find window or its toplevel parent - steamcompmgr_win_t *kbw = find_win( ctx, newKeyboardFocus ); - - if ( kbw ) - { - if ( kbw->xwayland().id == ctx->currentKeyboardFocusWindow ) - { - // focus went to a child, this is fine, make note of it in case we need to fix it - ctx->currentKeyboardFocusWindow = newKeyboardFocus; - } - else - { - // focus went elsewhere, correct it - bSetFocus = true; - } - } - } - - break; - } - case ReparentNotify: - if (ev.xreparent.parent == ctx->root) - add_win(ctx, ev.xreparent.window, 0, ev.xreparent.serial); - else - { - steamcompmgr_win_t * w = find_win(ctx, ev.xreparent.window); - - if (w && w->xwayland().id == ev.xreparent.window) - { - destroy_win(ctx, ev.xreparent.window, false, true); - } - else - { - // If something got reparented _to_ a toplevel window, - // go check for the fullscreen workaround again. - w = find_win(ctx, ev.xreparent.parent); - if (w) - { - get_size_hints(ctx, w); - MakeFocusDirty(); - } - } - } - break; - case CirculateNotify: - circulate_win(ctx, &ev.xcirculate); - break; - case MapRequest: - map_request(ctx, &ev.xmaprequest); - break; - case ConfigureRequest: - configure_request(ctx, &ev.xconfigurerequest); - break; - case CirculateRequest: - circulate_request(ctx, &ev.xcirculaterequest); - break; - case Expose: - break; - case PropertyNotify: - handle_property_notify(ctx, &ev.xproperty); - break; - case ClientMessage: - handle_client_message(ctx, &ev.xclient); - break; - case LeaveNotify: - break; - case SelectionNotify: - handle_selection_notify(ctx, &ev.xselection); - break; - case SelectionRequest: - handle_selection_request(ctx, &ev.xselectionrequest); - break; - - default: - if (ev.type == ctx->damage_event + XDamageNotify) - { - damage_win(ctx, (XDamageNotifyEvent *) &ev); - } - else if (ev.type == ctx->xfixes_event + XFixesCursorNotify) - { - cursor->setDirty(); - } - else if (ev.type == ctx->xfixes_event + XFixesSelectionNotify) - { - handle_xfixes_selection_notify(ctx, (XFixesSelectionNotifyEvent *) &ev); - } - break; - } - XFlush(ctx->dpy); - } - - if ( bSetFocus ) - { - XSetInputFocus(ctx->dpy, ctx->currentKeyboardFocusWindow, RevertToNone, CurrentTime); - } -} - -struct rgba_t -{ - uint8_t r,g,b,a; -}; - -static bool -load_mouse_cursor( MouseCursor *cursor, const char *path, int hx, int hy ) -{ - int w, h, channels; - rgba_t *data = (rgba_t *) stbi_load(path, &w, &h, &channels, STBI_rgb_alpha); - if (!data) - { - xwm_log.errorf("Failed to open/load cursor file"); - return false; - } - - std::transform(data, data + w * h, data, [](rgba_t x) { - if (x.a == 0) - return rgba_t{}; - return rgba_t{ - uint8_t((x.b * x.a) / 255), - uint8_t((x.g * x.a) / 255), - uint8_t((x.r * x.a) / 255), - x.a }; - }); - - // Data is freed by XDestroyImage in setCursorImage. - return cursor->setCursorImage((char *)data, w, h, hx, hy); -} - -const char* g_customCursorPath = nullptr; -int g_customCursorHotspotX = 0; -int g_customCursorHotspotY = 0; - -xwayland_ctx_t g_ctx; - -static bool setup_error_handlers = false; - -void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server) -{ - assert(!xwayland_server->ctx); - xwayland_server->ctx = std::make_unique(); - xwayland_ctx_t *ctx = xwayland_server->ctx.get(); - - int composite_major, composite_minor; - int xres_major, xres_minor; - - ctx->xwayland_server = xwayland_server; - ctx->dpy = xwayland_server->get_xdisplay(); - if (!ctx->dpy) - { - xwm_log.errorf("Can't open display"); - exit(1); - } - - if (!setup_error_handlers) - { - XSetErrorHandler(error); - XSetIOErrorHandler(handle_io_error); - setup_error_handlers = true; - } - - if (synchronize) - XSynchronize(ctx->dpy, 1); - - ctx->scr = DefaultScreen(ctx->dpy); - ctx->root = RootWindow(ctx->dpy, ctx->scr); - - if (!XRenderQueryExtension(ctx->dpy, &ctx->render_event, &ctx->render_error)) - { - xwm_log.errorf("No render extension"); - exit(1); - } - if (!XQueryExtension(ctx->dpy, COMPOSITE_NAME, &ctx->composite_opcode, - &ctx->composite_event, &ctx->composite_error)) - { - xwm_log.errorf("No composite extension"); - exit(1); - } - XCompositeQueryVersion(ctx->dpy, &composite_major, &composite_minor); - - if (!XDamageQueryExtension(ctx->dpy, &ctx->damage_event, &ctx->damage_error)) - { - xwm_log.errorf("No damage extension"); - exit(1); - } - if (!XFixesQueryExtension(ctx->dpy, &ctx->xfixes_event, &ctx->xfixes_error)) - { - xwm_log.errorf("No XFixes extension"); - exit(1); - } - if (!XShapeQueryExtension(ctx->dpy, &ctx->xshape_event, &ctx->xshape_error)) - { - xwm_log.errorf("No XShape extension"); - exit(1); - } - if (!XFixesQueryExtension(ctx->dpy, &ctx->xfixes_event, &ctx->xfixes_error)) - { - xwm_log.errorf("No XFixes extension"); - exit(1); - } - if (!XResQueryVersion(ctx->dpy, &xres_major, &xres_minor)) - { - xwm_log.errorf("No XRes extension"); - exit(1); - } - if (xres_major != 1 || xres_minor < 2) - { - xwm_log.errorf("Unsupported XRes version: have %d.%d, want 1.2", xres_major, xres_minor); - exit(1); - } - if (!XQueryExtension(ctx->dpy, - "XInputExtension", - &ctx->xinput_opcode, - &ctx->xinput_event, - &ctx->xinput_error)) - { - xwm_log.errorf("No XInput extension"); - exit(1); - } - int xi_major = 2; - int xi_minor = 0; - XIQueryVersion(ctx->dpy, &xi_major, &xi_minor); - - if (!register_cm(ctx)) - { - exit(1); - } - - register_systray(ctx); - - /* get atoms */ - ctx->atoms.steamAtom = XInternAtom(ctx->dpy, STEAM_PROP, false); - ctx->atoms.steamInputFocusAtom = XInternAtom(ctx->dpy, "STEAM_INPUT_FOCUS", false); - ctx->atoms.steamTouchClickModeAtom = XInternAtom(ctx->dpy, "STEAM_TOUCH_CLICK_MODE", false); - ctx->atoms.gameAtom = XInternAtom(ctx->dpy, GAME_PROP, false); - ctx->atoms.overlayAtom = XInternAtom(ctx->dpy, OVERLAY_PROP, false); - ctx->atoms.externalOverlayAtom = XInternAtom(ctx->dpy, EXTERNAL_OVERLAY_PROP, false); - ctx->atoms.opacityAtom = XInternAtom(ctx->dpy, OPACITY_PROP, false); - ctx->atoms.gamesRunningAtom = XInternAtom(ctx->dpy, GAMES_RUNNING_PROP, false); - ctx->atoms.screenScaleAtom = XInternAtom(ctx->dpy, SCREEN_SCALE_PROP, false); - ctx->atoms.screenZoomAtom = XInternAtom(ctx->dpy, SCREEN_MAGNIFICATION_PROP, false); - ctx->atoms.winTypeAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE", false); - ctx->atoms.winDesktopAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_DESKTOP", false); - ctx->atoms.winDockAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_DOCK", false); - ctx->atoms.winToolbarAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", false); - ctx->atoms.winMenuAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_MENU", false); - ctx->atoms.winUtilAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_UTILITY", false); - ctx->atoms.winSplashAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_SPLASH", false); - ctx->atoms.winDialogAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_DIALOG", false); - ctx->atoms.winNormalAtom = XInternAtom(ctx->dpy, "_NET_WM_WINDOW_TYPE_NORMAL", false); - ctx->atoms.sizeHintsAtom = XInternAtom(ctx->dpy, "WM_NORMAL_HINTS", false); - ctx->atoms.netWMStateFullscreenAtom = XInternAtom(ctx->dpy, "_NET_WM_STATE_FULLSCREEN", false); - ctx->atoms.activeWindowAtom = XInternAtom(ctx->dpy, "_NET_ACTIVE_WINDOW", false); - ctx->atoms.netWMStateAtom = XInternAtom(ctx->dpy, "_NET_WM_STATE", false); - ctx->atoms.WMTransientForAtom = XInternAtom(ctx->dpy, "WM_TRANSIENT_FOR", false); - ctx->atoms.netWMStateHiddenAtom = XInternAtom(ctx->dpy, "_NET_WM_STATE_HIDDEN", false); - ctx->atoms.netWMStateFocusedAtom = XInternAtom(ctx->dpy, "_NET_WM_STATE_FOCUSED", false); - ctx->atoms.netWMStateSkipTaskbarAtom = XInternAtom(ctx->dpy, "_NET_WM_STATE_SKIP_TASKBAR", false); - ctx->atoms.netWMStateSkipPagerAtom = XInternAtom(ctx->dpy, "_NET_WM_STATE_SKIP_PAGER", false); - ctx->atoms.WLSurfaceIDAtom = XInternAtom(ctx->dpy, "WL_SURFACE_ID", false); - ctx->atoms.WMStateAtom = XInternAtom(ctx->dpy, "WM_STATE", false); - ctx->atoms.utf8StringAtom = XInternAtom(ctx->dpy, "UTF8_STRING", false); - ctx->atoms.netWMNameAtom = XInternAtom(ctx->dpy, "_NET_WM_NAME", false); - ctx->atoms.netWMIcon = XInternAtom(ctx->dpy, "_NET_WM_ICON", false); - ctx->atoms.netSystemTrayOpcodeAtom = XInternAtom(ctx->dpy, "_NET_SYSTEM_TRAY_OPCODE", false); - ctx->atoms.steamStreamingClientAtom = XInternAtom(ctx->dpy, "STEAM_STREAMING_CLIENT", false); - ctx->atoms.steamStreamingClientVideoAtom = XInternAtom(ctx->dpy, "STEAM_STREAMING_CLIENT_VIDEO", false); - ctx->atoms.steamGamescopeVROverlayTarget = XInternAtom(ctx->dpy, "STEAM_GAMESCOPE_VROVERLAY_TARGET", false); - ctx->atoms.gamescopePid = XInternAtom(ctx->dpy, "GAMESCOPE_PID", false); - ctx->atoms.gamescopeVROverlayForwarding = XInternAtom(ctx->dpy, "GAMESCOPE_VROVERLAY_FORWARDING", false); - ctx->atoms.gamescopeFocusableAppsAtom = XInternAtom(ctx->dpy, "GAMESCOPE_FOCUSABLE_APPS", false); - ctx->atoms.gamescopeFocusableWindowsAtom = XInternAtom(ctx->dpy, "GAMESCOPE_FOCUSABLE_WINDOWS", false); - ctx->atoms.gamescopeFocusedAppAtom = XInternAtom( ctx->dpy, "GAMESCOPE_FOCUSED_APP", false ); - ctx->atoms.gamescopeFocusedAppGfxAtom = XInternAtom( ctx->dpy, "GAMESCOPE_FOCUSED_APP_GFX", false ); - ctx->atoms.gamescopeFocusedWindowAtom = XInternAtom( ctx->dpy, "GAMESCOPE_FOCUSED_WINDOW", false ); - ctx->atoms.gamescopeCtrlAppIDAtom = XInternAtom(ctx->dpy, "GAMESCOPECTRL_BASELAYER_APPID", false); - ctx->atoms.gamescopeCtrlWindowAtom = XInternAtom(ctx->dpy, "GAMESCOPECTRL_BASELAYER_WINDOW", false); - ctx->atoms.WMChangeStateAtom = XInternAtom(ctx->dpy, "WM_CHANGE_STATE", false); - ctx->atoms.gamescopeInputCounterAtom = XInternAtom(ctx->dpy, "GAMESCOPE_INPUT_COUNTER", false); - ctx->atoms.gamescopeScreenShotAtom = XInternAtom( ctx->dpy, "GAMESCOPECTRL_REQUEST_SCREENSHOT", false ); - ctx->atoms.gamescopeDebugScreenShotAtom = XInternAtom( ctx->dpy, "GAMESCOPECTRL_DEBUG_REQUEST_SCREENSHOT", false ); - - ctx->atoms.gamescopeFocusDisplay = XInternAtom(ctx->dpy, "GAMESCOPE_FOCUS_DISPLAY", false); - ctx->atoms.gamescopeMouseFocusDisplay = XInternAtom(ctx->dpy, "GAMESCOPE_MOUSE_FOCUS_DISPLAY", false); - ctx->atoms.gamescopeKeyboardFocusDisplay = XInternAtom( ctx->dpy, "GAMESCOPE_KEYBOARD_FOCUS_DISPLAY", false ); - - // In nanoseconds... - ctx->atoms.gamescopeTuneableVBlankRedZone = XInternAtom( ctx->dpy, "GAMESCOPE_TUNEABLE_VBLANK_REDZONE", false ); - ctx->atoms.gamescopeTuneableRateOfDecay = XInternAtom( ctx->dpy, "GAMESCOPE_TUNEABLE_VBLANK_RATE_OF_DECAY_PERCENTAGE", false ); - - ctx->atoms.gamescopeScalingFilter = XInternAtom( ctx->dpy, "GAMESCOPE_SCALING_FILTER", false ); - ctx->atoms.gamescopeFSRSharpness = XInternAtom( ctx->dpy, "GAMESCOPE_FSR_SHARPNESS", false ); - ctx->atoms.gamescopeSharpness = XInternAtom( ctx->dpy, "GAMESCOPE_SHARPNESS", false ); - - ctx->atoms.gamescopeXWaylandModeControl = XInternAtom( ctx->dpy, "GAMESCOPE_XWAYLAND_MODE_CONTROL", false ); - ctx->atoms.gamescopeFPSLimit = XInternAtom( ctx->dpy, "GAMESCOPE_FPS_LIMIT", false ); - ctx->atoms.gamescopeDynamicRefresh[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH", false ); - ctx->atoms.gamescopeDynamicRefresh[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH_EXTERNAL", false ); - ctx->atoms.gamescopeLowLatency = XInternAtom( ctx->dpy, "GAMESCOPE_LOW_LATENCY", false ); - - ctx->atoms.gamescopeFSRFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_FSR_FEEDBACK", false ); - - ctx->atoms.gamescopeBlurMode = XInternAtom( ctx->dpy, "GAMESCOPE_BLUR_MODE", false ); - ctx->atoms.gamescopeBlurRadius = XInternAtom( ctx->dpy, "GAMESCOPE_BLUR_RADIUS", false ); - ctx->atoms.gamescopeBlurFadeDuration = XInternAtom( ctx->dpy, "GAMESCOPE_BLUR_FADE_DURATION", false ); - - ctx->atoms.gamescopeCompositeForce = XInternAtom( ctx->dpy, "GAMESCOPE_COMPOSITE_FORCE", false ); - ctx->atoms.gamescopeCompositeDebug = XInternAtom( ctx->dpy, "GAMESCOPE_COMPOSITE_DEBUG", false ); - - ctx->atoms.gamescopeAllowTearing = XInternAtom( ctx->dpy, "GAMESCOPE_ALLOW_TEARING", false ); - - ctx->atoms.gamescopeDisplayForceInternal = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_FORCE_INTERNAL", false ); - ctx->atoms.gamescopeDisplayModeNudge = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_MODE_NUDGE", false ); - - ctx->atoms.gamescopeDisplayIsExternal = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_IS_EXTERNAL", false ); - ctx->atoms.gamescopeDisplayModeListExternal = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_MODE_LIST_EXTERNAL", false ); - - ctx->atoms.gamescopeCursorVisibleFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_CURSOR_VISIBLE_FEEDBACK", false ); - - ctx->atoms.gamescopeSteamMaxHeight = XInternAtom( ctx->dpy, "GAMESCOPE_STEAM_MAX_HEIGHT", false ); - ctx->atoms.gamescopeVRREnabled = XInternAtom( ctx->dpy, "GAMESCOPE_VRR_ENABLED", false ); - ctx->atoms.gamescopeVRRCapable = XInternAtom( ctx->dpy, "GAMESCOPE_VRR_CAPABLE", false ); - ctx->atoms.gamescopeVRRInUse = XInternAtom( ctx->dpy, "GAMESCOPE_VRR_FEEDBACK", false ); - - ctx->atoms.gamescopeNewScalingFilter = XInternAtom( ctx->dpy, "GAMESCOPE_NEW_SCALING_FILTER", false ); - ctx->atoms.gamescopeNewScalingScaler = XInternAtom( ctx->dpy, "GAMESCOPE_NEW_SCALING_SCALER", false ); - - ctx->atoms.gamescopeDisplayEdidPath = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_EDID_PATH", false ); - ctx->atoms.gamescopeXwaylandServerId = XInternAtom( ctx->dpy, "GAMESCOPE_XWAYLAND_SERVER_ID", false ); - - ctx->atoms.gamescopeDisplaySupportsHDR = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_SUPPORTS_HDR", false ); - ctx->atoms.gamescopeDisplayHDREnabled = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_HDR_ENABLED", false ); - ctx->atoms.gamescopeDebugForceHDR10Output = XInternAtom( ctx->dpy, "GAMESCOPE_DEBUG_FORCE_HDR10_PQ_OUTPUT", false ); - ctx->atoms.gamescopeDebugForceHDRSupport = XInternAtom( ctx->dpy, "GAMESCOPE_DEBUG_FORCE_HDR_SUPPORT", false ); - ctx->atoms.gamescopeDebugHDRHeatmap = XInternAtom( ctx->dpy, "GAMESCOPE_DEBUG_HDR_HEATMAP", false ); - ctx->atoms.gamescopeHDROutputFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_OUTPUT_FEEDBACK", false ); - ctx->atoms.gamescopeSDROnHDRContentBrightness = XInternAtom( ctx->dpy, "GAMESCOPE_SDR_ON_HDR_CONTENT_BRIGHTNESS", false ); - ctx->atoms.gamescopeHDRInputGain = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_INPUT_GAIN", false ); - ctx->atoms.gamescopeSDRInputGain = XInternAtom( ctx->dpy, "GAMESCOPE_SDR_INPUT_GAIN", false ); - ctx->atoms.gamescopeHDRItmEnable = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_ENABLE", false ); - ctx->atoms.gamescopeHDRItmSDRNits = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_SDR_NITS", false ); - ctx->atoms.gamescopeHDRItmTargetNits = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_TARGET_NITS", false ); - ctx->atoms.gamescopeColorLookPQ = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_LOOK_PQ", false ); - ctx->atoms.gamescopeColorLookG22 = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_LOOK_G22", false ); - ctx->atoms.gamescopeColorOutputVirtualWhite = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_VIRTUAL_WHITE", false ); - ctx->atoms.gamescopeHDRTonemapDisplayMetadata = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_TONEMAP_DISPLAY_METADATA", false ); - ctx->atoms.gamescopeHDRTonemapSourceMetadata = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_TONEMAP_SOURCE_METADATA", false ); - ctx->atoms.gamescopeHDRTonemapOperator = XInternAtom( ctx->dpy, "GAMESCOPE_HDR_TONEMAP_OPERATOR", false ); - - ctx->atoms.gamescopeForceWindowsFullscreen = XInternAtom( ctx->dpy, "GAMESCOPE_FORCE_WINDOWS_FULLSCREEN", false ); - - ctx->atoms.gamescopeColorLut3DOverride = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_3DLUT_OVERRIDE", false ); - ctx->atoms.gamescopeColorShaperLutOverride = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_SHAPERLUT_OVERRIDE", false ); - ctx->atoms.gamescopeColorSDRGamutWideness = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_SDR_GAMUT_WIDENESS", false ); - ctx->atoms.gamescopeColorNightMode = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_NIGHT_MODE", false ); - ctx->atoms.gamescopeColorManagementDisable = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MANAGEMENT_DISABLE", false ); - ctx->atoms.gamescopeColorAppWantsHDRFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_APP_WANTS_HDR_FEEDBACK", false ); - ctx->atoms.gamescopeColorAppHDRMetadataFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_APP_HDR_METADATA_FEEDBACK", false ); - ctx->atoms.gamescopeColorSliderInUse = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MANAGEMENT_CHANGING_HINT", false ); - ctx->atoms.gamescopeColorChromaticAdaptationMode = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_CHROMATIC_ADAPTATION_MODE", false ); - ctx->atoms.gamescopeColorMuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_IMAGE", false ); - ctx->atoms.gamescopeColorMuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_IMAGE_EXTERNAL", false ); - ctx->atoms.gamescopeColorMuraScale[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_SCALE", false ); - ctx->atoms.gamescopeColorMuraScale[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_SCALE_EXTERNAL", false ); - ctx->atoms.gamescopeColorMuraCorrectionDisabled[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_DISABLED", false ); - ctx->atoms.gamescopeColorMuraCorrectionDisabled[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_DISABLED_EXTERNAL", false ); - - ctx->atoms.gamescopeCreateXWaylandServer = XInternAtom( ctx->dpy, "GAMESCOPE_CREATE_XWAYLAND_SERVER", false ); - ctx->atoms.gamescopeCreateXWaylandServerFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_CREATE_XWAYLAND_SERVER_FEEDBACK", false ); - ctx->atoms.gamescopeDestroyXWaylandServer = XInternAtom( ctx->dpy, "GAMESCOPE_DESTROY_XWAYLAND_SERVER", false ); - - ctx->atoms.gamescopeReshadeEffect = XInternAtom( ctx->dpy, "GAMESCOPE_RESHADE_EFFECT", false ); - ctx->atoms.gamescopeReshadeTechniqueIdx = XInternAtom( ctx->dpy, "GAMESCOPE_RESHADE_TECHNIQUE_IDX", false ); - - ctx->atoms.gamescopeDisplayRefreshRateFeedback = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_REFRESH_RATE_FEEDBACK", false ); - ctx->atoms.gamescopeDisplayDynamicRefreshBasedOnGamePresence = XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_DYNAMIC_REFRESH_BASED_ON_GAME_PRESENCE", false ); - - ctx->atoms.gamescopeMainSteamVROverlay = XInternAtom( ctx->dpy, "GAMESCOPE_MAIN_STEAMVR_OVERLAY", false ); - ctx->atoms.steamosTouchPointerEmulation = XInternAtom( ctx->dpy, "_STEAMOS_TOUCH_POINTER_EMULATION", false ); - - ctx->atoms.wineHwndStyle = XInternAtom( ctx->dpy, "_WINE_HWND_STYLE", false ); - ctx->atoms.wineHwndStyleEx = XInternAtom( ctx->dpy, "_WINE_HWND_EXSTYLE", false ); - - ctx->atoms.clipboard = XInternAtom(ctx->dpy, "CLIPBOARD", false); - ctx->atoms.primarySelection = XInternAtom(ctx->dpy, "PRIMARY", false); - ctx->atoms.targets = XInternAtom(ctx->dpy, "TARGETS", false); - - ctx->atoms.wm_protocols = XInternAtom(ctx->dpy, "WM_PROTOCOLS", false); - ctx->atoms.wm_delete_window = XInternAtom(ctx->dpy, "WM_DELETE_WINDOW", false); - - ctx->root_width = DisplayWidth(ctx->dpy, ctx->scr); - ctx->root_height = DisplayHeight(ctx->dpy, ctx->scr); - - ctx->allDamage = None; - ctx->clipChanged = true; - - XChangeProperty(ctx->dpy, ctx->root, ctx->atoms.gamescopeXwaylandServerId, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&serverId, 1 ); - - uint32_t unPid = getpid(); - XChangeProperty(ctx->dpy, ctx->root, ctx->atoms.gamescopePid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&unPid, 1 ); - - uint32_t unVROverlayForwardingSupported = GetBackend()->SupportsVROverlayForwarding() ? 2 : 0; - XChangeProperty(ctx->dpy, ctx->root, ctx->atoms.gamescopeVROverlayForwarding, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&unVROverlayForwardingSupported, 1 ); - - XGrabServer(ctx->dpy); - - XCompositeRedirectSubwindows(ctx->dpy, ctx->root, CompositeRedirectManual); - - Window root_return, parent_return; - Window *children; - unsigned int nchildren; - - XSelectInput(ctx->dpy, ctx->root, - SubstructureNotifyMask| - ExposureMask| - StructureNotifyMask| - SubstructureRedirectMask| - FocusChangeMask| - PointerMotionMask| - LeaveWindowMask| - PropertyChangeMask); - XShapeSelectInput(ctx->dpy, ctx->root, ShapeNotifyMask); - XFixesSelectCursorInput(ctx->dpy, ctx->root, XFixesDisplayCursorNotifyMask); - XFixesSelectSelectionInput(ctx->dpy, ctx->root, ctx->atoms.clipboard, XFixesSetSelectionOwnerNotifyMask); - XFixesSelectSelectionInput(ctx->dpy, ctx->root, ctx->atoms.primarySelection, XFixesSetSelectionOwnerNotifyMask); - XQueryTree(ctx->dpy, ctx->root, &root_return, &parent_return, &children, &nchildren); - for (uint32_t i = 0; i < nchildren; i++) - add_win(ctx, children[i], i ? children[i-1] : None, 0); - XFree(children); - - XUngrabServer(ctx->dpy); - - XF86VidModeLockModeSwitch(ctx->dpy, ctx->scr, true); - - ctx->cursor = std::make_unique(ctx); - if (g_customCursorPath) - { - if (!load_mouse_cursor(ctx->cursor.get(), g_customCursorPath, g_customCursorHotspotX, g_customCursorHotspotY)) - xwm_log.errorf("Failed to load mouse cursor: %s", g_customCursorPath); - } - else - { - if ( std::shared_ptr pHostCursor = gamescope::GetX11HostCursor() ) - { - ctx->cursor->setCursorImage( - reinterpret_cast( pHostCursor->pPixels.data() ), - pHostCursor->uWidth, - pHostCursor->uHeight, - pHostCursor->uXHotspot, - pHostCursor->uYHotspot ); - } - else - { - xwm_log.infof( "Embedded, no cursor set. Using left_ptr by default." ); - if ( !ctx->cursor->setCursorImageByName( "left_ptr" ) ) - xwm_log.errorf( "Failed to load mouse cursor: left_ptr" ); - } - } - - ctx->cursor->undirty(); - - XFlush(ctx->dpy); -} - -void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = nullptr) -{ - bool capable = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->SupportsVRR(); - if ( capable != g_bVRRCapable_CachedValue || force ) - { - uint32_t capable_value = capable ? 1 : 0; - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeVRRCapable, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&capable_value, 1 ); - g_bVRRCapable_CachedValue = capable; - if (needs_flush) - *needs_flush = true; - } - - bool HDR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->SupportsHDR(); - if ( HDR != g_bSupportsHDR_CachedValue || force ) - { - uint32_t hdr_value = HDR ? 1 : 0; - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplaySupportsHDR, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&hdr_value, 1 ); - g_bSupportsHDR_CachedValue = HDR; - if (needs_flush) - *needs_flush = true; - } - - bool in_use = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive(); - if ( in_use != g_bVRRInUse_CachedValue || force ) - { - uint32_t in_use_value = in_use ? 1 : 0; - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeVRRInUse, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&in_use_value, 1 ); - g_bVRRInUse_CachedValue = in_use; - if (needs_flush) - *needs_flush = true; - } - - if ( g_nOutputRefresh != g_nCurrentRefreshRate_CachedValue || force ) - { - int32_t nRefresh = gamescope::ConvertmHzToHz( g_nOutputRefresh ); - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayRefreshRateFeedback, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&nRefresh, 1 ); - g_nCurrentRefreshRate_CachedValue = g_nOutputRefresh; - if (needs_flush) - *needs_flush = true; - } - - // Don't update this in-sync with DRM vrr usage. - // Keep this as a preference, starting with off. - if ( force ) - { - bool wants_vrr = cv_adaptive_sync; - uint32_t enabled_value = wants_vrr ? 1 : 0; - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeVRREnabled, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&enabled_value, 1 ); - if (needs_flush) - *needs_flush = true; - } -} - -void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) -{ - if (needs_flush) - *needs_flush = true; - - if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - { - XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayModeListExternal); - - uint32_t zero = 0; - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayIsExternal, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&zero, 1 ); - return; - } - - if ( !GetBackend()->GetCurrentConnector() ) - return; - - auto connectorModes = GetBackend()->GetCurrentConnector()->GetModes(); - - char modes[4096] = ""; - int remaining_size = sizeof(modes) - 1; - int len = 0; - for (int i = 0; remaining_size > 0 && i < (int)connectorModes.size(); i++) - { - const auto& mode = connectorModes[i]; - int mode_len = snprintf(&modes[len], remaining_size, "%s%dx%d@%d", - i == 0 ? "" : " ", - int(mode.uWidth), int(mode.uHeight), int(mode.uRefresh)); - len += mode_len; - remaining_size -= mode_len; - } - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayModeListExternal, XA_STRING, 8, PropModeReplace, - (unsigned char *)modes, strlen(modes) + 1 ); - - uint32_t one = 1; - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayIsExternal, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&one, 1 ); -} - -extern int g_nPreferredOutputWidth; -extern int g_nPreferredOutputHeight; - -static bool g_bWasFSRActive = false; - -bool g_bAppWantsHDRCached = false; - -void steamcompmgr_check_xdg(bool vblank, uint64_t vblank_idx) -{ - if (wlserver_xdg_dirty()) - { - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - if (pFocus->focusWindow && pFocus->focusWindow->type == steamcompmgr_win_type_t::XDG) - pFocus->focusWindow = nullptr; - if (pFocus->inputFocusWindow && pFocus->inputFocusWindow->type == steamcompmgr_win_type_t::XDG) - pFocus->inputFocusWindow = nullptr; - if (pFocus->overlayWindow && pFocus->overlayWindow->type == steamcompmgr_win_type_t::XDG) - pFocus->overlayWindow = nullptr; - if (pFocus->notificationWindow && pFocus->notificationWindow->type == steamcompmgr_win_type_t::XDG) - pFocus->notificationWindow = nullptr; - if (pFocus->overrideWindow && pFocus->overrideWindow->type == steamcompmgr_win_type_t::XDG) - pFocus->overrideWindow = nullptr; - if (pFocus->fadeWindow && pFocus->fadeWindow->type == steamcompmgr_win_type_t::XDG) - pFocus->fadeWindow = nullptr; - } - g_steamcompmgr_xdg_wins = wlserver_get_xdg_shell_windows(); - MakeFocusDirty(); - } - - handle_done_commits_xdg( vblank, vblank_idx ); - - // When we have observed both a complete commit and a VBlank, we should request a new frame. - if (vblank) - { - for ( const auto& xdg_win : g_steamcompmgr_xdg_wins ) - { - steamcompmgr_flush_frame_done(xdg_win.get()); - } - - wlserver_lock(); - handle_presented_xdg(); - wlserver_unlock(); - } - - check_new_xdg_res(); -} - -void update_edid_prop() -{ - const char *filename = gamescope::GetPatchedEdidPath(); - if (!filename) - return; - - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - XTextProperty text_property = - { - .value = (unsigned char *)filename, - .encoding = server->ctx->atoms.utf8StringAtom, - .format = 8, - .nitems = strlen(filename), - }; - - XSetTextProperty( server->ctx->dpy, server->ctx->root, &text_property, server->ctx->atoms.gamescopeDisplayEdidPath ); - } -} - -extern bool g_bLaunchMangoapp; - -extern void ShutdownGamescope(); - -gamescope::ConVar cv_shutdown_on_primary_child_death( "shutdown_on_primary_child_death", true, "Should gamescope shutdown when the primary application launched in it was shut down?" ); -static LogScope s_LaunchLogScope( "launch" ); - -static std::vector s_uRelativeMouseFilteredAppids; -static gamescope::ConVar cv_mouse_relative_filter_appids( "mouse_relative_filter_appids", -"8400" /* Geometry Wars: Retro Evolved */, -"Comma separated appids to filter out using relative mouse mode for.", -[]( gamescope::ConVar &cvar ) -{ - std::vector sFilterAppids = gamescope::Split( cvar, "," ); - std::vector uFilterAppids; - uFilterAppids.reserve( sFilterAppids.size() ); - for ( auto &sFilterAppid : sFilterAppids ) - { - std::optional ouFilterAppid = gamescope::Parse( sFilterAppid ); - uFilterAppids.push_back( *ouFilterAppid ); - } - - s_uRelativeMouseFilteredAppids = std::move( uFilterAppids ); -}, true); - -void LaunchNestedChildren( char **ppPrimaryChildArgv ) -{ - std::string sNewPreload; - { - const char *pszCurrentPreload = getenv( "LD_PRELOAD" ); - if ( pszCurrentPreload && *pszCurrentPreload ) - { - // Remove gameoverlayrenderer.so from the child if Gamescope - // is running with a window + Vulkan swapchain (eg. SDL2 backend) - if ( GetBackend()->UsesVulkanSwapchain() ) - { - std::vector svLibraries = gamescope::Split( pszCurrentPreload, " :" ); - std::erase_if( svLibraries, []( std::string_view svPreload ) - { - return svPreload.find( "gameoverlayrenderer.so" ) != std::string_view::npos; - }); - - bool bFirst = true; - for ( std::string_view svLibrary : svLibraries ) - { - if ( !bFirst ) - { - sNewPreload.append( ":" ); - } - bFirst = false; - sNewPreload.append( svLibrary ); - } - } - else - { - sNewPreload = pszCurrentPreload; - } - } - } - - // We could just run this inside the child process, - // but we might as well just run it here at this point. - // and affect all future child processes, without needing - // a pre-amble inside of them. - { - if ( !sNewPreload.empty() ) - setenv( "LD_PRELOAD", sNewPreload.c_str(), 1 ); - else - unsetenv( "LD_PRELOAD" ); - - unsetenv( "ENABLE_VKBASALT" ); - // Enable Gamescope WSI by default for nested. - setenv( "ENABLE_GAMESCOPE_WSI", "1", 0 ); - - // Unset this to avoid it leaking to Proton apps, etc. - unsetenv( "SDL_VIDEODRIVER" ); - // SDL3... - unsetenv( "SDL_VIDEO_DRIVER" ); - } - - // Gamescope itself does not set itself as a subreaper anymore. - // It launches direct children that do, and manage that they kill themselves - // when Gamescope dies. - // This allows us to launch stuff alongside Gamescope if we ever wanted -- rather - // than being under it. (eg. if we wanted a drm janitor or something.) - - if ( ppPrimaryChildArgv && *ppPrimaryChildArgv ) - { - pid_t nPrimaryChildPid = gamescope::Process::SpawnProcessInWatchdog( ppPrimaryChildArgv, false ); - - std::thread waitThread([ nPrimaryChildPid ]() - { - pthread_setname_np( pthread_self(), "gamescope-wait" ); - - gamescope::Process::WaitForChild( nPrimaryChildPid ); - s_LaunchLogScope.infof( "Primary child shut down!" ); - - if ( cv_shutdown_on_primary_child_death ) - ShutdownGamescope(); - }); - waitThread.detach(); - } - - if ( g_bLaunchMangoapp ) - { - char *ppMangoappArgv[] = { (char *)"mangoapp", NULL }; - gamescope::Process::SpawnProcessInWatchdog( ppMangoappArgv, true ); - } -} - -static gamescope::CTimerFunction g_FPSLimitVRRTimer{ [] -{ - g_FPSLimitVRRTimer.DisarmTimer(); -}}; - -void -steamcompmgr_main(int argc, char **argv) -{ - int readyPipeFD = -1; - - // Reset getopt() state - optind = 1; - - int o; - int opt_index = -1; - bool bForceWindowsFullscreen = false; - while ((o = getopt_long(argc, argv, gamescope_optstring, gamescope_options, &opt_index)) != -1) - { - const char *opt_name; - switch (o) { - case 'R': - readyPipeFD = open( optarg, O_WRONLY | O_CLOEXEC ); - break; - case 'T': - statsThreadPath = optarg; - { - statsThreadRun = true; - std::thread statsThreads( statsThreadMain ); - statsThreads.detach(); - } - break; - case 'C': - cursorHideTime = uint64_t( atoi( optarg ) ) * 1'000'000ul; - break; - case 'v': - drawDebugInfo = true; - break; - case 'c': - cv_composite_force = true; - break; - case 'x': - useXRes = false; - break; - case 0: // long options without a short option - opt_name = gamescope_options[opt_index].name; - if (strcmp(opt_name, "debug-focus") == 0) { - debugFocus = true; - } else if (strcmp(opt_name, "synchronous-x11") == 0) { - synchronize = true; - } else if (strcmp(opt_name, "debug-events") == 0) { - debugEvents = true; - } else if (strcmp(opt_name, "cursor") == 0) { - g_customCursorPath = optarg; - } else if (strcmp(opt_name, "cursor-hotspot") == 0) { - sscanf(optarg, "%d,%d", &g_customCursorHotspotX, &g_customCursorHotspotY); - } else if (strcmp(opt_name, "fade-out-duration") == 0) { - g_FadeOutDuration = atoi(optarg); - } else if (strcmp(opt_name, "force-windows-fullscreen") == 0) { - bForceWindowsFullscreen = true; - } else if (strcmp(opt_name, "hdr-enabled") == 0 || strcmp(opt_name, "hdr-enable") == 0) { - cv_hdr_enabled = true; - } else if (strcmp(opt_name, "hdr-debug-force-support") == 0) { - g_bForceHDRSupportDebug = true; - } else if (strcmp(opt_name, "hdr-debug-force-output") == 0) { - g_bForceHDR10OutputDebug = true; - } else if (strcmp(opt_name, "hdr-itm-enabled") == 0 || strcmp(opt_name, "hdr-itm-enable") == 0) { - g_bHDRItmEnable = true; - } else if (strcmp(opt_name, "sdr-gamut-wideness") == 0) { - g_ColorMgmt.pending.sdrGamutWideness = atof(optarg); - } else if (strcmp(opt_name, "hdr-sdr-content-nits") == 0) { - g_ColorMgmt.pending.flSDROnHDRBrightness = atof(optarg); - } else if (strcmp(opt_name, "hdr-itm-sdr-nits") == 0) { - g_flHDRItmSdrNits = atof(optarg); - } else if (strcmp(opt_name, "hdr-itm-target-nits") == 0) { - g_flHDRItmTargetNits = atof(optarg); - } else if (strcmp(opt_name, "framerate-limit") == 0) { - g_nSteamCompMgrTargetFPS = atoi(optarg); - } else if (strcmp(opt_name, "reshade-effect") == 0) { - g_reshade_effect = optarg; - } else if (strcmp(opt_name, "reshade-technique-idx") == 0) { - g_reshade_technique_idx = atoi(optarg); - } else if (strcmp(opt_name, "mura-map") == 0) { - set_mura_overlay(optarg); - } - break; - case '?': - assert(false); // unreachable - } - } - - int subCommandArg = -1; - if ( optind < argc ) - { - subCommandArg = optind; - } - - const char *pchEnableVkBasalt = getenv( "ENABLE_VKBASALT" ); - if ( pchEnableVkBasalt != nullptr && pchEnableVkBasalt[0] == '1' ) - { - cv_composite_force = true; - } - - // Enable color mgmt by default. - g_ColorMgmt.pending.enabled = true; - - currentOutputWidth = g_nPreferredOutputWidth; - currentOutputHeight = g_nPreferredOutputHeight; +static bool steamcompmgr_should_vblank_window( + steamcompmgr_win_t *w, uint64_t vblank_idx, uint64_t now ) +{ + return steamcompmgr_should_vblank_window( + steamcompmgr_window_should_limit_fps( w ), vblank_idx, w, now ); +} - init_runtime_info(); +static void steamcompmgr_latch_frame_done( + steamcompmgr_win_t *w, uint64_t vblank_idx, uint64_t now ) +{ + if ( steamcompmgr_should_vblank_window( w, vblank_idx, now ) ) + { + w->unlockedForFrameCallback = true; + } +} - std::unique_lock xwayland_server_guard(g_SteamCompMgrXWaylandServerMutex); +static inline float santitize_float( float f ) +{ +#ifndef __FAST_MATH__ + return ( std::isfinite( f ) ? f : 0.f ); +#else + return f; +#endif +} - // Initialize any xwayland ctxs we have +static void handle_property_notify( xwayland_ctx_t *ctx, XPropertyEvent *ev ) +{ + /* check if Trans property was changed */ + if ( ev->atom == ctx->atoms.opacityAtom ) + { + /* reset mode and redraw window */ + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w != nullptr ) + { + unsigned int newOpacity = get_prop( + ctx, w->xwayland( ).id, ctx->atoms.opacityAtom, OPAQUE ); + + if ( newOpacity != w->opacity ) + { + w->opacity = newOpacity; + + if ( gameFocused && ( w == ctx->focus.overlayWindow || + w == ctx->focus.notificationWindow ) ) + { + hasRepaintNonBasePlane = true; + } + if ( w == ctx->focus.externalOverlayWindow ) + { + hasRepaint = true; + } + } + + unsigned int maxOpacity = 0; + unsigned int maxOpacityExternal = 0; + + for ( w = ctx->list; w; w = w->xwayland( ).next ) + { + if ( w->isOverlay ) + { + if ( w->GetGeometry( ).nWidth > 1200 && + w->opacity >= maxOpacity ) + { + ctx->focus.overlayWindow = w; + maxOpacity = w->opacity; + } + } + if ( w->isExternalOverlay ) + { + if ( w->opacity >= maxOpacityExternal ) + { + ctx->focus.externalOverlayWindow = w; + maxOpacityExternal = w->opacity; + } + } + } + } + } + if ( ev->atom == ctx->atoms.steamAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->isSteamLegacyBigPicture = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.steamAtom, 0 ); + if ( w->isSteamLegacyBigPicture ) w->appID = 769; + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.steamInputFocusAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->inputFocusMode = get_prop( + ctx, w->xwayland( ).id, ctx->atoms.steamInputFocusAtom, 0 ); + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.steamTouchClickModeAtom ) + { + gamescope::cv_touch_click_mode = ( gamescope::TouchClickMode )get_prop( + ctx, ctx->root, ctx->atoms.steamTouchClickModeAtom, 0u ); + } + if ( ev->atom == ctx->atoms.steamStreamingClientAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->isSteamStreamingClient = get_prop( + ctx, + w->xwayland( ).id, + ctx->atoms.steamStreamingClientAtom, + 0 ); + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.steamStreamingClientVideoAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->isSteamStreamingClientVideo = get_prop( + ctx, + w->xwayland( ).id, + ctx->atoms.steamStreamingClientVideoAtom, + 0 ); + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.steamGamescopeVROverlayTarget ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->oulTargetVROverlay = get_u64_prop( + ctx, + w->xwayland( ).id, + ctx->atoms.steamGamescopeVROverlayTarget ); + w->pForwarderPlane = nullptr; + MakeFocusDirty( ); + hasRepaint = true; + g_bUpdateForwardedVROverlays = true; + w->bNeedsForwarding = true; + } + } + if ( ev->atom == ctx->atoms.gamescopeCtrlAppIDAtom ) + { + get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeCtrlAppIDAtom, + vecFocuscontrolAppIDs ); + MakeFocusDirty( ); + } + if ( ev->atom == ctx->atoms.gamescopeCtrlWindowAtom ) + { + ctx->focusControlWindow = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeCtrlWindowAtom, None ); + MakeFocusDirty( ); + } + if ( ev->atom == ctx->atoms.gamescopeScreenShotAtom ) + { + if ( ev->state == PropertyNewValue ) + { + gamescope::CScreenshotManager::Get( ).TakeScreenshot( + gamescope::GamescopeScreenshotInfo{ + .szScreenshotPath = "/tmp/gamescope.png", + .eScreenshotType = + ( gamescope_control_screenshot_type )get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeScreenShotAtom, + None ), + .uScreenshotFlags = 0, + .bX11PropertyRequested = true, + } ); + } + } + if ( ev->atom == ctx->atoms.gamescopeDebugScreenShotAtom ) + { + if ( ev->state == PropertyNewValue ) + { + gamescope::CScreenshotManager::Get( ).TakeScreenshot( + gamescope::GamescopeScreenshotInfo{ + .szScreenshotPath = "/tmp/gamescope.png", + .eScreenshotType = + ( gamescope_control_screenshot_type )get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeDebugScreenShotAtom, + None ), + .uScreenshotFlags = 0, + .bX11PropertyRequested = true, + } ); + } + } + if ( ev->atom == ctx->atoms.gameAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + uint32_t appID = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.gameAtom, 0 ); + + if ( w->appID != 0 && appID != 0 && w->appID != appID ) + { + xwm_log.errorf( "appid clash was %u now %u", w->appID, appID ); + } + w->appID = appID; + if ( w->isExternalOverlay ) w->appID = 0; + + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.overlayAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->isOverlay = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.overlayAtom, 0 ); + if ( w->isExternalOverlay ) w->appID = 0; + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.externalOverlayAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->isExternalOverlay = get_prop( + ctx, w->xwayland( ).id, ctx->atoms.externalOverlayAtom, 0 ); + if ( w->isExternalOverlay ) w->appID = 0; + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.winTypeAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + get_win_type( ctx, w ); + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.sizeHintsAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + get_size_hints( ctx, w ); + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.gamesRunningAtom ) + { + gamesRunningCount = + get_prop( ctx, ctx->root, ctx->atoms.gamesRunningAtom, 0 ); + + MakeFocusDirty( ); + } + if ( ev->atom == ctx->atoms.screenScaleAtom ) + { + overscanScaleRatio = + get_prop( ctx, ctx->root, ctx->atoms.screenScaleAtom, 0xFFFFFFFF ) / + ( double )0xFFFFFFFF; + + globalScaleRatio = overscanScaleRatio * zoomScaleRatio; + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->focusWindow ) { hasRepaint = true; } + } + + MakeFocusDirty( ); + } + if ( ev->atom == ctx->atoms.screenZoomAtom ) + { + zoomScaleRatio = + get_prop( ctx, ctx->root, ctx->atoms.screenZoomAtom, 0xFFFF ) / + ( double )0xFFFF; + + globalScaleRatio = overscanScaleRatio * zoomScaleRatio; + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->focusWindow ) { hasRepaint = true; } + } + + MakeFocusDirty( ); + } + if ( ev->atom == ctx->atoms.WMTransientForAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + Window transientFor = None; + if ( XGetTransientForHint( ctx->dpy, ev->window, &transientFor ) ) + { + w->xwayland( ).transientFor = transientFor; + } + else + { + w->xwayland( ).transientFor = None; + } + get_win_type( ctx, w ); + + MakeFocusDirty( ); + } + } + if ( ev->atom == XA_WM_NAME || ev->atom == ctx->atoms.netWMNameAtom ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + + if ( w ) + { + get_win_title( ctx, w, ev->atom ); + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( ev->window == x11_win( pFocus->focusWindow ) ) + { + if ( pFocus->GetNestedHints( ) ) + pFocus->GetNestedHints( )->SetTitle( w->title ); + } + } + } + } + if ( ev->atom == ctx->atoms.netWMIcon ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + + if ( w ) + { + get_win_icon( ctx, w ); + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( ev->window == x11_win( pFocus->focusWindow ) ) + { + if ( pFocus->GetNestedHints( ) ) + pFocus->GetNestedHints( )->SetIcon( w->icon ); + } + } + } + } +#if 0 + if ( ev->atom == ctx->atoms.gamescopeTuneableVBlankRedZone ) { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - init_xwayland_ctx(i, server); + g_uVblankDrawBufferRedZoneNS = (uint64_t)get_prop( ctx, ctx->root, ctx->atoms.gamescopeTuneableVBlankRedZone, g_uDefaultVBlankRedZone ); } - - gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0); - xwayland_ctx_t *root_ctx = root_server->ctx.get(); - - gamesRunningCount = get_prop(root_ctx, root_ctx->root, root_ctx->atoms.gamesRunningAtom, 0); - overscanScaleRatio = get_prop(root_ctx, root_ctx->root, root_ctx->atoms.screenScaleAtom, 0xFFFFFFFF) / (double)0xFFFFFFFF; - zoomScaleRatio = get_prop(root_ctx, root_ctx->root, root_ctx->atoms.screenZoomAtom, 0xFFFF) / (double)0xFFFF; - - globalScaleRatio = overscanScaleRatio * zoomScaleRatio; - - static constexpr uint64_t k_unSingleOutputVirtualConnectorKey = 0; - g_VirtualConnectorFocuses[ k_unSingleOutputVirtualConnectorKey ] = global_focus_t - { - .ulVirtualFocusKey = k_unSingleOutputVirtualConnectorKey, - .pVirtualConnector = GetBackend()->UsesVirtualConnectors() ? GetBackend()->CreateVirtualConnector( k_unSingleOutputVirtualConnectorKey ) : nullptr, - }; - - for ( auto &iter : g_VirtualConnectorFocuses ) + if ( ev->atom == ctx->atoms.gamescopeTuneableRateOfDecay ) { - global_focus_t *pFocus = &iter.second; - if ( pFocus->IsDirty() ) - determine_and_apply_focus( pFocus ); + g_uVBlankRateOfDecayPercentage = (uint64_t)get_prop( ctx, ctx->root, ctx->atoms.gamescopeTuneableRateOfDecay, g_uDefaultVBlankRateOfDecayPercentage ); } +#endif + if ( ev->atom == ctx->atoms.gamescopeScalingFilter ) + { + int nScalingMode = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeScalingFilter, 0 ); + switch ( nScalingMode ) + { + default: + case 0: + g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; + g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; + break; + case 1: + g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; + g_wantedUpscaleFilter = GamescopeUpscaleFilter::NEAREST; + break; + case 2: + g_wantedUpscaleScaler = GamescopeUpscaleScaler::INTEGER; + g_wantedUpscaleFilter = GamescopeUpscaleFilter::NEAREST; + break; + case 3: + g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; + g_wantedUpscaleFilter = GamescopeUpscaleFilter::FSR; + break; + case 4: + g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; + g_wantedUpscaleFilter = GamescopeUpscaleFilter::NIS; + break; + } + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeFSRSharpness || + ev->atom == ctx->atoms.gamescopeSharpness ) + { + g_upscaleFilterSharpness = + ( int )clamp( get_prop( ctx, ctx->root, ev->atom, 2 ), 0u, 20u ); + if ( g_upscaleFilter == GamescopeUpscaleFilter::FSR || + g_upscaleFilter == GamescopeUpscaleFilter::NIS ) + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeXWaylandModeControl ) + { + std::vector xwayland_mode_ctl; + bool hasModeCtrl = get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeXWaylandModeControl, + xwayland_mode_ctl ); + if ( hasModeCtrl && xwayland_mode_ctl.size( ) == 4 ) + { + size_t server_idx = size_t{ xwayland_mode_ctl[ 0 ] }; + int width = xwayland_mode_ctl[ 1 ]; + int height = xwayland_mode_ctl[ 2 ]; + bool allowSuperRes = !!xwayland_mode_ctl[ 3 ]; + + if ( !allowSuperRes ) + { + width = std::min( width, currentOutputWidth ); + height = std::min( height, currentOutputHeight ); + } + + gamescope_xwayland_server_t *server = + wlserver_get_xwayland_server( server_idx ); + if ( server ) + { + bool root_size_identical = server->ctx->root_width == width && + server->ctx->root_height == height; + + wlserver_lock( ); + wlserver_set_xwayland_server_mode( + server_idx, width, height, g_nOutputRefresh ); + wlserver_unlock( ); + + if ( root_size_identical ) + { + gamescope_xwayland_server_t *root_server = + wlserver_get_xwayland_server( 0 ); + xwayland_ctx_t *root_ctx = root_server->ctx.get( ); + XDeleteProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeXWaylandModeControl ); + XFlush( root_ctx->dpy ); + } + } + } + } + if ( ev->atom == ctx->atoms.gamescopeFPSLimit ) + { + g_nSteamCompMgrTargetFPS = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeFPSLimit, 0 ); + update_runtime_info( ); + } + for ( int i = 0; i < gamescope::GAMESCOPE_SCREEN_TYPE_COUNT; i++ ) + { + if ( ev->atom == ctx->atoms.gamescopeDynamicRefresh[ i ] ) + { + g_nDynamicRefreshRate[ i ] = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeDynamicRefresh[ i ], 0 ); + } + } + if ( ev->atom == ctx->atoms.gamescopeLowLatency ) + { + g_bLowLatency = + !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeLowLatency, 0 ); + } + if ( ev->atom == ctx->atoms.gamescopeBlurMode ) + { + BlurMode newBlur = ( BlurMode )get_prop( + ctx, ctx->root, ctx->atoms.gamescopeBlurMode, 0 ); + if ( newBlur < BLUR_MODE_OFF || newBlur > BLUR_MODE_ALWAYS ) + newBlur = BLUR_MODE_OFF; + + if ( newBlur != g_BlurMode ) + { + g_BlurFadeStartTime = get_time_in_milliseconds( ); + g_BlurModeOld = g_BlurMode; + g_BlurMode = newBlur; + hasRepaint = true; + } + } + if ( ev->atom == ctx->atoms.gamescopeBlurRadius ) + { + unsigned int pixel = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeBlurRadius, 0 ); + g_BlurRadius = + ( int )clamp( ( pixel / 2 ) + 1, 1u, kMaxBlurRadius - 1 ); + if ( g_BlurMode ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeBlurFadeDuration ) + { + g_BlurFadeDuration = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeBlurFadeDuration, 0 ); + } + if ( ev->atom == ctx->atoms.gamescopeCompositeForce ) + { + cv_composite_force = + !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeCompositeForce, 0 ); + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeCompositeDebug ) + { + cv_composite_debug = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeCompositeDebug, 0 ); + + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeAllowTearing ) + { + cv_tearing_enabled = + !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeAllowTearing, 0 ); + } + if ( ev->atom == ctx->atoms.gamescopeSteamMaxHeight ) + { + g_nSteamMaxHeight = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeSteamMaxHeight, 0 ); + MakeFocusDirty( ); + } + if ( ev->atom == ctx->atoms.gamescopeVRREnabled ) + { + bool enabled = + !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeVRREnabled, 0 ); + cv_adaptive_sync = enabled; + } + if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal ) + { + g_bForceInternal = !!get_prop( + ctx, ctx->root, ctx->atoms.gamescopeDisplayForceInternal, 0 ); + GetBackend( )->DirtyState( ); + } + if ( ev->atom == ctx->atoms.gamescopeDisplayModeNudge ) + { + GetBackend( )->DirtyState( true ); + XDeleteProperty( + ctx->dpy, ctx->root, ctx->atoms.gamescopeDisplayModeNudge ); + } + if ( ev->atom == ctx->atoms.gamescopeNewScalingFilter ) + { + GamescopeUpscaleFilter nScalingFilter = + ( GamescopeUpscaleFilter )get_prop( + ctx, ctx->root, ctx->atoms.gamescopeNewScalingFilter, 0 ); + if ( g_wantedUpscaleFilter != nScalingFilter ) + { + g_wantedUpscaleFilter = nScalingFilter; + hasRepaint = true; + } + } + if ( ev->atom == ctx->atoms.gamescopeNewScalingScaler ) + { + GamescopeUpscaleScaler nScalingScaler = + ( GamescopeUpscaleScaler )get_prop( + ctx, ctx->root, ctx->atoms.gamescopeNewScalingScaler, 0 ); + if ( g_wantedUpscaleScaler != nScalingScaler ) + { + g_wantedUpscaleScaler = nScalingScaler; + hasRepaint = true; + } + } + if ( ev->atom == ctx->atoms.gamescopeDisplayHDREnabled ) + { + cv_hdr_enabled = !!get_prop( + ctx, ctx->root, ctx->atoms.gamescopeDisplayHDREnabled, 0 ); + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeDebugForceHDR10Output ) + { + g_bForceHDR10OutputDebug = !!get_prop( + ctx, ctx->root, ctx->atoms.gamescopeDebugForceHDR10Output, 0 ); + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeDebugForceHDRSupport ) + { + g_bForceHDRSupportDebug = !!get_prop( + ctx, ctx->root, ctx->atoms.gamescopeDebugForceHDRSupport, 0 ); + GetBackend( )->HackUpdatePatchedEdid( ); + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeDebugHDRHeatmap ) + { + uint32_t heatmap = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugHDRHeatmap, 0 ); + cv_composite_debug &= ~CompositeDebugFlag::Heatmap; + cv_composite_debug &= ~CompositeDebugFlag::Heatmap_MSWCG; + cv_composite_debug &= ~CompositeDebugFlag::Heatmap_Hard; + if ( heatmap != 0 ) cv_composite_debug |= CompositeDebugFlag::Heatmap; + if ( heatmap == 2 ) + cv_composite_debug |= CompositeDebugFlag::Heatmap_MSWCG; + if ( heatmap == 3 ) + cv_composite_debug |= CompositeDebugFlag::Heatmap_Hard; + hasRepaint = true; + } + + if ( ev->atom == ctx->atoms.gamescopeHDRTonemapOperator ) + { + g_ColorMgmt.pending.hdrTonemapOperator = ( ETonemapOperator )get_prop( + ctx, ctx->root, ctx->atoms.gamescopeHDRTonemapOperator, 0 ); + hasRepaint = true; + } + + if ( ev->atom == ctx->atoms.gamescopeHDRTonemapDisplayMetadata ) + { + std::vector user_vec; + if ( get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeHDRTonemapDisplayMetadata, + user_vec ) && + user_vec.size( ) >= 2 ) + { + g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flBlackPointNits = + bit_cast( user_vec[ 0 ] ); + g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits = + bit_cast( user_vec[ 1 ] ); + } + else + { + g_ColorMgmt.pending.hdrTonemapDisplayMetadata.reset( ); + } + hasRepaint = true; + } + + if ( ev->atom == ctx->atoms.gamescopeHDRTonemapSourceMetadata ) + { + std::vector user_vec; + if ( get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeHDRTonemapSourceMetadata, + user_vec ) && + user_vec.size( ) >= 2 ) + { + g_ColorMgmt.pending.hdrTonemapSourceMetadata.flBlackPointNits = + bit_cast( user_vec[ 0 ] ); + g_ColorMgmt.pending.hdrTonemapSourceMetadata.flWhitePointNits = + bit_cast( user_vec[ 1 ] ); + } + else + { + g_ColorMgmt.pending.hdrTonemapSourceMetadata.reset( ); + } + hasRepaint = true; + } + + if ( ev->atom == ctx->atoms.gamescopeSDROnHDRContentBrightness ) + { + uint32_t val = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeSDROnHDRContentBrightness, 0 ); + if ( set_sdr_on_hdr_brightness( bit_cast( val ) ) ) + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeHDRItmEnable ) + { + g_bHDRItmEnable = + !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRItmEnable, 0 ); + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeHDRItmSDRNits ) + { + g_flHDRItmSdrNits = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRItmSDRNits, 0 ); + if ( g_flHDRItmSdrNits < 1.f ) g_flHDRItmSdrNits = 100.f; + else if ( g_flHDRItmSdrNits > 1000.f ) + g_flHDRItmSdrNits = 1000.f; + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeHDRItmTargetNits ) + { + g_flHDRItmTargetNits = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRItmTargetNits, 0 ); + if ( g_flHDRItmTargetNits < 1.f ) g_flHDRItmTargetNits = 1000.f; + else if ( g_flHDRItmTargetNits > 10000.f ) + g_flHDRItmTargetNits = 10000.f; + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeColorLookPQ ) + { + std::string path = + get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorLookPQ ); + if ( set_color_look_pq( path.c_str( ) ) ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeColorLookG22 ) + { + std::string path = + get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorLookG22 ); + if ( set_color_look_g22( path.c_str( ) ) ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeColorOutputVirtualWhite ) + { + std::vector user_vec; + if ( get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeColorOutputVirtualWhite, + user_vec ) && + user_vec.size( ) >= 2 ) + { + g_ColorMgmt.pending.outputVirtualWhite.x = + santitize_float( bit_cast( user_vec[ 0 ] ) ); + g_ColorMgmt.pending.outputVirtualWhite.y = + santitize_float( bit_cast( user_vec[ 1 ] ) ); + } + else + { + g_ColorMgmt.pending.outputVirtualWhite.x = 0.f; + g_ColorMgmt.pending.outputVirtualWhite.y = 0.f; + } + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeHDRInputGain ) + { + uint32_t val = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRInputGain, 0 ); + if ( set_hdr_input_gain( bit_cast( val ) ) ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeSDRInputGain ) + { + uint32_t val = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeSDRInputGain, 0 ); + if ( set_sdr_input_gain( bit_cast( val ) ) ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeForceWindowsFullscreen ) + { + ctx->force_windows_fullscreen = !!get_prop( + ctx, ctx->root, ctx->atoms.gamescopeForceWindowsFullscreen, 0 ); + MakeFocusDirty( ); + } + if ( ev->atom == ctx->atoms.gamescopeColorLut3DOverride ) + { + std::string path = get_string_prop( + ctx, ctx->root, ctx->atoms.gamescopeColorLut3DOverride ); + if ( set_color_3dlut_override( path.c_str( ) ) ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeColorShaperLutOverride ) + { + std::string path = get_string_prop( + ctx, ctx->root, ctx->atoms.gamescopeColorShaperLutOverride ); + if ( set_color_shaperlut_override( path.c_str( ) ) ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeColorSDRGamutWideness ) + { + uint32_t val = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeColorSDRGamutWideness, 0 ); + if ( set_color_sdr_gamut_wideness( bit_cast( val ) ) ) + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeColorNightMode ) + { + std::vector user_vec; + bool bHasVec = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeColorNightMode, user_vec ); + + // identity + float vec[ 3 ] = { 0.0f, 0.0f, 0.0f }; + if ( bHasVec && user_vec.size( ) == 3 ) + { + for ( int i = 0; i < 3; i++ ) + vec[ i ] = bit_cast( user_vec[ i ] ); + } + + nightmode_t nightmode; + nightmode.amount = vec[ 0 ]; + nightmode.hue = vec[ 1 ]; + nightmode.saturation = vec[ 2 ]; + + if ( set_color_nightmode( nightmode ) ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeColorManagementDisable ) + { + uint32_t val = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeColorManagementDisable, 0 ); + if ( set_color_mgmt_enabled( !val ) ) hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeColorSliderInUse ) + { + uint32_t val = + get_prop( ctx, ctx->root, ctx->atoms.gamescopeColorSliderInUse, 0 ); + g_bColorSliderInUse = !!val; + } + if ( ev->atom == ctx->atoms.gamescopeColorChromaticAdaptationMode ) + { + uint32_t val = get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeColorChromaticAdaptationMode, + 0 ); + g_ColorMgmt.pending.chromaticAdaptationMode = + ( EChromaticAdaptationMethod )val; + } + // TODO: Hook up gamescopeColorMuraCorrectionImage for external. + if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionImage + [ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] ) + { + std::string path = get_string_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeColorMuraCorrectionImage + [ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] ); + if ( set_mura_overlay( path.c_str( ) ) ) hasRepaint = true; + } + // TODO: Hook up gamescopeColorMuraScale for external. + if ( ev->atom == ctx->atoms.gamescopeColorMuraScale + [ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] ) + { + uint32_t val = get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeColorMuraScale + [ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ], + 0 ); + float new_scale = bit_cast( val ); + if ( set_mura_scale( new_scale ) ) hasRepaint = true; + } + // TODO: Hook up gamescopeColorMuraCorrectionDisabled for external. + if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionDisabled + [ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] ) + { + bool disabled = !!get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeColorMuraCorrectionDisabled + [ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ], + 0 ); + if ( g_bMuraCompensationDisabled != disabled ) + { + g_bMuraCompensationDisabled = disabled; + hasRepaint = true; + } + } + if ( ev->atom == ctx->atoms.gamescopeCreateXWaylandServer ) + { + uint32_t identifier = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeCreateXWaylandServer, 0 ); + if ( identifier ) + { + wlserver_lock( ); + uint32_t server_id = + ( uint32_t )wlserver_make_new_xwayland_server( ); + assert( server_id != ~0u ); + gamescope_xwayland_server_t *server = + wlserver_get_xwayland_server( server_id ); + init_xwayland_ctx( server_id, server ); + char propertyString[ 256 ]; + snprintf( + propertyString, + sizeof( propertyString ), + "%u %u %s", + identifier, + server_id, + server->get_nested_display_name( ) ); + XTextProperty text_property = { + .value = ( unsigned char * )propertyString, + .encoding = ctx->atoms.utf8StringAtom, + .format = 8, + .nitems = strlen( propertyString ), + }; + g_SteamCompMgrWaiter.AddWaitable( server->ctx.get( ) ); + XSetTextProperty( + ctx->dpy, + ctx->root, + &text_property, + ctx->atoms.gamescopeCreateXWaylandServerFeedback ); + wlserver_unlock( ); + } + } + if ( ev->atom == ctx->atoms.gamescopeDestroyXWaylandServer ) + { + uint32_t server_id = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeDestroyXWaylandServer, 0 ); + + gamescope_xwayland_server_t *server = + wlserver_get_xwayland_server( server_id ); + if ( server ) + { + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->focusWindow && + pFocus->focusWindow->type == + steamcompmgr_win_type_t::XWAYLAND && + pFocus->focusWindow->xwayland( ).ctx == + server->ctx.get( ) ) + pFocus->focusWindow = nullptr; + + if ( pFocus->inputFocusWindow && + pFocus->inputFocusWindow->type == + steamcompmgr_win_type_t::XWAYLAND && + pFocus->inputFocusWindow->xwayland( ).ctx == + server->ctx.get( ) ) + pFocus->inputFocusWindow = nullptr; + + if ( pFocus->overlayWindow && + pFocus->overlayWindow->type == + steamcompmgr_win_type_t::XWAYLAND && + pFocus->overlayWindow->xwayland( ).ctx == + server->ctx.get( ) ) + pFocus->overlayWindow = nullptr; + + if ( pFocus->externalOverlayWindow && + pFocus->externalOverlayWindow->type == + steamcompmgr_win_type_t::XWAYLAND && + pFocus->externalOverlayWindow->xwayland( ).ctx == + server->ctx.get( ) ) + pFocus->externalOverlayWindow = nullptr; + + if ( pFocus->notificationWindow && + pFocus->notificationWindow->type == + steamcompmgr_win_type_t::XWAYLAND && + pFocus->notificationWindow->xwayland( ).ctx == + server->ctx.get( ) ) + pFocus->notificationWindow = nullptr; + + if ( pFocus->overrideWindow && + pFocus->overrideWindow->type == + steamcompmgr_win_type_t::XWAYLAND && + pFocus->overrideWindow->xwayland( ).ctx == + server->ctx.get( ) ) + pFocus->overrideWindow = nullptr; + + if ( pFocus->keyboardFocusWindow && + pFocus->keyboardFocusWindow->type == + steamcompmgr_win_type_t::XWAYLAND && + pFocus->keyboardFocusWindow->xwayland( ).ctx == + server->ctx.get( ) ) + pFocus->keyboardFocusWindow = nullptr; + + if ( pFocus->fadeWindow && + pFocus->fadeWindow->type == + steamcompmgr_win_type_t::XWAYLAND && + pFocus->fadeWindow->xwayland( ).ctx == server->ctx.get( ) ) + pFocus->fadeWindow = nullptr; + + if ( pFocus->cursor && + pFocus->cursor->getCtx( ) == server->ctx.get( ) ) + pFocus->cursor = nullptr; + } + + wlserver_lock( ); + g_SteamCompMgrWaiter.RemoveWaitable( server->ctx.get( ) ); + wlserver_destroy_xwayland_server( server ); + wlserver_unlock( ); + + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.gamescopeReshadeTechniqueIdx ) + { + uint32_t technique_idx = get_prop( + ctx, ctx->root, ctx->atoms.gamescopeReshadeTechniqueIdx, 0 ); + g_reshade_technique_idx = technique_idx; + } + if ( ev->atom == ctx->atoms.gamescopeReshadeEffect ) + { + std::string path = get_string_prop( + ctx, ctx->root, ctx->atoms.gamescopeReshadeEffect ); + g_reshade_effect = path; + } + if ( ev->atom == + ctx->atoms.gamescopeDisplayDynamicRefreshBasedOnGamePresence ) + { + g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = !!get_prop( + ctx, + ctx->root, + ctx->atoms.gamescopeDisplayDynamicRefreshBasedOnGamePresence, + 0 ); + } + if ( ev->atom == ctx->atoms.wineHwndStyle ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->hasHwndStyle = true; + w->hwndStyle = + get_prop( ctx, w->xwayland( ).id, ctx->atoms.wineHwndStyle, 0 ); + MakeFocusDirty( ); + } + } + if ( ev->atom == ctx->atoms.wineHwndStyleEx ) + { + steamcompmgr_win_t *w = find_win( ctx, ev->window ); + if ( w ) + { + w->hasHwndStyleEx = true; + w->hwndStyleEx = get_prop( + ctx, w->xwayland( ).id, ctx->atoms.wineHwndStyleEx, 0 ); + MakeFocusDirty( ); + } + } +} + +static int error( Display *dpy, XErrorEvent *ev ) +{ + // Do nothing. XErrors are usually benign. + return 0; +} + +static void steamcompmgr_exit( void ) +{ + g_ImageWaiter.Shutdown( ); + + // Clean up any commits. + { + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + { + for ( steamcompmgr_win_t *w = server->ctx->list; w; + w = w->xwayland( ).next ) + w->commit_queue.clear( ); + } + } + g_steamcompmgr_xdg_wins.clear( ); + g_HeldCommits[ HELD_COMMIT_BASE ] = nullptr; + g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; + + for ( auto &lut : g_ColorMgmtLuts ) + lut.shutdown( ); + for ( auto &lut : g_ColorMgmtLutsOverride ) + lut.shutdown( ); + for ( auto &lut : g_ScreenshotColorMgmtLuts ) + lut.shutdown( ); + for ( auto &lut : g_ScreenshotColorMgmtLutsHDR ) + lut.shutdown( ); + + if ( statsThreadRun == true ) + { + statsThreadRun = false; + statsThreadSem.signal( ); + } + + { + g_ColorMgmt.pending.appHDRMetadata = nullptr; + g_ColorMgmt.current.appHDRMetadata = nullptr; + + s_scRGB709To2020Matrix = nullptr; + for ( int i = 0; i < gamescope::GAMESCOPE_SCREEN_TYPE_COUNT; i++ ) + { + s_MuraCorrectionImage[ i ] = nullptr; + s_MuraCTMBlob[ i ] = nullptr; + } + } + + g_VirtualConnectorFocuses.clear( ); - if ( readyPipeFD != -1 ) - { - dprintf( readyPipeFD, "%s %s\n", root_ctx->xwayland_server->get_nested_display_name(), wlserver_get_wl_display_name() ); - close( readyPipeFD ); - readyPipeFD = -1; - } + gamescope::IBackend::Set( nullptr ); - g_SteamCompMgrWaiter.AddWaitable( &GetVBlankTimer() ); - g_SteamCompMgrWaiter.AddWaitable( &g_FPSLimitVRRTimer ); - GetVBlankTimer().ArmNextVBlank( true ); + wlserver_lock( ); + wlserver_shutdown( ); + wlserver_unlock( false ); +} + +[[noreturn]] static int handle_io_error( Display *dpy ) +{ + xwm_log.errorf( "X11 I/O error! This is fatal. Aborting..." ); + abort( ); +} + +static bool register_cm( xwayland_ctx_t *ctx ) +{ + Window w; + Atom a; + static char net_wm_cm[] = "_NET_WM_CM_Sxx"; + + snprintf( net_wm_cm, sizeof( net_wm_cm ), "_NET_WM_CM_S%d", ctx->scr ); + a = XInternAtom( ctx->dpy, net_wm_cm, false ); + + w = XGetSelectionOwner( ctx->dpy, a ); + if ( w != None ) + { + XTextProperty tp; + char **strs; + int count; + Atom winNameAtom = XInternAtom( ctx->dpy, "_NET_WM_NAME", false ); + + if ( !XGetTextProperty( ctx->dpy, w, &tp, winNameAtom ) && + !XGetTextProperty( ctx->dpy, w, &tp, XA_WM_NAME ) ) + { + xwm_log.errorf( + "Another composite manager is already running (0x%lx)", + ( unsigned long )w ); + return false; + } + if ( XmbTextPropertyToTextList( ctx->dpy, &tp, &strs, &count ) == + Success ) + { + xwm_log.errorf( + "Another composite manager is already running (%s)", + strs[ 0 ] ); + + XFreeStringList( strs ); + } + + XFree( tp.value ); + + return false; + } + + w = XCreateSimpleWindow( + ctx->dpy, RootWindow( ctx->dpy, ctx->scr ), 0, 0, 1, 1, 0, None, None ); + + Xutf8SetWMProperties( + ctx->dpy, + w, + "steamcompmgr", + "steamcompmgr", + NULL, + 0, + NULL, + NULL, + NULL ); + + Atom atomWmCheck = + XInternAtom( ctx->dpy, "_NET_SUPPORTING_WM_CHECK", false ); + XChangeProperty( + ctx->dpy, + ctx->root, + atomWmCheck, + XA_WINDOW, + 32, + PropModeReplace, + ( unsigned char * )&w, + 1 ); + XChangeProperty( + ctx->dpy, + w, + atomWmCheck, + XA_WINDOW, + 32, + PropModeReplace, + ( unsigned char * )&w, + 1 ); + + Atom supportedAtoms[] = { + XInternAtom( ctx->dpy, "_NET_WM_STATE", false ), + XInternAtom( ctx->dpy, "_NET_WM_STATE_FULLSCREEN", false ), + XInternAtom( ctx->dpy, "_NET_WM_STATE_SKIP_TASKBAR", false ), + XInternAtom( ctx->dpy, "_NET_WM_STATE_SKIP_PAGER", false ), + XInternAtom( ctx->dpy, "_NET_ACTIVE_WINDOW", false ), + }; + + XChangeProperty( + ctx->dpy, + ctx->root, + XInternAtom( ctx->dpy, "_NET_SUPPORTED", false ), + XA_ATOM, + 32, + PropModeAppend, + ( unsigned char * )supportedAtoms, + sizeof( supportedAtoms ) / sizeof( supportedAtoms[ 0 ] ) ); + + XSetSelectionOwner( ctx->dpy, a, w, 0 ); + + ctx->ourWindow = w; + + return true; +} + +static void register_systray( xwayland_ctx_t *ctx ) +{ + static char net_system_tray_name[] = "_NET_SYSTEM_TRAY_Sxx"; + + snprintf( + net_system_tray_name, + sizeof( net_system_tray_name ), + "_NET_SYSTEM_TRAY_S%d", + ctx->scr ); + Atom net_system_tray = XInternAtom( ctx->dpy, net_system_tray_name, false ); + + XSetSelectionOwner( ctx->dpy, net_system_tray, ctx->ourWindow, 0 ); +} + +bool handle_done_commit( + steamcompmgr_win_t *w, + xwayland_ctx_t *ctx, + uint64_t commitID, + uint64_t earliestPresentTime, + uint64_t earliestLatchTime ) +{ + bool bFoundWindow = false; + uint32_t j; + for ( j = 0; j < w->commit_queue.size( ); j++ ) + { + if ( w->commit_queue[ j ]->feedback.has_value( ) ) + w->engineName = + w->commit_queue[ j ]->feedback.value( )->vk_engine_name; + + if ( w->commit_queue[ j ]->commitID == commitID ) + { + gpuvis_trace_printf( + "commit %lu done", w->commit_queue[ j ]->commitID ); + w->commit_queue[ j ]->done = true; + w->commit_queue[ j ]->earliest_present_time = earliestPresentTime; + w->commit_queue[ j ]->present_margin = + earliestPresentTime - earliestLatchTime; + bFoundWindow = true; + + // Window just got a new available commit, determine if that's worth + // a repaint + + // If this is a forwarded vr plane, repaint + if ( w->oulTargetVROverlay ) + { + g_bUpdateForwardedVROverlays = true; + w->bNeedsForwarding = true; + } + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + + // If this is an overlay that we're presenting, repaint + if ( w == pFocus->overlayWindow && w->opacity != TRANSLUCENT ) + { + hasRepaintNonBasePlane = true; + } + + if ( w == pFocus->notificationWindow && + w->opacity != TRANSLUCENT ) + { + hasRepaintNonBasePlane = true; + } + + // matt: the performance overlay in Steam will interfere with + // VRR if we let this repaint. This has been broken since the + // logic for external overlay repaints was moved out of + // outdatedInteractiveFocus. It can cause displays to jump + // between the focused app's refresh rate and the maximum panel + // refresh rate when presenting any type of overlay, creating + // noticeable VRR flicker in the process. + // TODO: fix this properly for all overlays, including Steam + // notifications and QAM HACK: If VRR is active, prevent + // external overlays, i.e. mangoapp, from repainting the base + // plane + if ( ( w == pFocus->externalOverlayWindow && + w->opacity != TRANSLUCENT ) && + ( GetBackend( )->GetCurrentConnector( ) && + !GetBackend( ) + ->GetCurrentConnector( ) + ->IsVRRActive( ) ) ) + { + hasRepaintNonBasePlane = true; + } + + // If this is the main plane, repaint + if ( w == pFocus->focusWindow && !w->isSteamStreamingClient ) + { + if ( !cv_paint_debug_pause_base_plane ) + g_HeldCommits[ HELD_COMMIT_BASE ] = + w->commit_queue[ j ]; + hasRepaint = true; + + focusWindow_engine = w->engineName; + focusWindow_pid = w->pid; + } + + if ( w == pFocus->overrideWindow ) + { + hasRepaintNonBasePlane = true; + } + + if ( w->isSteamStreamingClientVideo && pFocus->focusWindow && + pFocus->focusWindow->isSteamStreamingClient ) + { + if ( !cv_paint_debug_pause_base_plane ) + g_HeldCommits[ HELD_COMMIT_BASE ] = + w->commit_queue[ j ]; + hasRepaint = true; + } + } + + if ( w->outdatedInteractiveFocus ) + { + MakeFocusDirty( ); + w->outdatedInteractiveFocus = false; + } + + break; + } + } + + if ( bFoundWindow == true ) + { + if ( j > 0 ) + w->commit_queue.erase( + w->commit_queue.begin( ), w->commit_queue.begin( ) + j ); + w->receivedDoneCommit = true; + return true; + } + + return false; +} - { - gamescope_xwayland_server_t *pServer = NULL; - for (size_t i = 0; (pServer = wlserver_get_xwayland_server(i)); i++) - { - xwayland_ctx_t *pXWaylandCtx = pServer->ctx.get(); - g_SteamCompMgrWaiter.AddWaitable( pXWaylandCtx ); +// TODO: Merge these two functions. +void handle_done_commits_xwayland( + xwayland_ctx_t *ctx, bool vblank, uint64_t vblank_idx ) +{ + std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); + + uint64_t next_refresh_time = + g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; + + // commits that were not ready to be presented based on their display + // timing. + static std::vector commits_before_their_time; + commits_before_their_time.clear( ); + commits_before_their_time.reserve( 32 ); + + // windows in FIFO mode we got a new frame to present for this vblank + static std::unordered_set fifo_win_seqs; + fifo_win_seqs.clear( ); + fifo_win_seqs.reserve( 32 ); + + uint64_t now = get_time_in_nanos( ); + + // very fast loop yes + for ( auto &entry : ctx->doneCommits.listCommitsDone ) + { + bool entry_vblank = vblank; + + if ( GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->IsVRRActive( ) ) + { + for ( steamcompmgr_win_t *w = ctx->list; w; + w = w->xwayland( ).next ) + { + if ( w->seq != entry.winSeq ) continue; + + entry_vblank = + entry_vblank && steamcompmgr_should_vblank_window( + true, vblank_idx, w, now ); + } + } + else + { + entry_vblank = entry_vblank && steamcompmgr_should_vblank_window( + true, vblank_idx ); + } + + if ( entry.fifo && + ( !entry_vblank || fifo_win_seqs.count( entry.winSeq ) > 0 ) ) + { + commits_before_their_time.push_back( entry ); + continue; + } + + if ( !entry.earliestPresentTime ) + { + entry.earliestPresentTime = next_refresh_time; + entry.earliestLatchTime = now; + } + + if ( entry.desiredPresentTime > next_refresh_time ) + { + commits_before_their_time.push_back( entry ); + continue; + } + + for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland( ).next ) + { + if ( w->seq != entry.winSeq ) continue; + if ( handle_done_commit( + w, + ctx, + entry.commitID, + entry.earliestPresentTime, + entry.earliestLatchTime ) ) + { + if ( entry.fifo ) fifo_win_seqs.insert( entry.winSeq ); + break; + } + } + } + + ctx->doneCommits.listCommitsDone.swap( commits_before_their_time ); +} - pServer->ctx->force_windows_fullscreen = bForceWindowsFullscreen; - } - } +void handle_done_commits_xdg( bool vblank, uint64_t vblank_idx ) +{ + std::lock_guard lock( + g_steamcompmgr_xdg_done_commits.listCommitsDoneLock ); + + uint64_t next_refresh_time = + g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; + + // commits that were not ready to be presented based on their display + // timing. + static std::vector commits_before_their_time; + commits_before_their_time.clear( ); + commits_before_their_time.reserve( 32 ); + + // windows in FIFO mode we got a new frame to present for this vblank + static std::unordered_set fifo_win_seqs; + fifo_win_seqs.clear( ); + fifo_win_seqs.reserve( 32 ); + + uint64_t now = get_time_in_nanos( ); + + vblank = vblank && steamcompmgr_should_vblank_window( true, vblank_idx ); + + // very fast loop yes + for ( auto &entry : g_steamcompmgr_xdg_done_commits.listCommitsDone ) + { + if ( entry.fifo && + ( !vblank || fifo_win_seqs.count( entry.winSeq ) > 0 ) ) + { + commits_before_their_time.push_back( entry ); + continue; + } + + if ( !entry.earliestPresentTime ) + { + entry.earliestPresentTime = next_refresh_time; + entry.earliestLatchTime = now; + } + + if ( entry.desiredPresentTime > next_refresh_time ) + { + commits_before_their_time.push_back( entry ); + break; + } + + for ( const auto &xdg_win : g_steamcompmgr_xdg_wins ) + { + if ( xdg_win->seq != entry.winSeq ) continue; + if ( handle_done_commit( + xdg_win.get( ), + nullptr, + entry.commitID, + entry.earliestPresentTime, + entry.earliestLatchTime ) ) + { + if ( entry.fifo ) fifo_win_seqs.insert( entry.winSeq ); + break; + } + } + } + + g_steamcompmgr_xdg_done_commits.listCommitsDone.swap( + commits_before_their_time ); +} + +gamescope::ConVar cv_mangoapp_use_output_timing{ + "mangoapp_use_output_timing", true +}; - update_vrr_atoms(root_ctx, true); - update_mode_atoms(root_ctx); - XFlush(root_ctx->dpy); +void handle_presented_for_window( steamcompmgr_win_t *w ) +{ + // wlserver_lock is held. + + uint64_t next_refresh_time = + g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; + + uint64_t refresh_cycle = + g_nSteamCompMgrTargetFPS && steamcompmgr_window_should_limit_fps( w ) + ? g_SteamCompMgrLimitedAppRefreshCycle + : g_SteamCompMgrAppRefreshCycle; + + commit_t *lastCommit = get_window_last_done_commit_peek( w ); + if ( lastCommit ) + { + if ( !cv_mangoapp_use_output_timing ) + { + // We might present the same commit multiple times. In these cases + // there will be no frametime delta as the last frame was just + // re-used. + uint64_t frametime_ns = + lastCommit->present_time - w->last_commit_present_time; + if ( frametime_ns > 0 ) + { + if ( w->appID > 0 ) + wlserver_app_presented( w->appID, frametime_ns ); + + w->last_commit_present_time = lastCommit->present_time; + } + } + + if ( !lastCommit->presentation_feedbacks.empty( ) || + lastCommit->present_id ) + { + if ( !lastCommit->presentation_feedbacks.empty( ) ) + { + wlserver_presentation_feedback_presented( + lastCommit->surf, + lastCommit->presentation_feedbacks, + next_refresh_time, + refresh_cycle ); + } + + if ( lastCommit->present_id ) + { + wlserver_past_present_timing( + lastCommit->surf, + *lastCommit->present_id, + lastCommit->desired_present_time, + next_refresh_time, + lastCommit->earliest_present_time, + lastCommit->present_margin ); + lastCommit->present_id = std::nullopt; + } + } + } + + if ( struct wlr_surface *surface = w->current_surface( ) ) + { + auto info = get_wl_surface_info( surface ); + if ( info != nullptr && info->last_refresh_cycle != refresh_cycle ) + { + // Could have got the override set in this bubble. + surface = w->current_surface( ); + + if ( info->last_refresh_cycle != refresh_cycle ) + { + info->last_refresh_cycle = refresh_cycle; + wlserver_refresh_cycle( surface, refresh_cycle ); + } + } + } +} - if ( !GetBackend()->PostInit() ) - return; +void handle_presented_xwayland( xwayland_ctx_t *ctx ) +{ + for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland( ).next ) + { + handle_presented_for_window( w ); + } +} - if ( g_pVROverlayKey ) - { - set_string_prop( root_ctx, root_ctx->atoms.gamescopeMainSteamVROverlay, *g_pVROverlayKey ); - } +void handle_presented_xdg( ) +{ + for ( const auto &xdg_win : g_steamcompmgr_xdg_wins ) + { + handle_presented_for_window( xdg_win.get( ) ); + } +} - update_edid_prop(); - - update_screenshot_color_mgmt(); - - LaunchNestedChildren( subCommandArg >= 0 ? &argv[ subCommandArg ] : nullptr ); - - // Transpose to get this 3x3 matrix into the right state for applying as a 3x4 - // on DRM + the Vulkan side. - // ie. color.rgb = color.rgba * u_ctm[offsetLayerIdx]; - s_scRGB709To2020Matrix = GetBackend()->CreateBackendBlob( glm::mat3x4( glm::transpose( k_2020_from_709 ) ) ); - - FILE *sysfs_caps = fopen("/sys/fs/cgroup/dmem.capacity", "r"); - if (sysfs_caps != nullptr) { - char *line = NULL; - size_t size = 0; - while (getline(&line, &size, sysfs_caps) >= 0) { - char *capacity = strstr(line, " "); - if (capacity) - ++capacity; - else - continue; - uint64_t vramSize = strtoull(capacity, NULL, 10); - std::string idString = std::string(line, (capacity - 1) - line); - g_vramCapacities.emplace(idString, vramSize); - } - } +void nudge_steamcompmgr( void ) { g_SteamCompMgrWaiter.Nudge( ); } - for (;;) - { - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - assert(server->ctx); - if (server->ctx->HasQueuedEvents()) - server->ctx->Dispatch(); - } - } +void force_repaint( void ) +{ + g_bForceRepaint = true; + nudge_steamcompmgr( ); +} - g_SteamCompMgrWaiter.PollEvents(); +struct TempUpscaleImage_t +{ + gamescope::OwningRc pTexture; + // Timeline of upscale -> release, to be used as acquire for the commit. + std::shared_ptr pReleaseTimeline; + uint64_t ulLastPoint = 0ul; +}; - bool vblank = false; - if ( std::optional pendingVBlank = GetVBlankTimer().ProcessVBlank() ) - { - g_SteamCompMgrVBlankTime = *pendingVBlank; - vblank = true; - } +static std::vector g_pUpscaleImages; +void ClearUpscaleImages( ) { g_pUpscaleImages.clear( ); } + +static TempUpscaleImage_t * +GetTempUpscaleImage( uint32_t uWidth, uint32_t uHeight, uint32_t uDrmFormat ) +{ + if ( g_pUpscaleImages.size( ) ) + { + // Mixing and matching sizes to only do the min required would be nice + // but massively complicates caching. + if ( g_pUpscaleImages[ 0 ].pTexture->width( ) != uWidth || + g_pUpscaleImages[ 0 ].pTexture->height( ) != uHeight || + g_pUpscaleImages[ 0 ].pTexture->drmFormat( ) != uDrmFormat ) + { + g_pUpscaleImages.clear( ); + } + } + + for ( TempUpscaleImage_t &image : g_pUpscaleImages ) + { + if ( !image.pTexture->IsInUse( ) ) return ℑ + } + + if ( g_pUpscaleImages.size( ) > 8 ) + { + xwm_log.warnf( "No upscale images free!\n" ); + return {}; + } + + gamescope::OwningRc pTexture = new CVulkanTexture( ); + + std::shared_ptr pTimeline = + gamescope::CTimeline::Create( ); + if ( !pTimeline ) return nullptr; + + CVulkanTexture::createFlags imageFlags; + imageFlags.bSampled = true; + imageFlags.bStorage = true; + imageFlags.bFlippable = true; + pTexture->BInit( + g_nOutputWidth, g_nOutputHeight, 1, uDrmFormat, imageFlags ); + TempUpscaleImage_t &image = g_pUpscaleImages.emplace_back( + std::move( pTexture ), std::move( pTimeline ) ); + + return ℑ +} + +gamescope::ConVar cv_surface_update_force_only_current_surface( + "surface_update_force_only_current_surface", + false, + "Force updates to apply only to the current surface, ignoring commits for " + "other surfaces." ); + +void update_wayland_res( + CommitDoneList_t *doneCommits, + steamcompmgr_win_t *w, + ResListEntry_t &reslistentry ) +{ + struct wlr_buffer *buf = reslistentry.buf; + + if ( w == nullptr ) + { + wlserver_lock( ); + wlr_buffer_unlock( buf ); + wlserver_unlock( ); + + // Make sure to send the discarded event if we hit this + // to ensure forward progress. + if ( !reslistentry.presentation_feedbacks.empty( ) ) + { + wlserver_presentation_feedback_discard( + reslistentry.surf, reslistentry.presentation_feedbacks ); + // presentation_feedbacks cleared by + // wlserver_presentation_feedback_discard + } + + xwm_log.errorf( "waylandres but no win" ); + return; + } + + // If we ever use HDR on the surface, only ever accept flip commits from the + // WSI layer. + if ( reslistentry.feedback && reslistentry.feedback->vk_colorspace != + VK_COLOR_SPACE_SRGB_NONLINEAR_KHR ) + { + w->bHasHadNonSRGBColorSpace = true; + } + + // If there are random commits that are really thin/small when we have the + // WSI layer active ever, let's just ignore these as they are probably bogus + // commits from glamor. + bool bPossiblyBogus = + reslistentry.buf->width <= 2 || reslistentry.buf->height <= 2; + + // If the buffer has no damage, always prefer our override surface. + bool bHasDamage = ( reslistentry.surf->buffer_damage.extents.x2 - + reslistentry.surf->buffer_damage.extents.x1 ) > 2 && + ( reslistentry.surf->buffer_damage.extents.y2 - + reslistentry.surf->buffer_damage.extents.y1 ) > 2; + + // If we have an override surface, make sure this commit is for the current + // surface or if the commit is probably bogus. + bool bOnlyCurrentSurface = w->bHasHadNonSRGBColorSpace || bPossiblyBogus || + !bHasDamage || + cv_surface_update_force_only_current_surface; + + bool for_current_surface = + !w->override_surface( ) || w->current_surface( ) == reslistentry.surf; + + if ( !for_current_surface ) + { + xwm_log.debugf( "Got commit not for current surface." ); + } + + if ( bOnlyCurrentSurface && !for_current_surface ) + { + wlserver_lock( ); + wlr_buffer_unlock( buf ); + wlserver_unlock( ); + w->receivedDoneCommit = true; + return; + } + + bool already_exists = false; + for ( const auto &existing_commit : w->commit_queue ) + { + if ( existing_commit->buf == buf ) already_exists = true; + } + + if ( already_exists && !reslistentry.feedback && + reslistentry.presentation_feedbacks.empty( ) ) + { + wlserver_lock( ); + wlr_buffer_unlock( buf ); + wlserver_unlock( ); + xwm_log.debugf( "got the same buffer committed twice, ignoring." ); + + // If we have a duplicated commit + frame callback, ensure that is + // signalled. This matches Mutter and Weston behavior, so it's plausible + // that some application relies on forward progress. We're essentially + // discarding the commit here, so consider it complete right away. + w->receivedDoneCommit = true; + return; + } + + gamescope::Rc newCommit = import_commit( + w, + reslistentry.surf, + buf, + reslistentry.async, + std::move( reslistentry.feedback ), + std::move( reslistentry.presentation_feedbacks ), + reslistentry.present_id, + reslistentry.desired_present_time, + reslistentry.fifo ); + + int fence = -1; + if ( newCommit != nullptr ) + { + global_focus_t *pCurrentFocus = GetCurrentFocus( ); + + static bool bMangoappSocketDisable = + env_to_bool( getenv( "GAMESCOPE_MANGOAPP_SOCKET_DISABLE" ) ); + + // Whether or not to nudge mango app when this commit is done. + const bool mango_nudge = + pCurrentFocus && + ( ( w == pCurrentFocus->focusWindow && + !w->isSteamStreamingClient ) || + ( pCurrentFocus->focusWindow && + pCurrentFocus->focusWindow->isSteamStreamingClient && + w->isSteamStreamingClientVideo ) ) && + !bMangoappSocketDisable; + + bool bValidPreemptiveScale = + reslistentry.pAcquirePoint && pCurrentFocus && + w == pCurrentFocus->focusWindow && cv_upscale_preemptive; + bool bPreemptiveUpscale = + bValidPreemptiveScale && newCommit->ShouldPreemptivelyUpscale( ); + + bool bKnownReady = false; + + std::pair eventFd = + gamescope::CAcquireTimelinePoint::k_InvalidEvent; + + if ( bPreemptiveUpscale ) + { + FrameInfo_t upscaledFrameInfo{}; + upscaledFrameInfo.applyOutputColorMgmt = true; + upscaledFrameInfo.outputEncodingEOTF = + ( newCommit->colorspace( ) == + GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR || + newCommit->colorspace( ) == + GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB ) + ? EOTF_Gamma22 + : EOTF_PQ; + + float flOldGlobalScale = globalScaleRatio; + float flOldZoomScale = zoomScaleRatio; + float flOldOverscanScale = overscanScaleRatio; + overscanScaleRatio = 1.0f; + zoomScaleRatio = 1.0f; + globalScaleRatio = 1.0f; + paint_window_commit( newCommit, w, w, &upscaledFrameInfo, nullptr ); + upscaledFrameInfo.useFSRLayer0 = + g_upscaleFilter == GamescopeUpscaleFilter::FSR; + upscaledFrameInfo.useNISLayer0 = + g_upscaleFilter == GamescopeUpscaleFilter::NIS; + globalScaleRatio = flOldGlobalScale; + zoomScaleRatio = flOldZoomScale; + overscanScaleRatio = flOldOverscanScale; + + TempUpscaleImage_t *pTempImage = GetTempUpscaleImage( + g_nOutputWidth, g_nOutputHeight, g_output.uOutputFormat ); + if ( pTempImage ) + { + const uint64_t ulNextReleasePoint = ++pTempImage->ulLastPoint; + + std::unique_ptr pCommandBuffer = + g_device.commandBuffer( ); + + pCommandBuffer->AddDependency( + reslistentry.pAcquirePoint->GetTimeline( ) + ->ToVkSemaphore( ), + reslistentry.pAcquirePoint->GetPoint( ) ); + pCommandBuffer->AddSignal( + pTempImage->pReleaseTimeline->ToVkSemaphore( ), + ulNextReleasePoint ); + + static std::optional s_ulLastPreemptiveUpscaleSeqNo; + + if ( s_ulLastPreemptiveUpscaleSeqNo ) + { + vulkan_wait( *s_ulLastPreemptiveUpscaleSeqNo, true ); + } + + std::optional seqNo = vulkan_composite( + &upscaledFrameInfo, + nullptr, + false, + pTempImage->pTexture, + false, + std::move( pCommandBuffer ) ); + + if ( cv_upscale_preemptive_debug_force_sync ) + { + vulkan_wait( *seqNo, true ); + } + + s_ulLastPreemptiveUpscaleSeqNo = seqNo; + + newCommit->upscaledTexture = std::optional{ + std::in_place_t{}, + g_upscaleFilter, + g_upscaleScaler, + g_nOutputWidth, + g_nOutputHeight, + pTempImage->pTexture, + upscaledFrameInfo.outputEncodingEOTF == EOTF_Gamma22 + ? VK_COLOR_SPACE_SRGB_NONLINEAR_KHR + : VK_COLOR_SPACE_HDR10_ST2084_EXT, + }; + + // Manifest a new acquire timeline point with this inline work. + eventFd = gamescope::CAcquireTimelinePoint( + pTempImage->pReleaseTimeline, ulNextReleasePoint ) + .CreateEventFd( ); + + // xwm_log.infof( "Pre-emptively upscaling!" ); + } + else + { + bPreemptiveUpscale = false; + } + } + + if ( !bPreemptiveUpscale ) + { + if ( bValidPreemptiveScale ) { ClearUpscaleImages( ); } + + if ( reslistentry.pAcquirePoint ) + { + eventFd = reslistentry.pAcquirePoint->CreateEventFd( ); + } + } + + if ( gamescope::IBackendFb *pBackendFb = + newCommit->vulkanTex->GetBackendFb( ) ) + { + if ( reslistentry.pReleasePoint ) + pBackendFb->SetReleasePoint( reslistentry.pReleasePoint ); + else + pBackendFb->SetBuffer( buf ); + } + + if ( eventFd != gamescope::CAcquireTimelinePoint::k_InvalidEvent ) + { + fence = eventFd.first; + bKnownReady = eventFd.second; + } + else + { + struct wlr_dmabuf_attributes dmabuf = { 0 }; + if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) + { + fence = dup( dmabuf.fd[ 0 ] ); + } + else + { + fence = newCommit->vulkanTex->memoryFence( ); + } + } + + gpuvis_trace_printf( + "pushing wait for commit %lu win %lx", + newCommit->commitID, + w->type == steamcompmgr_win_type_t::XWAYLAND ? w->xwayland( ).id + : 0 ); + { + newCommit->SetFence( fence, mango_nudge, doneCommits ); + if ( bKnownReady ) newCommit->Signal( ); + else + g_ImageWaiter.AddWaitable( newCommit.get( ) ); + } + + w->commit_queue.push_back( std::move( newCommit ) ); + } +} + +void check_new_xwayland_res( xwayland_ctx_t *ctx ) +{ + // When importing buffer, we'll potentially need to perform operations with + // a wlserver lock (e.g. wlr_buffer_lock). We can't do this with a + // wayland_commit_queue lock because that causes deadlocks. + std::vector &tmp_queue = + ctx->xwayland_server->retrieve_commits( ); + + for ( uint32_t i = 0; i < tmp_queue.size( ); i++ ) + { + steamcompmgr_win_t *w = find_win( ctx, tmp_queue[ i ].surf ); + update_wayland_res( &ctx->doneCommits, w, tmp_queue[ i ] ); + } +} + +void check_new_xdg_res( ) +{ + std::vector tmp_queue = wlserver_xdg_commit_queue( ); + for ( uint32_t i = 0; i < tmp_queue.size( ); i++ ) + { + for ( const auto &xdg_win : g_steamcompmgr_xdg_wins ) + { + if ( xdg_win->xdg( ).surface.main_surface == tmp_queue[ i ].surf ) + { + update_wayland_res( + &g_steamcompmgr_xdg_done_commits, + xdg_win.get( ), + tmp_queue[ i ] ); + break; + } + } + } +} + +static void handle_xfixes_selection_notify( + xwayland_ctx_t *ctx, XFixesSelectionNotifyEvent *event ) +{ + if ( event->owner == ctx->ourWindow ) { return; } + + XConvertSelection( + ctx->dpy, + event->selection, + ctx->atoms.utf8StringAtom, + event->selection, + ctx->ourWindow, + CurrentTime ); + XFlush( ctx->dpy ); +} + +void xwayland_ctx_t::Dispatch( ) +{ + xwayland_ctx_t *ctx = this; + + MouseCursor *cursor = ctx->cursor.get( ); + bool bSetFocus = false; + + while ( XPending( ctx->dpy ) ) + { + XEvent ev; + int ret = XNextEvent( ctx->dpy, &ev ); + if ( ret != 0 ) + { + xwm_log.errorf( "XNextEvent failed" ); + break; + } + if ( debugEvents ) + { + gpuvis_trace_printf( "event %d", ev.type ); + printf( "event %d\n", ev.type ); + } + switch ( ev.type ) + { + case CreateNotify: + if ( ev.xcreatewindow.parent == ctx->root ) + add_win( + ctx, + ev.xcreatewindow.window, + 0, + ev.xcreatewindow.serial ); + break; + case ConfigureNotify: + configure_win( ctx, &ev.xconfigure ); + break; + case DestroyNotify: + { + steamcompmgr_win_t *w = + find_win( ctx, ev.xdestroywindow.window ); + + if ( w && w->xwayland( ).id == ev.xdestroywindow.window ) + destroy_win( ctx, ev.xdestroywindow.window, true, true ); + break; + } + case MapNotify: + { + steamcompmgr_win_t *w = find_win( ctx, ev.xmap.window ); + + if ( w && w->xwayland( ).id == ev.xmap.window ) + map_win( ctx, ev.xmap.window, ev.xmap.serial ); + break; + } + case UnmapNotify: + { + steamcompmgr_win_t *w = find_win( ctx, ev.xunmap.window ); + + if ( w && w->xwayland( ).id == ev.xunmap.window ) + unmap_win( ctx, ev.xunmap.window, true ); + break; + } + case FocusOut: + { + steamcompmgr_win_t *w = find_win( ctx, ev.xfocus.window ); + + // If focus escaped the current desired keyboard focus window, + // check where it went + if ( w && w->xwayland( ).id == ctx->currentKeyboardFocusWindow ) + { + Window newKeyboardFocus = None; + int nRevertMode = 0; + XGetInputFocus( ctx->dpy, &newKeyboardFocus, &nRevertMode ); + + // Find window or its toplevel parent + steamcompmgr_win_t *kbw = find_win( ctx, newKeyboardFocus ); + + if ( kbw ) + { + if ( kbw->xwayland( ).id == + ctx->currentKeyboardFocusWindow ) + { + // focus went to a child, this is fine, make note of + // it in case we need to fix it + ctx->currentKeyboardFocusWindow = newKeyboardFocus; + } + else + { + // focus went elsewhere, correct it + bSetFocus = true; + } + } + } + + break; + } + case ReparentNotify: + if ( ev.xreparent.parent == ctx->root ) + add_win( ctx, ev.xreparent.window, 0, ev.xreparent.serial ); + else + { + steamcompmgr_win_t *w = + find_win( ctx, ev.xreparent.window ); + + if ( w && w->xwayland( ).id == ev.xreparent.window ) + { + destroy_win( ctx, ev.xreparent.window, false, true ); + } + else + { + // If something got reparented _to_ a toplevel window, + // go check for the fullscreen workaround again. + w = find_win( ctx, ev.xreparent.parent ); + if ( w ) + { + get_size_hints( ctx, w ); + MakeFocusDirty( ); + } + } + } + break; + case CirculateNotify: + circulate_win( ctx, &ev.xcirculate ); + break; + case MapRequest: + map_request( ctx, &ev.xmaprequest ); + break; + case ConfigureRequest: + configure_request( ctx, &ev.xconfigurerequest ); + break; + case CirculateRequest: + circulate_request( ctx, &ev.xcirculaterequest ); + break; + case Expose: + break; + case PropertyNotify: + handle_property_notify( ctx, &ev.xproperty ); + break; + case ClientMessage: + handle_client_message( ctx, &ev.xclient ); + break; + case LeaveNotify: + break; + case SelectionNotify: + handle_selection_notify( ctx, &ev.xselection ); + break; + case SelectionRequest: + handle_selection_request( ctx, &ev.xselectionrequest ); + break; + + default: + if ( ev.type == ctx->damage_event + XDamageNotify ) + { + damage_win( ctx, ( XDamageNotifyEvent * )&ev ); + } + else if ( ev.type == ctx->xfixes_event + XFixesCursorNotify ) + { + cursor->setDirty( ); + } + else if ( ev.type == ctx->xfixes_event + XFixesSelectionNotify ) + { + handle_xfixes_selection_notify( + ctx, ( XFixesSelectionNotifyEvent * )&ev ); + } + break; + } + XFlush( ctx->dpy ); + } + + if ( bSetFocus ) + { + XSetInputFocus( + ctx->dpy, + ctx->currentKeyboardFocusWindow, + RevertToNone, + CurrentTime ); + } +} - if ( g_bRun == false ) - { - break; - } +struct rgba_t +{ + uint8_t r, g, b, a; +}; - // If this is from the timer or not. - // Consider this to also be "is this vblank, the fastest refresh cycle after our last commit?" - // as a question. - const bool bIsVBlankFromTimer = vblank; +static bool +load_mouse_cursor( MouseCursor *cursor, const char *path, int hx, int hy ) +{ + int w, h, channels; + rgba_t *data = + ( rgba_t * )stbi_load( path, &w, &h, &channels, STBI_rgb_alpha ); + if ( !data ) + { + xwm_log.errorf( "Failed to open/load cursor file" ); + return false; + } + + std::transform( + data, + data + w * h, + data, + []( rgba_t x ) + { + if ( x.a == 0 ) return rgba_t{}; + return rgba_t{ uint8_t( ( x.b * x.a ) / 255 ), + uint8_t( ( x.g * x.a ) / 255 ), + uint8_t( ( x.r * x.a ) / 255 ), + x.a }; + } ); + + // Data is freed by XDestroyImage in setCursorImage. + return cursor->setCursorImage( ( char * )data, w, h, hx, hy ); +} + +const char *g_customCursorPath = nullptr; +int g_customCursorHotspotX = 0; +int g_customCursorHotspotY = 0; - // We can always vblank if VRR. - const bool bVRR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive(); - if ( bVRR ) - vblank = true; +xwayland_ctx_t g_ctx; - bool flush_root = false; +static bool setup_error_handlers = false; - if ( inputCounter != lastPublishedInputCounter ) - { - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeInputCounterAtom, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&inputCounter, 1 ); +void init_xwayland_ctx( + uint32_t serverId, gamescope_xwayland_server_t *xwayland_server ) +{ + assert( !xwayland_server->ctx ); + xwayland_server->ctx = std::make_unique( ); + xwayland_ctx_t *ctx = xwayland_server->ctx.get( ); + + int composite_major, composite_minor; + int xres_major, xres_minor; + + ctx->xwayland_server = xwayland_server; + ctx->dpy = xwayland_server->get_xdisplay( ); + if ( !ctx->dpy ) + { + xwm_log.errorf( "Can't open display" ); + exit( 1 ); + } + + if ( !setup_error_handlers ) + { + XSetErrorHandler( error ); + XSetIOErrorHandler( handle_io_error ); + setup_error_handlers = true; + } + + if ( synchronize ) XSynchronize( ctx->dpy, 1 ); + + ctx->scr = DefaultScreen( ctx->dpy ); + ctx->root = RootWindow( ctx->dpy, ctx->scr ); + + if ( !XRenderQueryExtension( + ctx->dpy, &ctx->render_event, &ctx->render_error ) ) + { + xwm_log.errorf( "No render extension" ); + exit( 1 ); + } + if ( !XQueryExtension( + ctx->dpy, + COMPOSITE_NAME, + &ctx->composite_opcode, + &ctx->composite_event, + &ctx->composite_error ) ) + { + xwm_log.errorf( "No composite extension" ); + exit( 1 ); + } + XCompositeQueryVersion( ctx->dpy, &composite_major, &composite_minor ); + + if ( !XDamageQueryExtension( + ctx->dpy, &ctx->damage_event, &ctx->damage_error ) ) + { + xwm_log.errorf( "No damage extension" ); + exit( 1 ); + } + if ( !XFixesQueryExtension( + ctx->dpy, &ctx->xfixes_event, &ctx->xfixes_error ) ) + { + xwm_log.errorf( "No XFixes extension" ); + exit( 1 ); + } + if ( !XShapeQueryExtension( + ctx->dpy, &ctx->xshape_event, &ctx->xshape_error ) ) + { + xwm_log.errorf( "No XShape extension" ); + exit( 1 ); + } + if ( !XFixesQueryExtension( + ctx->dpy, &ctx->xfixes_event, &ctx->xfixes_error ) ) + { + xwm_log.errorf( "No XFixes extension" ); + exit( 1 ); + } + if ( !XResQueryVersion( ctx->dpy, &xres_major, &xres_minor ) ) + { + xwm_log.errorf( "No XRes extension" ); + exit( 1 ); + } + if ( xres_major != 1 || xres_minor < 2 ) + { + xwm_log.errorf( + "Unsupported XRes version: have %d.%d, want 1.2", + xres_major, + xres_minor ); + exit( 1 ); + } + if ( !XQueryExtension( + ctx->dpy, + "XInputExtension", + &ctx->xinput_opcode, + &ctx->xinput_event, + &ctx->xinput_error ) ) + { + xwm_log.errorf( "No XInput extension" ); + exit( 1 ); + } + int xi_major = 2; + int xi_minor = 0; + XIQueryVersion( ctx->dpy, &xi_major, &xi_minor ); + + if ( !register_cm( ctx ) ) { exit( 1 ); } + + register_systray( ctx ); + + /* get atoms */ + ctx->atoms.steamAtom = XInternAtom( ctx->dpy, STEAM_PROP, false ); + ctx->atoms.steamInputFocusAtom = + XInternAtom( ctx->dpy, "STEAM_INPUT_FOCUS", false ); + ctx->atoms.steamTouchClickModeAtom = + XInternAtom( ctx->dpy, "STEAM_TOUCH_CLICK_MODE", false ); + ctx->atoms.gameAtom = XInternAtom( ctx->dpy, GAME_PROP, false ); + ctx->atoms.overlayAtom = XInternAtom( ctx->dpy, OVERLAY_PROP, false ); + ctx->atoms.externalOverlayAtom = + XInternAtom( ctx->dpy, EXTERNAL_OVERLAY_PROP, false ); + ctx->atoms.opacityAtom = XInternAtom( ctx->dpy, OPACITY_PROP, false ); + ctx->atoms.gamesRunningAtom = + XInternAtom( ctx->dpy, GAMES_RUNNING_PROP, false ); + ctx->atoms.screenScaleAtom = + XInternAtom( ctx->dpy, SCREEN_SCALE_PROP, false ); + ctx->atoms.screenZoomAtom = + XInternAtom( ctx->dpy, SCREEN_MAGNIFICATION_PROP, false ); + ctx->atoms.winTypeAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE", false ); + ctx->atoms.winDesktopAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE_DESKTOP", false ); + ctx->atoms.winDockAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE_DOCK", false ); + ctx->atoms.winToolbarAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", false ); + ctx->atoms.winMenuAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE_MENU", false ); + ctx->atoms.winUtilAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE_UTILITY", false ); + ctx->atoms.winSplashAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE_SPLASH", false ); + ctx->atoms.winDialogAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE_DIALOG", false ); + ctx->atoms.winNormalAtom = + XInternAtom( ctx->dpy, "_NET_WM_WINDOW_TYPE_NORMAL", false ); + ctx->atoms.sizeHintsAtom = + XInternAtom( ctx->dpy, "WM_NORMAL_HINTS", false ); + ctx->atoms.netWMStateFullscreenAtom = + XInternAtom( ctx->dpy, "_NET_WM_STATE_FULLSCREEN", false ); + ctx->atoms.activeWindowAtom = + XInternAtom( ctx->dpy, "_NET_ACTIVE_WINDOW", false ); + ctx->atoms.netWMStateAtom = XInternAtom( ctx->dpy, "_NET_WM_STATE", false ); + ctx->atoms.WMTransientForAtom = + XInternAtom( ctx->dpy, "WM_TRANSIENT_FOR", false ); + ctx->atoms.netWMStateHiddenAtom = + XInternAtom( ctx->dpy, "_NET_WM_STATE_HIDDEN", false ); + ctx->atoms.netWMStateFocusedAtom = + XInternAtom( ctx->dpy, "_NET_WM_STATE_FOCUSED", false ); + ctx->atoms.netWMStateSkipTaskbarAtom = + XInternAtom( ctx->dpy, "_NET_WM_STATE_SKIP_TASKBAR", false ); + ctx->atoms.netWMStateSkipPagerAtom = + XInternAtom( ctx->dpy, "_NET_WM_STATE_SKIP_PAGER", false ); + ctx->atoms.WLSurfaceIDAtom = + XInternAtom( ctx->dpy, "WL_SURFACE_ID", false ); + ctx->atoms.WMStateAtom = XInternAtom( ctx->dpy, "WM_STATE", false ); + ctx->atoms.utf8StringAtom = XInternAtom( ctx->dpy, "UTF8_STRING", false ); + ctx->atoms.netWMNameAtom = XInternAtom( ctx->dpy, "_NET_WM_NAME", false ); + ctx->atoms.netWMIcon = XInternAtom( ctx->dpy, "_NET_WM_ICON", false ); + ctx->atoms.netSystemTrayOpcodeAtom = + XInternAtom( ctx->dpy, "_NET_SYSTEM_TRAY_OPCODE", false ); + ctx->atoms.steamStreamingClientAtom = + XInternAtom( ctx->dpy, "STEAM_STREAMING_CLIENT", false ); + ctx->atoms.steamStreamingClientVideoAtom = + XInternAtom( ctx->dpy, "STEAM_STREAMING_CLIENT_VIDEO", false ); + ctx->atoms.steamGamescopeVROverlayTarget = + XInternAtom( ctx->dpy, "STEAM_GAMESCOPE_VROVERLAY_TARGET", false ); + ctx->atoms.gamescopePid = XInternAtom( ctx->dpy, "GAMESCOPE_PID", false ); + ctx->atoms.gamescopeVROverlayForwarding = + XInternAtom( ctx->dpy, "GAMESCOPE_VROVERLAY_FORWARDING", false ); + ctx->atoms.gamescopeFocusableAppsAtom = + XInternAtom( ctx->dpy, "GAMESCOPE_FOCUSABLE_APPS", false ); + ctx->atoms.gamescopeFocusableWindowsAtom = + XInternAtom( ctx->dpy, "GAMESCOPE_FOCUSABLE_WINDOWS", false ); + ctx->atoms.gamescopeFocusedAppAtom = + XInternAtom( ctx->dpy, "GAMESCOPE_FOCUSED_APP", false ); + ctx->atoms.gamescopeFocusedAppGfxAtom = + XInternAtom( ctx->dpy, "GAMESCOPE_FOCUSED_APP_GFX", false ); + ctx->atoms.gamescopeFocusedWindowAtom = + XInternAtom( ctx->dpy, "GAMESCOPE_FOCUSED_WINDOW", false ); + ctx->atoms.gamescopeCtrlAppIDAtom = + XInternAtom( ctx->dpy, "GAMESCOPECTRL_BASELAYER_APPID", false ); + ctx->atoms.gamescopeCtrlWindowAtom = + XInternAtom( ctx->dpy, "GAMESCOPECTRL_BASELAYER_WINDOW", false ); + ctx->atoms.WMChangeStateAtom = + XInternAtom( ctx->dpy, "WM_CHANGE_STATE", false ); + ctx->atoms.gamescopeInputCounterAtom = + XInternAtom( ctx->dpy, "GAMESCOPE_INPUT_COUNTER", false ); + ctx->atoms.gamescopeScreenShotAtom = + XInternAtom( ctx->dpy, "GAMESCOPECTRL_REQUEST_SCREENSHOT", false ); + ctx->atoms.gamescopeDebugScreenShotAtom = XInternAtom( + ctx->dpy, "GAMESCOPECTRL_DEBUG_REQUEST_SCREENSHOT", false ); + + ctx->atoms.gamescopeFocusDisplay = + XInternAtom( ctx->dpy, "GAMESCOPE_FOCUS_DISPLAY", false ); + ctx->atoms.gamescopeMouseFocusDisplay = + XInternAtom( ctx->dpy, "GAMESCOPE_MOUSE_FOCUS_DISPLAY", false ); + ctx->atoms.gamescopeKeyboardFocusDisplay = + XInternAtom( ctx->dpy, "GAMESCOPE_KEYBOARD_FOCUS_DISPLAY", false ); + + // In nanoseconds... + ctx->atoms.gamescopeTuneableVBlankRedZone = + XInternAtom( ctx->dpy, "GAMESCOPE_TUNEABLE_VBLANK_REDZONE", false ); + ctx->atoms.gamescopeTuneableRateOfDecay = XInternAtom( + ctx->dpy, "GAMESCOPE_TUNEABLE_VBLANK_RATE_OF_DECAY_PERCENTAGE", false ); + + ctx->atoms.gamescopeScalingFilter = + XInternAtom( ctx->dpy, "GAMESCOPE_SCALING_FILTER", false ); + ctx->atoms.gamescopeFSRSharpness = + XInternAtom( ctx->dpy, "GAMESCOPE_FSR_SHARPNESS", false ); + ctx->atoms.gamescopeSharpness = + XInternAtom( ctx->dpy, "GAMESCOPE_SHARPNESS", false ); + + ctx->atoms.gamescopeXWaylandModeControl = + XInternAtom( ctx->dpy, "GAMESCOPE_XWAYLAND_MODE_CONTROL", false ); + ctx->atoms.gamescopeFPSLimit = + XInternAtom( ctx->dpy, "GAMESCOPE_FPS_LIMIT", false ); + ctx->atoms + .gamescopeDynamicRefresh[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = + XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH", false ); + ctx->atoms + .gamescopeDynamicRefresh[ gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ] = + XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH_EXTERNAL", false ); + ctx->atoms.gamescopeLowLatency = + XInternAtom( ctx->dpy, "GAMESCOPE_LOW_LATENCY", false ); + + ctx->atoms.gamescopeFSRFeedback = + XInternAtom( ctx->dpy, "GAMESCOPE_FSR_FEEDBACK", false ); + + ctx->atoms.gamescopeBlurMode = + XInternAtom( ctx->dpy, "GAMESCOPE_BLUR_MODE", false ); + ctx->atoms.gamescopeBlurRadius = + XInternAtom( ctx->dpy, "GAMESCOPE_BLUR_RADIUS", false ); + ctx->atoms.gamescopeBlurFadeDuration = + XInternAtom( ctx->dpy, "GAMESCOPE_BLUR_FADE_DURATION", false ); + + ctx->atoms.gamescopeCompositeForce = + XInternAtom( ctx->dpy, "GAMESCOPE_COMPOSITE_FORCE", false ); + ctx->atoms.gamescopeCompositeDebug = + XInternAtom( ctx->dpy, "GAMESCOPE_COMPOSITE_DEBUG", false ); + + ctx->atoms.gamescopeAllowTearing = + XInternAtom( ctx->dpy, "GAMESCOPE_ALLOW_TEARING", false ); + + ctx->atoms.gamescopeDisplayForceInternal = + XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_FORCE_INTERNAL", false ); + ctx->atoms.gamescopeDisplayModeNudge = + XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_MODE_NUDGE", false ); + + ctx->atoms.gamescopeDisplayIsExternal = + XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_IS_EXTERNAL", false ); + ctx->atoms.gamescopeDisplayModeListExternal = + XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_MODE_LIST_EXTERNAL", false ); + + ctx->atoms.gamescopeCursorVisibleFeedback = + XInternAtom( ctx->dpy, "GAMESCOPE_CURSOR_VISIBLE_FEEDBACK", false ); + + ctx->atoms.gamescopeSteamMaxHeight = + XInternAtom( ctx->dpy, "GAMESCOPE_STEAM_MAX_HEIGHT", false ); + ctx->atoms.gamescopeVRREnabled = + XInternAtom( ctx->dpy, "GAMESCOPE_VRR_ENABLED", false ); + ctx->atoms.gamescopeVRRCapable = + XInternAtom( ctx->dpy, "GAMESCOPE_VRR_CAPABLE", false ); + ctx->atoms.gamescopeVRRInUse = + XInternAtom( ctx->dpy, "GAMESCOPE_VRR_FEEDBACK", false ); + + ctx->atoms.gamescopeNewScalingFilter = + XInternAtom( ctx->dpy, "GAMESCOPE_NEW_SCALING_FILTER", false ); + ctx->atoms.gamescopeNewScalingScaler = + XInternAtom( ctx->dpy, "GAMESCOPE_NEW_SCALING_SCALER", false ); + + ctx->atoms.gamescopeDisplayEdidPath = + XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_EDID_PATH", false ); + ctx->atoms.gamescopeXwaylandServerId = + XInternAtom( ctx->dpy, "GAMESCOPE_XWAYLAND_SERVER_ID", false ); + + ctx->atoms.gamescopeDisplaySupportsHDR = + XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_SUPPORTS_HDR", false ); + ctx->atoms.gamescopeDisplayHDREnabled = + XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_HDR_ENABLED", false ); + ctx->atoms.gamescopeDebugForceHDR10Output = + XInternAtom( ctx->dpy, "GAMESCOPE_DEBUG_FORCE_HDR10_PQ_OUTPUT", false ); + ctx->atoms.gamescopeDebugForceHDRSupport = + XInternAtom( ctx->dpy, "GAMESCOPE_DEBUG_FORCE_HDR_SUPPORT", false ); + ctx->atoms.gamescopeDebugHDRHeatmap = + XInternAtom( ctx->dpy, "GAMESCOPE_DEBUG_HDR_HEATMAP", false ); + ctx->atoms.gamescopeHDROutputFeedback = + XInternAtom( ctx->dpy, "GAMESCOPE_HDR_OUTPUT_FEEDBACK", false ); + ctx->atoms.gamescopeSDROnHDRContentBrightness = XInternAtom( + ctx->dpy, "GAMESCOPE_SDR_ON_HDR_CONTENT_BRIGHTNESS", false ); + ctx->atoms.gamescopeHDRInputGain = + XInternAtom( ctx->dpy, "GAMESCOPE_HDR_INPUT_GAIN", false ); + ctx->atoms.gamescopeSDRInputGain = + XInternAtom( ctx->dpy, "GAMESCOPE_SDR_INPUT_GAIN", false ); + ctx->atoms.gamescopeHDRItmEnable = + XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_ENABLE", false ); + ctx->atoms.gamescopeHDRItmSDRNits = + XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_SDR_NITS", false ); + ctx->atoms.gamescopeHDRItmTargetNits = + XInternAtom( ctx->dpy, "GAMESCOPE_HDR_ITM_TARGET_NITS", false ); + ctx->atoms.gamescopeColorLookPQ = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_LOOK_PQ", false ); + ctx->atoms.gamescopeColorLookG22 = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_LOOK_G22", false ); + ctx->atoms.gamescopeColorOutputVirtualWhite = + XInternAtom( ctx->dpy, "GAMESCOPE_DISPLAY_VIRTUAL_WHITE", false ); + ctx->atoms.gamescopeHDRTonemapDisplayMetadata = XInternAtom( + ctx->dpy, "GAMESCOPE_HDR_TONEMAP_DISPLAY_METADATA", false ); + ctx->atoms.gamescopeHDRTonemapSourceMetadata = + XInternAtom( ctx->dpy, "GAMESCOPE_HDR_TONEMAP_SOURCE_METADATA", false ); + ctx->atoms.gamescopeHDRTonemapOperator = + XInternAtom( ctx->dpy, "GAMESCOPE_HDR_TONEMAP_OPERATOR", false ); + + ctx->atoms.gamescopeForceWindowsFullscreen = + XInternAtom( ctx->dpy, "GAMESCOPE_FORCE_WINDOWS_FULLSCREEN", false ); + + ctx->atoms.gamescopeColorLut3DOverride = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_3DLUT_OVERRIDE", false ); + ctx->atoms.gamescopeColorShaperLutOverride = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_SHAPERLUT_OVERRIDE", false ); + ctx->atoms.gamescopeColorSDRGamutWideness = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_SDR_GAMUT_WIDENESS", false ); + ctx->atoms.gamescopeColorNightMode = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_NIGHT_MODE", false ); + ctx->atoms.gamescopeColorManagementDisable = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MANAGEMENT_DISABLE", false ); + ctx->atoms.gamescopeColorAppWantsHDRFeedback = XInternAtom( + ctx->dpy, "GAMESCOPE_COLOR_APP_WANTS_HDR_FEEDBACK", false ); + ctx->atoms.gamescopeColorAppHDRMetadataFeedback = XInternAtom( + ctx->dpy, "GAMESCOPE_COLOR_APP_HDR_METADATA_FEEDBACK", false ); + ctx->atoms.gamescopeColorSliderInUse = XInternAtom( + ctx->dpy, "GAMESCOPE_COLOR_MANAGEMENT_CHANGING_HINT", false ); + ctx->atoms.gamescopeColorChromaticAdaptationMode = XInternAtom( + ctx->dpy, "GAMESCOPE_COLOR_CHROMATIC_ADAPTATION_MODE", false ); + ctx->atoms.gamescopeColorMuraCorrectionImage + [ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_IMAGE", false ); + ctx->atoms.gamescopeColorMuraCorrectionImage + [ gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ] = XInternAtom( + ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_IMAGE_EXTERNAL", false ); + ctx->atoms + .gamescopeColorMuraScale[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_SCALE", false ); + ctx->atoms + .gamescopeColorMuraScale[ gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ] = + XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_SCALE_EXTERNAL", false ); + ctx->atoms.gamescopeColorMuraCorrectionDisabled + [ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = XInternAtom( + ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_DISABLED", false ); + ctx->atoms.gamescopeColorMuraCorrectionDisabled + [ gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ] = XInternAtom( + ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_DISABLED_EXTERNAL", false ); + + ctx->atoms.gamescopeCreateXWaylandServer = + XInternAtom( ctx->dpy, "GAMESCOPE_CREATE_XWAYLAND_SERVER", false ); + ctx->atoms.gamescopeCreateXWaylandServerFeedback = XInternAtom( + ctx->dpy, "GAMESCOPE_CREATE_XWAYLAND_SERVER_FEEDBACK", false ); + ctx->atoms.gamescopeDestroyXWaylandServer = + XInternAtom( ctx->dpy, "GAMESCOPE_DESTROY_XWAYLAND_SERVER", false ); + + ctx->atoms.gamescopeReshadeEffect = + XInternAtom( ctx->dpy, "GAMESCOPE_RESHADE_EFFECT", false ); + ctx->atoms.gamescopeReshadeTechniqueIdx = + XInternAtom( ctx->dpy, "GAMESCOPE_RESHADE_TECHNIQUE_IDX", false ); + + ctx->atoms.gamescopeDisplayRefreshRateFeedback = XInternAtom( + ctx->dpy, "GAMESCOPE_DISPLAY_REFRESH_RATE_FEEDBACK", false ); + ctx->atoms.gamescopeDisplayDynamicRefreshBasedOnGamePresence = XInternAtom( + ctx->dpy, + "GAMESCOPE_DISPLAY_DYNAMIC_REFRESH_BASED_ON_GAME_PRESENCE", + false ); + + ctx->atoms.gamescopeMainSteamVROverlay = + XInternAtom( ctx->dpy, "GAMESCOPE_MAIN_STEAMVR_OVERLAY", false ); + ctx->atoms.steamosTouchPointerEmulation = + XInternAtom( ctx->dpy, "_STEAMOS_TOUCH_POINTER_EMULATION", false ); + + ctx->atoms.wineHwndStyle = + XInternAtom( ctx->dpy, "_WINE_HWND_STYLE", false ); + ctx->atoms.wineHwndStyleEx = + XInternAtom( ctx->dpy, "_WINE_HWND_EXSTYLE", false ); + + ctx->atoms.clipboard = XInternAtom( ctx->dpy, "CLIPBOARD", false ); + ctx->atoms.primarySelection = XInternAtom( ctx->dpy, "PRIMARY", false ); + ctx->atoms.targets = XInternAtom( ctx->dpy, "TARGETS", false ); + + ctx->atoms.wm_protocols = XInternAtom( ctx->dpy, "WM_PROTOCOLS", false ); + ctx->atoms.wm_delete_window = + XInternAtom( ctx->dpy, "WM_DELETE_WINDOW", false ); + + ctx->root_width = DisplayWidth( ctx->dpy, ctx->scr ); + ctx->root_height = DisplayHeight( ctx->dpy, ctx->scr ); + + ctx->allDamage = None; + ctx->clipChanged = true; + + XChangeProperty( + ctx->dpy, + ctx->root, + ctx->atoms.gamescopeXwaylandServerId, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&serverId, + 1 ); + + uint32_t unPid = getpid( ); + XChangeProperty( + ctx->dpy, + ctx->root, + ctx->atoms.gamescopePid, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&unPid, + 1 ); + + uint32_t unVROverlayForwardingSupported = + GetBackend( )->SupportsVROverlayForwarding( ) ? 2 : 0; + XChangeProperty( + ctx->dpy, + ctx->root, + ctx->atoms.gamescopeVROverlayForwarding, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&unVROverlayForwardingSupported, + 1 ); + + XGrabServer( ctx->dpy ); + + XCompositeRedirectSubwindows( + ctx->dpy, ctx->root, CompositeRedirectManual ); + + Window root_return, parent_return; + Window *children; + unsigned int nchildren; + + XSelectInput( + ctx->dpy, + ctx->root, + SubstructureNotifyMask | ExposureMask | StructureNotifyMask | + SubstructureRedirectMask | FocusChangeMask | PointerMotionMask | + LeaveWindowMask | PropertyChangeMask ); + XShapeSelectInput( ctx->dpy, ctx->root, ShapeNotifyMask ); + XFixesSelectCursorInput( + ctx->dpy, ctx->root, XFixesDisplayCursorNotifyMask ); + XFixesSelectSelectionInput( + ctx->dpy, + ctx->root, + ctx->atoms.clipboard, + XFixesSetSelectionOwnerNotifyMask ); + XFixesSelectSelectionInput( + ctx->dpy, + ctx->root, + ctx->atoms.primarySelection, + XFixesSetSelectionOwnerNotifyMask ); + XQueryTree( + ctx->dpy, + ctx->root, + &root_return, + &parent_return, + &children, + &nchildren ); + for ( uint32_t i = 0; i < nchildren; i++ ) + add_win( ctx, children[ i ], i ? children[ i - 1 ] : None, 0 ); + XFree( children ); + + XUngrabServer( ctx->dpy ); + + XF86VidModeLockModeSwitch( ctx->dpy, ctx->scr, true ); + + ctx->cursor = std::make_unique( ctx ); + if ( g_customCursorPath ) + { + if ( !load_mouse_cursor( + ctx->cursor.get( ), + g_customCursorPath, + g_customCursorHotspotX, + g_customCursorHotspotY ) ) + xwm_log.errorf( + "Failed to load mouse cursor: %s", g_customCursorPath ); + } + else + { + if ( std::shared_ptr pHostCursor = + gamescope::GetX11HostCursor( ) ) + { + ctx->cursor->setCursorImage( + reinterpret_cast( pHostCursor->pPixels.data( ) ), + pHostCursor->uWidth, + pHostCursor->uHeight, + pHostCursor->uXHotspot, + pHostCursor->uYHotspot ); + } + else + { + xwm_log.infof( + "Embedded, no cursor set. Using left_ptr by default." ); + if ( !ctx->cursor->setCursorImageByName( "left_ptr" ) ) + xwm_log.errorf( "Failed to load mouse cursor: left_ptr" ); + } + } + + ctx->cursor->undirty( ); + + XFlush( ctx->dpy ); +} + +void update_vrr_atoms( + xwayland_ctx_t *root_ctx, bool force, bool *needs_flush = nullptr ) +{ + bool capable = GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->SupportsVRR( ); + if ( capable != g_bVRRCapable_CachedValue || force ) + { + uint32_t capable_value = capable ? 1 : 0; + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeVRRCapable, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&capable_value, + 1 ); + g_bVRRCapable_CachedValue = capable; + if ( needs_flush ) *needs_flush = true; + } + + bool HDR = GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->SupportsHDR( ); + if ( HDR != g_bSupportsHDR_CachedValue || force ) + { + uint32_t hdr_value = HDR ? 1 : 0; + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeDisplaySupportsHDR, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&hdr_value, + 1 ); + g_bSupportsHDR_CachedValue = HDR; + if ( needs_flush ) *needs_flush = true; + } + + bool in_use = GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->IsVRRActive( ); + if ( in_use != g_bVRRInUse_CachedValue || force ) + { + uint32_t in_use_value = in_use ? 1 : 0; + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeVRRInUse, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&in_use_value, + 1 ); + g_bVRRInUse_CachedValue = in_use; + if ( needs_flush ) *needs_flush = true; + } + + if ( g_nOutputRefresh != g_nCurrentRefreshRate_CachedValue || force ) + { + int32_t nRefresh = gamescope::ConvertmHzToHz( g_nOutputRefresh ); + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeDisplayRefreshRateFeedback, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&nRefresh, + 1 ); + g_nCurrentRefreshRate_CachedValue = g_nOutputRefresh; + if ( needs_flush ) *needs_flush = true; + } + + // Don't update this in-sync with DRM vrr usage. + // Keep this as a preference, starting with off. + if ( force ) + { + bool wants_vrr = cv_adaptive_sync; + uint32_t enabled_value = wants_vrr ? 1 : 0; + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeVRREnabled, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&enabled_value, + 1 ); + if ( needs_flush ) *needs_flush = true; + } +} + +void update_mode_atoms( xwayland_ctx_t *root_ctx, bool *needs_flush = nullptr ) +{ + if ( needs_flush ) *needs_flush = true; + + if ( GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->GetScreenType( ) == + gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + { + XDeleteProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeDisplayModeListExternal ); + + uint32_t zero = 0; + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeDisplayIsExternal, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&zero, + 1 ); + return; + } + + if ( !GetBackend( )->GetCurrentConnector( ) ) return; + + auto connectorModes = GetBackend( )->GetCurrentConnector( )->GetModes( ); + + char modes[ 4096 ] = ""; + int remaining_size = sizeof( modes ) - 1; + int len = 0; + for ( int i = 0; remaining_size > 0 && i < ( int )connectorModes.size( ); + i++ ) + { + const auto &mode = connectorModes[ i ]; + int mode_len = snprintf( + &modes[ len ], + remaining_size, + "%s%dx%d@%d", + i == 0 ? "" : " ", + int( mode.uWidth ), + int( mode.uHeight ), + int( mode.uRefresh ) ); + len += mode_len; + remaining_size -= mode_len; + } + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeDisplayModeListExternal, + XA_STRING, + 8, + PropModeReplace, + ( unsigned char * )modes, + strlen( modes ) + 1 ); + + uint32_t one = 1; + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeDisplayIsExternal, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&one, + 1 ); +} - lastPublishedInputCounter = inputCounter; - flush_root = true; - } +extern int g_nPreferredOutputWidth; +extern int g_nPreferredOutputHeight; - if ( g_bFSRActive != g_bWasFSRActive ) - { - uint32_t active = g_bFSRActive ? 1 : 0; - XChangeProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeFSRFeedback, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&active, 1 ); +static bool g_bWasFSRActive = false; - g_bWasFSRActive = g_bFSRActive; - flush_root = true; - } +bool g_bAppWantsHDRCached = false; - bool bBackendJustInitted = GetBackend()->NewlyInitted(); +void steamcompmgr_check_xdg( bool vblank, uint64_t vblank_idx ) +{ + if ( wlserver_xdg_dirty( ) ) + { + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->focusWindow && + pFocus->focusWindow->type == steamcompmgr_win_type_t::XDG ) + pFocus->focusWindow = nullptr; + if ( pFocus->inputFocusWindow && pFocus->inputFocusWindow->type == + steamcompmgr_win_type_t::XDG ) + pFocus->inputFocusWindow = nullptr; + if ( pFocus->overlayWindow && + pFocus->overlayWindow->type == steamcompmgr_win_type_t::XDG ) + pFocus->overlayWindow = nullptr; + if ( pFocus->notificationWindow && + pFocus->notificationWindow->type == + steamcompmgr_win_type_t::XDG ) + pFocus->notificationWindow = nullptr; + if ( pFocus->overrideWindow && + pFocus->overrideWindow->type == steamcompmgr_win_type_t::XDG ) + pFocus->overrideWindow = nullptr; + if ( pFocus->fadeWindow && + pFocus->fadeWindow->type == steamcompmgr_win_type_t::XDG ) + pFocus->fadeWindow = nullptr; + } + g_steamcompmgr_xdg_wins = wlserver_get_xdg_shell_windows( ); + MakeFocusDirty( ); + } + + handle_done_commits_xdg( vblank, vblank_idx ); + + // When we have observed both a complete commit and a VBlank, we should + // request a new frame. + if ( vblank ) + { + for ( const auto &xdg_win : g_steamcompmgr_xdg_wins ) + { + steamcompmgr_flush_frame_done( xdg_win.get( ) ); + } + + wlserver_lock( ); + handle_presented_xdg( ); + wlserver_unlock( ); + } + + check_new_xdg_res( ); +} + +void update_edid_prop( ) +{ + const char *filename = gamescope::GetPatchedEdidPath( ); + if ( !filename ) return; + + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); i++ ) + { + XTextProperty text_property = { + .value = ( unsigned char * )filename, + .encoding = server->ctx->atoms.utf8StringAtom, + .format = 8, + .nitems = strlen( filename ), + }; + + XSetTextProperty( + server->ctx->dpy, + server->ctx->root, + &text_property, + server->ctx->atoms.gamescopeDisplayEdidPath ); + } +} - static gamescope::VirtualConnectorStrategy s_eLastVirtualConnectorStrategy = gamescope::cv_backend_virtual_connector_strategy; - gamescope::VirtualConnectorStrategy eVirtualConnectorStrategy = gamescope::cv_backend_virtual_connector_strategy; +extern bool g_bLaunchMangoapp; - if ( eVirtualConnectorStrategy != s_eLastVirtualConnectorStrategy || bBackendJustInitted ) - { - // If our virtual connector strategy changes, clear out our virtual connector - // global focuses. - g_VirtualConnectorFocuses.clear(); - s_eLastVirtualConnectorStrategy = eVirtualConnectorStrategy; +extern void ShutdownGamescope( ); - xwm_log.infof( "Late init of virtual connector stuff." ); +gamescope::ConVar cv_shutdown_on_primary_child_death( + "shutdown_on_primary_child_death", + true, + "Should gamescope shutdown when the primary application launched in it was " + "shut down?" ); +static LogScope s_LaunchLogScope( "launch" ); - // misyl: Make the virtual connector up-front if we are in a single-output mode. - // So we don't delay in getting display/output info to the game - static constexpr uint64_t k_unSingleOutputVirtualConnectorKey = 0; +static std::vector s_uRelativeMouseFilteredAppids; +static gamescope::ConVar cv_mouse_relative_filter_appids( + "mouse_relative_filter_appids", + "8400" /* Geometry Wars: Retro Evolved */, + "Comma separated appids to filter out using relative mouse mode for.", + []( gamescope::ConVar &cvar ) + { + std::vector sFilterAppids = + gamescope::Split( cvar, "," ); + std::vector uFilterAppids; + uFilterAppids.reserve( sFilterAppids.size( ) ); + for ( auto &sFilterAppid : sFilterAppids ) + { + std::optional ouFilterAppid = + gamescope::Parse( sFilterAppid ); + uFilterAppids.push_back( *ouFilterAppid ); + } + + s_uRelativeMouseFilteredAppids = std::move( uFilterAppids ); + }, + true ); - g_VirtualConnectorFocuses[ k_unSingleOutputVirtualConnectorKey ] = global_focus_t - { - .ulVirtualFocusKey = k_unSingleOutputVirtualConnectorKey, - .pVirtualConnector = GetBackend()->UsesVirtualConnectors() ? GetBackend()->CreateVirtualConnector( k_unSingleOutputVirtualConnectorKey ) : nullptr, - }; +void LaunchNestedChildren( char **ppPrimaryChildArgv ) +{ + std::string sNewPreload; + { + const char *pszCurrentPreload = getenv( "LD_PRELOAD" ); + if ( pszCurrentPreload && *pszCurrentPreload ) + { + // Remove gameoverlayrenderer.so from the child if Gamescope + // is running with a window + Vulkan swapchain (eg. SDL2 backend) + if ( GetBackend( )->UsesVulkanSwapchain( ) ) + { + std::vector svLibraries = + gamescope::Split( pszCurrentPreload, " :" ); + std::erase_if( + svLibraries, + []( std::string_view svPreload ) + { + return svPreload.find( "gameoverlayrenderer.so" ) != + std::string_view::npos; + } ); + + bool bFirst = true; + for ( std::string_view svLibrary : svLibraries ) + { + if ( !bFirst ) { sNewPreload.append( ":" ); } + bFirst = false; + sNewPreload.append( svLibrary ); + } + } + else + { + sNewPreload = pszCurrentPreload; + } + } + } + + // We could just run this inside the child process, + // but we might as well just run it here at this point. + // and affect all future child processes, without needing + // a pre-amble inside of them. + { + if ( !sNewPreload.empty( ) ) + setenv( "LD_PRELOAD", sNewPreload.c_str( ), 1 ); + else + unsetenv( "LD_PRELOAD" ); + + unsetenv( "ENABLE_VKBASALT" ); + // Enable Gamescope WSI by default for nested. + setenv( "ENABLE_GAMESCOPE_WSI", "1", 0 ); + + // Unset this to avoid it leaking to Proton apps, etc. + unsetenv( "SDL_VIDEODRIVER" ); + // SDL3... + unsetenv( "SDL_VIDEO_DRIVER" ); + } + + // Gamescope itself does not set itself as a subreaper anymore. + // It launches direct children that do, and manage that they kill themselves + // when Gamescope dies. + // This allows us to launch stuff alongside Gamescope if we ever wanted -- + // rather than being under it. (eg. if we wanted a drm janitor or + // something.) + + if ( ppPrimaryChildArgv && *ppPrimaryChildArgv ) + { + pid_t nPrimaryChildPid = gamescope::Process::SpawnProcessInWatchdog( + ppPrimaryChildArgv, false ); + + std::thread waitThread( + [ nPrimaryChildPid ]( ) + { + pthread_setname_np( pthread_self( ), "gamescope-wait" ); + + gamescope::Process::WaitForChild( nPrimaryChildPid ); + s_LaunchLogScope.infof( "Primary child shut down!" ); + + if ( cv_shutdown_on_primary_child_death ) ShutdownGamescope( ); + } ); + waitThread.detach( ); + } + + if ( g_bLaunchMangoapp ) + { + char *ppMangoappArgv[] = { ( char * )"mangoapp", NULL }; + gamescope::Process::SpawnProcessInWatchdog( ppMangoappArgv, true ); + } +} + +static gamescope::CTimerFunction g_FPSLimitVRRTimer{ + [] { g_FPSLimitVRRTimer.DisarmTimer( ); } +}; - hasRepaint = true; - } +void steamcompmgr_main( int argc, char **argv ) +{ + int readyPipeFD = -1; + + // Reset getopt() state + optind = 1; + + int o; + int opt_index = -1; + bool bForceWindowsFullscreen = false; + while ( ( o = getopt_long( + argc, + argv, + gamescope_optstring, + gamescope_options, + &opt_index ) ) != -1 ) + { + const char *opt_name; + switch ( o ) + { + case 'R': + readyPipeFD = open( optarg, O_WRONLY | O_CLOEXEC ); + break; + case 'T': + statsThreadPath = optarg; + { + statsThreadRun = true; + std::thread statsThreads( statsThreadMain ); + statsThreads.detach( ); + } + break; + case 'C': + cursorHideTime = uint64_t( atoi( optarg ) ) * 1'000'000ul; + break; + case 'v': + drawDebugInfo = true; + break; + case 'c': + cv_composite_force = true; + break; + case 'x': + useXRes = false; + break; + case 0: // long options without a short option + opt_name = gamescope_options[ opt_index ].name; + if ( strcmp( opt_name, "debug-focus" ) == 0 ) + { + debugFocus = true; + } + else if ( strcmp( opt_name, "synchronous-x11" ) == 0 ) + { + synchronize = true; + } + else if ( strcmp( opt_name, "debug-events" ) == 0 ) + { + debugEvents = true; + } + else if ( strcmp( opt_name, "cursor" ) == 0 ) + { + g_customCursorPath = optarg; + } + else if ( strcmp( opt_name, "cursor-hotspot" ) == 0 ) + { + sscanf( + optarg, + "%d,%d", + &g_customCursorHotspotX, + &g_customCursorHotspotY ); + } + else if ( strcmp( opt_name, "fade-out-duration" ) == 0 ) + { + g_FadeOutDuration = atoi( optarg ); + } + else if ( strcmp( opt_name, "force-windows-fullscreen" ) == 0 ) + { + bForceWindowsFullscreen = true; + } + else if ( + strcmp( opt_name, "hdr-enabled" ) == 0 || + strcmp( opt_name, "hdr-enable" ) == 0 ) + { + cv_hdr_enabled = true; + } + else if ( strcmp( opt_name, "hdr-debug-force-support" ) == 0 ) + { + g_bForceHDRSupportDebug = true; + } + else if ( strcmp( opt_name, "hdr-debug-force-output" ) == 0 ) + { + g_bForceHDR10OutputDebug = true; + } + else if ( + strcmp( opt_name, "hdr-itm-enabled" ) == 0 || + strcmp( opt_name, "hdr-itm-enable" ) == 0 ) + { + g_bHDRItmEnable = true; + } + else if ( strcmp( opt_name, "sdr-gamut-wideness" ) == 0 ) + { + g_ColorMgmt.pending.sdrGamutWideness = atof( optarg ); + } + else if ( strcmp( opt_name, "hdr-sdr-content-nits" ) == 0 ) + { + g_ColorMgmt.pending.flSDROnHDRBrightness = atof( optarg ); + } + else if ( strcmp( opt_name, "hdr-itm-sdr-nits" ) == 0 ) + { + g_flHDRItmSdrNits = atof( optarg ); + } + else if ( strcmp( opt_name, "hdr-itm-target-nits" ) == 0 ) + { + g_flHDRItmTargetNits = atof( optarg ); + } + else if ( strcmp( opt_name, "framerate-limit" ) == 0 ) + { + g_nSteamCompMgrTargetFPS = atoi( optarg ); + } + else if ( strcmp( opt_name, "reshade-effect" ) == 0 ) + { + g_reshade_effect = optarg; + } + else if ( strcmp( opt_name, "reshade-technique-idx" ) == 0 ) + { + g_reshade_technique_idx = atoi( optarg ); + } + else if ( strcmp( opt_name, "mura-map" ) == 0 ) + { + set_mura_overlay( optarg ); + } + break; + case '?': + assert( false ); // unreachable + } + } + + int subCommandArg = -1; + if ( optind < argc ) { subCommandArg = optind; } + + const char *pchEnableVkBasalt = getenv( "ENABLE_VKBASALT" ); + if ( pchEnableVkBasalt != nullptr && pchEnableVkBasalt[ 0 ] == '1' ) + { + cv_composite_force = true; + } + + // Enable color mgmt by default. + g_ColorMgmt.pending.enabled = true; + + currentOutputWidth = g_nPreferredOutputWidth; + currentOutputHeight = g_nPreferredOutputHeight; + + init_runtime_info( ); + + std::unique_lock xwayland_server_guard( + g_SteamCompMgrXWaylandServerMutex ); + + // Initialize any xwayland ctxs we have + { + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + init_xwayland_ctx( i, server ); + } + + gamescope_xwayland_server_t *root_server = + wlserver_get_xwayland_server( 0 ); + xwayland_ctx_t *root_ctx = root_server->ctx.get( ); + + gamesRunningCount = get_prop( + root_ctx, root_ctx->root, root_ctx->atoms.gamesRunningAtom, 0 ); + overscanScaleRatio = get_prop( + root_ctx, + root_ctx->root, + root_ctx->atoms.screenScaleAtom, + 0xFFFFFFFF ) / + ( double )0xFFFFFFFF; + zoomScaleRatio = + get_prop( + root_ctx, root_ctx->root, root_ctx->atoms.screenZoomAtom, 0xFFFF ) / + ( double )0xFFFF; + + globalScaleRatio = overscanScaleRatio * zoomScaleRatio; + + static constexpr uint64_t k_unSingleOutputVirtualConnectorKey = 0; + g_VirtualConnectorFocuses[ k_unSingleOutputVirtualConnectorKey ] = + global_focus_t{ + .ulVirtualFocusKey = k_unSingleOutputVirtualConnectorKey, + .pVirtualConnector = GetBackend( )->UsesVirtualConnectors( ) + ? GetBackend( )->CreateVirtualConnector( + k_unSingleOutputVirtualConnectorKey ) + : nullptr, + }; + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->IsDirty( ) ) determine_and_apply_focus( pFocus ); + } + + if ( readyPipeFD != -1 ) + { + dprintf( + readyPipeFD, + "%s %s\n", + root_ctx->xwayland_server->get_nested_display_name( ), + wlserver_get_wl_display_name( ) ); + close( readyPipeFD ); + readyPipeFD = -1; + } + + g_SteamCompMgrWaiter.AddWaitable( &GetVBlankTimer( ) ); + g_SteamCompMgrWaiter.AddWaitable( &g_FPSLimitVRRTimer ); + GetVBlankTimer( ).ArmNextVBlank( true ); + + { + gamescope_xwayland_server_t *pServer = NULL; + for ( size_t i = 0; ( pServer = wlserver_get_xwayland_server( i ) ); + i++ ) + { + xwayland_ctx_t *pXWaylandCtx = pServer->ctx.get( ); + g_SteamCompMgrWaiter.AddWaitable( pXWaylandCtx ); + + pServer->ctx->force_windows_fullscreen = bForceWindowsFullscreen; + } + } + + update_vrr_atoms( root_ctx, true ); + update_mode_atoms( root_ctx ); + XFlush( root_ctx->dpy ); + + if ( !GetBackend( )->PostInit( ) ) return; + + if ( g_pVROverlayKey ) + { + set_string_prop( + root_ctx, + root_ctx->atoms.gamescopeMainSteamVROverlay, + *g_pVROverlayKey ); + } + + update_edid_prop( ); + + update_screenshot_color_mgmt( ); + + LaunchNestedChildren( + subCommandArg >= 0 ? &argv[ subCommandArg ] : nullptr ); + + // Transpose to get this 3x3 matrix into the right state for applying as a + // 3x4 on DRM + the Vulkan side. ie. color.rgb = color.rgba * + // u_ctm[offsetLayerIdx]; + s_scRGB709To2020Matrix = GetBackend( )->CreateBackendBlob( + glm::mat3x4( glm::transpose( k_2020_from_709 ) ) ); + + FILE *sysfs_caps = fopen( "/sys/fs/cgroup/dmem.capacity", "r" ); + if ( sysfs_caps != nullptr ) + { + char *line = NULL; + size_t size = 0; + while ( getline( &line, &size, sysfs_caps ) >= 0 ) + { + char *capacity = strstr( line, " " ); + if ( capacity ) ++capacity; + else + continue; + uint64_t vramSize = strtoull( capacity, NULL, 10 ); + std::string idString = std::string( line, ( capacity - 1 ) - line ); + g_vramCapacities.emplace( idString, vramSize ); + } + } + + for ( ;; ) + { + { + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + { + assert( server->ctx ); + if ( server->ctx->HasQueuedEvents( ) ) server->ctx->Dispatch( ); + } + } + + g_SteamCompMgrWaiter.PollEvents( ); + + bool vblank = false; + if ( std::optional pendingVBlank = + GetVBlankTimer( ).ProcessVBlank( ) ) + { + g_SteamCompMgrVBlankTime = *pendingVBlank; + vblank = true; + } + + if ( g_bRun == false ) { break; } + + // If this is from the timer or not. + // Consider this to also be "is this vblank, the fastest refresh cycle + // after our last commit?" as a question. + const bool bIsVBlankFromTimer = vblank; + + // We can always vblank if VRR. + const bool bVRR = GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->IsVRRActive( ); + if ( bVRR ) vblank = true; + + bool flush_root = false; + + if ( inputCounter != lastPublishedInputCounter ) + { + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeInputCounterAtom, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&inputCounter, + 1 ); + + lastPublishedInputCounter = inputCounter; + flush_root = true; + } + + if ( g_bFSRActive != g_bWasFSRActive ) + { + uint32_t active = g_bFSRActive ? 1 : 0; + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeFSRFeedback, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&active, + 1 ); + + g_bWasFSRActive = g_bFSRActive; + flush_root = true; + } + + bool bBackendJustInitted = GetBackend( )->NewlyInitted( ); + + static gamescope::VirtualConnectorStrategy + s_eLastVirtualConnectorStrategy = + gamescope::cv_backend_virtual_connector_strategy; + gamescope::VirtualConnectorStrategy eVirtualConnectorStrategy = + gamescope::cv_backend_virtual_connector_strategy; + + if ( eVirtualConnectorStrategy != s_eLastVirtualConnectorStrategy || + bBackendJustInitted ) + { + // If our virtual connector strategy changes, clear out our virtual + // connector global focuses. + g_VirtualConnectorFocuses.clear( ); + s_eLastVirtualConnectorStrategy = eVirtualConnectorStrategy; + + xwm_log.infof( "Late init of virtual connector stuff." ); + + // misyl: Make the virtual connector up-front if we are in a + // single-output mode. So we don't delay in getting display/output + // info to the game + static constexpr uint64_t k_unSingleOutputVirtualConnectorKey = 0; + + g_VirtualConnectorFocuses[ k_unSingleOutputVirtualConnectorKey ] = + global_focus_t{ + .ulVirtualFocusKey = k_unSingleOutputVirtualConnectorKey, + .pVirtualConnector = + GetBackend( )->UsesVirtualConnectors( ) + ? GetBackend( )->CreateVirtualConnector( + k_unSingleOutputVirtualConnectorKey ) + : nullptr, + }; + + hasRepaint = true; + } #if 0 bool bDirtyFocuses = false; @@ -8581,636 +10075,750 @@ steamcompmgr_main(int argc, char **argv) } #endif - std::vector keysToClose; - { - std::unique_lock lock{ s_KeysToCloseMutex }; - keysToClose = std::move( s_KeysToClose ); - s_KeysToClose.clear(); - } - - // XXX: Need to look into why this doesn't work. - // if ( bDirtyFocuses ) - { - // TODO(misyl): Improve this situation, it's kind of a mess. - // We could/should make this event driven rather than solving - // per-frame. - - if ( !gamescope::VirtualConnectorIsSingleOutput() ) - { - std::vector newKeys; - - auto focusWindows = GetGlobalPossibleFocusWindows(); - for ( steamcompmgr_win_t *pWindow : focusWindows ) - { - // Exclude windows that are useless (1x1), or override redirect windows - if ( win_is_useless( pWindow ) || - ( pWindow->type == steamcompmgr_win_type_t::XWAYLAND && pWindow->xwayland().a.override_redirect && - !pWindow->isSteamLegacyBigPicture ) ) // bootstrapper is override redirect :( - { - continue; - } - - gamescope::VirtualConnectorKey_t ulKey = pWindow->GetVirtualConnectorKey( eVirtualConnectorStrategy ); - - if ( gamescope::Algorithm::Contains( keysToClose, ulKey ) ) - { - if ( pWindow->type == steamcompmgr_win_type_t::XWAYLAND ) - { - XEvent event = {0}; - event.xclient.type = ClientMessage; - event.xclient.window = pWindow->xwayland().id; - event.xclient.message_type = pWindow->xwayland().ctx->atoms.wm_protocols; - event.xclient.format = 32; - event.xclient.data.l[0] = pWindow->xwayland().ctx->atoms.wm_delete_window; - event.xclient.data.l[1] = CurrentTime; - - XSendEvent(pWindow->xwayland().ctx->dpy, pWindow->xwayland().id, False, NoEventMask, &event); - } - else - { - xwm_log.errorf( "Closing Wayland windows not supported yet." ); - } - } - - if ( !gamescope::Algorithm::Contains( newKeys, ulKey ) ) - newKeys.emplace_back( ulKey ); - } - std::sort( newKeys.begin(), newKeys.end() ); - - std::vector oldKeys; - for ( const auto &iter : g_VirtualConnectorFocuses ) - oldKeys.emplace_back( iter.first ); - std::sort( oldKeys.begin(), oldKeys.end() ); - - std::vector diffKeys; - - std::set_symmetric_difference(oldKeys.begin(), oldKeys.end(), - newKeys.begin(), newKeys.end(), - std::back_inserter(diffKeys), - [](auto& a, auto& b) { return a < b; }); - - for ( gamescope::VirtualConnectorKey_t ulKey : diffKeys ) - { - bool bIsSteam = gamescope::VirtualConnectorKeyIsSteam( ulKey ); - bool bIsBaseKey = ulKey == 0; - - if ( gamescope::Algorithm::Contains( newKeys, ulKey ) ) - { - g_VirtualConnectorFocuses[ulKey] = global_focus_t - { - .ulVirtualFocusKey = ulKey, - .pVirtualConnector = GetBackend()->UsesVirtualConnectors() ? GetBackend()->CreateVirtualConnector( ulKey ) : nullptr, - }; - } - else if ( !bIsSteam && !bIsBaseKey ) // Never remove Steam's virtual connector or the 0th connector. - { - g_VirtualConnectorFocuses.erase( ulKey ); - } - } - } - - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - if ( pFocus->IsDirty() ) - { - determine_and_apply_focus( pFocus ); - hasRepaint = true; - } - } - } - - // If our DRM state is out-of-date, refresh it. This might update - // the output size. - if ( GetBackend()->PollState() ) - { - hasRepaint = true; - - update_mode_atoms(root_ctx, &flush_root); - } - - g_uCompositeDebug = cv_composite_debug; - - g_bOutputHDREnabled = (g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug) && cv_hdr_enabled; - - // Pick our width/height for this potential frame, regardless of how it might change later - // At some point we might even add proper locking so we get real updates atomically instead - // of whatever jumble of races the below might cause over a couple of frames - if ( currentOutputWidth != g_nOutputWidth || - currentOutputHeight != g_nOutputHeight || - currentOutputRefresh != g_nOutputRefresh || - currentHDROutput != g_bOutputHDREnabled || - currentHDRForce != g_bForceHDRSupportDebug ) - { - if ( g_nXWaylandCount > 1 ) - { - g_nNestedHeight = ( g_nNestedWidth * g_nOutputHeight ) / g_nOutputWidth; - wlserver_lock(); - // Update only Steam, the root ctx, with the new output size for now - wlserver_set_xwayland_server_mode( 0, g_nOutputWidth, g_nOutputHeight, g_nOutputRefresh ); - wlserver_unlock(); - } - - // XXX(JoshA): Remake this. It sucks. - if ( GetBackend()->UsesVulkanSwapchain() ) - { - vulkan_remake_swapchain(); - - while ( !acquire_next_image() ) - vulkan_remake_swapchain(); - } - else - { - vulkan_remake_output_images(); - } - - - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - uint32_t hdr_value = ( g_bOutputHDREnabled || g_bForceHDRSupportDebug ) ? 1 : 0; - XChangeProperty(server->ctx->dpy, server->ctx->root, server->ctx->atoms.gamescopeHDROutputFeedback, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&hdr_value, 1 ); - - server->ctx->cursor->setDirty(); - - if (server->ctx.get() == root_ctx) - { - flush_root = true; - } - else - { - XFlush(server->ctx->dpy); - } - } - } - - currentOutputWidth = g_nOutputWidth; - currentOutputHeight = g_nOutputHeight; - currentOutputRefresh = g_nOutputRefresh; - currentHDROutput = g_bOutputHDREnabled; - currentHDRForce = g_bForceHDRSupportDebug; + std::vector keysToClose; + { + std::unique_lock lock{ s_KeysToCloseMutex }; + keysToClose = std::move( s_KeysToClose ); + s_KeysToClose.clear( ); + } + + // XXX: Need to look into why this doesn't work. + // if ( bDirtyFocuses ) + { + // TODO(misyl): Improve this situation, it's kind of a mess. + // We could/should make this event driven rather than solving + // per-frame. + + if ( !gamescope::VirtualConnectorIsSingleOutput( ) ) + { + std::vector newKeys; + + auto focusWindows = GetGlobalPossibleFocusWindows( ); + for ( steamcompmgr_win_t *pWindow : focusWindows ) + { + // Exclude windows that are useless (1x1), or override + // redirect windows + if ( win_is_useless( pWindow ) || + ( pWindow->type == steamcompmgr_win_type_t::XWAYLAND && + pWindow->xwayland( ).a.override_redirect && + !pWindow->isSteamLegacyBigPicture ) ) // bootstrapper + // is override + // redirect :( + { + continue; + } + + gamescope::VirtualConnectorKey_t ulKey = + pWindow->GetVirtualConnectorKey( + eVirtualConnectorStrategy ); + + if ( gamescope::Algorithm::Contains( keysToClose, ulKey ) ) + { + if ( pWindow->type == + steamcompmgr_win_type_t::XWAYLAND ) + { + XEvent event = { 0 }; + event.xclient.type = ClientMessage; + event.xclient.window = pWindow->xwayland( ).id; + event.xclient.message_type = + pWindow->xwayland( ).ctx->atoms.wm_protocols; + event.xclient.format = 32; + event.xclient.data.l[ 0 ] = + pWindow->xwayland( ) + .ctx->atoms.wm_delete_window; + event.xclient.data.l[ 1 ] = CurrentTime; + + XSendEvent( + pWindow->xwayland( ).ctx->dpy, + pWindow->xwayland( ).id, + False, + NoEventMask, + &event ); + } + else + { + xwm_log.errorf( + "Closing Wayland windows not supported yet." ); + } + } + + if ( !gamescope::Algorithm::Contains( newKeys, ulKey ) ) + newKeys.emplace_back( ulKey ); + } + std::sort( newKeys.begin( ), newKeys.end( ) ); + + std::vector oldKeys; + for ( const auto &iter : g_VirtualConnectorFocuses ) + oldKeys.emplace_back( iter.first ); + std::sort( oldKeys.begin( ), oldKeys.end( ) ); + + std::vector diffKeys; + + std::set_symmetric_difference( + oldKeys.begin( ), + oldKeys.end( ), + newKeys.begin( ), + newKeys.end( ), + std::back_inserter( diffKeys ), + []( auto &a, auto &b ) { return a < b; } ); + + for ( gamescope::VirtualConnectorKey_t ulKey : diffKeys ) + { + bool bIsSteam = + gamescope::VirtualConnectorKeyIsSteam( ulKey ); + bool bIsBaseKey = ulKey == 0; + + if ( gamescope::Algorithm::Contains( newKeys, ulKey ) ) + { + g_VirtualConnectorFocuses[ ulKey ] = global_focus_t{ + .ulVirtualFocusKey = ulKey, + .pVirtualConnector = + GetBackend( )->UsesVirtualConnectors( ) + ? GetBackend( )->CreateVirtualConnector( + ulKey ) + : nullptr, + }; + } + else if ( !bIsSteam && !bIsBaseKey ) // Never remove Steam's + // virtual connector or + // the 0th connector. + { + g_VirtualConnectorFocuses.erase( ulKey ); + } + } + } + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->IsDirty( ) ) + { + determine_and_apply_focus( pFocus ); + hasRepaint = true; + } + } + } + + // If our DRM state is out-of-date, refresh it. This might update + // the output size. + if ( GetBackend( )->PollState( ) ) + { + hasRepaint = true; + + update_mode_atoms( root_ctx, &flush_root ); + } + + g_uCompositeDebug = cv_composite_debug; + + g_bOutputHDREnabled = + ( g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug ) && + cv_hdr_enabled; + + // Pick our width/height for this potential frame, regardless of how it + // might change later At some point we might even add proper locking so + // we get real updates atomically instead of whatever jumble of races + // the below might cause over a couple of frames + if ( currentOutputWidth != g_nOutputWidth || + currentOutputHeight != g_nOutputHeight || + currentOutputRefresh != g_nOutputRefresh || + currentHDROutput != g_bOutputHDREnabled || + currentHDRForce != g_bForceHDRSupportDebug ) + { + if ( g_nXWaylandCount > 1 ) + { + g_nNestedHeight = + ( g_nNestedWidth * g_nOutputHeight ) / g_nOutputWidth; + wlserver_lock( ); + // Update only Steam, the root ctx, with the new output size for + // now + wlserver_set_xwayland_server_mode( + 0, g_nOutputWidth, g_nOutputHeight, g_nOutputRefresh ); + wlserver_unlock( ); + } + + // XXX(JoshA): Remake this. It sucks. + if ( GetBackend( )->UsesVulkanSwapchain( ) ) + { + vulkan_remake_swapchain( ); + + while ( !acquire_next_image( ) ) + vulkan_remake_swapchain( ); + } + else + { + vulkan_remake_output_images( ); + } + + { + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; + ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + { + uint32_t hdr_value = + ( g_bOutputHDREnabled || g_bForceHDRSupportDebug ) ? 1 + : 0; + XChangeProperty( + server->ctx->dpy, + server->ctx->root, + server->ctx->atoms.gamescopeHDROutputFeedback, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&hdr_value, + 1 ); + + server->ctx->cursor->setDirty( ); + + if ( server->ctx.get( ) == root_ctx ) { flush_root = true; } + else + { + XFlush( server->ctx->dpy ); + } + } + } + + currentOutputWidth = g_nOutputWidth; + currentOutputHeight = g_nOutputHeight; + currentOutputRefresh = g_nOutputRefresh; + currentHDROutput = g_bOutputHDREnabled; + currentHDRForce = g_bForceHDRSupportDebug; #if HAVE_PIPEWIRE - nudge_pipewire(); + nudge_pipewire( ); #endif - } - - // Ask for a new surface every vblank - // When we observe a new commit being complete for a surface, we ask for a new frame. - // This ensures that FIFO works properly, since otherwise we might ask for a new frame - // application can commit a new frame that completes before we ever displayed - // the current pending commit. - static uint64_t vblank_idx = 0; - if ( vblank ) - { - { - uint64_t now = get_time_in_nanos(); - - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - for (steamcompmgr_win_t *w = server->ctx->list; w; w = w->xwayland().next) - { - steamcompmgr_latch_frame_done( w, vblank_idx, now ); - } - } - - for ( const auto& xdg_win : g_steamcompmgr_xdg_wins ) - { - steamcompmgr_latch_frame_done( xdg_win.get(), vblank_idx, now ); - } - } - } - - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { - handle_done_commits_xwayland(server->ctx.get(), vblank, vblank_idx); - - // When we have observed both a complete commit and a VBlank, we should request a new frame. - if (vblank) - { - for (steamcompmgr_win_t *w = server->ctx->list; w; w = w->xwayland().next) - { - steamcompmgr_flush_frame_done(w); - } - } - } - } - - steamcompmgr_check_xdg(vblank, vblank_idx); - - if ( s_oLowestFPSLimitScheduleVRR ) - { - g_FPSLimitVRRTimer.ArmTimer( *s_oLowestFPSLimitScheduleVRR ); - s_oLowestFPSLimitScheduleVRR = std::nullopt; - } - - if ( vblank ) - { - vblank_idx++; - - int nRealRefreshmHz = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; - g_SteamCompMgrAppRefreshCycle = gamescope::mHzToRefreshCycle( nRealRefreshmHz ); - g_SteamCompMgrLimitedAppRefreshCycle = g_SteamCompMgrAppRefreshCycle; - if ( g_nSteamCompMgrTargetFPS ) - { - int nRealRefreshHz = gamescope::ConvertmHzToHz( nRealRefreshmHz ); - int nTargetFPS = g_nSteamCompMgrTargetFPS; - nTargetFPS = std::min( nTargetFPS, nRealRefreshHz ); - - if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive() ) - { - g_SteamCompMgrLimitedAppRefreshCycle = gamescope::mHzToRefreshCycle( gamescope::ConvertHztomHz( nTargetFPS ) ); - } - else - { - int nVblankDivisor = nRealRefreshHz / nTargetFPS; - - g_SteamCompMgrLimitedAppRefreshCycle = g_SteamCompMgrAppRefreshCycle * nVblankDivisor; - } - } - } - - // Handle presentation-time stuff - // - // Notes: - // - // We send the presented event just after the latest latch time possible so PresentWait in Vulkan - // still returns pretty optimally. The extra 2ms or so can be "display latency" - // We still provide the predicted TTL refresh time in the presented event though. - // - // We ignore or lie most of the flags because they aren't particularly useful for a client - // to know anyway and it would delay us sending this at an optimal time. - // (particularly for DXGI frame latency handles under Proton.) - // - // The boat is still out as to whether we should do latest latch or pageflip/ttl for the event. - // For now, going to keep this, and if we change our minds later, it's no big deal. - // - // It's a little strange, but we return `presented` for any window not visible - // and `presented` for anything visible. It's a little disingenuous because we didn't - // actually show a window if it wasn't visible, but we could! And that is the first - // opportunity it had. It's confusing but we need this for forward progress. - - if ( vblank ) - { - wlserver_lock(); - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - handle_presented_xwayland( server->ctx.get() ); - wlserver_unlock(); - } - - // - - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - check_new_xwayland_res(server->ctx.get()); - } - - - { - GamescopeAppTextureColorspace current_app_colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - std::shared_ptr app_hdr_metadata = nullptr; - if ( g_HeldCommits[HELD_COMMIT_BASE] != nullptr ) - { - current_app_colorspace = g_HeldCommits[HELD_COMMIT_BASE]->colorspace(); - if (g_HeldCommits[HELD_COMMIT_BASE]->feedback) - app_hdr_metadata = g_HeldCommits[HELD_COMMIT_BASE]->feedback->hdr_metadata_blob; - } - - bool app_wants_hdr = ColorspaceIsHDR( current_app_colorspace ); - - if ( app_wants_hdr != g_bAppWantsHDRCached ) - { - uint32_t app_wants_hdr_prop = app_wants_hdr ? 1 : 0; - - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeColorAppWantsHDRFeedback, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)&app_wants_hdr_prop, 1 ); - - g_bAppWantsHDRCached = app_wants_hdr; - flush_root = true; - } - - if ( app_hdr_metadata != g_ColorMgmt.pending.appHDRMetadata ) - { - if ( app_hdr_metadata ) - { - std::vector app_hdr_metadata_blob; - app_hdr_metadata_blob.resize((sizeof(hdr_metadata_infoframe) + (sizeof(uint32_t) - 1)) / sizeof(uint32_t)); - memset(app_hdr_metadata_blob.data(), 0, sizeof(uint32_t) * app_hdr_metadata_blob.size()); - memcpy(app_hdr_metadata_blob.data(), &app_hdr_metadata->View().hdmi_metadata_type1, sizeof(hdr_metadata_infoframe)); - - XChangeProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback, XA_CARDINAL, 32, PropModeReplace, - (unsigned char *)app_hdr_metadata_blob.data(), (int)app_hdr_metadata_blob.size() ); - } - else - { - XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback); - } - - g_ColorMgmt.pending.appHDRMetadata = app_hdr_metadata; - flush_root = true; - } - } - - // Handles if we got a commit for the window we want to focus - // to switch to it for painting (outdatedInteractiveFocus) - // Doesn't realllly matter but avoids an extra frame of being on the wrong window. - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pFocus = &iter.second; - if ( pFocus->IsDirty() ) - { - determine_and_apply_focus( pFocus ); - hasRepaint = true; - } - } - - // XXX(misyl): This is bad! We shouldnt change the upscaler like this at all!!! - // We should move this to business logic in paint_window or something! - if ( GetCurrentFocus() && window_is_steam( GetCurrentFocus()->focusWindow ) ) - { - g_bSteamIsActiveWindow = true; - g_upscaleScaler = GamescopeUpscaleScaler::FIT; - g_upscaleFilter = GamescopeUpscaleFilter::LINEAR; - } - else - { - g_bSteamIsActiveWindow = false; - g_upscaleScaler = g_wantedUpscaleScaler; - g_upscaleFilter = g_wantedUpscaleFilter; - } - - // If we're in the middle of a fade, then keep us - // as needing a repaint. - if ( is_fading_out() ) - hasRepaint = true; - - bool bPainted = false; - - static int nIgnoredOverlayRepaints = 0; - - if ( !hasRepaintNonBasePlane ) - nIgnoredOverlayRepaints = 0; - - if ( cv_adaptive_sync_ignore_overlay ) - nIgnoredOverlayRepaints = 0; - - for ( auto &iter : g_VirtualConnectorFocuses ) - { - global_focus_t *pPaintFocus = &iter.second; - - if ( vblank ) - { - if ( pPaintFocus->cursor ) - pPaintFocus->cursor->UpdatePosition(); - } - - if ( pPaintFocus->GetNestedHints() && !g_bForceRelativeMouse ) - { - const bool bImageEmpty = - ( pPaintFocus->cursor && pPaintFocus->cursor->imageEmpty() ) && - ( !window_is_steam( pPaintFocus->inputFocusWindow ) ); - - const bool bHasPointerConstraint = pPaintFocus->cursor && pPaintFocus->cursor->IsConstrained(); - - uint32_t uAppId = pPaintFocus->inputFocusWindow - ? pPaintFocus->inputFocusWindow->appID - : 0; - - const bool bExcludedAppId = uAppId && gamescope::Algorithm::Contains( s_uRelativeMouseFilteredAppids, uAppId ); - - const bool bRelativeMouseMode = bImageEmpty && bHasPointerConstraint && !bExcludedAppId; - - pPaintFocus->GetNestedHints()->SetRelativeMouseMode( bRelativeMouseMode ); - } - - // HACK: Disable tearing if we have an overlay to avoid stutters right now - // TODO: Fix properly. - const bool bHasOverlay = ( pPaintFocus->overlayWindow && pPaintFocus->overlayWindow->opacity ) || - ( pPaintFocus->externalOverlayWindow && pPaintFocus->externalOverlayWindow->opacity ) || - ( pPaintFocus->overrideWindow && pPaintFocus->focusWindow && !pPaintFocus->focusWindow->isSteamStreamingClient && pPaintFocus->overrideWindow->opacity ); - - // If we are running behind, allow tearing. - - // A false vblank value means bShouldPaint will resolve to false below, effectively ignoring this flag and losing any request - // to force a repaint. Don't clear g_bForceRepaint unless vblank is true. - const bool bForceRepaint = vblank && g_bForceRepaint.exchange(false); - const bool bForceSyncFlip = bForceRepaint || is_fading_out(); - - // If we are compositing, always force sync flips because we currently wait - // for composition to finish before submitting. - // If we want to do async + composite, we should set up syncfile stuff and have DRM wait on it. - const bool bSurfaceWantsAsync = (g_HeldCommits[HELD_COMMIT_BASE] != nullptr && g_HeldCommits[HELD_COMMIT_BASE]->async); - const bool bTearing = cv_tearing_enabled && GetBackend()->SupportsTearing() && bSurfaceWantsAsync; - - enum class FlipType - { - Normal, - Async, - VRR, - }; - - FlipType eFlipType = FlipType::Normal; - - if ( bForceSyncFlip ) - eFlipType = FlipType::Normal; - else if ( bVRR ) - eFlipType = FlipType::VRR; - else if ( bTearing ) - { - eFlipType = FlipType::Async; - - if ( nIgnoredOverlayRepaints ) - eFlipType = FlipType::Normal; - if ( bHasOverlay ) // Don't tear if the Steam or perf overlay is up atm. - eFlipType = FlipType::Normal; - if ( GetVBlankTimer().WasCompositing() ) - eFlipType = FlipType::Normal; - } - else - eFlipType = FlipType::Normal; - - bool bShouldPaint = false; - - //if ( GetBackend()->IsVisible() ) - if ( true ) - { - switch ( eFlipType ) - { - case FlipType::Normal: - { - bShouldPaint = vblank && ( hasRepaint || hasRepaintNonBasePlane || bForceSyncFlip ); - break; - } - - case FlipType::Async: - { - bShouldPaint = hasRepaint; - - if ( vblank && !bShouldPaint && hasRepaintNonBasePlane ) - nIgnoredOverlayRepaints++; - - break; - } - - case FlipType::VRR: - { - bShouldPaint = hasRepaint; - - if ( bIsVBlankFromTimer ) - { - if ( hasRepaintNonBasePlane ) - { - if ( nIgnoredOverlayRepaints >= cv_adaptive_sync_overlay_cycles ) - { - // If we hit vblank and we previously punted on drawing an overlay - // we should go ahead and draw now. - bShouldPaint = true; - } - else if ( !bShouldPaint ) - { - // If we hit vblank (ie. fastest refresh cycle since last commit), - // and we aren't painting and we have a pending overlay, then: - // defer it until the next game update or next true vblank. - if ( !cv_adaptive_sync_ignore_overlay ) - nIgnoredOverlayRepaints++; - } - } - } - - // If we have a pending page flip and doing VRR, lets not do another... - if ( GetBackend()->GetCurrentConnector() && - GetBackend()->GetCurrentConnector()->PresentationFeedback().CurrentPresentsInFlight() != 0 ) - bShouldPaint = false; - - break; - } - } - } - else - { - bShouldPaint = false; - } - - if ( bShouldPaint ) - { - paint_all( pPaintFocus, eFlipType == FlipType::Async ); - - bPainted = true; - } - } - - if ( vblank && g_bUpdateForwardedVROverlays ) - { - ForwardVROverlayTargets(); - - g_bUpdateForwardedVROverlays = false; - - bPainted = true; - } - - if ( bPainted ) - { - GetBackend()->OnEndFrame(); - - hasRepaint = false; - hasRepaintNonBasePlane = false; - nIgnoredOverlayRepaints = 0; - - { - gamescope::CScriptScopedLock script; - script.Manager().CallHook( "OnPostPaint" ); - } - } - - if ( bIsVBlankFromTimer ) - { - // Pre-emptively re-arm the vblank timer if it - // isn't already re-armed. - // - // Juuust in case pageflip handler doesn't happen - // so we don't stop vblanking forever. - GetVBlankTimer().ArmNextVBlank( true ); + } + + // Ask for a new surface every vblank + // When we observe a new commit being complete for a surface, we ask for + // a new frame. This ensures that FIFO works properly, since otherwise + // we might ask for a new frame application can commit a new frame that + // completes before we ever displayed the current pending commit. + static uint64_t vblank_idx = 0; + if ( vblank ) + { + { + uint64_t now = get_time_in_nanos( ); + + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; + ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + { + for ( steamcompmgr_win_t *w = server->ctx->list; w; + w = w->xwayland( ).next ) + { + steamcompmgr_latch_frame_done( w, vblank_idx, now ); + } + } + + for ( const auto &xdg_win : g_steamcompmgr_xdg_wins ) + { + steamcompmgr_latch_frame_done( + xdg_win.get( ), vblank_idx, now ); + } + } + } + + { + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + { + handle_done_commits_xwayland( + server->ctx.get( ), vblank, vblank_idx ); + + // When we have observed both a complete commit and a VBlank, we + // should request a new frame. + if ( vblank ) + { + for ( steamcompmgr_win_t *w = server->ctx->list; w; + w = w->xwayland( ).next ) + { + steamcompmgr_flush_frame_done( w ); + } + } + } + } + + steamcompmgr_check_xdg( vblank, vblank_idx ); + + if ( s_oLowestFPSLimitScheduleVRR ) + { + g_FPSLimitVRRTimer.ArmTimer( *s_oLowestFPSLimitScheduleVRR ); + s_oLowestFPSLimitScheduleVRR = std::nullopt; + } + + if ( vblank ) + { + vblank_idx++; + + int nRealRefreshmHz = + g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; + g_SteamCompMgrAppRefreshCycle = + gamescope::mHzToRefreshCycle( nRealRefreshmHz ); + g_SteamCompMgrLimitedAppRefreshCycle = + g_SteamCompMgrAppRefreshCycle; + if ( g_nSteamCompMgrTargetFPS ) + { + int nRealRefreshHz = + gamescope::ConvertmHzToHz( nRealRefreshmHz ); + int nTargetFPS = g_nSteamCompMgrTargetFPS; + nTargetFPS = std::min( nTargetFPS, nRealRefreshHz ); + + if ( GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->IsVRRActive( ) ) + { + g_SteamCompMgrLimitedAppRefreshCycle = + gamescope::mHzToRefreshCycle( + gamescope::ConvertHztomHz( nTargetFPS ) ); + } + else + { + int nVblankDivisor = nRealRefreshHz / nTargetFPS; + + g_SteamCompMgrLimitedAppRefreshCycle = + g_SteamCompMgrAppRefreshCycle * nVblankDivisor; + } + } + } + + // Handle presentation-time stuff + // + // Notes: + // + // We send the presented event just after the latest latch time possible + // so PresentWait in Vulkan still returns pretty optimally. The extra + // 2ms or so can be "display latency" We still provide the predicted TTL + // refresh time in the presented event though. + // + // We ignore or lie most of the flags because they aren't particularly + // useful for a client to know anyway and it would delay us sending this + // at an optimal time. (particularly for DXGI frame latency handles + // under Proton.) + // + // The boat is still out as to whether we should do latest latch or + // pageflip/ttl for the event. For now, going to keep this, and if we + // change our minds later, it's no big deal. + // + // It's a little strange, but we return `presented` for any window not + // visible and `presented` for anything visible. It's a little + // disingenuous because we didn't actually show a window if it wasn't + // visible, but we could! And that is the first opportunity it had. It's + // confusing but we need this for forward progress. + + if ( vblank ) + { + wlserver_lock( ); + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + handle_presented_xwayland( server->ctx.get( ) ); + wlserver_unlock( ); + } + + // + + { + gamescope_xwayland_server_t *server = NULL; + for ( size_t i = 0; ( server = wlserver_get_xwayland_server( i ) ); + i++ ) + check_new_xwayland_res( server->ctx.get( ) ); + } + + { + GamescopeAppTextureColorspace current_app_colorspace = + GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + std::shared_ptr app_hdr_metadata = nullptr; + if ( g_HeldCommits[ HELD_COMMIT_BASE ] != nullptr ) + { + current_app_colorspace = + g_HeldCommits[ HELD_COMMIT_BASE ]->colorspace( ); + if ( g_HeldCommits[ HELD_COMMIT_BASE ]->feedback ) + app_hdr_metadata = g_HeldCommits[ HELD_COMMIT_BASE ] + ->feedback.value( ) + ->hdr_metadata_blob; + } + + bool app_wants_hdr = ColorspaceIsHDR( current_app_colorspace ); + + if ( app_wants_hdr != g_bAppWantsHDRCached ) + { + uint32_t app_wants_hdr_prop = app_wants_hdr ? 1 : 0; + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeColorAppWantsHDRFeedback, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )&app_wants_hdr_prop, + 1 ); + + g_bAppWantsHDRCached = app_wants_hdr; + flush_root = true; + } + + if ( app_hdr_metadata != g_ColorMgmt.pending.appHDRMetadata ) + { + if ( app_hdr_metadata ) + { + std::vector app_hdr_metadata_blob; + app_hdr_metadata_blob.resize( + ( sizeof( hdr_metadata_infoframe ) + + ( sizeof( uint32_t ) - 1 ) ) / + sizeof( uint32_t ) ); + memset( + app_hdr_metadata_blob.data( ), + 0, + sizeof( uint32_t ) * app_hdr_metadata_blob.size( ) ); + memcpy( + app_hdr_metadata_blob.data( ), + &app_hdr_metadata->View( ) + .hdmi_metadata_type1, + sizeof( hdr_metadata_infoframe ) ); + + XChangeProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback, + XA_CARDINAL, + 32, + PropModeReplace, + ( unsigned char * )app_hdr_metadata_blob.data( ), + ( int )app_hdr_metadata_blob.size( ) ); + } + else + { + XDeleteProperty( + root_ctx->dpy, + root_ctx->root, + root_ctx->atoms.gamescopeColorAppHDRMetadataFeedback ); + } + + g_ColorMgmt.pending.appHDRMetadata = app_hdr_metadata; + flush_root = true; + } + } + + // Handles if we got a commit for the window we want to focus + // to switch to it for painting (outdatedInteractiveFocus) + // Doesn't realllly matter but avoids an extra frame of being on the + // wrong window. + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pFocus = &iter.second; + if ( pFocus->IsDirty( ) ) + { + determine_and_apply_focus( pFocus ); + hasRepaint = true; + } + } + + // XXX(misyl): This is bad! We shouldnt change the upscaler like this at + // all!!! We should move this to business logic in paint_window or + // something! + if ( GetCurrentFocus( ) && + window_is_steam( GetCurrentFocus( )->focusWindow ) ) + { + g_bSteamIsActiveWindow = true; + g_upscaleScaler = GamescopeUpscaleScaler::FIT; + g_upscaleFilter = GamescopeUpscaleFilter::LINEAR; + } + else + { + g_bSteamIsActiveWindow = false; + g_upscaleScaler = g_wantedUpscaleScaler; + g_upscaleFilter = g_wantedUpscaleFilter; + } + + // If we're in the middle of a fade, then keep us + // as needing a repaint. + if ( is_fading_out( ) ) hasRepaint = true; + + bool bPainted = false; + + static int nIgnoredOverlayRepaints = 0; + + if ( !hasRepaintNonBasePlane ) nIgnoredOverlayRepaints = 0; + + if ( cv_adaptive_sync_ignore_overlay ) nIgnoredOverlayRepaints = 0; + + for ( auto &iter : g_VirtualConnectorFocuses ) + { + global_focus_t *pPaintFocus = &iter.second; + + if ( vblank ) + { + if ( pPaintFocus->cursor ) + pPaintFocus->cursor->UpdatePosition( ); + } + + if ( pPaintFocus->GetNestedHints( ) && !g_bForceRelativeMouse ) + { + const bool bImageEmpty = + ( pPaintFocus->cursor && + pPaintFocus->cursor->imageEmpty( ) ) && + ( !window_is_steam( pPaintFocus->inputFocusWindow ) ); + + const bool bHasPointerConstraint = + pPaintFocus->cursor && + pPaintFocus->cursor->IsConstrained( ); + + uint32_t uAppId = pPaintFocus->inputFocusWindow + ? pPaintFocus->inputFocusWindow->appID + : 0; + + const bool bExcludedAppId = + uAppId && gamescope::Algorithm::Contains( + s_uRelativeMouseFilteredAppids, uAppId ); + + const bool bRelativeMouseMode = + bImageEmpty && bHasPointerConstraint && !bExcludedAppId; + + pPaintFocus->GetNestedHints( )->SetRelativeMouseMode( + bRelativeMouseMode ); + } + + // HACK: Disable tearing if we have an overlay to avoid stutters + // right now + // TODO: Fix properly. + const bool bHasOverlay = + ( pPaintFocus->overlayWindow && + pPaintFocus->overlayWindow->opacity ) || + ( pPaintFocus->externalOverlayWindow && + pPaintFocus->externalOverlayWindow->opacity ) || + ( pPaintFocus->overrideWindow && pPaintFocus->focusWindow && + !pPaintFocus->focusWindow->isSteamStreamingClient && + pPaintFocus->overrideWindow->opacity ); + + // If we are running behind, allow tearing. + + // A false vblank value means bShouldPaint will resolve to false + // below, effectively ignoring this flag and losing any request to + // force a repaint. Don't clear g_bForceRepaint unless vblank is + // true. + const bool bForceRepaint = + vblank && g_bForceRepaint.exchange( false ); + const bool bForceSyncFlip = bForceRepaint || is_fading_out( ); + + // If we are compositing, always force sync flips because we + // currently wait for composition to finish before submitting. If we + // want to do async + composite, we should set up syncfile stuff and + // have DRM wait on it. + const bool bSurfaceWantsAsync = + ( g_HeldCommits[ HELD_COMMIT_BASE ] != nullptr && + g_HeldCommits[ HELD_COMMIT_BASE ]->async ); + const bool bTearing = cv_tearing_enabled && + GetBackend( )->SupportsTearing( ) && + bSurfaceWantsAsync; + + enum class FlipType + { + Normal, + Async, + VRR, + }; + + FlipType eFlipType = FlipType::Normal; + + if ( bForceSyncFlip ) eFlipType = FlipType::Normal; + else if ( bVRR ) + eFlipType = FlipType::VRR; + else if ( bTearing ) + { + eFlipType = FlipType::Async; + + if ( nIgnoredOverlayRepaints ) eFlipType = FlipType::Normal; + if ( bHasOverlay ) // Don't tear if the Steam or perf overlay is + // up atm. + eFlipType = FlipType::Normal; + if ( GetVBlankTimer( ).WasCompositing( ) ) + eFlipType = FlipType::Normal; + } + else + eFlipType = FlipType::Normal; + + bool bShouldPaint = false; + + // if ( GetBackend()->IsVisible() ) + if ( true ) + { + switch ( eFlipType ) + { + case FlipType::Normal: + { + bShouldPaint = + vblank && ( hasRepaint || hasRepaintNonBasePlane || + bForceSyncFlip ); + break; + } + + case FlipType::Async: + { + bShouldPaint = hasRepaint; + + if ( vblank && !bShouldPaint && hasRepaintNonBasePlane ) + nIgnoredOverlayRepaints++; + + break; + } + + case FlipType::VRR: + { + bShouldPaint = hasRepaint; + + if ( bIsVBlankFromTimer ) + { + if ( hasRepaintNonBasePlane ) + { + if ( nIgnoredOverlayRepaints >= + cv_adaptive_sync_overlay_cycles ) + { + // If we hit vblank and we previously punted + // on drawing an overlay we should go ahead + // and draw now. + bShouldPaint = true; + } + else if ( !bShouldPaint ) + { + // If we hit vblank (ie. fastest refresh + // cycle since last commit), and we aren't + // painting and we have a pending overlay, + // then: defer it until the next game update + // or next true vblank. + if ( !cv_adaptive_sync_ignore_overlay ) + nIgnoredOverlayRepaints++; + } + } + } + + // If we have a pending page flip and doing VRR, lets + // not do another... + if ( GetBackend( )->GetCurrentConnector( ) && + GetBackend( ) + ->GetCurrentConnector( ) + ->PresentationFeedback( ) + .CurrentPresentsInFlight( ) != 0 ) + bShouldPaint = false; + + break; + } + } + } + else + { + bShouldPaint = false; + } + + if ( bShouldPaint ) + { + paint_all( pPaintFocus, eFlipType == FlipType::Async ); + + bPainted = true; + } + } + + if ( vblank && g_bUpdateForwardedVROverlays ) + { + ForwardVROverlayTargets( ); + + g_bUpdateForwardedVROverlays = false; + + bPainted = true; + } + + if ( bPainted ) + { + GetBackend( )->OnEndFrame( ); + + hasRepaint = false; + hasRepaintNonBasePlane = false; + nIgnoredOverlayRepaints = 0; + + { + gamescope::CScriptScopedLock script; + script.Manager( ).CallHook( "OnPostPaint" ); + } + } + + if ( bIsVBlankFromTimer ) + { + // Pre-emptively re-arm the vblank timer if it + // isn't already re-armed. + // + // Juuust in case pageflip handler doesn't happen + // so we don't stop vblanking forever. + GetVBlankTimer( ).ArmNextVBlank( true ); #if HAVE_PIPEWIRE - if ( pipewire_is_streaming() ) - paint_pipewire(); + if ( pipewire_is_streaming( ) ) paint_pipewire( ); #endif - } + } - update_vrr_atoms(root_ctx, false, &flush_root); + update_vrr_atoms( root_ctx, false, &flush_root ); - if (GetCurrentFocus() && GetCurrentFocus()->cursor) - { - GetCurrentFocus()->cursor->checkSuspension(); + if ( GetCurrentFocus( ) && GetCurrentFocus( )->cursor ) + { + GetCurrentFocus( )->cursor->checkSuspension( ); - if (GetCurrentFocus()->cursor->needs_server_flush()) - { - flush_root = true; - GetCurrentFocus()->cursor->inform_flush(); - } - } + if ( GetCurrentFocus( )->cursor->needs_server_flush( ) ) + { + flush_root = true; + GetCurrentFocus( )->cursor->inform_flush( ); + } + } - if (flush_root) - { - XFlush(root_ctx->dpy); - } + if ( flush_root ) { XFlush( root_ctx->dpy ); } - vulkan_garbage_collect(); + vulkan_garbage_collect( ); - vblank = false; - } + vblank = false; + } - steamcompmgr_exit(); + steamcompmgr_exit( ); } struct wlr_surface *steamcompmgr_get_server_input_surface( size_t idx ) { - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( idx ); - if ( server && server->ctx && server->ctx->focus.inputFocusWindow && server->ctx->focus.inputFocusWindow->xwayland().surface.main_surface ) - return server->ctx->focus.inputFocusWindow->xwayland().surface.main_surface; - return NULL; + gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( idx ); + if ( server && server->ctx && server->ctx->focus.inputFocusWindow && + server->ctx->focus.inputFocusWindow->xwayland( ).surface.main_surface ) + return server->ctx->focus.inputFocusWindow->xwayland( ) + .surface.main_surface; + return NULL; } -struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( gamescope_xwayland_server_t *xwayland_server, uint32_t xid ) +struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( + gamescope_xwayland_server_t *xwayland_server, uint32_t xid ) { - if ( !xwayland_server ) - return nullptr; + if ( !xwayland_server ) return nullptr; - if ( !xwayland_server->ctx ) - return nullptr; + if ( !xwayland_server->ctx ) return nullptr; - // Lookup children too so we can get the window - // and go back to it's top-level parent. - // The xwayland bypass layer does this as we can have child windows - // that cover the whole parent. - std::unique_lock lock( xwayland_server->ctx->list_mutex ); - steamcompmgr_win_t *w = find_win( xwayland_server->ctx.get(), xid, true ); - if ( !w ) - return nullptr; + // Lookup children too so we can get the window + // and go back to it's top-level parent. + // The xwayland bypass layer does this as we can have child windows + // that cover the whole parent. + std::unique_lock lock( xwayland_server->ctx->list_mutex ); + steamcompmgr_win_t *w = find_win( xwayland_server->ctx.get( ), xid, true ); + if ( !w ) return nullptr; - return &w->xwayland().surface; + return &w->xwayland( ).surface; } -MouseCursor *steamcompmgr_get_current_cursor() +MouseCursor *steamcompmgr_get_current_cursor( ) { - global_focus_t *pFocus = GetCurrentFocus(); - if ( !pFocus ) - return nullptr; + global_focus_t *pFocus = GetCurrentFocus( ); + if ( !pFocus ) return nullptr; - return pFocus->cursor; + return pFocus->cursor; } -MouseCursor *steamcompmgr_get_server_cursor(uint32_t idx) +MouseCursor *steamcompmgr_get_server_cursor( uint32_t idx ) { - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( idx ); - if ( server && server->ctx ) - return server->ctx->cursor.get(); - return nullptr; + gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( idx ); + if ( server && server->ctx ) return server->ctx->cursor.get( ); + return nullptr; } diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp index 6734c15ace..99f21cfabc 100644 --- a/src/steamcompmgr.hpp +++ b/src/steamcompmgr.hpp @@ -1,25 +1,25 @@ #include -#include "wlr_begin.hpp" -#include -#include #include +#include +#include +#include "wlr_begin.hpp" #include "wlr_end.hpp" extern uint32_t currentOutputWidth; extern uint32_t currentOutputHeight; -unsigned int get_time_in_milliseconds(void); -uint64_t get_time_in_nanos(); -void sleep_for_nanos(uint64_t nanos); -void sleep_until_nanos(uint64_t nanos); -timespec nanos_to_timespec( uint64_t ulNanos ); +unsigned int get_time_in_milliseconds( void ); +uint64_t get_time_in_nanos( ); +void sleep_for_nanos( uint64_t nanos ); +void sleep_until_nanos( uint64_t nanos ); +timespec nanos_to_timespec( uint64_t ulNanos ); -void steamcompmgr_main(int argc, char **argv); +void steamcompmgr_main( int argc, char **argv ); #include "rendervulkan.hpp" -#include "wlserver.hpp" #include "vblankmanager.hpp" +#include "wlserver.hpp" #include #include @@ -31,12 +31,12 @@ struct steamcompmgr_win_t; struct xwayland_ctx_t; class gamescope_xwayland_server_t; -static const uint32_t g_zposBase = 0; -static const uint32_t g_zposOverride = 1; +static const uint32_t g_zposBase = 0; +static const uint32_t g_zposOverride = 1; static const uint32_t g_zposExternalOverlay = 2; -static const uint32_t g_zposOverlay = 3; -static const uint32_t g_zposCursor = 4; -static const uint32_t g_zposMuraCorrection = 5; +static const uint32_t g_zposOverlay = 3; +static const uint32_t g_zposCursor = 4; +static const uint32_t g_zposMuraCorrection = 5; extern bool g_bHDRItmEnable; extern bool g_bForceHDRSupportDebug; @@ -45,84 +45,88 @@ extern EStreamColorspace g_ForcedNV12ColorSpace; struct CursorBarrierInfo { - int x1 = 0; - int y1 = 0; - int x2 = 0; - int y2 = 0; + int x1 = 0; + int y1 = 0; + int x2 = 0; + int y2 = 0; }; struct CursorBarrier { - PointerBarrier obj = None; - CursorBarrierInfo info = {}; + PointerBarrier obj = None; + CursorBarrierInfo info = {}; }; class MouseCursor { public: - explicit MouseCursor(xwayland_ctx_t *ctx); + explicit MouseCursor( xwayland_ctx_t *ctx ); - int x() const; - int y() const; + int x( ) const; + int y( ) const; - void paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, FrameInfo_t *frameInfo); - void setDirty(); + void paint( + steamcompmgr_win_t *window, + steamcompmgr_win_t *fit, + FrameInfo_t *frameInfo ); + void setDirty( ); - // Will take ownership of data. - bool setCursorImage(char *data, int w, int h, int hx, int hy); - bool setCursorImageByName(const char *name); + // Will take ownership of data. + bool setCursorImage( char *data, int w, int h, int hx, int hy ); + bool setCursorImageByName( const char *name ); - void hide() - { - wlserver_lock(); - wlserver_mousehide(); - wlserver_unlock( false ); - checkSuspension(); - } + void hide( ) + { + wlserver_lock( ); + wlserver_mousehide( ); + wlserver_unlock( false ); + checkSuspension( ); + } - void UpdatePosition(); + void UpdatePosition( ); - bool isHidden() { return wlserver.bCursorHidden || m_imageEmpty; } - bool imageEmpty() const { return m_imageEmpty; } + bool isHidden( ) { return wlserver.bCursorHidden || m_imageEmpty; } + bool imageEmpty( ) const { return m_imageEmpty; } - void undirty() { getTexture(); } + void undirty( ) { getTexture( ); } - xwayland_ctx_t *getCtx() const { return m_ctx; } + xwayland_ctx_t *getCtx( ) const { return m_ctx; } - bool needs_server_flush() const { return m_needs_server_flush; } - void inform_flush() { m_needs_server_flush = false; } + bool needs_server_flush( ) const { return m_needs_server_flush; } + void inform_flush( ) { m_needs_server_flush = false; } - void GetDesiredSize( int& nWidth, int &nHeight ); + void GetDesiredSize( int &nWidth, int &nHeight ); - void checkSuspension(); + void checkSuspension( ); - bool IsConstrained() const { return m_bConstrained; } -private: + bool IsConstrained( ) const { return m_bConstrained; } - bool getTexture(); +private: + bool getTexture( ); - void updateCursorFeedback( bool bForce = false ); + void updateCursorFeedback( bool bForce = false ); - int m_x = 0, m_y = 0; - bool m_bConstrained = false; - int m_hotspotX = 0, m_hotspotY = 0; + int m_x = 0, m_y = 0; + bool m_bConstrained = false; + int m_hotspotX = 0, m_hotspotY = 0; - gamescope::OwningRc m_texture; - bool m_dirty; - uint64_t m_ulLastConnectorId = 0; - bool m_imageEmpty; + gamescope::OwningRc m_texture; + bool m_dirty; + uint64_t m_ulLastConnectorId = 0; + bool m_imageEmpty; - xwayland_ctx_t *m_ctx; + xwayland_ctx_t *m_ctx; - bool m_bCursorVisibleFeedback = false; - bool m_needs_server_flush = false; + bool m_bCursorVisibleFeedback = false; + bool m_needs_server_flush = false; }; -extern std::vector< wlr_surface * > wayland_surfaces_deleted; +extern std::vector wayland_surfaces_deleted; extern bool hasFocusWindow; -// These are used for touch scaling, so it's really the window that's focused for touch +// These are used for touch scaling, so it's really the window that's focused +// for touch extern float focusedWindowScaleX; extern float focusedWindowScaleY; extern float focusedWindowOffsetX; @@ -136,24 +140,35 @@ extern uint64_t g_lastWinSeq; void nudge_steamcompmgr( void ); void force_repaint( void ); -extern void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns ); +extern void mangoapp_update( + uint64_t visible_frametime, + uint64_t app_frametime_ns, + uint64_t latency_ns ); struct wlr_surface *steamcompmgr_get_server_input_surface( size_t idx ); -wlserver_vk_swapchain_feedback* steamcompmgr_get_base_layer_swapchain_feedback(); +wlserver_vk_swapchain_feedback * +steamcompmgr_get_base_layer_swapchain_feedback( ); -struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( gamescope_xwayland_server_t *xwayland_server, uint32_t xid ); +struct wlserver_x11_surface_info *lookup_x11_surface_info_from_xid( + gamescope_xwayland_server_t *xwayland_server, uint32_t xid ); -extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; -extern pid_t focusWindow_pid; +extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; +extern pid_t focusWindow_pid; extern std::atomic> focusWindow_engine; -void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server); -void gamescope_set_selection(std::string contents, GamescopeSelection eSelection); -void gamescope_set_reshade_effect(std::string effect_path); -void gamescope_clear_reshade_effect(); +void init_xwayland_ctx( + uint32_t serverId, gamescope_xwayland_server_t *xwayland_server ); +void gamescope_set_selection( + std::string contents, GamescopeSelection eSelection ); +void gamescope_set_reshade_effect( std::string effect_path ); +void gamescope_clear_reshade_effect( ); -MouseCursor *steamcompmgr_get_current_cursor(); -MouseCursor *steamcompmgr_get_server_cursor(uint32_t serverId); +MouseCursor *steamcompmgr_get_current_cursor( ); +MouseCursor *steamcompmgr_get_server_cursor( uint32_t serverId ); extern gamescope::ConVar cv_tearing_enabled; -extern void steamcompmgr_set_app_refresh_cycle_override( gamescope::GamescopeScreenType type, int override_fps, bool change_refresh, bool change_fps_cap ); +extern void steamcompmgr_set_app_refresh_cycle_override( + gamescope::GamescopeScreenType type, + int override_fps, + bool change_refresh, + bool change_fps_cap ); diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp index e30b3243dd..2956ccc3ef 100644 --- a/src/steamcompmgr_shared.hpp +++ b/src/steamcompmgr_shared.hpp @@ -1,320 +1,334 @@ #pragma once -#include #include #include +#include #include -#include "xwayland_ctx.hpp" #include "gamescope-control-protocol.h" +#include "xwayland_ctx.hpp" struct commit_t; struct wlserver_vk_swapchain_feedback; struct wlserver_x11_surface_info { - std::atomic override_surface; - std::atomic main_surface; + std::atomic override_surface; + std::atomic main_surface; - struct wlr_surface *current_surface() const - { - if ( override_surface ) - return override_surface; - return main_surface; - } + struct wlr_surface *current_surface( ) const + { + if ( override_surface ) return override_surface; + return main_surface; + } - // owned by wlserver - uint32_t wl_id, x11_id; - struct wl_list pending_link; + // owned by wlserver + uint32_t wl_id, x11_id; + struct wl_list pending_link; - gamescope_xwayland_server_t *xwayland_server; + gamescope_xwayland_server_t *xwayland_server; }; // not always an xdg window anymore // wayland-y window (not xwayland) struct wlserver_xdg_surface_info { - std::atomic main_surface; + std::atomic main_surface; - struct wlr_surface *current_surface() - { - return main_surface; - } + struct wlr_surface *current_surface( ) { return main_surface; } - // owned by wlserver - struct wlr_xdg_surface *xdg_surface = nullptr; - struct wlr_layer_surface_v1 *layer_surface = nullptr; - steamcompmgr_win_t *win = nullptr; - bool bDoneConfigure = false; + // owned by wlserver + struct wlr_xdg_surface *xdg_surface = nullptr; + struct wlr_layer_surface_v1 *layer_surface = nullptr; + steamcompmgr_win_t *win = nullptr; + bool bDoneConfigure = false; - std::atomic mapped = { false }; + std::atomic mapped = { false }; - struct wl_list link; + struct wl_list link; - struct wl_listener map; - struct wl_listener unmap; - struct wl_listener destroy; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener destroy; }; - enum class steamcompmgr_win_type_t { - XWAYLAND, - XDG, // could also be layer shell + XWAYLAND, + XDG, // could also be layer shell }; struct steamcompmgr_xwayland_win_t { - steamcompmgr_win_t *next; + steamcompmgr_win_t *next; - Window id; - XWindowAttributes a; - Damage damage; - unsigned long map_sequence; - unsigned long damage_sequence; + Window id; + XWindowAttributes a; + Damage damage; + unsigned long map_sequence; + unsigned long damage_sequence; - Window transientFor; + Window transientFor; - struct wlserver_x11_surface_info surface; + struct wlserver_x11_surface_info surface; - xwayland_ctx_t *ctx; + xwayland_ctx_t *ctx; }; struct steamcompmgr_xdg_win_t { - uint32_t id; + uint32_t id; - struct wlserver_xdg_surface_info surface; - struct wlr_box geometry; + struct wlserver_xdg_surface_info surface; + struct wlr_box geometry; }; struct Rect { - int32_t nX; - int32_t nY; + int32_t nX; + int32_t nY; - int32_t nWidth; - int32_t nHeight; + int32_t nWidth; + int32_t nHeight; }; extern focus_t g_steamcompmgr_xdg_focus; -struct steamcompmgr_win_t { - unsigned int opacity = 0xffffffff; - - uint64_t seq = 0; - - std::shared_ptr title; - bool utf8_title = false; - pid_t pid = -1; - - bool isSteamLegacyBigPicture = false; - bool isSteamStreamingClient = false; - bool isSteamStreamingClientVideo = false; - uint32_t inputFocusMode = 0; - uint32_t appID = 0; - bool isOverlay = false; - bool isExternalOverlay = false; - - bool bIsSteamPid = false; - bool bIsSteamWebHelperPid = false; - bool bIsVRWebHelperPid = false; - bool bIsDolphin = false; // File Manager - - std::string pid_name; - - bool IsAnyOverlay() const - { - return isOverlay || isExternalOverlay; - } - - bool isFullscreen = false; - bool isSysTrayIcon = false; - bool sizeHintsSpecified = false; - bool skipTaskbar = false; - bool skipPager = false; - unsigned int requestedWidth = 0; - unsigned int requestedHeight = 0; - bool is_dialog = false; - bool maybe_a_dropdown = false; - bool outdatedInteractiveFocus = false; - - uint64_t last_commit_first_latch_time = 0; - uint64_t last_commit_present_time = 0; - - bool hasHwndStyle = false; - uint32_t hwndStyle = 0; - bool hasHwndStyleEx = false; - uint32_t hwndStyleEx = 0; - - bool bHasHadNonSRGBColorSpace = false; - - bool nudged = false; - bool ignoreOverrideRedirect = false; - - bool unlockedForFrameCallback = false; - bool receivedDoneCommit = false; - - std::shared_ptr engineName; - - std::vector< gamescope::Rc > commit_queue; - std::shared_ptr> icon; - - steamcompmgr_win_type_t type; - - std::optional oulTargetVROverlay; - std::shared_ptr pForwarderPlane; - bool bNeedsForwarding = false; - - steamcompmgr_xwayland_win_t& xwayland() { return std::get(_window_types); } - const steamcompmgr_xwayland_win_t& xwayland() const { return std::get(_window_types); } - - steamcompmgr_xdg_win_t& xdg() { return std::get(_window_types); } - const steamcompmgr_xdg_win_t& xdg() const { return std::get(_window_types); } - - std::variant - _window_types; - - focus_t *GetFocus() const - { - if (type == steamcompmgr_win_type_t::XWAYLAND) - return &xwayland().ctx->focus; - else if (type == steamcompmgr_win_type_t::XDG) - return &g_steamcompmgr_xdg_focus; - else - return nullptr; - } - - Rect GetGeometry() const - { - if (type == steamcompmgr_win_type_t::XWAYLAND) - return Rect{ xwayland().a.x, xwayland().a.y, xwayland().a.width, xwayland().a.height }; - else if (type == steamcompmgr_win_type_t::XDG) - return Rect{ xdg().geometry.x, xdg().geometry.y, xdg().geometry.width, xdg().geometry.height }; - else - return Rect{}; - } - - uint32_t id() const - { - if (type == steamcompmgr_win_type_t::XWAYLAND) - return uint32_t(xwayland().id); - else if (type == steamcompmgr_win_type_t::XDG) - return xdg().id; - else - return ~(0u); - } - - wlr_surface *main_surface() const - { - if (type == steamcompmgr_win_type_t::XWAYLAND) - return xwayland().surface.main_surface; - else if (type == steamcompmgr_win_type_t::XDG) - return xdg().surface.main_surface; - else - return nullptr; - } - - wlr_surface *current_surface() const - { - if (type == steamcompmgr_win_type_t::XWAYLAND) - return xwayland().surface.current_surface(); - - return main_surface(); - } - - wlr_surface *override_surface() const - { - if (type == steamcompmgr_win_type_t::XWAYLAND) - return xwayland().surface.override_surface; - else - return nullptr; - } - - const char *debug_name() const - { - if ( title ) - return title->c_str(); - - return pid_name.c_str(); - } - - gamescope::VirtualConnectorKey_t GetVirtualConnectorKey( gamescope::VirtualConnectorStrategy eStrategy ) - { - switch ( eStrategy ) - { - default: - case gamescope::VirtualConnectorStrategies::SingleApplication: - case gamescope::VirtualConnectorStrategies::SteamControlled: - return 0; - case gamescope::VirtualConnectorStrategies::PerAppId: - if ( this->isSteamLegacyBigPicture ) - { - // Steam Bootstrapper - return gamescope::k_ulSteamBootstrapperKey; - } - else if ( this->appID ) - { - return static_cast( this->appID ); - } - else - { - return static_cast( gamescope::k_ulNonSteamWindowBit | this->seq ); - } - case gamescope::VirtualConnectorStrategies::PerWindow: - return static_cast( this->seq ); - } - } +struct steamcompmgr_win_t +{ + unsigned int opacity = 0xffffffff; + + uint64_t seq = 0; + + std::shared_ptr title; + bool utf8_title = false; + pid_t pid = -1; + + bool isSteamLegacyBigPicture = false; + bool isSteamStreamingClient = false; + bool isSteamStreamingClientVideo = false; + uint32_t inputFocusMode = 0; + uint32_t appID = 0; + bool isOverlay = false; + bool isExternalOverlay = false; + + bool bIsSteamPid = false; + bool bIsSteamWebHelperPid = false; + bool bIsVRWebHelperPid = false; + bool bIsDolphin = false; // File Manager + + std::string pid_name; + + bool IsAnyOverlay( ) const { return isOverlay || isExternalOverlay; } + + bool isFullscreen = false; + bool isSysTrayIcon = false; + bool sizeHintsSpecified = false; + bool skipTaskbar = false; + bool skipPager = false; + unsigned int requestedWidth = 0; + unsigned int requestedHeight = 0; + bool is_dialog = false; + bool maybe_a_dropdown = false; + bool outdatedInteractiveFocus = false; + + uint64_t last_commit_first_latch_time = 0; + uint64_t last_commit_present_time = 0; + + bool hasHwndStyle = false; + uint32_t hwndStyle = 0; + bool hasHwndStyleEx = false; + uint32_t hwndStyleEx = 0; + + bool bHasHadNonSRGBColorSpace = false; + + bool nudged = false; + bool ignoreOverrideRedirect = false; + + bool unlockedForFrameCallback = false; + bool receivedDoneCommit = false; + + std::shared_ptr engineName; + + std::vector> commit_queue; + std::shared_ptr> icon; + + steamcompmgr_win_type_t type; + + std::optional oulTargetVROverlay; + std::shared_ptr pForwarderPlane; + bool bNeedsForwarding = false; + + steamcompmgr_xwayland_win_t &xwayland( ) + { return std::get( _window_types ); } + const steamcompmgr_xwayland_win_t &xwayland( ) const + { return std::get( _window_types ); } + + steamcompmgr_xdg_win_t &xdg( ) + { return std::get( _window_types ); } + const steamcompmgr_xdg_win_t &xdg( ) const + { return std::get( _window_types ); } + + std::variant + _window_types; + + focus_t *GetFocus( ) const + { + if ( type == steamcompmgr_win_type_t::XWAYLAND ) + return &xwayland( ).ctx->focus; + else if ( type == steamcompmgr_win_type_t::XDG ) + return &g_steamcompmgr_xdg_focus; + else + return nullptr; + } + + Rect GetGeometry( ) const + { + if ( type == steamcompmgr_win_type_t::XWAYLAND ) + return Rect{ xwayland( ).a.x, + xwayland( ).a.y, + xwayland( ).a.width, + xwayland( ).a.height }; + else if ( type == steamcompmgr_win_type_t::XDG ) + return Rect{ xdg( ).geometry.x, + xdg( ).geometry.y, + xdg( ).geometry.width, + xdg( ).geometry.height }; + else + return Rect{}; + } + + uint32_t id( ) const + { + if ( type == steamcompmgr_win_type_t::XWAYLAND ) + return uint32_t( xwayland( ).id ); + else if ( type == steamcompmgr_win_type_t::XDG ) + return xdg( ).id; + else + return ~( 0u ); + } + + wlr_surface *main_surface( ) const + { + if ( type == steamcompmgr_win_type_t::XWAYLAND ) + return xwayland( ).surface.main_surface; + else if ( type == steamcompmgr_win_type_t::XDG ) + return xdg( ).surface.main_surface; + else + return nullptr; + } + + wlr_surface *current_surface( ) const + { + if ( type == steamcompmgr_win_type_t::XWAYLAND ) + return xwayland( ).surface.current_surface( ); + + return main_surface( ); + } + + wlr_surface *override_surface( ) const + { + if ( type == steamcompmgr_win_type_t::XWAYLAND ) + return xwayland( ).surface.override_surface; + else + return nullptr; + } + + const char *debug_name( ) const + { + if ( title ) return title->c_str( ); + + return pid_name.c_str( ); + } + + gamescope::VirtualConnectorKey_t + GetVirtualConnectorKey( gamescope::VirtualConnectorStrategy eStrategy ) + { + switch ( eStrategy ) + { + default: + case gamescope::VirtualConnectorStrategies::SingleApplication: + case gamescope::VirtualConnectorStrategies::SteamControlled: + return 0; + case gamescope::VirtualConnectorStrategies::PerAppId: + if ( this->isSteamLegacyBigPicture ) + { + // Steam Bootstrapper + return gamescope::k_ulSteamBootstrapperKey; + } + else if ( this->appID ) + { + return static_cast( + this->appID ); + } + else + { + return static_cast( + gamescope::k_ulNonSteamWindowBit | this->seq ); + } + case gamescope::VirtualConnectorStrategies::PerWindow: + return static_cast( + this->seq ); + } + } }; extern std::atomic hasRepaint; namespace gamescope { - struct GamescopeScreenshotInfo - { - std::string szScreenshotPath; - gamescope_control_screenshot_type eScreenshotType = GAMESCOPE_CONTROL_SCREENSHOT_TYPE_BASE_PLANE_ONLY; - uint32_t uScreenshotFlags = 0; - bool bX11PropertyRequested = false; - bool bWaylandRequested = false; - }; - - class CScreenshotManager - { - public: - void TakeScreenshot( GamescopeScreenshotInfo info = GamescopeScreenshotInfo{} ) - { - std::unique_lock lock{ m_ScreenshotInfoMutex }; - m_ScreenshotInfo = std::move( info ); - hasRepaint = true; - } - - void TakeScreenshot( bool bAVIF ) - { - char szTimeBuffer[ 1024 ]; - time_t currentTime = time(0); - struct tm *pLocalTime = localtime( ¤tTime ); - strftime( szTimeBuffer, sizeof( szTimeBuffer ), bAVIF ? "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.avif" : "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.png", pLocalTime ); - - TakeScreenshot( GamescopeScreenshotInfo - { - .szScreenshotPath = szTimeBuffer, - } ); - } - - std::optional ProcessPendingScreenshot() - { - std::unique_lock lock{ m_ScreenshotInfoMutex }; - return std::exchange( m_ScreenshotInfo, std::nullopt ); - } - - static CScreenshotManager &Get(); - private: - std::mutex m_ScreenshotInfoMutex; - std::optional m_ScreenshotInfo; - }; - - extern CScreenshotManager g_ScreenshotMgr; -} + struct GamescopeScreenshotInfo + { + std::string szScreenshotPath; + gamescope_control_screenshot_type eScreenshotType = + GAMESCOPE_CONTROL_SCREENSHOT_TYPE_BASE_PLANE_ONLY; + uint32_t uScreenshotFlags = 0; + bool bX11PropertyRequested = false; + bool bWaylandRequested = false; + }; + + class CScreenshotManager + { + public: + void TakeScreenshot( + GamescopeScreenshotInfo info = GamescopeScreenshotInfo{} ) + { + std::unique_lock lock{ m_ScreenshotInfoMutex }; + m_ScreenshotInfo = std::move( info ); + hasRepaint = true; + } + + void TakeScreenshot( bool bAVIF ) + { + char szTimeBuffer[ 1024 ]; + time_t currentTime = time( 0 ); + struct tm *pLocalTime = localtime( ¤tTime ); + strftime( + szTimeBuffer, + sizeof( szTimeBuffer ), + bAVIF ? "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.avif" + : "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.png", + pLocalTime ); + + TakeScreenshot( + GamescopeScreenshotInfo{ + .szScreenshotPath = szTimeBuffer, + } ); + } + + std::optional ProcessPendingScreenshot( ) + { + std::unique_lock lock{ m_ScreenshotInfoMutex }; + return std::exchange( m_ScreenshotInfo, std::nullopt ); + } + + static CScreenshotManager &Get( ); + + private: + std::mutex m_ScreenshotInfoMutex; + std::optional m_ScreenshotInfo; + }; + + extern CScreenshotManager g_ScreenshotMgr; +} // namespace gamescope diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp index f036d000a8..0a6eed9b68 100644 --- a/src/vblankmanager.cpp +++ b/src/vblankmanager.cpp @@ -1,12 +1,13 @@ -// Try to figure out when vblank is and notify steamcompmgr to render some time before it +// Try to figure out when vblank is and notify steamcompmgr to render some time +// before it +#include +#include +#include #include #include #include #include -#include -#include -#include #include #include @@ -14,397 +15,399 @@ #include "gpuvis_trace_utils.h" -#include "vblankmanager.hpp" -#include "steamcompmgr.hpp" #include "main.hpp" #include "refresh_rate.h" +#include "steamcompmgr.hpp" +#include "vblankmanager.hpp" -LogScope g_VBlankLog("vblank"); +LogScope g_VBlankLog( "vblank" ); namespace gamescope { - ConVar vblank_debug( "vblank_debug", false, "Enable vblank debug spew to stderr." ); - - CVBlankTimer::CVBlankTimer() - { - m_ulTargetVBlank = get_time_in_nanos(); - m_ulLastVBlank = m_ulTargetVBlank; - - if ( !GetBackend()->NeedsFrameSync() ) - { - // Majority of backends fall down this optimal - // timerfd path, vs nudge thread. - g_VBlankLog.infof( "Using timerfd." ); - } - else - { - g_VBlankLog.infof( "Using nudge thread." ); - - if ( pipe2( m_nNudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) - { - g_VBlankLog.errorf_errno( "Failed to create VBlankTimer pipe." ); - abort(); - } - - std::thread vblankThread( [this]() { this->NudgeThread(); } ); - vblankThread.detach(); - } - } - - CVBlankTimer::~CVBlankTimer() - { - std::unique_lock lock( m_ScheduleMutex ); - - m_bRunning = false; - - m_bArmed = true; - m_bArmed.notify_all(); - - for ( int i = 0; i < 2; i++ ) - { - if ( m_nNudgePipe[ i ] >= 0 ) - { - close ( m_nNudgePipe[ i ] ); - m_nNudgePipe[ i ] = -1; - } - } - } - - int CVBlankTimer::GetRefresh() const - { - return g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; - } - - uint64_t CVBlankTimer::GetLastVBlank() const - { - return m_ulLastVBlank; - } - - uint64_t CVBlankTimer::GetNextVBlank( uint64_t ulOffset ) const - { - const uint64_t ulIntervalNSecs = mHzToRefreshCycle( GetRefresh() ); - const uint64_t ulNow = get_time_in_nanos(); - - uint64_t ulTargetPoint = GetLastVBlank() + ulIntervalNSecs - ulOffset; - - while ( ulTargetPoint < ulNow ) - ulTargetPoint += ulIntervalNSecs; - - return ulTargetPoint; - } - - VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) - { - const GamescopeScreenType eScreenType = GetBackend()->GetScreenType(); - - const int nRefreshRate = GetRefresh(); - const uint64_t ulRefreshInterval = mHzToRefreshCycle( nRefreshRate ); - - bool bVRR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->IsVRRActive(); - uint64_t ulOffset = 0; - if ( !bVRR ) - { - // The redzone is relative to 60Hz for external displays. - // Scale it by our target refresh so we don't miss submitting for - // vblank in DRM. - // (This fixes wonky frame-pacing on 4K@30Hz screens) - // - // TODO(Josh): Is this fudging still needed with our SteamOS kernel patches - // to not account for vertical front porch when dealing with the vblank - // drm_commit is going to target? - // Need to re-test that. - const uint64_t ulRedZone = eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL - ? m_ulVBlankDrawBufferRedZone - : std::min( m_ulVBlankDrawBufferRedZone, ( m_ulVBlankDrawBufferRedZone * 60'000 * nRefreshRate ) / 60'000 ); - - const uint64_t ulDecayAlpha = m_ulVBlankRateOfDecayPercentage; // eg. 980 = 98% - - uint64_t ulDrawTime = m_ulLastDrawTime; - /// See comment of m_ulVBlankDrawTimeMinCompositing. - if ( m_bCurrentlyCompositing ) - ulDrawTime = std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); - - uint64_t ulNewRollingDrawTime; - // This is a rolling average when ulDrawTime < m_ulRollingMaxDrawTime, - // and a maximum when ulDrawTime > m_ulRollingMaxDrawTime. - // - // This allows us to deal with spikes in the draw buffer time very easily. - // eg. if we suddenly spike up (eg. because of test commits taking a stupid long time), - // we will then be able to deal with spikes in the long term, even if several commits after - // we get back into a good state and then regress again. - - // If we go over half of our deadzone, be more defensive about things and - // spike up back to our current drawtime (sawtooth). - if ( int64_t( ulDrawTime ) - int64_t( ulRedZone / 2 ) > int64_t( m_ulRollingMaxDrawTime ) ) - ulNewRollingDrawTime = ulDrawTime; - else - ulNewRollingDrawTime = ( ( ulDecayAlpha * m_ulRollingMaxDrawTime ) + ( kVBlankRateOfDecayMax - ulDecayAlpha ) * ulDrawTime ) / kVBlankRateOfDecayMax; - - // If we need to offset for our draw more than half of our vblank, something is very wrong. - // Clamp our max time to half of the vblank if we can. - ulNewRollingDrawTime = std::min( ulNewRollingDrawTime, ulRefreshInterval - ulRedZone ); - - // If this is not a pre-emptive re-arming, then update - // the rolling internal max draw time for next time. - if ( !bPreemptive ) - m_ulRollingMaxDrawTime = ulNewRollingDrawTime; - - ulOffset = ulNewRollingDrawTime + ulRedZone; - - if ( vblank_debug && !bPreemptive ) - VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); - } - else - { - // See above. - if ( !bPreemptive ) - { - // Reset the max draw time to default, it is unused for VRR. - m_ulRollingMaxDrawTime = kStartingVBlankDrawTime; - } - - uint64_t ulRedZone = kVRRFlushingTime; - - uint64_t ulDrawTime = 0; - /// See comment of m_ulVBlankDrawTimeMinCompositing. - if ( m_bCurrentlyCompositing ) - ulDrawTime = std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); - - ulOffset = ulDrawTime + ulRedZone; - - if ( vblank_debug && !bPreemptive ) - VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); - } - - const uint64_t ulScheduledWakeupPoint = GetNextVBlank( ulOffset ); - const uint64_t ulTargetVBlank = ulScheduledWakeupPoint + ulOffset; - - VBlankScheduleTime schedule = - { - .ulTargetVBlank = ulTargetVBlank, - .ulScheduledWakeupPoint = ulScheduledWakeupPoint, - }; - return schedule; - } - - std::optional CVBlankTimer::ProcessVBlank() - { - return std::exchange( m_PendingVBlank, std::nullopt ); - } - - void CVBlankTimer::MarkVBlank( uint64_t ulNanos, bool bReArmTimer ) - { - m_ulLastVBlank = ulNanos; - if ( bReArmTimer ) - { - // Force timer re-arm with the new vblank timings. - ArmNextVBlank( false ); - } - } - - bool CVBlankTimer::WasCompositing() const - { - return m_bCurrentlyCompositing; - } - - void CVBlankTimer::UpdateWasCompositing( bool bCompositing ) - { - m_bCurrentlyCompositing = bCompositing; - } - - void CVBlankTimer::UpdateLastDrawTime( uint64_t ulNanos ) - { - m_ulLastDrawTime = ulNanos; - } - - void CVBlankTimer::WaitToBeArmed() - { - // Wait for m_bArmed to change *from* false. - m_bArmed.wait( false ); - } - - void CVBlankTimer::ArmNextVBlank( bool bPreemptive ) - { - std::unique_lock lock( m_ScheduleMutex ); - - // If we're pre-emptively re-arming, don't - // do anything if we are already armed. - if ( bPreemptive && m_bArmed ) - return; - - m_bArmed = true; - m_bArmed.notify_all(); - - if ( UsingTimerFD() ) - { - m_TimerFDSchedule = CalcNextWakeupTime( bPreemptive ); - - ITimerWaitable::ArmTimer( m_TimerFDSchedule.ulScheduledWakeupPoint ); - } - } - - bool CVBlankTimer::UsingTimerFD() const - { - return m_nNudgePipe[ 0 ] < 0; - } - - int CVBlankTimer::GetFD() - { - return UsingTimerFD() ? ITimerWaitable::GetFD() : m_nNudgePipe[ 0 ]; - } - - void CVBlankTimer::OnPollIn() - { - if ( UsingTimerFD() ) - { - std::unique_lock lock( m_ScheduleMutex ); - - // Disarm the timer if it was armed. - if ( !m_bArmed.exchange( false ) ) - return; - - - m_PendingVBlank = VBlankTime - { - .schedule = m_TimerFDSchedule, - // One might think this should just be 'now', however consider the fact - // that the effective draw-time should also include the scheduling quantums - // and any work before we reached this poll. - // The old path used to be be on its own thread, simply awaking from sleep - // then writing to a pipe and going back to sleep, the wakeup time was before we - // did the write, so we included the quantum of pipe nudge -> wakeup. - // Doing this aims to include that, like we were before, but with timerfd. - .ulWakeupTime = m_TimerFDSchedule.ulScheduledWakeupPoint, - }; - - gpuvis_trace_printf( "vblank timerfd wakeup" ); - - ITimerWaitable::DisarmTimer(); - } - else - { - VBlankTime time{}; - for ( ;; ) - { - ssize_t ret = read( m_nNudgePipe[ 0 ], &time, sizeof( time ) ); - - if ( ret < 0 ) - { - if ( errno == EAGAIN ) - continue; - - g_VBlankLog.errorf_errno( "Failed to read nudge pipe. Pre-emptively re-arming." ); - ArmNextVBlank( true ); - return; - } - else if ( ret != sizeof( VBlankTime ) ) - { - g_VBlankLog.errorf( "Nudge pipe had less data than sizeof( VBlankTime ). Pre-emptively re-arming." ); - ArmNextVBlank( true ); - return; - } - else - { - break; - } - } - - uint64_t ulDiff = get_time_in_nanos() - time.ulWakeupTime; - if ( ulDiff > 1'000'000ul ) - { - gpuvis_trace_printf( "Ignoring stale vblank... Pre-emptively re-arming." ); - ArmNextVBlank( true ); - return; - } - - gpuvis_trace_printf( "got vblank" ); - m_PendingVBlank = time; - } - } - - void CVBlankTimer::VBlankDebugSpew( uint64_t ulOffset, uint64_t ulDrawTime, uint64_t ulRedZone ) - { - static uint64_t s_ulVBlankID = 0; - static uint64_t s_ulLastDrawTime = kStartingVBlankDrawTime; - static uint64_t s_ulLastOffset = kStartingVBlankDrawTime + ulRedZone; - - if ( s_ulVBlankID++ % 300 == 0 || ulDrawTime > s_ulLastOffset ) - { - if ( ulDrawTime > s_ulLastOffset ) - g_VBlankLog.infof( " !! missed vblank " ); - - g_VBlankLog.infof( "redZone: %.2fms decayRate: %lu%% - rollingMaxDrawTime: %.2fms lastDrawTime: %.2fms lastOffset: %.2fms - drawTime: %.2fms offset: %.2fms", - ulRedZone / 1'000'000.0, - m_ulVBlankRateOfDecayPercentage, - m_ulRollingMaxDrawTime / 1'000'000.0, - s_ulLastDrawTime / 1'000'000.0, - s_ulLastOffset / 1'000'000.0, - ulDrawTime / 1'000'000.0, - ulOffset / 1'000'000.0 ); - } - - s_ulLastDrawTime = ulDrawTime; - s_ulLastOffset = ulOffset; - } - - void CVBlankTimer::NudgeThread() - { - pthread_setname_np( pthread_self(), "gamescope-vblk" ); - - for ( ;; ) - { - WaitToBeArmed(); - - if ( !m_bRunning ) - return; - - VBlankScheduleTime schedule; - if ( GetBackend()->GetCurrentConnector() ) - { - schedule = GetBackend()->GetCurrentConnector()->FrameSync(); - } - else - { - // If we don't currently have a connector, make up some dummy refresh cycle. - sleep_for_nanos( mHzToRefreshCycle( g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh ) ); - uint64_t ulNow = get_time_in_nanos(); - schedule = VBlankScheduleTime - { - .ulTargetVBlank = ulNow + 3'000'000, - .ulScheduledWakeupPoint = ulNow, - }; - } - - const uint64_t ulWakeupTime = get_time_in_nanos(); - { - std::unique_lock lock( m_ScheduleMutex ); - - // Unarm, we are processing now! - m_bArmed = false; - - VBlankTime timeInfo = - { - .schedule = schedule, - .ulWakeupTime = ulWakeupTime, - }; - - ssize_t ret = write( m_nNudgePipe[ 1 ], &timeInfo, sizeof( timeInfo ) ); - if ( ret <= 0 ) - { - g_VBlankLog.errorf_errno( "Nudge write failed" ); - } - else - { - gpuvis_trace_printf( "sent vblank (nudge thread)" ); - } - } - } - } -} - -gamescope::CVBlankTimer &GetVBlankTimer() + ConVar vblank_debug( + "vblank_debug", false, "Enable vblank debug spew to stderr." ); + + CVBlankTimer::CVBlankTimer( ) + { + m_ulTargetVBlank = get_time_in_nanos( ); + m_ulLastVBlank = m_ulTargetVBlank; + + if ( !GetBackend( )->NeedsFrameSync( ) ) + { + // Majority of backends fall down this optimal + // timerfd path, vs nudge thread. + g_VBlankLog.infof( "Using timerfd." ); + } + else + { + g_VBlankLog.infof( "Using nudge thread." ); + + if ( pipe2( m_nNudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) + { + g_VBlankLog.errorf_errno( + "Failed to create VBlankTimer pipe." ); + abort( ); + } + + std::thread vblankThread( [ this ]( ) { this->NudgeThread( ); } ); + vblankThread.detach( ); + } + } + + CVBlankTimer::~CVBlankTimer( ) + { + std::unique_lock lock( m_ScheduleMutex ); + + m_bRunning = false; + + m_bArmed = true; + m_bArmed.notify_all( ); + + for ( int i = 0; i < 2; i++ ) + { + if ( m_nNudgePipe[ i ] >= 0 ) + { + close( m_nNudgePipe[ i ] ); + m_nNudgePipe[ i ] = -1; + } + } + } + + int CVBlankTimer::GetRefresh( ) const + { return g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; } + + uint64_t CVBlankTimer::GetLastVBlank( ) const { return m_ulLastVBlank; } + + uint64_t CVBlankTimer::GetNextVBlank( uint64_t ulOffset ) const + { + const uint64_t ulIntervalNSecs = mHzToRefreshCycle( GetRefresh( ) ); + const uint64_t ulNow = get_time_in_nanos( ); + + uint64_t ulTargetPoint = GetLastVBlank( ) + ulIntervalNSecs - ulOffset; + + while ( ulTargetPoint < ulNow ) + ulTargetPoint += ulIntervalNSecs; + + return ulTargetPoint; + } + + VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) + { + const GamescopeScreenType eScreenType = GetBackend( )->GetScreenType( ); + + const int nRefreshRate = GetRefresh( ); + const uint64_t ulRefreshInterval = mHzToRefreshCycle( nRefreshRate ); + + bool bVRR = GetBackend( )->GetCurrentConnector( ) && + GetBackend( )->GetCurrentConnector( )->IsVRRActive( ); + uint64_t ulOffset = 0; + if ( !bVRR ) + { + // The redzone is relative to 60Hz for external displays. + // Scale it by our target refresh so we don't miss submitting for + // vblank in DRM. + // (This fixes wonky frame-pacing on 4K@30Hz screens) + // + // TODO(Josh): Is this fudging still needed with our SteamOS kernel + // patches to not account for vertical front porch when dealing with + // the vblank drm_commit is going to target? Need to re-test that. + const uint64_t ulRedZone = + eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL + ? m_ulVBlankDrawBufferRedZone + : std::min( + m_ulVBlankDrawBufferRedZone, + ( m_ulVBlankDrawBufferRedZone * 60'000 * + nRefreshRate ) / + 60'000 ); + + const uint64_t ulDecayAlpha = + m_ulVBlankRateOfDecayPercentage; // eg. 980 = 98% + + uint64_t ulDrawTime = m_ulLastDrawTime; + /// See comment of m_ulVBlankDrawTimeMinCompositing. + if ( m_bCurrentlyCompositing ) + ulDrawTime = + std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); + + uint64_t ulNewRollingDrawTime; + // This is a rolling average when ulDrawTime < + // m_ulRollingMaxDrawTime, and a maximum when ulDrawTime > + // m_ulRollingMaxDrawTime. + // + // This allows us to deal with spikes in the draw buffer time very + // easily. eg. if we suddenly spike up (eg. because of test commits + // taking a stupid long time), we will then be able to deal with + // spikes in the long term, even if several commits after we get + // back into a good state and then regress again. + + // If we go over half of our deadzone, be more defensive about + // things and spike up back to our current drawtime (sawtooth). + if ( int64_t( ulDrawTime ) - int64_t( ulRedZone / 2 ) > + int64_t( m_ulRollingMaxDrawTime ) ) + ulNewRollingDrawTime = ulDrawTime; + else + ulNewRollingDrawTime = + ( ( ulDecayAlpha * m_ulRollingMaxDrawTime ) + + ( kVBlankRateOfDecayMax - ulDecayAlpha ) * ulDrawTime ) / + kVBlankRateOfDecayMax; + + // If we need to offset for our draw more than half of our vblank, + // something is very wrong. Clamp our max time to half of the vblank + // if we can. + ulNewRollingDrawTime = + std::min( ulNewRollingDrawTime, ulRefreshInterval - ulRedZone ); + + // If this is not a pre-emptive re-arming, then update + // the rolling internal max draw time for next time. + if ( !bPreemptive ) m_ulRollingMaxDrawTime = ulNewRollingDrawTime; + + ulOffset = ulNewRollingDrawTime + ulRedZone; + + if ( vblank_debug && !bPreemptive ) + VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); + } + else + { + // See above. + if ( !bPreemptive ) + { + // Reset the max draw time to default, it is unused for VRR. + m_ulRollingMaxDrawTime = kStartingVBlankDrawTime; + } + + uint64_t ulRedZone = kVRRFlushingTime; + + uint64_t ulDrawTime = 0; + /// See comment of m_ulVBlankDrawTimeMinCompositing. + if ( m_bCurrentlyCompositing ) + ulDrawTime = + std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); + + ulOffset = ulDrawTime + ulRedZone; + + if ( vblank_debug && !bPreemptive ) + VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); + } + + const uint64_t ulScheduledWakeupPoint = GetNextVBlank( ulOffset ); + const uint64_t ulTargetVBlank = ulScheduledWakeupPoint + ulOffset; + + VBlankScheduleTime schedule = { + .ulTargetVBlank = ulTargetVBlank, + .ulScheduledWakeupPoint = ulScheduledWakeupPoint, + }; + return schedule; + } + + std::optional CVBlankTimer::ProcessVBlank( ) + { return std::exchange( m_PendingVBlank, std::nullopt ); } + + void CVBlankTimer::MarkVBlank( uint64_t ulNanos, bool bReArmTimer ) + { + m_ulLastVBlank = ulNanos; + if ( bReArmTimer ) + { + // Force timer re-arm with the new vblank timings. + ArmNextVBlank( false ); + } + } + + bool CVBlankTimer::WasCompositing( ) const + { return m_bCurrentlyCompositing; } + + void CVBlankTimer::UpdateWasCompositing( bool bCompositing ) + { m_bCurrentlyCompositing = bCompositing; } + + void CVBlankTimer::UpdateLastDrawTime( uint64_t ulNanos ) + { m_ulLastDrawTime = ulNanos; } + + void CVBlankTimer::WaitToBeArmed( ) + { + // Wait for m_bArmed to change *from* false. + m_bArmed.wait( false ); + } + + void CVBlankTimer::ArmNextVBlank( bool bPreemptive ) + { + std::unique_lock lock( m_ScheduleMutex ); + + // If we're pre-emptively re-arming, don't + // do anything if we are already armed. + if ( bPreemptive && m_bArmed ) return; + + m_bArmed = true; + m_bArmed.notify_all( ); + + if ( UsingTimerFD( ) ) + { + m_TimerFDSchedule = CalcNextWakeupTime( bPreemptive ); + + ITimerWaitable::ArmTimer( + m_TimerFDSchedule.ulScheduledWakeupPoint ); + } + } + + bool CVBlankTimer::UsingTimerFD( ) const { return m_nNudgePipe[ 0 ] < 0; } + + int CVBlankTimer::GetFD( ) + { return UsingTimerFD( ) ? ITimerWaitable::GetFD( ) : m_nNudgePipe[ 0 ]; } + + void CVBlankTimer::OnPollIn( ) + { + if ( UsingTimerFD( ) ) + { + std::unique_lock lock( m_ScheduleMutex ); + + // Disarm the timer if it was armed. + if ( !m_bArmed.exchange( false ) ) return; + + m_PendingVBlank = VBlankTime{ + .schedule = m_TimerFDSchedule, + // One might think this should just be 'now', however consider + // the fact that the effective draw-time should also include the + // scheduling quantums and any work before we reached this poll. + // The old path used to be be on its own thread, simply awaking + // from sleep then writing to a pipe and going back to sleep, + // the wakeup time was before we did the write, so we included + // the quantum of pipe nudge -> wakeup. Doing this aims to + // include that, like we were before, but with timerfd. + .ulWakeupTime = m_TimerFDSchedule.ulScheduledWakeupPoint, + }; + + gpuvis_trace_printf( "vblank timerfd wakeup" ); + + ITimerWaitable::DisarmTimer( ); + } + else + { + VBlankTime time{}; + for ( ;; ) + { + ssize_t ret = read( m_nNudgePipe[ 0 ], &time, sizeof( time ) ); + + if ( ret < 0 ) + { + if ( errno == EAGAIN ) continue; + + g_VBlankLog.errorf_errno( + "Failed to read nudge pipe. Pre-emptively re-arming." ); + ArmNextVBlank( true ); + return; + } + else if ( ret != sizeof( VBlankTime ) ) + { + g_VBlankLog.errorf( + "Nudge pipe had less data than sizeof( VBlankTime ). " + "Pre-emptively re-arming." ); + ArmNextVBlank( true ); + return; + } + else + { + break; + } + } + + uint64_t ulDiff = get_time_in_nanos( ) - time.ulWakeupTime; + if ( ulDiff > 1'000'000ul ) + { + gpuvis_trace_printf( + "Ignoring stale vblank... Pre-emptively re-arming." ); + ArmNextVBlank( true ); + return; + } + + gpuvis_trace_printf( "got vblank" ); + m_PendingVBlank = time; + } + } + + void CVBlankTimer::VBlankDebugSpew( + uint64_t ulOffset, uint64_t ulDrawTime, uint64_t ulRedZone ) + { + static uint64_t s_ulVBlankID = 0; + static uint64_t s_ulLastDrawTime = kStartingVBlankDrawTime; + static uint64_t s_ulLastOffset = kStartingVBlankDrawTime + ulRedZone; + + if ( s_ulVBlankID++ % 300 == 0 || ulDrawTime > s_ulLastOffset ) + { + if ( ulDrawTime > s_ulLastOffset ) + g_VBlankLog.infof( " !! missed vblank " ); + + g_VBlankLog.infof( + "redZone: %.2fms decayRate: %lu%% - rollingMaxDrawTime: %.2fms " + "lastDrawTime: %.2fms lastOffset: %.2fms - drawTime: %.2fms " + "offset: %.2fms", + ulRedZone / 1'000'000.0, + m_ulVBlankRateOfDecayPercentage, + m_ulRollingMaxDrawTime / 1'000'000.0, + s_ulLastDrawTime / 1'000'000.0, + s_ulLastOffset / 1'000'000.0, + ulDrawTime / 1'000'000.0, + ulOffset / 1'000'000.0 ); + } + + s_ulLastDrawTime = ulDrawTime; + s_ulLastOffset = ulOffset; + } + + void CVBlankTimer::NudgeThread( ) + { + pthread_setname_np( pthread_self( ), "gamescope-vblk" ); + + for ( ;; ) + { + WaitToBeArmed( ); + + if ( !m_bRunning ) return; + + VBlankScheduleTime schedule; + if ( GetBackend( )->GetCurrentConnector( ) ) + { + schedule = GetBackend( )->GetCurrentConnector( )->FrameSync( ); + } + else + { + // If we don't currently have a connector, make up some dummy + // refresh cycle. + sleep_for_nanos( mHzToRefreshCycle( + g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh ) ); + uint64_t ulNow = get_time_in_nanos( ); + schedule = VBlankScheduleTime{ + .ulTargetVBlank = ulNow + 3'000'000, + .ulScheduledWakeupPoint = ulNow, + }; + } + + const uint64_t ulWakeupTime = get_time_in_nanos( ); + { + std::unique_lock lock( m_ScheduleMutex ); + + // Unarm, we are processing now! + m_bArmed = false; + + VBlankTime timeInfo = { + .schedule = schedule, + .ulWakeupTime = ulWakeupTime, + }; + + ssize_t ret = + write( m_nNudgePipe[ 1 ], &timeInfo, sizeof( timeInfo ) ); + if ( ret <= 0 ) + { + g_VBlankLog.errorf_errno( "Nudge write failed" ); + } + else + { + gpuvis_trace_printf( "sent vblank (nudge thread)" ); + } + } + } + } +} // namespace gamescope + +gamescope::CVBlankTimer &GetVBlankTimer( ) { static gamescope::CVBlankTimer s_VBlankTimer; return s_VBlankTimer; } - diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp index 582f0e2a57..93ebe6a142 100644 --- a/src/vblankmanager.hpp +++ b/src/vblankmanager.hpp @@ -31,44 +31,48 @@ namespace gamescope // VBlank timer defaults and starting values. // Anything time-related is nanoseconds unless otherwise specified. static constexpr uint64_t kStartingVBlankDrawTime = 3'000'000ul; - static constexpr uint64_t kDefaultMinVBlankTime = 350'000ul; - static constexpr uint64_t kDefaultVBlankRedZone = 1'650'000ul; - static constexpr uint64_t kDefaultVBlankDrawTimeMinCompositing = 2'400'000ul; - static constexpr uint64_t kDefaultVBlankRateOfDecayPercentage = 980ul; // 98% + static constexpr uint64_t kDefaultMinVBlankTime = 350'000ul; + static constexpr uint64_t kDefaultVBlankRedZone = 1'650'000ul; + static constexpr uint64_t kDefaultVBlankDrawTimeMinCompositing = + 2'400'000ul; + static constexpr uint64_t kDefaultVBlankRateOfDecayPercentage = + 980ul; // 98% static constexpr uint64_t kVBlankRateOfDecayMax = 1000ul; // 100% static constexpr uint64_t kVRRFlushingTime = 300'000; - CVBlankTimer(); - ~CVBlankTimer(); + CVBlankTimer( ); + ~CVBlankTimer( ); - int GetRefresh() const; - uint64_t GetLastVBlank() const; + int GetRefresh( ) const; + uint64_t GetLastVBlank( ) const; uint64_t GetNextVBlank( uint64_t ulOffset ) const; VBlankScheduleTime CalcNextWakeupTime( bool bPreemptive ); - void Reschedule(); + void Reschedule( ); - std::optional ProcessVBlank(); + std::optional ProcessVBlank( ); void MarkVBlank( uint64_t ulNanos, bool bReArmTimer ); - bool WasCompositing() const; + bool WasCompositing( ) const; void UpdateWasCompositing( bool bCompositing ); void UpdateLastDrawTime( uint64_t ulNanos ); - void WaitToBeArmed(); + void WaitToBeArmed( ); void ArmNextVBlank( bool bPreemptive ); - bool UsingTimerFD() const; - int GetFD() final; - void OnPollIn() final; + bool UsingTimerFD( ) const; + int GetFD( ) final; + void OnPollIn( ) final; + private: - void VBlankDebugSpew( uint64_t ulOffset, uint64_t ulDrawTime, uint64_t ulRedZone ); + void VBlankDebugSpew( + uint64_t ulOffset, uint64_t ulDrawTime, uint64_t ulRedZone ); - uint64_t m_ulTargetVBlank = 0; - std::atomic m_ulLastVBlank = { 0 }; - std::atomic m_bArmed = { false }; - std::atomic m_bRunning = { true }; + uint64_t m_ulTargetVBlank = 0; + std::atomic m_ulLastVBlank = { 0 }; + std::atomic m_bArmed = { false }; + std::atomic m_bRunning = { true }; std::optional m_PendingVBlank; @@ -78,11 +82,11 @@ namespace gamescope // m_bArmed is atomic so can still be .wait()'ed // on/read outside. // Does not cover m_ulLastVBlank, this is just atomic. - std::mutex m_ScheduleMutex; + std::mutex m_ScheduleMutex; VBlankScheduleTime m_TimerFDSchedule{}; std::thread m_NudgeThread; - int m_nNudgePipe[2] = { -1, -1 }; + int m_nNudgePipe[ 2 ] = { -1, -1 }; ///////////////////////////// // Scheduling bits and bobs. @@ -94,7 +98,8 @@ namespace gamescope std::atomic m_bCurrentlyCompositing = { false }; // This is the last time a 'draw' took from wake-up to page flip. // 3ms by default to get the ball rolling. - // This is calculated by steamcompmgr/drm and fed-back to the vblank timer. + // This is calculated by steamcompmgr/drm and fed-back to the vblank + // timer. std::atomic m_ulLastDrawTime = { kStartingVBlankDrawTime }; ////////////////////////////////// @@ -106,10 +111,11 @@ namespace gamescope // doing pre-emptive timer re-arms. uint64_t m_ulRollingMaxDrawTime = kStartingVBlankDrawTime; - // This accounts for some time we cannot account for (which (I think) is the drm_commit -> triggering the pageflip) - // It would be nice to make this lower if we can find a way to track that effectively - // Perhaps the missing time is spent elsewhere, but given we track from the pipe write - // to after the return from `drm_commit` -- I am very doubtful. + // This accounts for some time we cannot account for (which (I think) is + // the drm_commit -> triggering the pageflip) It would be nice to make + // this lower if we can find a way to track that effectively Perhaps the + // missing time is spent elsewhere, but given we track from the pipe + // write to after the return from `drm_commit` -- I am very doubtful. // 1.3ms by default. (kDefaultMinVBlankTime) uint64_t m_ulMinVBlankTime = kDefaultMinVBlankTime; @@ -118,19 +124,20 @@ namespace gamescope uint64_t m_ulVBlankDrawBufferRedZone = kDefaultVBlankRedZone; // The minimum drawtime to use when we are compositing. - // Getting closer and closer to vblank when compositing means that we can get into - // a feedback loop with our GPU clocks. Pick a sane minimum draw time. - // 2.4ms by default. (kDefaultVBlankDrawTimeMinCompositing) - uint64_t m_ulVBlankDrawTimeMinCompositing = kDefaultVBlankDrawTimeMinCompositing; - - // The rate of decay (as a percentage) of the rolling average -> current draw time - // 930 = 93%. - // 93% by default. (kDefaultVBlankRateOfDecayPercentage) - uint64_t m_ulVBlankRateOfDecayPercentage = kDefaultVBlankRateOfDecayPercentage; - - void NudgeThread(); + // Getting closer and closer to vblank when compositing means that we + // can get into a feedback loop with our GPU clocks. Pick a sane minimum + // draw time. 2.4ms by default. (kDefaultVBlankDrawTimeMinCompositing) + uint64_t m_ulVBlankDrawTimeMinCompositing = + kDefaultVBlankDrawTimeMinCompositing; + + // The rate of decay (as a percentage) of the rolling average -> current + // draw time 930 = 93%. 93% by default. + // (kDefaultVBlankRateOfDecayPercentage) + uint64_t m_ulVBlankRateOfDecayPercentage = + kDefaultVBlankRateOfDecayPercentage; + + void NudgeThread( ); }; -} - -gamescope::CVBlankTimer &GetVBlankTimer(); +} // namespace gamescope +gamescope::CVBlankTimer &GetVBlankTimer( ); diff --git a/src/waitable.h b/src/waitable.h index 30edf8f52d..d883479375 100644 --- a/src/waitable.h +++ b/src/waitable.h @@ -1,11 +1,11 @@ #pragma once -#include -#include #include -#include +#include #include #include +#include +#include #include #include @@ -21,40 +21,37 @@ namespace gamescope class IWaitable { public: - virtual ~IWaitable() {} + virtual ~IWaitable( ) {} - virtual int GetFD() { return -1; } + virtual int GetFD( ) { return -1; } - virtual void OnPollIn() {} - virtual void OnPollOut() {} - virtual void OnPollHangUp() + virtual void OnPollIn( ) {} + virtual void OnPollOut( ) {} + virtual void OnPollHangUp( ) { g_WaitableLog.errorf( "IWaitable hung up. Aborting." ); - abort(); + abort( ); } void HandleEvents( uint32_t nEvents ) { - if ( nEvents & EPOLLIN ) - this->OnPollIn(); - if ( nEvents & EPOLLOUT ) - this->OnPollOut(); - if ( nEvents & EPOLLHUP ) - this->OnPollHangUp(); + if ( nEvents & EPOLLIN ) this->OnPollIn( ); + if ( nEvents & EPOLLOUT ) this->OnPollOut( ); + if ( nEvents & EPOLLHUP ) this->OnPollHangUp( ); } static void Drain( int nFD ) { - if ( nFD < 0 ) - return; + if ( nFD < 0 ) return; - char buf[1024]; - for (;;) + char buf[ 1024 ]; + for ( ;; ) { if ( read( nFD, buf, sizeof( buf ) ) < 0 ) { if ( errno != EAGAIN ) - g_WaitableLog.errorf_errno( "Failed to drain CNudgeWaitable" ); + g_WaitableLog.errorf_errno( + "Failed to drain CNudgeWaitable" ); break; } } @@ -64,98 +61,76 @@ namespace gamescope class CNudgeWaitable final : public IWaitable { public: - CNudgeWaitable() + CNudgeWaitable( ) { - if ( pipe2( m_nFDs, O_CLOEXEC | O_NONBLOCK ) != 0 ) - Shutdown(); + if ( pipe2( m_nFDs, O_CLOEXEC | O_NONBLOCK ) != 0 ) Shutdown( ); } - ~CNudgeWaitable() - { - Shutdown(); - } + ~CNudgeWaitable( ) { Shutdown( ); } - void Shutdown() + void Shutdown( ) { for ( int i = 0; i < 2; i++ ) { - if ( m_nFDs[i] >= 0 ) + if ( m_nFDs[ i ] >= 0 ) { - close( m_nFDs[i] ); - m_nFDs[i] = -1; + close( m_nFDs[ i ] ); + m_nFDs[ i ] = -1; } } } - void Drain() - { - IWaitable::Drain( m_nFDs[0] ); - } + void Drain( ) { IWaitable::Drain( m_nFDs[ 0 ] ); } - void OnPollIn() final - { - Drain(); - } + void OnPollIn( ) final { Drain( ); } - bool Nudge() - { - return write( m_nFDs[1], "\n", 1 ) >= 0; - } + bool Nudge( ) { return write( m_nFDs[ 1 ], "\n", 1 ) >= 0; } + + int GetFD( ) final { return m_nFDs[ 0 ]; } - int GetFD() final { return m_nFDs[0]; } private: - int m_nFDs[2] = { -1, -1 }; + int m_nFDs[ 2 ] = { -1, -1 }; }; - class CFunctionWaitable final : public IWaitable { public: - CFunctionWaitable( int nFD, std::function fnPollFunc = nullptr ) - : m_nFD{ nFD } - , m_fnPollFunc{ fnPollFunc } - { - } + CFunctionWaitable( + int nFD, std::function fnPollFunc = nullptr ) : + m_nFD{ nFD }, m_fnPollFunc{ fnPollFunc } + {} - void OnPollIn() final + void OnPollIn( ) final { - if ( m_fnPollFunc ) - m_fnPollFunc(); + if ( m_fnPollFunc ) m_fnPollFunc( ); } - void Drain() - { - IWaitable::Drain( m_nFD ); - } + void Drain( ) { IWaitable::Drain( m_nFD ); } + + int GetFD( ) final { return m_nFD; } - int GetFD() final - { - return m_nFD; - } private: - int m_nFD; - std::function m_fnPollFunc; + int m_nFD; + std::function m_fnPollFunc; }; class ITimerWaitable : public IWaitable { public: - ITimerWaitable() + ITimerWaitable( ) { - m_nFD = timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC ); - if ( m_nFD < 0 ) - { - g_WaitableLog.errorf_errno( "Failed to create timerfd." ); - abort(); - } + m_nFD = + timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC ); + if ( m_nFD < 0 ) + { + g_WaitableLog.errorf_errno( "Failed to create timerfd." ); + abort( ); + } } - ~ITimerWaitable() - { - Shutdown(); - } + ~ITimerWaitable( ) { Shutdown( ); } - void Shutdown() + void Shutdown( ) { if ( m_nFD >= 0 ) { @@ -164,28 +139,25 @@ namespace gamescope } } - void ArmTimer( uint64_t ulScheduledWakeupTime, bool bRepeatingRelative = false ) + void ArmTimer( + uint64_t ulScheduledWakeupTime, bool bRepeatingRelative = false ) { - timespec wakeupTimeSpec = nanos_to_timespec( ulScheduledWakeupTime ); - - itimerspec timerspec = - { - .it_interval = bRepeatingRelative ? wakeupTimeSpec : timespec{}, - .it_value = bRepeatingRelative ? timespec{} : wakeupTimeSpec, - }; - if ( timerfd_settime( m_nFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) - g_WaitableLog.errorf_errno( "timerfd_settime failed!" ); - } + timespec wakeupTimeSpec = + nanos_to_timespec( ulScheduledWakeupTime ); - void DisarmTimer() - { - ArmTimer( 0ul, false ); + itimerspec timerspec = { + .it_interval = bRepeatingRelative ? wakeupTimeSpec : timespec{}, + .it_value = bRepeatingRelative ? timespec{} : wakeupTimeSpec, + }; + if ( timerfd_settime( m_nFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < + 0 ) + g_WaitableLog.errorf_errno( "timerfd_settime failed!" ); } - int GetFD() - { - return m_nFD; - } + void DisarmTimer( ) { ArmTimer( 0ul, false ); } + + int GetFD( ) { return m_nFD; } + private: int m_nFD = -1; }; @@ -193,41 +165,30 @@ namespace gamescope class CTimerFunction final : public ITimerWaitable { public: - CTimerFunction( std::function fnPollFunc ) - : m_fnPollFunc{ fnPollFunc } - { - } + CTimerFunction( std::function fnPollFunc ) : + m_fnPollFunc{ fnPollFunc } + {} + + void OnPollIn( ) final { m_fnPollFunc( ); } - void OnPollIn() final - { - m_fnPollFunc(); - } private: - std::function m_fnPollFunc; + std::function m_fnPollFunc; }; - template - class CWaiter + template class CWaiter { public: - CWaiter() - : m_nEpollFD{ epoll_create1( EPOLL_CLOEXEC ) } - { - AddWaitable( &m_NudgeWaitable ); - } + CWaiter( ) : m_nEpollFD{ epoll_create1( EPOLL_CLOEXEC ) } + { AddWaitable( &m_NudgeWaitable ); } - ~CWaiter() - { - Shutdown(); - } + ~CWaiter( ) { Shutdown( ); } - void Shutdown() + void Shutdown( ) { - if ( !m_bRunning ) - return; + if ( !m_bRunning ) return; m_bRunning = false; - Nudge(); + Nudge( ); if ( m_nEpollFD >= 0 ) { @@ -236,7 +197,8 @@ namespace gamescope } } - bool AddWaitable( IWaitable *pWaitable, uint32_t nEvents = EPOLLIN | EPOLLHUP ) + bool AddWaitable( + IWaitable *pWaitable, uint32_t nEvents = EPOLLIN | EPOLLHUP ) { epoll_event event = { @@ -247,7 +209,9 @@ namespace gamescope }, }; - if ( epoll_ctl( m_nEpollFD, EPOLL_CTL_ADD, pWaitable->GetFD(), &event ) != 0 ) + if ( epoll_ctl( + m_nEpollFD, EPOLL_CTL_ADD, pWaitable->GetFD( ), &event ) != + 0 ) { g_WaitableLog.errorf_errno( "Failed to add waitable" ); return false; @@ -258,34 +222,36 @@ namespace gamescope void RemoveWaitable( IWaitable *pWaitable ) { - epoll_ctl( m_nEpollFD, EPOLL_CTL_DEL, pWaitable->GetFD(), nullptr ); + epoll_ctl( + m_nEpollFD, EPOLL_CTL_DEL, pWaitable->GetFD( ), nullptr ); } int PollEvents( int nTimeOut = -1 ) { - epoll_event events[MaxEvents]; + epoll_event events[ MaxEvents ]; for ( ;; ) { - int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, nTimeOut ); + int nEventCount = + epoll_wait( m_nEpollFD, events, MaxEvents, nTimeOut ); - if ( !m_bRunning ) - return 0; + if ( !m_bRunning ) return 0; if ( nEventCount < 0 ) { - if ( errno == EAGAIN || errno == EINTR ) - continue; + if ( errno == EAGAIN || errno == EINTR ) continue; - g_WaitableLog.errorf_errno( "Failed to epoll_wait in CAsyncWaiter" ); + g_WaitableLog.errorf_errno( + "Failed to epoll_wait in CAsyncWaiter" ); return nEventCount; } for ( int i = 0; i < nEventCount; i++ ) { - epoll_event &event = events[i]; + epoll_event &event = events[ i ]; - IWaitable *pWaitable = reinterpret_cast( event.data.ptr ); + IWaitable *pWaitable = + reinterpret_cast( event.data.ptr ); pWaitable->HandleEvents( event.events ); } @@ -293,135 +259,122 @@ namespace gamescope } } - bool Nudge() - { - return m_NudgeWaitable.Nudge(); - } + bool Nudge( ) { return m_NudgeWaitable.Nudge( ); } - bool IsRunning() - { - return m_bRunning; - } + bool IsRunning( ) { return m_bRunning; } private: std::atomic m_bRunning = { true }; - CNudgeWaitable m_NudgeWaitable; + CNudgeWaitable m_NudgeWaitable; int m_nEpollFD = -1; }; - // A raw pointer class that's compatible with shared/unique_ptr + Rc semantics - // eg. .get(), etc. - // for compatibility with structures that use other types that assume ownership/lifetime - // in some way. - template - class CRawPointer + // A raw pointer class that's compatible with shared/unique_ptr + Rc + // semantics eg. .get(), etc. for compatibility with structures that use + // other types that assume ownership/lifetime in some way. + template class CRawPointer { public: - CRawPointer() {} + CRawPointer( ) {} CRawPointer( std::nullptr_t ) {} - CRawPointer( const CRawPointer &other ) - : m_pObject{ other.m_pObject } - { - } + CRawPointer( const CRawPointer &other ) : m_pObject{ other.m_pObject } + {} - CRawPointer( CRawPointer&& other ) - : m_pObject{ other.m_pObject } - { - other.m_pObject = nullptr; - } + CRawPointer( CRawPointer &&other ) : m_pObject{ other.m_pObject } + { other.m_pObject = nullptr; } - CRawPointer( T* pObject ) - : m_pObject{ pObject } - { - } + CRawPointer( T *pObject ) : m_pObject{ pObject } {} - CRawPointer& operator = ( std::nullptr_t ) + CRawPointer &operator=( std::nullptr_t ) { m_pObject = nullptr; return *this; } - CRawPointer& operator = ( const CRawPointer& other ) + CRawPointer &operator=( const CRawPointer &other ) { m_pObject = other.m_pObject; return *this; } - CRawPointer& operator = ( CRawPointer&& other ) + CRawPointer &operator=( CRawPointer &&other ) { this->m_pObject = other.m_pObject; other.m_pObject = nullptr; return *this; } - T& operator * () const { return *m_pObject; } - T* operator -> () const { return m_pObject; } - T* get() const { return m_pObject; } + T &operator*( ) const { return *m_pObject; } + T *operator->( ) const { return m_pObject; } + T *get( ) const { return m_pObject; } + + bool operator==( const CRawPointer &other ) const + { return m_pObject == other.m_pObject; } + bool operator!=( const CRawPointer &other ) const + { return m_pObject != other.m_pObject; } - bool operator == ( const CRawPointer& other ) const { return m_pObject == other.m_pObject; } - bool operator != ( const CRawPointer& other ) const { return m_pObject != other.m_pObject; } + bool operator==( T *pOther ) const { return m_pObject == pOther; } + bool operator!=( T *pOther ) const { return m_pObject == pOther; } - bool operator == ( T *pOther ) const { return m_pObject == pOther; } - bool operator != ( T *pOther ) const { return m_pObject == pOther; } + bool operator==( std::nullptr_t ) const { return m_pObject == nullptr; } + bool operator!=( std::nullptr_t ) const { return m_pObject != nullptr; } - bool operator == ( std::nullptr_t ) const { return m_pObject == nullptr; } - bool operator != ( std::nullptr_t ) const { return m_pObject != nullptr; } private: - T* m_pObject = nullptr; + T *m_pObject = nullptr; }; - template , size_t MaxEvents = 1024> + template< + typename WaitableType = CRawPointer, + size_t MaxEvents = 1024> class CAsyncWaiter : private CWaiter { public: - CAsyncWaiter( const char *pszThreadName ) - : m_Thread{ [cWaiter = this, cName = pszThreadName](){ cWaiter->WaiterThreadFunc(cName); } } + CAsyncWaiter( const char *pszThreadName ) : + m_Thread{ [ cWaiter = this, cName = pszThreadName ]( ) + { cWaiter->WaiterThreadFunc( cName ); } } { - if constexpr ( UseTracking() ) + if constexpr ( UseTracking( ) ) { m_AddedWaitables.reserve( 32 ); m_RemovedWaitables.reserve( 32 ); } } - ~CAsyncWaiter() - { - Shutdown(); - } + ~CAsyncWaiter( ) { Shutdown( ); } - void Shutdown() + void Shutdown( ) { - CWaiter::Shutdown(); + CWaiter::Shutdown( ); - if ( m_Thread.joinable() ) - m_Thread.join(); + if ( m_Thread.joinable( ) ) m_Thread.join( ); - if constexpr ( UseTracking() ) + if constexpr ( UseTracking( ) ) { { std::unique_lock lock( m_AddedWaitablesMutex ); - m_AddedWaitables.clear(); + m_AddedWaitables.clear( ); } { std::unique_lock lock( m_RemovedWaitablesMutex ); - m_RemovedWaitables.clear(); + m_RemovedWaitables.clear( ); } } } - bool AddWaitable( WaitableType pWaitable, uint32_t nEvents = EPOLLIN | EPOLLHUP ) + bool AddWaitable( + WaitableType pWaitable, uint32_t nEvents = EPOLLIN | EPOLLHUP ) { - if constexpr ( UseTracking() ) + if constexpr ( UseTracking( ) ) { - if ( !pWaitable->HasLiveReferences() ) - return false; + if ( !pWaitable->HasLiveReferences( ) ) return false; std::unique_lock lock( m_AddedWaitablesMutex ); - if ( !CWaiter::AddWaitable( pWaitable.get(), nEvents ) ) + if ( !CWaiter::AddWaitable( + pWaitable.get( ), nEvents ) ) return false; m_AddedWaitables.emplace_back( pWaitable ); @@ -429,46 +382,46 @@ namespace gamescope } else { - return CWaiter::AddWaitable( pWaitable.get(), nEvents ); + return CWaiter::AddWaitable( + pWaitable.get( ), nEvents ); } } void RemoveWaitable( WaitableType pWaitable ) { - if constexpr ( UseTracking() ) + if constexpr ( UseTracking( ) ) { - if ( !pWaitable->HasLiveReferences() ) - return; + if ( !pWaitable->HasLiveReferences( ) ) return; std::unique_lock lock( m_RemovedWaitablesMutex ); - m_RemovedWaitables.emplace_back( pWaitable.get() ); + m_RemovedWaitables.emplace_back( pWaitable.get( ) ); } - CWaiter::RemoveWaitable( pWaitable.get() ); + CWaiter::RemoveWaitable( pWaitable.get( ) ); } void WaiterThreadFunc( const char *pszThreadName ) { - pthread_setname_np( pthread_self(), pszThreadName ); + pthread_setname_np( pthread_self( ), pszThreadName ); - while ( this->IsRunning() ) + while ( this->IsRunning( ) ) { - CWaiter::PollEvents(); + CWaiter::PollEvents( ); - if constexpr ( UseTracking() ) + if constexpr ( UseTracking( ) ) { - std::scoped_lock lock( m_AddedWaitablesMutex, m_RemovedWaitablesMutex ); - for ( auto& pRemoved : m_RemovedWaitables ) + std::scoped_lock lock( + m_AddedWaitablesMutex, m_RemovedWaitablesMutex ); + for ( auto &pRemoved : m_RemovedWaitables ) std::erase( m_AddedWaitables, pRemoved ); - m_RemovedWaitables.clear(); + m_RemovedWaitables.clear( ); } } } + private: - static constexpr bool UseTracking() - { - return !std::is_same>::value; - } + static constexpr bool UseTracking( ) + { return !std::is_same>::value; } std::thread m_Thread; @@ -476,13 +429,11 @@ namespace gamescope // of objects (eg. shared_ptr) could be too short. // Eg. RemoveWaitable but still processing events, or about // to start processing events. - std::mutex m_AddedWaitablesMutex; + std::mutex m_AddedWaitablesMutex; std::vector m_AddedWaitables; - std::mutex m_RemovedWaitablesMutex; + std::mutex m_RemovedWaitablesMutex; std::vector m_RemovedWaitables; }; - -} - +} // namespace gamescope diff --git a/src/win32_styles.h b/src/win32_styles.h index 88741e2554..1df7041ddc 100644 --- a/src/win32_styles.h +++ b/src/win32_styles.h @@ -1,56 +1,58 @@ #pragma once -static constexpr uint32_t WS_OVERLAPPED = 0x00000000u; -static constexpr uint32_t WS_POPUP = 0x80000000u; -static constexpr uint32_t WS_CHILD = 0x40000000u; -static constexpr uint32_t WS_MINIMIZE = 0x20000000u; -static constexpr uint32_t WS_VISIBLE = 0x10000000u; -static constexpr uint32_t WS_DISABLED = 0x08000000u; -static constexpr uint32_t WS_CLIPSIBLINGS = 0x04000000u; -static constexpr uint32_t WS_CLIPCHILDREN = 0x02000000u; -static constexpr uint32_t WS_MAXIMIZE = 0x01000000u; -static constexpr uint32_t WS_BORDER = 0x00800000u; -static constexpr uint32_t WS_DLGFRAME = 0x00400000u; -static constexpr uint32_t WS_VSCROLL = 0x00200000u; -static constexpr uint32_t WS_HSCROLL = 0x00100000u; -static constexpr uint32_t WS_SYSMENU = 0x00080000u; -static constexpr uint32_t WS_THICKFRAME = 0x00040000u; -static constexpr uint32_t WS_GROUP = 0x00020000u; -static constexpr uint32_t WS_TABSTOP = 0x00010000u; -static constexpr uint32_t WS_MINIMIZEBOX = 0x00020000u; -static constexpr uint32_t WS_MAXIMIZEBOX = 0x00010000u; -static constexpr uint32_t WS_CAPTION = WS_BORDER | WS_DLGFRAME; -static constexpr uint32_t WS_TILED = WS_OVERLAPPED; -static constexpr uint32_t WS_ICONIC = WS_MINIMIZE; -static constexpr uint32_t WS_SIZEBOX = WS_THICKFRAME; -static constexpr uint32_t WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME| WS_MINIMIZEBOX | WS_MAXIMIZEBOX; -static constexpr uint32_t WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU; -static constexpr uint32_t WS_CHILDWINDOW = WS_CHILD; -static constexpr uint32_t WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW; +static constexpr uint32_t WS_OVERLAPPED = 0x00000000u; +static constexpr uint32_t WS_POPUP = 0x80000000u; +static constexpr uint32_t WS_CHILD = 0x40000000u; +static constexpr uint32_t WS_MINIMIZE = 0x20000000u; +static constexpr uint32_t WS_VISIBLE = 0x10000000u; +static constexpr uint32_t WS_DISABLED = 0x08000000u; +static constexpr uint32_t WS_CLIPSIBLINGS = 0x04000000u; +static constexpr uint32_t WS_CLIPCHILDREN = 0x02000000u; +static constexpr uint32_t WS_MAXIMIZE = 0x01000000u; +static constexpr uint32_t WS_BORDER = 0x00800000u; +static constexpr uint32_t WS_DLGFRAME = 0x00400000u; +static constexpr uint32_t WS_VSCROLL = 0x00200000u; +static constexpr uint32_t WS_HSCROLL = 0x00100000u; +static constexpr uint32_t WS_SYSMENU = 0x00080000u; +static constexpr uint32_t WS_THICKFRAME = 0x00040000u; +static constexpr uint32_t WS_GROUP = 0x00020000u; +static constexpr uint32_t WS_TABSTOP = 0x00010000u; +static constexpr uint32_t WS_MINIMIZEBOX = 0x00020000u; +static constexpr uint32_t WS_MAXIMIZEBOX = 0x00010000u; +static constexpr uint32_t WS_CAPTION = WS_BORDER | WS_DLGFRAME; +static constexpr uint32_t WS_TILED = WS_OVERLAPPED; +static constexpr uint32_t WS_ICONIC = WS_MINIMIZE; +static constexpr uint32_t WS_SIZEBOX = WS_THICKFRAME; +static constexpr uint32_t WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | + WS_SYSMENU | WS_THICKFRAME | + WS_MINIMIZEBOX | WS_MAXIMIZEBOX; +static constexpr uint32_t WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU; +static constexpr uint32_t WS_CHILDWINDOW = WS_CHILD; +static constexpr uint32_t WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW; -static constexpr uint32_t WS_EX_DLGMODALFRAME = 0x00000001u; -static constexpr uint32_t WS_EX_DRAGDETECT = 0x00000002u; // Undocumented -static constexpr uint32_t WS_EX_NOPARENTNOTIFY = 0x00000004u; -static constexpr uint32_t WS_EX_TOPMOST = 0x00000008u; -static constexpr uint32_t WS_EX_ACCEPTFILES = 0x00000010u; -static constexpr uint32_t WS_EX_TRANSPARENT = 0x00000020u; -static constexpr uint32_t WS_EX_MDICHILD = 0x00000040u; -static constexpr uint32_t WS_EX_TOOLWINDOW = 0x00000080u; -static constexpr uint32_t WS_EX_WINDOWEDGE = 0x00000100u; -static constexpr uint32_t WS_EX_CLIENTEDGE = 0x00000200u; -static constexpr uint32_t WS_EX_CONTEXTHELP = 0x00000400u; -static constexpr uint32_t WS_EX_RIGHT = 0x00001000u; -static constexpr uint32_t WS_EX_LEFT = 0x00000000u; -static constexpr uint32_t WS_EX_RTLREADING = 0x00002000u; -static constexpr uint32_t WS_EX_LTRREADING = 0x00000000u; -static constexpr uint32_t WS_EX_LEFTSCROLLBAR = 0x00004000u; -static constexpr uint32_t WS_EX_RIGHTSCROLLBAR = 0x00000000u; -static constexpr uint32_t WS_EX_CONTROLPARENT = 0x00010000u; -static constexpr uint32_t WS_EX_STATICEDGE = 0x00020000u; -static constexpr uint32_t WS_EX_APPWINDOW = 0x00040000u; -static constexpr uint32_t WS_EX_LAYERED = 0x00080000u; -static constexpr uint32_t WS_EX_NOINHERITLAYOUT = 0x00100000u; -static constexpr uint32_t WS_EX_NOREDIRECTIONBITMAP = 0x00200000u; -static constexpr uint32_t WS_EX_LAYOUTRTL = 0x00400000u; -static constexpr uint32_t WS_EX_COMPOSITED = 0x02000000u; -static constexpr uint32_t WS_EX_NOACTIVATE = 0x08000000u; +static constexpr uint32_t WS_EX_DLGMODALFRAME = 0x00000001u; +static constexpr uint32_t WS_EX_DRAGDETECT = 0x00000002u; // Undocumented +static constexpr uint32_t WS_EX_NOPARENTNOTIFY = 0x00000004u; +static constexpr uint32_t WS_EX_TOPMOST = 0x00000008u; +static constexpr uint32_t WS_EX_ACCEPTFILES = 0x00000010u; +static constexpr uint32_t WS_EX_TRANSPARENT = 0x00000020u; +static constexpr uint32_t WS_EX_MDICHILD = 0x00000040u; +static constexpr uint32_t WS_EX_TOOLWINDOW = 0x00000080u; +static constexpr uint32_t WS_EX_WINDOWEDGE = 0x00000100u; +static constexpr uint32_t WS_EX_CLIENTEDGE = 0x00000200u; +static constexpr uint32_t WS_EX_CONTEXTHELP = 0x00000400u; +static constexpr uint32_t WS_EX_RIGHT = 0x00001000u; +static constexpr uint32_t WS_EX_LEFT = 0x00000000u; +static constexpr uint32_t WS_EX_RTLREADING = 0x00002000u; +static constexpr uint32_t WS_EX_LTRREADING = 0x00000000u; +static constexpr uint32_t WS_EX_LEFTSCROLLBAR = 0x00004000u; +static constexpr uint32_t WS_EX_RIGHTSCROLLBAR = 0x00000000u; +static constexpr uint32_t WS_EX_CONTROLPARENT = 0x00010000u; +static constexpr uint32_t WS_EX_STATICEDGE = 0x00020000u; +static constexpr uint32_t WS_EX_APPWINDOW = 0x00040000u; +static constexpr uint32_t WS_EX_LAYERED = 0x00080000u; +static constexpr uint32_t WS_EX_NOINHERITLAYOUT = 0x00100000u; +static constexpr uint32_t WS_EX_NOREDIRECTIONBITMAP = 0x00200000u; +static constexpr uint32_t WS_EX_LAYOUTRTL = 0x00400000u; +static constexpr uint32_t WS_EX_COMPOSITED = 0x02000000u; +static constexpr uint32_t WS_EX_NOACTIVATE = 0x08000000u; diff --git a/src/wlr_begin.hpp b/src/wlr_begin.hpp index c0936fc9bc..4fc343e1a1 100644 --- a/src/wlr_begin.hpp +++ b/src/wlr_begin.hpp @@ -1,6 +1,7 @@ #include -extern "C" { +extern "C" +{ #define static #define class class_ #define namespace _namespace diff --git a/src/wlserver.cpp b/src/wlserver.cpp index 2675583d15..6aec883bbd 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -2,74 +2,74 @@ #define _GNU_SOURCE 1 #include -#include -#include +#include +#include #include +#include #include -#include -#include -#include #include +#include +#include #include #include #include -#include "WaylandServer/WaylandResource.h" -#include "WaylandServer/WaylandProtocol.h" +#include "WaylandServer/GamescopeActionBinding.h" #include "WaylandServer/LinuxDrmSyncobj.h" #include "WaylandServer/Reshade.h" -#include "WaylandServer/GamescopeActionBinding.h" +#include "WaylandServer/WaylandProtocol.h" +#include "WaylandServer/WaylandResource.h" -#include "wlr_begin.hpp" #include #include +#include "wlr_begin.hpp" #if HAVE_DRM -#include + #include #endif #include #include #include #include +#include #include #include +#include #include +#include +#include #include #include -#include -#include -#include #include -#include -#include -#include +#include #include +#include #include "wlr_end.hpp" -#include "gamescope-xwayland-protocol.h" -#include "gamescope-pipewire-protocol.h" #include "gamescope-control-protocol.h" +#include "gamescope-pipewire-protocol.h" #include "gamescope-private-protocol.h" #include "gamescope-swapchain-protocol.h" +#include "gamescope-xwayland-protocol.h" #include "presentation-time-protocol.h" -#include "wlserver.hpp" +#include "InputEmulation.h" +#include "Timeline.h" +#include "Utils/NonCopyable.h" +#include "color_helpers.h" +#include "commit.h" #include "hdmi.h" +#include "ime.hpp" +#include "log.hpp" #include "main.hpp" +#include "refresh_rate.h" #include "steamcompmgr.hpp" -#include "color_helpers.h" -#include "log.hpp" -#include "ime.hpp" +#include "wlserver.hpp" #include "xwayland_ctx.hpp" -#include "refresh_rate.h" -#include "InputEmulation.h" -#include "commit.h" -#include "Timeline.h" -#include "Utils/NonCopyable.h" #if HAVE_PIPEWIRE -#include "pipewire.hpp" + #include "pipewire.hpp" #endif #include "gpuvis_trace_utils.h" @@ -78,678 +78,853 @@ #include #include -static LogScope wl_log("wlserver"); +static LogScope wl_log( "wlserver" ); using namespace std::literals; extern gamescope::ConVar cv_drm_debug_disable_explicit_sync; -//#define GAMESCOPE_SWAPCHAIN_DEBUG +// #define GAMESCOPE_SWAPCHAIN_DEBUG -struct wlserver_t wlserver = { - .touch_down_ids = {} -}; +struct wlserver_t wlserver = { .touch_down_ids = {} }; -struct wlserver_content_override { - gamescope_xwayland_server_t *server; - struct wlr_surface *surface; - uint32_t x11_window; - struct wl_listener surface_destroy_listener; - struct wl_resource *gamescope_swapchain; +struct wlserver_content_override +{ + gamescope_xwayland_server_t *server; + struct wlr_surface *surface; + uint32_t x11_window; + struct wl_listener surface_destroy_listener; + struct wl_resource *gamescope_swapchain; }; std::mutex g_wlserver_xdg_shell_windows_lock; -static struct wl_list pending_surfaces = {0}; +static struct wl_list pending_surfaces = { 0 }; static std::atomic g_bShutdownWLServer{ false }; -static void wlserver_x11_surface_info_set_wlr( struct wlserver_x11_surface_info *surf, struct wlr_surface *wlr_surf, bool override ); -wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf); - -static void wlserver_update_cursor_constraint(); -static void handle_pointer_constraint(struct wl_listener *listener, void *data); -static void wlserver_constrain_cursor( struct wlr_pointer_constraint_v1 *pNewConstraint ); -struct wlr_surface *wlserver_surface_to_main_surface( struct wlr_surface *pSurface ); -bool wlserver_process_hotkeys( wlr_keyboard *keyboard, uint32_t key, bool press ); +static void wlserver_x11_surface_info_set_wlr( + struct wlserver_x11_surface_info *surf, + struct wlr_surface *wlr_surf, + bool override ); +wlserver_wl_surface_info *get_wl_surface_info( struct wlr_surface *wlr_surf ); + +static void wlserver_update_cursor_constraint( ); +static void +handle_pointer_constraint( struct wl_listener *listener, void *data ); +static void +wlserver_constrain_cursor( struct wlr_pointer_constraint_v1 *pNewConstraint ); +struct wlr_surface * +wlserver_surface_to_main_surface( struct wlr_surface *pSurface ); +bool wlserver_process_hotkeys( + wlr_keyboard *keyboard, uint32_t key, bool press ); extern std::atomic hasRepaint; -std::vector& gamescope_xwayland_server_t::retrieve_commits() +std::vector &gamescope_xwayland_server_t::retrieve_commits( ) { - static std::vector commits; - commits.clear(); - commits.reserve(16); + static std::vector commits; + commits.clear( ); + commits.reserve( 16 ); - { - std::lock_guard lock( wayland_commit_lock ); - commits.swap(wayland_commit_queue); - } - return commits; + { + std::lock_guard lock( wayland_commit_lock ); + commits.swap( wayland_commit_queue ); + } + return commits; } -gamescope::ConVar cv_drm_debug_syncobj_force_wait_on_commit( "drm_debug_syncobj_force_wait_on_commit", false, "Force a wait on DRM sync objects before committing buffers" ); +gamescope::ConVar cv_drm_debug_syncobj_force_wait_on_commit( + "drm_debug_syncobj_force_wait_on_commit", + false, + "Force a wait on DRM sync objects before committing buffers" ); -std::optional PrepareCommit( struct wlr_surface *surf, struct wlr_buffer *buf ) +std::optional +PrepareCommit( struct wlr_surface *surf, struct wlr_buffer *buf ) { - auto wl_surf = get_wl_surface_info( surf ); + auto wl_surf = get_wl_surface_info( surf ); - const auto& pFeedback = wlserver_surface_swapchain_feedback(surf); + const auto &pFeedback = wlserver_surface_swapchain_feedback( surf ); - bool bExplicitSync = wl_surf->pSyncobjSurface && wl_surf->pSyncobjSurface->HasExplicitSync(); + bool bExplicitSync = wl_surf->pSyncobjSurface && + wl_surf->pSyncobjSurface->HasExplicitSync( ); - std::shared_ptr pAcquirePoint; - std::shared_ptr pReleasePoint; - if ( bExplicitSync ) - { - pAcquirePoint = wl_surf->pSyncobjSurface->ExtractAcquireTimelinePoint(); - pReleasePoint = wl_surf->pSyncobjSurface->ExtractReleaseTimelinePoint(); + std::shared_ptr pAcquirePoint; + std::shared_ptr pReleasePoint; + if ( bExplicitSync ) + { + pAcquirePoint = + wl_surf->pSyncobjSurface->ExtractAcquireTimelinePoint( ); + pReleasePoint = + wl_surf->pSyncobjSurface->ExtractReleaseTimelinePoint( ); - if ( cv_drm_debug_syncobj_force_wait_on_commit ) - { - if ( !pAcquirePoint->Wait() ) - wl_log.errorf( "drmSyncobjWait failed!" ); - } - } + if ( cv_drm_debug_syncobj_force_wait_on_commit ) + { + if ( !pAcquirePoint->Wait( ) ) + wl_log.errorf( "drmSyncobjWait failed!" ); + } + } - auto oNewEntry = std::optional { - std::in_place_t{}, - surf, - buf, - wlserver_surface_is_async(surf), - wlserver_surface_is_fifo(surf), - pFeedback, - std::move(wl_surf->pending_presentation_feedbacks), - wl_surf->present_id, - wl_surf->desired_present_time, - std::move( pAcquirePoint ), - std::move( pReleasePoint ) - }; - wl_surf->present_id = std::nullopt; - wl_surf->desired_present_time = 0; - wl_surf->pending_presentation_feedbacks.clear(); - wl_surf->oCurrentPresentMode = std::nullopt; + auto oNewEntry = std::optional{ + std::in_place_t{}, + surf, + buf, + wlserver_surface_is_async( surf ), + wlserver_surface_is_fifo( surf ), + pFeedback, + std::move( wl_surf->pending_presentation_feedbacks ), + wl_surf->present_id, + wl_surf->desired_present_time, + std::move( pAcquirePoint ), + std::move( pReleasePoint ) + }; + wl_surf->present_id = std::nullopt; + wl_surf->desired_present_time = 0; + wl_surf->pending_presentation_feedbacks.clear( ); + wl_surf->oCurrentPresentMode = std::nullopt; - struct wlr_surface *pConstraintSurface = wlserver_surface_to_main_surface( surf ); + struct wlr_surface *pConstraintSurface = + wlserver_surface_to_main_surface( surf ); - struct wlr_pointer_constraint_v1 *pConstraint = wlserver.GetCursorConstraint(); - if ( pConstraint && pConstraint->surface == pConstraintSurface ) - wlserver_update_cursor_constraint(); + struct wlr_pointer_constraint_v1 *pConstraint = + wlserver.GetCursorConstraint( ); + if ( pConstraint && pConstraint->surface == pConstraintSurface ) + wlserver_update_cursor_constraint( ); - return oNewEntry; + return oNewEntry; } -void gamescope_xwayland_server_t::wayland_commit(struct wlr_surface *surf, struct wlr_buffer *buf) +void gamescope_xwayland_server_t::wayland_commit( + struct wlr_surface *surf, struct wlr_buffer *buf ) { - std::optional oEntry = PrepareCommit( surf, buf ); - if ( !oEntry ) - return; + std::optional oEntry = PrepareCommit( surf, buf ); + if ( !oEntry ) return; - { - std::lock_guard lock( wayland_commit_lock ); - wayland_commit_queue.emplace_back( std::move( *oEntry ) ); - } + { + std::lock_guard lock( wayland_commit_lock ); + wayland_commit_queue.emplace_back( std::move( *oEntry ) ); + } - nudge_steamcompmgr(); + nudge_steamcompmgr( ); } struct PendingCommit_t { - struct wlr_surface *surf; - struct wlr_buffer *buf; + struct wlr_surface *surf; + struct wlr_buffer *buf; }; std::list g_PendingCommits; -void wlserver_xdg_commit(struct wlr_surface *surf, struct wlr_buffer *buf) +void wlserver_xdg_commit( struct wlr_surface *surf, struct wlr_buffer *buf ) { - std::optional oEntry = PrepareCommit( surf, buf ); - if ( !oEntry ) - return; + std::optional oEntry = PrepareCommit( surf, buf ); + if ( !oEntry ) return; - { - std::lock_guard lock( wlserver.xdg_commit_lock ); - wlserver.xdg_commit_queue.push_back( std::move( *oEntry ) ); - } + { + std::lock_guard lock( wlserver.xdg_commit_lock ); + wlserver.xdg_commit_queue.push_back( std::move( *oEntry ) ); + } - nudge_steamcompmgr(); + nudge_steamcompmgr( ); } -void xwayland_surface_commit(struct wlr_surface *wlr_surface) { - wlr_surface->current.committed = 0; +void xwayland_surface_commit( struct wlr_surface *wlr_surface ) +{ + wlr_surface->current.committed = 0; - wlserver_x11_surface_info *wlserver_x11_surface_info = get_wl_surface_info(wlr_surface)->x11_surface; - wlserver_xdg_surface_info *wlserver_xdg_surface_info = get_wl_surface_info(wlr_surface)->xdg_surface; + wlserver_x11_surface_info *wlserver_x11_surface_info = + get_wl_surface_info( wlr_surface )->x11_surface; + wlserver_xdg_surface_info *wlserver_xdg_surface_info = + get_wl_surface_info( wlr_surface )->xdg_surface; - if ( wlserver_xdg_surface_info ) - { - if ( !wlserver_xdg_surface_info->bDoneConfigure ) - { - if ( wlserver_xdg_surface_info->xdg_surface ) - wlr_xdg_surface_schedule_configure( wlserver_xdg_surface_info->xdg_surface ); + if ( wlserver_xdg_surface_info ) + { + if ( !wlserver_xdg_surface_info->bDoneConfigure ) + { + if ( wlserver_xdg_surface_info->xdg_surface ) + wlr_xdg_surface_schedule_configure( + wlserver_xdg_surface_info->xdg_surface ); - if ( wlserver_xdg_surface_info->layer_surface ) - wlr_layer_surface_v1_configure( wlserver_xdg_surface_info->layer_surface, g_nNestedWidth, g_nNestedHeight ); + if ( wlserver_xdg_surface_info->layer_surface ) + wlr_layer_surface_v1_configure( + wlserver_xdg_surface_info->layer_surface, + g_nNestedWidth, + g_nNestedHeight ); - wlserver_xdg_surface_info->bDoneConfigure = true; - } - } + wlserver_xdg_surface_info->bDoneConfigure = true; + } + } - // Committing without buffer state is valid and commits the same buffer again. - // Mutter and Weston have forward progress on the frame callback in this situation, - // so let the commit go through. It will be duplication-eliminated later. + // Committing without buffer state is valid and commits the same buffer + // again. Mutter and Weston have forward progress on the frame callback in + // this situation, so let the commit go through. It will be + // duplication-eliminated later. - VulkanWlrTexture_t *tex = (VulkanWlrTexture_t *) wlr_surface_get_texture( wlr_surface ); - if ( tex == NULL ) - { - return; - } + VulkanWlrTexture_t *tex = + ( VulkanWlrTexture_t * )wlr_surface_get_texture( wlr_surface ); + if ( tex == NULL ) { return; } - struct wlr_buffer *buf = wlr_buffer_lock( tex->buf ); + struct wlr_buffer *buf = wlr_buffer_lock( tex->buf ); - gpuvis_trace_printf( "xwayland_surface_commit wlr_surface %p", wlr_surface ); + gpuvis_trace_printf( + "xwayland_surface_commit wlr_surface %p", wlr_surface ); - if (wlserver_x11_surface_info) - { - assert(wlserver_x11_surface_info->xwayland_server); - wlserver_x11_surface_info->xwayland_server->wayland_commit( wlr_surface, buf ); - } - else if (wlserver_xdg_surface_info) - { - wlserver_xdg_commit(wlr_surface, buf); - } - else - { - g_PendingCommits.push_back(PendingCommit_t{ wlr_surface, buf }); - } + if ( wlserver_x11_surface_info ) + { + assert( wlserver_x11_surface_info->xwayland_server ); + wlserver_x11_surface_info->xwayland_server->wayland_commit( + wlr_surface, buf ); + } + else if ( wlserver_xdg_surface_info ) + { + wlserver_xdg_commit( wlr_surface, buf ); + } + else + { + g_PendingCommits.push_back( PendingCommit_t{ wlr_surface, buf } ); + } } -void gamescope_xwayland_server_t::on_xwayland_ready(void *data) +void gamescope_xwayland_server_t::on_xwayland_ready( void *data ) { - xwayland_ready = true; + xwayland_ready = true; - if (!xwayland_server->options.no_touch_pointer_emulation) - wl_log.infof("Xwayland doesn't support -noTouchPointerEmulation, touch events might get duplicated"); + if ( !xwayland_server->options.no_touch_pointer_emulation ) + wl_log.infof( + "Xwayland doesn't support -noTouchPointerEmulation, touch events " + "might get duplicated" ); - dpy = XOpenDisplay( get_nested_display_name() ); + dpy = XOpenDisplay( get_nested_display_name( ) ); } -void gamescope_xwayland_server_t::xwayland_ready_callback(struct wl_listener *listener, void *data) +void gamescope_xwayland_server_t::xwayland_ready_callback( + struct wl_listener *listener, void *data ) { - gamescope_xwayland_server_t *server = wl_container_of( listener, server, xwayland_ready_listener ); - server->on_xwayland_ready(data); + gamescope_xwayland_server_t *server = + wl_container_of( listener, server, xwayland_ready_listener ); + server->on_xwayland_ready( data ); } -static void bump_input_counter() +static void bump_input_counter( ) { - inputCounter++; - nudge_steamcompmgr(); + inputCounter++; + nudge_steamcompmgr( ); } -static void wlserver_handle_modifiers(struct wl_listener *listener, void *data) +static void +wlserver_handle_modifiers( struct wl_listener *listener, void *data ) { - struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard; + struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard; - wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard ); - wlr_seat_keyboard_notify_modifiers( wlserver.wlr.seat, &keyboard->modifiers ); + wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard ); + wlr_seat_keyboard_notify_modifiers( + wlserver.wlr.seat, &keyboard->modifiers ); - bump_input_counter(); + bump_input_counter( ); } -static void wlserver_handle_key(struct wl_listener *listener, void *data) +static void wlserver_handle_key( struct wl_listener *listener, void *data ) { - struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard; - struct wlr_keyboard_key_event *event = (struct wlr_keyboard_key_event *) data; + struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard; + struct wlr_keyboard_key_event *event = + ( struct wlr_keyboard_key_event * )data; - xkb_keycode_t keycode = event->keycode + 8; - xkb_keysym_t keysym = xkb_state_key_get_one_sym(keyboard->xkb_state, keycode); + xkb_keycode_t keycode = event->keycode + 8; + xkb_keysym_t keysym = + xkb_state_key_get_one_sym( keyboard->xkb_state, keycode ); #if HAVE_SESSION - if (wlserver.wlr.session && event->state == WL_KEYBOARD_KEY_STATE_PRESSED && keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { - unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; - wlr_session_change_vt(wlserver.wlr.session, vt); - return; - } + if ( wlserver.wlr.session && + event->state == WL_KEYBOARD_KEY_STATE_PRESSED && + keysym >= XKB_KEY_XF86Switch_VT_1 && + keysym <= XKB_KEY_XF86Switch_VT_12 ) + { + unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; + wlr_session_change_vt( wlserver.wlr.session, vt ); + return; + } #endif - // TODO: Remove the below hack when Steam is shipping - // `gamescope_action_binding_manager` in Steam Stable - // as it can just use a keybind to grab these always. - bool forbidden_key = - keysym == XKB_KEY_XF86AudioLowerVolume || - keysym == XKB_KEY_XF86AudioRaiseVolume || - keysym == XKB_KEY_XF86PowerOff; - if ( ( event->state == WL_KEYBOARD_KEY_STATE_PRESSED || event->state == WL_KEYBOARD_KEY_STATE_RELEASED ) && forbidden_key ) - { - // Always send volume+/- to root server only, to avoid it reaching the game. - struct wlr_surface *old_kb_surf = wlserver.kb_focus_surface; - struct wlr_surface *new_kb_surf = steamcompmgr_get_server_input_surface( 0 ); - if ( new_kb_surf ) - { - wlserver_keyboardfocus( new_kb_surf, false ); - wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard ); - wlr_seat_keyboard_notify_key( wlserver.wlr.seat, event->time_msec, event->keycode, event->state ); - wlserver_keyboardfocus( old_kb_surf, false ); - return; - } - } - - if ( !wlserver_process_hotkeys( keyboard, event->keycode, event->state == WL_KEYBOARD_KEY_STATE_PRESSED ) ) - { - wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard ); - wlr_seat_keyboard_notify_key( wlserver.wlr.seat, event->time_msec, event->keycode, event->state ); - } + // TODO: Remove the below hack when Steam is shipping + // `gamescope_action_binding_manager` in Steam Stable + // as it can just use a keybind to grab these always. + bool forbidden_key = keysym == XKB_KEY_XF86AudioLowerVolume || + keysym == XKB_KEY_XF86AudioRaiseVolume || + keysym == XKB_KEY_XF86PowerOff; + if ( ( event->state == WL_KEYBOARD_KEY_STATE_PRESSED || + event->state == WL_KEYBOARD_KEY_STATE_RELEASED ) && + forbidden_key ) + { + // Always send volume+/- to root server only, to avoid it reaching the + // game. + struct wlr_surface *old_kb_surf = wlserver.kb_focus_surface; + struct wlr_surface *new_kb_surf = + steamcompmgr_get_server_input_surface( 0 ); + if ( new_kb_surf ) + { + wlserver_keyboardfocus( new_kb_surf, false ); + wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard ); + wlr_seat_keyboard_notify_key( + wlserver.wlr.seat, + event->time_msec, + event->keycode, + event->state ); + wlserver_keyboardfocus( old_kb_surf, false ); + return; + } + } + + if ( !wlserver_process_hotkeys( + keyboard, + event->keycode, + event->state == WL_KEYBOARD_KEY_STATE_PRESSED ) ) + { + wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard ); + wlr_seat_keyboard_notify_key( + wlserver.wlr.seat, event->time_msec, event->keycode, event->state ); + } - bump_input_counter(); + bump_input_counter( ); } -static void wlserver_perform_rel_pointer_motion(double unaccel_dx, double unaccel_dy) +static void +wlserver_perform_rel_pointer_motion( double unaccel_dx, double unaccel_dy ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - wlr_relative_pointer_manager_v1_send_relative_motion( wlserver.relative_pointer_manager, wlserver.wlr.seat, 0, unaccel_dx, unaccel_dy, unaccel_dx, unaccel_dy ); + wlr_relative_pointer_manager_v1_send_relative_motion( + wlserver.relative_pointer_manager, + wlserver.wlr.seat, + 0, + unaccel_dx, + unaccel_dy, + unaccel_dx, + unaccel_dy ); } -static void wlserver_handle_pointer_motion(struct wl_listener *listener, void *data) +static void +wlserver_handle_pointer_motion( struct wl_listener *listener, void *data ) { - struct wlr_pointer_motion_event *event = (struct wlr_pointer_motion_event *) data; + struct wlr_pointer_motion_event *event = + ( struct wlr_pointer_motion_event * )data; - wlserver_mousemotion(event->unaccel_dx, event->unaccel_dy, event->time_msec); + wlserver_mousemotion( + event->unaccel_dx, event->unaccel_dy, event->time_msec ); } void wlserver_open_steam_menu( bool qam ) { - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( 0 ); - if (!server) - return; + gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( 0 ); + if ( !server ) return; - uint32_t keycode = qam ? XK_2 : XK_1; + uint32_t keycode = qam ? XK_2 : XK_1; - XTestFakeKeyEvent(server->get_xdisplay(), XKeysymToKeycode( server->get_xdisplay(), XK_Control_L ), True, CurrentTime); - XTestFakeKeyEvent(server->get_xdisplay(), XKeysymToKeycode( server->get_xdisplay(), keycode ), True, CurrentTime); + XTestFakeKeyEvent( + server->get_xdisplay( ), + XKeysymToKeycode( server->get_xdisplay( ), XK_Control_L ), + True, + CurrentTime ); + XTestFakeKeyEvent( + server->get_xdisplay( ), + XKeysymToKeycode( server->get_xdisplay( ), keycode ), + True, + CurrentTime ); - XTestFakeKeyEvent(server->get_xdisplay(), XKeysymToKeycode( server->get_xdisplay(), keycode ), False, CurrentTime); - XTestFakeKeyEvent(server->get_xdisplay(), XKeysymToKeycode( server->get_xdisplay(), XK_Control_L ), False, CurrentTime); + XTestFakeKeyEvent( + server->get_xdisplay( ), + XKeysymToKeycode( server->get_xdisplay( ), keycode ), + False, + CurrentTime ); + XTestFakeKeyEvent( + server->get_xdisplay( ), + XKeysymToKeycode( server->get_xdisplay( ), XK_Control_L ), + False, + CurrentTime ); } -static void wlserver_handle_pointer_button(struct wl_listener *listener, void *data) +static void +wlserver_handle_pointer_button( struct wl_listener *listener, void *data ) { - struct wlserver_pointer *pointer = wl_container_of( listener, pointer, button ); - struct wlr_pointer_button_event *event = (struct wlr_pointer_button_event *) data; + struct wlserver_pointer *pointer = + wl_container_of( listener, pointer, button ); + struct wlr_pointer_button_event *event = + ( struct wlr_pointer_button_event * )data; - wlr_seat_pointer_notify_button( wlserver.wlr.seat, event->time_msec, event->button, event->state ); + wlr_seat_pointer_notify_button( + wlserver.wlr.seat, event->time_msec, event->button, event->state ); } -static void wlserver_handle_pointer_axis(struct wl_listener *listener, void *data) +static void +wlserver_handle_pointer_axis( struct wl_listener *listener, void *data ) { - struct wlserver_pointer *pointer = wl_container_of( listener, pointer, axis ); - struct wlr_pointer_axis_event *event = (struct wlr_pointer_axis_event *) data; + struct wlserver_pointer *pointer = + wl_container_of( listener, pointer, axis ); + struct wlr_pointer_axis_event *event = + ( struct wlr_pointer_axis_event * )data; - wlr_seat_pointer_notify_axis( wlserver.wlr.seat, event->time_msec, event->orientation, event->delta, event->delta_discrete, event->source, event->relative_direction ); + wlr_seat_pointer_notify_axis( + wlserver.wlr.seat, + event->time_msec, + event->orientation, + event->delta, + event->delta_discrete, + event->source, + event->relative_direction ); } -static void wlserver_handle_pointer_frame(struct wl_listener *listener, void *data) +static void +wlserver_handle_pointer_frame( struct wl_listener *listener, void *data ) { - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); - bump_input_counter(); + bump_input_counter( ); } -static inline uint32_t TouchClickModeToLinuxButton( gamescope::TouchClickMode eTouchClickMode ) +static inline uint32_t +TouchClickModeToLinuxButton( gamescope::TouchClickMode eTouchClickMode ) { - switch ( eTouchClickMode ) - { - default: - case gamescope::TouchClickModes::Hover: - return 0; - case gamescope::TouchClickModes::Trackpad: - case gamescope::TouchClickModes::Left: - return BTN_LEFT; - case gamescope::TouchClickModes::Right: - return BTN_RIGHT; - case gamescope::TouchClickModes::Middle: - return BTN_MIDDLE; - } + switch ( eTouchClickMode ) + { + default: + case gamescope::TouchClickModes::Hover: + return 0; + case gamescope::TouchClickModes::Trackpad: + case gamescope::TouchClickModes::Left: + return BTN_LEFT; + case gamescope::TouchClickModes::Right: + return BTN_RIGHT; + case gamescope::TouchClickModes::Middle: + return BTN_MIDDLE; + } } std::atomic g_bPendingTouchMovement = { false }; -static void wlserver_touch_associate_connector(struct wlserver_touch *touch) -{ - if (touch->connector != nullptr) return; - - // Heuristic to associate a monitor to a touch input device: - // - if a touchscreen's bus is I²C, it can very likely be associated to an internal monitor. - // - if its bus is USB, it can be associated to an external monitor. - // This isn't perfect, but we can't rely on the physical sizes reported by both devices, - // because it's not uncommon for touchscreens to report wildly incorrect sizes. - gamescope::IBackendConnector* connector = nullptr; - struct libinput_device *lidev = wlr_libinput_get_device_handle(&touch->wlr->base); - struct udev_device *dev = libinput_device_get_udev_device(lidev); - auto *parent = dev; - while (parent) { - const char *subsystem = udev_device_get_subsystem(parent); - if (subsystem) { - if (strcmp( subsystem, "i2c" ) == 0) { - connector = GetBackend()->GetConnector(gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL); - break; - } else if (strcmp( subsystem, "usb" ) == 0) { - connector = GetBackend()->GetConnector(gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL); - break; - } - } - parent = udev_device_get_parent(parent); - } - udev_device_unref(dev); - if (connector != nullptr) { - touch->connector = connector; - wl_log.infof("associating connector %s (%s %s) with touch input device %s", - connector->GetName(), connector->GetMake(), connector->GetModel(), - libinput_device_get_name(lidev)); - } -} - -static void wlserver_handle_touch_down(struct wl_listener *listener, void *data) -{ - struct wlserver_touch *touch = wl_container_of( listener, touch, down ); - struct wlr_touch_down_event *event = (struct wlr_touch_down_event *) data; - - wlserver_touch_associate_connector( touch ); - wlserver_touchdown( event->x, event->y, event->touch_id, event->time_msec, touch->connector ); -} - -static void wlserver_handle_touch_up(struct wl_listener *listener, void *data) -{ - struct wlserver_touch *touch = wl_container_of( listener, touch, up ); - struct wlr_touch_up_event *event = (struct wlr_touch_up_event *) data; - - wlserver_touchup( event->touch_id, event->time_msec ); -} - -static void wlserver_handle_touch_motion(struct wl_listener *listener, void *data) -{ - struct wlserver_touch *touch = wl_container_of( listener, touch, motion ); - struct wlr_touch_motion_event *event = (struct wlr_touch_motion_event *) data; - - wlserver_touch_associate_connector( touch ); - wlserver_touchmotion( event->x, event->y, event->touch_id, event->time_msec, false, touch->connector ); -} - -static void wlserver_new_input(struct wl_listener *listener, void *data) -{ - struct wlr_input_device *device = (struct wlr_input_device *) data; - - switch ( device->type ) - { - case WLR_INPUT_DEVICE_KEYBOARD: - { - struct wlr_keyboard *keyboard = wlr_keyboard_from_input_device(device); - wlr_keyboard_set_keymap(keyboard, wlserver.keyboard_group->keyboard.keymap); - if (!wlr_keyboard_group_add_keyboard(wlserver.keyboard_group, keyboard)) { - wl_log.errorf("failed to add physical keyboard %s", device->name); - break; - } - // Sync the state of the modifiers and the state of the LEDs - struct wlr_keyboard_modifiers mods = wlserver.keyboard_group->keyboard.modifiers; - if (mods.depressed != keyboard->modifiers.depressed || - mods.latched != keyboard->modifiers.latched || - mods.locked != keyboard->modifiers.locked || - mods.group != keyboard->modifiers.group) { - wlr_keyboard_notify_modifiers(keyboard, - mods.depressed, mods.latched, mods.locked, mods.group); - } - } - break; - case WLR_INPUT_DEVICE_POINTER: - { - struct wlserver_pointer *pointer = (struct wlserver_pointer *) calloc( 1, sizeof( struct wlserver_pointer ) ); - - pointer->wlr = (struct wlr_pointer *)device; - - pointer->motion.notify = wlserver_handle_pointer_motion; - wl_signal_add( &pointer->wlr->events.motion, &pointer->motion ); - pointer->button.notify = wlserver_handle_pointer_button; - wl_signal_add( &pointer->wlr->events.button, &pointer->button ); - pointer->axis.notify = wlserver_handle_pointer_axis; - wl_signal_add( &pointer->wlr->events.axis, &pointer->axis); - pointer->frame.notify = wlserver_handle_pointer_frame; - wl_signal_add( &pointer->wlr->events.frame, &pointer->frame); - } - break; - case WLR_INPUT_DEVICE_TOUCH: - { - struct wlserver_touch *touch = (struct wlserver_touch *) calloc( 1, sizeof( struct wlserver_touch ) ); - - touch->wlr = (struct wlr_touch *)device; - - touch->down.notify = wlserver_handle_touch_down; - wl_signal_add( &touch->wlr->events.down, &touch->down ); - touch->up.notify = wlserver_handle_touch_up; - wl_signal_add( &touch->wlr->events.up, &touch->up ); - touch->motion.notify = wlserver_handle_touch_motion; - wl_signal_add( &touch->wlr->events.motion, &touch->motion ); - - wlserver_touch_associate_connector( touch ); - } - break; - default: - break; - } +static void wlserver_touch_associate_connector( struct wlserver_touch *touch ) +{ + if ( touch->connector != nullptr ) return; + + // Heuristic to associate a monitor to a touch input device: + // - if a touchscreen's bus is I²C, it can very likely be associated to an + // internal monitor. + // - if its bus is USB, it can be associated to an external monitor. + // This isn't perfect, but we can't rely on the physical sizes reported by + // both devices, because it's not uncommon for touchscreens to report wildly + // incorrect sizes. + gamescope::IBackendConnector *connector = nullptr; + struct libinput_device *lidev = + wlr_libinput_get_device_handle( &touch->wlr->base ); + struct udev_device *dev = libinput_device_get_udev_device( lidev ); + auto *parent = dev; + while ( parent ) + { + const char *subsystem = udev_device_get_subsystem( parent ); + if ( subsystem ) + { + if ( strcmp( subsystem, "i2c" ) == 0 ) + { + connector = GetBackend( )->GetConnector( + gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ); + break; + } + else if ( strcmp( subsystem, "usb" ) == 0 ) + { + connector = GetBackend( )->GetConnector( + gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ); + break; + } + } + parent = udev_device_get_parent( parent ); + } + udev_device_unref( dev ); + if ( connector != nullptr ) + { + touch->connector = connector; + wl_log.infof( + "associating connector %s (%s %s) with touch input device %s", + connector->GetName( ), + connector->GetMake( ), + connector->GetModel( ), + libinput_device_get_name( lidev ) ); + } +} + +static void +wlserver_handle_touch_down( struct wl_listener *listener, void *data ) +{ + struct wlserver_touch *touch = wl_container_of( listener, touch, down ); + struct wlr_touch_down_event *event = ( struct wlr_touch_down_event * )data; + + wlserver_touch_associate_connector( touch ); + wlserver_touchdown( + event->x, + event->y, + event->touch_id, + event->time_msec, + touch->connector ); +} + +static void wlserver_handle_touch_up( struct wl_listener *listener, void *data ) +{ + struct wlserver_touch *touch = wl_container_of( listener, touch, up ); + struct wlr_touch_up_event *event = ( struct wlr_touch_up_event * )data; + + wlserver_touchup( event->touch_id, event->time_msec ); +} + +static void +wlserver_handle_touch_motion( struct wl_listener *listener, void *data ) +{ + struct wlserver_touch *touch = wl_container_of( listener, touch, motion ); + struct wlr_touch_motion_event *event = + ( struct wlr_touch_motion_event * )data; + + wlserver_touch_associate_connector( touch ); + wlserver_touchmotion( + event->x, + event->y, + event->touch_id, + event->time_msec, + false, + touch->connector ); +} + +static void wlserver_new_input( struct wl_listener *listener, void *data ) +{ + struct wlr_input_device *device = ( struct wlr_input_device * )data; + + switch ( device->type ) + { + case WLR_INPUT_DEVICE_KEYBOARD: + { + struct wlr_keyboard *keyboard = + wlr_keyboard_from_input_device( device ); + wlr_keyboard_set_keymap( + keyboard, wlserver.keyboard_group->keyboard.keymap ); + if ( !wlr_keyboard_group_add_keyboard( + wlserver.keyboard_group, keyboard ) ) + { + wl_log.errorf( + "failed to add physical keyboard %s", device->name ); + break; + } + // Sync the state of the modifiers and the state of the LEDs + struct wlr_keyboard_modifiers mods = + wlserver.keyboard_group->keyboard.modifiers; + if ( mods.depressed != keyboard->modifiers.depressed || + mods.latched != keyboard->modifiers.latched || + mods.locked != keyboard->modifiers.locked || + mods.group != keyboard->modifiers.group ) + { + wlr_keyboard_notify_modifiers( + keyboard, + mods.depressed, + mods.latched, + mods.locked, + mods.group ); + } + } + break; + case WLR_INPUT_DEVICE_POINTER: + { + struct wlserver_pointer *pointer = + ( struct wlserver_pointer * )calloc( + 1, sizeof( struct wlserver_pointer ) ); + + pointer->wlr = ( struct wlr_pointer * )device; + + pointer->motion.notify = wlserver_handle_pointer_motion; + wl_signal_add( &pointer->wlr->events.motion, &pointer->motion ); + pointer->button.notify = wlserver_handle_pointer_button; + wl_signal_add( &pointer->wlr->events.button, &pointer->button ); + pointer->axis.notify = wlserver_handle_pointer_axis; + wl_signal_add( &pointer->wlr->events.axis, &pointer->axis ); + pointer->frame.notify = wlserver_handle_pointer_frame; + wl_signal_add( &pointer->wlr->events.frame, &pointer->frame ); + } + break; + case WLR_INPUT_DEVICE_TOUCH: + { + struct wlserver_touch *touch = ( struct wlserver_touch * )calloc( + 1, sizeof( struct wlserver_touch ) ); + + touch->wlr = ( struct wlr_touch * )device; + + touch->down.notify = wlserver_handle_touch_down; + wl_signal_add( &touch->wlr->events.down, &touch->down ); + touch->up.notify = wlserver_handle_touch_up; + wl_signal_add( &touch->wlr->events.up, &touch->up ); + touch->motion.notify = wlserver_handle_touch_motion; + wl_signal_add( &touch->wlr->events.motion, &touch->motion ); + + wlserver_touch_associate_connector( touch ); + } + break; + default: + break; + } } static struct wl_listener new_input_listener = { .notify = wlserver_new_input }; -wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf) +wlserver_wl_surface_info *get_wl_surface_info( struct wlr_surface *wlr_surf ) { - if (!wlr_surf) - return NULL; - return reinterpret_cast(wlr_surf->data); + if ( !wlr_surf ) return NULL; + return reinterpret_cast( wlr_surf->data ); } static void handle_wl_surface_commit( struct wl_listener *l, void *data ) { - wlserver_wl_surface_info *surf = wl_container_of( l, surf, commit ); - xwayland_surface_commit(surf->wlr); + wlserver_wl_surface_info *surf = wl_container_of( l, surf, commit ); + xwayland_surface_commit( surf->wlr ); } static void handle_wl_surface_destroy( struct wl_listener *l, void *data ) { - wlserver_wl_surface_info *surf = wl_container_of( l, surf, destroy ); - if (surf->x11_surface) - { - wlserver_x11_surface_info *x11_surface = surf->x11_surface; - - wlserver_x11_surface_info_finish(x11_surface); - // Re-init it so it can be destroyed for good on the x11 side. - // This just clears it out from the main wl surface mainly. - // - // wl_list_remove leaves stuff in a weird state, so we need to call - // this to re-init the list to avoid a crash. - wlserver_x11_surface_info_init(x11_surface, x11_surface->xwayland_server, x11_surface->x11_id); - } + wlserver_wl_surface_info *surf = wl_container_of( l, surf, destroy ); + if ( surf->x11_surface ) + { + wlserver_x11_surface_info *x11_surface = surf->x11_surface; + + wlserver_x11_surface_info_finish( x11_surface ); + // Re-init it so it can be destroyed for good on the x11 side. + // This just clears it out from the main wl surface mainly. + // + // wl_list_remove leaves stuff in a weird state, so we need to call + // this to re-init the list to avoid a crash. + wlserver_x11_surface_info_init( + x11_surface, x11_surface->xwayland_server, x11_surface->x11_id ); + } - if ( surf->wlr == wlserver.mouse_focus_surface ) - wlserver.mouse_focus_surface = nullptr; + if ( surf->wlr == wlserver.mouse_focus_surface ) + wlserver.mouse_focus_surface = nullptr; - if ( surf->wlr == wlserver.kb_focus_surface ) - wlserver.kb_focus_surface = nullptr; + if ( surf->wlr == wlserver.kb_focus_surface ) + wlserver.kb_focus_surface = nullptr; - wlserver.current_dropdown_surfaces.erase( surf->wlr ); + wlserver.current_dropdown_surfaces.erase( surf->wlr ); - for (auto it = g_PendingCommits.begin(); it != g_PendingCommits.end();) - { - if (it->surf == surf->wlr) - { - // We owned the buffer lock, so unlock it here. - wlr_buffer_unlock(it->buf); - it = g_PendingCommits.erase(it); - } - else - { - it++; - } - } + for ( auto it = g_PendingCommits.begin( ); it != g_PendingCommits.end( ); ) + { + if ( it->surf == surf->wlr ) + { + // We owned the buffer lock, so unlock it here. + wlr_buffer_unlock( it->buf ); + it = g_PendingCommits.erase( it ); + } + else + { + it++; + } + } - for (auto& feedback : surf->pending_presentation_feedbacks) - { - wp_presentation_feedback_send_discarded(feedback); - wl_resource_destroy(feedback); - } - surf->pending_presentation_feedbacks.clear(); + for ( auto &feedback : surf->pending_presentation_feedbacks ) + { + wp_presentation_feedback_send_discarded( feedback ); + wl_resource_destroy( feedback ); + } + surf->pending_presentation_feedbacks.clear( ); - if ( surf->pSyncobjSurface ) - { - surf->pSyncobjSurface->Detach(); - assert( surf->pSyncobjSurface == nullptr ); - } + if ( surf->pSyncobjSurface ) + { + surf->pSyncobjSurface->Detach( ); + assert( surf->pSyncobjSurface == nullptr ); + } - surf->wlr->data = nullptr; + surf->wlr->data = nullptr; - for ( wl_resource *pSwapchain : surf->gamescope_swapchains ) - { - wl_resource_set_user_data( pSwapchain, nullptr ); - } + for ( wl_resource *pSwapchain : surf->gamescope_swapchains ) + { + wl_resource_set_user_data( pSwapchain, nullptr ); + } - delete surf; + delete surf; } -static void wlserver_new_surface(struct wl_listener *l, void *data) +static void wlserver_new_surface( struct wl_listener *l, void *data ) { - struct wlr_surface *wlr_surf = (struct wlr_surface *)data; - uint32_t id = wl_resource_get_id(wlr_surf->resource); + struct wlr_surface *wlr_surf = ( struct wlr_surface * )data; + uint32_t id = wl_resource_get_id( wlr_surf->resource ); - wlserver_wl_surface_info *wl_surface_info = new wlserver_wl_surface_info; - wl_surface_info->wlr = wlr_surf; + wlserver_wl_surface_info *wl_surface_info = new wlserver_wl_surface_info; + wl_surface_info->wlr = wlr_surf; - wl_surface_info->destroy.notify = handle_wl_surface_destroy; - wl_signal_add( &wlr_surf->events.destroy, &wl_surface_info->destroy ); + wl_surface_info->destroy.notify = handle_wl_surface_destroy; + wl_signal_add( &wlr_surf->events.destroy, &wl_surface_info->destroy ); - wl_surface_info->commit.notify = handle_wl_surface_commit; - wl_signal_add( &wlr_surf->events.commit, &wl_surface_info->commit ); + wl_surface_info->commit.notify = handle_wl_surface_commit; + wl_signal_add( &wlr_surf->events.commit, &wl_surface_info->commit ); - wlr_surf->data = wl_surface_info; + wlr_surf->data = wl_surface_info; - struct wlserver_x11_surface_info *s, *tmp; - wl_list_for_each_safe(s, tmp, &pending_surfaces, pending_link) - { - if (s->wl_id == id && s->main_surface == nullptr) - { - wlserver_x11_surface_info_set_wlr( s, wlr_surf, false ); - } - } + struct wlserver_x11_surface_info *s, *tmp; + wl_list_for_each_safe( s, tmp, &pending_surfaces, pending_link ) + { + if ( s->wl_id == id && s->main_surface == nullptr ) + { + wlserver_x11_surface_info_set_wlr( s, wlr_surf, false ); + } + } } -static struct wl_listener new_surface_listener = { .notify = wlserver_new_surface }; +static struct wl_listener new_surface_listener = { .notify = + wlserver_new_surface }; -void gamescope_xwayland_server_t::destroy_content_override( struct wlserver_content_override *co ) +void gamescope_xwayland_server_t::destroy_content_override( + struct wlserver_content_override *co ) { #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "destroy_content_override REAL: co: %p co->surface: %p co->x11_window: 0x%x co->gamescope_swapchain: %p", co, co->surface, co->x11_window, co->gamescope_swapchain ); + wl_log.infof( + "destroy_content_override REAL: co: %p co->surface: %p co->x11_window: " + "0x%x co->gamescope_swapchain: %p", + co, + co->surface, + co->x11_window, + co->gamescope_swapchain ); #endif - if ( co->surface ) - { - wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info( co->surface ); - if ( wl_surface_info ) - wl_surface_info->x11_surface = nullptr; - } - - if ( co->gamescope_swapchain ) - { - gamescope_swapchain_send_retired(co->gamescope_swapchain); - } - wl_list_remove( &co->surface_destroy_listener.link ); - content_overrides.erase( co->x11_window ); - free( co ); -} -void gamescope_xwayland_server_t::destroy_content_override( struct wlserver_x11_surface_info *x11_surface, struct wlr_surface *surf ) + if ( co->surface ) + { + wlserver_wl_surface_info *wl_surface_info = + get_wl_surface_info( co->surface ); + if ( wl_surface_info ) wl_surface_info->x11_surface = nullptr; + } + + if ( co->gamescope_swapchain ) + { + gamescope_swapchain_send_retired( co->gamescope_swapchain ); + } + wl_list_remove( &co->surface_destroy_listener.link ); + content_overrides.erase( co->x11_window ); + free( co ); +} +void gamescope_xwayland_server_t::destroy_content_override( + struct wlserver_x11_surface_info *x11_surface, struct wlr_surface *surf ) { #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "destroy_content_override LOOKUP: x11_surface: %p x11_window: 0x%x surf: %p", x11_surface, x11_surface->x11_id, surf ); + wl_log.infof( + "destroy_content_override LOOKUP: x11_surface: %p x11_window: 0x%x " + "surf: %p", + x11_surface, + x11_surface->x11_id, + surf ); #endif - auto iter = content_overrides.find( x11_surface->x11_id ); - if (iter == content_overrides.end()) - return; + auto iter = content_overrides.find( x11_surface->x11_id ); + if ( iter == content_overrides.end( ) ) return; - if ( x11_surface->override_surface == surf ) - x11_surface->override_surface = nullptr; + if ( x11_surface->override_surface == surf ) + x11_surface->override_surface = nullptr; - struct wlserver_content_override *co = iter->second; + struct wlserver_content_override *co = iter->second; #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "destroy_content_override LOOKUP FOUND: x11_surface: %p x11_window: 0x%x surf: %p co: %p co->surface: %p", x11_surface, x11_surface->x11_id, surf, co, co->surface ); + wl_log.infof( + "destroy_content_override LOOKUP FOUND: x11_surface: %p x11_window: " + "0x%x surf: %p co: %p co->surface: %p", + x11_surface, + x11_surface->x11_id, + surf, + co, + co->surface ); #endif - co->gamescope_swapchain = nullptr; - if (co->surface == surf) - destroy_content_override(iter->second); + co->gamescope_swapchain = nullptr; + if ( co->surface == surf ) destroy_content_override( iter->second ); } - -static void content_override_handle_surface_destroy( struct wl_listener *listener, void *data ) +static void content_override_handle_surface_destroy( + struct wl_listener *listener, void *data ) { - struct wlserver_content_override *co = wl_container_of( listener, co, surface_destroy_listener ); - gamescope_xwayland_server_t *server = co->server; - assert( server ); - server->destroy_content_override( co ); + struct wlserver_content_override *co = + wl_container_of( listener, co, surface_destroy_listener ); + gamescope_xwayland_server_t *server = co->server; + assert( server ); + server->destroy_content_override( co ); } static void gamescope_swapchain_destroy_co( struct wl_resource *resource ); -void gamescope_xwayland_server_t::handle_override_window_content( struct wl_client *client, struct wl_resource *gamescope_swapchain_resource, struct wlr_surface *surface, uint32_t x11_window ) +void gamescope_xwayland_server_t::handle_override_window_content( + struct wl_client *client, + struct wl_resource *gamescope_swapchain_resource, + struct wlr_surface *surface, + uint32_t x11_window ) { - wlserver_x11_surface_info *x11_surface = lookup_x11_surface_info_from_xid( this, x11_window ); - // If we found an x11_surface, go back up to our parent. - if ( x11_surface ) - x11_window = x11_surface->x11_id; + wlserver_x11_surface_info *x11_surface = + lookup_x11_surface_info_from_xid( this, x11_window ); + // If we found an x11_surface, go back up to our parent. + if ( x11_surface ) x11_window = x11_surface->x11_id; #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "handle_override_window_content: (1) x11_window: 0x%x swapchain_resource: %p surface: %p", x11_window, gamescope_swapchain_resource, surface ); + wl_log.infof( + "handle_override_window_content: (1) x11_window: 0x%x " + "swapchain_resource: %p surface: %p", + x11_window, + gamescope_swapchain_resource, + surface ); #endif - if ( content_overrides.count( x11_window ) ) { - if ( content_overrides[x11_window]->gamescope_swapchain == gamescope_swapchain_resource ) - return; + if ( content_overrides.count( x11_window ) ) + { + if ( content_overrides[ x11_window ]->gamescope_swapchain == + gamescope_swapchain_resource ) + return; #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "handle_override_window_content: (2) DESTROYING x11_window: 0x%x old_swapchain: %p new_swapchain: %p", x11_window, content_overrides[x11_window]->gamescope_swapchain, gamescope_swapchain_resource ); + wl_log.infof( + "handle_override_window_content: (2) DESTROYING x11_window: 0x%x " + "old_swapchain: %p new_swapchain: %p", + x11_window, + content_overrides[ x11_window ]->gamescope_swapchain, + gamescope_swapchain_resource ); #endif - destroy_content_override( content_overrides[ x11_window ] ); - } - - if ( gamescope_swapchain_resource ) - { - gamescope_swapchain_destroy_co( gamescope_swapchain_resource ); - } - - struct wlserver_content_override *co = (struct wlserver_content_override *)calloc(1, sizeof(*co)); - co->server = this; - co->surface = surface; - co->x11_window = x11_window; - co->gamescope_swapchain = gamescope_swapchain_resource; - co->surface_destroy_listener.notify = content_override_handle_surface_destroy; - wl_signal_add( &surface->events.destroy, &co->surface_destroy_listener ); - content_overrides[ x11_window ] = co; + destroy_content_override( content_overrides[ x11_window ] ); + } + + if ( gamescope_swapchain_resource ) + { + gamescope_swapchain_destroy_co( gamescope_swapchain_resource ); + } + + struct wlserver_content_override *co = + ( struct wlserver_content_override * )calloc( 1, sizeof( *co ) ); + co->server = this; + co->surface = surface; + co->x11_window = x11_window; + co->gamescope_swapchain = gamescope_swapchain_resource; + co->surface_destroy_listener.notify = + content_override_handle_surface_destroy; + wl_signal_add( &surface->events.destroy, &co->surface_destroy_listener ); + content_overrides[ x11_window ] = co; #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "handle_override_window_content: (3) x11_window: 0x%x swapchain_resource: %p surface: %p co: %p", x11_window, gamescope_swapchain_resource, surface, co ); + wl_log.infof( + "handle_override_window_content: (3) x11_window: 0x%x " + "swapchain_resource: %p surface: %p co: %p", + x11_window, + gamescope_swapchain_resource, + surface, + co ); #endif - if ( x11_surface ) - wlserver_x11_surface_info_set_wlr( x11_surface, surface, true ); + if ( x11_surface ) + wlserver_x11_surface_info_set_wlr( x11_surface, surface, true ); if ( x11_surface ) { - for (auto it = g_PendingCommits.begin(); it != g_PendingCommits.end();) + for ( auto it = g_PendingCommits.begin( ); + it != g_PendingCommits.end( ); ) { - if (it->surf == surface) + if ( it->surf == surface ) { PendingCommit_t pending = *it; // Still have the buffer lock from before... - assert(x11_surface); - assert(x11_surface->xwayland_server); - x11_surface->xwayland_server->wayland_commit( pending.surf, pending.buf ); + assert( x11_surface ); + assert( x11_surface->xwayland_server ); + x11_surface->xwayland_server->wayland_commit( + pending.surf, pending.buf ); - it = g_PendingCommits.erase(it); + it = g_PendingCommits.erase( it ); } else { @@ -759,1175 +934,1350 @@ void gamescope_xwayland_server_t::handle_override_window_content( struct wl_clie } } -struct wl_client *gamescope_xwayland_server_t::get_client() -{ - if (!xwayland_server) - return nullptr; - - return xwayland_server->client; -} - -struct wlr_output *gamescope_xwayland_server_t::get_output() +struct wl_client *gamescope_xwayland_server_t::get_client( ) { - return output; -} + if ( !xwayland_server ) return nullptr; -struct wlr_output_state *gamescope_xwayland_server_t::get_output_state() -{ - return output_state; + return xwayland_server->client; } +struct wlr_output *gamescope_xwayland_server_t::get_output( ) { return output; } +struct wlr_output_state *gamescope_xwayland_server_t::get_output_state( ) +{ return output_state; } - - -static void gamescope_xwayland_handle_override_window_content( struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t x11_window ) +static void gamescope_xwayland_handle_override_window_content( + struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource, + uint32_t x11_window ) { - // This should ideally use the surface's xwayland, but we don't know it. - // We probably need to change our override_window_content protocol to add a - // xwayland socket name. - // - // Right now, the surface -> xwayland association comes from the - // handle_wl_id stuff from steamcompmgr. - // However, this surface has no associated X window, and won't recieve - // wl_id stuff as it's meant to replace another window's surface - // which we can't do without knowing the x11_window's xwayland server - // here for it to do that override logic in the first place. - // - // So... Just assume it comes from server 0 for now. - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( 0 ); - assert( server ); - struct wlr_surface *surface = wlr_surface_from_resource( surface_resource ); - server->handle_override_window_content(client, nullptr, surface, x11_window); + // This should ideally use the surface's xwayland, but we don't know it. + // We probably need to change our override_window_content protocol to add a + // xwayland socket name. + // + // Right now, the surface -> xwayland association comes from the + // handle_wl_id stuff from steamcompmgr. + // However, this surface has no associated X window, and won't recieve + // wl_id stuff as it's meant to replace another window's surface + // which we can't do without knowing the x11_window's xwayland server + // here for it to do that override logic in the first place. + // + // So... Just assume it comes from server 0 for now. + gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( 0 ); + assert( server ); + struct wlr_surface *surface = wlr_surface_from_resource( surface_resource ); + server->handle_override_window_content( + client, nullptr, surface, x11_window ); } -static void gamescope_xwayland_handle_destroy( struct wl_client *client, struct wl_resource *resource ) -{ - wl_resource_destroy( resource ); -} +static void gamescope_xwayland_handle_destroy( + struct wl_client *client, struct wl_resource *resource ) +{ wl_resource_destroy( resource ); } static const struct gamescope_xwayland_interface gamescope_xwayland_impl = { - .destroy = gamescope_xwayland_handle_destroy, - .override_window_content = gamescope_xwayland_handle_override_window_content, + .destroy = gamescope_xwayland_handle_destroy, + .override_window_content = + gamescope_xwayland_handle_override_window_content, }; -static void gamescope_xwayland_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id ) +static void gamescope_xwayland_bind( + struct wl_client *client, void *data, uint32_t version, uint32_t id ) { - struct wl_resource *resource = wl_resource_create( client, &gamescope_xwayland_interface, version, id ); - wl_resource_set_implementation( resource, &gamescope_xwayland_impl, NULL, NULL ); + struct wl_resource *resource = wl_resource_create( + client, &gamescope_xwayland_interface, version, id ); + wl_resource_set_implementation( + resource, &gamescope_xwayland_impl, NULL, NULL ); } static void create_gamescope_xwayland( void ) { - uint32_t version = 1; - wl_global_create( wlserver.display, &gamescope_xwayland_interface, version, NULL, gamescope_xwayland_bind ); + uint32_t version = 1; + wl_global_create( + wlserver.display, + &gamescope_xwayland_interface, + version, + NULL, + gamescope_xwayland_bind ); } - - - - - - - - static void gamescope_swapchain_destroy_co( struct wl_resource *resource ) { #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "gamescope_swapchain_destroy_co swapchain: %p", resource ); + wl_log.infof( "gamescope_swapchain_destroy_co swapchain: %p", resource ); #endif - wlserver_wl_surface_info *wl_surface_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource ); - if ( wl_surface_info ) - { + wlserver_wl_surface_info *wl_surface_info = + ( wlserver_wl_surface_info * )wl_resource_get_user_data( resource ); + if ( wl_surface_info ) + { #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "gamescope_swapchain_destroy_co swapchain: %p GOT WAYLAND SURFACE", resource ); + wl_log.infof( + "gamescope_swapchain_destroy_co swapchain: %p GOT WAYLAND SURFACE", + resource ); #endif - wlserver_x11_surface_info *x11_surface = wl_surface_info->x11_surface; - if (x11_surface) - { + wlserver_x11_surface_info *x11_surface = wl_surface_info->x11_surface; + if ( x11_surface ) + { #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "gamescope_swapchain_destroy_co swapchain: %p GOT X11 SURFACE", resource ); + wl_log.infof( + "gamescope_swapchain_destroy_co swapchain: %p GOT X11 SURFACE", + resource ); #endif - x11_surface->xwayland_server->destroy_content_override( x11_surface, wl_surface_info->wlr ); - } - } + x11_surface->xwayland_server->destroy_content_override( + x11_surface, wl_surface_info->wlr ); + } + } } -static void gamescope_swapchain_handle_resource_destroy( struct wl_resource *resource ) +static void +gamescope_swapchain_handle_resource_destroy( struct wl_resource *resource ) { #ifdef GAMESCOPE_SWAPCHAIN_DEBUG - wl_log.infof( "gamescope_swapchain_handle_resource_destroy swapchain: %p", resource ); + wl_log.infof( + "gamescope_swapchain_handle_resource_destroy swapchain: %p", resource ); #endif - wlserver_wl_surface_info *wl_surface_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource ); - if ( wl_surface_info ) - { - gamescope_swapchain_destroy_co( resource ); - std::erase(wl_surface_info->gamescope_swapchains, resource); - } -} - -static void gamescope_swapchain_destroy( struct wl_client *client, struct wl_resource *resource ) -{ - wl_resource_destroy( resource ); -} - -static void gamescope_swapchain_override_window_content( struct wl_client *client, struct wl_resource *resource, uint32_t server_id, uint32_t x11_window ) -{ - wlserver_wl_surface_info *wl_surface_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource ); - - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( server_id ); - assert( server ); - server->handle_override_window_content(client, resource, wl_surface_info->wlr, x11_window); -} - -static void gamescope_swapchain_swapchain_feedback( struct wl_client *client, struct wl_resource *resource, - uint32_t image_count, - uint32_t vk_format, - uint32_t vk_colorspace, - uint32_t vk_composite_alpha, - uint32_t vk_pre_transform, - uint32_t vk_clipped, - const char *vk_engine_name) -{ - wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource ); - if ( wl_info ) - { - wl_info->swapchain_feedback = std::make_unique(wlserver_vk_swapchain_feedback{ - .image_count = image_count, - .vk_format = VkFormat(vk_format), - .vk_colorspace = VkColorSpaceKHR(vk_colorspace), - .vk_composite_alpha = VkCompositeAlphaFlagBitsKHR(vk_composite_alpha), - .vk_pre_transform = VkSurfaceTransformFlagBitsKHR(vk_pre_transform), - .vk_clipped = VkBool32(vk_clipped), - .vk_engine_name = std::make_shared(vk_engine_name), - .hdr_metadata_blob = nullptr, - }); - } -} - -static void gamescope_swapchain_set_hdr_metadata( struct wl_client *client, struct wl_resource *resource, - uint32_t display_primary_red_x, - uint32_t display_primary_red_y, - uint32_t display_primary_green_x, - uint32_t display_primary_green_y, - uint32_t display_primary_blue_x, - uint32_t display_primary_blue_y, - uint32_t white_point_x, - uint32_t white_point_y, - uint32_t max_display_mastering_luminance, - uint32_t min_display_mastering_luminance, - uint32_t max_cll, - uint32_t max_fall) -{ - wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource ); - - if ( wl_info ) - { - if ( !wl_info->swapchain_feedback ) { - wl_log.errorf("set_hdr_metadata with no swapchain_feedback."); - return; - } - - // Check validity of this metadata, - // if it's garbage, just toss it... - if (!max_cll || !max_fall || (!white_point_x && !white_point_y)) - return; - - hdr_output_metadata metadata = {}; - metadata.metadata_type = 0; - - hdr_metadata_infoframe& infoframe = metadata.hdmi_metadata_type1; - infoframe.eotf = HDMI_EOTF_ST2084; - infoframe.metadata_type = 0; - infoframe.display_primaries[0].x = display_primary_red_x; - infoframe.display_primaries[0].y = display_primary_red_y; - infoframe.display_primaries[1].x = display_primary_green_x; - infoframe.display_primaries[1].y = display_primary_green_y; - infoframe.display_primaries[2].x = display_primary_blue_x; - infoframe.display_primaries[2].y = display_primary_blue_y; - infoframe.white_point.x = white_point_x; - infoframe.white_point.y = white_point_y; - infoframe.max_display_mastering_luminance = max_display_mastering_luminance; - infoframe.min_display_mastering_luminance = min_display_mastering_luminance; - infoframe.max_cll = max_cll; - infoframe.max_fall = max_fall; - - wl_info->swapchain_feedback->hdr_metadata_blob = GetBackend()->CreateBackendBlob( metadata ); - } -} - -static void gamescope_swapchain_set_present_time( struct wl_client *client, struct wl_resource *resource, - uint32_t present_id, - uint32_t desired_present_time_hi, - uint32_t desired_present_time_lo) -{ - wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource ); - - if ( wl_info ) - { - wl_info->present_id = present_id; - wl_info->desired_present_time = (uint64_t(desired_present_time_hi) << 32) | desired_present_time_lo; - } -} - -static void gamescope_swapchain_set_present_mode( struct wl_client *client, struct wl_resource *resource, uint32_t present_mode ) -{ - wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource ); - - if ( wl_info ) - { - wl_info->oCurrentPresentMode = VkPresentModeKHR( present_mode ); - } + wlserver_wl_surface_info *wl_surface_info = + ( wlserver_wl_surface_info * )wl_resource_get_user_data( resource ); + if ( wl_surface_info ) + { + gamescope_swapchain_destroy_co( resource ); + std::erase( wl_surface_info->gamescope_swapchains, resource ); + } } -static const struct gamescope_swapchain_interface gamescope_swapchain_impl = { - .destroy = gamescope_swapchain_destroy, - .override_window_content = gamescope_swapchain_override_window_content, - .swapchain_feedback = gamescope_swapchain_swapchain_feedback, - .set_present_mode = gamescope_swapchain_set_present_mode, - .set_hdr_metadata = gamescope_swapchain_set_hdr_metadata, - .set_present_time = gamescope_swapchain_set_present_time, -}; - -static void gamescope_swapchain_factory_v2_destroy( struct wl_client *client, struct wl_resource *resource ) -{ - wl_resource_destroy( resource ); +static void gamescope_swapchain_destroy( + struct wl_client *client, struct wl_resource *resource ) +{ wl_resource_destroy( resource ); } + +static void gamescope_swapchain_override_window_content( + struct wl_client *client, + struct wl_resource *resource, + uint32_t server_id, + uint32_t x11_window ) +{ + wlserver_wl_surface_info *wl_surface_info = + ( wlserver_wl_surface_info * )wl_resource_get_user_data( resource ); + + gamescope_xwayland_server_t *server = + wlserver_get_xwayland_server( server_id ); + assert( server ); + server->handle_override_window_content( + client, resource, wl_surface_info->wlr, x11_window ); +} + +static void gamescope_swapchain_swapchain_feedback( + struct wl_client *client, + struct wl_resource *resource, + uint32_t image_count, + uint32_t vk_format, + uint32_t vk_colorspace, + uint32_t vk_composite_alpha, + uint32_t vk_pre_transform, + uint32_t vk_clipped, + const char *vk_engine_name ) +{ + wlserver_wl_surface_info *wl_info = + ( wlserver_wl_surface_info * )wl_resource_get_user_data( resource ); + if ( wl_info ) + { + wl_info->swapchain_feedback = + std::make_unique( + wlserver_vk_swapchain_feedback{ + .image_count = image_count, + .vk_format = VkFormat( vk_format ), + .vk_colorspace = VkColorSpaceKHR( vk_colorspace ), + .vk_composite_alpha = + VkCompositeAlphaFlagBitsKHR( vk_composite_alpha ), + .vk_pre_transform = + VkSurfaceTransformFlagBitsKHR( vk_pre_transform ), + .vk_clipped = VkBool32( vk_clipped ), + .vk_engine_name = + std::make_shared( vk_engine_name ), + .hdr_metadata_blob = nullptr, + } ); + } } -static void gamescope_swapchain_factory_v2_create_swapchain( struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t id ) -{ - struct wlr_surface *surface = wlr_surface_from_resource( surface_resource ); - - wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface); - - struct wl_resource *gamescope_swapchain_resource - = wl_resource_create( client, &gamescope_swapchain_interface, wl_resource_get_version( resource ), id ); - wl_resource_set_implementation( gamescope_swapchain_resource, &gamescope_swapchain_impl, wl_surface_info, gamescope_swapchain_handle_resource_destroy ); - - if (wl_surface_info->gamescope_swapchains.size()) - wl_log.errorf("create_swapchain: Surface already had a gamescope_swapchain! Warning!"); +static void gamescope_swapchain_set_hdr_metadata( + struct wl_client *client, + struct wl_resource *resource, + uint32_t display_primary_red_x, + uint32_t display_primary_red_y, + uint32_t display_primary_green_x, + uint32_t display_primary_green_y, + uint32_t display_primary_blue_x, + uint32_t display_primary_blue_y, + uint32_t white_point_x, + uint32_t white_point_y, + uint32_t max_display_mastering_luminance, + uint32_t min_display_mastering_luminance, + uint32_t max_cll, + uint32_t max_fall ) +{ + wlserver_wl_surface_info *wl_info = + ( wlserver_wl_surface_info * )wl_resource_get_user_data( resource ); + + if ( wl_info ) + { + if ( !wl_info->swapchain_feedback ) + { + wl_log.errorf( "set_hdr_metadata with no swapchain_feedback." ); + return; + } - wl_surface_info->gamescope_swapchains.emplace_back( gamescope_swapchain_resource ); + // Check validity of this metadata, + // if it's garbage, just toss it... + if ( !max_cll || !max_fall || ( !white_point_x && !white_point_y ) ) + return; + + hdr_output_metadata metadata = {}; + metadata.metadata_type = 0; + + hdr_metadata_infoframe &infoframe = metadata.hdmi_metadata_type1; + infoframe.eotf = HDMI_EOTF_ST2084; + infoframe.metadata_type = 0; + infoframe.display_primaries[ 0 ].x = display_primary_red_x; + infoframe.display_primaries[ 0 ].y = display_primary_red_y; + infoframe.display_primaries[ 1 ].x = display_primary_green_x; + infoframe.display_primaries[ 1 ].y = display_primary_green_y; + infoframe.display_primaries[ 2 ].x = display_primary_blue_x; + infoframe.display_primaries[ 2 ].y = display_primary_blue_y; + infoframe.white_point.x = white_point_x; + infoframe.white_point.y = white_point_y; + infoframe.max_display_mastering_luminance = + max_display_mastering_luminance; + infoframe.min_display_mastering_luminance = + min_display_mastering_luminance; + infoframe.max_cll = max_cll; + infoframe.max_fall = max_fall; + + wl_info->swapchain_feedback->hdr_metadata_blob = + GetBackend( )->CreateBackendBlob( metadata ); + } } -static const struct gamescope_swapchain_factory_v2_interface gamescope_swapchain_factory_v2_impl = { - .destroy = gamescope_swapchain_factory_v2_destroy, - .create_swapchain = gamescope_swapchain_factory_v2_create_swapchain, -}; - -static void gamescope_swapchain_factory_v2_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id ) +static void gamescope_swapchain_set_present_time( + struct wl_client *client, + struct wl_resource *resource, + uint32_t present_id, + uint32_t desired_present_time_hi, + uint32_t desired_present_time_lo ) { - struct wl_resource *resource = wl_resource_create( client, &gamescope_swapchain_factory_v2_interface, version, id ); - wl_resource_set_implementation( resource, &gamescope_swapchain_factory_v2_impl, NULL, NULL ); -} + wlserver_wl_surface_info *wl_info = + ( wlserver_wl_surface_info * )wl_resource_get_user_data( resource ); -static void create_gamescope_swapchain_factory_v2( void ) -{ - uint32_t version = 1; - wl_global_create( wlserver.display, &gamescope_swapchain_factory_v2_interface, version, NULL, gamescope_swapchain_factory_v2_bind ); + if ( wl_info ) + { + wl_info->present_id = present_id; + wl_info->desired_present_time = + ( uint64_t( desired_present_time_hi ) << 32 ) | + desired_present_time_lo; + } } +static void gamescope_swapchain_set_present_mode( + struct wl_client *client, + struct wl_resource *resource, + uint32_t present_mode ) +{ + wlserver_wl_surface_info *wl_info = + ( wlserver_wl_surface_info * )wl_resource_get_user_data( resource ); + if ( wl_info ) + { + wl_info->oCurrentPresentMode = VkPresentModeKHR( present_mode ); + } +} +static const struct gamescope_swapchain_interface gamescope_swapchain_impl = { + .destroy = gamescope_swapchain_destroy, + .override_window_content = gamescope_swapchain_override_window_content, + .swapchain_feedback = gamescope_swapchain_swapchain_feedback, + .set_present_mode = gamescope_swapchain_set_present_mode, + .set_hdr_metadata = gamescope_swapchain_set_hdr_metadata, + .set_present_time = gamescope_swapchain_set_present_time, +}; +static void gamescope_swapchain_factory_v2_destroy( + struct wl_client *client, struct wl_resource *resource ) +{ wl_resource_destroy( resource ); } +static void gamescope_swapchain_factory_v2_create_swapchain( + struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource, + uint32_t id ) +{ + struct wlr_surface *surface = wlr_surface_from_resource( surface_resource ); + wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info( surface ); + struct wl_resource *gamescope_swapchain_resource = wl_resource_create( + client, + &gamescope_swapchain_interface, + wl_resource_get_version( resource ), + id ); + wl_resource_set_implementation( + gamescope_swapchain_resource, + &gamescope_swapchain_impl, + wl_surface_info, + gamescope_swapchain_handle_resource_destroy ); + if ( wl_surface_info->gamescope_swapchains.size( ) ) + wl_log.errorf( + "create_swapchain: Surface already had a gamescope_swapchain! " + "Warning!" ); + wl_surface_info->gamescope_swapchains.emplace_back( + gamescope_swapchain_resource ); +} +static const struct gamescope_swapchain_factory_v2_interface + gamescope_swapchain_factory_v2_impl = { + .destroy = gamescope_swapchain_factory_v2_destroy, + .create_swapchain = gamescope_swapchain_factory_v2_create_swapchain, + }; +static void gamescope_swapchain_factory_v2_bind( + struct wl_client *client, void *data, uint32_t version, uint32_t id ) +{ + struct wl_resource *resource = wl_resource_create( + client, &gamescope_swapchain_factory_v2_interface, version, id ); + wl_resource_set_implementation( + resource, &gamescope_swapchain_factory_v2_impl, NULL, NULL ); +} - -#if HAVE_PIPEWIRE -static void gamescope_pipewire_handle_destroy( struct wl_client *client, struct wl_resource *resource ) +static void create_gamescope_swapchain_factory_v2( void ) { - wl_resource_destroy( resource ); + uint32_t version = 1; + wl_global_create( + wlserver.display, + &gamescope_swapchain_factory_v2_interface, + version, + NULL, + gamescope_swapchain_factory_v2_bind ); } +#if HAVE_PIPEWIRE +static void gamescope_pipewire_handle_destroy( + struct wl_client *client, struct wl_resource *resource ) +{ wl_resource_destroy( resource ); } + static const struct gamescope_pipewire_interface gamescope_pipewire_impl = { - .destroy = gamescope_pipewire_handle_destroy, + .destroy = gamescope_pipewire_handle_destroy, }; -static void gamescope_pipewire_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id ) +static void gamescope_pipewire_bind( + struct wl_client *client, void *data, uint32_t version, uint32_t id ) { - struct wl_resource *resource = wl_resource_create( client, &gamescope_pipewire_interface, version, id ); - wl_resource_set_implementation( resource, &gamescope_pipewire_impl, NULL, NULL ); + struct wl_resource *resource = wl_resource_create( + client, &gamescope_pipewire_interface, version, id ); + wl_resource_set_implementation( + resource, &gamescope_pipewire_impl, NULL, NULL ); - gamescope_pipewire_send_stream_node( resource, get_pipewire_stream_node_id() ); + gamescope_pipewire_send_stream_node( + resource, get_pipewire_stream_node_id( ) ); } static void create_gamescope_pipewire( void ) { - uint32_t version = 1; - wl_global_create( wlserver.display, &gamescope_pipewire_interface, version, NULL, gamescope_pipewire_bind ); + uint32_t version = 1; + wl_global_create( + wlserver.display, + &gamescope_pipewire_interface, + version, + NULL, + gamescope_pipewire_bind ); } #endif // -static void gamescope_control_set_app_target_refresh_cycle( struct wl_client *client, struct wl_resource *resource, uint32_t fps, uint32_t flags ) -{ - auto display_type = gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL; - if ( flags & GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_INTERNAL_DISPLAY ) - display_type = gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; - - steamcompmgr_set_app_refresh_cycle_override( - display_type, - fps, - !!( flags & GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_ALLOW_REFRESH_SWITCHING ), - !( flags & GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_ONLY_CHANGE_REFRESH_RATE ) ); -} - -static void gamescope_control_take_screenshot( struct wl_client *client, struct wl_resource *resource, const char *path, uint32_t type, uint32_t flags ) -{ - gamescope::CScreenshotManager::Get().TakeScreenshot( gamescope::GamescopeScreenshotInfo - { - .szScreenshotPath = path, - .eScreenshotType = (gamescope_control_screenshot_type)type, - .uScreenshotFlags = flags, - .bWaylandRequested = true, - } ); +static void gamescope_control_set_app_target_refresh_cycle( + struct wl_client *client, + struct wl_resource *resource, + uint32_t fps, + uint32_t flags ) +{ + auto display_type = gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL; + if ( flags & GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_INTERNAL_DISPLAY ) + display_type = gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; + + steamcompmgr_set_app_refresh_cycle_override( + display_type, + fps, + !!( flags & + GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_ALLOW_REFRESH_SWITCHING ), + !( flags & + GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_ONLY_CHANGE_REFRESH_RATE ) ); +} + +static void gamescope_control_take_screenshot( + struct wl_client *client, + struct wl_resource *resource, + const char *path, + uint32_t type, + uint32_t flags ) +{ + gamescope::CScreenshotManager::Get( ).TakeScreenshot( + gamescope::GamescopeScreenshotInfo{ + .szScreenshotPath = path, + .eScreenshotType = ( gamescope_control_screenshot_type )type, + .uScreenshotFlags = flags, + .bWaylandRequested = true, + } ); } void drm_sleep_screen( gamescope::GamescopeScreenType eType, bool bSleep ); -static void gamescope_control_display_sleep( struct wl_client *client, struct wl_resource *resource, uint32_t display_type_flags, uint32_t flags ) -{ - if ( flags & ( GAMESCOPE_CONTROL_DISPLAY_SLEEP_FLAGS_SLEEP | GAMESCOPE_CONTROL_DISPLAY_SLEEP_FLAGS_WAKE ) ) - { - const bool sleep = !!( flags & GAMESCOPE_CONTROL_DISPLAY_SLEEP_FLAGS_SLEEP ); - if ( display_type_flags & GAMESCOPE_CONTROL_DISPLAY_TYPE_FLAGS_EXTERNAL_DISPLAY ) - drm_sleep_screen( gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL, sleep ); - - if ( display_type_flags & GAMESCOPE_CONTROL_DISPLAY_TYPE_FLAGS_INTERNAL_DISPLAY ) - drm_sleep_screen( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL, sleep ); - } -} - -extern gamescope::ConVar cv_overlay_unmultiplied_alpha; -extern std::atomic> g_ColorMgmtLooks[EOTF_Count]; - -static gamescope::ConCommand cc_set_look("set_look", "Set a look for a specific EOTF. Eg. set_look mylook.cube (g22 only), set_look pq mylook.cube, set_look mylook_g22.cube mylook_pq.cube", -[]( std::span args ) -{ - if ( args.size() == 2 ) - { - std::string arg1 = std::string{ args[1] }; - bool bRaisesBlackLevelFloor = false; - g_ColorMgmtLooks[ EOTF_Gamma22 ] = LoadCubeLut( arg1.c_str(), bRaisesBlackLevelFloor ); - cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; - g_ColorMgmt.pending.externalDirtyCtr++; - hasRepaint = true; - } - else if ( args.size() == 3 ) - { - std::string arg2 = std::string{ args[2] }; - - bool bRaisesBlackLevelFloor = false; - if ( args[1] == "g22" || args[1] == "G22") - { - g_ColorMgmtLooks[ EOTF_Gamma22 ] = LoadCubeLut( arg2.c_str(), bRaisesBlackLevelFloor ); - } - else if ( args[1] == "pq" || args[1] == "PQ" ) - { - g_ColorMgmtLooks[ EOTF_PQ ] = LoadCubeLut( arg2.c_str(), bRaisesBlackLevelFloor ); - } - else - { - std::string arg1 = std::string{ args[1] }; - - std::shared_ptr pG22LUT; - std::shared_ptr pPQLUT; - - bool bDummy = false; - pG22LUT = LoadCubeLut( arg1.c_str(), bRaisesBlackLevelFloor ); - pPQLUT = LoadCubeLut( arg2.c_str(), bDummy ); - - g_ColorMgmtLooks[ EOTF_Gamma22 ] = pG22LUT; - g_ColorMgmtLooks[ EOTF_PQ ] = pPQLUT; - } - cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; - g_ColorMgmt.pending.externalDirtyCtr++; - hasRepaint = true; - } - else - { - cv_overlay_unmultiplied_alpha = false; - g_ColorMgmtLooks[ EOTF_Gamma22 ] = nullptr; - g_ColorMgmtLooks[ EOTF_PQ ] = nullptr; - g_ColorMgmt.pending.externalDirtyCtr++; - hasRepaint = true; - } -}); - -static void gamescope_control_set_look( struct wl_client *client, struct wl_resource *resource, int g22_fd, int pq_fd, uint32_t flags ) -{ - std::shared_ptr pG22LUT; - std::shared_ptr pPQLUT; - bool bRaisesBlackLevelFloor = false; - - if ( g22_fd >= 0 ) - { - // takes ownership of FD. - FILE *pG22 = fdopen( g22_fd, "r" ); - if ( pG22 ) - { - pG22LUT = LoadCubeLut( pG22, bRaisesBlackLevelFloor ); - fclose( pG22 ); - } - else - { - wl_log.errorf_errno( "gamescope_control_set_look error opening g22 lut" ); - close( g22_fd ); - } - g22_fd = -1; - } - - if ( pq_fd >= 0 ) - { - // takes ownership of FD. - FILE *pPQ = fdopen( pq_fd, "r" ); - if ( pPQ ) - { - bool bDummy = false; - pPQLUT = LoadCubeLut( pPQ, bDummy ); - fclose( pPQ ); - } - else - { - wl_log.errorf_errno( "gamescope_control_set_look error opening pq lut" ); - close( pq_fd ); - } - pq_fd = -1; - } - - cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; - g_ColorMgmtLooks[ EOTF_Gamma22 ] = pG22LUT; - g_ColorMgmtLooks[ EOTF_PQ ] = pPQLUT; - g_ColorMgmt.pending.externalDirtyCtr++; - hasRepaint = true; -} - -static void gamescope_control_unset_look( struct wl_client *client, struct wl_resource *resource ) -{ - cv_overlay_unmultiplied_alpha = false; - g_ColorMgmtLooks[ EOTF_Gamma22 ] = nullptr; - g_ColorMgmtLooks[ EOTF_PQ ] = nullptr; - g_ColorMgmt.pending.externalDirtyCtr++; - hasRepaint = true; -} - -static void gamescope_control_handle_destroy( struct wl_client *client, struct wl_resource *resource ) -{ - wl_resource_destroy( resource ); -} - -static void gamescope_control_request_app_performance_stats( struct wl_client *client, struct wl_resource *resource, uint32_t app_id ) -{ - assert( wlserver_is_lock_held() ); - wlserver.app_perf_requests[ app_id ].push_back( resource ); +static void gamescope_control_display_sleep( + struct wl_client *client, + struct wl_resource *resource, + uint32_t display_type_flags, + uint32_t flags ) +{ + if ( flags & ( GAMESCOPE_CONTROL_DISPLAY_SLEEP_FLAGS_SLEEP | + GAMESCOPE_CONTROL_DISPLAY_SLEEP_FLAGS_WAKE ) ) + { + const bool sleep = + !!( flags & GAMESCOPE_CONTROL_DISPLAY_SLEEP_FLAGS_SLEEP ); + if ( display_type_flags & + GAMESCOPE_CONTROL_DISPLAY_TYPE_FLAGS_EXTERNAL_DISPLAY ) + drm_sleep_screen( + gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL, sleep ); + + if ( display_type_flags & + GAMESCOPE_CONTROL_DISPLAY_TYPE_FLAGS_INTERNAL_DISPLAY ) + drm_sleep_screen( + gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL, sleep ); + } } -void wlserver_app_presented( uint32_t app_id, uint64_t frametime_ns ) -{ - assert( wlserver_is_lock_held() ); +extern gamescope::ConVar cv_overlay_unmultiplied_alpha; +extern std::atomic> g_ColorMgmtLooks[ EOTF_Count ]; - auto it = wlserver.app_perf_requests.find( app_id ); - if ( it == wlserver.app_perf_requests.end() ) - return; +static gamescope::ConCommand cc_set_look( + "set_look", + "Set a look for a specific EOTF. Eg. set_look mylook.cube (g22 only), " + "set_look pq mylook.cube, set_look mylook_g22.cube mylook_pq.cube", + []( std::span args ) + { + if ( args.size( ) == 2 ) + { + std::string arg1 = std::string{ args[ 1 ] }; + bool bRaisesBlackLevelFloor = false; + g_ColorMgmtLooks[ EOTF_Gamma22 ] = + LoadCubeLut( arg1.c_str( ), bRaisesBlackLevelFloor ); + cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; + g_ColorMgmt.pending.externalDirtyCtr++; + hasRepaint = true; + } + else if ( args.size( ) == 3 ) + { + std::string arg2 = std::string{ args[ 2 ] }; - // Fire the response to everyone that requested it - for ( wl_resource *resource : it->second ) - { - uint32_t frametime_hi = frametime_ns >> 32; - uint32_t frametime_lo = frametime_ns & 0xffffffff; + bool bRaisesBlackLevelFloor = false; + if ( args[ 1 ] == "g22" || args[ 1 ] == "G22" ) + { + g_ColorMgmtLooks[ EOTF_Gamma22 ] = + LoadCubeLut( arg2.c_str( ), bRaisesBlackLevelFloor ); + } + else if ( args[ 1 ] == "pq" || args[ 1 ] == "PQ" ) + { + g_ColorMgmtLooks[ EOTF_PQ ] = + LoadCubeLut( arg2.c_str( ), bRaisesBlackLevelFloor ); + } + else + { + std::string arg1 = std::string{ args[ 1 ] }; - gamescope_control_send_app_performance_stats( resource, app_id, frametime_lo, frametime_hi ); - } + std::shared_ptr pG22LUT; + std::shared_ptr pPQLUT; - // Event fired, clear the request until we get the next one - wlserver.app_perf_requests.erase( it ); -} + bool bDummy = false; + pG22LUT = LoadCubeLut( arg1.c_str( ), bRaisesBlackLevelFloor ); + pPQLUT = LoadCubeLut( arg2.c_str( ), bDummy ); -static const struct gamescope_control_interface gamescope_control_impl = { - .destroy = gamescope_control_handle_destroy, - .set_app_target_refresh_cycle = gamescope_control_set_app_target_refresh_cycle, - .take_screenshot = gamescope_control_take_screenshot, - .display_sleep = gamescope_control_display_sleep, - .set_look = gamescope_control_set_look, - .unset_look = gamescope_control_unset_look, - .request_app_performance_stats = gamescope_control_request_app_performance_stats, -}; + g_ColorMgmtLooks[ EOTF_Gamma22 ] = pG22LUT; + g_ColorMgmtLooks[ EOTF_PQ ] = pPQLUT; + } + cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; + g_ColorMgmt.pending.externalDirtyCtr++; + hasRepaint = true; + } + else + { + cv_overlay_unmultiplied_alpha = false; + g_ColorMgmtLooks[ EOTF_Gamma22 ] = nullptr; + g_ColorMgmtLooks[ EOTF_PQ ] = nullptr; + g_ColorMgmt.pending.externalDirtyCtr++; + hasRepaint = true; + } + } ); -static uint32_t get_conn_display_info_flags() +static void gamescope_control_set_look( + struct wl_client *client, + struct wl_resource *resource, + int g22_fd, + int pq_fd, + uint32_t flags ) { - gamescope::IBackendConnector *pConn = GetBackend()->GetCurrentConnector(); + std::shared_ptr pG22LUT; + std::shared_ptr pPQLUT; + bool bRaisesBlackLevelFloor = false; - if ( !pConn ) - return 0; + if ( g22_fd >= 0 ) + { + // takes ownership of FD. + FILE *pG22 = fdopen( g22_fd, "r" ); + if ( pG22 ) + { + pG22LUT = LoadCubeLut( pG22, bRaisesBlackLevelFloor ); + fclose( pG22 ); + } + else + { + wl_log.errorf_errno( + "gamescope_control_set_look error opening g22 lut" ); + close( g22_fd ); + } + g22_fd = -1; + } - uint32_t flags = 0; - if ( pConn->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; - if ( pConn->SupportsVRR() ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; - if ( pConn->GetHDRInfo().bExposeHDRSupport ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; + if ( pq_fd >= 0 ) + { + // takes ownership of FD. + FILE *pPQ = fdopen( pq_fd, "r" ); + if ( pPQ ) + { + bool bDummy = false; + pPQLUT = LoadCubeLut( pPQ, bDummy ); + fclose( pPQ ); + } + else + { + wl_log.errorf_errno( + "gamescope_control_set_look error opening pq lut" ); + close( pq_fd ); + } + pq_fd = -1; + } - return flags; + cv_overlay_unmultiplied_alpha = bRaisesBlackLevelFloor; + g_ColorMgmtLooks[ EOTF_Gamma22 ] = pG22LUT; + g_ColorMgmtLooks[ EOTF_PQ ] = pPQLUT; + g_ColorMgmt.pending.externalDirtyCtr++; + hasRepaint = true; +} + +static void gamescope_control_unset_look( + struct wl_client *client, struct wl_resource *resource ) +{ + cv_overlay_unmultiplied_alpha = false; + g_ColorMgmtLooks[ EOTF_Gamma22 ] = nullptr; + g_ColorMgmtLooks[ EOTF_PQ ] = nullptr; + g_ColorMgmt.pending.externalDirtyCtr++; + hasRepaint = true; +} + +static void gamescope_control_handle_destroy( + struct wl_client *client, struct wl_resource *resource ) +{ wl_resource_destroy( resource ); } + +static void gamescope_control_request_app_performance_stats( + struct wl_client *client, struct wl_resource *resource, uint32_t app_id ) +{ + assert( wlserver_is_lock_held( ) ); + wlserver.app_perf_requests[ app_id ].push_back( resource ); +} + +void wlserver_app_presented( uint32_t app_id, uint64_t frametime_ns ) +{ + assert( wlserver_is_lock_held( ) ); + + auto it = wlserver.app_perf_requests.find( app_id ); + if ( it == wlserver.app_perf_requests.end( ) ) return; + + // Fire the response to everyone that requested it + for ( wl_resource *resource : it->second ) + { + uint32_t frametime_hi = frametime_ns >> 32; + uint32_t frametime_lo = frametime_ns & 0xffffffff; + + gamescope_control_send_app_performance_stats( + resource, app_id, frametime_lo, frametime_hi ); + } + + // Event fired, clear the request until we get the next one + wlserver.app_perf_requests.erase( it ); +} + +static const struct gamescope_control_interface gamescope_control_impl = { + .destroy = gamescope_control_handle_destroy, + .set_app_target_refresh_cycle = + gamescope_control_set_app_target_refresh_cycle, + .take_screenshot = gamescope_control_take_screenshot, + .display_sleep = gamescope_control_display_sleep, + .set_look = gamescope_control_set_look, + .unset_look = gamescope_control_unset_look, + .request_app_performance_stats = + gamescope_control_request_app_performance_stats, +}; + +static uint32_t get_conn_display_info_flags( ) +{ + gamescope::IBackendConnector *pConn = GetBackend( )->GetCurrentConnector( ); + + if ( !pConn ) return 0; + + uint32_t flags = 0; + if ( pConn->GetScreenType( ) == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; + if ( pConn->SupportsVRR( ) ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; + if ( pConn->GetHDRInfo( ).bExposeHDRSupport ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; + + return flags; } void wlserver_send_gamescope_control( wl_resource *control ) { - assert( wlserver_is_lock_held() ); - - gamescope::IBackendConnector *pConn = GetBackend()->GetCurrentConnector(); - if ( !pConn ) - return; - - uint32_t flags = get_conn_display_info_flags(); - - struct wl_array display_rates; - wl_array_init(&display_rates); - if ( pConn->GetValidDynamicRefreshRates().size() ) - { - for ( uint32_t uRateHz : pConn->GetValidDynamicRefreshRates() ) - { - uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, sizeof( uint32_t ) ); - *ptr = uRateHz; - } - } - else if ( g_nOutputRefresh > 0 ) - { - uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, sizeof(uint32_t) ); - *ptr = (uint32_t)gamescope::ConvertmHzToHz( g_nOutputRefresh ); - } - gamescope_control_send_active_display_info( control, pConn->GetName(), pConn->GetMake(), pConn->GetModel(), flags, &display_rates ); - wl_array_release(&display_rates); -} - -static void gamescope_control_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id ) -{ - struct wl_resource *resource = wl_resource_create( client, &gamescope_control_interface, version, id ); - wl_resource_set_implementation( resource, &gamescope_control_impl, NULL, - [](struct wl_resource *resource) - { - std::erase_if(wlserver.gamescope_controls, [=](struct wl_resource *control) { return control == resource; }); - for ( auto &[ app_id, resources] : wlserver.app_perf_requests ) - { - std::erase_if( resources, [=]( struct wl_resource *control ) { return control == resource; } ); - } - }); - - // Send feature support - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_RESHADE_SHADERS, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DISPLAY_INFO, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_PIXEL_FILTER, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_REFRESH_CYCLE_ONLY_CHANGE_REFRESH_RATE, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_MURA_CORRECTION, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_LOOK, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_PERF_QUERY, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DONE, 0, 0 ); - - wlserver_send_gamescope_control( resource ); - - wlserver.gamescope_controls.push_back(resource); + assert( wlserver_is_lock_held( ) ); + + gamescope::IBackendConnector *pConn = GetBackend( )->GetCurrentConnector( ); + if ( !pConn ) return; + + uint32_t flags = get_conn_display_info_flags( ); + + struct wl_array display_rates; + wl_array_init( &display_rates ); + if ( pConn->GetValidDynamicRefreshRates( ).size( ) ) + { + for ( uint32_t uRateHz : pConn->GetValidDynamicRefreshRates( ) ) + { + uint32_t *ptr = ( uint32_t * )wl_array_add( + &display_rates, sizeof( uint32_t ) ); + *ptr = uRateHz; + } + } + else if ( g_nOutputRefresh > 0 ) + { + uint32_t *ptr = + ( uint32_t * )wl_array_add( &display_rates, sizeof( uint32_t ) ); + *ptr = ( uint32_t )gamescope::ConvertmHzToHz( g_nOutputRefresh ); + } + gamescope_control_send_active_display_info( + control, + pConn->GetName( ), + pConn->GetMake( ), + pConn->GetModel( ), + flags, + &display_rates ); + wl_array_release( &display_rates ); +} + +static void gamescope_control_bind( + struct wl_client *client, void *data, uint32_t version, uint32_t id ) +{ + struct wl_resource *resource = + wl_resource_create( client, &gamescope_control_interface, version, id ); + wl_resource_set_implementation( + resource, + &gamescope_control_impl, + NULL, + []( struct wl_resource *resource ) + { + std::erase_if( + wlserver.gamescope_controls, + [ = ]( struct wl_resource *control ) + { return control == resource; } ); + for ( auto &[ app_id, resources ] : wlserver.app_perf_requests ) + { + std::erase_if( + resources, + [ = ]( struct wl_resource *control ) + { return control == resource; } ); + } + } ); + + // Send feature support + gamescope_control_send_feature_support( + resource, GAMESCOPE_CONTROL_FEATURE_RESHADE_SHADERS, 1, 0 ); + gamescope_control_send_feature_support( + resource, GAMESCOPE_CONTROL_FEATURE_DISPLAY_INFO, 1, 0 ); + gamescope_control_send_feature_support( + resource, GAMESCOPE_CONTROL_FEATURE_PIXEL_FILTER, 1, 0 ); + gamescope_control_send_feature_support( + resource, + GAMESCOPE_CONTROL_FEATURE_REFRESH_CYCLE_ONLY_CHANGE_REFRESH_RATE, + 1, + 0 ); + gamescope_control_send_feature_support( + resource, GAMESCOPE_CONTROL_FEATURE_MURA_CORRECTION, 1, 0 ); + gamescope_control_send_feature_support( + resource, GAMESCOPE_CONTROL_FEATURE_LOOK, 1, 0 ); + gamescope_control_send_feature_support( + resource, GAMESCOPE_CONTROL_FEATURE_PERF_QUERY, 1, 0 ); + gamescope_control_send_feature_support( + resource, GAMESCOPE_CONTROL_FEATURE_DONE, 0, 0 ); + + wlserver_send_gamescope_control( resource ); + + wlserver.gamescope_controls.push_back( resource ); } static void create_gamescope_control( void ) { - uint32_t version = 6; - wl_global_create( wlserver.display, &gamescope_control_interface, version, NULL, gamescope_control_bind ); + uint32_t version = 6; + wl_global_create( + wlserver.display, + &gamescope_control_interface, + version, + NULL, + gamescope_control_bind ); } //////////////////////// // gamescope_private //////////////////////// -static void gamescope_private_execute( struct wl_client *client, struct wl_resource *resource, const char *cvar_name, const char *value ) +static void gamescope_private_execute( + struct wl_client *client, + struct wl_resource *resource, + const char *cvar_name, + const char *value ) { - std::vector args; - args.emplace_back( cvar_name ); - args.emplace_back( value ); - if ( gamescope::ConCommand::Exec( std::span{ args } ) ) - gamescope_private_send_command_executed( resource ); + std::vector args; + args.emplace_back( cvar_name ); + args.emplace_back( value ); + if ( gamescope::ConCommand::Exec( std::span{ args } ) ) + gamescope_private_send_command_executed( resource ); } -static void gamescope_private_handle_destroy( struct wl_client *client, struct wl_resource *resource ) -{ - wl_resource_destroy( resource ); -} +static void gamescope_private_handle_destroy( + struct wl_client *client, struct wl_resource *resource ) +{ wl_resource_destroy( resource ); } static const struct gamescope_private_interface gamescope_private_impl = { - .destroy = gamescope_private_handle_destroy, - .execute = gamescope_private_execute, + .destroy = gamescope_private_handle_destroy, + .execute = gamescope_private_execute, }; -static void gamescope_private_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id ) +static void gamescope_private_bind( + struct wl_client *client, void *data, uint32_t version, uint32_t id ) { - struct wl_resource *resource = wl_resource_create( client, &gamescope_private_interface, version, id ); - console_log.m_LoggingListeners[(uintptr_t)resource] = [ resource ]( LogPriority ePriority, std::string_view psvScope, std::string_view psvText ) - { - if ( !wlserver_is_lock_held() ) - return; - - // Can't send a length with string on Wayland api currently... :( - std::string sText = std::string{ psvText }; - gamescope_private_send_log( resource, sText.c_str() ); - }; - wl_resource_set_implementation( resource, &gamescope_private_impl, NULL, - [](struct wl_resource *resource) - { - console_log.m_LoggingListeners.erase( (uintptr_t)resource ); - }); + struct wl_resource *resource = + wl_resource_create( client, &gamescope_private_interface, version, id ); + console_log.m_LoggingListeners[ ( uintptr_t )resource ] = + [ resource ]( + LogPriority ePriority, + std::string_view psvScope, + std::string_view psvText ) + { + if ( !wlserver_is_lock_held( ) ) return; + // Can't send a length with string on Wayland api currently... :( + std::string sText = std::string{ psvText }; + gamescope_private_send_log( resource, sText.c_str( ) ); + }; + wl_resource_set_implementation( + resource, + &gamescope_private_impl, + NULL, + []( struct wl_resource *resource ) + { console_log.m_LoggingListeners.erase( ( uintptr_t )resource ); } ); } static void create_gamescope_private( void ) { - uint32_t version = 1; - wl_global_create( wlserver.display, &gamescope_private_interface, version, NULL, gamescope_private_bind ); + uint32_t version = 1; + wl_global_create( + wlserver.display, + &gamescope_private_interface, + version, + NULL, + gamescope_private_bind ); } -static void create_explicit_sync() -{ - new gamescope::WaylandServer::CLinuxDrmSyncobj( wlserver.display ); -} - -static void create_reshade() -{ - new gamescope::WaylandServer::CReshade( wlserver.display ); -} +static void create_explicit_sync( ) +{ new gamescope::WaylandServer::CLinuxDrmSyncobj( wlserver.display ); } +static void create_reshade( ) +{ new gamescope::WaylandServer::CReshade( wlserver.display ); } //////////////////////// // presentation-time //////////////////////// -static void presentation_time_destroy( struct wl_client *client, struct wl_resource *resource ) -{ - wl_resource_destroy( resource ); -} +static void presentation_time_destroy( + struct wl_client *client, struct wl_resource *resource ) +{ wl_resource_destroy( resource ); } -static void presentation_time_feedback( struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t id ) +static void presentation_time_feedback( + struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource, + uint32_t id ) { - struct wlr_surface *surface = wlr_surface_from_resource( surface_resource ); + struct wlr_surface *surface = wlr_surface_from_resource( surface_resource ); - wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface); + wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info( surface ); - struct wl_resource *presentation_feedback_resource - = wl_resource_create( client, &wp_presentation_feedback_interface, wl_resource_get_version( resource ), id ); - wl_resource_set_implementation( presentation_feedback_resource, NULL, wl_surface_info, NULL ); + struct wl_resource *presentation_feedback_resource = wl_resource_create( + client, + &wp_presentation_feedback_interface, + wl_resource_get_version( resource ), + id ); + wl_resource_set_implementation( + presentation_feedback_resource, NULL, wl_surface_info, NULL ); - wl_surface_info->pending_presentation_feedbacks.emplace_back(presentation_feedback_resource); + wl_surface_info->pending_presentation_feedbacks.emplace_back( + presentation_feedback_resource ); } static const struct wp_presentation_interface presentation_time_impl = { - .destroy = presentation_time_destroy, - .feedback = presentation_time_feedback, + .destroy = presentation_time_destroy, + .feedback = presentation_time_feedback, }; -static void presentation_time_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id ) +static void presentation_time_bind( + struct wl_client *client, void *data, uint32_t version, uint32_t id ) { - struct wl_resource *resource = wl_resource_create( client, &wp_presentation_interface, version, id ); - wl_resource_set_implementation( resource, &presentation_time_impl, NULL, NULL ); + struct wl_resource *resource = + wl_resource_create( client, &wp_presentation_interface, version, id ); + wl_resource_set_implementation( + resource, &presentation_time_impl, NULL, NULL ); - wp_presentation_send_clock_id( resource, CLOCK_MONOTONIC ); + wp_presentation_send_clock_id( resource, CLOCK_MONOTONIC ); } static void create_presentation_time( void ) { - uint32_t version = 1; - wl_global_create( wlserver.display, &wp_presentation_interface, version, NULL, presentation_time_bind ); + uint32_t version = 1; + wl_global_create( + wlserver.display, + &wp_presentation_interface, + version, + NULL, + presentation_time_bind ); } -void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std::vector& presentation_feedbacks, uint64_t last_refresh_nsec, uint64_t refresh_cycle ) +void wlserver_presentation_feedback_presented( + struct wlr_surface *surface, + std::vector &presentation_feedbacks, + uint64_t last_refresh_nsec, + uint64_t refresh_cycle ) { - wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface); + wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info( surface ); - if ( !wl_surface_info ) - return; + if ( !wl_surface_info ) return; - uint32_t flags = 0; + uint32_t flags = 0; - // Don't know when we want to send this. - // Not useful for an app to know. - flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC; + // Don't know when we want to send this. + // Not useful for an app to know. + flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC; - // We set HW clock because it's based on the HW clock of the last page flip. - flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + // We set HW clock because it's based on the HW clock of the last page flip. + flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; - // We don't set HW_COMPLETION because we actually kinda are using a timer here. - // We want to signal this event at latest latch time, but with the info of the refresh - // cadence we want to track. + // We don't set HW_COMPLETION because we actually kinda are using a timer + // here. We want to signal this event at latest latch time, but with the + // info of the refresh cadence we want to track. - // Probably. Not delaying sending this to find out. - // Not useful for an app to know. - flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; + // Probably. Not delaying sending this to find out. + // Not useful for an app to know. + flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; - wl_surface_info->sequence++; + wl_surface_info->sequence++; - for (auto& feedback : presentation_feedbacks) - { - timespec last_refresh_ts; - last_refresh_ts.tv_sec = time_t(last_refresh_nsec / 1'000'000'000ul); - last_refresh_ts.tv_nsec = long(last_refresh_nsec % 1'000'000'000ul); - - wp_presentation_feedback_send_presented( - feedback, - last_refresh_ts.tv_sec >> 32, - last_refresh_ts.tv_sec & 0xffffffff, - last_refresh_ts.tv_nsec, - uint32_t(refresh_cycle), - wl_surface_info->sequence >> 32, - wl_surface_info->sequence & 0xffffffff, - flags); - wl_resource_destroy(feedback); - } + for ( auto &feedback : presentation_feedbacks ) + { + timespec last_refresh_ts; + last_refresh_ts.tv_sec = time_t( last_refresh_nsec / 1'000'000'000ul ); + last_refresh_ts.tv_nsec = long( last_refresh_nsec % 1'000'000'000ul ); + + wp_presentation_feedback_send_presented( + feedback, + last_refresh_ts.tv_sec >> 32, + last_refresh_ts.tv_sec & 0xffffffff, + last_refresh_ts.tv_nsec, + uint32_t( refresh_cycle ), + wl_surface_info->sequence >> 32, + wl_surface_info->sequence & 0xffffffff, + flags ); + wl_resource_destroy( feedback ); + } - presentation_feedbacks.clear(); + presentation_feedbacks.clear( ); } -void wlserver_presentation_feedback_discard( struct wlr_surface *surface, std::vector& presentation_feedbacks ) +void wlserver_presentation_feedback_discard( + struct wlr_surface *surface, + std::vector &presentation_feedbacks ) { - wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface); + wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info( surface ); - if ( !wl_surface_info ) - return; + if ( !wl_surface_info ) return; - wl_surface_info->sequence++; + wl_surface_info->sequence++; - for (auto& feedback : presentation_feedbacks) - { - wp_presentation_feedback_send_discarded(feedback); - } - presentation_feedbacks.clear(); + for ( auto &feedback : presentation_feedbacks ) + { + wp_presentation_feedback_send_discarded( feedback ); + } + presentation_feedbacks.clear( ); } /////////////////////// - -void wlserver_past_present_timing( struct wlr_surface *surface, uint32_t present_id, uint64_t desired_present_time, uint64_t actual_present_time, uint64_t earliest_present_time, uint64_t present_margin ) +void wlserver_past_present_timing( + struct wlr_surface *surface, + uint32_t present_id, + uint64_t desired_present_time, + uint64_t actual_present_time, + uint64_t earliest_present_time, + uint64_t present_margin ) { - wlserver_wl_surface_info *wl_info = get_wl_surface_info( surface ); - if ( !wl_info ) - return; + wlserver_wl_surface_info *wl_info = get_wl_surface_info( surface ); + if ( !wl_info ) return; - for (auto& swapchain : wl_info->gamescope_swapchains) { - gamescope_swapchain_send_past_present_timing( - swapchain, - present_id, - desired_present_time >> 32, - desired_present_time & 0xffffffff, - actual_present_time >> 32, - actual_present_time & 0xffffffff, - earliest_present_time >> 32, - earliest_present_time & 0xffffffff, - present_margin >> 32, - present_margin & 0xffffffff); - } + for ( auto &swapchain : wl_info->gamescope_swapchains ) + { + gamescope_swapchain_send_past_present_timing( + swapchain, + present_id, + desired_present_time >> 32, + desired_present_time & 0xffffffff, + actual_present_time >> 32, + actual_present_time & 0xffffffff, + earliest_present_time >> 32, + earliest_present_time & 0xffffffff, + present_margin >> 32, + present_margin & 0xffffffff ); + } } -void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle ) +void wlserver_refresh_cycle( + struct wlr_surface *surface, uint64_t refresh_cycle ) { - wlserver_wl_surface_info *wl_info = get_wl_surface_info( surface ); - if ( !wl_info ) - return; + wlserver_wl_surface_info *wl_info = get_wl_surface_info( surface ); + if ( !wl_info ) return; - for (auto& swapchain : wl_info->gamescope_swapchains) { - gamescope_swapchain_send_refresh_cycle( - swapchain, - refresh_cycle >> 32, - refresh_cycle & 0xffffffff); - } + for ( auto &swapchain : wl_info->gamescope_swapchains ) + { + gamescope_swapchain_send_refresh_cycle( + swapchain, refresh_cycle >> 32, refresh_cycle & 0xffffffff ); + } } /////////////////////// #if HAVE_SESSION -bool wlsession_active() -{ - return wlserver.wlr.session->active; -} +bool wlsession_active( ) { return wlserver.wlr.session->active; } static void handle_session_active( struct wl_listener *listener, void *data ) { - GetBackend()->DirtyState( wlserver.wlr.session->active, wlserver.wlr.session->active ); - wl_log.infof( "Session %s", wlserver.wlr.session->active ? "resumed" : "paused" ); + GetBackend( )->DirtyState( + wlserver.wlr.session->active, wlserver.wlr.session->active ); + wl_log.infof( + "Session %s", wlserver.wlr.session->active ? "resumed" : "paused" ); } #endif -static void handle_wlr_log(enum wlr_log_importance importance, const char *fmt, va_list args) +static void handle_wlr_log( + enum wlr_log_importance importance, const char *fmt, va_list args ) { - enum LogPriority prio; - switch (importance) { - case WLR_ERROR: - prio = LOG_ERROR; - break; - case WLR_INFO: - prio = LOG_INFO; - break; - default: - prio = LOG_DEBUG; - break; - } + enum LogPriority prio; + switch ( importance ) + { + case WLR_ERROR: + prio = LOG_ERROR; + break; + case WLR_INFO: + prio = LOG_INFO; + break; + default: + prio = LOG_DEBUG; + break; + } - wl_log.vlogf(prio, fmt, args); + wl_log.vlogf( prio, fmt, args ); } void wlserver_set_output_info( const wlserver_output_info *info ) { - free(wlserver.output_info.description); - wlserver.output_info.description = strdup(info->description); - wlserver.output_info.phys_width = info->phys_width; - wlserver.output_info.phys_height = info->phys_height; + free( wlserver.output_info.description ); + wlserver.output_info.description = strdup( info->description ); + wlserver.output_info.phys_width = info->phys_width; + wlserver.output_info.phys_height = info->phys_height; - if (wlserver.wlr.xwayland_servers.empty()) - return; + if ( wlserver.wlr.xwayland_servers.empty( ) ) return; - gamescope_xwayland_server_t *server = wlserver.wlr.xwayland_servers[0].get(); - server->update_output_info(); + gamescope_xwayland_server_t *server = + wlserver.wlr.xwayland_servers[ 0 ].get( ); + server->update_output_info( ); } -static bool filter_global(const struct wl_client *client, const struct wl_global *global, void *data) +static bool filter_global( + const struct wl_client *client, const struct wl_global *global, void *data ) { - const struct wl_interface *iface = wl_global_get_interface(global); + const struct wl_interface *iface = wl_global_get_interface( global ); - if ( cv_drm_debug_disable_explicit_sync && iface->name == "wp_linux_drm_syncobj_manager_v1"sv ) - return false; + if ( cv_drm_debug_disable_explicit_sync && + iface->name == "wp_linux_drm_syncobj_manager_v1"sv ) + return false; - if (strcmp(iface->name, wl_output_interface.name) != 0) - return true; + if ( strcmp( iface->name, wl_output_interface.name ) != 0 ) return true; - struct wlr_output *output = (struct wlr_output *) wl_global_get_user_data(global); - if (!output) - { - /* Can happen if the output has been destroyed, but the client hasn't - * received the wl_registry.global_remove event yet. */ - return false; - } + struct wlr_output *output = + ( struct wlr_output * )wl_global_get_user_data( global ); + if ( !output ) + { + /* Can happen if the output has been destroyed, but the client hasn't + * received the wl_registry.global_remove event yet. */ + return false; + } - if (g_bShutdownWLServer) - return false; + if ( g_bShutdownWLServer ) return false; - /* We create one wl_output global per Xwayland server, to easily have - * per-server output configuration. Only expose the wl_output belonging to - * the server. */ - for (size_t i = 0; i < wlserver.wlr.xwayland_servers.size(); i++) { - gamescope_xwayland_server_t *server = wlserver.wlr.xwayland_servers[i].get(); - if (server && server->get_client() == client) - return server->get_output() == output; - } + /* We create one wl_output global per Xwayland server, to easily have + * per-server output configuration. Only expose the wl_output belonging to + * the server. */ + for ( size_t i = 0; i < wlserver.wlr.xwayland_servers.size( ); i++ ) + { + gamescope_xwayland_server_t *server = + wlserver.wlr.xwayland_servers[ i ].get( ); + if ( server && server->get_client( ) == client ) + return server->get_output( ) == output; + } - if (wlserver.wlr.xwayland_servers.empty()) - return false; + if ( wlserver.wlr.xwayland_servers.empty( ) ) return false; - gamescope_xwayland_server_t *server = wlserver.wlr.xwayland_servers[0].get(); - /* If we aren't an xwayland server, then only expose the first wl_output - * that's associated with from server 0. */ - return server && server->get_output() == output; + gamescope_xwayland_server_t *server = + wlserver.wlr.xwayland_servers[ 0 ].get( ); + /* If we aren't an xwayland server, then only expose the first wl_output + * that's associated with from server 0. */ + return server && server->get_output( ) == output; } -bool wlsession_init( void ) { - static bool s_bInitted = false; - if ( s_bInitted ) - return true; +bool wlsession_init( void ) +{ + static bool s_bInitted = false; + if ( s_bInitted ) return true; - wlr_log_init(WLR_DEBUG, handle_wlr_log); + wlr_log_init( WLR_DEBUG, handle_wlr_log ); - wlserver.display = wl_display_create(); - wlserver.event_loop = wl_display_get_event_loop( wlserver.display ); - wlserver.wlr.headless_backend = wlr_headless_backend_create( wlserver.event_loop ); + wlserver.display = wl_display_create( ); + wlserver.event_loop = wl_display_get_event_loop( wlserver.display ); + wlserver.wlr.headless_backend = + wlr_headless_backend_create( wlserver.event_loop ); - wl_display_set_global_filter(wlserver.display, filter_global, nullptr); + wl_display_set_global_filter( wlserver.display, filter_global, nullptr ); - const struct wlserver_output_info output_info = { - .description = "Virtual gamescope output", - }; - wlserver_set_output_info( &output_info ); + const struct wlserver_output_info output_info = { + .description = "Virtual gamescope output", + }; + wlserver_set_output_info( &output_info ); #if HAVE_SESSION - if ( !GetBackend()->IsSessionBased() ) - { - s_bInitted = true; - return true; - } - - wlserver.wlr.session = wlr_session_create( wlserver.event_loop ); - if ( wlserver.wlr.session == nullptr ) - { - wl_log.errorf( "Failed to create session" ); - return false; - } - - wlserver.session_active.notify = handle_session_active; - wl_signal_add( &wlserver.wlr.session->events.active, &wlserver.session_active ); + if ( !GetBackend( )->IsSessionBased( ) ) + { + s_bInitted = true; + return true; + } + + wlserver.wlr.session = wlr_session_create( wlserver.event_loop ); + if ( wlserver.wlr.session == nullptr ) + { + wl_log.errorf( "Failed to create session" ); + return false; + } + + wlserver.session_active.notify = handle_session_active; + wl_signal_add( + &wlserver.wlr.session->events.active, &wlserver.session_active ); #endif - s_bInitted = true; + s_bInitted = true; - return true; + return true; } #if HAVE_SESSION static void kms_device_handle_change( struct wl_listener *listener, void *data ) { - GetBackend()->DirtyState(); - wl_log.infof( "Got change event for KMS device" ); + GetBackend( )->DirtyState( ); + wl_log.infof( "Got change event for KMS device" ); - nudge_steamcompmgr(); + nudge_steamcompmgr( ); } -int wlsession_open_kms( const char *device_name ) { - if ( device_name != nullptr ) - { - wlserver.wlr.device = wlr_session_open_file( wlserver.wlr.session, device_name ); - if ( wlserver.wlr.device == nullptr ) - return -1; - } - else - { - ssize_t n = wlr_session_find_gpus( wlserver.wlr.session, 1, &wlserver.wlr.device ); - if ( n < 0 ) - { - wl_log.errorf( "Failed to list GPUs" ); - return -1; - } - if ( n == 0 ) - { - wl_log.errorf( "No GPU detected" ); - return -1; - } - } +int wlsession_open_kms( const char *device_name ) +{ + if ( device_name != nullptr ) + { + wlserver.wlr.device = + wlr_session_open_file( wlserver.wlr.session, device_name ); + if ( wlserver.wlr.device == nullptr ) return -1; + } + else + { + ssize_t n = wlr_session_find_gpus( + wlserver.wlr.session, 1, &wlserver.wlr.device ); + if ( n < 0 ) + { + wl_log.errorf( "Failed to list GPUs" ); + return -1; + } + if ( n == 0 ) + { + wl_log.errorf( "No GPU detected" ); + return -1; + } + } - struct wl_listener *listener = new wl_listener(); - listener->notify = kms_device_handle_change; - wl_signal_add( &wlserver.wlr.device->events.change, listener ); + struct wl_listener *listener = new wl_listener( ); + listener->notify = kms_device_handle_change; + wl_signal_add( &wlserver.wlr.device->events.change, listener ); - return wlserver.wlr.device->fd; + return wlserver.wlr.device->fd; } -void wlsession_close_kms() -{ - wlr_session_close_file( wlserver.wlr.session, wlserver.wlr.device ); -} +void wlsession_close_kms( ) +{ wlr_session_close_file( wlserver.wlr.session, wlserver.wlr.device ); } #endif -gamescope_xwayland_server_t::gamescope_xwayland_server_t(wl_display *display, int nIndex) +gamescope_xwayland_server_t::gamescope_xwayland_server_t( + wl_display *display, int nIndex ) { - m_nIndex = nIndex; + m_nIndex = nIndex; - struct wlr_xwayland_server_options xwayland_options = { - .lazy = false, - .enable_wm = false, - .no_touch_pointer_emulation = true, - .force_xrandr_emulation = true, - }; - xwayland_server = wlr_xwayland_server_create(display, &xwayland_options); - wl_signal_add(&xwayland_server->events.ready, &xwayland_ready_listener); + struct wlr_xwayland_server_options xwayland_options = { + .lazy = false, + .enable_wm = false, + .no_touch_pointer_emulation = true, + .force_xrandr_emulation = true, + }; + xwayland_server = wlr_xwayland_server_create( display, &xwayland_options ); + wl_signal_add( &xwayland_server->events.ready, &xwayland_ready_listener ); - output = wlr_headless_add_output(wlserver.wlr.headless_backend, 1280, 720); - output_state = new wlr_output_state; - wlr_output_state_init(output_state); + output = + wlr_headless_add_output( wlserver.wlr.headless_backend, 1280, 720 ); + output_state = new wlr_output_state; + wlr_output_state_init( output_state ); - output->make = strdup("gamescope"); // freed by wlroots - output->model = strdup("gamescope"); // freed by wlroots - wlr_output_set_name(output, "gamescope"); + output->make = strdup( "gamescope" ); // freed by wlroots + output->model = strdup( "gamescope" ); // freed by wlroots + wlr_output_set_name( output, "gamescope" ); - int refresh = g_nNestedRefresh; - if (refresh == 0) { - refresh = g_nOutputRefresh; - } + int refresh = g_nNestedRefresh; + if ( refresh == 0 ) { refresh = g_nOutputRefresh; } - int width = g_nNestedWidth; - int height = g_nNestedHeight; - if ( g_nXWaylandCount > 1 && nIndex == 0 ) - { - width = g_nOutputWidth; - height = g_nOutputHeight; - } + int width = g_nNestedWidth; + int height = g_nNestedHeight; + if ( g_nXWaylandCount > 1 && nIndex == 0 ) + { + width = g_nOutputWidth; + height = g_nOutputHeight; + } - wlr_output_state_set_enabled(output_state, true); - wlr_output_state_set_custom_mode(output_state, width, height, refresh); - if (!wlr_output_commit_state(output, output_state)) - { - wl_log.errorf("Failed to commit headless output"); - abort(); - } + wlr_output_state_set_enabled( output_state, true ); + wlr_output_state_set_custom_mode( output_state, width, height, refresh ); + if ( !wlr_output_commit_state( output, output_state ) ) + { + wl_log.errorf( "Failed to commit headless output" ); + abort( ); + } - update_output_info(); + update_output_info( ); - wlr_output_create_global(output, wlserver.display); + wlr_output_create_global( output, wlserver.display ); } -gamescope_xwayland_server_t::~gamescope_xwayland_server_t() +gamescope_xwayland_server_t::~gamescope_xwayland_server_t( ) { - /* Clear content overrides */ - for (auto& co : content_overrides) - { - wl_list_remove( &co.second->surface_destroy_listener.link ); - free( co.second ); - } - content_overrides.clear(); + /* Clear content overrides */ + for ( auto &co : content_overrides ) + { + wl_list_remove( &co.second->surface_destroy_listener.link ); + free( co.second ); + } + content_overrides.clear( ); - wlr_xwayland_server_destroy(xwayland_server); - xwayland_server = nullptr; + wlr_xwayland_server_destroy( xwayland_server ); + xwayland_server = nullptr; - wlr_output_destroy(output); - delete output_state; + wlr_output_destroy( output ); + delete output_state; } -void gamescope_xwayland_server_t::update_output_info() +void gamescope_xwayland_server_t::update_output_info( ) { - const auto *info = &wlserver.output_info; + const auto *info = &wlserver.output_info; - output->phys_width = info->phys_width; - output->phys_height = info->phys_height; - struct wl_resource *resource; - wl_resource_for_each(resource, &output->resources) { - wl_output_send_geometry(resource, 0, 0, - output->phys_width, output->phys_height, output->subpixel, - output->make, output->model, output->transform); - } - wlr_output_schedule_done(output); + output->phys_width = info->phys_width; + output->phys_height = info->phys_height; + struct wl_resource *resource; + wl_resource_for_each( resource, &output->resources ) + { + wl_output_send_geometry( + resource, + 0, + 0, + output->phys_width, + output->phys_height, + output->subpixel, + output->make, + output->model, + output->transform ); + } + wlr_output_schedule_done( output ); - wlr_output_set_description(output, info->description); + wlr_output_set_description( output, info->description ); } -static void xdg_surface_map(struct wl_listener *listener, void *data) { - struct wlserver_xdg_surface_info* info = - wl_container_of(listener, info, map); +static void xdg_surface_map( struct wl_listener *listener, void *data ) +{ + struct wlserver_xdg_surface_info *info = + wl_container_of( listener, info, map ); - info->mapped = true; - wlserver.xdg_dirty = true; + info->mapped = true; + wlserver.xdg_dirty = true; } -static void xdg_surface_unmap(struct wl_listener *listener, void *data) { - struct wlserver_xdg_surface_info* info = - wl_container_of(listener, info, unmap); +static void xdg_surface_unmap( struct wl_listener *listener, void *data ) +{ + struct wlserver_xdg_surface_info *info = + wl_container_of( listener, info, unmap ); - info->mapped = false; - wlserver.xdg_dirty = true; + info->mapped = false; + wlserver.xdg_dirty = true; } -static void waylandy_surface_destroy(struct wl_listener *listener, void *data) { - struct wlserver_xdg_surface_info* info = - wl_container_of(listener, info, destroy); +static void waylandy_surface_destroy( struct wl_listener *listener, void *data ) +{ + struct wlserver_xdg_surface_info *info = + wl_container_of( listener, info, destroy ); - wlserver_wl_surface_info *wlserver_surface = get_wl_surface_info(info->main_surface); - if (!wlserver_surface) - { - wl_log.infof("No base surface info. (destroy)"); - return; - } + wlserver_wl_surface_info *wlserver_surface = + get_wl_surface_info( info->main_surface ); + if ( !wlserver_surface ) + { + wl_log.infof( "No base surface info. (destroy)" ); + return; + } - { - std::unique_lock lock(g_wlserver_xdg_shell_windows_lock); - std::erase_if(wlserver.xdg_wins, [=](auto win) { return win.get() == info->win; }); - } - info->main_surface = nullptr; - info->win = nullptr; - info->xdg_surface = nullptr; - info->layer_surface = nullptr; - info->mapped = false; + { + std::unique_lock lock( g_wlserver_xdg_shell_windows_lock ); + std::erase_if( + wlserver.xdg_wins, + [ = ]( auto win ) { return win.get( ) == info->win; } ); + } + info->main_surface = nullptr; + info->win = nullptr; + info->xdg_surface = nullptr; + info->layer_surface = nullptr; + info->mapped = false; - wl_list_remove(&info->map.link); - wl_list_remove(&info->unmap.link); - wl_list_remove(&info->destroy.link); + wl_list_remove( &info->map.link ); + wl_list_remove( &info->unmap.link ); + wl_list_remove( &info->destroy.link ); - wlserver_surface->xdg_surface = nullptr; + wlserver_surface->xdg_surface = nullptr; } -void xdg_toplevel_new(struct wl_listener *listener, void *data) -{ -} +void xdg_toplevel_new( struct wl_listener *listener, void *data ) {} uint32_t get_appid_from_pid( pid_t pid ); -wlserver_xdg_surface_info* waylandy_type_surface_new(struct wl_client *client, struct wlr_surface *surface) +wlserver_xdg_surface_info *waylandy_type_surface_new( + struct wl_client *client, struct wlr_surface *surface ) { - wlserver_wl_surface_info *wlserver_surface = get_wl_surface_info(surface); - if (!wlserver_surface) - { - wl_log.infof("No base surface info. (new)"); - return nullptr; - } + wlserver_wl_surface_info *wlserver_surface = get_wl_surface_info( surface ); + if ( !wlserver_surface ) + { + wl_log.infof( "No base surface info. (new)" ); + return nullptr; + } - auto window = std::make_shared(); - { - std::unique_lock lock(g_wlserver_xdg_shell_windows_lock); - wlserver.xdg_wins.emplace_back(window); - } + auto window = std::make_shared( ); + { + std::unique_lock lock( g_wlserver_xdg_shell_windows_lock ); + wlserver.xdg_wins.emplace_back( window ); + } - window->seq = ++g_lastWinSeq; - window->type = steamcompmgr_win_type_t::XDG; - if ( client ) - { - pid_t nPid = 0; - wl_client_get_credentials( client, &nPid, nullptr, nullptr ); - window->appID = get_appid_from_pid( nPid ); - } - window->_window_types.emplace(); + window->seq = ++g_lastWinSeq; + window->type = steamcompmgr_win_type_t::XDG; + if ( client ) + { + pid_t nPid = 0; + wl_client_get_credentials( client, &nPid, nullptr, nullptr ); + window->appID = get_appid_from_pid( nPid ); + } + window->_window_types.emplace( ); - static uint32_t s_window_serial = 0; - window->xdg().id = ++s_window_serial; + static uint32_t s_window_serial = 0; + window->xdg( ).id = ++s_window_serial; - wlserver_xdg_surface_info* xdg_surface_info = &window->xdg().surface; - xdg_surface_info->main_surface = surface; - xdg_surface_info->win = window.get(); + wlserver_xdg_surface_info *xdg_surface_info = &window->xdg( ).surface; + xdg_surface_info->main_surface = surface; + xdg_surface_info->win = window.get( ); - wlserver_surface->xdg_surface = xdg_surface_info; + wlserver_surface->xdg_surface = xdg_surface_info; - xdg_surface_info->map.notify = xdg_surface_map; - wl_signal_add(&surface->events.map, &xdg_surface_info->map); - xdg_surface_info->unmap.notify = xdg_surface_unmap; - wl_signal_add(&surface->events.unmap, &xdg_surface_info->unmap); + xdg_surface_info->map.notify = xdg_surface_map; + wl_signal_add( &surface->events.map, &xdg_surface_info->map ); + xdg_surface_info->unmap.notify = xdg_surface_unmap; + wl_signal_add( &surface->events.unmap, &xdg_surface_info->unmap ); - for (auto it = g_PendingCommits.begin(); it != g_PendingCommits.end();) - { - if (it->surf == surface) - { - PendingCommit_t pending = *it; + for ( auto it = g_PendingCommits.begin( ); it != g_PendingCommits.end( ); ) + { + if ( it->surf == surface ) + { + PendingCommit_t pending = *it; - wlserver_xdg_commit(pending.surf, pending.buf); + wlserver_xdg_commit( pending.surf, pending.buf ); - it = g_PendingCommits.erase(it); - } - else - { - it++; - } - } + it = g_PendingCommits.erase( it ); + } + else + { + it++; + } + } - return xdg_surface_info; + return xdg_surface_info; } -void xdg_surface_new(struct wl_listener *listener, void *data) +void xdg_surface_new( struct wl_listener *listener, void *data ) { - struct wlr_xdg_surface *xdg_surface = (struct wlr_xdg_surface *)data; + struct wlr_xdg_surface *xdg_surface = ( struct wlr_xdg_surface * )data; - wlserver_xdg_surface_info *surface_info = waylandy_type_surface_new(xdg_surface->client->client, xdg_surface->surface); - surface_info->destroy.notify = waylandy_surface_destroy; - wl_signal_add(&xdg_surface->events.destroy, &surface_info->destroy); + wlserver_xdg_surface_info *surface_info = waylandy_type_surface_new( + xdg_surface->client->client, xdg_surface->surface ); + surface_info->destroy.notify = waylandy_surface_destroy; + wl_signal_add( &xdg_surface->events.destroy, &surface_info->destroy ); - surface_info->xdg_surface = xdg_surface; + surface_info->xdg_surface = xdg_surface; } - -void layer_shell_surface_new(struct wl_listener *listener, void *data) +void layer_shell_surface_new( struct wl_listener *listener, void *data ) { - struct wlr_layer_surface_v1 *layer_surface = (struct wlr_layer_surface_v1 *)data; + struct wlr_layer_surface_v1 *layer_surface = + ( struct wlr_layer_surface_v1 * )data; - wlserver_xdg_surface_info *surface_info = waylandy_type_surface_new(nullptr, layer_surface->surface); - surface_info->destroy.notify = waylandy_surface_destroy; - wl_signal_add(&layer_surface->events.destroy, &surface_info->destroy); + wlserver_xdg_surface_info *surface_info = + waylandy_type_surface_new( nullptr, layer_surface->surface ); + surface_info->destroy.notify = waylandy_surface_destroy; + wl_signal_add( &layer_surface->events.destroy, &surface_info->destroy ); - surface_info->layer_surface = layer_surface; + surface_info->layer_surface = layer_surface; - surface_info->win->isExternalOverlay = true; + surface_info->win->isExternalOverlay = true; } #if HAVE_LIBEIS @@ -1936,323 +2286,359 @@ static gamescope::CAsyncWaiter g_LibEisWaiter( "gamescope-eis" ); static std::unique_ptr g_InputServer; #endif -bool wlserver_init( void ) { - assert( wlserver.display != nullptr ); +bool wlserver_init( void ) +{ + assert( wlserver.display != nullptr ); - wl_list_init(&pending_surfaces); + wl_list_init( &pending_surfaces ); - wlserver.wlr.multi_backend = wlr_multi_backend_create( wlserver.event_loop ); - wlr_multi_backend_add( wlserver.wlr.multi_backend, wlserver.wlr.headless_backend ); + wlserver.wlr.multi_backend = + wlr_multi_backend_create( wlserver.event_loop ); + wlr_multi_backend_add( + wlserver.wlr.multi_backend, wlserver.wlr.headless_backend ); - assert( wlserver.event_loop && wlserver.wlr.multi_backend ); + assert( wlserver.event_loop && wlserver.wlr.multi_backend ); - wl_signal_add( &wlserver.wlr.multi_backend->events.new_input, &new_input_listener ); + wl_signal_add( + &wlserver.wlr.multi_backend->events.new_input, &new_input_listener ); - if ( GetBackend()->IsSessionBased() ) - { + if ( GetBackend( )->IsSessionBased( ) ) + { #if HAVE_DRM - wlserver.wlr.libinput_backend = wlr_libinput_backend_create( wlserver.wlr.session ); - if ( wlserver.wlr.libinput_backend == NULL) - { - return false; - } - wlr_multi_backend_add( wlserver.wlr.multi_backend, wlserver.wlr.libinput_backend ); + wlserver.wlr.libinput_backend = + wlr_libinput_backend_create( wlserver.wlr.session ); + if ( wlserver.wlr.libinput_backend == NULL ) { return false; } + wlr_multi_backend_add( + wlserver.wlr.multi_backend, wlserver.wlr.libinput_backend ); #endif - } + } - // Create a stub wlr_keyboard only used to set the keymap - // We need to wait for the backend to be started before adding the device - struct wlr_keyboard *kbd = (struct wlr_keyboard *) calloc(1, sizeof(*kbd)); - wlr_keyboard_init(kbd, nullptr, "virtual"); - wlserver.wlr.virtual_keyboard_device = kbd; + // Create a stub wlr_keyboard only used to set the keymap + // We need to wait for the backend to be started before adding the device + struct wlr_keyboard *kbd = + ( struct wlr_keyboard * )calloc( 1, sizeof( *kbd ) ); + wlr_keyboard_init( kbd, nullptr, "virtual" ); + wlserver.wlr.virtual_keyboard_device = kbd; - // Create a keyboard group to keep all externally connected keyboards - // in sync (one single layout and a shared state) - struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - struct xkb_rule_names rules = { 0 }; - rules.rules = getenv("XKB_DEFAULT_RULES"); - rules.model = getenv("XKB_DEFAULT_MODEL"); - rules.layout = getenv("XKB_DEFAULT_LAYOUT"); - rules.variant = getenv("XKB_DEFAULT_VARIANT"); - rules.options = getenv("XKB_DEFAULT_OPTIONS"); - struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); - wlserver.keyboard_group = wlr_keyboard_group_create(); - struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard; - wlr_keyboard_set_repeat_info(keyboard, 25, 600); - wlr_keyboard_set_keymap(keyboard, keymap); - wlserver.keyboard_group_modifiers.notify = wlserver_handle_modifiers; - wl_signal_add(&keyboard->events.modifiers, &wlserver.keyboard_group_modifiers); - wlserver.keyboard_group_key.notify = wlserver_handle_key; - wl_signal_add(&keyboard->events.key, &wlserver.keyboard_group_key); + // Create a keyboard group to keep all externally connected keyboards + // in sync (one single layout and a shared state) + struct xkb_context *context = xkb_context_new( XKB_CONTEXT_NO_FLAGS ); + struct xkb_rule_names rules = { 0 }; + rules.rules = getenv( "XKB_DEFAULT_RULES" ); + rules.model = getenv( "XKB_DEFAULT_MODEL" ); + rules.layout = getenv( "XKB_DEFAULT_LAYOUT" ); + rules.variant = getenv( "XKB_DEFAULT_VARIANT" ); + rules.options = getenv( "XKB_DEFAULT_OPTIONS" ); + struct xkb_keymap *keymap = xkb_keymap_new_from_names( + context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS ); + wlserver.keyboard_group = wlr_keyboard_group_create( ); + struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard; + wlr_keyboard_set_repeat_info( keyboard, 25, 600 ); + wlr_keyboard_set_keymap( keyboard, keymap ); + wlserver.keyboard_group_modifiers.notify = wlserver_handle_modifiers; + wl_signal_add( + &keyboard->events.modifiers, &wlserver.keyboard_group_modifiers ); + wlserver.keyboard_group_key.notify = wlserver_handle_key; + wl_signal_add( &keyboard->events.key, &wlserver.keyboard_group_key ); - wlserver.wlr.renderer = vulkan_renderer_create(); + wlserver.wlr.renderer = vulkan_renderer_create( ); - wlr_renderer_init_wl_display(wlserver.wlr.renderer, wlserver.display); + wlr_renderer_init_wl_display( wlserver.wlr.renderer, wlserver.display ); - wlserver.wlr.compositor = wlr_compositor_create(wlserver.display, 5, wlserver.wlr.renderer); + wlserver.wlr.compositor = + wlr_compositor_create( wlserver.display, 5, wlserver.wlr.renderer ); - wl_signal_add( &wlserver.wlr.compositor->events.new_surface, &new_surface_listener ); + wl_signal_add( + &wlserver.wlr.compositor->events.new_surface, &new_surface_listener ); - create_ime_manager( &wlserver ); + create_ime_manager( &wlserver ); - create_reshade(); + create_reshade( ); - new gamescope::WaylandServer::CGamescopeActionBindingProtocol( wlserver.display ); + new gamescope::WaylandServer::CGamescopeActionBindingProtocol( + wlserver.display ); - create_gamescope_xwayland(); + create_gamescope_xwayland( ); - create_gamescope_swapchain_factory_v2(); + create_gamescope_swapchain_factory_v2( ); #if HAVE_PIPEWIRE - create_gamescope_pipewire(); + create_gamescope_pipewire( ); #endif - create_gamescope_control(); - - create_gamescope_private(); - - create_presentation_time(); - - // Have to make this old ancient thing for compat with older XWayland. - // Someday, he will be purged. - wlr_drm_create(wlserver.display, wlserver.wlr.renderer); - - if ( GetBackend()->SupportsExplicitSync() ) - { - create_explicit_sync(); - wl_log.infof( "Using explicit sync when available" ); - } - - wlserver.relative_pointer_manager = wlr_relative_pointer_manager_v1_create(wlserver.display); - if ( !wlserver.relative_pointer_manager ) - { - wl_log.errorf( "Failed to create relative pointer manager" ); - return false; - } - wlserver.constraints = wlr_pointer_constraints_v1_create(wlserver.display); - if ( !wlserver.constraints ) - { - wl_log.errorf( "Failed to create pointer constraints" ); - return false; - } - wlserver.new_pointer_constraint.notify = handle_pointer_constraint; - wl_signal_add(&wlserver.constraints->events.new_constraint, &wlserver.new_pointer_constraint); - - wlserver.xdg_shell = wlr_xdg_shell_create(wlserver.display, 3); - if (!wlserver.xdg_shell) - { - wl_log.infof("Unable to create XDG shell interface"); - return false; - } - wlserver.new_xdg_surface.notify = xdg_surface_new; - wlserver.new_xdg_toplevel.notify = xdg_toplevel_new; - wl_signal_add(&wlserver.xdg_shell->events.new_surface, &wlserver.new_xdg_surface); - wl_signal_add(&wlserver.xdg_shell->events.new_toplevel, &wlserver.new_xdg_toplevel); - - wlserver.layer_shell_v1 = wlr_layer_shell_v1_create(wlserver.display, 4); - if (!wlserver.layer_shell_v1) - { - wl_log.infof("Unable to create layer shell interface"); - return false; - } - wlserver.new_layer_shell_surface.notify = layer_shell_surface_new; - wl_signal_add(&wlserver.layer_shell_v1->events.new_surface, &wlserver.new_layer_shell_surface); - - int result = -1; - int display_slot = 0; - - while ( result != 0 && display_slot < 128 ) - { - sprintf( wlserver.wl_display_name, "gamescope-%d", display_slot ); - result = wl_display_add_socket( wlserver.display, wlserver.wl_display_name ); - display_slot++; - } - - if ( result != 0 ) - { - wl_log.errorf_errno("Unable to open wayland socket"); - wlr_backend_destroy( wlserver.wlr.multi_backend ); - return false; - } - - wlserver.wlr.seat = wlr_seat_create(wlserver.display, "seat0"); - wlr_seat_set_capabilities( wlserver.wlr.seat, WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD | WL_SEAT_CAPABILITY_TOUCH ); - - wl_log.infof("Running compositor on wayland display '%s'", wlserver.wl_display_name); - - if (!wlr_backend_start( wlserver.wlr.multi_backend )) - { - wl_log.errorf("Failed to start backend"); - wlr_backend_destroy( wlserver.wlr.multi_backend ); - wl_display_destroy(wlserver.display); - return false; - } - - wl_signal_emit( &wlserver.wlr.multi_backend->events.new_input, kbd ); + create_gamescope_control( ); + + create_gamescope_private( ); + + create_presentation_time( ); + + // Have to make this old ancient thing for compat with older XWayland. + // Someday, he will be purged. + wlr_drm_create( wlserver.display, wlserver.wlr.renderer ); + + if ( GetBackend( )->SupportsExplicitSync( ) ) + { + create_explicit_sync( ); + wl_log.infof( "Using explicit sync when available" ); + } + + wlserver.relative_pointer_manager = + wlr_relative_pointer_manager_v1_create( wlserver.display ); + if ( !wlserver.relative_pointer_manager ) + { + wl_log.errorf( "Failed to create relative pointer manager" ); + return false; + } + wlserver.constraints = + wlr_pointer_constraints_v1_create( wlserver.display ); + if ( !wlserver.constraints ) + { + wl_log.errorf( "Failed to create pointer constraints" ); + return false; + } + wlserver.new_pointer_constraint.notify = handle_pointer_constraint; + wl_signal_add( + &wlserver.constraints->events.new_constraint, + &wlserver.new_pointer_constraint ); + + wlserver.xdg_shell = wlr_xdg_shell_create( wlserver.display, 3 ); + if ( !wlserver.xdg_shell ) + { + wl_log.infof( "Unable to create XDG shell interface" ); + return false; + } + wlserver.new_xdg_surface.notify = xdg_surface_new; + wlserver.new_xdg_toplevel.notify = xdg_toplevel_new; + wl_signal_add( + &wlserver.xdg_shell->events.new_surface, &wlserver.new_xdg_surface ); + wl_signal_add( + &wlserver.xdg_shell->events.new_toplevel, &wlserver.new_xdg_toplevel ); + + wlserver.layer_shell_v1 = wlr_layer_shell_v1_create( wlserver.display, 4 ); + if ( !wlserver.layer_shell_v1 ) + { + wl_log.infof( "Unable to create layer shell interface" ); + return false; + } + wlserver.new_layer_shell_surface.notify = layer_shell_surface_new; + wl_signal_add( + &wlserver.layer_shell_v1->events.new_surface, + &wlserver.new_layer_shell_surface ); + + int result = -1; + int display_slot = 0; + + while ( result != 0 && display_slot < 128 ) + { + sprintf( wlserver.wl_display_name, "gamescope-%d", display_slot ); + result = + wl_display_add_socket( wlserver.display, wlserver.wl_display_name ); + display_slot++; + } + + if ( result != 0 ) + { + wl_log.errorf_errno( "Unable to open wayland socket" ); + wlr_backend_destroy( wlserver.wlr.multi_backend ); + return false; + } + + wlserver.wlr.seat = wlr_seat_create( wlserver.display, "seat0" ); + wlr_seat_set_capabilities( + wlserver.wlr.seat, + WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD | + WL_SEAT_CAPABILITY_TOUCH ); + + wl_log.infof( + "Running compositor on wayland display '%s'", + wlserver.wl_display_name ); + + if ( !wlr_backend_start( wlserver.wlr.multi_backend ) ) + { + wl_log.errorf( "Failed to start backend" ); + wlr_backend_destroy( wlserver.wlr.multi_backend ); + wl_display_destroy( wlserver.display ); + return false; + } + + wl_signal_emit( &wlserver.wlr.multi_backend->events.new_input, kbd ); #if HAVE_LIBEIS - { - char szEISocket[ 64 ]; - snprintf( szEISocket, sizeof( szEISocket ), "%s-ei", wlserver.wl_display_name ); - - std::unique_ptr pInputServer = std::make_unique(); - if ( pInputServer->Init( szEISocket ) ) - { - g_InputServer = std::move( pInputServer ); - - setenv( "LIBEI_SOCKET", szEISocket, 1 ); - g_LibEisWaiter.AddWaitable( g_InputServer.get() ); - wl_log.infof( "Successfully initialized libei for input emulation!" ); - } - else - { - wl_log.errorf( "Initializing libei failed, XTEST will not be available!" ); - } - } + { + char szEISocket[ 64 ]; + snprintf( + szEISocket, + sizeof( szEISocket ), + "%s-ei", + wlserver.wl_display_name ); + + std::unique_ptr pInputServer = + std::make_unique( ); + if ( pInputServer->Init( szEISocket ) ) + { + g_InputServer = std::move( pInputServer ); + + setenv( "LIBEI_SOCKET", szEISocket, 1 ); + g_LibEisWaiter.AddWaitable( g_InputServer.get( ) ); + wl_log.infof( + "Successfully initialized libei for input emulation!" ); + } + else + { + wl_log.errorf( + "Initializing libei failed, XTEST will not be available!" ); + } + } #else - wl_log.errorf( "Gamescope built without libei, XTEST will not be available!" ); + wl_log.errorf( + "Gamescope built without libei, XTEST will not be available!" ); #endif - for (int i = 0; i < g_nXWaylandCount; i++) - { - auto server = std::make_unique(wlserver.display, i); - wlserver.wlr.xwayland_servers.emplace_back(std::move(server)); - } + for ( int i = 0; i < g_nXWaylandCount; i++ ) + { + auto server = std::make_unique( + wlserver.display, i ); + wlserver.wlr.xwayland_servers.emplace_back( std::move( server ) ); + } - for (size_t i = 0; i < wlserver.wlr.xwayland_servers.size(); i++) - { - while (!wlserver.wlr.xwayland_servers[i]->is_xwayland_ready()) { - wl_display_flush_clients(wlserver.display); - if (wl_event_loop_dispatch(wlserver.event_loop, -1) < 0) { - wl_log.errorf("wl_event_loop_dispatch failed\n"); - return false; - } - } - } + for ( size_t i = 0; i < wlserver.wlr.xwayland_servers.size( ); i++ ) + { + while ( !wlserver.wlr.xwayland_servers[ i ]->is_xwayland_ready( ) ) + { + wl_display_flush_clients( wlserver.display ); + if ( wl_event_loop_dispatch( wlserver.event_loop, -1 ) < 0 ) + { + wl_log.errorf( "wl_event_loop_dispatch failed\n" ); + return false; + } + } + } - return true; + return true; } pthread_mutex_t waylock = PTHREAD_MUTEX_INITIALIZER; -bool wlserver_is_lock_held(void) +bool wlserver_is_lock_held( void ) { - int err = pthread_mutex_trylock(&waylock); - if (err == 0) - { - pthread_mutex_unlock(&waylock); - return false; - } - return true; + int err = pthread_mutex_trylock( &waylock ); + if ( err == 0 ) + { + pthread_mutex_unlock( &waylock ); + return false; + } + return true; } -void wlserver_lock(void) -{ - pthread_mutex_lock(&waylock); -} +void wlserver_lock( void ) { pthread_mutex_lock( &waylock ); } -void wlserver_unlock(bool flush) +void wlserver_unlock( bool flush ) { - if (flush) - wl_display_flush_clients(wlserver.display); - pthread_mutex_unlock(&waylock); + if ( flush ) wl_display_flush_clients( wlserver.display ); + pthread_mutex_unlock( &waylock ); } extern std::mutex g_SteamCompMgrXWaylandServerMutex; -static int g_wlserverNudgePipe[2] = {-1, -1}; +static int g_wlserverNudgePipe[ 2 ] = { -1, -1 }; -void wlserver_run(void) +void wlserver_run( void ) { - pthread_setname_np( pthread_self(), "gamescope-wl" ); + pthread_setname_np( pthread_self( ), "gamescope-wl" ); - if ( pipe2( g_wlserverNudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) - { - wl_log.errorf_errno( "wlserver: pipe2 failed" ); - exit( 1 ); - } + if ( pipe2( g_wlserverNudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) + { + wl_log.errorf_errno( "wlserver: pipe2 failed" ); + exit( 1 ); + } - struct pollfd pollfds[2] = { + struct pollfd pollfds[ 2 ] = { { - .fd = wl_event_loop_get_fd( wlserver.event_loop ), + .fd = wl_event_loop_get_fd( wlserver.event_loop ), .events = POLLIN, - }, + }, { - .fd = g_wlserverNudgePipe[ 0 ], - .events = POLLIN, + .fd = g_wlserverNudgePipe[ 0 ], + .events = POLLIN, }, }; - wlserver.bWaylandServerRunning = true; - wlserver.bWaylandServerRunning.notify_all(); - - while ( !g_bShutdownWLServer ) - { - int ret = poll( pollfds, 2, -1 ); - - if ( ret < 0 ) { - if ( errno == EINTR || errno == EAGAIN ) - continue; - wl_log.errorf_errno( "poll failed" ); - break; - } - - if ( pollfds[ 0 ].revents & (POLLHUP | POLLERR) ) { - wl_log.errorf( "socket %s", ( pollfds[ 0 ].revents & POLLERR ) ? "error" : "closed" ); - break; - } - - if ( pollfds[ 0 ].revents & POLLIN ) { - // We have wayland stuff to do, do it while locked - wlserver_lock(); - - wl_display_flush_clients(wlserver.display); - int ret = wl_event_loop_dispatch(wlserver.event_loop, 0); - if (ret < 0) { - wlserver_unlock(); - break; - } - - wlserver_unlock(); - } - } - - wlserver.bWaylandServerRunning = false; - wlserver.bWaylandServerRunning.notify_all(); - - { - std::unique_lock lock3(wlserver.xdg_commit_lock); - wlserver.xdg_commit_queue.clear(); - } - - { - std::unique_lock lock2(g_wlserver_xdg_shell_windows_lock); - wlserver.xdg_wins.clear(); - } + wlserver.bWaylandServerRunning = true; + wlserver.bWaylandServerRunning.notify_all( ); + + while ( !g_bShutdownWLServer ) + { + int ret = poll( pollfds, 2, -1 ); + + if ( ret < 0 ) + { + if ( errno == EINTR || errno == EAGAIN ) continue; + wl_log.errorf_errno( "poll failed" ); + break; + } + + if ( pollfds[ 0 ].revents & ( POLLHUP | POLLERR ) ) + { + wl_log.errorf( + "socket %s", + ( pollfds[ 0 ].revents & POLLERR ) ? "error" : "closed" ); + break; + } + + if ( pollfds[ 0 ].revents & POLLIN ) + { + // We have wayland stuff to do, do it while locked + wlserver_lock( ); + + wl_display_flush_clients( wlserver.display ); + int ret = wl_event_loop_dispatch( wlserver.event_loop, 0 ); + if ( ret < 0 ) + { + wlserver_unlock( ); + break; + } + + wlserver_unlock( ); + } + } + + wlserver.bWaylandServerRunning = false; + wlserver.bWaylandServerRunning.notify_all( ); + + { + std::unique_lock lock3( wlserver.xdg_commit_lock ); + wlserver.xdg_commit_queue.clear( ); + } + + { + std::unique_lock lock2( g_wlserver_xdg_shell_windows_lock ); + wlserver.xdg_wins.clear( ); + } #if HAVE_LIBEIS - g_InputServer = nullptr; + g_InputServer = nullptr; #endif - // Released when steamcompmgr closes. - std::unique_lock xwayland_server_guard(g_SteamCompMgrXWaylandServerMutex); - // We need to shutdown Xwayland before disconnecting all clients, otherwise - // wlroots will restart it automatically. - wlserver_lock(); - wlserver.wlr.xwayland_servers.clear(); - wl_display_destroy_clients(wlserver.display); - wl_display_destroy(wlserver.display); + // Released when steamcompmgr closes. + std::unique_lock xwayland_server_guard( + g_SteamCompMgrXWaylandServerMutex ); + // We need to shutdown Xwayland before disconnecting all clients, otherwise + // wlroots will restart it automatically. + wlserver_lock( ); + wlserver.wlr.xwayland_servers.clear( ); + wl_display_destroy_clients( wlserver.display ); + wl_display_destroy( wlserver.display ); wlserver.display = NULL; - wlserver_unlock(false); + wlserver_unlock( false ); } -void wlserver_shutdown() +void wlserver_shutdown( ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - g_bShutdownWLServer = true; + g_bShutdownWLServer = true; - if (wlserver.display) + if ( wlserver.display ) { if ( write( g_wlserverNudgePipe[ 1 ], "\n", 1 ) < 0 ) wl_log.errorf_errno( "wlserver_force_shutdown: write failed" ); @@ -2261,926 +2647,1037 @@ void wlserver_shutdown() void wlserver_keyboardfocus( struct wlr_surface *surface, bool bConstrain ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - if (wlserver.kb_focus_surface != surface) { - auto old_wl_surf = get_wl_surface_info( wlserver.kb_focus_surface ); - if (old_wl_surf && old_wl_surf->xdg_surface && old_wl_surf->xdg_surface->xdg_surface && old_wl_surf->xdg_surface->xdg_surface->toplevel) - wlr_xdg_toplevel_set_activated(old_wl_surf->xdg_surface->xdg_surface->toplevel, false); - - auto new_wl_surf = get_wl_surface_info( surface ); - if (new_wl_surf && new_wl_surf->xdg_surface && new_wl_surf->xdg_surface->xdg_surface && new_wl_surf->xdg_surface->xdg_surface->toplevel) - wlr_xdg_toplevel_set_activated(new_wl_surf->xdg_surface->xdg_surface->toplevel, true); - } - - assert( wlserver.wlr.virtual_keyboard_device != nullptr ); - wlr_seat_set_keyboard( wlserver.wlr.seat, wlserver.wlr.virtual_keyboard_device ); - - struct wlr_keyboard *keyboard = wlr_seat_get_keyboard( wlserver.wlr.seat ); - if ( keyboard == nullptr ) - wlr_seat_keyboard_notify_enter( wlserver.wlr.seat, surface, nullptr, 0, nullptr); - else - wlr_seat_keyboard_notify_enter( wlserver.wlr.seat, surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); - - wlserver.kb_focus_surface = surface; + if ( wlserver.kb_focus_surface != surface ) + { + auto old_wl_surf = get_wl_surface_info( wlserver.kb_focus_surface ); + if ( old_wl_surf && old_wl_surf->xdg_surface && + old_wl_surf->xdg_surface->xdg_surface && + old_wl_surf->xdg_surface->xdg_surface->toplevel ) + wlr_xdg_toplevel_set_activated( + old_wl_surf->xdg_surface->xdg_surface->toplevel, false ); + + auto new_wl_surf = get_wl_surface_info( surface ); + if ( new_wl_surf && new_wl_surf->xdg_surface && + new_wl_surf->xdg_surface->xdg_surface && + new_wl_surf->xdg_surface->xdg_surface->toplevel ) + wlr_xdg_toplevel_set_activated( + new_wl_surf->xdg_surface->xdg_surface->toplevel, true ); + } - if ( bConstrain ) - { - struct wlr_pointer_constraint_v1 *constraint = wlr_pointer_constraints_v1_constraint_for_surface( wlserver.constraints, surface, wlserver.wlr.seat ); - wlserver_constrain_cursor( constraint ); - } + assert( wlserver.wlr.virtual_keyboard_device != nullptr ); + wlr_seat_set_keyboard( + wlserver.wlr.seat, wlserver.wlr.virtual_keyboard_device ); + + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard( wlserver.wlr.seat ); + if ( keyboard == nullptr ) + wlr_seat_keyboard_notify_enter( + wlserver.wlr.seat, surface, nullptr, 0, nullptr ); + else + wlr_seat_keyboard_notify_enter( + wlserver.wlr.seat, + surface, + keyboard->keycodes, + keyboard->num_keycodes, + &keyboard->modifiers ); + + wlserver.kb_focus_surface = surface; + + if ( bConstrain ) + { + struct wlr_pointer_constraint_v1 *constraint = + wlr_pointer_constraints_v1_constraint_for_surface( + wlserver.constraints, surface, wlserver.wlr.seat ); + wlserver_constrain_cursor( constraint ); + } } -bool wlserver_process_hotkeys( wlr_keyboard *keyboard, uint32_t key, bool press ) +bool wlserver_process_hotkeys( + wlr_keyboard *keyboard, uint32_t key, bool press ) { - xkb_keycode_t keycode = key + 8; - xkb_keysym_t keysym = xkb_state_key_get_one_sym( keyboard->xkb_state, keycode ); + xkb_keycode_t keycode = key + 8; + xkb_keysym_t keysym = + xkb_state_key_get_one_sym( keyboard->xkb_state, keycode ); - keysym = NormalizeKeysymForHotkey( keysym ); + keysym = NormalizeKeysymForHotkey( keysym ); - static std::unordered_set s_setPressedKeySyms; - if ( press ) - { - s_setPressedKeySyms.emplace( keysym ); - } - else - { - s_setPressedKeySyms.erase( keysym ); - } + static std::unordered_set s_setPressedKeySyms; + if ( press ) { s_setPressedKeySyms.emplace( keysym ); } + else + { + s_setPressedKeySyms.erase( keysym ); + } - if ( log_binding.Enabled( LOG_DEBUG ) ) - { - std::string sPressedKeySymsDebugName = ComputeDebugName( s_setPressedKeySyms ); - log_binding.debugf( "Looking for: [%s].", sPressedKeySymsDebugName.c_str() ); - } + if ( log_binding.Enabled( LOG_DEBUG ) ) + { + std::string sPressedKeySymsDebugName = + ComputeDebugName( s_setPressedKeySyms ); + log_binding.debugf( + "Looking for: [%s].", sPressedKeySymsDebugName.c_str( ) ); + } - { - using namespace gamescope::WaylandServer; + { + using namespace gamescope::WaylandServer; - std::span ppBindings = CGamescopeActionBinding::GetBindings(); + std::span ppBindings = + CGamescopeActionBinding::GetBindings( ); - for ( CGamescopeActionBinding *pBinding : ppBindings ) - { - if ( !pBinding->IsArmed() ) - continue; + for ( CGamescopeActionBinding *pBinding : ppBindings ) + { + if ( !pBinding->IsArmed( ) ) continue; - std::span pKeybinds = pBinding->GetKeyboardTriggers(); - for ( const Keybind_t &keybind : pKeybinds ) - { - if ( !pBinding->IsArmed() ) - break; + std::span pKeybinds = pBinding->GetKeyboardTriggers( ); + for ( const Keybind_t &keybind : pKeybinds ) + { + if ( !pBinding->IsArmed( ) ) break; - if ( s_setPressedKeySyms != keybind.setKeySyms ) - continue; + if ( s_setPressedKeySyms != keybind.setKeySyms ) continue; - if ( pBinding->Execute() ) - return true; - } - } - } + if ( pBinding->Execute( ) ) return true; + } + } + } - return false; + return false; } void wlserver_key( uint32_t key, bool press, uint32_t time ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - wlr_keyboard *keyboard = wlserver.wlr.virtual_keyboard_device; + wlr_keyboard *keyboard = wlserver.wlr.virtual_keyboard_device; - if ( !wlserver_process_hotkeys( keyboard, key, press ) ) - { - assert( keyboard != nullptr ); - wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard ); - wlr_seat_keyboard_notify_key( wlserver.wlr.seat, time, key, press ); - } + if ( !wlserver_process_hotkeys( keyboard, key, press ) ) + { + assert( keyboard != nullptr ); + wlr_seat_set_keyboard( wlserver.wlr.seat, keyboard ); + wlr_seat_keyboard_notify_key( wlserver.wlr.seat, time, key, press ); + } - bump_input_counter(); + bump_input_counter( ); } -struct wlr_surface *wlserver_surface_to_main_surface( struct wlr_surface *pSurface ) +struct wlr_surface * +wlserver_surface_to_main_surface( struct wlr_surface *pSurface ) { - if ( !pSurface ) - return nullptr; + if ( !pSurface ) return nullptr; - wlserver_wl_surface_info *pInfo = get_wl_surface_info( pSurface ); - if ( !pInfo ) - return nullptr; + wlserver_wl_surface_info *pInfo = get_wl_surface_info( pSurface ); + if ( !pInfo ) return nullptr; - if ( pInfo->x11_surface ) - { - wlr_surface *pMain = pInfo->x11_surface->main_surface.load(); - if ( pMain ) - pSurface = pMain; - } + if ( pInfo->x11_surface ) + { + wlr_surface *pMain = pInfo->x11_surface->main_surface.load( ); + if ( pMain ) pSurface = pMain; + } - return pSurface; + return pSurface; } -struct wlr_surface *wlserver_surface_to_override_surface( struct wlr_surface *pSurface ) +struct wlr_surface * +wlserver_surface_to_override_surface( struct wlr_surface *pSurface ) { - if ( !pSurface ) - return nullptr; + if ( !pSurface ) return nullptr; - wlserver_wl_surface_info *pInfo = get_wl_surface_info( pSurface ); - if ( !pInfo ) - return nullptr; + wlserver_wl_surface_info *pInfo = get_wl_surface_info( pSurface ); + if ( !pInfo ) return nullptr; - if ( pInfo->x11_surface ) - { - wlr_surface *pOverride = pInfo->x11_surface->override_surface.load(); - if ( pOverride ) - pSurface = pOverride; - } + if ( pInfo->x11_surface ) + { + wlr_surface *pOverride = pInfo->x11_surface->override_surface.load( ); + if ( pOverride ) pSurface = pOverride; + } - return pSurface; + return pSurface; } std::pair wlserver_get_surface_extent( struct wlr_surface *pSurface ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - pSurface = wlserver_surface_to_override_surface( pSurface ); + pSurface = wlserver_surface_to_override_surface( pSurface ); - if ( !pSurface ) - return std::make_pair( g_nNestedWidth, g_nNestedHeight ); + if ( !pSurface ) return std::make_pair( g_nNestedWidth, g_nNestedHeight ); - return std::make_pair( pSurface->current.width, pSurface->current.height ); + return std::make_pair( pSurface->current.width, pSurface->current.height ); } -bool ShouldDrawCursor(); -void wlserver_oncursorevent() +bool ShouldDrawCursor( ); +void wlserver_oncursorevent( ) { - // Don't repaint if we would use a nested cursor. - if ( !ShouldDrawCursor() ) - return; + // Don't repaint if we would use a nested cursor. + if ( !ShouldDrawCursor( ) ) return; - if ( !wlserver.bCursorHidden && wlserver.bCursorHasImage ) - { - hasRepaint = true; - } + if ( !wlserver.bCursorHidden && wlserver.bCursorHasImage ) + { + hasRepaint = true; + } } -static std::pair wlserver_get_cursor_bounds() +static std::pair wlserver_get_cursor_bounds( ) { - auto [nWidth, nHeight] = wlserver_get_surface_extent( wlserver.mouse_focus_surface ); - for ( auto iter : wlserver.current_dropdown_surfaces ) - { - auto [nDropdownX, nDropdownY] = iter.second; - auto [nDropdownWidth, nDropdownHeight] = wlserver_get_surface_extent( iter.first ); + auto [ nWidth, nHeight ] = + wlserver_get_surface_extent( wlserver.mouse_focus_surface ); + for ( auto iter : wlserver.current_dropdown_surfaces ) + { + auto [ nDropdownX, nDropdownY ] = iter.second; + auto [ nDropdownWidth, nDropdownHeight ] = + wlserver_get_surface_extent( iter.first ); - nWidth = std::max( nWidth, nDropdownX + nDropdownWidth ); - nHeight = std::max( nHeight, nDropdownY + nDropdownHeight ); - } + nWidth = std::max( nWidth, nDropdownX + nDropdownWidth ); + nHeight = std::max( nHeight, nDropdownY + nDropdownHeight ); + } - return std::make_pair( nWidth, nHeight ); + return std::make_pair( nWidth, nHeight ); } -static void wlserver_clampcursor() +static void wlserver_clampcursor( ) { - auto [nWidth, nHeight] = wlserver_get_cursor_bounds(); - wlserver.mouse_surface_cursorx = std::clamp( wlserver.mouse_surface_cursorx, 0.0, double( std::max( nWidth - 1, 0 ) ) ); - wlserver.mouse_surface_cursory = std::clamp( wlserver.mouse_surface_cursory, 0.0, double( std::max( nHeight - 1, 0 ) ) ); + auto [ nWidth, nHeight ] = wlserver_get_cursor_bounds( ); + wlserver.mouse_surface_cursorx = std::clamp( + wlserver.mouse_surface_cursorx, + 0.0, + double( std::max( nWidth - 1, 0 ) ) ); + wlserver.mouse_surface_cursory = std::clamp( + wlserver.mouse_surface_cursory, + 0.0, + double( std::max( nHeight - 1, 0 ) ) ); } -void wlserver_mousefocus( struct wlr_surface *wlrsurface, int x /* = 0 */, int y /* = 0 */ ) +void wlserver_mousefocus( + struct wlr_surface *wlrsurface, int x /* = 0 */, int y /* = 0 */ ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - if ( wlserver.mouse_focus_surface == wlrsurface ) - { - wlserver_clampcursor(); - } - else - { - wlserver.mouse_focus_surface = wlrsurface; + if ( wlserver.mouse_focus_surface == wlrsurface ) + { + wlserver_clampcursor( ); + } + else + { + wlserver.mouse_focus_surface = wlrsurface; - auto [nWidth, nHeight] = wlserver_get_surface_extent( wlrsurface ); + auto [ nWidth, nHeight ] = wlserver_get_surface_extent( wlrsurface ); - if ( x < nWidth && y < nHeight ) - { - wlserver.mouse_surface_cursorx = x; - wlserver.mouse_surface_cursory = y; - } - else - { - wlserver.mouse_surface_cursorx = nWidth / 2.0; - wlserver.mouse_surface_cursory = nHeight / 2.0; - } - } + if ( x < nWidth && y < nHeight ) + { + wlserver.mouse_surface_cursorx = x; + wlserver.mouse_surface_cursory = y; + } + else + { + wlserver.mouse_surface_cursorx = nWidth / 2.0; + wlserver.mouse_surface_cursory = nHeight / 2.0; + } + } - wlserver_oncursorevent(); - wlr_seat_pointer_warp( wlserver.wlr.seat, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); - wlr_seat_pointer_notify_enter( wlserver.wlr.seat, wlrsurface, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); + wlserver_oncursorevent( ); + wlr_seat_pointer_warp( + wlserver.wlr.seat, + wlserver.mouse_surface_cursorx, + wlserver.mouse_surface_cursory ); + wlr_seat_pointer_notify_enter( + wlserver.wlr.seat, + wlrsurface, + wlserver.mouse_surface_cursorx, + wlserver.mouse_surface_cursory ); } -void wlserver_clear_dropdowns() -{ - wlserver.current_dropdown_surfaces.clear(); -} +void wlserver_clear_dropdowns( ) +{ wlserver.current_dropdown_surfaces.clear( ); } void wlserver_notify_dropdown( struct wlr_surface *wlrsurface, int nX, int nY ) -{ - wlserver.current_dropdown_surfaces[wlrsurface] = std::make_pair( nX, nY ); -} +{ wlserver.current_dropdown_surfaces[ wlrsurface ] = std::make_pair( nX, nY ); } -void wlserver_mousehide() +void wlserver_mousehide( ) { - wlserver.ulLastMovedCursorTime = 0; - if ( wlserver.bCursorHidden != true ) - { - wlserver.bCursorHidden = true; - hasRepaint = true; - } + wlserver.ulLastMovedCursorTime = 0; + if ( wlserver.bCursorHidden != true ) + { + wlserver.bCursorHidden = true; + hasRepaint = true; + } } struct GamescopePointerConstraint { - struct wlr_pointer_constraint_v1 *pConstraint = nullptr; + struct wlr_pointer_constraint_v1 *pConstraint = nullptr; - struct wl_listener set_region{}; - struct wl_listener destroy{}; + struct wl_listener set_region{}; + struct wl_listener destroy{}; }; -static void wlserver_warp_to_constraint_hint() +static void wlserver_warp_to_constraint_hint( ) { - struct wlr_pointer_constraint_v1 *pConstraint = wlserver.GetCursorConstraint(); - - if (pConstraint->current.cursor_hint.enabled) - { - double sx = pConstraint->current.cursor_hint.x; - double sy = pConstraint->current.cursor_hint.y; + struct wlr_pointer_constraint_v1 *pConstraint = + wlserver.GetCursorConstraint( ); - if ( wlserver.mouse_surface_cursorx == sx && wlserver.mouse_surface_cursory == sy ) - return; + if ( pConstraint->current.cursor_hint.enabled ) + { + double sx = pConstraint->current.cursor_hint.x; + double sy = pConstraint->current.cursor_hint.y; + + if ( wlserver.mouse_surface_cursorx == sx && + wlserver.mouse_surface_cursory == sy ) + return; - wlserver.mouse_surface_cursorx = sx; - wlserver.mouse_surface_cursory = sy; - wlr_seat_pointer_warp( wlserver.wlr.seat, sx, sy ); + wlserver.mouse_surface_cursorx = sx; + wlserver.mouse_surface_cursory = sy; + wlr_seat_pointer_warp( wlserver.wlr.seat, sx, sy ); - uint64_t ulNow = get_time_in_nanos(); - static uint32_t s_unSyntheticMoveCount = 0; + uint64_t ulNow = get_time_in_nanos( ); + static uint32_t s_unSyntheticMoveCount = 0; - // Add a heuristic for whether the cursor is continually moving, or if - // this is just a simple warp to the saame place. - bool bSynthetic = true; - if ( wlserver.ulLastMovedCursorTime + 200'000'000 >= ulNow ) - { - if ( s_unSyntheticMoveCount++ >= 3 ) - { - bSynthetic = false; - } - } - else - { - s_unSyntheticMoveCount = 0; - } + // Add a heuristic for whether the cursor is continually moving, or if + // this is just a simple warp to the saame place. + bool bSynthetic = true; + if ( wlserver.ulLastMovedCursorTime + 200'000'000 >= ulNow ) + { + if ( s_unSyntheticMoveCount++ >= 3 ) { bSynthetic = false; } + } + else + { + s_unSyntheticMoveCount = 0; + } - wlserver.ulLastMovedCursorTime = ulNow; + wlserver.ulLastMovedCursorTime = ulNow; - if ( !bSynthetic ) - wlserver.bCursorHidden = !wlserver.bCursorHasImage; - } + if ( !bSynthetic ) wlserver.bCursorHidden = !wlserver.bCursorHasImage; + } } -static void wlserver_update_cursor_constraint() +static void wlserver_update_cursor_constraint( ) { - struct wlr_pointer_constraint_v1 *pConstraint = wlserver.GetCursorConstraint(); - pixman_region32_t *pRegion = &pConstraint->region; - - if ( wlserver.mouse_constraint_requires_warp && pConstraint->surface ) - { - wlserver.mouse_constraint_requires_warp = false; + struct wlr_pointer_constraint_v1 *pConstraint = + wlserver.GetCursorConstraint( ); + pixman_region32_t *pRegion = &pConstraint->region; - wlserver_warp_to_constraint_hint(); + if ( wlserver.mouse_constraint_requires_warp && pConstraint->surface ) + { + wlserver.mouse_constraint_requires_warp = false; - if (!pixman_region32_contains_point(pRegion, floor(wlserver.mouse_surface_cursorx), floor(wlserver.mouse_surface_cursory), NULL)) - { - int nboxes; - pixman_box32_t *boxes = pixman_region32_rectangles(pRegion, &nboxes); - if ( nboxes ) - { - wlserver.mouse_surface_cursorx = std::clamp( wlserver.mouse_surface_cursorx, boxes[0].x1, boxes[0].x2); - wlserver.mouse_surface_cursory = std::clamp( wlserver.mouse_surface_cursory, boxes[0].y1, boxes[0].y2); + wlserver_warp_to_constraint_hint( ); - wlr_seat_pointer_warp( wlserver.wlr.seat, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); - } - } - } + if ( !pixman_region32_contains_point( + pRegion, + floor( wlserver.mouse_surface_cursorx ), + floor( wlserver.mouse_surface_cursory ), + NULL ) ) + { + int nboxes; + pixman_box32_t *boxes = + pixman_region32_rectangles( pRegion, &nboxes ); + if ( nboxes ) + { + wlserver.mouse_surface_cursorx = std::clamp( + wlserver.mouse_surface_cursorx, + boxes[ 0 ].x1, + boxes[ 0 ].x2 ); + wlserver.mouse_surface_cursory = std::clamp( + wlserver.mouse_surface_cursory, + boxes[ 0 ].y1, + boxes[ 0 ].y2 ); + + wlr_seat_pointer_warp( + wlserver.wlr.seat, + wlserver.mouse_surface_cursorx, + wlserver.mouse_surface_cursory ); + } + } + } - if (pConstraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) - pixman_region32_copy(&wlserver.confine, pRegion); - else - pixman_region32_clear(&wlserver.confine); + if ( pConstraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED ) + pixman_region32_copy( &wlserver.confine, pRegion ); + else + pixman_region32_clear( &wlserver.confine ); } -static void wlserver_constrain_cursor( struct wlr_pointer_constraint_v1 *pNewConstraint ) +static void +wlserver_constrain_cursor( struct wlr_pointer_constraint_v1 *pNewConstraint ) { - struct wlr_pointer_constraint_v1 *pOldConstraint = wlserver.GetCursorConstraint(); + struct wlr_pointer_constraint_v1 *pOldConstraint = + wlserver.GetCursorConstraint( ); - if ( pOldConstraint == pNewConstraint ) - return; + if ( pOldConstraint == pNewConstraint ) return; - if ( pOldConstraint ) - { - if ( !pNewConstraint ) - wlserver_warp_to_constraint_hint(); + if ( pOldConstraint ) + { + if ( !pNewConstraint ) wlserver_warp_to_constraint_hint( ); - wlr_pointer_constraint_v1_send_deactivated( pOldConstraint ); - } + wlr_pointer_constraint_v1_send_deactivated( pOldConstraint ); + } - wlserver.SetMouseConstraint( pNewConstraint ); + wlserver.SetMouseConstraint( pNewConstraint ); - if ( !pNewConstraint ) - return; + if ( !pNewConstraint ) return; - wlserver.mouse_constraint_requires_warp = true; + wlserver.mouse_constraint_requires_warp = true; - wlserver_update_cursor_constraint(); + wlserver_update_cursor_constraint( ); - wlr_pointer_constraint_v1_send_activated( pNewConstraint ); + wlr_pointer_constraint_v1_send_activated( pNewConstraint ); } -static void handle_pointer_constraint_set_region(struct wl_listener *listener, void *data) +static void +handle_pointer_constraint_set_region( struct wl_listener *listener, void *data ) { - GamescopePointerConstraint *pGamescopeConstraint = wl_container_of(listener, pGamescopeConstraint, set_region); + GamescopePointerConstraint *pGamescopeConstraint = + wl_container_of( listener, pGamescopeConstraint, set_region ); - // If the region has been updated, we might need to warp again next commit. - wlserver.mouse_constraint_requires_warp = true; + // If the region has been updated, we might need to warp again next commit. + wlserver.mouse_constraint_requires_warp = true; } -void handle_constraint_destroy(struct wl_listener *listener, void *data) +void handle_constraint_destroy( struct wl_listener *listener, void *data ) { - GamescopePointerConstraint *pGamescopeConstraint = wl_container_of(listener, pGamescopeConstraint, destroy); + GamescopePointerConstraint *pGamescopeConstraint = + wl_container_of( listener, pGamescopeConstraint, destroy ); - wl_list_remove(&pGamescopeConstraint->set_region.link); - wl_list_remove(&pGamescopeConstraint->destroy.link); + wl_list_remove( &pGamescopeConstraint->set_region.link ); + wl_list_remove( &pGamescopeConstraint->destroy.link ); - struct wlr_pointer_constraint_v1 *pCurrentConstraint = wlserver.GetCursorConstraint(); - if ( pCurrentConstraint == pGamescopeConstraint->pConstraint ) - { - wlserver_warp_to_constraint_hint(); + struct wlr_pointer_constraint_v1 *pCurrentConstraint = + wlserver.GetCursorConstraint( ); + if ( pCurrentConstraint == pGamescopeConstraint->pConstraint ) + { + wlserver_warp_to_constraint_hint( ); - wlserver.SetMouseConstraint( nullptr ); - } + wlserver.SetMouseConstraint( nullptr ); + } - delete pGamescopeConstraint; + delete pGamescopeConstraint; } -static void handle_pointer_constraint(struct wl_listener *listener, void *data) +static void +handle_pointer_constraint( struct wl_listener *listener, void *data ) { - struct wlr_pointer_constraint_v1 *pConstraint = (struct wlr_pointer_constraint_v1 *) data; + struct wlr_pointer_constraint_v1 *pConstraint = + ( struct wlr_pointer_constraint_v1 * )data; - GamescopePointerConstraint *pGamescopeConstraint = new GamescopePointerConstraint; - pGamescopeConstraint->pConstraint = pConstraint; + GamescopePointerConstraint *pGamescopeConstraint = + new GamescopePointerConstraint; + pGamescopeConstraint->pConstraint = pConstraint; - pGamescopeConstraint->set_region.notify = handle_pointer_constraint_set_region; - wl_signal_add(&pConstraint->events.set_region, &pGamescopeConstraint->set_region); + pGamescopeConstraint->set_region.notify = + handle_pointer_constraint_set_region; + wl_signal_add( + &pConstraint->events.set_region, &pGamescopeConstraint->set_region ); - pGamescopeConstraint->destroy.notify = handle_constraint_destroy; - wl_signal_add(&pConstraint->events.destroy, &pGamescopeConstraint->destroy); + pGamescopeConstraint->destroy.notify = handle_constraint_destroy; + wl_signal_add( + &pConstraint->events.destroy, &pGamescopeConstraint->destroy ); - if ( wlserver.kb_focus_surface && wlserver.kb_focus_surface == pConstraint->surface ) - wlserver_constrain_cursor(pConstraint); + if ( wlserver.kb_focus_surface && + wlserver.kb_focus_surface == pConstraint->surface ) + wlserver_constrain_cursor( pConstraint ); } static bool wlserver_apply_constraint( double *dx, double *dy ) { - struct wlr_pointer_constraint_v1 *pConstraint = wlserver.GetCursorConstraint(); + struct wlr_pointer_constraint_v1 *pConstraint = + wlserver.GetCursorConstraint( ); - if ( pConstraint ) - { - if ( pConstraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED ) - return false; - - double sx = wlserver.mouse_surface_cursorx; - double sy = wlserver.mouse_surface_cursory; - - double sx_confined, sy_confined; - if ( !wlr_region_confine( &wlserver.confine, sx, sy, sx + *dx, sy + *dy, &sx_confined, &sy_confined ) ) - return false; - - *dx = sx_confined - sx; - *dy = sy_confined - sy; - - if ( *dx == 0.0 && *dy == 0.0 ) - return false; - } + if ( pConstraint ) + { + if ( pConstraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED ) + return false; + + double sx = wlserver.mouse_surface_cursorx; + double sy = wlserver.mouse_surface_cursory; + + double sx_confined, sy_confined; + if ( !wlr_region_confine( + &wlserver.confine, + sx, + sy, + sx + *dx, + sy + *dy, + &sx_confined, + &sy_confined ) ) + return false; + + *dx = sx_confined - sx; + *dy = sy_confined - sy; + + if ( *dx == 0.0 && *dy == 0.0 ) return false; + } - return true; + return true; } void wlserver_mousemotion( double dx, double dy, uint32_t time ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - dx *= g_mouseSensitivity; - dy *= g_mouseSensitivity; + dx *= g_mouseSensitivity; + dy *= g_mouseSensitivity; - wlserver_perform_rel_pointer_motion( dx, dy ); + wlserver_perform_rel_pointer_motion( dx, dy ); - if ( !wlserver_apply_constraint( &dx, &dy ) ) - { - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); - return; - } + if ( !wlserver_apply_constraint( &dx, &dy ) ) + { + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + return; + } - wlserver.ulLastMovedCursorTime = get_time_in_nanos(); - wlserver.bCursorHidden = !wlserver.bCursorHasImage; + wlserver.ulLastMovedCursorTime = get_time_in_nanos( ); + wlserver.bCursorHidden = !wlserver.bCursorHasImage; - wlserver.mouse_surface_cursorx += dx; - wlserver.mouse_surface_cursory += dy; + wlserver.mouse_surface_cursorx += dx; + wlserver.mouse_surface_cursory += dy; - wlserver_clampcursor(); + wlserver_clampcursor( ); - wlserver_oncursorevent(); + wlserver_oncursorevent( ); - wlr_seat_pointer_notify_motion( wlserver.wlr.seat, time, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + wlr_seat_pointer_notify_motion( + wlserver.wlr.seat, + time, + wlserver.mouse_surface_cursorx, + wlserver.mouse_surface_cursory ); + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); } void wlserver_mousewarp( double x, double y, uint32_t time, bool bSynthetic ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - wlserver.mouse_surface_cursorx = x; - wlserver.mouse_surface_cursory = y; + wlserver.mouse_surface_cursorx = x; + wlserver.mouse_surface_cursory = y; - wlserver_clampcursor(); + wlserver_clampcursor( ); - wlserver.ulLastMovedCursorTime = get_time_in_nanos(); - if ( !bSynthetic ) - wlserver.bCursorHidden = !wlserver.bCursorHasImage; + wlserver.ulLastMovedCursorTime = get_time_in_nanos( ); + if ( !bSynthetic ) wlserver.bCursorHidden = !wlserver.bCursorHasImage; - wlserver_oncursorevent(); + wlserver_oncursorevent( ); - wlr_seat_pointer_notify_motion( wlserver.wlr.seat, time, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + wlr_seat_pointer_notify_motion( + wlserver.wlr.seat, + time, + wlserver.mouse_surface_cursorx, + wlserver.mouse_surface_cursory ); + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); } void wlserver_fake_mouse_pos( double x, double y ) { - // Fake a pos for eg. hiding true cursor state from Steam. - wlr_seat_pointer_notify_motion( wlserver.wlr.seat, 0, x, y ); - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + // Fake a pos for eg. hiding true cursor state from Steam. + wlr_seat_pointer_notify_motion( wlserver.wlr.seat, 0, x, y ); + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); } void wlserver_mousebutton( int button, bool press, uint32_t time ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - wlserver.bCursorHidden = !wlserver.bCursorHasImage; + wlserver.bCursorHidden = !wlserver.bCursorHasImage; - wlserver_oncursorevent(); + wlserver_oncursorevent( ); - wlr_seat_pointer_notify_button( wlserver.wlr.seat, time, button, press ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED ); - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + wlr_seat_pointer_notify_button( + wlserver.wlr.seat, + time, + button, + press ? WL_POINTER_BUTTON_STATE_PRESSED + : WL_POINTER_BUTTON_STATE_RELEASED ); + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); } void wlserver_mousewheel( double flX, double flY, uint32_t time ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - wlr_seat_pointer_notify_axis( wlserver.wlr.seat, time, WL_POINTER_AXIS_HORIZONTAL_SCROLL, flX, flX * WLR_POINTER_AXIS_DISCRETE_STEP, WL_POINTER_AXIS_SOURCE_WHEEL, WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL ); - wlr_seat_pointer_notify_axis( wlserver.wlr.seat, time, WL_POINTER_AXIS_VERTICAL_SCROLL, flY, flY * WLR_POINTER_AXIS_DISCRETE_STEP, WL_POINTER_AXIS_SOURCE_WHEEL, WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL ); - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + wlr_seat_pointer_notify_axis( + wlserver.wlr.seat, + time, + WL_POINTER_AXIS_HORIZONTAL_SCROLL, + flX, + flX * WLR_POINTER_AXIS_DISCRETE_STEP, + WL_POINTER_AXIS_SOURCE_WHEEL, + WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL ); + wlr_seat_pointer_notify_axis( + wlserver.wlr.seat, + time, + WL_POINTER_AXIS_VERTICAL_SCROLL, + flY, + flY * WLR_POINTER_AXIS_DISCRETE_STEP, + WL_POINTER_AXIS_SOURCE_WHEEL, + WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL ); + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); } -void wlserver_send_frame_done( struct wlr_surface *surf, const struct timespec *when ) +void wlserver_send_frame_done( + struct wlr_surface *surf, const struct timespec *when ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - wlr_surface_send_frame_done( surf, when ); + wlr_surface_send_frame_done( surf, when ); } bool wlserver_surface_is_async( struct wlr_surface *surf ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - auto wl_surf = get_wl_surface_info( surf ); - if ( !wl_surf ) - return false; + auto wl_surf = get_wl_surface_info( surf ); + if ( !wl_surf ) return false; - // If we are using the Gamescope WSI layer, treat both immediate and mailbox as - // "async", this is because we have a global tearing override for games. - // When that is enabled we want anything not FIFO or explicitly vsynced to - // have tearing. - if ( wl_surf->oCurrentPresentMode ) - { - return wl_surf->oCurrentPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR || - wl_surf->oCurrentPresentMode == VK_PRESENT_MODE_MAILBOX_KHR; - } + // If we are using the Gamescope WSI layer, treat both immediate and mailbox + // as "async", this is because we have a global tearing override for games. + // When that is enabled we want anything not FIFO or explicitly vsynced to + // have tearing. + if ( wl_surf->oCurrentPresentMode ) + { + return wl_surf->oCurrentPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR || + wl_surf->oCurrentPresentMode == VK_PRESENT_MODE_MAILBOX_KHR; + } - return false; + return false; } bool wlserver_surface_is_fifo( struct wlr_surface *surf ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - auto wl_surf = get_wl_surface_info( surf ); - if ( !wl_surf ) - return false; + auto wl_surf = get_wl_surface_info( surf ); + if ( !wl_surf ) return false; - if ( wl_surf->oCurrentPresentMode ) - { - return wl_surf->oCurrentPresentMode == VK_PRESENT_MODE_FIFO_KHR; - } + if ( wl_surf->oCurrentPresentMode ) + { + return wl_surf->oCurrentPresentMode == VK_PRESENT_MODE_FIFO_KHR; + } - return false; + return false; } static std::shared_ptr s_NullFeedback; -const std::shared_ptr& wlserver_surface_swapchain_feedback( struct wlr_surface *surf ) +const std::shared_ptr & +wlserver_surface_swapchain_feedback( struct wlr_surface *surf ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - auto wl_surf = get_wl_surface_info( surf ); - if ( !wl_surf ) - return s_NullFeedback; + auto wl_surf = get_wl_surface_info( surf ); + if ( !wl_surf ) return s_NullFeedback; - return wl_surf->swapchain_feedback; + return wl_surf->swapchain_feedback; } /* Handle the orientation of the touch inputs */ -static void apply_touchscreen_orientation(GamescopePanelOrientation orientation, double *x, double *y ) -{ - double tx = 0; - double ty = 0; - - switch ( orientation ) - { - default: - case GAMESCOPE_PANEL_ORIENTATION_AUTO: - case GAMESCOPE_PANEL_ORIENTATION_0: - tx = *x; - ty = *y; - break; - case GAMESCOPE_PANEL_ORIENTATION_90: - tx = 1.0 - *y; - ty = *x; - break; - case GAMESCOPE_PANEL_ORIENTATION_180: - tx = 1.0 - *x; - ty = 1.0 - *y; - break; - case GAMESCOPE_PANEL_ORIENTATION_270: - tx = *y; - ty = 1.0 - *x; - break; - } - - *x = tx; - *y = ty; -} - -void wlserver_touchmotion( double x, double y, int touch_id, uint32_t time, bool bAlwaysWarpCursor, gamescope::IBackendConnector* connector ) -{ - assert( wlserver_is_lock_held() ); - - if ( wlserver.mouse_focus_surface != NULL ) - { - double tx = x; - double ty = y; - - if ( connector ) - { - apply_touchscreen_orientation(connector->GetCurrentOrientation(), &tx, &ty); - } - - tx *= g_nOutputWidth; - ty *= g_nOutputHeight; - tx += focusedWindowOffsetX; - ty += focusedWindowOffsetY; - tx *= focusedWindowScaleX; - ty *= focusedWindowScaleY; - - auto [nWidth, nHeight] = wlserver_get_cursor_bounds(); - tx = clamp( tx, 0.0, nWidth - 0.1 ); - ty = clamp( ty, 0.0, nHeight - 0.1 ); - - double trackpad_dx, trackpad_dy; - - trackpad_dx = tx - wlserver.mouse_surface_cursorx; - trackpad_dy = ty - wlserver.mouse_surface_cursory; - - gamescope::TouchClickMode eMode = GetBackend()->GetTouchClickMode(); - - if ( eMode == gamescope::TouchClickModes::Passthrough ) - { - wlr_seat_touch_notify_motion( wlserver.wlr.seat, time, touch_id, tx, ty ); - - if ( bAlwaysWarpCursor ) - wlserver_mousewarp( tx, ty, time, false ); - } - else if ( eMode == gamescope::TouchClickModes::Disabled ) - { - return; - } - else if ( eMode == gamescope::TouchClickModes::Trackpad ) - { - wlserver_mousemotion( trackpad_dx, trackpad_dy, time ); - } - else - { - g_bPendingTouchMovement = true; - - wlserver_mousewarp( tx, ty, time, false ); - } - } - - bump_input_counter(); -} - -void wlserver_touchdown( double x, double y, int touch_id, uint32_t time, gamescope::IBackendConnector* connector ) -{ - assert( wlserver_is_lock_held() ); - - if ( wlserver.mouse_focus_surface != NULL ) - { - double tx = x; - double ty = y; - - if ( connector ) - { - apply_touchscreen_orientation(connector->GetCurrentOrientation(), &tx, &ty); - } - - tx *= g_nOutputWidth; - ty *= g_nOutputHeight; - tx += focusedWindowOffsetX; - ty += focusedWindowOffsetY; - tx *= focusedWindowScaleX; - ty *= focusedWindowScaleY; - - gamescope::TouchClickMode eMode = GetBackend()->GetTouchClickMode(); - - if ( eMode == gamescope::TouchClickModes::Passthrough ) - { - wlr_seat_touch_notify_down( wlserver.wlr.seat, wlserver.mouse_focus_surface, time, touch_id, - tx, ty ); - - wlserver.touch_down_ids.insert( touch_id ); - } - else if ( eMode == gamescope::TouchClickModes::Disabled ) - { - return; - } - else - { - g_bPendingTouchMovement = true; +static void apply_touchscreen_orientation( + GamescopePanelOrientation orientation, double *x, double *y ) +{ + double tx = 0; + double ty = 0; + + switch ( orientation ) + { + default: + case GAMESCOPE_PANEL_ORIENTATION_AUTO: + case GAMESCOPE_PANEL_ORIENTATION_0: + tx = *x; + ty = *y; + break; + case GAMESCOPE_PANEL_ORIENTATION_90: + tx = 1.0 - *y; + ty = *x; + break; + case GAMESCOPE_PANEL_ORIENTATION_180: + tx = 1.0 - *x; + ty = 1.0 - *y; + break; + case GAMESCOPE_PANEL_ORIENTATION_270: + tx = *y; + ty = 1.0 - *x; + break; + } - if ( eMode != gamescope::TouchClickModes::Trackpad ) - { - wlserver_mousewarp( tx, ty, time, false ); - } - - uint32_t button = TouchClickModeToLinuxButton( eMode ); - - if ( button != 0 && eMode < WLSERVER_BUTTON_COUNT ) - { - wlr_seat_pointer_notify_button( wlserver.wlr.seat, time, button, WL_POINTER_BUTTON_STATE_PRESSED ); - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); - - wlserver.button_held[ eMode ] = true; - } - } - } - - bump_input_counter(); + *x = tx; + *y = ty; } -void wlserver_touchup( int touch_id, uint32_t time ) +void wlserver_touchmotion( + double x, + double y, + int touch_id, + uint32_t time, + bool bAlwaysWarpCursor, + gamescope::IBackendConnector *connector ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); + + if ( wlserver.mouse_focus_surface != NULL ) + { + double tx = x; + double ty = y; + + if ( connector ) + { + apply_touchscreen_orientation( + connector->GetCurrentOrientation( ), &tx, &ty ); + } + + tx *= g_nOutputWidth; + ty *= g_nOutputHeight; + tx += focusedWindowOffsetX; + ty += focusedWindowOffsetY; + tx *= focusedWindowScaleX; + ty *= focusedWindowScaleY; + + auto [ nWidth, nHeight ] = wlserver_get_cursor_bounds( ); + tx = clamp( tx, 0.0, nWidth - 0.1 ); + ty = clamp( ty, 0.0, nHeight - 0.1 ); - if ( wlserver.mouse_focus_surface != NULL ) - { - bool bReleasedAny = false; - for ( int i = 0; i < WLSERVER_BUTTON_COUNT; i++ ) - { - if ( wlserver.button_held[ i ] == true ) - { - uint32_t button = TouchClickModeToLinuxButton( (gamescope::TouchClickMode) i ); + double trackpad_dx, trackpad_dy; - if ( button != 0 ) - { - wlr_seat_pointer_notify_button( wlserver.wlr.seat, time, button, WL_POINTER_BUTTON_STATE_RELEASED ); - bReleasedAny = true; - } + trackpad_dx = tx - wlserver.mouse_surface_cursorx; + trackpad_dy = ty - wlserver.mouse_surface_cursory; - wlserver.button_held[ i ] = false; - } - } + gamescope::TouchClickMode eMode = GetBackend( )->GetTouchClickMode( ); - if ( bReleasedAny == true ) - { - wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); - } + if ( eMode == gamescope::TouchClickModes::Passthrough ) + { + wlr_seat_touch_notify_motion( + wlserver.wlr.seat, time, touch_id, tx, ty ); - if ( wlserver.touch_down_ids.count( touch_id ) > 0 ) - { - wlr_seat_touch_notify_up( wlserver.wlr.seat, time, touch_id ); - wlserver.touch_down_ids.erase( touch_id ); - } - } + if ( bAlwaysWarpCursor ) wlserver_mousewarp( tx, ty, time, false ); + } + else if ( eMode == gamescope::TouchClickModes::Disabled ) { return; } + else if ( eMode == gamescope::TouchClickModes::Trackpad ) + { + wlserver_mousemotion( trackpad_dx, trackpad_dy, time ); + } + else + { + g_bPendingTouchMovement = true; - bump_input_counter(); + wlserver_mousewarp( tx, ty, time, false ); + } + } + + bump_input_counter( ); } -gamescope_xwayland_server_t *wlserver_get_xwayland_server( size_t index ) +void wlserver_touchdown( + double x, + double y, + int touch_id, + uint32_t time, + gamescope::IBackendConnector *connector ) { - if (index >= wlserver.wlr.xwayland_servers.size() ) - return NULL; - return wlserver.wlr.xwayland_servers[index].get(); + assert( wlserver_is_lock_held( ) ); + + if ( wlserver.mouse_focus_surface != NULL ) + { + double tx = x; + double ty = y; + + if ( connector ) + { + apply_touchscreen_orientation( + connector->GetCurrentOrientation( ), &tx, &ty ); + } + + tx *= g_nOutputWidth; + ty *= g_nOutputHeight; + tx += focusedWindowOffsetX; + ty += focusedWindowOffsetY; + tx *= focusedWindowScaleX; + ty *= focusedWindowScaleY; + + gamescope::TouchClickMode eMode = GetBackend( )->GetTouchClickMode( ); + + if ( eMode == gamescope::TouchClickModes::Passthrough ) + { + wlr_seat_touch_notify_down( + wlserver.wlr.seat, + wlserver.mouse_focus_surface, + time, + touch_id, + tx, + ty ); + + wlserver.touch_down_ids.insert( touch_id ); + } + else if ( eMode == gamescope::TouchClickModes::Disabled ) { return; } + else + { + g_bPendingTouchMovement = true; + + if ( eMode != gamescope::TouchClickModes::Trackpad ) + { + wlserver_mousewarp( tx, ty, time, false ); + } + + uint32_t button = TouchClickModeToLinuxButton( eMode ); + + if ( button != 0 && eMode < WLSERVER_BUTTON_COUNT ) + { + wlr_seat_pointer_notify_button( + wlserver.wlr.seat, + time, + button, + WL_POINTER_BUTTON_STATE_PRESSED ); + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + + wlserver.button_held[ eMode ] = true; + } + } + } + + bump_input_counter( ); } -const char *wlserver_get_wl_display_name( void ) +void wlserver_touchup( int touch_id, uint32_t time ) { - return wlserver.wl_display_name; -} - -static void wlserver_x11_surface_info_set_wlr( struct wlserver_x11_surface_info *surf, struct wlr_surface *wlr_surf, bool override ) -{ - assert( wlserver_is_lock_held() ); - - if (!override) - { - wl_list_remove( &surf->pending_link ); - wl_list_init( &surf->pending_link ); - } - - wlserver_wl_surface_info *wl_surf_info = get_wl_surface_info(wlr_surf); - if (override) - { - if ( surf->override_surface ) - { - wlserver_wl_surface_info *wl_info = get_wl_surface_info(surf->override_surface); - if (wl_info) - wl_info->x11_surface = nullptr; - } - - surf->override_surface = wlr_surf; - } - else - { - if ( surf->main_surface ) - { - wlserver_wl_surface_info *wl_info = get_wl_surface_info(surf->main_surface); - if (wl_info) - wl_info->x11_surface = nullptr; - } + assert( wlserver_is_lock_held( ) ); - surf->main_surface = wlr_surf; - } - wl_surf_info->x11_surface = surf; + if ( wlserver.mouse_focus_surface != NULL ) + { + bool bReleasedAny = false; + for ( int i = 0; i < WLSERVER_BUTTON_COUNT; i++ ) + { + if ( wlserver.button_held[ i ] == true ) + { + uint32_t button = TouchClickModeToLinuxButton( + ( gamescope::TouchClickMode )i ); + + if ( button != 0 ) + { + wlr_seat_pointer_notify_button( + wlserver.wlr.seat, + time, + button, + WL_POINTER_BUTTON_STATE_RELEASED ); + bReleasedAny = true; + } + + wlserver.button_held[ i ] = false; + } + } - for (auto it = g_PendingCommits.begin(); it != g_PendingCommits.end();) - { - if (it->surf == wlr_surf) - { - PendingCommit_t pending = *it; + if ( bReleasedAny == true ) + { + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + } - // Still have the buffer lock from before... - wlserver_x11_surface_info *wlserver_x11_surface_info = get_wl_surface_info(wlr_surf)->x11_surface; - assert(wlserver_x11_surface_info); - assert(wlserver_x11_surface_info->xwayland_server); - wlserver_x11_surface_info->xwayland_server->wayland_commit( pending.surf, pending.buf ); + if ( wlserver.touch_down_ids.count( touch_id ) > 0 ) + { + wlr_seat_touch_notify_up( wlserver.wlr.seat, time, touch_id ); + wlserver.touch_down_ids.erase( touch_id ); + } + } - it = g_PendingCommits.erase(it); - } - else - { - it++; - } - } + bump_input_counter( ); } -void wlserver_x11_surface_info_init( struct wlserver_x11_surface_info *surf, gamescope_xwayland_server_t *server, uint32_t x11_id ) +gamescope_xwayland_server_t *wlserver_get_xwayland_server( size_t index ) { - surf->wl_id = 0; - surf->x11_id = x11_id; - surf->main_surface = nullptr; - surf->override_surface = nullptr; - surf->xwayland_server = server; - wl_list_init( &surf->pending_link ); + if ( index >= wlserver.wlr.xwayland_servers.size( ) ) return NULL; + return wlserver.wlr.xwayland_servers[ index ].get( ); } -void gamescope_xwayland_server_t::set_wl_id( struct wlserver_x11_surface_info *surf, uint32_t id ) +const char *wlserver_get_wl_display_name( void ) +{ return wlserver.wl_display_name; } + +static void wlserver_x11_surface_info_set_wlr( + struct wlserver_x11_surface_info *surf, + struct wlr_surface *wlr_surf, + bool override ) { - if (surf->wl_id) - { - if (surf->main_surface) - { - struct wl_resource *old_resource = wl_client_get_object( xwayland_server->client, surf->wl_id ); - if (!old_resource) - { - wl_log.errorf("wlserver_x11_surface_info had bad wl_id? Oh no! %d", surf->wl_id); - return; - } - wlr_surface *old_wlr_surf = wlr_surface_from_resource( old_resource ); - if (!old_wlr_surf) - { - wl_log.errorf("wlserver_x11_surface_info wl_id's was not a wlr_surf?! Oh no! %d", surf->wl_id); - return; - } + assert( wlserver_is_lock_held( ) ); - wlserver_wl_surface_info *old_surface_info = get_wl_surface_info(old_wlr_surf); - old_surface_info->x11_surface = nullptr; - } - else - { - wl_list_remove( &surf->pending_link ); - wl_list_init( &surf->pending_link ); - } - } + if ( !override ) + { + wl_list_remove( &surf->pending_link ); + wl_list_init( &surf->pending_link ); + } - surf->wl_id = id; - surf->main_surface = nullptr; - surf->xwayland_server = this; + wlserver_wl_surface_info *wl_surf_info = get_wl_surface_info( wlr_surf ); + if ( override ) + { + if ( surf->override_surface ) + { + wlserver_wl_surface_info *wl_info = + get_wl_surface_info( surf->override_surface ); + if ( wl_info ) wl_info->x11_surface = nullptr; + } - wl_list_insert( &pending_surfaces, &surf->pending_link ); - - struct wlr_surface *wlr_override_surf = nullptr; - struct wlr_surface *wlr_surf = nullptr; - if ( content_overrides.count( surf->x11_id ) ) - { - wlr_override_surf = content_overrides[ surf->x11_id ]->surface; - } - - struct wl_resource *resource = wl_client_get_object( xwayland_server->client, id ); - if ( resource != nullptr ) - wlr_surf = wlr_surface_from_resource( resource ); - - if ( wlr_surf != nullptr ) - wlserver_x11_surface_info_set_wlr( surf, wlr_surf, false ); - - if ( wlr_override_surf != nullptr ) - wlserver_x11_surface_info_set_wlr( surf, wlr_override_surf, true ); -} + surf->override_surface = wlr_surf; + } + else + { + if ( surf->main_surface ) + { + wlserver_wl_surface_info *wl_info = + get_wl_surface_info( surf->main_surface ); + if ( wl_info ) wl_info->x11_surface = nullptr; + } -bool gamescope_xwayland_server_t::is_xwayland_ready() const -{ - return xwayland_ready; + surf->main_surface = wlr_surf; + } + wl_surf_info->x11_surface = surf; + + for ( auto it = g_PendingCommits.begin( ); it != g_PendingCommits.end( ); ) + { + if ( it->surf == wlr_surf ) + { + PendingCommit_t pending = *it; + + // Still have the buffer lock from before... + wlserver_x11_surface_info *wlserver_x11_surface_info = + get_wl_surface_info( wlr_surf )->x11_surface; + assert( wlserver_x11_surface_info ); + assert( wlserver_x11_surface_info->xwayland_server ); + wlserver_x11_surface_info->xwayland_server->wayland_commit( + pending.surf, pending.buf ); + + it = g_PendingCommits.erase( it ); + } + else + { + it++; + } + } } -_XDisplay *gamescope_xwayland_server_t::get_xdisplay() +void wlserver_x11_surface_info_init( + struct wlserver_x11_surface_info *surf, + gamescope_xwayland_server_t *server, + uint32_t x11_id ) { - return dpy; + surf->wl_id = 0; + surf->x11_id = x11_id; + surf->main_surface = nullptr; + surf->override_surface = nullptr; + surf->xwayland_server = server; + wl_list_init( &surf->pending_link ); } -const char *gamescope_xwayland_server_t::get_nested_display_name() const +void gamescope_xwayland_server_t::set_wl_id( + struct wlserver_x11_surface_info *surf, uint32_t id ) { - return xwayland_server->display_name; + if ( surf->wl_id ) + { + if ( surf->main_surface ) + { + struct wl_resource *old_resource = + wl_client_get_object( xwayland_server->client, surf->wl_id ); + if ( !old_resource ) + { + wl_log.errorf( + "wlserver_x11_surface_info had bad wl_id? Oh no! %d", + surf->wl_id ); + return; + } + wlr_surface *old_wlr_surf = + wlr_surface_from_resource( old_resource ); + if ( !old_wlr_surf ) + { + wl_log.errorf( + "wlserver_x11_surface_info wl_id's was not a wlr_surf?! Oh " + "no! %d", + surf->wl_id ); + return; + } + + wlserver_wl_surface_info *old_surface_info = + get_wl_surface_info( old_wlr_surf ); + old_surface_info->x11_surface = nullptr; + } + else + { + wl_list_remove( &surf->pending_link ); + wl_list_init( &surf->pending_link ); + } + } + + surf->wl_id = id; + surf->main_surface = nullptr; + surf->xwayland_server = this; + + wl_list_insert( &pending_surfaces, &surf->pending_link ); + + struct wlr_surface *wlr_override_surf = nullptr; + struct wlr_surface *wlr_surf = nullptr; + if ( content_overrides.count( surf->x11_id ) ) + { + wlr_override_surf = content_overrides[ surf->x11_id ]->surface; + } + + struct wl_resource *resource = + wl_client_get_object( xwayland_server->client, id ); + if ( resource != nullptr ) wlr_surf = wlr_surface_from_resource( resource ); + + if ( wlr_surf != nullptr ) + wlserver_x11_surface_info_set_wlr( surf, wlr_surf, false ); + + if ( wlr_override_surf != nullptr ) + wlserver_x11_surface_info_set_wlr( surf, wlr_override_surf, true ); } +bool gamescope_xwayland_server_t::is_xwayland_ready( ) const +{ return xwayland_ready; } + +_XDisplay *gamescope_xwayland_server_t::get_xdisplay( ) { return dpy; } + +const char *gamescope_xwayland_server_t::get_nested_display_name( ) const +{ return xwayland_server->display_name; } + void wlserver_x11_surface_info_finish( struct wlserver_x11_surface_info *surf ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - if (surf->main_surface) - { - surf->xwayland_server->destroy_content_override( surf, surf->main_surface ); + if ( surf->main_surface ) + { + surf->xwayland_server->destroy_content_override( + surf, surf->main_surface ); - wlserver_wl_surface_info *wl_info = get_wl_surface_info(surf->main_surface); - if (wl_info) - wl_info->x11_surface = nullptr; - } + wlserver_wl_surface_info *wl_info = + get_wl_surface_info( surf->main_surface ); + if ( wl_info ) wl_info->x11_surface = nullptr; + } - if (surf->override_surface) - { - surf->xwayland_server->destroy_content_override( surf, surf->override_surface ); + if ( surf->override_surface ) + { + surf->xwayland_server->destroy_content_override( + surf, surf->override_surface ); - wlserver_wl_surface_info *wl_info = get_wl_surface_info(surf->override_surface); - if (wl_info) - wl_info->x11_surface = nullptr; - } + wlserver_wl_surface_info *wl_info = + get_wl_surface_info( surf->override_surface ); + if ( wl_info ) wl_info->x11_surface = nullptr; + } - surf->wl_id = 0; - surf->main_surface = nullptr; - surf->override_surface = nullptr; - wl_list_remove( &surf->pending_link ); + surf->wl_id = 0; + surf->main_surface = nullptr; + surf->override_surface = nullptr; + wl_list_remove( &surf->pending_link ); } -void wlserver_set_xwayland_server_mode( size_t idx, int w, int h, int nRefreshmHz ) +void wlserver_set_xwayland_server_mode( + size_t idx, int w, int h, int nRefreshmHz ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( idx ); - if ( !server ) - return; + gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( idx ); + if ( !server ) return; - struct wlr_output *output = server->get_output(); - struct wlr_output_state *output_state = server->get_output_state(); - wlr_output_state_set_custom_mode(output_state, w, h, nRefreshmHz ); - if (!wlr_output_commit_state(output, output_state)) - { - wl_log.errorf("Failed to commit headless output"); - abort(); - } + struct wlr_output *output = server->get_output( ); + struct wlr_output_state *output_state = server->get_output_state( ); + wlr_output_state_set_custom_mode( output_state, w, h, nRefreshmHz ); + if ( !wlr_output_commit_state( output, output_state ) ) + { + wl_log.errorf( "Failed to commit headless output" ); + abort( ); + } - wl_log.infof("Updating mode for xwayland server #%zu: %dx%d@%d", idx, w, h, gamescope::ConvertmHzToHz( nRefreshmHz ) ); + wl_log.infof( + "Updating mode for xwayland server #%zu: %dx%d@%d", + idx, + w, + h, + gamescope::ConvertmHzToHz( nRefreshmHz ) ); } // Definitely not very efficient if we end up with @@ -3193,47 +3690,51 @@ void wlserver_set_xwayland_server_mode( size_t idx, int w, int h, int nRefreshmH // Given we are only expecting like 1-2 xdg-shell // windows for our current usecases (Waydroid, etc) // I think this is reasonable for now. -std::vector> wlserver_get_xdg_shell_windows() +std::vector> +wlserver_get_xdg_shell_windows( ) { - std::unique_lock lock(g_wlserver_xdg_shell_windows_lock); - return wlserver.xdg_wins; + std::unique_lock lock( g_wlserver_xdg_shell_windows_lock ); + return wlserver.xdg_wins; } -bool wlserver_xdg_dirty() -{ - return wlserver.xdg_dirty.exchange(false); -} +bool wlserver_xdg_dirty( ) { return wlserver.xdg_dirty.exchange( false ); } -std::vector wlserver_xdg_commit_queue() +std::vector wlserver_xdg_commit_queue( ) { - std::vector commits; - { - std::lock_guard lock( wlserver.xdg_commit_lock ); - commits = std::move(wlserver.xdg_commit_queue); - } - return commits; + std::vector commits; + { + std::lock_guard lock( wlserver.xdg_commit_lock ); + commits = std::move( wlserver.xdg_commit_queue ); + } + return commits; } -uint32_t wlserver_make_new_xwayland_server() +uint32_t wlserver_make_new_xwayland_server( ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - auto& server = wlserver.wlr.xwayland_servers.emplace_back(std::make_unique(wlserver.display, (int)wlserver.wlr.xwayland_servers.size())); + auto &server = wlserver.wlr.xwayland_servers.emplace_back( + std::make_unique( + wlserver.display, ( int )wlserver.wlr.xwayland_servers.size( ) ) ); - while (!server->is_xwayland_ready()) { - wl_display_flush_clients(wlserver.display); - if (wl_event_loop_dispatch(wlserver.event_loop, -1) < 0) { - wl_log.errorf("wl_event_loop_dispatch failed\n"); - return ~0u; - } - } + while ( !server->is_xwayland_ready( ) ) + { + wl_display_flush_clients( wlserver.display ); + if ( wl_event_loop_dispatch( wlserver.event_loop, -1 ) < 0 ) + { + wl_log.errorf( "wl_event_loop_dispatch failed\n" ); + return ~0u; + } + } - return uint32_t(wlserver.wlr.xwayland_servers.size() - 1); + return uint32_t( wlserver.wlr.xwayland_servers.size( ) - 1 ); } -void wlserver_destroy_xwayland_server(gamescope_xwayland_server_t *server) +void wlserver_destroy_xwayland_server( gamescope_xwayland_server_t *server ) { - assert( wlserver_is_lock_held() ); + assert( wlserver_is_lock_held( ) ); - std::erase_if(wlserver.wlr.xwayland_servers, [=](const auto& other) { return other.get() == server; }); + std::erase_if( + wlserver.wlr.xwayland_servers, + [ = ]( const auto &other ) { return other.get( ) == server; } ); } diff --git a/src/wlserver.hpp b/src/wlserver.hpp index eb1270806d..bda6a4fac7 100644 --- a/src/wlserver.hpp +++ b/src/wlserver.hpp @@ -2,16 +2,16 @@ #pragma once -#include #include -#include +#include +#include #include #include -#include +#include #include -#include #include -#include +#include +#include #include "WaylandServer/WaylandDecls.h" #include "WaylandServer/WaylandServerLegacy.h" @@ -23,7 +23,7 @@ #include "steamcompmgr_shared.hpp" #if HAVE_DRM -#define HAVE_SESSION 1 + #define HAVE_SESSION 1 #endif #define WLSERVER_BUTTON_COUNT 7 @@ -33,245 +33,286 @@ struct xwayland_ctx_t; struct GamescopeAcquireTimelineState { - int32_t nEventFd = -1; - bool bKnownReady = false; + int32_t nEventFd = -1; + bool bKnownReady = false; }; -struct ResListEntry_t { - struct wlr_surface *surf; - struct wlr_buffer *buf; - bool async; - bool fifo; - std::shared_ptr feedback; - std::vector presentation_feedbacks; - std::optional present_id; - uint64_t desired_present_time; - std::shared_ptr pAcquirePoint; - std::shared_ptr pReleasePoint; +struct ResListEntry_t +{ + struct wlr_surface *surf; + struct wlr_buffer *buf; + bool async; + bool fifo; + std::shared_ptr feedback; + std::vector presentation_feedbacks; + std::optional present_id; + uint64_t desired_present_time; + std::shared_ptr pAcquirePoint; + std::shared_ptr pReleasePoint; }; struct wlserver_content_override; -bool wlserver_is_lock_held(void); +bool wlserver_is_lock_held( void ); class gamescope_xwayland_server_t { public: - gamescope_xwayland_server_t(wl_display *display, int nIndex); - ~gamescope_xwayland_server_t(); + gamescope_xwayland_server_t( wl_display *display, int nIndex ); + ~gamescope_xwayland_server_t( ); - void on_xwayland_ready(void *data); - static void xwayland_ready_callback(struct wl_listener *listener, void *data); + void on_xwayland_ready( void *data ); + static void + xwayland_ready_callback( struct wl_listener *listener, void *data ); - bool is_xwayland_ready() const; - const char *get_nested_display_name() const; + bool is_xwayland_ready( ) const; + const char *get_nested_display_name( ) const; - void set_wl_id( struct wlserver_x11_surface_info *surf, uint32_t id ); + void set_wl_id( struct wlserver_x11_surface_info *surf, uint32_t id ); - _XDisplay *get_xdisplay(); + _XDisplay *get_xdisplay( ); - std::unique_ptr ctx; + std::unique_ptr ctx; - void wayland_commit(struct wlr_surface *surf, struct wlr_buffer *buf); + void wayland_commit( struct wlr_surface *surf, struct wlr_buffer *buf ); - std::vector& retrieve_commits(); + std::vector &retrieve_commits( ); - void handle_override_window_content( struct wl_client *client, struct wl_resource *gamescope_swapchain_resource, struct wlr_surface *surface, uint32_t x11_window ); - void destroy_content_override( struct wlserver_x11_surface_info *x11_surface, struct wlr_surface *surf); - void destroy_content_override(struct wlserver_content_override *co); + void handle_override_window_content( + struct wl_client *client, + struct wl_resource *gamescope_swapchain_resource, + struct wlr_surface *surface, + uint32_t x11_window ); + void destroy_content_override( + struct wlserver_x11_surface_info *x11_surface, + struct wlr_surface *surf ); + void destroy_content_override( struct wlserver_content_override *co ); - struct wl_client *get_client(); - struct wlr_output *get_output(); - struct wlr_output_state *get_output_state(); + struct wl_client *get_client( ); + struct wlr_output *get_output( ); + struct wlr_output_state *get_output_state( ); - void update_output_info(); + void update_output_info( ); - int get_index() const { return m_nIndex; } + int get_index( ) const { return m_nIndex; } private: - struct wlr_xwayland_server *xwayland_server = NULL; - struct wl_listener xwayland_ready_listener = { .notify = xwayland_ready_callback }; + struct wlr_xwayland_server *xwayland_server = NULL; + struct wl_listener xwayland_ready_listener = { + .notify = xwayland_ready_callback + }; - struct wlr_output *output = nullptr; - struct wlr_output_state *output_state = nullptr; + struct wlr_output *output = nullptr; + struct wlr_output_state *output_state = nullptr; - std::unordered_map content_overrides; + std::unordered_map content_overrides; - bool xwayland_ready = false; - _XDisplay *dpy = NULL; + bool xwayland_ready = false; + _XDisplay *dpy = NULL; - int m_nIndex = 0; + int m_nIndex = 0; - std::mutex wayland_commit_lock; - std::vector wayland_commit_queue; + std::mutex wayland_commit_lock; + std::vector wayland_commit_queue; }; -struct wlserver_t { - struct wl_display *display; - struct wl_event_loop *event_loop; - char wl_display_name[32]; - - struct { - struct wlr_backend *multi_backend; - struct wlr_backend *headless_backend; - struct wlr_backend *libinput_backend; - - struct wlr_renderer *renderer; - struct wlr_compositor *compositor; - struct wlr_session *session; - struct wlr_seat *seat; - - // Used to simulate key events and set the keymap - struct wlr_keyboard *virtual_keyboard_device; - - struct wlr_device *device; - - std::vector> xwayland_servers; - } wlr; - - struct wlr_surface *mouse_focus_surface; - struct wlr_surface *kb_focus_surface; - std::unordered_map> current_dropdown_surfaces; - double mouse_surface_cursorx = 0.0f; - double mouse_surface_cursory = 0.0f; - bool mouse_constraint_requires_warp = false; - pixman_region32_t confine; - std::atomic mouse_constraint = { nullptr }; - - void SetMouseConstraint( struct wlr_pointer_constraint_v1 *pConstraint ) - { - assert( wlserver_is_lock_held() ); - // Set by wlserver only. Read by both wlserver + steamcompmgr with no - // need to actually be sequentially consistent. - mouse_constraint.store( pConstraint, std::memory_order_relaxed ); - } - - struct wlr_pointer_constraint_v1 *GetCursorConstraint() const - { - assert( wlserver_is_lock_held() ); - return mouse_constraint.load( std::memory_order_relaxed ); - } - - bool HasMouseConstraint() const - { - // Does not need to be sequentially consistent. - // Used by the steamcompmgr thread to check if there is currently a mouse constraint. - return mouse_constraint.load( std::memory_order_relaxed ) != nullptr; - } - - uint64_t ulLastMovedCursorTime = 0; - bool bCursorHidden = true; - bool bCursorHasImage = true; - - bool button_held[ WLSERVER_BUTTON_COUNT ]; - std::set touch_down_ids; - - struct { - char *name; - char *description; - int phys_width, phys_height; // millimeters - } output_info; - - struct wl_listener session_active; - struct wl_listener new_input_method; - - struct wlr_xdg_shell *xdg_shell; - struct wlr_layer_shell_v1 *layer_shell_v1; - struct wlr_relative_pointer_manager_v1 *relative_pointer_manager; - struct wlr_pointer_constraints_v1 *constraints; - struct wl_listener new_xdg_surface; - struct wl_listener new_xdg_toplevel; - struct wl_listener new_layer_shell_surface; - struct wl_listener new_pointer_constraint; - std::vector> xdg_wins; - std::atomic xdg_dirty; - std::mutex xdg_commit_lock; - std::vector xdg_commit_queue; - - std::vector gamescope_controls; - std::unordered_map< uint32_t, std::vector > app_perf_requests; - - std::atomic bWaylandServerRunning = { false }; - - // Share one single keymap and state between all connected physical keyboards +struct wlserver_t +{ + struct wl_display *display; + struct wl_event_loop *event_loop; + char wl_display_name[ 32 ]; + + struct + { + struct wlr_backend *multi_backend; + struct wlr_backend *headless_backend; + struct wlr_backend *libinput_backend; + + struct wlr_renderer *renderer; + struct wlr_compositor *compositor; + struct wlr_session *session; + struct wlr_seat *seat; + + // Used to simulate key events and set the keymap + struct wlr_keyboard *virtual_keyboard_device; + + struct wlr_device *device; + + std::vector> + xwayland_servers; + } wlr; + + struct wlr_surface *mouse_focus_surface; + struct wlr_surface *kb_focus_surface; + std::unordered_map> + current_dropdown_surfaces; + double mouse_surface_cursorx = 0.0f; + double mouse_surface_cursory = 0.0f; + bool mouse_constraint_requires_warp = false; + pixman_region32_t confine; + std::atomic mouse_constraint = { + nullptr + }; + + void SetMouseConstraint( struct wlr_pointer_constraint_v1 *pConstraint ) + { + assert( wlserver_is_lock_held( ) ); + // Set by wlserver only. Read by both wlserver + steamcompmgr with no + // need to actually be sequentially consistent. + mouse_constraint.store( pConstraint, std::memory_order_relaxed ); + } + + struct wlr_pointer_constraint_v1 *GetCursorConstraint( ) const + { + assert( wlserver_is_lock_held( ) ); + return mouse_constraint.load( std::memory_order_relaxed ); + } + + bool HasMouseConstraint( ) const + { + // Does not need to be sequentially consistent. + // Used by the steamcompmgr thread to check if there is currently a + // mouse constraint. + return mouse_constraint.load( std::memory_order_relaxed ) != nullptr; + } + + uint64_t ulLastMovedCursorTime = 0; + bool bCursorHidden = true; + bool bCursorHasImage = true; + + bool button_held[ WLSERVER_BUTTON_COUNT ]; + std::set touch_down_ids; + + struct + { + char *name; + char *description; + int phys_width, phys_height; // millimeters + } output_info; + + struct wl_listener session_active; + struct wl_listener new_input_method; + + struct wlr_xdg_shell *xdg_shell; + struct wlr_layer_shell_v1 *layer_shell_v1; + struct wlr_relative_pointer_manager_v1 *relative_pointer_manager; + struct wlr_pointer_constraints_v1 *constraints; + struct wl_listener new_xdg_surface; + struct wl_listener new_xdg_toplevel; + struct wl_listener new_layer_shell_surface; + struct wl_listener new_pointer_constraint; + std::vector> xdg_wins; + std::atomic xdg_dirty; + std::mutex xdg_commit_lock; + std::vector xdg_commit_queue; + + std::vector gamescope_controls; + std::unordered_map> app_perf_requests; + + std::atomic bWaylandServerRunning = { false }; + + // Share one single keymap and state between all connected physical + // keyboards struct wlr_keyboard_group *keyboard_group; - struct wl_listener keyboard_group_modifiers; - struct wl_listener keyboard_group_key; + struct wl_listener keyboard_group_modifiers; + struct wl_listener keyboard_group_key; }; extern struct wlserver_t wlserver; -std::vector wlserver_xdg_commit_queue(); +std::vector wlserver_xdg_commit_queue( ); -struct wlserver_pointer { - struct wlr_pointer *wlr; - - struct wl_listener motion; - struct wl_listener button; - struct wl_listener axis; - struct wl_listener frame; +struct wlserver_pointer +{ + struct wlr_pointer *wlr; + + struct wl_listener motion; + struct wl_listener button; + struct wl_listener axis; + struct wl_listener frame; }; -struct wlserver_touch { - struct wlr_touch *wlr; - - struct wl_listener down; - struct wl_listener up; - struct wl_listener motion; +struct wlserver_touch +{ + struct wlr_touch *wlr; - gamescope::IBackendConnector* connector; + struct wl_listener down; + struct wl_listener up; + struct wl_listener motion; + + gamescope::IBackendConnector *connector; }; -void xwayland_surface_commit(struct wlr_surface *wlr_surface); +void xwayland_surface_commit( struct wlr_surface *wlr_surface ); bool wlsession_init( void ); -int wlsession_open_kms( const char *device_name ); -void wlsession_close_kms(); +int wlsession_open_kms( const char *device_name ); +void wlsession_close_kms( ); bool wlserver_init( void ); -void wlserver_run(void); +void wlserver_run( void ); -void wlserver_lock(void); -void wlserver_unlock(bool flush = true); -bool wlserver_is_lock_held(void); +void wlserver_lock( void ); +void wlserver_unlock( bool flush = true ); +bool wlserver_is_lock_held( void ); -void wlserver_keyboardfocus( struct wlr_surface *surface, bool bConstrain = true ); +void wlserver_keyboardfocus( + struct wlr_surface *surface, bool bConstrain = true ); void wlserver_key( uint32_t key, bool press, uint32_t time ); -void wlserver_mousefocus( struct wlr_surface *wlrsurface, int x = 0, int y = 0 ); -void wlserver_clear_dropdowns(); +void wlserver_mousefocus( + struct wlr_surface *wlrsurface, int x = 0, int y = 0 ); +void wlserver_clear_dropdowns( ); void wlserver_notify_dropdown( struct wlr_surface *wlrsurface, int nX, int nY ); void wlserver_mousemotion( double x, double y, uint32_t time ); -void wlserver_mousehide(); +void wlserver_mousehide( ); void wlserver_mousewarp( double x, double y, uint32_t time, bool bSynthetic ); void wlserver_mousebutton( int button, bool press, uint32_t time ); void wlserver_mousewheel( double x, double y, uint32_t time ); -void wlserver_touchmotion( double x, double y, int touch_id, uint32_t time, bool bAlwaysWarpCursor = false, gamescope::IBackendConnector* connector = nullptr ); -void wlserver_touchdown( double x, double y, int touch_id, uint32_t time, gamescope::IBackendConnector* connector = nullptr ); +void wlserver_touchmotion( + double x, + double y, + int touch_id, + uint32_t time, + bool bAlwaysWarpCursor = false, + gamescope::IBackendConnector *connector = nullptr ); +void wlserver_touchdown( + double x, + double y, + int touch_id, + uint32_t time, + gamescope::IBackendConnector *connector = nullptr ); void wlserver_touchup( int touch_id, uint32_t time ); -void wlserver_send_frame_done( struct wlr_surface *surf, const struct timespec *when ); +void wlserver_send_frame_done( + struct wlr_surface *surf, const struct timespec *when ); bool wlserver_surface_is_async( struct wlr_surface *surf ); bool wlserver_surface_is_fifo( struct wlr_surface *surf ); -const std::shared_ptr& wlserver_surface_swapchain_feedback( struct wlr_surface *surf ); +const std::shared_ptr & +wlserver_surface_swapchain_feedback( struct wlr_surface *surf ); -std::vector> wlserver_get_xdg_shell_windows(); -bool wlserver_xdg_dirty(); +std::vector> +wlserver_get_xdg_shell_windows( ); +bool wlserver_xdg_dirty( ); -struct wlserver_output_info { - const char *description; - int phys_width, phys_height; // millimeters +struct wlserver_output_info +{ + const char *description; + int phys_width, phys_height; // millimeters }; void wlserver_set_output_info( const wlserver_output_info *info ); gamescope_xwayland_server_t *wlserver_get_xwayland_server( size_t index ); -const char *wlserver_get_wl_display_name( void ); +const char *wlserver_get_wl_display_name( void ); -void wlserver_x11_surface_info_init( struct wlserver_x11_surface_info *surf, gamescope_xwayland_server_t *server, uint32_t x11_id ); +void wlserver_x11_surface_info_init( + struct wlserver_x11_surface_info *surf, + gamescope_xwayland_server_t *server, + uint32_t x11_id ); void wlserver_x11_surface_info_finish( struct wlserver_x11_surface_info *surf ); void wlserver_set_xwayland_server_mode( size_t idx, int w, int h, int refresh ); @@ -280,23 +321,41 @@ extern std::atomic g_bPendingTouchMovement; void wlserver_open_steam_menu( bool qam ); -uint32_t wlserver_make_new_xwayland_server(); -void wlserver_destroy_xwayland_server(gamescope_xwayland_server_t *server); - -void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std::vector& presentation_feedbacks, uint64_t last_refresh_nsec, uint64_t refresh_cycle ); -void wlserver_presentation_feedback_discard( struct wlr_surface *surface, std::vector& presentation_feedbacks ); - -void wlserver_past_present_timing( struct wlr_surface *surface, uint32_t present_id, uint64_t desired_present_time, uint64_t actual_present_time, uint64_t earliest_present_time, uint64_t present_margin ); -void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle ); +uint32_t wlserver_make_new_xwayland_server( ); +void wlserver_destroy_xwayland_server( gamescope_xwayland_server_t *server ); + +void wlserver_presentation_feedback_presented( + struct wlr_surface *surface, + std::vector &presentation_feedbacks, + uint64_t last_refresh_nsec, + uint64_t refresh_cycle ); +void wlserver_presentation_feedback_discard( + struct wlr_surface *surface, + std::vector &presentation_feedbacks ); + +void wlserver_past_present_timing( + struct wlr_surface *surface, + uint32_t present_id, + uint64_t desired_present_time, + uint64_t actual_present_time, + uint64_t earliest_present_time, + uint64_t present_margin ); +void wlserver_refresh_cycle( + struct wlr_surface *surface, uint64_t refresh_cycle ); void wlserver_app_presented( uint32_t app_id, uint64_t frametime_ns ); -void wlserver_shutdown(); +void wlserver_shutdown( ); void wlserver_send_gamescope_control( wl_resource *control ); -bool wlsession_active(); +bool wlsession_active( ); void wlserver_fake_mouse_pos( double x, double y ); -void wlserver_mousewheel2( int32_t nDiscreteX, int32_t nDiscreteY, double flX, double flY, uint32_t uTime ); +void wlserver_mousewheel2( + int32_t nDiscreteX, + int32_t nDiscreteY, + double flX, + double flY, + uint32_t uTime ); diff --git a/src/x11cursor.cpp b/src/x11cursor.cpp index 412dd6f42f..78f521b1ce 100644 --- a/src/x11cursor.cpp +++ b/src/x11cursor.cpp @@ -1,53 +1,52 @@ -#include #include -#include "backend.h" +#include #include "Utils/Defer.h" +#include "backend.h" #include "xwayland_ctx.hpp" extern const char *g_pOriginalDisplay; namespace gamescope { - std::shared_ptr GetX11HostCursor() - { - if ( !g_pOriginalDisplay ) - return nullptr; + std::shared_ptr GetX11HostCursor( ) + { + if ( !g_pOriginalDisplay ) return nullptr; - Display *display = XOpenDisplay( g_pOriginalDisplay ); - if ( !display ) - return nullptr; - defer( XCloseDisplay( display ) ); + Display *display = XOpenDisplay( g_pOriginalDisplay ); + if ( !display ) return nullptr; + defer( XCloseDisplay( display ) ); - int xfixes_event, xfixes_error; - if ( !XFixesQueryExtension( display, &xfixes_event, &xfixes_error ) ) - { - xwm_log.errorf("No XFixes extension on current compositor"); - return nullptr; - } + int xfixes_event, xfixes_error; + if ( !XFixesQueryExtension( display, &xfixes_event, &xfixes_error ) ) + { + xwm_log.errorf( "No XFixes extension on current compositor" ); + return nullptr; + } - XFixesCursorImage *image = XFixesGetCursorImage( display ); - if ( !image ) - return nullptr; - defer( XFree( image ) ); + XFixesCursorImage *image = XFixesGetCursorImage( display ); + if ( !image ) return nullptr; + defer( XFree( image ) ); - // image->pixels is `unsigned long*` :/ - // Thanks X11. - std::vector cursorData = std::vector( image->width * image->height ); - for (uint32_t y = 0; y < image->height; y++) - { - for (uint32_t x = 0; x < image->width; x++) - { - cursorData[y * image->width + x] = static_cast( image->pixels[image->height * y + x] ); - } - } + // image->pixels is `unsigned long*` :/ + // Thanks X11. + std::vector cursorData = + std::vector( image->width * image->height ); + for ( uint32_t y = 0; y < image->height; y++ ) + { + for ( uint32_t x = 0; x < image->width; x++ ) + { + cursorData[ y * image->width + x ] = static_cast( + image->pixels[ image->height * y + x ] ); + } + } - return std::make_shared(INestedHints::CursorInfo - { - .pPixels = std::move( cursorData ), - .uWidth = image->width, - .uHeight = image->height, - .uXHotspot = image->xhot, - .uYHotspot = image->yhot, - }); - } -} + return std::make_shared( + INestedHints::CursorInfo{ + .pPixels = std::move( cursorData ), + .uWidth = image->width, + .uHeight = image->height, + .uXHotspot = image->xhot, + .uYHotspot = image->yhot, + } ); + } +} // namespace gamescope diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp index d680fbfe90..5b7e99157e 100644 --- a/src/xwayland_ctx.hpp +++ b/src/xwayland_ctx.hpp @@ -3,17 +3,17 @@ #include "backend.h" #include "waitable.h" -#include #include +#include #include +#include #include #include -#include +#include #include #include #include -#include #include #include @@ -26,257 +26,255 @@ extern LogScope xwm_log; struct focus_t { - steamcompmgr_win_t *focusWindow = nullptr; - steamcompmgr_win_t *inputFocusWindow = nullptr; - uint32_t inputFocusMode = 0; - steamcompmgr_win_t *overlayWindow = nullptr; - steamcompmgr_win_t *externalOverlayWindow = nullptr; - steamcompmgr_win_t *notificationWindow = nullptr; - steamcompmgr_win_t *overrideWindow = nullptr; - steamcompmgr_win_t *overrideWindowMouse = nullptr; - bool outdatedInteractiveFocus = false; - bool bResetToCorner = false; - bool bResetToCenter = false; - - uint64_t ulCurrentFocusSerial = UINT64_MAX; - - bool IsDirty(); + steamcompmgr_win_t *focusWindow = nullptr; + steamcompmgr_win_t *inputFocusWindow = nullptr; + uint32_t inputFocusMode = 0; + steamcompmgr_win_t *overlayWindow = nullptr; + steamcompmgr_win_t *externalOverlayWindow = nullptr; + steamcompmgr_win_t *notificationWindow = nullptr; + steamcompmgr_win_t *overrideWindow = nullptr; + steamcompmgr_win_t *overrideWindowMouse = nullptr; + bool outdatedInteractiveFocus = false; + bool bResetToCorner = false; + bool bResetToCenter = false; + + uint64_t ulCurrentFocusSerial = UINT64_MAX; + + bool IsDirty( ); }; struct CommitDoneEntry_t { - uint64_t winSeq; - uint64_t commitID; - uint64_t desiredPresentTime; - uint64_t earliestPresentTime; - uint64_t earliestLatchTime; - bool fifo; + uint64_t winSeq; + uint64_t commitID; + uint64_t desiredPresentTime; + uint64_t earliestPresentTime; + uint64_t earliestLatchTime; + bool fifo; }; struct CommitDoneList_t { - std::mutex listCommitsDoneLock; - std::vector< CommitDoneEntry_t > listCommitsDone; + std::mutex listCommitsDoneLock; + std::vector listCommitsDone; }; struct xwayland_ctx_t final : public gamescope::IWaitable { - gamescope_xwayland_server_t *xwayland_server; - Display *dpy; - - // Not used for most of steamcompmgr thread. Just to sync whenever - // wlserver wants it. - std::mutex list_mutex; - steamcompmgr_win_t *list; - int scr; - Window root; - XserverRegion allDamage; - bool clipChanged; - int root_height, root_width; - int xfixes_event, xfixes_error; - int damage_event, damage_error; - int composite_event, composite_error; - int render_event, render_error; - int xshape_event, xshape_error; - int composite_opcode; - int xinput_opcode, xinput_event, xinput_error; - Window ourWindow; - - focus_t focus; - Window currentKeyboardFocusWindow; - Window focusControlWindow; - - std::unique_ptr cursor; - - CommitDoneList_t doneCommits; - - double accum_x = 0.0; - double accum_y = 0.0; - - bool force_windows_fullscreen = false; - - bool bTouchPointerEmulation = false; - - std::vector< steamcompmgr_win_t* > GetPossibleFocusWindows(); - void DetermineAndApplyFocus( const std::vector< steamcompmgr_win_t* > &vecPossibleFocusWindows ); - - struct { - Atom steamAtom; - Atom gameAtom; - Atom overlayAtom; - Atom externalOverlayAtom; - Atom gamesRunningAtom; - Atom screenZoomAtom; - Atom screenScaleAtom; - Atom opacityAtom; - Atom winTypeAtom; - Atom winDesktopAtom; - Atom winDockAtom; - Atom winToolbarAtom; - Atom winMenuAtom; - Atom winUtilAtom; - Atom winSplashAtom; - Atom winDialogAtom; - Atom winNormalAtom; - Atom sizeHintsAtom; - Atom netWMStateFullscreenAtom; - Atom activeWindowAtom; - Atom netWMStateAtom; - Atom WMTransientForAtom; - Atom netWMStateHiddenAtom; - Atom netWMStateFocusedAtom; - Atom netWMStateSkipTaskbarAtom; - Atom netWMStateSkipPagerAtom; - Atom WLSurfaceIDAtom; - Atom WMStateAtom; - Atom steamInputFocusAtom; - Atom WMChangeStateAtom; - Atom steamTouchClickModeAtom; - Atom utf8StringAtom; - Atom netWMNameAtom; - Atom netWMIcon; - Atom netSystemTrayOpcodeAtom; - Atom steamStreamingClientAtom; - Atom steamStreamingClientVideoAtom; - Atom steamGamescopeVROverlayTarget; - Atom gamescopePid; - Atom gamescopeVROverlayForwarding; - Atom gamescopeFocusableAppsAtom; - Atom gamescopeFocusableWindowsAtom; - Atom gamescopeFocusedWindowAtom; - Atom gamescopeFocusedAppAtom; - Atom gamescopeFocusedAppGfxAtom; - Atom gamescopeCtrlAppIDAtom; - Atom gamescopeCtrlWindowAtom; - Atom gamescopeInputCounterAtom; - Atom gamescopeScreenShotAtom; - Atom gamescopeDebugScreenShotAtom; - - Atom gamescopeFocusDisplay; - Atom gamescopeMouseFocusDisplay; - Atom gamescopeKeyboardFocusDisplay; - - Atom gamescopeTuneableVBlankRedZone; - Atom gamescopeTuneableRateOfDecay; - - Atom gamescopeScalingFilter; - Atom gamescopeFSRSharpness; - Atom gamescopeSharpness; - - Atom gamescopeXWaylandModeControl; - - Atom gamescopeFPSLimit; - Atom gamescopeDynamicRefresh[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; - Atom gamescopeLowLatency; - - Atom gamescopeFSRFeedback; - - Atom gamescopeBlurMode; - Atom gamescopeBlurRadius; - Atom gamescopeBlurFadeDuration; - - Atom gamescopeCompositeForce; - Atom gamescopeCompositeDebug; - - Atom gamescopeAllowTearing; - Atom gamescopeDisplayForceInternal; - Atom gamescopeDisplayModeNudge; - - Atom gamescopeDisplayIsExternal; - Atom gamescopeDisplayModeListExternal; - - Atom gamescopeCursorVisibleFeedback; - - Atom gamescopeSteamMaxHeight; - - Atom gamescopeVRRCapable; - Atom gamescopeVRREnabled; - Atom gamescopeVRRInUse; - - Atom gamescopeNewScalingFilter; - Atom gamescopeNewScalingScaler; - - Atom gamescopeDisplayEdidPath; - - Atom gamescopeXwaylandServerId; - - Atom gamescopeDisplaySupportsHDR; - Atom gamescopeDisplayHDREnabled; - Atom gamescopeDebugForceHDR10Output; - Atom gamescopeDebugForceHDRSupport; - Atom gamescopeDebugHDRHeatmap; - Atom gamescopeDebugHDRHeatmap_MSWCG; - Atom gamescopeHDROutputFeedback; - Atom gamescopeSDROnHDRContentBrightness; - Atom gamescopeHDRInputGain; - Atom gamescopeSDRInputGain; - Atom gamescopeHDRItmEnable; - Atom gamescopeHDRItmSDRNits; - Atom gamescopeHDRItmTargetNits; - Atom gamescopeColorLookPQ; - Atom gamescopeColorLookG22; - Atom gamescopeColorOutputVirtualWhite; - Atom gamescopeHDRTonemapDisplayMetadata; - Atom gamescopeHDRTonemapSourceMetadata; - Atom gamescopeHDRTonemapOperator; - - Atom gamescopeForceWindowsFullscreen; - - Atom gamescopeColorLut3DOverride; - Atom gamescopeColorShaperLutOverride; - - Atom gamescopeColorSDRGamutWideness; - Atom gamescopeColorNightMode; // amount, hue, saturation - Atom gamescopeColorManagementDisable; - Atom gamescopeColorAppWantsHDRFeedback; - Atom gamescopeColorAppHDRMetadataFeedback; - Atom gamescopeColorSliderInUse; - Atom gamescopeColorChromaticAdaptationMode; - Atom gamescopeColorMuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; - Atom gamescopeColorMuraScale[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; - Atom gamescopeColorMuraCorrectionDisabled[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; - - Atom gamescopeCreateXWaylandServer; - Atom gamescopeCreateXWaylandServerFeedback; - Atom gamescopeDestroyXWaylandServer; - - Atom gamescopeReshadeEffect; - Atom gamescopeReshadeTechniqueIdx; - - Atom gamescopeDisplayRefreshRateFeedback; - Atom gamescopeDisplayDynamicRefreshBasedOnGamePresence; - - Atom gamescopeMainSteamVROverlay; - Atom steamosTouchPointerEmulation; + gamescope_xwayland_server_t *xwayland_server; + Display *dpy; + + // Not used for most of steamcompmgr thread. Just to sync whenever + // wlserver wants it. + std::mutex list_mutex; + steamcompmgr_win_t *list; + int scr; + Window root; + XserverRegion allDamage; + bool clipChanged; + int root_height, root_width; + int xfixes_event, xfixes_error; + int damage_event, damage_error; + int composite_event, composite_error; + int render_event, render_error; + int xshape_event, xshape_error; + int composite_opcode; + int xinput_opcode, xinput_event, xinput_error; + Window ourWindow; + + focus_t focus; + Window currentKeyboardFocusWindow; + Window focusControlWindow; + + std::unique_ptr cursor; + + CommitDoneList_t doneCommits; + + double accum_x = 0.0; + double accum_y = 0.0; + + bool force_windows_fullscreen = false; + + bool bTouchPointerEmulation = false; + + std::vector GetPossibleFocusWindows( ); + void DetermineAndApplyFocus( + const std::vector &vecPossibleFocusWindows ); + + struct + { + Atom steamAtom; + Atom gameAtom; + Atom overlayAtom; + Atom externalOverlayAtom; + Atom gamesRunningAtom; + Atom screenZoomAtom; + Atom screenScaleAtom; + Atom opacityAtom; + Atom winTypeAtom; + Atom winDesktopAtom; + Atom winDockAtom; + Atom winToolbarAtom; + Atom winMenuAtom; + Atom winUtilAtom; + Atom winSplashAtom; + Atom winDialogAtom; + Atom winNormalAtom; + Atom sizeHintsAtom; + Atom netWMStateFullscreenAtom; + Atom activeWindowAtom; + Atom netWMStateAtom; + Atom WMTransientForAtom; + Atom netWMStateHiddenAtom; + Atom netWMStateFocusedAtom; + Atom netWMStateSkipTaskbarAtom; + Atom netWMStateSkipPagerAtom; + Atom WLSurfaceIDAtom; + Atom WMStateAtom; + Atom steamInputFocusAtom; + Atom WMChangeStateAtom; + Atom steamTouchClickModeAtom; + Atom utf8StringAtom; + Atom netWMNameAtom; + Atom netWMIcon; + Atom netSystemTrayOpcodeAtom; + Atom steamStreamingClientAtom; + Atom steamStreamingClientVideoAtom; + Atom steamGamescopeVROverlayTarget; + Atom gamescopePid; + Atom gamescopeVROverlayForwarding; + Atom gamescopeFocusableAppsAtom; + Atom gamescopeFocusableWindowsAtom; + Atom gamescopeFocusedWindowAtom; + Atom gamescopeFocusedAppAtom; + Atom gamescopeFocusedAppGfxAtom; + Atom gamescopeCtrlAppIDAtom; + Atom gamescopeCtrlWindowAtom; + Atom gamescopeInputCounterAtom; + Atom gamescopeScreenShotAtom; + Atom gamescopeDebugScreenShotAtom; + + Atom gamescopeFocusDisplay; + Atom gamescopeMouseFocusDisplay; + Atom gamescopeKeyboardFocusDisplay; + + Atom gamescopeTuneableVBlankRedZone; + Atom gamescopeTuneableRateOfDecay; + + Atom gamescopeScalingFilter; + Atom gamescopeFSRSharpness; + Atom gamescopeSharpness; + + Atom gamescopeXWaylandModeControl; + + Atom gamescopeFPSLimit; + Atom gamescopeDynamicRefresh[ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ]; + Atom gamescopeLowLatency; + + Atom gamescopeFSRFeedback; + + Atom gamescopeBlurMode; + Atom gamescopeBlurRadius; + Atom gamescopeBlurFadeDuration; + + Atom gamescopeCompositeForce; + Atom gamescopeCompositeDebug; + + Atom gamescopeAllowTearing; + Atom gamescopeDisplayForceInternal; + Atom gamescopeDisplayModeNudge; + + Atom gamescopeDisplayIsExternal; + Atom gamescopeDisplayModeListExternal; + + Atom gamescopeCursorVisibleFeedback; + + Atom gamescopeSteamMaxHeight; + + Atom gamescopeVRRCapable; + Atom gamescopeVRREnabled; + Atom gamescopeVRRInUse; + + Atom gamescopeNewScalingFilter; + Atom gamescopeNewScalingScaler; + + Atom gamescopeDisplayEdidPath; + + Atom gamescopeXwaylandServerId; + + Atom gamescopeDisplaySupportsHDR; + Atom gamescopeDisplayHDREnabled; + Atom gamescopeDebugForceHDR10Output; + Atom gamescopeDebugForceHDRSupport; + Atom gamescopeDebugHDRHeatmap; + Atom gamescopeDebugHDRHeatmap_MSWCG; + Atom gamescopeHDROutputFeedback; + Atom gamescopeSDROnHDRContentBrightness; + Atom gamescopeHDRInputGain; + Atom gamescopeSDRInputGain; + Atom gamescopeHDRItmEnable; + Atom gamescopeHDRItmSDRNits; + Atom gamescopeHDRItmTargetNits; + Atom gamescopeColorLookPQ; + Atom gamescopeColorLookG22; + Atom gamescopeColorOutputVirtualWhite; + Atom gamescopeHDRTonemapDisplayMetadata; + Atom gamescopeHDRTonemapSourceMetadata; + Atom gamescopeHDRTonemapOperator; + + Atom gamescopeForceWindowsFullscreen; + + Atom gamescopeColorLut3DOverride; + Atom gamescopeColorShaperLutOverride; + + Atom gamescopeColorSDRGamutWideness; + Atom gamescopeColorNightMode; // amount, hue, saturation + Atom gamescopeColorManagementDisable; + Atom gamescopeColorAppWantsHDRFeedback; + Atom gamescopeColorAppHDRMetadataFeedback; + Atom gamescopeColorSliderInUse; + Atom gamescopeColorChromaticAdaptationMode; + Atom gamescopeColorMuraCorrectionImage + [ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ]; + Atom gamescopeColorMuraScale[ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ]; + Atom gamescopeColorMuraCorrectionDisabled + [ gamescope::GAMESCOPE_SCREEN_TYPE_COUNT ]; + + Atom gamescopeCreateXWaylandServer; + Atom gamescopeCreateXWaylandServerFeedback; + Atom gamescopeDestroyXWaylandServer; + + Atom gamescopeReshadeEffect; + Atom gamescopeReshadeTechniqueIdx; + + Atom gamescopeDisplayRefreshRateFeedback; + Atom gamescopeDisplayDynamicRefreshBasedOnGamePresence; + + Atom gamescopeMainSteamVROverlay; + Atom steamosTouchPointerEmulation; + + Atom wineHwndStyle; + Atom wineHwndStyleEx; + + Atom clipboard; + Atom primarySelection; + Atom targets; + + Atom wm_protocols; + Atom wm_delete_window; + } atoms; - Atom wineHwndStyle; - Atom wineHwndStyleEx; + bool HasQueuedEvents( ); - Atom clipboard; - Atom primarySelection; - Atom targets; + void Dispatch( ); - Atom wm_protocols; - Atom wm_delete_window; - } atoms; - - bool HasQueuedEvents(); + int GetFD( ) final { return XConnectionNumber( dpy ); } - void Dispatch(); - - int GetFD() final - { - return XConnectionNumber( dpy ); - } + void OnPollIn( ) final { Dispatch( ); } - void OnPollIn() final - { - Dispatch(); - } - - void OnPollHangUp() final - { - xwm_log.errorf( "XWayland server hung up! This is fatal. Aborting..." ); - abort(); - } + void OnPollHangUp( ) final + { + xwm_log.errorf( "XWayland server hung up! This is fatal. Aborting..." ); + abort( ); + } }; diff --git a/subprojects/.gitignore b/subprojects/.gitignore index aa23451096..c265132933 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -1,3 +1,3 @@ -glm-* -packagecache -stb +/*/ +!/packagefiles/ +/.wraplock diff --git a/subprojects/glm.wrap b/subprojects/glm.wrap index fc709178f2..7fbb7b6800 100644 --- a/subprojects/glm.wrap +++ b/subprojects/glm.wrap @@ -1,8 +1,9 @@ [wrap-git] directory = glm - url = https://github.com/g-truc/glm.git revision = 0af55ccecd98d4e5a8d1fad7de25ba429d60e863 depth = 1 - patch_directory = glm + +[provide] +glm = glm_dep diff --git a/subprojects/libdisplay-info b/subprojects/libdisplay-info deleted file mode 160000 index 47a5590e9c..0000000000 --- a/subprojects/libdisplay-info +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 47a5590e9c4eb35d67651b8c05a55f1a48259329 diff --git a/subprojects/libdisplay-info.wrap b/subprojects/libdisplay-info.wrap new file mode 100644 index 0000000000..73c491419f --- /dev/null +++ b/subprojects/libdisplay-info.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/emersion/libdisplay-info.git +revision = 47a5590e9c4eb35d67651b8c05a55f1a48259329 +depth = 1 + +[provide] +libdisplay-info = di_dep diff --git a/subprojects/libliftoff b/subprojects/libliftoff deleted file mode 160000 index 8b08dc1c14..0000000000 --- a/subprojects/libliftoff +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8b08dc1c14fd019cc90ddabe34ad16596b0691f4 diff --git a/subprojects/libliftoff.wrap b/subprojects/libliftoff.wrap new file mode 100644 index 0000000000..0cb00b997d --- /dev/null +++ b/subprojects/libliftoff.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/emersion/libliftoff.git +revision = 8b08dc1c14fd019cc90ddabe34ad16596b0691f4 +depth = 1 + +[provide] +libliftoff = liftoff diff --git a/subprojects/openvr b/subprojects/openvr deleted file mode 160000 index ff87f683f4..0000000000 --- a/subprojects/openvr +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ff87f683f41fe26cc9353dd9d9d7028357fd8e1a diff --git a/subprojects/openvr.wrap b/subprojects/openvr.wrap new file mode 100644 index 0000000000..b095230e65 --- /dev/null +++ b/subprojects/openvr.wrap @@ -0,0 +1,8 @@ +[wrap-git] +url = https://github.com/ValveSoftware/openvr.git +revision = ff87f683f41fe26cc9353dd9d9d7028357fd8e1a +depth = 1 +patch_directory = openvr + +[provide] +openvr = openvr_api diff --git a/subprojects/packagefiles/openvr/meson.build b/subprojects/packagefiles/openvr/meson.build new file mode 100644 index 0000000000..1380ba696e --- /dev/null +++ b/subprojects/packagefiles/openvr/meson.build @@ -0,0 +1,58 @@ +project('openvr', 'cpp', version : '2.15.6', license : 'BSD-3-Clause') + +cmake_build_dir = meson.current_build_dir() / 'cmake_build' +cppc = meson.get_compiler('cpp') + +run_command( + 'cmake', + '-B', cmake_build_dir, + '-S', meson.current_source_dir(), + '-DCMAKE_BUILD_TYPE=Release', + '-DUSE_LIBCXX=false', + '-DCMAKE_POLICY_VERSION_MINIMUM=3.5', + '-DBUILD_SHARED=false', + '-DCMAKE_POSITION_INDEPENDENT_CODE=ON', + check : true +) + +run_command( + 'cmake', + '--build', cmake_build_dir, + check : true +) + +system = host_machine.system() +cpu_family = host_machine.cpu_family() + +if system == 'windows' + platform_arch = cpu_family == 'x86_64' ? 'win64' : 'win32' +elif system == 'linux' + if cpu_family == 'aarch64' + platform_arch = 'linuxarm64' + elif cpu_family == 'x86_64' + platform_arch = 'linux64' + else + platform_arch = 'linux32' + endif +elif system == 'darwin' + platform_arch = cpu_family == 'x86_64' ? 'osx64' : 'osx32' +elif system == 'android' + if cpu_family == 'aarch64' + platform_arch = 'androidarm64' + else + error('Unsupported Android architecture') + endif +else + error('Unsupported platform: ' + system) +endif + +openvr_lib = cppc.find_library( + 'openvr_api', + dirs : meson.current_source_dir() / 'bin' / platform_arch, + required : true +) +openvr_dep = declare_dependency( + dependencies : [openvr_lib], + include_directories : include_directories('headers'), +) +meson.override_dependency('openvr', openvr_dep) diff --git a/subprojects/stb.wrap b/subprojects/stb.wrap index 0083f4f76f..fd11eb41d6 100644 --- a/subprojects/stb.wrap +++ b/subprojects/stb.wrap @@ -1,8 +1,9 @@ [wrap-git] directory = stb - url = https://github.com/nothings/stb.git revision = 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 depth = 1 - patch_directory = stb + +[provide] +stb = stb_dep diff --git a/subprojects/v4l-utils.wrap b/subprojects/v4l-utils.wrap new file mode 100644 index 0000000000..469df77457 --- /dev/null +++ b/subprojects/v4l-utils.wrap @@ -0,0 +1,2 @@ +[wrap-redirect] +filename = libdisplay-info/subprojects/v4l-utils.wrap diff --git a/subprojects/vkroots b/subprojects/vkroots deleted file mode 160000 index 5106d8a0df..0000000000 --- a/subprojects/vkroots +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5106d8a0df95de66cc58dc1ea37e69c99afc9540 diff --git a/subprojects/vkroots.wrap b/subprojects/vkroots.wrap new file mode 100644 index 0000000000..338f477f1a --- /dev/null +++ b/subprojects/vkroots.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://github.com/misyltoad/vkroots.git +revision = 5106d8a0df95de66cc58dc1ea37e69c99afc9540 +depth = 1 + +[provide] +vkroots = vkroots_dep diff --git a/subprojects/wlroots b/subprojects/wlroots deleted file mode 160000 index c08d99437e..0000000000 --- a/subprojects/wlroots +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c08d99437ec8bb56a703f04ad1ef199502c62d10 diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap new file mode 100644 index 0000000000..1fdcce90e2 --- /dev/null +++ b/subprojects/wlroots.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://github.com/misyltoad/wlroots.git +revision = c08d99437ec8bb56a703f04ad1ef199502c62d10 +depth = 1 + +[provide] +wlroots = wlroots_dep