From 0bbd9a53d9fe0247d6c96a2c5f0728fbdba3d8a5 Mon Sep 17 00:00:00 2001 From: HikariKnight <2557889+HikariKnight@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:18:25 +0100 Subject: [PATCH 01/12] feat(deck): add tdpfix for cards that has 15W TDP on boot due to writable sysfs on boot (#892) * fix: correctly check for valve-hardware using hardware helper * feat(deck): add tdpfix for cards that has their sysfs writable on boot --- Containerfile | 1 + spec_files/jupiter-hw-support/priv-write.patch | 10 +++++----- .../lib/systemd/system/bazzite-tdpfix.service | 11 +++++++++++ .../deck/shared/usr/libexec/bazzite-tdpfix | 17 +++++++++++++++++ 4 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 system_files/deck/shared/usr/lib/systemd/system/bazzite-tdpfix.service create mode 100755 system_files/deck/shared/usr/libexec/bazzite-tdpfix diff --git a/Containerfile b/Containerfile index 717dc41e91..5b7997b7e3 100644 --- a/Containerfile +++ b/Containerfile @@ -762,6 +762,7 @@ RUN /tmp/image-info.sh && \ systemctl enable cec-onboot.service && \ systemctl enable cec-onpoweroff.service && \ systemctl enable cec-onsleep.service && \ + systemctl enable bazzite-tdpfix.service && \ systemctl --global enable steam-web-debug-portforward.service && \ systemctl --global disable sdgyrodsu.service && \ systemctl disable input-remapper.service && \ diff --git a/spec_files/jupiter-hw-support/priv-write.patch b/spec_files/jupiter-hw-support/priv-write.patch index 3951ef9f8f..d0d66ebcdd 100644 --- a/spec_files/jupiter-hw-support/priv-write.patch +++ b/spec_files/jupiter-hw-support/priv-write.patch @@ -12,7 +12,7 @@ index e307823..45946ed 100755 if [[ $EUID -ne 0 ]]; then exec pkexec --disable-internal-agent "$0" "$@" -@@ -12,21 +15,21 @@ WRITE_VALUE="$2" +@@ -12,21 +15,21 @@ function CommitWrite() { @@ -37,12 +37,12 @@ index e307823..45946ed 100755 exit 1 } -@@ -40,15 +43,35 @@ if [[ "$WRITE_PATH" == /dev/drm_dp_aux0 ]]; then +@@ -40,15 +43,35 @@ fi if [[ "$WRITE_PATH" == /sys/class/drm/card*/device/power_dpm_force_performance_level ]]; then - CommitWrite -+ if [[ /usr/libexec/hardware/valve-hardware || "$ENABLE_HARDWARE_CONTROL_ON_NON_DECK_HARDWARE" = 1 ]]; then ++ if /usr/libexec/hardware/valve-hardware || [[ "$ENABLE_HARDWARE_CONTROL_ON_NON_DECK_HARDWARE" = 1 ]]; then + for i in $(ls /sys/class/drm/card*/device/power_dpm_force_performance_level) + do + WRITE_PATH="$i" @@ -55,7 +55,7 @@ index e307823..45946ed 100755 if [[ "$WRITE_PATH" == /sys/class/drm/card*/device/pp_od_clk_voltage ]]; then - CommitWrite -+ if [[ /usr/libexec/hardware/valve-hardware || "$ENABLE_HARDWARE_CONTROL_ON_NON_DECK_HARDWARE" = 1 ]]; then ++ if /usr/libexec/hardware/valve-hardware || [[ "$ENABLE_HARDWARE_CONTROL_ON_NON_DECK_HARDWARE" = 1 ]]; then + for i in $(ls /sys/class/drm/card*/device/pp_od_clk_voltage) + do + WRITE_PATH="$i" @@ -68,7 +68,7 @@ index e307823..45946ed 100755 if [[ "$WRITE_PATH" == /sys/class/hwmon/hwmon*/power*_cap ]]; then - CommitWrite -+ if [[ /usr/libexec/hardware/valve-hardware || "$ENABLE_HARDWARE_CONTROL_ON_NON_DECK_HARDWARE" = 1 ]]; then ++ if /usr/libexec/hardware/valve-hardware || [[ "$ENABLE_HARDWARE_CONTROL_ON_NON_DECK_HARDWARE" = 1 ]]; then + CommitWrite + else + echo "commit: Skipped $WRITE_VALUE -> $WRITE_PATH - see /etc/default/steam-hardware-control" | systemd-cat -t p-steamos-priv-write -p warning diff --git a/system_files/deck/shared/usr/lib/systemd/system/bazzite-tdpfix.service b/system_files/deck/shared/usr/lib/systemd/system/bazzite-tdpfix.service new file mode 100644 index 0000000000..f1dfe5022a --- /dev/null +++ b/system_files/deck/shared/usr/lib/systemd/system/bazzite-tdpfix.service @@ -0,0 +1,11 @@ +[Unit] +Description=Resets power1_cap permissions to 644 if it is 666 at boot +After=multi-user.target rc-local.service systemd-user-sessions.service +Wants=modprobe@amdgpu.service + +[Service] +Type=oneshot +ExecStart=/usr/libexec/bazzite-tdpfix + +[Install] +WantedBy=multi-user.target diff --git a/system_files/deck/shared/usr/libexec/bazzite-tdpfix b/system_files/deck/shared/usr/libexec/bazzite-tdpfix new file mode 100755 index 0000000000..0fe6cf1385 --- /dev/null +++ b/system_files/deck/shared/usr/libexec/bazzite-tdpfix @@ -0,0 +1,17 @@ +#!/usr/bin/bash +# This is a workaround for cards that somehow has their sysfs power1_cap permission set to 666 on boot +# Which in turn makes steam directly set the TDP to 15W +POWER_CAP_PATH=$(find /sys/class/hwmon/*/ -name "power1_cap") + +# If the permissions are set to writable +if [ "$(stat -c %a "$POWER_CAP_PATH")" == "666" ]; then + chmod 644 "$POWER_CAP_PATH" + echo "fix: Permissions reset to 644 on $POWER_CAP_PATH" | systemd-cat -t bazzite-tdpfix -p warning +fi + +# If TDP is 15W and default TDP is higher than 45W, set card to use default TDP +# This will then be handled by firmware or software afterwards as this is set once +if [ "$(cat "$POWER_CAP_PATH")" == "15000000" ] && [ "$(cat "${POWER_CAP_PATH}_default")" -gt "45000000" ]; then + "$(cat "${POWER_CAP_PATH}_default")" > "$POWER_CAP_PATH" + echo "fix: TDP reset to default on $POWER_CAP_PATH" | systemd-cat -t bazzite-tdpfix -p warning +fi From 5aada39077bf83f67a434572242216bd5162efe2 Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Mon, 18 Mar 2024 09:15:52 -0700 Subject: [PATCH 02/12] chore: Add F40 gamescope spec file w/ needed HDR patch --- .../gamescope/{ => 39}/add_720p_var.patch | 0 spec_files/gamescope/{ => 39}/chimeraos.patch | 0 spec_files/gamescope/{ => 39}/crashfix.patch | 0 spec_files/gamescope/{ => 39}/gamescope.spec | 0 spec_files/gamescope/{ => 39}/legion_go.patch | 0 spec_files/gamescope/{ => 39}/stb.pc | 0 .../{ => 39}/touch_gestures_env.patch | 0 spec_files/gamescope/40/1149.patch | 544 ++++++++++ spec_files/gamescope/40/add_720p_var.patch | 35 + spec_files/gamescope/40/chimeraos.patch | 974 ++++++++++++++++++ spec_files/gamescope/40/crashfix.patch | 57 + spec_files/gamescope/40/gamescope.spec | 112 ++ spec_files/gamescope/40/legion_go.patch | 30 + spec_files/gamescope/40/stb.pc | 7 + .../gamescope/40/touch_gestures_env.patch | 77 ++ 15 files changed, 1836 insertions(+) rename spec_files/gamescope/{ => 39}/add_720p_var.patch (100%) rename spec_files/gamescope/{ => 39}/chimeraos.patch (100%) rename spec_files/gamescope/{ => 39}/crashfix.patch (100%) rename spec_files/gamescope/{ => 39}/gamescope.spec (100%) rename spec_files/gamescope/{ => 39}/legion_go.patch (100%) rename spec_files/gamescope/{ => 39}/stb.pc (100%) rename spec_files/gamescope/{ => 39}/touch_gestures_env.patch (100%) create mode 100644 spec_files/gamescope/40/1149.patch create mode 100644 spec_files/gamescope/40/add_720p_var.patch create mode 100644 spec_files/gamescope/40/chimeraos.patch create mode 100644 spec_files/gamescope/40/crashfix.patch create mode 100644 spec_files/gamescope/40/gamescope.spec create mode 100644 spec_files/gamescope/40/legion_go.patch create mode 100644 spec_files/gamescope/40/stb.pc create mode 100644 spec_files/gamescope/40/touch_gestures_env.patch diff --git a/spec_files/gamescope/add_720p_var.patch b/spec_files/gamescope/39/add_720p_var.patch similarity index 100% rename from spec_files/gamescope/add_720p_var.patch rename to spec_files/gamescope/39/add_720p_var.patch diff --git a/spec_files/gamescope/chimeraos.patch b/spec_files/gamescope/39/chimeraos.patch similarity index 100% rename from spec_files/gamescope/chimeraos.patch rename to spec_files/gamescope/39/chimeraos.patch diff --git a/spec_files/gamescope/crashfix.patch b/spec_files/gamescope/39/crashfix.patch similarity index 100% rename from spec_files/gamescope/crashfix.patch rename to spec_files/gamescope/39/crashfix.patch diff --git a/spec_files/gamescope/gamescope.spec b/spec_files/gamescope/39/gamescope.spec similarity index 100% rename from spec_files/gamescope/gamescope.spec rename to spec_files/gamescope/39/gamescope.spec diff --git a/spec_files/gamescope/legion_go.patch b/spec_files/gamescope/39/legion_go.patch similarity index 100% rename from spec_files/gamescope/legion_go.patch rename to spec_files/gamescope/39/legion_go.patch diff --git a/spec_files/gamescope/stb.pc b/spec_files/gamescope/39/stb.pc similarity index 100% rename from spec_files/gamescope/stb.pc rename to spec_files/gamescope/39/stb.pc diff --git a/spec_files/gamescope/touch_gestures_env.patch b/spec_files/gamescope/39/touch_gestures_env.patch similarity index 100% rename from spec_files/gamescope/touch_gestures_env.patch rename to spec_files/gamescope/39/touch_gestures_env.patch diff --git a/spec_files/gamescope/40/1149.patch b/spec_files/gamescope/40/1149.patch new file mode 100644 index 0000000000..1fc9f06036 --- /dev/null +++ b/spec_files/gamescope/40/1149.patch @@ -0,0 +1,544 @@ +From 85432af61b779a02b636fdc29d98aba5e89fcff7 Mon Sep 17 00:00:00 2001 +From: Andrew O'Neil +Date: Sat, 24 Feb 2024 11:40:08 +1100 +Subject: [PATCH] Update AMD color management for Linux 6.8 + +--- + src/drm.cpp | 258 ++++++++++++++++++++++++++-------------------- + src/drm_include.h | 76 +++++++------- + 2 files changed, 188 insertions(+), 146 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 9420ccbe4..29b8d06f5 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -148,15 +148,15 @@ namespace gamescope + std::optional rotation; + std::optional COLOR_ENCODING; + std::optional COLOR_RANGE; +- std::optional VALVE1_PLANE_DEGAMMA_TF; +- std::optional VALVE1_PLANE_DEGAMMA_LUT; +- std::optional VALVE1_PLANE_CTM; +- std::optional VALVE1_PLANE_HDR_MULT; +- std::optional VALVE1_PLANE_SHAPER_LUT; +- std::optional VALVE1_PLANE_SHAPER_TF; +- std::optional VALVE1_PLANE_LUT3D; +- std::optional VALVE1_PLANE_BLEND_TF; +- std::optional VALVE1_PLANE_BLEND_LUT; ++ 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; } +@@ -187,7 +187,7 @@ namespace gamescope + std::optional CTM; + std::optional VRR_ENABLED; + std::optional OUT_FENCE_PTR; +- std::optional VALVE1_CRTC_REGAMMA_TF; ++ std::optional AMD_CRTC_REGAMMA_TF; + std::optional DUMMY_END; + }; + CRTCProperties &GetProperties() { return m_Props; } +@@ -401,7 +401,7 @@ struct drm_t { + uint32_t color_mgmt_serial; + std::shared_ptr lut3d_id[ EOTF_Count ]; + std::shared_ptr shaperlut_id[ EOTF_Count ]; +- drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; ++ amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } current, pending; + + /* FBs in the atomic request, but not yet submitted to KMS */ +@@ -1287,8 +1287,8 @@ void finish_drm(struct drm_t *drm) + if ( pCRTC->GetProperties().OUT_FENCE_PTR ) + pCRTC->GetProperties().OUT_FENCE_PTR->SetPendingValue( req, 0, true ); + +- if ( pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) +- pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->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< gamescope::CDRMPlane > &pPlane : drm->planes ) +@@ -1313,32 +1313,32 @@ void finish_drm(struct drm_t *drm) + //if ( pPlane->GetProperties().zpos ) + // pPlane->GetProperties().zpos->SetPendingValue( req, , true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_TF ) +- pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); ++ if ( pPlane->GetProperties().AMD_PLANE_DEGAMMA_TF ) ++ pPlane->GetProperties().AMD_PLANE_DEGAMMA_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_LUT ) +- pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_LUT->SetPendingValue( req, 0, true ); ++ if ( pPlane->GetProperties().AMD_PLANE_DEGAMMA_LUT ) ++ pPlane->GetProperties().AMD_PLANE_DEGAMMA_LUT->SetPendingValue( req, 0, true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_CTM ) +- pPlane->GetProperties().VALVE1_PLANE_CTM->SetPendingValue( req, 0, true ); ++ if ( pPlane->GetProperties().AMD_PLANE_CTM ) ++ pPlane->GetProperties().AMD_PLANE_CTM->SetPendingValue( req, 0, true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_HDR_MULT ) +- pPlane->GetProperties().VALVE1_PLANE_HDR_MULT->SetPendingValue( req, 0x100000000ULL, true ); ++ if ( pPlane->GetProperties().AMD_PLANE_HDR_MULT ) ++ pPlane->GetProperties().AMD_PLANE_HDR_MULT->SetPendingValue( req, 0x100000000ULL, true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_SHAPER_TF ) +- pPlane->GetProperties().VALVE1_PLANE_SHAPER_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); ++ if ( pPlane->GetProperties().AMD_PLANE_SHAPER_TF ) ++ pPlane->GetProperties().AMD_PLANE_SHAPER_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_SHAPER_LUT ) +- pPlane->GetProperties().VALVE1_PLANE_SHAPER_LUT->SetPendingValue( req, 0, true ); ++ if ( pPlane->GetProperties().AMD_PLANE_SHAPER_LUT ) ++ pPlane->GetProperties().AMD_PLANE_SHAPER_LUT->SetPendingValue( req, 0, true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_LUT3D ) +- pPlane->GetProperties().VALVE1_PLANE_LUT3D->SetPendingValue( req, 0, true ); ++ if ( pPlane->GetProperties().AMD_PLANE_LUT3D ) ++ pPlane->GetProperties().AMD_PLANE_LUT3D->SetPendingValue( req, 0, true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_BLEND_TF ) +- pPlane->GetProperties().VALVE1_PLANE_BLEND_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); ++ if ( pPlane->GetProperties().AMD_PLANE_BLEND_TF ) ++ pPlane->GetProperties().AMD_PLANE_BLEND_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); + +- if ( pPlane->GetProperties().VALVE1_PLANE_BLEND_LUT ) +- pPlane->GetProperties().VALVE1_PLANE_BLEND_LUT->SetPendingValue( req, 0, 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 +@@ -1637,37 +1637,73 @@ struct LiftoffStateCacheEntryKasher + + std::unordered_set g_LiftoffStateCache; + +-static inline drm_valve1_transfer_function colorspace_to_plane_degamma_tf(GamescopeAppTextureColorspace colorspace) ++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 DRM_VALVE1_TRANSFER_FUNCTION_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 DRM_VALVE1_TRANSFER_FUNCTION_LINEAR; ++ return AMDGPU_TRANSFER_FUNCTION_IDENTITY; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: +- return DRM_VALVE1_TRANSFER_FUNCTION_PQ; ++ return AMDGPU_TRANSFER_FUNCTION_PQ_EOTF; + } + } + +-static inline drm_valve1_transfer_function colorspace_to_plane_shaper_tf(GamescopeAppTextureColorspace colorspace) ++static inline amdgpu_transfer_function colorspace_to_plane_shaper_tf(GamescopeAppTextureColorspace colorspace) + { + switch ( colorspace ) + { + default: + case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: +- return DRM_VALVE1_TRANSFER_FUNCTION_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 DRM_VALVE1_TRANSFER_FUNCTION_PQ; ++ return AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF; + case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: +- return DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; ++ 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; + } + } + +@@ -1862,33 +1898,33 @@ namespace gamescope + 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.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.VALVE1_PLANE_DEGAMMA_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_DEGAMMA_TF", this, *rawProperties ); +- m_Props.VALVE1_PLANE_DEGAMMA_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_DEGAMMA_LUT", this, *rawProperties ); +- m_Props.VALVE1_PLANE_CTM = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_CTM", this, *rawProperties ); +- m_Props.VALVE1_PLANE_HDR_MULT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_HDR_MULT", this, *rawProperties ); +- m_Props.VALVE1_PLANE_SHAPER_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_SHAPER_LUT", this, *rawProperties ); +- m_Props.VALVE1_PLANE_SHAPER_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_SHAPER_TF", this, *rawProperties ); +- m_Props.VALVE1_PLANE_LUT3D = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_LUT3D", this, *rawProperties ); +- m_Props.VALVE1_PLANE_BLEND_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_BLEND_TF", this, *rawProperties ); +- m_Props.VALVE1_PLANE_BLEND_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_BLEND_LUT", this, *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.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 ); + } + } + +@@ -1908,14 +1944,14 @@ namespace gamescope + 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.VALVE1_CRTC_REGAMMA_TF = CDRMAtomicProperty::Instantiate( "VALVE1_CRTC_REGAMMA_TF", this, *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 ); + } + } + +@@ -2376,8 +2412,8 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + + if ( drm_supports_color_mgmt( drm ) ) + { +- drm_valve1_transfer_function degamma_tf = colorspace_to_plane_degamma_tf( entry.layerState[i].colorspace ); +- drm_valve1_transfer_function shaper_tf = colorspace_to_plane_shaper_tf( entry.layerState[i].colorspace ); ++ 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 ( entry.layerState[i].ycbcr ) + { +@@ -2389,27 +2425,27 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + // + // Doing LINEAR/DEFAULT here introduces banding so... this is the best way. + // (sRGB DEGAMMA does NOT work on YUV planes!) +- degamma_tf = DRM_VALVE1_TRANSFER_FUNCTION_BT709; +- shaper_tf = DRM_VALVE1_TRANSFER_FUNCTION_BT709; ++ degamma_tf = AMDGPU_TRANSFER_FUNCTION_BT709_OETF; ++ shaper_tf = AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF; + } + + if (!g_bDisableDegamma) +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", degamma_tf ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", degamma_tf ); + else +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", 0 ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", 0 ); + + if ( !g_bDisableShaperAnd3DLUT ) + { +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", shaper_tf ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); ++ 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 ], "VALVE1_PLANE_SHAPER_LUT", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); ++ 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 ); + } + } + } +@@ -2417,25 +2453,25 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + { + if ( drm_supports_color_mgmt( drm ) ) + { +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); ++ 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 (!g_bDisableBlendTF && !bSinglePlane) +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", drm->pending.output_tf ); ++ 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 ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT ); + + if (frameInfo->layers[i].ctm != nullptr) +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", frameInfo->layers[i].ctm->GetBlobValue() ); ++ 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 ], "VALVE1_PLANE_CTM", 0 ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 ); + } + } + else +@@ -2447,12 +2483,12 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + + if ( drm_supports_color_mgmt( drm ) ) + { +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); ++ 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 ); + } + } + } +@@ -2533,7 +2569,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + drm->needs_modeset = true; + } + +- uint32_t uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; ++ drm_colorspace uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; + + const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; + gamescope::BackendBlob *pHDRMetadata = nullptr; +@@ -2571,17 +2607,17 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + if ( !g_bDisableRegamma && !bSinglePlane ) + { + drm->pending.output_tf = g_bOutputHDREnabled +- ? DRM_VALVE1_TRANSFER_FUNCTION_PQ +- : DRM_VALVE1_TRANSFER_FUNCTION_SRGB; ++ ? AMDGPU_TRANSFER_FUNCTION_PQ_EOTF ++ : AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; + } + else + { +- drm->pending.output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; ++ drm->pending.output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } + } + else + { +- drm->pending.output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; ++ drm->pending.output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } + + uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; +@@ -2644,8 +2680,8 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + if ( pCRTC->GetProperties().OUT_FENCE_PTR ) + pCRTC->GetProperties().OUT_FENCE_PTR->SetPendingValue( drm->req, 0, bForceInRequest ); + +- if ( pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) +- pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->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 ) +@@ -2679,8 +2715,8 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + + if ( drm->pCRTC ) + { +- if ( drm->pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) +- drm->pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->SetPendingValue( drm->req, drm->pending.output_tf, bForceInRequest ); ++ if ( drm->pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF ) ++ drm->pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF->SetPendingValue( drm->req, inverse_tf(drm->pending.output_tf), bForceInRequest ); + } + + drm->flags = flags; +@@ -2995,7 +3031,7 @@ bool drm_supports_color_mgmt(struct drm_t *drm) + if ( !drm->pPrimaryPlane ) + return false; + +- return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); ++ return drm->pPrimaryPlane->GetProperties().AMD_PLANE_CTM.has_value(); + } + + std::span drm_get_valid_refresh_rates( struct drm_t *drm ) +diff --git a/src/drm_include.h b/src/drm_include.h +index 500c3040a..d0ed3ce9e 100644 +--- a/src/drm_include.h ++++ b/src/drm_include.h +@@ -28,19 +28,22 @@ enum drm_color_range { + DRM_COLOR_RANGE_MAX, + }; + +-enum drm_valve1_transfer_function { +- DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, +- +- DRM_VALVE1_TRANSFER_FUNCTION_SRGB, +- DRM_VALVE1_TRANSFER_FUNCTION_BT709, +- DRM_VALVE1_TRANSFER_FUNCTION_PQ, +- DRM_VALVE1_TRANSFER_FUNCTION_LINEAR, +- DRM_VALVE1_TRANSFER_FUNCTION_UNITY, +- DRM_VALVE1_TRANSFER_FUNCTION_HLG, +- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA22, +- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA24, +- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA26, +- DRM_VALVE1_TRANSFER_FUNCTION_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 drm_panel_orientation { +@@ -51,28 +54,31 @@ enum drm_panel_orientation { + DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, + }; + +-/* For Default case, driver will set the colorspace */ +-#define DRM_MODE_COLORIMETRY_DEFAULT 0 +-/* CEA 861 Normal Colorimetry options */ +-#define DRM_MODE_COLORIMETRY_NO_DATA 0 +-#define DRM_MODE_COLORIMETRY_SMPTE_170M_YCC 1 +-#define DRM_MODE_COLORIMETRY_BT709_YCC 2 +-/* CEA 861 Extended Colorimetry Options */ +-#define DRM_MODE_COLORIMETRY_XVYCC_601 3 +-#define DRM_MODE_COLORIMETRY_XVYCC_709 4 +-#define DRM_MODE_COLORIMETRY_SYCC_601 5 +-#define DRM_MODE_COLORIMETRY_OPYCC_601 6 +-#define DRM_MODE_COLORIMETRY_OPRGB 7 +-#define DRM_MODE_COLORIMETRY_BT2020_CYCC 8 +-#define DRM_MODE_COLORIMETRY_BT2020_RGB 9 +-#define DRM_MODE_COLORIMETRY_BT2020_YCC 10 +-/* Additional Colorimetry extension added as part of CTA 861.G */ +-#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 11 +-#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER 12 +-/* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format */ +-#define DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED 13 +-#define DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT 14 +-#define DRM_MODE_COLORIMETRY_BT601_YCC 15 ++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 diff --git a/spec_files/gamescope/40/add_720p_var.patch b/spec_files/gamescope/40/add_720p_var.patch new file mode 100644 index 0000000000..05ff1c72a4 --- /dev/null +++ b/spec_files/gamescope/40/add_720p_var.patch @@ -0,0 +1,35 @@ +From 19c6635d5e20dd429cb23b4a7c728afa306fae0a Mon Sep 17 00:00:00 2001 +From: Sterophonick +Date: Sat, 10 Feb 2024 22:00:36 -0700 +Subject: [PATCH] steamcompmgr: add env var to enable/disable 720p restriction + +--- + src/steamcompmgr.cpp | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 00c00e9..795898c 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -137,6 +137,9 @@ extern float g_flInternalDisplayBrightnessNits; + extern float g_flHDRItmSdrNits; + extern float g_flHDRItmTargetNits; + ++// define env_to_bool to point to the function in drm: remove in later patches pl0x ++extern bool env_to_bool(const char *env); ++ + uint64_t g_lastWinSeq = 0; + + static std::shared_ptr s_scRGB709To2020Matrix; +@@ -5657,7 +5660,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + int width = xwayland_mode_ctl[ 1 ]; + int height = xwayland_mode_ctl[ 2 ]; + +- if ( g_nOutputWidth != 1280 && width == 1280 ) ++ if ( g_nOutputWidth != 1280 && width == 1280 && !env_to_bool(getenv("GAMESCOPE_ENABLE_720P_RESTRICT")) ) + { + width = g_nOutputWidth; + height = g_nOutputHeight; +-- +2.43.0 + diff --git a/spec_files/gamescope/40/chimeraos.patch b/spec_files/gamescope/40/chimeraos.patch new file mode 100644 index 0000000000..a2018692aa --- /dev/null +++ b/spec_files/gamescope/40/chimeraos.patch @@ -0,0 +1,974 @@ +From 4cafbd696c342c1f45eea6242dcaadd26e8e4a3d Mon Sep 17 00:00:00 2001 +From: Matthew Anderson +Date: Fri, 6 Oct 2023 17:22:41 -0500 +Subject: [PATCH 1/9] Add initial rotation atom controls + +--- + src/drm.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ + src/drm.hpp | 11 +++++++++++ + src/steamcompmgr.cpp | 32 ++++++++++++++++++++++++++++++++ + src/xwayland_ctx.hpp | 1 + + 4 files changed, 86 insertions(+) + +diff --git a/src/drm.cpp b/src/drm.cpp +index c2694f0..de5e3ca 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -45,6 +45,7 @@ struct drm_t g_DRM = {}; + 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; ++bool g_rotate_ctl_enable = false; + bool g_bUseLayers = true; + bool g_bDebugLayers = false; + const char *g_sOutputName = nullptr; +@@ -65,6 +66,7 @@ bool g_bSupportsAsyncFlips = false; + + enum drm_mode_generation g_drmModeGeneration = DRM_MODE_GENERATE_CVT; + enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; ++enum g_rotate_ctl g_drmRotateCTL; + std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; + + bool g_bForceDisableColorMgmt = false; +@@ -2010,6 +2012,27 @@ static void update_drm_effective_orientation(struct drm_t *drm, struct connector + static void update_drm_effective_orientations(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) + { + drm_screen_type screenType = drm_get_connector_type(conn->connector); ++ ++ if (g_rotate_ctl_enable) ++ { ++ switch (g_drmRotateCTL) ++ { ++ default: ++ case NORMAL: ++ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_0; ++ break; ++ case LEFT_UP: ++ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_90; ++ break; ++ case UPSIDEDOWN: ++ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_180; ++ break; ++ case RIGHT_UP: ++ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_270; ++ break; ++ } ++ return; ++ } + if (screenType == DRM_SCREEN_TYPE_INTERNAL) + { + update_drm_effective_orientation(drm, conn, mode); +@@ -3083,6 +3106,25 @@ bool drm_set_refresh( struct drm_t *drm, int refresh ) + return drm_set_mode(drm, &mode); + } + ++void drm_set_orientation( struct drm_t *drm, bool isRotated) ++{ ++ int width = g_nOutputWidth; ++ int height = g_nOutputHeight; ++ g_bRotated = isRotated; ++ if ( g_bRotated ) { ++ int tmp = width; ++ width = height; ++ height = tmp; ++ } ++ ++ if (!drm->connector || !drm->connector->connector) ++ return; ++ ++ drmModeConnector *connector = drm->connector->connector; ++ const drmModeModeInfo *mode = find_mode(connector, width, height, 0); ++ update_drm_effective_orientations(drm, drm->connector, mode); ++} ++ + bool drm_set_resolution( struct drm_t *drm, int width, int height ) + { + if (!drm->connector || !drm->connector->connector) +diff --git a/src/drm.hpp b/src/drm.hpp +index 6810797..b2ab49f 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -325,13 +325,24 @@ enum g_panel_orientation { + PANEL_ORIENTATION_AUTO, + }; + ++enum g_rotate_ctl{ ++ NORMAL, ++ LEFT_UP, ++ UPSIDEDOWN, ++ RIGHT_UP, ++}; ++ + extern enum drm_mode_generation g_drmModeGeneration; + extern enum g_panel_orientation g_drmModeOrientation; ++extern enum g_rotate_ctl g_drmRotateCTL; ++extern bool g_rotate_ctl_enable; + + extern std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* + + extern bool g_bForceDisableColorMgmt; + ++void drm_set_orientation( struct drm_t *drm, bool isRotated ); ++ + bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_adaptive_sync); + void finish_drm(struct drm_t *drm); + int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index b02fa33..277a54c 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -5644,6 +5644,37 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + if ( g_upscaleFilter == GamescopeUpscaleFilter::FSR || g_upscaleFilter == GamescopeUpscaleFilter::NIS ) + hasRepaint = true; + } ++ if ( ev->atom == ctx->atoms.gamescopeRotateControl ) ++ { ++ std::vector< uint32_t > drm_rot_ctl; ++ bool rotate = get_prop( ctx, ctx->root, ctx->atoms.gamescopeRotateControl, drm_rot_ctl ); ++ bool rotated = false; ++ if ( rotate && drm_rot_ctl.size() == 1 ) ++ { ++ xwm_log.debugf("drm_rot_ctl %d", drm_rot_ctl[0]); ++ g_rotate_ctl_enable = true; ++ switch ( drm_rot_ctl[0] ) ++ { ++ case 0: ++ g_drmRotateCTL = NORMAL; ++ rotated = false; ++ break; ++ case 1: ++ g_drmRotateCTL = LEFT_UP; ++ rotated = true; ++ break; ++ case 2: ++ g_drmRotateCTL = UPSIDEDOWN; ++ rotated = false; ++ break; ++ case 3: ++ g_drmRotateCTL = RIGHT_UP; ++ rotated = true; ++ break; ++ } ++ drm_set_orientation(&g_DRM, rotated); ++ } ++ } + if ( ev->atom == ctx->atoms.gamescopeXWaylandModeControl ) + { + std::vector< uint32_t > xwayland_mode_ctl; +@@ -7248,6 +7279,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + ctx->atoms.gamescopeFSRSharpness = XInternAtom( ctx->dpy, "GAMESCOPE_FSR_SHARPNESS", false ); + ctx->atoms.gamescopeSharpness = XInternAtom( ctx->dpy, "GAMESCOPE_SHARPNESS", false ); + ++ ctx->atoms.gamescopeRotateControl = XInternAtom( ctx->dpy, "GAMESCOPE_ROTATE_CONTROL", 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[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH", false ); +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 5764c4b..6231007 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -146,6 +146,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + Atom gamescopeFSRSharpness; + Atom gamescopeSharpness; + ++ Atom gamescopeRotateControl; + Atom gamescopeXWaylandModeControl; + + Atom gamescopeFPSLimit; +-- +2.42.0 + + +From 4d8f1c32f1be873bf009b3d14b1ff3756495da63 Mon Sep 17 00:00:00 2001 +From: Matthew Anderson +Date: Sat, 7 Oct 2023 10:38:09 -0500 +Subject: [PATCH 2/9] Flag drm_out_of_date to ensure rotation logic gets reset + +--- + src/steamcompmgr.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 277a54c..236bba4 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -5673,6 +5673,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + break; + } + drm_set_orientation(&g_DRM, rotated); ++ g_DRM.out_of_date = 2; + } + } + if ( ev->atom == ctx->atoms.gamescopeXWaylandModeControl ) +-- +2.42.0 + + +From b145e5cde74d026ffceddee7f4096a23e60e6112 Mon Sep 17 00:00:00 2001 +From: Matthew Anderson +Date: Tue, 25 Apr 2023 06:45:01 -0500 +Subject: [PATCH 3/9] Add --force-panel-type and --force-external-orientation + arguments (#2) + +* Add --force-panel-type and --force-external-orientation arguments. + +* Rotate only the internal display when faked as "external" + +* Try to prevent the external display from being rotated when --force-panel-type external is used. + +* Fixed docking issue when --force-panel-type external is used and you dock/undock the handheld. +--- + src/drm.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++------- + src/drm.hpp | 15 +++++++++++++++ + src/main.cpp | 36 +++++++++++++++++++++++++++++++++++ + 3 files changed, 98 insertions(+), 7 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index de5e3ca..f4fe8fd 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -46,6 +46,7 @@ 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; + bool g_rotate_ctl_enable = false; ++bool g_bDisplayTypeInternal = false; + bool g_bUseLayers = true; + bool g_bDebugLayers = false; + const char *g_sOutputName = nullptr; +@@ -68,6 +69,8 @@ enum drm_mode_generation g_drmModeGeneration = DRM_MODE_GENERATE_CVT; + enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; + enum g_rotate_ctl g_drmRotateCTL; + std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; ++enum g_panel_external_orientation g_drmModeExternalOrientation = PANEL_EXTERNAL_ORIENTATION_AUTO; ++enum g_panel_type g_drmPanelType = PANEL_TYPE_AUTO; + + bool g_bForceDisableColorMgmt = false; + +@@ -1981,8 +1984,29 @@ static uint64_t determine_drm_orientation(struct drm_t *drm, struct connector *c + static void update_drm_effective_orientation(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) + { + drm_screen_type screenType = drm_get_connector_type(conn->connector); +- +- if (screenType == DRM_SCREEN_TYPE_INTERNAL) ++ if ( screenType == DRM_SCREEN_TYPE_EXTERNAL && g_bDisplayTypeInternal == true ) ++ { ++ switch ( g_drmModeExternalOrientation ) ++ { ++ case PANEL_EXTERNAL_ORIENTATION_0: ++ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_0; ++ break; ++ case PANEL_EXTERNAL_ORIENTATION_90: ++ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_90; ++ break; ++ case PANEL_EXTERNAL_ORIENTATION_180: ++ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_180; ++ break; ++ case PANEL_EXTERNAL_ORIENTATION_270: ++ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_270; ++ break; ++ case PANEL_EXTERNAL_ORIENTATION_AUTO: ++ g_drmEffectiveOrientation[screenType] = determine_drm_orientation(drm, conn, mode); ++ break; ++ } ++ return; ++ } ++ else if ( screenType == DRM_SCREEN_TYPE_INTERNAL ) + { + switch ( g_drmModeOrientation ) + { +@@ -2933,11 +2957,27 @@ bool drm_get_vrr_in_use(struct drm_t *drm) + + drm_screen_type drm_get_connector_type(drmModeConnector *connector) + { +- if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || +- connector->connector_type == DRM_MODE_CONNECTOR_LVDS || +- connector->connector_type == DRM_MODE_CONNECTOR_DSI) +- return DRM_SCREEN_TYPE_INTERNAL; +- ++ // Set to the default state of false to make sure the external display isn't rotated when a system is docked ++ g_bDisplayTypeInternal = false; ++ switch ( g_drmPanelType ) ++ { ++ case PANEL_TYPE_INTERNAL: ++ return DRM_SCREEN_TYPE_INTERNAL; ++ break; ++ case PANEL_TYPE_EXTERNAL: ++ if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || ++ connector->connector_type == DRM_MODE_CONNECTOR_LVDS || ++ connector->connector_type == DRM_MODE_CONNECTOR_DSI) ++ g_bDisplayTypeInternal = true; ++ return DRM_SCREEN_TYPE_EXTERNAL; ++ break; ++ case PANEL_TYPE_AUTO: ++ if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || ++ connector->connector_type == DRM_MODE_CONNECTOR_LVDS || ++ connector->connector_type == DRM_MODE_CONNECTOR_DSI) ++ return DRM_SCREEN_TYPE_INTERNAL; ++ break; ++ } + return DRM_SCREEN_TYPE_EXTERNAL; + } + +diff --git a/src/drm.hpp b/src/drm.hpp +index b2ab49f..53fc540 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -331,11 +331,26 @@ enum g_rotate_ctl{ + UPSIDEDOWN, + RIGHT_UP, + }; ++enum g_panel_external_orientation { ++ PANEL_EXTERNAL_ORIENTATION_0, /* NORMAL */ ++ PANEL_EXTERNAL_ORIENTATION_270, /* RIGHT */ ++ PANEL_EXTERNAL_ORIENTATION_90, /* LEFT */ ++ PANEL_EXTERNAL_ORIENTATION_180, /* UPSIDE DOWN */ ++ PANEL_EXTERNAL_ORIENTATION_AUTO, ++}; ++ ++enum g_panel_type { ++ PANEL_TYPE_INTERNAL, ++ PANEL_TYPE_EXTERNAL, ++ PANEL_TYPE_AUTO, ++}; + + extern enum drm_mode_generation g_drmModeGeneration; + extern enum g_panel_orientation g_drmModeOrientation; + extern enum g_rotate_ctl g_drmRotateCTL; + extern bool g_rotate_ctl_enable; ++extern enum g_panel_external_orientation g_drmModeExternalOrientation; ++extern enum g_panel_type g_drmPanelType; + + extern std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* + +diff --git a/src/main.cpp b/src/main.cpp +index 76721d6..f6ba34f 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -118,6 +118,8 @@ const struct option *gamescope_options = (struct option[]){ + { "disable-xres", no_argument, nullptr, 'x' }, + { "fade-out-duration", required_argument, nullptr, 0 }, + { "force-orientation", required_argument, nullptr, 0 }, ++ { "force-external-orientation", required_argument, nullptr, 0 }, ++ { "force-panel-type", required_argument, nullptr, 0 }, + { "force-windows-fullscreen", no_argument, nullptr, 0 }, + + { "disable-color-management", no_argument, nullptr, 0 }, +@@ -167,6 +169,8 @@ const char usage[] = + " --xwayland-count create N xwayland servers\n" + " --prefer-vk-device prefer Vulkan device for compositing (ex: 1002:7300)\n" + " --force-orientation rotate the internal display (left, right, normal, upsidedown)\n" ++ " --force-external-orientation rotate the external display (left, right, normal, upsidedown)\n" ++ " --force-panel-type force gamescope to treat the display as either internal or external\n" + " --force-windows-fullscreen force windows inside of gamescope to be the size of the nested display (fullscreen)\n" + " --cursor-scale-height if specified, sets a base output height to linearly scale the cursor against.\n" + " --hdr-enabled enable HDR output (needs Gamescope WSI layer enabled for support from clients)\n" +@@ -353,6 +357,18 @@ static enum drm_mode_generation parse_drm_mode_generation(const char *str) + } + } + ++static enum g_panel_type force_panel_type(const char *str) ++{ ++ if (strcmp(str, "internal") == 0) { ++ return PANEL_TYPE_INTERNAL; ++ } else if (strcmp(str, "external") == 0) { ++ return PANEL_TYPE_EXTERNAL; ++ } else { ++ fprintf( stderr, "gamescope: invalid value for --force-panel-type\n" ); ++ exit(1); ++ } ++} ++ + static enum g_panel_orientation force_orientation(const char *str) + { + if (strcmp(str, "normal") == 0) { +@@ -408,6 +424,22 @@ static enum GamescopeUpscaleFilter parse_upscaler_filter(const char *str) + struct sigaction handle_signal_action = {}; + extern pid_t child_pid; + ++static enum g_panel_external_orientation force_external_orientation(const char *str) ++{ ++ if (strcmp(str, "normal") == 0) { ++ return PANEL_EXTERNAL_ORIENTATION_0; ++ } else if (strcmp(str, "right") == 0) { ++ return PANEL_EXTERNAL_ORIENTATION_270; ++ } else if (strcmp(str, "left") == 0) { ++ return PANEL_EXTERNAL_ORIENTATION_90; ++ } else if (strcmp(str, "upsidedown") == 0) { ++ return PANEL_EXTERNAL_ORIENTATION_180; ++ } else { ++ fprintf( stderr, "gamescope: invalid value for --force-external-orientation\n" ); ++ exit(1); ++ } ++} ++ + static void handle_signal( int sig ) + { + switch ( sig ) { +@@ -614,6 +646,10 @@ int main(int argc, char **argv) + g_drmModeGeneration = parse_drm_mode_generation( optarg ); + } else if (strcmp(opt_name, "force-orientation") == 0) { + g_drmModeOrientation = force_orientation( optarg ); ++ } else if (strcmp(opt_name, "force-external-orientation") == 0) { ++ g_drmModeExternalOrientation = force_external_orientation( optarg ); ++ } else if (strcmp(opt_name, "force-panel-type") == 0) { ++ g_drmPanelType = force_panel_type( optarg ); + } else if (strcmp(opt_name, "sharpness") == 0 || + strcmp(opt_name, "fsr-sharpness") == 0) { + g_upscaleFilterSharpness = atoi( optarg ); +-- +2.42.0 + + +From 4d1f8e34b70fee42e4e30feac16eda7aa2aa63e7 Mon Sep 17 00:00:00 2001 +From: Matthew Anderson +Date: Tue, 25 Jul 2023 18:05:05 -0500 +Subject: [PATCH 4/9] Set default to native resolution of display if Steam + tries to force 720p/800p + +You can select 720p/800p still in game or via Steam's resolution setting +Steam > Settings > Display > Resolution + +This effectively reverts the changes Valve made a year ago forcing us to +720p. +--- + src/steamcompmgr.cpp | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 236bba4..60f9828 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -5685,6 +5685,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + size_t server_idx = size_t{ xwayland_mode_ctl[ 0 ] }; + int width = xwayland_mode_ctl[ 1 ]; + int height = xwayland_mode_ctl[ 2 ]; ++ ++ if ( g_nOutputWidth != 1280 && width == 1280 ) ++ { ++ width = g_nOutputWidth; ++ height = g_nOutputHeight; ++ } ++ + bool allowSuperRes = !!xwayland_mode_ctl[ 3 ]; + + if ( !allowSuperRes ) +-- +2.42.0 + + +From 8959ef22543eb94d329ef9c117ec662061a3db6c Mon Sep 17 00:00:00 2001 +From: Matthew Anderson +Date: Wed, 26 Jul 2023 20:46:29 -0500 +Subject: [PATCH 5/9] Fix internal display touchscreen orientation when it's + forced + +--- + src/main.cpp | 6 ++++++ + src/main.hpp | 2 ++ + src/wlserver.cpp | 25 +++++++++++++++++++++++++ + 3 files changed, 33 insertions(+) + +diff --git a/src/main.cpp b/src/main.cpp +index f6ba34f..17409b5 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -269,6 +269,8 @@ bool g_bHeadless = false; + + bool g_bGrabbed = false; + ++bool g_bExternalForced = false; ++ + GamescopeUpscaleFilter g_upscaleFilter = GamescopeUpscaleFilter::LINEAR; + GamescopeUpscaleScaler g_upscaleScaler = GamescopeUpscaleScaler::AUTO; + +@@ -427,12 +429,16 @@ extern pid_t child_pid; + static enum g_panel_external_orientation force_external_orientation(const char *str) + { + if (strcmp(str, "normal") == 0) { ++ g_bExternalForced = true; + return PANEL_EXTERNAL_ORIENTATION_0; + } else if (strcmp(str, "right") == 0) { ++ g_bExternalForced = true; + return PANEL_EXTERNAL_ORIENTATION_270; + } else if (strcmp(str, "left") == 0) { ++ g_bExternalForced = true; + return PANEL_EXTERNAL_ORIENTATION_90; + } else if (strcmp(str, "upsidedown") == 0) { ++ g_bExternalForced = true; + return PANEL_EXTERNAL_ORIENTATION_180; + } else { + fprintf( stderr, "gamescope: invalid value for --force-external-orientation\n" ); +diff --git a/src/main.hpp b/src/main.hpp +index 7d8e9f1..97ec0a8 100644 +--- a/src/main.hpp ++++ b/src/main.hpp +@@ -23,6 +23,8 @@ extern bool g_bFullscreen; + + extern bool g_bGrabbed; + ++extern bool g_bExternalForced; ++ + enum class GamescopeUpscaleFilter : uint32_t + { + LINEAR = 0, +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 3fbc4ff..dc37f97 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -1976,6 +1976,31 @@ static void apply_touchscreen_orientation(double *x, double *y ) + ty = 1.0 - *x; + break; + } ++ // Rotate screen if it's forced with --force-external-orientation ++ ++ if ( g_bExternalForced == true) ++ { ++ switch ( g_drmEffectiveOrientation[DRM_SCREEN_TYPE_EXTERNAL] ) ++ { ++ default: ++ case DRM_MODE_ROTATE_0: ++ tx = *x; ++ ty = *y; ++ break; ++ case DRM_MODE_ROTATE_90: ++ tx = 1.0 - *y; ++ ty = *x; ++ break; ++ case DRM_MODE_ROTATE_180: ++ tx = 1.0 - *x; ++ ty = 1.0 - *y; ++ break; ++ case DRM_MODE_ROTATE_270: ++ tx = *y; ++ ty = 1.0 - *x; ++ break; ++ } ++ } + + *x = tx; + *y = ty; +-- +2.42.0 + + +From 17a8118d9ede790f27fa085a1d287f31e81abb64 Mon Sep 17 00:00:00 2001 +From: Matthew Anderson +Date: Fri, 6 Oct 2023 23:58:17 -0500 +Subject: [PATCH 6/9] Add initial display selection atom + +--- + src/drm.cpp | 20 +++++++++++++ + src/drm.hpp | 1 + + src/steamcompmgr.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++++ + src/xwayland_ctx.hpp | 1 + + 4 files changed, 91 insertions(+) + +diff --git a/src/drm.cpp b/src/drm.cpp +index f4fe8fd..d2f7677 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -50,6 +50,7 @@ bool g_bDisplayTypeInternal = false; + bool g_bUseLayers = true; + bool g_bDebugLayers = false; + const char *g_sOutputName = nullptr; ++char* targetConnector = (char*)"eDP-1"; + + #ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP + #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 +@@ -1245,6 +1246,19 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + } + } + ++ for (auto &kv : drm->connectors) { ++ struct connector *conn = &kv.second; ++ drm_log.debugf("force set adapter"); ++ drm_log.debugf("conn->name: %s", conn->name); ++ drm_log.debugf("targetConnector: %s", targetConnector); ++ if (strcmp(conn->name, targetConnector) == 0) ++ { ++ drm_log.debugf("target was found!!!"); ++ drm_log.infof(" %s (%s)", conn->name, targetConnector); ++ best = conn; ++ } ++ } ++ + if (!force) { + if ((!best && drm->connector) || (best && best == drm->connector)) { + // Let's keep our current connector +@@ -2907,6 +2921,12 @@ static bool drm_set_crtc( struct drm_t *drm, struct crtc *crtc ) + return true; + } + ++void drm_set_prefered_connector( struct drm_t *drm, char* name ) ++{ ++ drm_log.infof("selecting prefered connector %s", name); ++ targetConnector = name; ++} ++ + bool drm_set_connector( struct drm_t *drm, struct connector *conn ) + { + drm_log.infof("selecting connector %s", conn->name); +diff --git a/src/drm.hpp b/src/drm.hpp +index 53fc540..739f51b 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -368,6 +368,7 @@ uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct + void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); + void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); + void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); ++void drm_set_prefered_connector( struct drm_t *drm, char* name ); + bool drm_set_connector( struct drm_t *drm, struct connector *conn ); + bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); + bool drm_set_refresh( struct drm_t *drm, int refresh ); +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 60f9828..aeef706 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -5719,6 +5719,74 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + } + } + } ++ if ( ev->atom == ctx->atoms.gamescopeConnectorControl ) ++ { ++ std::vector< uint32_t > connector_ctl; ++ bool hasConnectorCtrl = get_prop( ctx, ctx->root, ctx->atoms.gamescopeConnectorControl, connector_ctl ); ++ char* adapter_type; ++ if ( hasConnectorCtrl && connector_ctl.size() == 1 ) ++ { ++ switch (connector_ctl[0]) ++ { ++ case 0: ++ adapter_type = (char*)"eDP-1"; ++ break; ++ case 1: ++ adapter_type = (char*)"eDP-2"; ++ break; ++ case 2: ++ adapter_type = (char*)"eDP-3"; ++ break; ++ case 3: ++ adapter_type = (char*)"DP-1"; ++ break; ++ case 4: ++ adapter_type = (char*)"DP-2"; ++ break; ++ case 5: ++ adapter_type = (char*)"DP-3"; ++ break; ++ case 6: ++ adapter_type = (char*)"HDMI-A-1"; ++ break; ++ case 7: ++ adapter_type = (char*)"HDMI-A-2"; ++ break; ++ case 8: ++ adapter_type = (char*)"HDMI-A-3"; ++ break; ++ case 9: ++ adapter_type = (char*)"HDMI-B-1"; ++ break; ++ case 10: ++ adapter_type = (char*)"HDMI-B-2"; ++ break; ++ case 11: ++ adapter_type = (char*)"HDMI-B-3"; ++ break; ++ case 12: ++ adapter_type = (char*)"HDMI-C-1"; ++ break; ++ case 13: ++ adapter_type = (char*)"HDMI-C-2"; ++ break; ++ case 14: ++ adapter_type = (char*)"HDMI-C-3"; ++ break; ++ case 15: ++ adapter_type = (char*)"DSI-1"; ++ break; ++ case 16: ++ adapter_type = (char*)"DSI-2"; ++ break; ++ case 17: ++ adapter_type = (char*)"DSI-3"; ++ break; ++ } ++ g_DRM.out_of_date = 2; ++ drm_set_prefered_connector(&g_DRM, adapter_type); ++ } ++ } + if ( ev->atom == ctx->atoms.gamescopeFPSLimit ) + { + g_nSteamCompMgrTargetFPS = get_prop( ctx, ctx->root, ctx->atoms.gamescopeFPSLimit, 0 ); +@@ -7289,6 +7357,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + + ctx->atoms.gamescopeRotateControl = XInternAtom( ctx->dpy, "GAMESCOPE_ROTATE_CONTROL", false ); + ctx->atoms.gamescopeXWaylandModeControl = XInternAtom( ctx->dpy, "GAMESCOPE_XWAYLAND_MODE_CONTROL", false ); ++ ctx->atoms.gamescopeConnectorControl = XInternAtom(ctx->dpy, "GAMESCOPE_CONNECTOR_CONTROL", false ); + ctx->atoms.gamescopeFPSLimit = XInternAtom( ctx->dpy, "GAMESCOPE_FPS_LIMIT", false ); + ctx->atoms.gamescopeDynamicRefresh[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH", false ); + ctx->atoms.gamescopeDynamicRefresh[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH_EXTERNAL", false ); +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 6231007..9dbc544 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -149,6 +149,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + Atom gamescopeRotateControl; + Atom gamescopeXWaylandModeControl; + ++ Atom gamescopeConnectorControl; + Atom gamescopeFPSLimit; + Atom gamescopeDynamicRefresh[DRM_SCREEN_TYPE_COUNT]; + Atom gamescopeLowLatency; +-- +2.42.0 + + +From 8b94b4297324bddf48f3578592cdb6f9fe20e5a4 Mon Sep 17 00:00:00 2001 +From: Matthew Anderson +Date: Mon, 9 Oct 2023 11:21:11 -0500 +Subject: [PATCH 7/9] Use sysfs connector_ids for target device selection. + +--- + src/drm.cpp | 14 +++-------- + src/drm.hpp | 2 +- + src/steamcompmgr.cpp | 60 +------------------------------------------- + 3 files changed, 6 insertions(+), 70 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index d2f7677..59516c7 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -50,7 +50,7 @@ bool g_bDisplayTypeInternal = false; + bool g_bUseLayers = true; + bool g_bDebugLayers = false; + const char *g_sOutputName = nullptr; +-char* targetConnector = (char*)"eDP-1"; ++uint32_t targetConnector; + + #ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP + #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 +@@ -1248,13 +1248,8 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + + for (auto &kv : drm->connectors) { + struct connector *conn = &kv.second; +- drm_log.debugf("force set adapter"); +- drm_log.debugf("conn->name: %s", conn->name); +- drm_log.debugf("targetConnector: %s", targetConnector); +- if (strcmp(conn->name, targetConnector) == 0) ++ if ( conn->id == targetConnector) + { +- drm_log.debugf("target was found!!!"); +- drm_log.infof(" %s (%s)", conn->name, targetConnector); + best = conn; + } + } +@@ -2921,10 +2916,9 @@ static bool drm_set_crtc( struct drm_t *drm, struct crtc *crtc ) + return true; + } + +-void drm_set_prefered_connector( struct drm_t *drm, char* name ) ++void drm_set_prefered_connector( struct drm_t *drm, uint32_t connector_type_id ) + { +- drm_log.infof("selecting prefered connector %s", name); +- targetConnector = name; ++ targetConnector = connector_type_id; + } + + bool drm_set_connector( struct drm_t *drm, struct connector *conn ) +diff --git a/src/drm.hpp b/src/drm.hpp +index 739f51b..6320bf7 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -368,7 +368,7 @@ uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct + void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); + void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); + void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); +-void drm_set_prefered_connector( struct drm_t *drm, char* name ); ++void drm_set_prefered_connector( struct drm_t *drm, uint32_t connector_type_id ); + bool drm_set_connector( struct drm_t *drm, struct connector *conn ); + bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); + bool drm_set_refresh( struct drm_t *drm, int refresh ); +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index aeef706..9a3f495 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -5723,68 +5723,10 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + { + std::vector< uint32_t > connector_ctl; + bool hasConnectorCtrl = get_prop( ctx, ctx->root, ctx->atoms.gamescopeConnectorControl, connector_ctl ); +- char* adapter_type; + if ( hasConnectorCtrl && connector_ctl.size() == 1 ) + { +- switch (connector_ctl[0]) +- { +- case 0: +- adapter_type = (char*)"eDP-1"; +- break; +- case 1: +- adapter_type = (char*)"eDP-2"; +- break; +- case 2: +- adapter_type = (char*)"eDP-3"; +- break; +- case 3: +- adapter_type = (char*)"DP-1"; +- break; +- case 4: +- adapter_type = (char*)"DP-2"; +- break; +- case 5: +- adapter_type = (char*)"DP-3"; +- break; +- case 6: +- adapter_type = (char*)"HDMI-A-1"; +- break; +- case 7: +- adapter_type = (char*)"HDMI-A-2"; +- break; +- case 8: +- adapter_type = (char*)"HDMI-A-3"; +- break; +- case 9: +- adapter_type = (char*)"HDMI-B-1"; +- break; +- case 10: +- adapter_type = (char*)"HDMI-B-2"; +- break; +- case 11: +- adapter_type = (char*)"HDMI-B-3"; +- break; +- case 12: +- adapter_type = (char*)"HDMI-C-1"; +- break; +- case 13: +- adapter_type = (char*)"HDMI-C-2"; +- break; +- case 14: +- adapter_type = (char*)"HDMI-C-3"; +- break; +- case 15: +- adapter_type = (char*)"DSI-1"; +- break; +- case 16: +- adapter_type = (char*)"DSI-2"; +- break; +- case 17: +- adapter_type = (char*)"DSI-3"; +- break; +- } + g_DRM.out_of_date = 2; +- drm_set_prefered_connector(&g_DRM, adapter_type); ++ drm_set_prefered_connector(&g_DRM, connector_ctl[0]); + } + } + if ( ev->atom == ctx->atoms.gamescopeFPSLimit ) +-- +2.42.0 + + +From 40cb952642118fb983ec4e3deedd7410dbf69a07 Mon Sep 17 00:00:00 2001 +From: Matthew Anderson +Date: Tue, 16 Jan 2024 13:57:50 -0600 +Subject: [PATCH 8/9] Add edge gesture support to open Home and QAM + +--- + src/wlserver.cpp | 28 +++++++++++++++++++++++++++- + 1 file changed, 27 insertions(+), 1 deletion(-) + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index dc37f97..e7fb7c9 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -2048,8 +2048,34 @@ void wlserver_touchmotion( double x, double y, int touch_id, uint32_t time ) + + if ( get_effective_touch_mode() == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) + { ++ bool start_gesture = false; + wlr_seat_touch_notify_motion( wlserver.wlr.seat, time, touch_id, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); +- } ++ ++ // Round the x-coordinate to the nearest whole number ++ uint32_t roundedCursorX = static_cast(std::round(wlserver.mouse_surface_cursorx)); ++ // Grab 2% of the display to be used for the edge range ++ double edge_range = g_nOutputWidth * 0.02; ++ ++ // if the touch cursor x position is less or equal to the range then start the gesture for left to right ++ if (roundedCursorX <= edge_range) { ++ start_gesture = true; ++ } ++ // if the touch cursor x position is the output width minus the edge range value then we are doing right to left ++ if (roundedCursorX >= g_nOutputWidth - edge_range) { ++ start_gesture = true; ++ } ++ // when the gesture is started and we are moving to the end of the edge range open home ++ if (start_gesture && roundedCursorX >= 1 && roundedCursorX <= edge_range) { ++ wl_log.infof("Detected Home gesture"); ++ wlserver_open_steam_menu(0); ++ start_gesture = false; ++ } ++ // when the gesture is started and we are moving from the output width minus the edge range to the output width open QAM ++ if (start_gesture && roundedCursorX >= g_nOutputWidth - edge_range && roundedCursorX <= g_nOutputWidth ) { ++ wl_log.infof("Detected QAM gesture"); ++ wlserver_open_steam_menu(1); ++ start_gesture = false; ++ } } + else if ( get_effective_touch_mode() == WLSERVER_TOUCH_CLICK_DISABLED ) + { + return; +-- +2.42.0 + + +From f975e7a804100bb031fab0526d6714530381ec45 Mon Sep 17 00:00:00 2001 +From: Bouke Sybren Haarsma +Date: Wed, 3 Jan 2024 17:03:04 +0100 +Subject: [PATCH 9/9] remove hacky texture + +This will use more hardware planes, causing some devices to composite yeilding lower framerates +--- + src/steamcompmgr.cpp | 29 ----------------------------- + 1 file changed, 29 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 9a3f495..9e7eee5 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -2540,35 +2540,6 @@ paint_all(bool async) + if ( overlay == global_focus.inputFocusWindow ) + update_touch_scaling( &frameInfo ); + } +- else +- { +- auto tex = vulkan_get_hacky_blank_texture(); +- if ( !BIsNested() && 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->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; +- layer->ctm = nullptr; +- layer->tex = tex; +- layer->fbid = tex->fbid(); +- +- layer->filter = GamescopeUpscaleFilter::NEAREST; +- layer->blackBorder = true; +- } +- } + + if (notification) + { +-- +2.42.0 + diff --git a/spec_files/gamescope/40/crashfix.patch b/spec_files/gamescope/40/crashfix.patch new file mode 100644 index 0000000000..f50180e0d6 --- /dev/null +++ b/spec_files/gamescope/40/crashfix.patch @@ -0,0 +1,57 @@ +From adaa5e064a6149e1f8122cc55589f60b6f58f7ea Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Cl=C3=A9ment=20Gu=C3=A9rin?= +Date: Tue, 19 Dec 2023 16:34:17 -0800 +Subject: [PATCH 1/2] drm: fix NPE while in headless mode + +caused by e810317 +--- + src/drm.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 59516c7..8759321 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -3337,6 +3337,7 @@ void drm_get_native_colorimetry( struct drm_t *drm, + *displayEOTF = EOTF_Gamma22; + *outputEncodingColorimetry = displaycolorimetry_709; + *outputEncodingEOTF = EOTF_Gamma22; ++ return; + } + + *displayColorimetry = drm->connector->metadata.colorimetry; +-- +2.42.0 + + +From 08c56c656539c88b23d243869b00cf3dd33bcb1d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Cl=C3=A9ment=20Gu=C3=A9rin?= +Date: Wed, 20 Dec 2023 17:18:32 -0800 +Subject: [PATCH 2/2] drm: fix other headless NPE + +fixes a980d912 +--- + src/drm.cpp | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 8759321..d632128 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -2584,10 +2584,10 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + assert( drm->req == nullptr ); + drm->req = drmModeAtomicAlloc(); + +- bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; +- bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; +- + if (drm->connector != nullptr) { ++ bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; ++ bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; ++ + if (drm->connector->has_colorspace) { + drm->connector->pending.colorspace = ( bConnectorHDR ) ? DRM_MODE_COLORIMETRY_BT2020_RGB : DRM_MODE_COLORIMETRY_DEFAULT; + } +-- +2.42.0 + diff --git a/spec_files/gamescope/40/gamescope.spec b/spec_files/gamescope/40/gamescope.spec new file mode 100644 index 0000000000..96a52a7192 --- /dev/null +++ b/spec_files/gamescope/40/gamescope.spec @@ -0,0 +1,112 @@ +%global libliftoff_minver 0.4.1 + +Name: gamescope +Version: 3.13.19 +Release: 1%{?dist}.bazzite.{{{ git_dir_version }}} +Summary: Micro-compositor for video games on Wayland + +License: BSD +URL: https://github.com/ValveSoftware/gamescope + +# Create stb.pc to satisfy dependency('stb') +Source1: stb.pc + +# https://github.com/ChimeraOS/gamescope +Source2: chimeraos.patch +Source3: crashfix.patch +Source4: add_720p_var.patch +Source5: touch_gestures_env.patch +Source6: legion_go.patch + +# https://github.com/ValveSoftware/gamescope/pull/1149 +Source7: 1149.patch + +BuildRequires: meson >= 0.54.0 +BuildRequires: ninja-build +BuildRequires: cmake +BuildRequires: gcc +BuildRequires: gcc-c++ +BuildRequires: glm-devel +BuildRequires: google-benchmark-devel +BuildRequires: libXmu-devel +BuildRequires: pkgconfig(libdisplay-info) +BuildRequires: pkgconfig(x11) +BuildRequires: pkgconfig(xdamage) +BuildRequires: pkgconfig(xcomposite) +BuildRequires: pkgconfig(xrender) +BuildRequires: pkgconfig(xext) +BuildRequires: pkgconfig(xfixes) +BuildRequires: pkgconfig(xxf86vm) +BuildRequires: pkgconfig(xtst) +BuildRequires: pkgconfig(xres) +BuildRequires: pkgconfig(libdrm) +BuildRequires: pkgconfig(vulkan) +BuildRequires: pkgconfig(wayland-scanner) +BuildRequires: pkgconfig(wayland-server) +BuildRequires: pkgconfig(wayland-protocols) >= 1.17 +BuildRequires: pkgconfig(xkbcommon) +BuildRequires: pkgconfig(sdl2) +BuildRequires: pkgconfig(libpipewire-0.3) +BuildRequires: (pkgconfig(wlroots) >= 0.17.0 with pkgconfig(wlroots) < 0.18.0) +BuildRequires: (pkgconfig(libliftoff) >= 0.4.1 with pkgconfig(libliftoff) < 0.5) +BuildRequires: pkgconfig(libcap) +BuildRequires: pkgconfig(hwdata) +BuildRequires: pkgconfig(xwayland) +BuildRequires: pkgconfig(xcursor) +BuildRequires: vkroots-devel +BuildRequires: /usr/bin/glslangValidator +BuildRequires: git +BuildRequires: stb_image-devel +BuildRequires: stb_image_write-devel +BuildRequires: stb_image_resize-devel + +# libliftoff hasn't bumped soname, but API/ABI has changed for 0.2.0 release +Requires: libliftoff%{?_isa} >= %{libliftoff_minver} +Requires: xorg-x11-server-Xwayland +Requires: google-benchmark +Requires: gamescope-libs = %{version}-%{release} +Recommends: mesa-dri-drivers +Recommends: mesa-vulkan-drivers + +%description +%{name} is the micro-compositor optimized for running video games on Wayland. + +%package libs +Summary: libs for %{name} +%description libs +%summary + +%prep +git clone --single-branch --branch %{version} https://github.com/ValveSoftware/gamescope.git +cd gamescope +git submodule update --init --recursive +mkdir -p pkgconfig +cp %{SOURCE1} pkgconfig/stb.pc +patch -Np1 < %{SOURCE2} +patch -Np1 < %{SOURCE3} +patch -Np1 < %{SOURCE4} +patch -Np1 < %{SOURCE5} +patch -Np1 < %{SOURCE6} +patch -Np1 < %{SOURCE7} + +%build +cd gamescope +export PKG_CONFIG_PATH=pkgconfig +%meson -Dpipewire=enabled -Denable_gamescope=true -Denable_gamescope_wsi_layer=true -Denable_openvr_support=true -Dforce_fallback_for=[] +%meson_build + +%install +cd gamescope +%meson_install --skip-subprojects + +%files +%license gamescope/LICENSE +%doc gamescope/README.md +%attr(0755, root, root) %caps(cap_sys_nice=eip) %{_bindir}/gamescope + +%files libs +%{_libdir}/*.so +%{_datadir}/vulkan/implicit_layer.d/ + +%changelog +{{{ git_dir_changelog }}} diff --git a/spec_files/gamescope/40/legion_go.patch b/spec_files/gamescope/40/legion_go.patch new file mode 100644 index 0000000000..e2a3ac8d8d --- /dev/null +++ b/spec_files/gamescope/40/legion_go.patch @@ -0,0 +1,30 @@ +diff --git a/src/drm.cpp b/src/drm.cpp +index acff5e5..fdf58ee 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -101,6 +101,12 @@ static uint32_t galileo_display_rates[] = + 90, + }; + ++static uint32_t legion_go_display_rates[] = ++{ ++ 60, ++ 144, ++}; ++ + static uint32_t get_conn_display_info_flags(struct drm_t *drm, struct connector *connector) + { + if (!connector) +@@ -911,8 +917,11 @@ static void parse_edid( drm_t *drm, struct connector *conn) + conn->valid_display_rates = std::span(galileo_display_rates); + } else { + conn->is_galileo_display = 0; +- if ( conn->is_steam_deck_display ) ++ if ( conn->is_steam_deck_display ) { + conn->valid_display_rates = std::span(steam_deck_display_rates); ++ } else if ( strcmp(conn->make_pnp, "LEN") == 0 && strcmp(conn->model, "Go Display") == 0 ) { ++ conn->valid_display_rates = std::span(legion_go_display_rates); ++ } + } + + drm_hdr_parse_edid(drm, conn, edid); diff --git a/spec_files/gamescope/40/stb.pc b/spec_files/gamescope/40/stb.pc new file mode 100644 index 0000000000..02c304a9fa --- /dev/null +++ b/spec_files/gamescope/40/stb.pc @@ -0,0 +1,7 @@ +prefix=/usr +includedir=${prefix}/include/stb + +Name: stb +Description: Single-file public domain libraries for C/C++ +Version: 0.1.0 +Cflags: -I${includedir} diff --git a/spec_files/gamescope/40/touch_gestures_env.patch b/spec_files/gamescope/40/touch_gestures_env.patch new file mode 100644 index 0000000000..45befc222c --- /dev/null +++ b/spec_files/gamescope/40/touch_gestures_env.patch @@ -0,0 +1,77 @@ +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index d569ee5..0512ab0 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -66,6 +66,8 @@ extern "C" { + + static LogScope wl_log("wlserver"); + ++extern bool env_to_bool(const char *env); ++ + struct wlserver_t wlserver = { + .touch_down_ids = {} + }; +@@ -2043,34 +2045,38 @@ void wlserver_touchmotion( double x, double y, int touch_id, uint32_t time ) + + if ( get_effective_touch_mode() == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) + { +- bool start_gesture = false; + wlr_seat_touch_notify_motion( wlserver.wlr.seat, time, touch_id, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); + +- // Round the x-coordinate to the nearest whole number +- uint32_t roundedCursorX = static_cast(std::round(wlserver.mouse_surface_cursorx)); +- // Grab 2% of the display to be used for the edge range +- double edge_range = g_nOutputWidth * 0.02; +- +- // if the touch cursor x position is less or equal to the range then start the gesture for left to right +- if (roundedCursorX <= edge_range) { +- start_gesture = true; +- } +- // if the touch cursor x position is the output width minus the edge range value then we are doing right to left +- if (roundedCursorX >= g_nOutputWidth - edge_range) { +- start_gesture = true; +- } +- // when the gesture is started and we are moving to the end of the edge range open home +- if (start_gesture && roundedCursorX >= 1 && roundedCursorX <= edge_range) { +- wl_log.infof("Detected Home gesture"); +- wlserver_open_steam_menu(0); +- start_gesture = false; ++ if (!env_to_bool(getenv("GAMESCOPE_DISABLE_TOUCH_GESTURES"))) { ++ bool start_gesture = false; ++ ++ // Round the x-coordinate to the nearest whole number ++ uint32_t roundedCursorX = static_cast(std::round(wlserver.mouse_surface_cursorx)); ++ // Grab 2% of the display to be used for the edge range ++ double edge_range = g_nOutputWidth * 0.02; ++ ++ // if the touch cursor x position is less or equal to the range then start the gesture for left to right ++ if (roundedCursorX <= edge_range) { ++ start_gesture = true; ++ } ++ // if the touch cursor x position is the output width minus the edge range value then we are doing right to left ++ if (roundedCursorX >= g_nOutputWidth - edge_range) { ++ start_gesture = true; ++ } ++ // when the gesture is started and we are moving to the end of the edge range open home ++ if (start_gesture && roundedCursorX >= 1 && roundedCursorX <= edge_range) { ++ wl_log.infof("Detected Home gesture"); ++ wlserver_open_steam_menu(0); ++ start_gesture = false; ++ } ++ // when the gesture is started and we are moving from the output width minus the edge range to the output width open QAM ++ if (start_gesture && roundedCursorX >= g_nOutputWidth - edge_range && roundedCursorX <= g_nOutputWidth ) { ++ wl_log.infof("Detected QAM gesture"); ++ wlserver_open_steam_menu(1); ++ start_gesture = false; ++ } + } +- // when the gesture is started and we are moving from the output width minus the edge range to the output width open QAM +- if (start_gesture && roundedCursorX >= g_nOutputWidth - edge_range && roundedCursorX <= g_nOutputWidth ) { +- wl_log.infof("Detected QAM gesture"); +- wlserver_open_steam_menu(1); +- start_gesture = false; +- } } ++ } + else if ( get_effective_touch_mode() == WLSERVER_TOUCH_CLICK_DISABLED ) + { + return; From 8a9d89036fdfe444fd6396381aaa05a290c10553 Mon Sep 17 00:00:00 2001 From: Hai Ninh Hoang <34930708+haininhhoang94@users.noreply.github.com> Date: Mon, 18 Mar 2024 23:23:49 +0700 Subject: [PATCH 03/12] AIR plus rotation fix. same as Loki Max (#888) * AIR plus rotation fix. same as Loki Max * chore: Syntax correction --------- Co-authored-by: Kyle Gospodnetich --- system_files/deck/kinoite/usr/libexec/bazzite-rotation-fix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system_files/deck/kinoite/usr/libexec/bazzite-rotation-fix b/system_files/deck/kinoite/usr/libexec/bazzite-rotation-fix index fc5454d6fa..e42eb8ab87 100755 --- a/system_files/deck/kinoite/usr/libexec/bazzite-rotation-fix +++ b/system_files/deck/kinoite/usr/libexec/bazzite-rotation-fix @@ -38,7 +38,7 @@ fi echo $(date '+%Y-%m-%d %H:%M:%S') Fixing desktop orientation... | tee -a /tmp/bazrotfix.log if [[ ! -z "$IS_GAMEMODE" ]]; then kscreen-doctor output.1.rotation.normal 2>&1 | tee -a /tmp/bazrotfix.log -elif [[ ":83E1:Loki Max:" =~ ":$SYS_ID:" ]]; then +elif [[ ":83E1:Loki Max:AIR Plus:" =~ ":$SYS_ID:" ]]; then kscreen-doctor output.1.rotation.left 2>&1 | tee -a /tmp/bazrotfix.log else kscreen-doctor output.1.rotation.normal 2>&1 | tee -a /tmp/bazrotfix.log From af8c2f5b9b93d2feaabffdca593881eac8d4c564 Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Mon, 18 Mar 2024 10:09:25 -0700 Subject: [PATCH 04/12] chore: Update HDR patch --- spec_files/gamescope/40/1149.patch | 25568 ++++++++++++++++++++++++++- 1 file changed, 25567 insertions(+), 1 deletion(-) diff --git a/spec_files/gamescope/40/1149.patch b/spec_files/gamescope/40/1149.patch index 1fc9f06036..ec6dc63c6b 100644 --- a/spec_files/gamescope/40/1149.patch +++ b/spec_files/gamescope/40/1149.patch @@ -1,7 +1,25573 @@ +From 146da86c89a392d61ab562c1ef355c5097086fed Mon Sep 17 00:00:00 2001 +From: Simon Ser +Date: Fri, 24 Nov 2023 14:31:52 +0100 +Subject: [PATCH 001/134] steamcompmgr: don't erase multiple commits with + future desired time + +If the queue contains two commits with a desired time set in the +future, keep the second one in the queue instead of discarding it. +--- + src/steamcompmgr.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 081d342e5..1c6ed8d63 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -6384,7 +6384,7 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) + if ( entry.desiredPresentTime > next_refresh_time ) + { + commits_before_their_time.push_back( entry ); +- break; ++ continue; + } + + for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next ) + +From 592f7780e043cfb0d32600343e7c7adf35729ce9 Mon Sep 17 00:00:00 2001 +From: Simon Ser +Date: Fri, 24 Nov 2023 13:29:37 +0100 +Subject: [PATCH 002/134] steamcompmgr: add per-window sequence number + +This is guaranteed to never be re-used. +--- + src/steamcompmgr.cpp | 3 +++ + src/steamcompmgr.hpp | 1 + + src/steamcompmgr_shared.hpp | 2 ++ + src/wlserver.cpp | 1 + + 4 files changed, 7 insertions(+) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 1c6ed8d63..925d0dc59 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -130,6 +130,8 @@ extern float g_flInternalDisplayBrightnessNits; + extern float g_flHDRItmSdrNits; + extern float g_flHDRItmTargetNits; + ++uint64_t g_lastWinSeq = 0; ++ + extern std::atomic g_lastVblank; + + static std::shared_ptr s_scRGB709To2020Matrix; +@@ -4580,6 +4582,7 @@ add_win(xwayland_ctx_t *ctx, Window id, Window prev, unsigned long sequence) + if (!new_win) + return; + ++ new_win->seq = ++g_lastWinSeq; + new_win->type = steamcompmgr_win_type_t::XWAYLAND; + new_win->_window_types.emplace(); + +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index fb8a5d281..e77665afc 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -140,6 +140,7 @@ extern float focusedWindowOffsetY; + extern bool g_bFSRActive; + + extern uint32_t inputCounter; ++extern uint64_t g_lastWinSeq; + + void nudge_steamcompmgr( void ); + void take_screenshot( int flags = TAKE_SCREENSHOT_BASEPLANE_ONLY ); +diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp +index 2b0f2e4ef..3bcdb16e8 100644 +--- a/src/steamcompmgr_shared.hpp ++++ b/src/steamcompmgr_shared.hpp +@@ -90,6 +90,8 @@ struct steamcompmgr_xdg_win_t + struct steamcompmgr_win_t { + unsigned int opacity; + ++ uint64_t seq; ++ + std::shared_ptr title; + bool utf8_title; + pid_t pid; +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 49f96ed62..5547f347f 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -1367,6 +1367,7 @@ void xdg_surface_new(struct wl_listener *listener, void *data) + wlserver.xdg_wins.emplace_back(window); + } + ++ window->seq = ++g_lastWinSeq; + window->type = steamcompmgr_win_type_t::XDG; + window->_window_types.emplace(); + + +From a1d021441d843b199d4ae6cfff32380e5fb9cfb8 Mon Sep 17 00:00:00 2001 +From: Simon Ser +Date: Fri, 24 Nov 2023 13:34:37 +0100 +Subject: [PATCH 003/134] steamcompmgr: track window for each commit + +--- + src/steamcompmgr.cpp | 10 +++++++--- + src/xwayland_ctx.hpp | 1 + + 2 files changed, 8 insertions(+), 3 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 925d0dc59..5263cf92d 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -757,6 +757,7 @@ struct commit_t : public gamescope::IWaitable + bool async = false; + std::optional feedback = std::nullopt; + ++ uint64_t win_seq = 0; + struct wlr_surface *surf = nullptr; + std::vector presentation_feedbacks; + +@@ -793,7 +794,7 @@ struct commit_t : public gamescope::IWaitable + // When we get the new IWaitable stuff in there. + { + std::unique_lock< std::mutex > lock( pDoneCommits->listCommitsDoneLock ); +- pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ commitID, desired_present_time } ); ++ pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ win_seq, commitID, desired_present_time } ); + } + + if ( m_bMangoNudge ) +@@ -1366,11 +1367,12 @@ destroy_buffer( struct wl_listener *listener, void * ) + } + + static std::shared_ptr +-import_commit ( 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 ) ++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 ) + { + std::shared_ptr commit = std::make_shared(); + std::unique_lock lock( wlr_buffer_map_lock ); + ++ commit->win_seq = w->seq; + commit->surf = surf; + commit->buf = buf; + commit->async = async; +@@ -6392,6 +6394,8 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) + + 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)) + break; + } +@@ -6578,7 +6582,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re + return; + } + +- std::shared_ptr newCommit = import_commit( reslistentry.surf, buf, reslistentry.async, std::move(reslistentry.feedback), std::move(reslistentry.presentation_feedbacks), reslistentry.present_id, reslistentry.desired_present_time ); ++ std::shared_ptr 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 ); + + int fence = -1; + if ( newCommit ) +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index f4704f869..c57630d65 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -35,6 +35,7 @@ struct focus_t + + struct CommitDoneEntry_t + { ++ uint64_t winSeq; + uint64_t commitID; + uint64_t desiredPresentTime; + uint64_t earliestPresentTime; + +From 3a13b35411f43397c6defb2eccaeb34d0de2036e Mon Sep 17 00:00:00 2001 +From: Simon Ser +Date: Fri, 24 Nov 2023 14:35:41 +0100 +Subject: [PATCH 004/134] Add support for vendored commit-queue-v1 + +--- + protocol/gamescope-commit-queue-v1.xml | 181 +++++++++++++++++++++++++ + protocol/meson.build | 1 + + src/steamcompmgr.cpp | 31 ++++- + src/wlserver.cpp | 148 ++++++++++++++++++++ + src/wlserver.hpp | 1 + + src/xwayland_ctx.hpp | 1 + + 6 files changed, 358 insertions(+), 5 deletions(-) + create mode 100644 protocol/gamescope-commit-queue-v1.xml + +diff --git a/protocol/gamescope-commit-queue-v1.xml b/protocol/gamescope-commit-queue-v1.xml +new file mode 100644 +index 000000000..d460e0bc1 +--- /dev/null ++++ b/protocol/gamescope-commit-queue-v1.xml +@@ -0,0 +1,181 @@ ++ ++ ++ ++ Copyright © 2023 Valve Corporation ++ ++ Permission is hereby granted, free of charge, to any person obtaining a ++ copy of this software and associated documentation files (the "Software"), ++ to deal in the Software without restriction, including without limitation ++ the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ and/or sell copies of the Software, and to permit persons to whom the ++ Software is furnished to do so, subject to the following conditions: ++ ++ The above copyright notice and this permission notice (including the next ++ paragraph) shall be included in all copies or substantial portions of the ++ Software. ++ ++ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ DEALINGS IN THE SOFTWARE. ++ ++ ++ ++ ++ By design Wayland uses a "mailbox" style presentation model. Under ++ the mailbox model, when wl_surface.commit is called, the currently ++ pending state is intended to replace the current state immediately. ++ ++ If state is committed many times before the compositor repaints a ++ scene, each commit takes place immediately, updating the existing ++ state. When the compositor repaints the display only the most ++ recent accumulation of state is visible. This may lead to client ++ buffers being released without presentation if they were replaced ++ before being displayed. ++ ++ There are other presentation models such as FIFO (First In First ++ Out) in which state commits are explicitly queued for future ++ repaint intervals, and client buffers should not be released ++ without being displayed. ++ ++ Graphics APIs such as Vulkan aim to support these presentation ++ models, but they are not implementable on top of our mailbox model ++ without the ability to change the default surface state handling ++ behaviour. ++ ++ This interface provides a way to control the compositor's surface ++ state handling to enable presentation models other than mailbox. ++ ++ It does so by exposing control of a compositor surface state queue, ++ and specifying for each call of wl_surface.commit whether the ++ pending state should be handled in a mailbox or a FIFO fashion. ++ ++ Warning! The protocol described in this file is currently in the testing ++ phase. Backward compatible changes may be added together with the ++ corresponding interface version bump. Backward incompatible changes can ++ only be done by creating a new major version of the extension. ++ ++ ++ ++ These fatal protocol errors may be emitted in response to ++ illegal requests. ++ ++ ++ ++ ++ ++ ++ Informs the server that the client will no longer be using ++ this protocol object. Existing objects created by this object ++ are not affected. ++ ++ ++ ++ ++ ++ Establish a queue controller for a surface. ++ ++ Graphics APIs (EGL, Vulkan) will likely use this protocol ++ internally, so clients using them shouldn't directly use this ++ protocol on surfaces managed by those APIs, or a ++ queue_controller_already_exists protocol error will occur. ++ ++ ++ ++ ++ ++ ++ ++ ++ A queue controller for a surface. ++ ++ A wayland compositor may implicitly queue surface state to ++ allow it to pick the most recently ready state at repaint time, ++ or to allow surface state to contain timing information. ++ ++ The commit queue controller object allows explicit control over ++ the queue of upcoming surface state by allowing a client to attach ++ a queue drain mode to pending surface state before it calls ++ wl_surface.commit. ++ ++ ++ ++ ++ These fatal protocol errors may be emitted in response to ++ illegal requests. ++ ++ ++ ++ ++ ++ ++ This enum is used to choose how the compositor processes a queue ++ entry at output repaint time. ++ ++ ++ ++ State from this queue slot may be updated immediately (without ++ completing a repaint) if newer state is ready to display at ++ repaint time. ++ ++ ++ ++ ++ This queue slot will be the last state update for this surface ++ that the compositor will process during the repaint in which ++ it is ready for display. ++ ++ If the compositor is presenting with tearing, the surface state ++ must be made current for an iteration of the compositor's repaint ++ loop. This may result in the state being visible for a very short ++ duration, with visible artifacts, or even not visible at all for ++ surfaces that aren't full screen. ++ ++ The compositor must not cause state processing to stall indefinitely ++ for a surface that is occluded or otherwise not visible. Instead, ++ if the compositor is choosing not to present a surface for reasons ++ unrelated to state readiness, the FIFO condition must be considered ++ satisfied at the moment new state becomes ready to replace the ++ undisplayed state. ++ ++ ++ ++ ++ ++ ++ This request adds a queue drain mode to the pending surface ++ state, which will be commit by the next wl_surface.commit. ++ ++ This request tells the compositor how to process the state ++ from that commit when handling its internal state queue. ++ ++ If the drain mode is "mailbox", the compositor may continue ++ processing the next state in the queue before it repaints ++ the display. ++ ++ If the drain mode is "fifo", the compositor should ensure the ++ queue is not advanced until after this state has been current ++ for a repaint. The queue may be advance without repaint in the ++ case of off-screen or occluded surfaces. ++ ++ The default drain mode when none is specified is "mailbox". ++ ++ ++ ++ ++ ++ ++ Informs the server that the client will no longer be using ++ this protocol object. ++ ++ Surface state changes previously made by this protocol are ++ unaffected by this object's destruction. ++ ++ ++ ++ +diff --git a/protocol/meson.build b/protocol/meson.build +index df11a728b..034b0be73 100644 +--- a/protocol/meson.build ++++ b/protocol/meson.build +@@ -22,6 +22,7 @@ protocols = [ + 'gamescope-tearing-control-unstable-v1.xml', + 'gamescope-control.xml', + 'gamescope-swapchain.xml', ++ 'gamescope-commit-queue-v1.xml', + ] + + protocols_client_src = [] +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 5263cf92d..6821d2d09 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -48,6 +48,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -755,6 +756,7 @@ struct commit_t : public gamescope::IWaitable + uint64_t commitID = 0; + bool done = false; + bool async = false; ++ bool fifo = false; + std::optional feedback = std::nullopt; + + uint64_t win_seq = 0; +@@ -794,7 +796,12 @@ struct commit_t : public gamescope::IWaitable + // When we get the new IWaitable stuff in there. + { + std::unique_lock< std::mutex > lock( pDoneCommits->listCommitsDoneLock ); +- pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ win_seq, commitID, desired_present_time } ); ++ pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ ++ .winSeq = win_seq, ++ .commitID = commitID, ++ .desiredPresentTime = desired_present_time, ++ .fifo = fifo, ++ } ); + } + + if ( m_bMangoNudge ) +@@ -1367,7 +1374,7 @@ destroy_buffer( struct wl_listener *listener, void * ) + } + + static std::shared_ptr +-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 ) ++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 ) + { + std::shared_ptr commit = std::make_shared(); + std::unique_lock lock( wlr_buffer_map_lock ); +@@ -1376,6 +1383,7 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff + commit->surf = surf; + commit->buf = buf; + commit->async = async; ++ commit->fifo = fifo; + commit->presentation_feedbacks = std::move(presentation_feedbacks); + if (swapchain_feedback) + commit->feedback = *swapchain_feedback; +@@ -6366,7 +6374,7 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co + } + + // TODO: Merge these two functions. +-void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) ++void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) + { + std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); + +@@ -6375,11 +6383,20 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) + // commits that were not ready to be presented based on their display timing. + std::vector< CommitDoneEntry_t > commits_before_their_time; + ++ // windows in FIFO mode we got a new frame to present for this vblank ++ std::unordered_set< uint64_t > fifo_win_seqs; ++ + uint64_t now = get_time_in_nanos(); + + // very fast loop yes + for ( auto& entry : ctx->doneCommits.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; +@@ -6397,7 +6414,11 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) + 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; ++ } + } + } + +@@ -6582,7 +6603,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re + return; + } + +- std::shared_ptr 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 ); ++ std::shared_ptr 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 ) +@@ -7925,7 +7946,7 @@ steamcompmgr_main(int argc, char **argv) + 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()); ++ handle_done_commits_xwayland(server->ctx.get(), vblank); + + // When we have observed both a complete commit and a VBlank, we should request a new frame. + if (vblank) +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 5547f347f..dceb8727e 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -42,6 +42,7 @@ extern "C" { + #include "gamescope-control-protocol.h" + #include "gamescope-swapchain-protocol.h" + #include "gamescope-tearing-control-unstable-v1-protocol.h" ++#include "gamescope-commit-queue-v1-protocol.h" + #include "presentation-time-protocol.h" + + #include "wlserver.hpp" +@@ -86,6 +87,7 @@ static struct wl_list pending_surfaces = {0}; + + 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 enum gamescope_commit_queue_v1_queue_mode gamescope_commit_queue_v1_get_surface_mode(struct wlr_surface *surface); + + std::vector gamescope_xwayland_server_t::retrieve_commits() + { +@@ -104,10 +106,13 @@ void gamescope_xwayland_server_t::wayland_commit(struct wlr_surface *surf, struc + + auto wl_surf = get_wl_surface_info( surf ); + ++ auto queue_mode = gamescope_commit_queue_v1_get_surface_mode(surf); ++ + ResListEntry_t newEntry = { + .surf = surf, + .buf = buf, + .async = wlserver_surface_is_async(surf), ++ .fifo = queue_mode == GAMESCOPE_COMMIT_QUEUE_V1_QUEUE_MODE_FIFO, + .feedback = wlserver_surface_swapchain_feedback(surf), + .presentation_feedbacks = std::move(wl_surf->pending_presentation_feedbacks), + .present_id = wl_surf->present_id, +@@ -960,6 +965,147 @@ static void create_gamescope_tearing( void ) + } + + ++struct gamescope_commit_queue_v1 { ++ struct wl_resource *resource; ++ struct wlr_surface *surface; ++ ++ struct { ++ enum gamescope_commit_queue_v1_queue_mode mode; ++ } current, pending; ++ ++ struct wlr_addon surface_addon; ++ struct wl_listener surface_commit; ++}; ++ ++extern const struct gamescope_commit_queue_v1_interface queue_impl; ++ ++// Returns NULL if inert ++static struct gamescope_commit_queue_v1 *queue_from_resource(struct wl_resource *resource) { ++ assert(wl_resource_instance_of(resource, ++ &gamescope_commit_queue_v1_interface, &queue_impl)); ++ return (struct gamescope_commit_queue_v1 *) wl_resource_get_user_data(resource); ++} ++ ++static void resource_handle_destroy(struct wl_client *client, struct wl_resource *resource) { ++ wl_resource_destroy(resource); ++} ++ ++static void queue_destroy(struct gamescope_commit_queue_v1 *queue) { ++ if (queue == NULL) { ++ return; ++ } ++ wl_list_remove(&queue->surface_commit.link); ++ wlr_addon_finish(&queue->surface_addon); ++ wl_resource_set_user_data(queue->resource, NULL); // make inert ++ free(queue); ++} ++ ++static void surface_addon_handle_destroy(struct wlr_addon *addon) { ++ struct gamescope_commit_queue_v1 *queue = wl_container_of(addon, queue, surface_addon); ++ queue_destroy(queue); ++} ++ ++static const struct wlr_addon_interface surface_addon_impl = { ++ .name = "gamescope_commit_queue_v1", ++ .destroy = surface_addon_handle_destroy, ++}; ++ ++static void queue_handle_set_queue_mode(struct wl_client *client, ++ struct wl_resource *resource, uint32_t mode) { ++ struct gamescope_commit_queue_v1 *queue = queue_from_resource(resource); ++ ++ if (mode > GAMESCOPE_COMMIT_QUEUE_V1_QUEUE_MODE_FIFO) { ++ wl_resource_post_error(resource, GAMESCOPE_COMMIT_QUEUE_V1_ERROR_INVALID_QUEUE_MODE, ++ "Invalid queue mode"); ++ return; ++ } ++ ++ queue->pending.mode = (enum gamescope_commit_queue_v1_queue_mode) mode; ++} ++ ++const struct gamescope_commit_queue_v1_interface queue_impl = { ++ .set_queue_mode = queue_handle_set_queue_mode, ++ .destroy = resource_handle_destroy, ++}; ++ ++static void queue_handle_surface_commit(struct wl_listener *listener, void *data) { ++ struct gamescope_commit_queue_v1 *queue = wl_container_of(listener, queue, surface_commit); ++ queue->current = queue->pending; ++} ++ ++static void queue_handle_resource_destroy(struct wl_resource *resource) { ++ struct gamescope_commit_queue_v1 *queue = queue_from_resource(resource); ++ queue_destroy(queue); ++} ++ ++static void manager_handle_get_queue_controller(struct wl_client *client, ++ struct wl_resource *manager_resource, uint32_t id, ++ struct wl_resource *surface_resource) { ++ struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); ++ ++ if (wlr_addon_find(&surface->addons, NULL, &surface_addon_impl) != NULL) { ++ wl_resource_post_error(manager_resource, ++ GAMESCOPE_COMMIT_QUEUE_MANAGER_V1_ERROR_QUEUE_CONTROLLER_ALREADY_EXISTS, ++ "A gamescope_commit_queue_v1 object already exists for this surface"); ++ return; ++ } ++ ++ struct gamescope_commit_queue_v1 *queue = (struct gamescope_commit_queue_v1 *) calloc(1, sizeof(*queue)); ++ if (queue == NULL) { ++ wl_resource_post_no_memory(manager_resource); ++ return; ++ } ++ ++ queue->surface = surface; ++ ++ uint32_t version = wl_resource_get_version(manager_resource); ++ queue->resource = wl_resource_create(client, ++ &gamescope_commit_queue_v1_interface, version, id); ++ if (queue->resource == NULL) { ++ free(queue); ++ wl_resource_post_no_memory(manager_resource); ++ return; ++ } ++ wl_resource_set_implementation(queue->resource, ++ &queue_impl, queue, queue_handle_resource_destroy); ++ ++ wlr_addon_init(&queue->surface_addon, &surface->addons, NULL, &surface_addon_impl); ++ ++ queue->surface_commit.notify = queue_handle_surface_commit; ++ wl_signal_add(&surface->events.commit, &queue->surface_commit); ++} ++ ++static const struct gamescope_commit_queue_manager_v1_interface manager_impl = { ++ .destroy = resource_handle_destroy, ++ .get_queue_controller = manager_handle_get_queue_controller, ++}; ++ ++static void commit_queue_manager_bind(struct wl_client *client, void *data, ++ uint32_t version, uint32_t id) { ++ struct wl_resource *resource = wl_resource_create(client, ++ &gamescope_commit_queue_manager_v1_interface, version, id); ++ if (resource == NULL) { ++ wl_client_post_no_memory(client); ++ return; ++ } ++ wl_resource_set_implementation(resource, &manager_impl, NULL, NULL); ++} ++ ++static void commit_queue_manager_v1_create(struct wl_display *display) { ++ wl_global_create(display, &gamescope_commit_queue_manager_v1_interface, 1, NULL, commit_queue_manager_bind); ++} ++ ++static enum gamescope_commit_queue_v1_queue_mode gamescope_commit_queue_v1_get_surface_mode(struct wlr_surface *surface) { ++ struct wlr_addon *addon = ++ wlr_addon_find(&surface->addons, NULL, &surface_addon_impl); ++ if (addon == NULL) { ++ return GAMESCOPE_COMMIT_QUEUE_V1_QUEUE_MODE_MAILBOX; ++ } ++ struct gamescope_commit_queue_v1 *queue = wl_container_of(addon, queue, surface_addon); ++ return queue->current.mode; ++} ++ ++ + //////////////////////// + // presentation-time + //////////////////////// +@@ -1462,6 +1608,8 @@ bool wlserver_init( void ) { + + create_presentation_time(); + ++ commit_queue_manager_v1_create(wlserver.display); ++ + wlserver.xdg_shell = wlr_xdg_shell_create(wlserver.display, 3); + if (!wlserver.xdg_shell) + { +diff --git a/src/wlserver.hpp b/src/wlserver.hpp +index 07f36d614..d63325298 100644 +--- a/src/wlserver.hpp ++++ b/src/wlserver.hpp +@@ -38,6 +38,7 @@ 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; +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index c57630d65..52f42ef42 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -40,6 +40,7 @@ struct CommitDoneEntry_t + uint64_t desiredPresentTime; + uint64_t earliestPresentTime; + uint64_t earliestLatchTime; ++ bool fifo; + }; + + struct CommitDoneList_t + +From 183e632631eb401f8cf9cc98b92f40e0dbe7326b Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 11:02:38 +0000 +Subject: [PATCH 005/134] steamcompmgr: Avoid allocations in + handle_done_commits_xwayland + +--- + src/steamcompmgr.cpp | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 6821d2d09..451004409 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -6381,10 +6381,14 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) + uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time; + + // commits that were not ready to be presented based on their display timing. +- std::vector< CommitDoneEntry_t > commits_before_their_time; ++ 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 +- std::unordered_set< uint64_t > fifo_win_seqs; ++ 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(); + +@@ -6422,7 +6426,7 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) + } + } + +- ctx->doneCommits.listCommitsDone = std::move( commits_before_their_time ); ++ ctx->doneCommits.listCommitsDone.swap( commits_before_their_time ); + } + + void handle_done_commits_xdg() + +From c455413bb272c3274a0fe2baa150bbefba86155c Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 18:22:26 +0000 +Subject: [PATCH 006/134] drm: Fix scanout gamma for Steam In-Home Streaming + +--- + src/drm.cpp | 39 +++++++++++++++++++++++++-------------- + 1 file changed, 25 insertions(+), 14 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 575a79841..65676ffb9 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -2382,27 +2382,38 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + 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 ( entry.layerState[i].ycbcr ) ++ if ( frameInfo->layers[i].applyColorMgmt ) + { +- liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_ENCODING", entry.layerState[i].colorEncoding ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_RANGE", entry.layerState[i].colorRange ); +- if ( drm_supports_color_mgmt( drm ) ) ++ if ( entry.layerState[i].ycbcr ) + { +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", DRM_VALVE1_TRANSFER_FUNCTION_BT709 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_ENCODING", entry.layerState[i].colorEncoding ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_RANGE", entry.layerState[i].colorRange ); + } +- } +- else if ( frameInfo->layers[i].applyColorMgmt ) +- { +- liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_ENCODING" ); +- liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" ); ++ else ++ { ++ liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_ENCODING" ); ++ liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" ); ++ } ++ + if ( drm_supports_color_mgmt( drm ) ) + { + drm_valve1_transfer_function degamma_tf = colorspace_to_plane_degamma_tf( entry.layerState[i].colorspace ); + drm_valve1_transfer_function shaper_tf = colorspace_to_plane_shaper_tf( entry.layerState[i].colorspace ); ++ ++ if ( entry.layerState[i].ycbcr ) ++ { ++ // 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 = DRM_VALVE1_TRANSFER_FUNCTION_BT709; ++ shaper_tf = DRM_VALVE1_TRANSFER_FUNCTION_BT709; ++ } ++ + if (!g_bDisableDegamma) + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", degamma_tf ); + else + +From 5caef73bcf422598d62d7c3c44c498d05fd296dc Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 18:23:01 +0000 +Subject: [PATCH 007/134] rendervulkan: Fix Steam In-Home Streaming gamma on + composite + +--- + src/rendervulkan.hpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index 3e33bdce8..c60b796a1 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -113,7 +113,7 @@ inline GamescopeAppTextureColorspace VkColorSpaceToGamescopeAppTextureColorSpace + default: + case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: + // We will use image view conversions for these 8888 formats. +- if (ToSrgbVulkanFormat(format) != ToLinearVulkanFormat(format)) ++ if (ToSrgbVulkanFormat(format) != ToLinearVulkanFormat(format) || format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) + return GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; + return GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; + + +From 54d3c0bbbba7a55fd31f3db8651e8e44e236d68b Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 18:26:08 +0000 +Subject: [PATCH 008/134] shaders: Add some extra toys + +--- + src/shaders/colorimetry.h | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/src/shaders/colorimetry.h b/src/shaders/colorimetry.h +index 8be73ec1d..00adebf4f 100644 +--- a/src/shaders/colorimetry.h ++++ b/src/shaders/colorimetry.h +@@ -28,6 +28,28 @@ vec4 linearToSrgb(vec4 color) { + return vec4(linearToSrgb(color.rgb), color.a); + } + ++///////////////////////////// ++// Extra Helpers ++///////////////////////////// ++ ++vec3 g24ToLinear(vec3 color) { ++ return pow(color, vec3(2.4f)); ++} ++ ++vec4 g24ToLinear(vec4 color) { ++ return vec4(g24ToLinear(color.rgb), color.a); ++} ++ ++ ++vec3 g22ToLinear(vec3 color) { ++ return pow(color, vec3(2.2f)); ++} ++ ++vec4 g22ToLinear(vec4 color) { ++ return vec4(g22ToLinear(color.rgb), color.a); ++} ++ ++ + ///////////////////////////// + // PQ Encoding Helpers + ///////////////////////////// + +From 59053562e5ef8a1421e8501fc47d9ef584ef8898 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 18:28:49 +0000 +Subject: [PATCH 009/134] waitable: Use EPOLL_CLOEXEC + +--- + src/waitable.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/waitable.h b/src/waitable.h +index 76b0620a3..3c9a7ec4d 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -87,7 +87,7 @@ namespace gamescope + { + public: + CWaiter() +- : m_nEpollFD{ epoll_create1( 0 ) } ++ : m_nEpollFD{ epoll_create1( EPOLL_CLOEXEC ) } + { + AddWaitable( &m_NudgeWaitable, EPOLLIN ); + } + +From 1acdadcc57414d96d87b2737d8ea3108d5900f79 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 18:44:47 +0000 +Subject: [PATCH 010/134] steamcompmgr: Fix some issues with image thread on + window close + +--- + src/steamcompmgr.cpp | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 451004409..ae040aa8f 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -711,6 +711,8 @@ struct ignore { + unsigned long sequence; + }; + ++struct commit_t; ++ + gamescope::CAsyncWaiter g_ImageWaiter{ "gamescope_img" }; + + struct commit_t : public gamescope::IWaitable +@@ -722,6 +724,8 @@ struct commit_t : public gamescope::IWaitable + } + ~commit_t() + { ++ CloseFence(); ++ + if ( fb_id != 0 ) + { + drm_unlock_fbid( &g_DRM, fb_id ); +@@ -778,9 +782,7 @@ struct commit_t : public gamescope::IWaitable + { + gpuvis_trace_end_ctx_printf( commitID, "wait fence" ); + +- g_ImageWaiter.RemoveWaitable( this ); +- close( m_nCommitFence ); +- m_nCommitFence = -1; ++ CloseFence(); + + uint64_t frametime; + if ( m_bMangoNudge ) +@@ -809,6 +811,17 @@ struct commit_t : public gamescope::IWaitable + + nudge_steamcompmgr(); + } ++ ++ void CloseFence() ++ { ++ if ( m_nCommitFence < 0 ) ++ return; ++ ++ g_ImageWaiter.RemoveWaitable( this ); ++ close( m_nCommitFence ); ++ m_nCommitFence = -1; ++ } ++ + int m_nCommitFence = -1; + bool m_bMangoNudge = false; + CommitDoneList_t *pDoneCommits = nullptr; // I hate this + +From a2a91e3a409b6d1f0400f63c81af1c4f887b7b46 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 19:42:31 +0000 +Subject: [PATCH 011/134] waitable: Add lazy GC for async waitables to avoid + bubble for free-ing. + +I have never ever hit this case in any testing, but it's technically possible, so handle it. +--- + src/steamcompmgr.cpp | 63 ++++++++++++++++++++++++++++++++++---------- + src/waitable.h | 28 +++++++++++++++++++- + 2 files changed, 76 insertions(+), 15 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index ae040aa8f..f95845af9 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -713,7 +713,7 @@ struct ignore { + + struct commit_t; + +-gamescope::CAsyncWaiter g_ImageWaiter{ "gamescope_img" }; ++gamescope::CAsyncWaiter> g_ImageWaiter{ "gamescope_img" }; + + struct commit_t : public gamescope::IWaitable + { +@@ -724,7 +724,11 @@ struct commit_t : public gamescope::IWaitable + } + ~commit_t() + { +- CloseFence(); ++ { ++ std::unique_lock lock( m_WaitableCommitStateMutex ); ++ g_ImageWaiter.RemoveWaitable( this ); ++ CloseFenceInternal(); ++ } + + if ( fb_id != 0 ) + { +@@ -782,7 +786,14 @@ struct commit_t : public gamescope::IWaitable + { + gpuvis_trace_end_ctx_printf( commitID, "wait fence" ); + +- CloseFence(); ++ { ++ std::unique_lock lock( m_WaitableCommitStateMutex ); ++ if ( m_nCommitFence < 0 ) ++ return; ++ ++ g_ImageWaiter.RemoveWaitable( this ); ++ CloseFenceInternal(); ++ } + + uint64_t frametime; + if ( m_bMangoNudge ) +@@ -797,8 +808,8 @@ struct commit_t : public gamescope::IWaitable + // Instead of looping over all the windows like before. + // When we get the new IWaitable stuff in there. + { +- std::unique_lock< std::mutex > lock( pDoneCommits->listCommitsDoneLock ); +- pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ ++ 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, +@@ -812,21 +823,42 @@ struct commit_t : public gamescope::IWaitable + nudge_steamcompmgr(); + } + +- void CloseFence() ++ void CloseFenceInternal() + { + if ( m_nCommitFence < 0 ) + return; + +- g_ImageWaiter.RemoveWaitable( this ); + close( m_nCommitFence ); + m_nCommitFence = -1; + } + ++ void SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ) ++ { ++ std::unique_lock lock( m_WaitableCommitStateMutex ); ++ CloseFenceInternal(); ++ ++ m_nCommitFence = nFence; ++ m_bMangoNudge = bMangoNudge; ++ m_pDoneCommits = pDoneCommits; ++ } ++ ++ std::mutex m_WaitableCommitStateMutex; + int m_nCommitFence = -1; + bool m_bMangoNudge = false; +- CommitDoneList_t *pDoneCommits = nullptr; // I hate this ++ CommitDoneList_t *m_pDoneCommits = nullptr; // I hate this + }; + ++static inline void GarbageCollectWaitableCommit( std::shared_ptr &commit ) ++{ ++ std::unique_lock lock( commit->m_WaitableCommitStateMutex ); ++ ++ if ( commit->m_nCommitFence >= 0 ) ++ { ++ g_ImageWaiter.GCWaitable( commit, commit.get() ); ++ commit->CloseFenceInternal(); ++ } ++} ++ + static std::vector pollfds; + + #define MWM_HINTS_FUNCTIONS 1 +@@ -4843,6 +4875,9 @@ finish_destroy_win(xwayland_ctx_t *ctx, Window id, bool gone) + + if (gone) + { ++ // Manually GC this here to avoid bubbles on RemoveWaitable ++ for ( auto& commit : w->commit_queue ) ++ GarbageCollectWaitableCommit( commit ); + // release all commits now we are closed. + w->commit_queue.clear(); + } +@@ -6183,6 +6218,8 @@ error(Display *dpy, XErrorEvent *ev) + [[noreturn]] static void + steamcompmgr_exit(void) + { ++ g_ImageWaiter.Shutdown(); ++ + // Clean up any commits. + { + gamescope_xwayland_server_t *server = NULL; +@@ -6196,8 +6233,6 @@ steamcompmgr_exit(void) + g_HeldCommits[ HELD_COMMIT_BASE ] = nullptr; + g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; + +- g_ImageWaiter.Shutdown(); +- + if ( statsThreadRun == true ) + { + statsThreadRun = false; +@@ -6377,6 +6412,9 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co + if ( j > 0 ) + { + // we can release all commits prior to done ones ++ // GC to be safe ++ for ( auto it = w->commit_queue.begin(); it != w->commit_queue.begin() + j; it++ ) ++ GarbageCollectWaitableCommit( *it ); + w->commit_queue.erase( w->commit_queue.begin(), w->commit_queue.begin() + j ); + } + w->receivedDoneCommit = true; +@@ -6641,10 +6679,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re + + gpuvis_trace_printf( "pushing wait for commit %lu win %lx", newCommit->commitID, w->type == steamcompmgr_win_type_t::XWAYLAND ? w->xwayland().id : 0 ); + { +- newCommit->m_nCommitFence = fence; +- newCommit->m_bMangoNudge = mango_nudge; +- newCommit->pDoneCommits = doneCommits; +- ++ newCommit->SetFence( fence, mango_nudge, doneCommits ); + g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN ); + } + +diff --git a/src/waitable.h b/src/waitable.h +index 3c9a7ec4d..6b4b20cf8 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -178,13 +178,14 @@ namespace gamescope + int m_nEpollFD = -1; + }; + +- template ++ template + class CAsyncWaiter : public CWaiter + { + public: + CAsyncWaiter( const char *pszThreadName ) + : m_Thread{ [cWaiter = this, cName = pszThreadName](){ cWaiter->WaiterThreadFunc(cName); } } + { ++ m_WaitableGarbageCollector.reserve( 32 ); + } + + ~CAsyncWaiter() +@@ -198,6 +199,19 @@ namespace gamescope + + if ( m_Thread.joinable() ) + m_Thread.join(); ++ ++ std::unique_lock lock( m_WaitableGCMutex ); ++ m_WaitableGarbageCollector.clear(); ++ } ++ ++ void GCWaitable( GCWaitableType GCWaitable, IWaitable *pWaitable ) ++ { ++ std::unique_lock lock( m_WaitableGCMutex ); ++ ++ m_WaitableGarbageCollector.emplace_back( std::move( GCWaitable ) ); ++ ++ this->RemoveWaitable( pWaitable ); ++ this->Nudge(); + } + + void WaiterThreadFunc( const char *pszThreadName ) +@@ -205,10 +219,22 @@ namespace gamescope + pthread_setname_np( pthread_self(), pszThreadName ); + + while ( this->IsRunning() ) ++ { + CWaiter::PollEvents(); ++ ++ std::unique_lock lock( m_WaitableGCMutex ); ++ m_WaitableGarbageCollector.clear(); ++ } + } + private: + std::thread m_Thread; ++ ++ // Avoids bubble in the waiter thread func where lifetimes ++ // of objects (eg. shared_ptr) could be too short. ++ // Eg. RemoveWaitable but still processing events, or about ++ // to start processing events. ++ std::mutex m_WaitableGCMutex; ++ std::vector m_WaitableGarbageCollector; + }; + + + +From 036866c7d1dee0e932f6bd5443bf527252a3aa78 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 20:16:55 +0000 +Subject: [PATCH 012/134] wlserver: Fix crash on some game exits + +--- + src/wlserver.cpp | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index dceb8727e..473156803 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -1151,6 +1151,9 @@ void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std: + { + wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface); + ++ if ( !wl_surface_info ) ++ return; ++ + uint32_t flags = 0; + + // Don't know when we want to send this. +@@ -1194,6 +1197,9 @@ void wlserver_presentation_feedback_discard( struct wlr_surface *surface, std::v + { + wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface); + ++ if ( !wl_surface_info ) ++ return; ++ + wl_surface_info->sequence++; + + for (auto& feedback : presentation_feedbacks) +@@ -1209,6 +1215,9 @@ void wlserver_presentation_feedback_discard( struct wlr_surface *surface, std::v + 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; ++ + gamescope_swapchain_send_past_present_timing( + wl_info->gamescope_swapchain, + present_id, +@@ -1225,6 +1234,9 @@ void wlserver_past_present_timing( struct wlr_surface *surface, uint32_t present + 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; ++ + gamescope_swapchain_send_refresh_cycle( + wl_info->gamescope_swapchain, + refresh_cycle >> 32, +@@ -1917,11 +1929,15 @@ bool wlserver_surface_is_async( struct wlr_surface *surf ) + return wl_surf->presentation_hint != 0; + } + ++static std::shared_ptr s_NullFeedback; ++ + const std::shared_ptr& wlserver_surface_swapchain_feedback( struct wlr_surface *surf ) + { + assert( wlserver_is_lock_held() ); + + auto wl_surf = get_wl_surface_info( surf ); ++ if ( !wl_surf ) ++ return s_NullFeedback; + + return wl_surf->swapchain_feedback; + } + +From a5a4e364dfb9d079e40543b05db0d75a82b440a2 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 20:33:54 +0000 +Subject: [PATCH 013/134] steamcompmgr: Fix fifo queue fps limit + +--- + src/steamcompmgr.cpp | 29 +++++++++++++++++++++-------- + 1 file changed, 21 insertions(+), 8 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index f95845af9..c0f749e69 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -5330,14 +5330,13 @@ steamcompmgr_flush_frame_done( steamcompmgr_win_t *w ) + } + } + +-static void +-steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx ) ++static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vblank_idx ) + { + bool bSendCallback = true; + + int nRefresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; + int nTargetFPS = g_nSteamCompMgrTargetFPS; +- if ( g_nSteamCompMgrTargetFPS && steamcompmgr_window_should_limit_fps( w ) && nRefresh > nTargetFPS ) ++ if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && nRefresh > nTargetFPS ) + { + int nVblankDivisor = nRefresh / nTargetFPS; + +@@ -5345,7 +5344,18 @@ steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx ) + bSendCallback = false; + } + +- if ( bSendCallback ) ++ return bSendCallback; ++} ++ ++static bool steamcompmgr_should_vblank_window( steamcompmgr_win_t *w, uint64_t vblank_idx ) ++{ ++ return steamcompmgr_should_vblank_window( steamcompmgr_window_should_limit_fps( w ), vblank_idx ); ++} ++ ++static void ++steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx ) ++{ ++ if ( steamcompmgr_should_vblank_window( w, vblank_idx ) ) + { + w->unlockedForFrameCallback = true; + } +@@ -6425,7 +6435,7 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co + } + + // TODO: Merge these two functions. +-void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) ++void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vblank_idx ) + { + std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); + +@@ -6443,6 +6453,8 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) + + uint64_t now = get_time_in_nanos(); + ++ vblank = vblank && steamcompmgr_should_vblank_window( true, vblank_idx ); ++ + // very fast loop yes + for ( auto& entry : ctx->doneCommits.listCommitsDone ) + { +@@ -7973,9 +7985,9 @@ steamcompmgr_main(int argc, char **argv) + // 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 == true ) + { +- static int vblank_idx = 0; + { + gamescope_xwayland_server_t *server = NULL; + for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) +@@ -7991,14 +8003,13 @@ steamcompmgr_main(int argc, char **argv) + steamcompmgr_latch_frame_done( xdg_win.get(), vblank_idx ); + } + } +- vblank_idx++; + } + + { + 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); ++ 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) +@@ -8013,6 +8024,8 @@ steamcompmgr_main(int argc, char **argv) + + if ( vblank ) + { ++ vblank_idx++; ++ + int nRealRefresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; + int nTargetFPS = g_nSteamCompMgrTargetFPS ? g_nSteamCompMgrTargetFPS : nRealRefresh; + nTargetFPS = std::min( nTargetFPS, nRealRefresh ); + +From ce089200e49526456d0855c7e5c518ebf66ccf9b Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 29 Nov 2023 22:09:07 +0000 +Subject: [PATCH 014/134] drm: Fix vrr_capable and other metadata not being + updated + +Fixes Deck Dock VRR capable getting passed to Steam +--- + src/drm.cpp | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 65676ffb9..39c641b7e 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -987,7 +987,10 @@ static bool refresh_state( drm_t *drm ) + parse_edid(drm, conn); + + if ( conn->name != nullptr ) +- continue; ++ { ++ free(conn->name); ++ conn->name = nullptr; ++ } + + const char *type_str = drmModeGetConnectorTypeName(conn->connector->connector_type); + if (!type_str) + +From 639260814b584a2e38560fbe1845a97322672bff Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 27 Nov 2023 21:14:35 -0800 +Subject: [PATCH 015/134] mangoapp: Use visible frames for reporting to + mangohud if FIFO + +--- + src/drm.cpp | 3 +++ + src/mangoapp.cpp | 23 +++++++++++++++++++++++ + src/rendervulkan.cpp | 5 ++++- + src/steamcompmgr.cpp | 15 ++++++++++++++- + 4 files changed, 44 insertions(+), 2 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 39c641b7e..3c2999401 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -272,6 +272,7 @@ static void drm_unlock_fb_internal( struct drm_t *drm, struct fb *fb ); + + std::atomic g_nCompletedPageFlipCount = { 0u }; + ++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) + { + uint64_t flipcount = (uint64_t)data; +@@ -333,6 +334,8 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi + g_DRM.fbids_queued.clear(); + + g_DRM.flip_lock.unlock(); ++ ++ mangoapp_output_update( vblanktime ); + } + + void flip_handler_thread_run(void) +diff --git a/src/mangoapp.cpp b/src/mangoapp.cpp +index e751e5ff2..2d0c59f72 100644 +--- a/src/mangoapp.cpp ++++ b/src/mangoapp.cpp +@@ -52,3 +52,26 @@ void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uin + mangoapp_msg_v1.outputHeight = g_nOutputHeight; + 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; ++void mangoapp_output_update( uint64_t vblanktime ) ++{ ++ 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; ++ mangoapp_update( frametime, uint64_t(~0ull), uint64_t(~0ull) ); ++ } ++} +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index a1805edb6..f18b91fe3 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -2581,6 +2581,7 @@ bool acquire_next_image( void ) + 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; +@@ -2598,7 +2599,9 @@ static void present_wait_thread_func( void ) + if (present_wait_id != 0) + { + g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu ); +- vblank_mark_possible_vblank( get_time_in_nanos() ); ++ uint64_t vblanktime = get_time_in_nanos(); ++ vblank_mark_possible_vblank( vblanktime ); ++ mangoapp_output_update( vblanktime ); + } + } + } +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index c0f749e69..0df145ea2 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -765,6 +765,7 @@ struct commit_t : public gamescope::IWaitable + bool done = false; + bool async = false; + bool fifo = false; ++ bool is_steam = false; + std::optional feedback = std::nullopt; + + uint64_t win_seq = 0; +@@ -818,11 +819,16 @@ struct commit_t : public gamescope::IWaitable + } + + if ( m_bMangoNudge ) +- mangoapp_update( frametime, uint64_t(~0ull), uint64_t(~0ull) ); ++ mangoapp_update( IsPerfOverlayFIFO() ? uint64_t(~0ull) : frametime, frametime, uint64_t(~0ull) ); + + nudge_steamcompmgr(); + } + ++ bool IsPerfOverlayFIFO() ++ { ++ return fifo || is_steam; ++ } ++ + void CloseFenceInternal() + { + if ( m_nCommitFence < 0 ) +@@ -949,6 +955,9 @@ std::mutex g_SteamCompMgrXWaylandServerMutex; + + VBlankTimeInfo_t g_SteamCompMgrVBlankTime = {}; + ++uint64_t g_uCurrentBasePlaneCommitID = 0; ++bool g_bCurrentBasePlaneIsFifo = false; ++ + static int g_nSteamCompMgrTargetFPS = 0; + static uint64_t g_uDynamicRefreshEqualityTime = 0; + static int g_nDynamicRefreshRate[DRM_SCREEN_TYPE_COUNT] = { 0, 0 }; +@@ -1429,6 +1438,7 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff + commit->buf = buf; + commit->async = async; + commit->fifo = fifo; ++ commit->is_steam = window_is_steam( w ); + commit->presentation_feedbacks = std::move(presentation_feedbacks); + if (swapchain_feedback) + commit->feedback = *swapchain_feedback; +@@ -2392,6 +2402,9 @@ paint_window(steamcompmgr_win_t *w, steamcompmgr_win_t *scaleW, struct FrameInfo + g_CachedPlanes[ HELD_COMMIT_BASE ] = basePlane; + if ( !(flags & PaintWindowFlag::FadeTarget) ) + g_CachedPlanes[ HELD_COMMIT_FADE ] = basePlane; ++ ++ g_uCurrentBasePlaneCommitID = lastCommit->commitID; ++ g_bCurrentBasePlaneIsFifo = lastCommit->IsPerfOverlayFIFO(); + } + } + + +From ce34c432172f16ff52230debd49227e8209cb6e4 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Fri, 1 Dec 2023 10:37:47 +0000 +Subject: [PATCH 016/134] drm: Reset CTM on layers with applyColorMgmt = false + +--- + src/drm.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 3c2999401..8b62b4c61 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -2448,6 +2448,7 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", 0 ); + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", 0 ); + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); + } + } + + +From 38bfd04be4e24f7a28517cabbbce1ea5095bc934 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Fri, 1 Dec 2023 21:03:44 +0000 +Subject: [PATCH 017/134] steamcompmgr: Reduce alloc overhead of new xwayland + resources + +--- + src/steamcompmgr.cpp | 4 ++-- + src/wlserver.cpp | 10 +++++++--- + src/wlserver.hpp | 2 +- + 3 files changed, 10 insertions(+), 6 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 0df145ea2..f5c51a5fa 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -6705,7 +6705,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re + 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 ); +- g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN ); ++ g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN | EPOLLHUP ); + } + + w->commit_queue.push_back( std::move(newCommit) ); +@@ -6717,7 +6717,7 @@ 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(); ++ std::vector& tmp_queue = ctx->xwayland_server->retrieve_commits(); + + for ( uint32_t i = 0; i < tmp_queue.size(); i++ ) + { +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 473156803..44f2b6724 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -89,12 +89,15 @@ static void wlserver_x11_surface_info_set_wlr( struct wlserver_x11_surface_info + wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf); + static enum gamescope_commit_queue_v1_queue_mode gamescope_commit_queue_v1_get_surface_mode(struct wlr_surface *surface); + +-std::vector gamescope_xwayland_server_t::retrieve_commits() ++std::vector& gamescope_xwayland_server_t::retrieve_commits() + { +- std::vector commits; ++ static std::vector commits; ++ commits.clear(); ++ commits.reserve(16); ++ + { + std::lock_guard lock( wayland_commit_lock ); +- commits = std::move(wayland_commit_queue); ++ commits.swap(wayland_commit_queue); + } + return commits; + } +@@ -121,6 +124,7 @@ void gamescope_xwayland_server_t::wayland_commit(struct wlr_surface *surf, struc + wl_surf->present_id = std::nullopt; + wl_surf->desired_present_time = 0; + wl_surf->pending_presentation_feedbacks.clear(); ++ + wayland_commit_queue.push_back( newEntry ); + } + +diff --git a/src/wlserver.hpp b/src/wlserver.hpp +index d63325298..852a5a15d 100644 +--- a/src/wlserver.hpp ++++ b/src/wlserver.hpp +@@ -67,7 +67,7 @@ class gamescope_xwayland_server_t + + 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 *resource, struct wlr_surface *surface, uint32_t x11_window ); + void destroy_content_override( struct wlserver_x11_surface_info *x11_surface, struct wlr_surface *surf); + +From 7fa6ae8bd538c320c8227273fad489840f3a39ba Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Fri, 1 Dec 2023 21:15:32 +0000 +Subject: [PATCH 018/134] steamcompmgr: Remove dmabuf waitable on HUP + +Otherwise we can get in a bad situation where we start spinning as epoll returns instantly. +--- + src/steamcompmgr.cpp | 25 +++++++++++++++++-------- + src/waitable.h | 8 +++++--- + 2 files changed, 22 insertions(+), 11 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index f5c51a5fa..7adf9842d 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -726,7 +726,6 @@ struct commit_t : public gamescope::IWaitable + { + { + std::unique_lock lock( m_WaitableCommitStateMutex ); +- g_ImageWaiter.RemoveWaitable( this ); + CloseFenceInternal(); + } + +@@ -789,11 +788,8 @@ struct commit_t : public gamescope::IWaitable + + { + std::unique_lock lock( m_WaitableCommitStateMutex ); +- if ( m_nCommitFence < 0 ) ++ if ( !CloseFenceInternal() ) + return; +- +- g_ImageWaiter.RemoveWaitable( this ); +- CloseFenceInternal(); + } + + uint64_t frametime; +@@ -824,18 +820,27 @@ struct commit_t : public gamescope::IWaitable + nudge_steamcompmgr(); + } + ++ void OnPollHangUp() final ++ { ++ std::unique_lock lock( m_WaitableCommitStateMutex ); ++ CloseFenceInternal(); ++ } ++ + bool IsPerfOverlayFIFO() + { + return fifo || is_steam; + } + +- void CloseFenceInternal() ++ // Returns true if we had a fence that was closed. ++ bool CloseFenceInternal() + { + if ( m_nCommitFence < 0 ) +- return; ++ return false; + ++ // Will automatically remove from epoll! + close( m_nCommitFence ); + m_nCommitFence = -1; ++ return true; + } + + void SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ) +@@ -858,6 +863,10 @@ static inline void GarbageCollectWaitableCommit( std::shared_ptr &comm + { + std::unique_lock lock( commit->m_WaitableCommitStateMutex ); + ++ // This case is basically never ever hit. ++ // But we should handle it just in case. ++ // I have not seen it ever trigger even in extensive ++ // stress testing. + if ( commit->m_nCommitFence >= 0 ) + { + g_ImageWaiter.GCWaitable( commit, commit.get() ); +@@ -4888,7 +4897,7 @@ finish_destroy_win(xwayland_ctx_t *ctx, Window id, bool gone) + + if (gone) + { +- // Manually GC this here to avoid bubbles on RemoveWaitable ++ // Manually GC this here to avoid bubbles on close/RemoveWaitable + for ( auto& commit : w->commit_queue ) + GarbageCollectWaitableCommit( commit ); + // release all commits now we are closed. +diff --git a/src/waitable.h b/src/waitable.h +index 6b4b20cf8..29d5050fe 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -21,6 +21,8 @@ namespace gamescope + virtual void OnPollOut() {} + virtual void OnPollHangUp() {} + ++ virtual bool ShouldCloseOnHangUp() const { return true; } ++ + void HandleEvents( uint32_t nEvents ) + { + if ( nEvents & EPOLLIN ) +@@ -89,7 +91,7 @@ namespace gamescope + CWaiter() + : m_nEpollFD{ epoll_create1( EPOLL_CLOEXEC ) } + { +- AddWaitable( &m_NudgeWaitable, EPOLLIN ); ++ AddWaitable( &m_NudgeWaitable, EPOLLIN | EPOLLHUP ); + } + + ~CWaiter() +@@ -141,7 +143,7 @@ namespace gamescope + { + epoll_event events[MaxEvents]; + +- int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, -1); ++ int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, -1 ); + + if ( !m_bRunning ) + return; +@@ -156,7 +158,7 @@ namespace gamescope + { + epoll_event &event = events[i]; + +- IWaitable *pWaitable = reinterpret_cast(event.data.ptr); ++ IWaitable *pWaitable = reinterpret_cast( event.data.ptr ); + pWaitable->HandleEvents( event.events ); + } + } + +From 9a53b6eb37817ef403c89c104bcb73e617799114 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Fri, 1 Dec 2023 21:16:20 +0000 +Subject: [PATCH 019/134] waitable: Remove unused func + +--- + src/waitable.h | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/waitable.h b/src/waitable.h +index 29d5050fe..8748f861c 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -21,8 +21,6 @@ namespace gamescope + virtual void OnPollOut() {} + virtual void OnPollHangUp() {} + +- virtual bool ShouldCloseOnHangUp() const { return true; } +- + void HandleEvents( uint32_t nEvents ) + { + if ( nEvents & EPOLLIN ) + +From 08f01b9dec31843364592800494d5d871312c15a Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Fri, 1 Dec 2023 21:57:25 +0000 +Subject: [PATCH 020/134] steamcompmgr: Workaround kernel NULL pointer bug with + epoll + dmabuf + close + +https://lists.freedesktop.org/archives/dri-devel/2023-December/433308.html +--- + src/steamcompmgr.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 7adf9842d..dd89310e4 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -838,6 +838,7 @@ struct commit_t : public gamescope::IWaitable + return false; + + // Will automatically remove from epoll! ++ g_ImageWaiter.RemoveWaitable( this ); + close( m_nCommitFence ); + m_nCommitFence = -1; + return true; + +From baf211f5d11dbe84d9df8687055e7f51fc1de626 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 3 Dec 2023 07:38:03 +0000 +Subject: [PATCH 021/134] drm: Handle rotated screens properly in + drm_get_default_refresh + +Fixes getting stuck in 60Hz mode after sleep +--- + src/drm.cpp | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 8b62b4c61..c975bfd8d 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -3045,12 +3045,12 @@ 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->connector || !drm->connector->connector) + return false; + +@@ -3108,8 +3108,16 @@ int drm_get_default_refresh(struct drm_t *drm) + + if ( drm->connector && drm->connector->connector ) + { ++ int width = g_nOutputWidth; ++ int height = g_nOutputHeight; ++ if ( g_bRotated ) { ++ int tmp = width; ++ width = height; ++ height = tmp; ++ } ++ + drmModeConnector *connector = drm->connector->connector; +- const drmModeModeInfo *mode = find_mode( connector, g_nOutputWidth, g_nOutputHeight, 0); ++ const drmModeModeInfo *mode = find_mode( connector, width, height, 0); + if ( mode ) + return mode->vrefresh; + } + +From b5e14ba6c3f778f3e6192b59b4f556bfcc60280e Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 3 Dec 2023 07:48:06 +0000 +Subject: [PATCH 022/134] steamcompmgr: Handle external overlays better for + steamcompmgr_user_has_any_game_open + +--- + src/steamcompmgr.cpp | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index dd89310e4..d9c4f6bb4 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -1061,6 +1061,11 @@ window_is_steam( steamcompmgr_win_t *w ) + + bool g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = false; + ++bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w ) ++{ ++ return w && !window_is_steam( w ) && !w->isOverlay && !w->isExternalOverlay; ++} ++ + static bool + steamcompmgr_user_has_any_game_open() + { +@@ -1070,18 +1075,13 @@ steamcompmgr_user_has_any_game_open() + if (!server->ctx) + continue; + +- if (server->ctx->focus.focusWindow && !window_is_steam(server->ctx->focus.focusWindow)) ++ if (steamcompmgr_window_should_limit_fps( server->ctx->focus.focusWindow )) + return true; + } + + return false; + } + +-bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w ) +-{ +- return w && !window_is_steam( w ) && !w->isOverlay && !w->isExternalOverlay; +-} +- + bool steamcompmgr_window_should_refresh_switch( steamcompmgr_win_t *w ) + { + if ( g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive ) + +From 6935dfe947334a41c3786eba1dbb16d3bf5c5a9c Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 4 Dec 2023 06:17:17 +0000 +Subject: [PATCH 023/134] waitable: Fix draining CNudgeWaitable + +--- + src/waitable.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/waitable.h b/src/waitable.h +index 8748f861c..1fe78aff2 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -72,6 +72,11 @@ namespace gamescope + } + } + ++ void OnPollIn() final ++ { ++ Drain(); ++ } ++ + bool Nudge() + { + return write( m_nFDs[1], "\n", 1 ) >= 0; + +From 936d86ed4dd09ce1c13170964c12e4700c48e861 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 4 Dec 2023 06:38:15 +0000 +Subject: [PATCH 024/134] waitable: Set running to false before nudge + +Fixes bubble where we can stay open +--- + src/waitable.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/waitable.h b/src/waitable.h +index 1fe78aff2..292312ec9 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -107,8 +107,8 @@ namespace gamescope + if ( !m_bRunning ) + return; + +- Nudge(); + m_bRunning = false; ++ Nudge(); + + if ( m_nEpollFD >= 0 ) + { + +From 3dc93b70984d89c2953b1d1194a7bafbd632440f Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 5 Dec 2023 21:03:36 +0000 +Subject: [PATCH 025/134] steamcompmgr: Add support for cursor resize-based + scaling + +X11/XCursor is not good at dynamically changing the requested cursor theme size. Always request the largest and downsample it ourselves. +--- + src/meson.build | 3 +- + src/steamcompmgr.cpp | 105 ++++++++++++++++++++++++++++++------------- + src/steamcompmgr.hpp | 2 + + 3 files changed, 79 insertions(+), 31 deletions(-) + +diff --git a/src/meson.build b/src/meson.build +index d3dec0d2f..5385dfb8b 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -1,5 +1,6 @@ + dep_xdamage = dependency('xdamage') + dep_xcomposite = dependency('xcomposite') ++dep_xcursor = dependency('xcursor') + dep_xrender = dependency('xrender') + dep_xext = dependency('xext') + dep_xfixes = dependency('xfixes') +@@ -134,7 +135,7 @@ endif + dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, + xkbcommon, thread_dep, sdl_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, ++ stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, + ], + install: true, + ) +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index d9c4f6bb4..8345bfa1b 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -32,6 +32,7 @@ + #include "xwayland_ctx.hpp" + #include + #include ++#include + #include + #include + #include +@@ -87,6 +88,8 @@ + #include "log.hpp" + #include "defer.hpp" + ++static const int g_nBaseCursorScale = 36; ++ + #if HAVE_PIPEWIRE + #include "pipewire.hpp" + #endif +@@ -99,6 +102,7 @@ + #define STB_IMAGE_WRITE_IMPLEMENTATION + #include + #include ++#include + + #define GPUVIS_TRACE_IMPLEMENTATION + #include "gpuvis_trace_utils.h" +@@ -1825,29 +1829,11 @@ bool MouseCursor::setCursorImage(char *data, int w, int h, int hx, int hy) + + bool MouseCursor::setCursorImageByName(const char *name) + { +- int screen = DefaultScreen(m_ctx->dpy); +- +- XColor fg; +- fg.pixel = WhitePixel(m_ctx->dpy, screen); +- XQueryColor(m_ctx->dpy, DefaultColormap(m_ctx->dpy, screen), &fg); +- +- XColor bg; +- bg.pixel = BlackPixel(m_ctx->dpy, screen); +- XQueryColor(m_ctx->dpy, DefaultColormap(m_ctx->dpy, screen), &bg); +- + int index = XmuCursorNameToIndex(name); + if (index < 0) + return false; + +- Font font = XLoadFont(m_ctx->dpy, "cursor"); +- if (!font) +- return false; +- defer( XUnloadFont(m_ctx->dpy, font) ); +- +- Cursor cursor = XCreateGlyphCursor(m_ctx->dpy, font, font, index, index + 1, &fg, &bg); +- if ( !cursor ) +- return false; +- defer( XFreeCursor(m_ctx->dpy, cursor) ); ++ Cursor cursor = XcursorShapeLoadCursor( m_ctx->dpy, index ); + + XDefineCursor(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), cursor); + XFlush(m_ctx->dpy); +@@ -1988,6 +1974,9 @@ bool MouseCursor::getTexture() + m_hotspotX = image->xhot; + m_hotspotY = image->yhot; + ++ int nDesiredWidth, nDesiredHeight; ++ GetDesiredSize( nDesiredWidth, nDesiredHeight ); ++ + uint32_t surfaceWidth; + uint32_t surfaceHeight; + if ( BIsNested() == false && alwaysComposite == false ) +@@ -1997,8 +1986,8 @@ bool MouseCursor::getTexture() + } + else + { +- surfaceWidth = image->width; +- surfaceHeight = image->height; ++ surfaceWidth = nDesiredWidth; ++ surfaceHeight = nDesiredHeight; + } + + m_texture = nullptr; +@@ -2008,20 +1997,59 @@ bool MouseCursor::getTexture() + + std::shared_ptr> cursorBuffer = nullptr; + ++ int nContentWidth = image->width; ++ int nContentHeight = image->height; ++ + if (image->width && image->height) + { +- cursorBuffer = std::make_shared>(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]; ++ 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::make_shared>(surfaceWidth * surfaceHeight); ++ for (int i = 0; i < nDesiredHeight; i++) { ++ for (int j = 0; j < nDesiredWidth; j++) { ++ (*cursorBuffer)[i * surfaceWidth + j] = resizeBuffer[i * nDesiredWidth + j]; ++ ++ if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { ++ bNoCursor = false; ++ } ++ } ++ } + +- if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { +- bNoCursor = false; ++ m_hotspotX = ( m_hotspotX * nDesiredWidth ) / image->width; ++ m_hotspotY = ( m_hotspotY * nDesiredHeight ) / image->height; ++ ++ nContentWidth = nDesiredWidth; ++ nContentHeight = nDesiredHeight; ++ } ++ else ++ { ++ cursorBuffer = std::make_shared>(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]; ++ ++ if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { ++ bNoCursor = false; ++ } + } + } + } + } + ++ + if (bNoCursor) + cursorBuffer = nullptr; + +@@ -2049,14 +2077,27 @@ bool MouseCursor::getTexture() + // TODO: choose format & modifiers from cursor plane + } + +- m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, image->width, image->height, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer->data()); +- sdlwindow_cursor(std::move(cursorBuffer), image->width, image->height, image->xhot, image->yhot); ++ m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer->data()); ++ sdlwindow_cursor(std::move(cursorBuffer), nDesiredWidth, nDesiredHeight, image->xhot, image->yhot); + 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 ); ++ } ++ ++ nWidth = nSize; ++ nHeight = nSize; ++} ++ + void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, struct FrameInfo_t *frameInfo) + { + if ( m_hideForMovement || m_imageEmpty ) { +@@ -2085,7 +2126,9 @@ void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, str + float cursor_scale = 1.0f; + if ( g_nCursorScaleHeight > 0 ) + { +- cursor_scale = floor(currentOutputHeight / (float)g_nCursorScaleHeight); ++ int nDesiredWidth, nDesiredHeight; ++ GetDesiredSize( nDesiredWidth, nDesiredHeight ); ++ cursor_scale = nDesiredHeight / (float)m_texture->contentHeight(); + } + cursor_scale = std::max(cursor_scale, 1.0f); + +@@ -7982,6 +8025,8 @@ steamcompmgr_main(int argc, char **argv) + 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; +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index e77665afc..b1701c97c 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -94,6 +94,8 @@ class MouseCursor + bool needs_server_flush() const { return m_needs_server_flush; } + void inform_flush() { m_needs_server_flush = false; } + ++ void GetDesiredSize( int& nWidth, int &nHeight ); ++ + private: + void warp(int x, int y); + void checkSuspension(); + +From 9894245aa7a98063bf99fc78269d5d7a6f0a358b Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 5 Dec 2023 21:03:45 +0000 +Subject: [PATCH 026/134] main: Default XCURSOR_SIZE to 256 + +--- + src/main.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/main.cpp b/src/main.cpp +index 46e343ccd..76721d63d 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -697,6 +697,7 @@ int main(int argc, char **argv) + #endif + + setenv( "XWAYLAND_FORCE_ENABLE_EXTRA_MODES", "1", 1 ); ++ setenv( "XCURSOR_SIZE", "256", 1 ); + + raise_fd_limit(); + + +From 2b9c739d20179b294fac4adb311d3d79516a9e5c Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 6 Dec 2023 16:43:42 +0000 +Subject: [PATCH 027/134] pipewire: Make dmabuf.n_planes != 1 non-fatal + +--- + src/pipewire.cpp | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/src/pipewire.cpp b/src/pipewire.cpp +index a05346ce4..236e8ac7e 100644 +--- a/src/pipewire.cpp ++++ b/src/pipewire.cpp +@@ -478,7 +478,11 @@ static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffe + + if (is_dmabuf) { + const struct wlr_dmabuf_attributes dmabuf = buffer->texture->dmabuf(); +- assert(dmabuf.n_planes == 1); ++ 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) { + +From 13381d23ec845e108759a70dd212bdd9fc94d2e5 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Thu, 7 Dec 2023 15:39:43 +0000 +Subject: [PATCH 028/134] waitable: Default to EPOLLIN | EPOLLHUP + +--- + src/steamcompmgr.cpp | 2 +- + src/waitable.h | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 8345bfa1b..65a2e2aa2 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -6758,7 +6758,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re + 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 ); +- g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN | EPOLLHUP ); ++ g_ImageWaiter.AddWaitable( newCommit.get() ); + } + + w->commit_queue.push_back( std::move(newCommit) ); +diff --git a/src/waitable.h b/src/waitable.h +index 292312ec9..4bc77ff62 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -94,7 +94,7 @@ namespace gamescope + CWaiter() + : m_nEpollFD{ epoll_create1( EPOLL_CLOEXEC ) } + { +- AddWaitable( &m_NudgeWaitable, EPOLLIN | EPOLLHUP ); ++ AddWaitable( &m_NudgeWaitable ); + } + + ~CWaiter() +@@ -117,7 +117,7 @@ namespace gamescope + } + } + +- bool AddWaitable( IWaitable *pWaitable, uint32_t nEvents = EPOLLIN | EPOLLOUT | EPOLLHUP ) ++ bool AddWaitable( IWaitable *pWaitable, uint32_t nEvents = EPOLLIN | EPOLLHUP ) + { + epoll_event event = + { + +From 499cd6f4982333775ba232729cd5c44096aca490 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 6 Dec 2023 23:18:37 +0000 +Subject: [PATCH 029/134] steamcompmgr: Move win32 styles to win32_styles.h + +--- + src/steamcompmgr.cpp | 60 +------------------------------------------- + src/win32_styles.h | 56 +++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 57 insertions(+), 59 deletions(-) + create mode 100644 src/win32_styles.h + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 65a2e2aa2..c71516d1d 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -87,6 +87,7 @@ + #include "sdlwindow.hpp" + #include "log.hpp" + #include "defer.hpp" ++#include "win32_styles.h" + + static const int g_nBaseCursorScale = 36; + +@@ -640,65 +641,6 @@ bool set_color_look_g22(const char *path) + + bool g_bColorSliderInUse = false; + +-// +-// +-// +- +-const uint32_t WS_OVERLAPPED = 0x00000000u; +-const uint32_t WS_POPUP = 0x80000000u; +-const uint32_t WS_CHILD = 0x40000000u; +-const uint32_t WS_MINIMIZE = 0x20000000u; +-const uint32_t WS_VISIBLE = 0x10000000u; +-const uint32_t WS_DISABLED = 0x08000000u; +-const uint32_t WS_CLIPSIBLINGS = 0x04000000u; +-const uint32_t WS_CLIPCHILDREN = 0x02000000u; +-const uint32_t WS_MAXIMIZE = 0x01000000u; +-const uint32_t WS_BORDER = 0x00800000u; +-const uint32_t WS_DLGFRAME = 0x00400000u; +-const uint32_t WS_VSCROLL = 0x00200000u; +-const uint32_t WS_HSCROLL = 0x00100000u; +-const uint32_t WS_SYSMENU = 0x00080000u; +-const uint32_t WS_THICKFRAME = 0x00040000u; +-const uint32_t WS_GROUP = 0x00020000u; +-const uint32_t WS_TABSTOP = 0x00010000u; +-const uint32_t WS_MINIMIZEBOX = 0x00020000u; +-const uint32_t WS_MAXIMIZEBOX = 0x00010000u; +-const uint32_t WS_CAPTION = WS_BORDER | WS_DLGFRAME; +-const uint32_t WS_TILED = WS_OVERLAPPED; +-const uint32_t WS_ICONIC = WS_MINIMIZE; +-const uint32_t WS_SIZEBOX = WS_THICKFRAME; +-const uint32_t WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME| WS_MINIMIZEBOX | WS_MAXIMIZEBOX; +-const uint32_t WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU; +-const uint32_t WS_CHILDWINDOW = WS_CHILD; +-const uint32_t WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW; +- +-const uint32_t WS_EX_DLGMODALFRAME = 0x00000001u; +-const uint32_t WS_EX_DRAGDETECT = 0x00000002u; // Undocumented +-const uint32_t WS_EX_NOPARENTNOTIFY = 0x00000004u; +-const uint32_t WS_EX_TOPMOST = 0x00000008u; +-const uint32_t WS_EX_ACCEPTFILES = 0x00000010u; +-const uint32_t WS_EX_TRANSPARENT = 0x00000020u; +-const uint32_t WS_EX_MDICHILD = 0x00000040u; +-const uint32_t WS_EX_TOOLWINDOW = 0x00000080u; +-const uint32_t WS_EX_WINDOWEDGE = 0x00000100u; +-const uint32_t WS_EX_CLIENTEDGE = 0x00000200u; +-const uint32_t WS_EX_CONTEXTHELP = 0x00000400u; +-const uint32_t WS_EX_RIGHT = 0x00001000u; +-const uint32_t WS_EX_LEFT = 0x00000000u; +-const uint32_t WS_EX_RTLREADING = 0x00002000u; +-const uint32_t WS_EX_LTRREADING = 0x00000000u; +-const uint32_t WS_EX_LEFTSCROLLBAR = 0x00004000u; +-const uint32_t WS_EX_RIGHTSCROLLBAR = 0x00000000u; +-const uint32_t WS_EX_CONTROLPARENT = 0x00010000u; +-const uint32_t WS_EX_STATICEDGE = 0x00020000u; +-const uint32_t WS_EX_APPWINDOW = 0x00040000u; +-const uint32_t WS_EX_LAYERED = 0x00080000u; +-const uint32_t WS_EX_NOINHERITLAYOUT = 0x00100000u; +-const uint32_t WS_EX_NOREDIRECTIONBITMAP = 0x00200000u; +-const uint32_t WS_EX_LAYOUTRTL = 0x00400000u; +-const uint32_t WS_EX_COMPOSITED = 0x02000000u; +-const uint32_t WS_EX_NOACTIVATE = 0x08000000u; +- + template< typename T > + constexpr const T& clamp( const T& x, const T& min, const T& max ) + { +diff --git a/src/win32_styles.h b/src/win32_styles.h +new file mode 100644 +index 000000000..88741e255 +--- /dev/null ++++ b/src/win32_styles.h +@@ -0,0 +1,56 @@ ++#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_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; + +From 2e1dd0b8e21c95b9895a622af52fe16bf78038c7 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 6 Dec 2023 23:21:02 +0000 +Subject: [PATCH 030/134] steamcompmgr: Move mwm hints to mwm_hints.h + +--- + src/mwm_hints.h | 29 +++++++++++++++++++++++++++++ + src/steamcompmgr.cpp | 29 +---------------------------- + 2 files changed, 30 insertions(+), 28 deletions(-) + create mode 100644 src/mwm_hints.h + +diff --git a/src/mwm_hints.h b/src/mwm_hints.h +new file mode 100644 +index 000000000..68c204e16 +--- /dev/null ++++ b/src/mwm_hints.h +@@ -0,0 +1,29 @@ ++#pragma once ++ ++#define MWM_HINTS_FUNCTIONS 1 ++#define MWM_HINTS_DECORATIONS 2 ++#define MWM_HINTS_INPUT_MODE 4 ++#define MWM_HINTS_STATUS 8 ++ ++#define MWM_FUNC_ALL 0x01 ++#define MWM_FUNC_RESIZE 0x02 ++#define MWM_FUNC_MOVE 0x04 ++#define MWM_FUNC_MINIMIZE 0x08 ++#define MWM_FUNC_MAXIMIZE 0x10 ++#define MWM_FUNC_CLOSE 0x20 ++ ++#define MWM_DECOR_ALL 0x01 ++#define MWM_DECOR_BORDER 0x02 ++#define MWM_DECOR_RESIZEH 0x04 ++#define MWM_DECOR_TITLE 0x08 ++#define MWM_DECOR_MENU 0x10 ++#define MWM_DECOR_MINIMIZE 0x20 ++#define MWM_DECOR_MAXIMIZE 0x40 ++ ++#define MWM_INPUT_MODELESS 0 ++#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 ++#define MWM_INPUT_SYSTEM_MODAL 2 ++#define MWM_INPUT_FULL_APPLICATION_MODAL 3 ++#define MWM_INPUT_APPLICATION_MODAL 1 ++ ++#define MWM_TEAROFF_WINDOW 1 +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index c71516d1d..49b316d63 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -88,6 +88,7 @@ + #include "log.hpp" + #include "defer.hpp" + #include "win32_styles.h" ++#include "mwm_hints.h" + + static const int g_nBaseCursorScale = 36; + +@@ -823,34 +824,6 @@ static inline void GarbageCollectWaitableCommit( std::shared_ptr &comm + + static std::vector pollfds; + +-#define MWM_HINTS_FUNCTIONS 1 +-#define MWM_HINTS_DECORATIONS 2 +-#define MWM_HINTS_INPUT_MODE 4 +-#define MWM_HINTS_STATUS 8 +- +-#define MWM_FUNC_ALL 0x01 +-#define MWM_FUNC_RESIZE 0x02 +-#define MWM_FUNC_MOVE 0x04 +-#define MWM_FUNC_MINIMIZE 0x08 +-#define MWM_FUNC_MAXIMIZE 0x10 +-#define MWM_FUNC_CLOSE 0x20 +- +-#define MWM_DECOR_ALL 0x01 +-#define MWM_DECOR_BORDER 0x02 +-#define MWM_DECOR_RESIZEH 0x04 +-#define MWM_DECOR_TITLE 0x08 +-#define MWM_DECOR_MENU 0x10 +-#define MWM_DECOR_MINIMIZE 0x20 +-#define MWM_DECOR_MAXIMIZE 0x40 +- +-#define MWM_INPUT_MODELESS 0 +-#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +-#define MWM_INPUT_SYSTEM_MODAL 2 +-#define MWM_INPUT_FULL_APPLICATION_MODAL 3 +-#define MWM_INPUT_APPLICATION_MODAL 1 +- +-#define MWM_TEAROFF_WINDOW 1 +- + Window x11_win(steamcompmgr_win_t *w) { + if (w == nullptr) + return None; + +From 07024b8979b57bfc9c2b86c820bbbf7b9fe66934 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Thu, 7 Dec 2023 13:21:04 +0000 +Subject: [PATCH 031/134] rendervulkan: Notify on g_currentPresentWaitId change + +--- + src/rendervulkan.cpp | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index f18b91fe3..32b87ba5b 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -2678,7 +2678,10 @@ void vulkan_present_to_window( void ) + }; + + if ( g_device.vk.QueuePresentKHR( g_device.queue(), &presentInfo ) == VK_SUCCESS ) ++ { + g_currentPresentWaitId = presentId; ++ g_currentPresentWaitId.notify_all(); ++ } + else + vulkan_remake_swapchain(); + +@@ -2925,6 +2928,7 @@ bool vulkan_remake_swapchain( void ) + { + std::unique_lock lock(present_wait_lock); + g_currentPresentWaitId = 0; ++ g_currentPresentWaitId.notify_all(); + + VulkanOutput_t *pOutput = &g_output; + g_device.waitIdle(); + +From 9376d530d8ee28c0556a53915be85e860ade2834 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 6 Dec 2023 22:51:33 +0000 +Subject: [PATCH 032/134] steamcompmgr: Move main poll event loop to new + waitable system + +--- + src/steamcompmgr.cpp | 130 ++++++++++--------------------------------- + src/waitable.h | 105 ++++++++++++++++++++++++---------- + src/xwayland_ctx.hpp | 25 ++++++++- + 3 files changed, 129 insertions(+), 131 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 49b316d63..40961877a 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -110,7 +110,7 @@ static const int g_nBaseCursorScale = 36; + #include "gpuvis_trace_utils.h" + + +-static LogScope xwm_log("xwm"); ++LogScope xwm_log("xwm"); + LogScope g_WaitableLog("waitable"); + + bool g_bWasPartialComposite = false; +@@ -822,7 +822,7 @@ static inline void GarbageCollectWaitableCommit( std::shared_ptr &comm + } + } + +-static std::vector pollfds; ++gamescope::CWaiter g_SteamCompMgrWaiter; + + Window x11_win(steamcompmgr_win_t *w) { + if (w == nullptr) +@@ -1074,8 +1074,6 @@ static bool g_bPropertyRequestedScreenshot; + + static std::atomic g_bForceRepaint{false}; + +-static int g_nudgePipe[2] = {-1, -1}; +- + static int g_nCursorScaleHeight = -1; + + // poor man's semaphore +@@ -1105,21 +1103,6 @@ class sem + int count = 0; + }; + +-static void +-dispatch_nudge( int fd ) +-{ +- for (;;) +- { +- static char buf[1024]; +- if ( read( fd, buf, sizeof(buf) ) < 0 ) +- { +- if ( errno != EAGAIN ) +- xwm_log.errorf_errno(" steamcompmgr: dispatch_nudge: read failed" ); +- break; +- } +- } +-} +- + sem statsThreadSem; + std::mutex statsEventQueueLock; + std::vector< std::string > statsEventQueue; +@@ -1268,12 +1251,11 @@ should_ignore(xwayland_ctx_t *ctx, unsigned long sequence) + return ctx->ignore_head && ctx->ignore_head->sequence == sequence; + } + +-static bool +-x_events_queued(xwayland_ctx_t* ctx) ++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(ctx->dpy, QueuedAlready) != 0; ++ return XEventsQueued( dpy, QueuedAlready ) != 0; + } + + static steamcompmgr_win_t * +@@ -6030,10 +6012,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + .format = 8, + .nitems = strlen(propertyString), + }; +- pollfds.push_back(pollfd { +- .fd = XConnectionNumber( server->ctx->dpy ), +- .events = POLLIN, +- }); ++ g_SteamCompMgrWaiter.AddWaitable( server->ctx.get() ); + XSetTextProperty( ctx->dpy, ctx->root, &text_property, ctx->atoms.gamescopeCreateXWaylandServerFeedback ); + wlserver_unlock(); + } +@@ -6090,9 +6069,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + global_focus.cursor = nullptr; + + wlserver_lock(); +- std::erase_if(pollfds, [=](const auto& other){ +- return other.fd == XConnectionNumber( server->ctx->dpy ); +- }); ++ g_SteamCompMgrWaiter.RemoveWaitable( server->ctx.get() ); + wlserver_destroy_xwayland_server(server); + wlserver_unlock(); + +@@ -6589,8 +6566,7 @@ void handle_presented_xdg() + + void nudge_steamcompmgr( void ) + { +- if ( write( g_nudgePipe[ 1 ], "\n", 1 ) < 0 ) +- xwm_log.errorf_errno( "nudge_steamcompmgr: write failed" ); ++ g_SteamCompMgrWaiter.Nudge(); + } + + void take_screenshot( int flags ) +@@ -6858,9 +6834,10 @@ handle_xfixes_selection_notify( xwayland_ctx_t *ctx, XFixesSelectionNotifyEvent + XFlush(ctx->dpy); + } + +-static void +-dispatch_x11( xwayland_ctx_t *ctx ) ++void xwayland_ctx_t::Dispatch() + { ++ xwayland_ctx_t *ctx = this; ++ + MouseCursor *cursor = ctx->cursor.get(); + bool bShouldResetCursor = false; + bool bSetFocus = false; +@@ -7140,13 +7117,6 @@ load_host_cursor( MouseCursor *cursor ) + return true; + } + +-enum steamcompmgr_event_type { +- EVENT_VBLANK, +- EVENT_NUDGE, +- EVENT_X11, +- // Any past here are X11 +-}; +- + const char* g_customCursorPath = nullptr; + int g_customCursorHotspotX = 0; + int g_customCursorHotspotY = 0; +@@ -7702,12 +7672,6 @@ steamcompmgr_main(int argc, char **argv) + subCommandArg = optind; + } + +- if ( pipe2( g_nudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) +- { +- xwm_log.errorf_errno( "steamcompmgr: pipe2 failed" ); +- exit( 1 ); +- } +- + const char *pchEnableVkBasalt = getenv( "ENABLE_VKBASALT" ); + if ( pchEnableVkBasalt != nullptr && pchEnableVkBasalt[0] == '1' ) + { +@@ -7761,27 +7725,22 @@ steamcompmgr_main(int argc, char **argv) + spawn_client( &argv[ subCommandArg ] ); + } + +- // EVENT_VBLANK +- pollfds.push_back(pollfd { +- .fd = vblankFD, +- .events = POLLIN, +- }); +- // EVENT_NUDGE +- pollfds.push_back(pollfd { +- .fd = g_nudgePipe[ 0 ], +- .events = POLLIN, +- }); +- // EVENT_X11 ++ bool vblank = false; ++ g_SteamCompMgrWaiter.AddWaitable( ++ new gamescope::CFunctionWaitable{ vblankFD, [ vblankFD, &vblank ]() ++ { ++ vblank = dispatch_vblank( vblankFD ); ++ }} ++ ); ++ + { +- gamescope_xwayland_server_t *server = NULL; +- for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) ++ gamescope_xwayland_server_t *pServer = NULL; ++ for (size_t i = 0; (pServer = wlserver_get_xwayland_server(i)); i++) + { +- pollfds.push_back(pollfd { +- .fd = XConnectionNumber( server->ctx->dpy ), +- .events = POLLIN, +- }); ++ xwayland_ctx_t *pXWaylandCtx = pServer->ctx.get(); ++ g_SteamCompMgrWaiter.AddWaitable( pXWaylandCtx ); + +- server->ctx->force_windows_fullscreen = bForceWindowsFullscreen; ++ pServer->ctx->force_windows_fullscreen = bForceWindowsFullscreen; + } + } + +@@ -7804,52 +7763,19 @@ steamcompmgr_main(int argc, char **argv) + + for (;;) + { +- bool vblank = false; ++ vblank = false; + + { + gamescope_xwayland_server_t *server = NULL; + for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) + { +- assert(server); +- if (x_events_queued(server->ctx.get())) +- dispatch_x11(server->ctx.get()); ++ assert(server->ctx); ++ if (server->ctx->HasQueuedEvents()) ++ server->ctx->Dispatch(); + } + } + +- if ( poll( pollfds.data(), pollfds.size(), -1 ) < 0) +- { +- if ( errno == EAGAIN ) +- continue; +- +- xwm_log.errorf_errno( "poll failed" ); +- break; +- } +- +- for (size_t i = EVENT_X11; i < pollfds.size(); i++) +- { +- if ( pollfds[ i ].revents & POLLHUP ) +- { +- xwm_log.errorf( "Lost connection to the X11 server %zd", i - EVENT_X11 ); +- break; +- } +- } +- +- assert( !( pollfds[ EVENT_VBLANK ].revents & POLLHUP ) ); +- assert( !( pollfds[ EVENT_NUDGE ].revents & POLLHUP ) ); +- +- for (size_t i = EVENT_X11; i < pollfds.size(); i++) +- { +- if ( pollfds[ i ].revents & POLLIN ) +- { +- gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(i - EVENT_X11); +- assert(server); +- dispatch_x11( server->ctx.get() ); +- } +- } +- if ( pollfds[ EVENT_VBLANK ].revents & POLLIN ) +- vblank = dispatch_vblank( vblankFD ); +- if ( pollfds[ EVENT_NUDGE ].revents & POLLIN ) +- dispatch_nudge( g_nudgePipe[ 0 ] ); ++ g_SteamCompMgrWaiter.PollEvents(); + + if ( g_bRun == false ) + { +diff --git a/src/waitable.h b/src/waitable.h +index 4bc77ff62..a8f1f212a 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -2,8 +2,11 @@ + + #include + #include ++#include + #include + ++#include ++ + #include "log.hpp" + + extern LogScope g_WaitableLog; +@@ -19,7 +22,11 @@ namespace gamescope + + virtual void OnPollIn() {} + virtual void OnPollOut() {} +- virtual void OnPollHangUp() {} ++ virtual void OnPollHangUp() ++ { ++ g_WaitableLog.errorf( "IWaitable hung up. Aborting." ); ++ abort(); ++ } + + void HandleEvents( uint32_t nEvents ) + { +@@ -30,6 +37,23 @@ namespace gamescope + if ( nEvents & EPOLLHUP ) + this->OnPollHangUp(); + } ++ ++ static void Drain( int nFD ) ++ { ++ if ( nFD < 0 ) ++ return; ++ ++ char buf[1024]; ++ for (;;) ++ { ++ if ( read( nFD, buf, sizeof( buf ) ) < 0 ) ++ { ++ if ( errno != EAGAIN ) ++ g_WaitableLog.errorf_errno( "Failed to drain CNudgeWaitable" ); ++ break; ++ } ++ } ++ } + }; + + class CNudgeWaitable final : public IWaitable +@@ -57,19 +81,7 @@ namespace gamescope + + void Drain() + { +- if ( m_nFDs[0] < 0 ) +- return; +- +- char buf[1024]; +- for (;;) +- { +- if ( read( m_nFDs[0], buf, sizeof( buf ) ) < 0 ) +- { +- if ( errno != EAGAIN ) +- g_WaitableLog.errorf_errno( "Failed to drain CNudgeWaitable" ); +- break; +- } +- } ++ IWaitable::Drain( m_nFDs[0] ); + } + + void OnPollIn() final +@@ -87,6 +99,35 @@ namespace gamescope + int m_nFDs[2] = { -1, -1 }; + }; + ++ ++ class CFunctionWaitable final : public IWaitable ++ { ++ public: ++ CFunctionWaitable( int nFD, std::function fnPollFunc ) ++ : m_nFD{ nFD } ++ , m_fnPollFunc{ fnPollFunc } ++ { ++ } ++ ++ void OnPollIn() final ++ { ++ m_fnPollFunc(); ++ } ++ ++ void Drain() ++ { ++ IWaitable::Drain( m_nFD ); ++ } ++ ++ int GetFD() final ++ { ++ return m_nFD; ++ } ++ private: ++ int m_nFD; ++ std::function m_fnPollFunc; ++ }; ++ + template + class CWaiter + { +@@ -142,27 +183,35 @@ namespace gamescope + epoll_ctl( m_nEpollFD, EPOLL_CTL_DEL, pWaitable->GetFD(), nullptr ); + } + +- void PollEvents() ++ void PollEvents( int nTimeOut = -1 ) + { + epoll_event events[MaxEvents]; + +- int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, -1 ); ++ for ( ;; ) ++ { ++ int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, nTimeOut ); + +- if ( !m_bRunning ) +- return; ++ if ( !m_bRunning ) ++ return; + +- if ( nEventCount < 0 ) +- { +- g_WaitableLog.errorf_errno( "Failed to epoll_wait in CAsyncWaiter" ); +- return; +- } ++ if ( nEventCount < 0 ) ++ { ++ if ( errno == EAGAIN ) ++ continue; + +- for ( int i = 0; i < nEventCount; i++ ) +- { +- epoll_event &event = events[i]; ++ g_WaitableLog.errorf_errno( "Failed to epoll_wait in CAsyncWaiter" ); ++ return; ++ } ++ ++ for ( int i = 0; i < nEventCount; i++ ) ++ { ++ epoll_event &event = events[i]; + +- IWaitable *pWaitable = reinterpret_cast( event.data.ptr ); +- pWaitable->HandleEvents( event.events ); ++ IWaitable *pWaitable = reinterpret_cast( event.data.ptr ); ++ pWaitable->HandleEvents( event.events ); ++ } ++ ++ return; + } + } + +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 52f42ef42..5764c4b7b 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -1,6 +1,7 @@ + #pragma once + + #include "drm.hpp" ++#include "waitable.h" + + #include + #include +@@ -21,6 +22,8 @@ struct ignore; + struct steamcompmgr_win_t; + class MouseCursor; + ++extern LogScope xwm_log; ++ + struct focus_t + { + steamcompmgr_win_t *focusWindow; +@@ -49,7 +52,7 @@ struct CommitDoneList_t + std::vector< CommitDoneEntry_t > listCommitsDone; + }; + +-struct xwayland_ctx_t ++struct xwayland_ctx_t final : public gamescope::IWaitable + { + gamescope_xwayland_server_t *xwayland_server; + Display *dpy; +@@ -234,4 +237,24 @@ struct xwayland_ctx_t + Atom primarySelection; + Atom targets; + } atoms; ++ ++ bool HasQueuedEvents(); ++ ++ void Dispatch(); ++ ++ int GetFD() final ++ { ++ return XConnectionNumber( dpy ); ++ } ++ ++ void OnPollIn() final ++ { ++ Dispatch(); ++ } ++ ++ void OnPollHangUp() final ++ { ++ xwm_log.errorf( "XWayland server hung up! This is fatal. Aborting..." ); ++ abort(); ++ } + }; + +From ed6d387602e5da9cb1b8be3245a64d296371d9b6 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Thu, 7 Dec 2023 15:28:57 +0000 +Subject: [PATCH 033/134] vblankmanager: Port to timerfd + +Ports vblankmanager to be timerfd based, and also fxies issues +with re-arming at higher refresh rates. + +The old nudge thread method still exists (and is needed for VR), and +it has also been improved to fix the re-arming issue. + +The old method can be enabled with GAMESCOPE_DISABLE_TIMERFD. +--- + src/drm.cpp | 6 +- + src/rendervulkan.cpp | 2 +- + src/steamcompmgr.cpp | 98 ++++---- + src/steamcompmgr.hpp | 3 +- + src/vblankmanager.cpp | 521 +++++++++++++++++++++++++++++------------- + src/vblankmanager.hpp | 143 ++++++++++-- + 6 files changed, 538 insertions(+), 235 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index c975bfd8d..c2694f00f 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -283,7 +283,7 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi + + // This is the last vblank time + uint64_t vblanktime = sec * 1'000'000'000lu + usec * 1'000lu; +- vblank_mark_possible_vblank(vblanktime); ++ g_VBlankTimer.MarkVBlank( vblanktime, true ); + + // TODO: get the fbids_queued instance from data if we ever have more than one in flight + +@@ -1351,7 +1351,7 @@ void load_pnps(void) + fclose(f); + } + +-static bool env_to_bool(const char *env) ++bool env_to_bool(const char *env) + { + if (!env || !*env) + return false; +@@ -1758,7 +1758,7 @@ int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) + // 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. +- g_uVblankDrawTimeNS = get_time_in_nanos() - g_SteamCompMgrVBlankTime.pipe_write_time; ++ g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); + + if ( isPageFlip ) { + // Wait for flip handler to unlock +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 32b87ba5b..3e179c4b1 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -2600,7 +2600,7 @@ static void present_wait_thread_func( void ) + { + g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu ); + uint64_t vblanktime = get_time_in_nanos(); +- vblank_mark_possible_vblank( vblanktime ); ++ g_VBlankTimer.MarkVBlank( vblanktime, true ); + mangoapp_output_update( vblanktime ); + } + } +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 40961877a..33fa43438 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -139,8 +139,6 @@ extern float g_flHDRItmTargetNits; + + uint64_t g_lastWinSeq = 0; + +-extern std::atomic g_lastVblank; +- + static std::shared_ptr s_scRGB709To2020Matrix; + + std::string clipboard; +@@ -156,6 +154,16 @@ uint64_t timespec_to_nanos(struct timespec& spec) + return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec; + } + ++timespec nanos_to_timespec( uint64_t ulNanos ) ++{ ++ 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(); + +@@ -882,7 +890,7 @@ bool synchronize; + + std::mutex g_SteamCompMgrXWaylandServerMutex; + +-VBlankTimeInfo_t g_SteamCompMgrVBlankTime = {}; ++gamescope::VBlankTime g_SteamCompMgrVBlankTime = {}; + + uint64_t g_uCurrentBasePlaneCommitID = 0; + bool g_bCurrentBasePlaneIsFifo = false; +@@ -1194,9 +1202,7 @@ uint64_t get_time_in_nanos() + + void sleep_for_nanos(uint64_t nanos) + { +- timespec ts; +- ts.tv_sec = time_t(nanos / 1'000'000'000ul); +- ts.tv_nsec = long(nanos % 1'000'000'000ul); ++ timespec ts = nanos_to_timespec( nanos ); + nanosleep(&ts, nullptr); + } + +@@ -2736,7 +2742,7 @@ paint_all(bool async) + } + + // Update to let the vblank manager know we are currently compositing. +- g_bCurrentlyCompositing = bDoComposite; ++ g_VBlankTimer.UpdateWasCompositing( bDoComposite ); + + if ( bDoComposite == true ) + { +@@ -2859,7 +2865,7 @@ paint_all(bool async) + } + + // Update the time it took us to commit +- g_uVblankDrawTimeNS = get_time_in_nanos() - g_SteamCompMgrVBlankTime.pipe_write_time; ++ g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); + } + else + { +@@ -3139,7 +3145,7 @@ paint_all(bool async) + int ret = system(cmd); + + /* Above call may fail, ffmpeg returns 0 on success */ +- if (ret) { ++ if (ret) { + xwm_log.infof("Ffmpeg call return status %i", ret); + xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); + } else { +@@ -5587,6 +5593,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + focusDirty = true; + } + } ++#if 0 + if ( ev->atom == ctx->atoms.gamescopeTuneableVBlankRedZone ) + { + g_uVblankDrawBufferRedZoneNS = (uint64_t)get_prop( ctx, ctx->root, ctx->atoms.gamescopeTuneableVBlankRedZone, g_uDefaultVBlankRedZone ); +@@ -5595,6 +5602,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + { + 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 ); +@@ -6397,7 +6405,7 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vb + { + std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); + +- uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time; ++ 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; +@@ -6454,7 +6462,7 @@ void handle_done_commits_xdg() + { + std::lock_guard lock( g_steamcompmgr_xdg_done_commits.listCommitsDoneLock ); + +- uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time; ++ uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; + + // commits that were not ready to be presented based on their display timing. + std::vector< CommitDoneEntry_t > commits_before_their_time; +@@ -6488,7 +6496,7 @@ void handle_done_commits_xdg() + + void handle_presented_for_window( steamcompmgr_win_t* w ) + { +- uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time; ++ uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; + + uint64_t refresh_cycle = g_nSteamCompMgrTargetFPS && steamcompmgr_window_should_limit_fps( w ) + ? g_SteamCompMgrLimitedAppRefreshCycle +@@ -7012,41 +7020,6 @@ void xwayland_ctx_t::Dispatch() + } + } + +-static bool +-dispatch_vblank( int fd ) +-{ +- bool vblank = false; +- for (;;) +- { +- VBlankTimeInfo_t vblanktime = {}; +- ssize_t ret = read( fd, &vblanktime, sizeof( vblanktime ) ); +- if ( ret < 0 ) +- { +- if ( errno == EAGAIN ) +- break; +- +- xwm_log.errorf_errno( "steamcompmgr: dispatch_vblank: read failed" ); +- break; +- } +- +- g_SteamCompMgrVBlankTime = vblanktime; +- +- uint64_t diff = get_time_in_nanos() - vblanktime.pipe_write_time; +- +- // give it 1 ms of slack from pipe to steamcompmgr... maybe too long +- if ( diff > 1'000'000ul ) +- { +- gpuvis_trace_printf( "ignored stale vblank" ); +- } +- else +- { +- gpuvis_trace_printf( "got vblank" ); +- vblank = true; +- } +- } +- return vblank; +-} +- + struct rgba_t + { + uint8_t r,g,b,a; +@@ -7690,9 +7663,6 @@ steamcompmgr_main(int argc, char **argv) + vrsession_steam_mode( steamMode ); + #endif + +- int vblankFD = vblank_init(); +- assert( vblankFD >= 0 ); +- + std::unique_lock xwayland_server_guard(g_SteamCompMgrXWaylandServerMutex); + + // Initialize any xwayland ctxs we have +@@ -7726,12 +7696,8 @@ steamcompmgr_main(int argc, char **argv) + } + + bool vblank = false; +- g_SteamCompMgrWaiter.AddWaitable( +- new gamescope::CFunctionWaitable{ vblankFD, [ vblankFD, &vblank ]() +- { +- vblank = dispatch_vblank( vblankFD ); +- }} +- ); ++ g_SteamCompMgrWaiter.AddWaitable( &g_VBlankTimer ); ++ g_VBlankTimer.RearmTimer( true ); + + { + gamescope_xwayland_server_t *pServer = NULL; +@@ -7777,6 +7743,12 @@ steamcompmgr_main(int argc, char **argv) + + g_SteamCompMgrWaiter.PollEvents(); + ++ if ( std::optional pendingVBlank = g_VBlankTimer.ProcessVBlank() ) ++ { ++ g_SteamCompMgrVBlankTime = *pendingVBlank; ++ vblank = true; ++ } ++ + if ( g_bRun == false ) + { + break; +@@ -8068,13 +8040,13 @@ steamcompmgr_main(int argc, char **argv) + // 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 bNeedsSyncFlip = bForceSyncFlip || g_bCurrentlyCompositing || nIgnoredOverlayRepaints; ++ const bool bNeedsSyncFlip = bForceSyncFlip || g_VBlankTimer.WasCompositing() || nIgnoredOverlayRepaints; + const bool bDoAsyncFlip = ( ((g_nAsyncFlipsEnabled >= 1) && g_bSupportsAsyncFlips && bSurfaceWantsAsync && !bHasOverlay) || bVRR ) && !bSteamOverlayOpen && !bNeedsSyncFlip; + + bool bShouldPaint = false; + if ( bDoAsyncFlip ) + { +- if ( hasRepaint && !g_bCurrentlyCompositing ) ++ if ( hasRepaint && !g_VBlankTimer.WasCompositing() ) + bShouldPaint = true; + } + else +@@ -8110,6 +8082,16 @@ steamcompmgr_main(int argc, char **argv) + } + } + ++ if ( vblank ) ++ { ++ // 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. ++ g_VBlankTimer.RearmTimer( true ); ++ } ++ + update_vrr_atoms(root_ctx, false, &flush_root); + + if (global_focus.cursor) +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index b1701c97c..e73a70767 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -12,6 +12,7 @@ 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); + +@@ -155,7 +156,7 @@ 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 ); + +-extern VBlankTimeInfo_t g_SteamCompMgrVBlankTime; ++extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; + extern pid_t focusWindow_pid; + + void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server); +diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp +index 925fda3a9..9ccb130eb 100644 +--- a/src/vblankmanager.cpp ++++ b/src/vblankmanager.cpp +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + + #include "gpuvis_trace_utils.h" + +@@ -24,235 +25,441 @@ + #include "vr_session.hpp" + #endif + +-static int g_vblankPipe[2]; ++LogScope g_VBlankLog("vblank"); + +-std::atomic g_lastVblank; ++// #define VBLANK_DEBUG + +-// 3ms by default -- a good starting value. +-const uint64_t g_uStartingDrawTime = 3'000'000; ++extern bool env_to_bool(const char *env); + +-// This is the last time a draw took. +-std::atomic g_uVblankDrawTimeNS = { g_uStartingDrawTime }; ++namespace gamescope ++{ ++ CVBlankTimer::CVBlankTimer() ++ { ++ m_ulTargetVBlank = get_time_in_nanos(); ++ m_ulLastVBlank = m_ulTargetVBlank; + +-// 1.3ms by default. (g_uDefaultMinVBlankTime) +-// 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. +-uint64_t g_uMinVblankTime = g_uDefaultMinVBlankTime; ++ const bool bShouldUseTimerFD = !BIsVRSession() || env_to_bool( "GAMESCOPE_DISABLE_TIMERFD" ); + +-// Tuneable +-// 0.3ms by default. (g_uDefaultVBlankRedZone) +-// This is the leeway we always apply to our buffer. +-uint64_t g_uVblankDrawBufferRedZoneNS = g_uDefaultVBlankRedZone; ++ if ( bShouldUseTimerFD ) ++ { ++ g_VBlankLog.infof( "Using timerfd." ); ++ m_nTimerFD = timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC ); ++ if ( m_nTimerFD < 0 ) ++ { ++ g_VBlankLog.errorf_errno( "Failed to create VBlankTimer timerfd." ); ++ abort(); ++ } ++ } ++ else ++ { ++ g_VBlankLog.infof( "Using nudge thread." ); + +-// Tuneable +-// 93% by default. (g_uVBlankRateOfDecayPercentage) +-// The rate of decay (as a percentage) of the rolling average -> current draw time +-uint64_t g_uVBlankRateOfDecayPercentage = g_uDefaultVBlankRateOfDecayPercentage; ++ if ( pipe2( m_nNudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) ++ { ++ g_VBlankLog.errorf_errno( "Failed to create VBlankTimer pipe." ); ++ abort(); ++ } + +-const uint64_t g_uVBlankRateOfDecayMax = 1000; ++#if HAVE_OPENVR ++ if ( BIsVRSession() ) ++ { ++ std::thread vblankThread( [this]() { this->VRNudgeThread(); } ); ++ vblankThread.detach(); ++ } ++ else ++#endif ++ { ++ std::thread vblankThread( [this]() { this->NudgeThread(); } ); ++ vblankThread.detach(); ++ } ++ } ++ } + +-static std::atomic g_uRollingMaxDrawTime = { g_uStartingDrawTime }; ++ CVBlankTimer::~CVBlankTimer() ++ { ++ std::unique_lock lock( m_ScheduleMutex ); + +-std::atomic g_bCurrentlyCompositing = { false }; ++ m_bRunning = false; + +-// 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 clocks. Pick a sane minimum draw time. +-const uint64_t g_uVBlankDrawTimeMinCompositing = 2'400'000; ++ m_bArmed = true; ++ m_bArmed.notify_all(); + +-//#define VBLANK_DEBUG ++ if ( m_nTimerFD >= 0 ) ++ { ++ close( m_nTimerFD ); ++ m_nTimerFD = 0; ++ } + +-uint64_t vblank_next_target( uint64_t offset ) +-{ +- const int refresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; +- const uint64_t nsecInterval = 1'000'000'000ul / refresh; ++ for ( int i = 0; i < 2; i++ ) ++ { ++ if ( m_nNudgePipe[ i ] >= 0 ) ++ { ++ close ( m_nNudgePipe[ i ] ); ++ m_nNudgePipe[ i ] = 0; ++ } ++ } ++ } + +- uint64_t lastVblank = g_lastVblank - offset; ++ int CVBlankTimer::GetRefresh() const ++ { ++ return g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; ++ } + +- uint64_t now = get_time_in_nanos(); +- uint64_t targetPoint = lastVblank + nsecInterval; +- while ( targetPoint < now ) +- targetPoint += nsecInterval; ++ uint64_t CVBlankTimer::GetLastVBlank() const ++ { ++ return m_ulLastVBlank; ++ } + +- return targetPoint; +-} ++ uint64_t CVBlankTimer::GetNextVBlank( uint64_t ulOffset ) const ++ { ++ const uint64_t ulIntervalNSecs = kSecInNanoSecs / GetRefresh(); ++ const uint64_t ulNow = get_time_in_nanos(); + +-void vblankThreadRun( void ) +-{ +- pthread_setname_np( pthread_self(), "gamescope-vblk" ); ++ uint64_t ulTargetPoint = GetLastVBlank() + ulIntervalNSecs - ulOffset; + +- // Start off our average with our starting draw time. +- uint64_t rollingMaxDrawTime = g_uStartingDrawTime; ++ while ( ulTargetPoint < ulNow ) ++ ulTargetPoint += ulIntervalNSecs; + +- const uint64_t range = g_uVBlankRateOfDecayMax; +- while ( true ) ++ return ulTargetPoint; ++ } ++ ++ VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) + { +- const int refresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; +- const uint64_t nsecInterval = 1'000'000'000ul / refresh; +- // The redzone is relative to 60Hz, scale it by our +- // target refresh so we don't miss submitting for vblank in DRM. +- // (This fixes 4K@30Hz screens) +- const uint64_t nsecToSec = 1'000'000'000ul; +- const drm_screen_type screen_type = drm_get_screen_type( &g_DRM ); +- const uint64_t redZone = screen_type == DRM_SCREEN_TYPE_INTERNAL +- ? g_uVblankDrawBufferRedZoneNS +- : ( g_uVblankDrawBufferRedZoneNS * 60 * nsecToSec ) / ( refresh * nsecToSec ); +- +- uint64_t offset; ++ const drm_screen_type eScreenType = drm_get_screen_type( &g_DRM ); ++ ++ const int nRefreshRate = GetRefresh(); ++ const uint64_t ulRefreshInterval = kSecInNanoSecs / nRefreshRate; ++ // 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 == DRM_SCREEN_TYPE_INTERNAL ++ ? m_ulVBlankDrawBufferRedZone ++ : ( m_ulVBlankDrawBufferRedZone * 60 * kSecInNanoSecs ) / ( nRefreshRate * kSecInNanoSecs ); ++ + bool bVRR = drm_get_vrr_in_use( &g_DRM ); ++ uint64_t ulOffset = 0; + if ( !bVRR ) + { +- const uint64_t alpha = g_uVBlankRateOfDecayPercentage; ++ const uint64_t ulDecayAlpha = m_ulVBlankRateOfDecayPercentage; // eg. 980 = 98% + +- uint64_t drawTime = g_uVblankDrawTimeNS; ++ uint64_t ulDrawTime = m_ulLastDrawTime; ++ /// See comment of m_ulVBlankDrawTimeMinCompositing. ++ if ( m_bCurrentlyCompositing ) ++ ulDrawTime = std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); + +- if ( g_bCurrentlyCompositing ) +- drawTime = std::max(drawTime, g_uVBlankDrawTimeMinCompositing); +- // This is a rolling average when drawTime < rollingMaxDrawTime, +- // and a a max when drawTime > rollingMaxDrawTime. ++ 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. +- if ( int64_t(drawTime) - int64_t(redZone / 2) > int64_t(rollingMaxDrawTime) ) +- rollingMaxDrawTime = drawTime; ++ // 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 +- rollingMaxDrawTime = ( ( alpha * rollingMaxDrawTime ) + ( range - alpha ) * drawTime ) / range; ++ 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. +- rollingMaxDrawTime = std::min( rollingMaxDrawTime, nsecInterval - redZone ); ++ ulNewRollingDrawTime = std::min( ulNewRollingDrawTime, ulRefreshInterval - ulRedZone ); + +- g_uRollingMaxDrawTime = rollingMaxDrawTime; ++ // 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; + +- offset = rollingMaxDrawTime + redZone; ++ ulOffset = ulNewRollingDrawTime + ulRedZone; ++ ++ if ( !bPreemptive ) ++ VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); + } + else + { +- // VRR: +- // Just ensure that if we missed a frame due to already +- // having a page flip in-flight, that we flush it out with this. +- // Nothing fancy needed, just need to get on the other side of the page flip. +- // +- // We don't use any of the rolling times due to them varying given our +- // 'vblank' time is varying. +- g_uRollingMaxDrawTime = g_uStartingDrawTime; +- +- offset = 1'000'000 + redZone; ++ // See above. ++ if ( !bPreemptive ) ++ { ++ // Reset the max draw time to default, it is unused for VRR. ++ m_ulRollingMaxDrawTime = kStartingVBlankDrawTime; ++ } ++ ++ // TODO(Josh): We can probably do better than this for VRR. ++ uint64_t ulDrawTime = kVRRFlushingDrawTime; ++ /// See comment of m_ulVBlankDrawTimeMinCompositing. ++ if ( m_bCurrentlyCompositing ) ++ ulDrawTime = std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); ++ ++ ulOffset = ulDrawTime + ulRedZone; ++ ++ if ( !bPreemptive ) ++ VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); + } + +-#ifdef VBLANK_DEBUG +- // Debug stuff for logging missed vblanks +- static uint64_t vblankIdx = 0; +- static uint64_t lastDrawTime = g_uVblankDrawTimeNS; +- static uint64_t lastOffset = g_uVblankDrawTimeNS + redZone; ++ const uint64_t ulScheduledWakeupPoint = GetNextVBlank( ulOffset ); ++ const uint64_t ulTargetVBlank = ulScheduledWakeupPoint + ulOffset; + +- if ( vblankIdx++ % 300 == 0 || drawTime > lastOffset ) ++ VBlankScheduleTime schedule = + { +- if ( drawTime > lastOffset ) +- fprintf( stderr, " !! missed vblank " ); ++ .ulTargetVBlank = ulTargetVBlank, ++ .ulScheduledWakeupPoint = ulScheduledWakeupPoint, ++ }; ++ return schedule; ++ } + +- fprintf( stderr, "redZone: %.2fms decayRate: %lu%% - rollingMaxDrawTime: %.2fms lastDrawTime: %.2fms lastOffset: %.2fms - drawTime: %.2fms offset: %.2fms\n", +- redZone / 1'000'000.0, +- g_uVBlankRateOfDecayPercentage, +- rollingMaxDrawTime / 1'000'000.0, +- lastDrawTime / 1'000'000.0, +- lastOffset / 1'000'000.0, +- drawTime / 1'000'000.0, +- offset / 1'000'000.0 ); ++ 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. ++ RearmTimer( false ); + } ++ } + +- lastDrawTime = drawTime; +- lastOffset = offset; +-#endif ++ bool CVBlankTimer::WasCompositing() const ++ { ++ return m_bCurrentlyCompositing; ++ } ++ ++ void CVBlankTimer::UpdateWasCompositing( bool bCompositing ) ++ { ++ m_bCurrentlyCompositing = bCompositing; ++ } ++ ++ void CVBlankTimer::UpdateLastDrawTime( uint64_t ulNanos ) ++ { ++ m_ulLastDrawTime = ulNanos; ++ } + +- uint64_t targetPoint = vblank_next_target( offset ); ++ void CVBlankTimer::WaitToBeArmed() ++ { ++ // Wait for m_bArmed to change *from* false. ++ m_bArmed.wait( false ); ++ } + +- sleep_until_nanos( targetPoint ); ++ void CVBlankTimer::RearmTimer( bool bPreemptive ) ++ { ++ std::unique_lock lock( m_ScheduleMutex ); + +- VBlankTimeInfo_t time_info = +- { +- .target_vblank_time = targetPoint + offset, +- .pipe_write_time = get_time_in_nanos(), +- }; ++ // If we're pre-emptively re-arming, don't ++ // do anything if we are already armed. ++ if ( bPreemptive && m_bArmed ) ++ return; + +- ssize_t ret = write( g_vblankPipe[ 1 ], &time_info, sizeof( time_info ) ); +- if ( ret <= 0 ) +- { +- perror( "vblankmanager: write failed" ); +- } +- else ++ m_bArmed = true; ++ m_bArmed.notify_all(); ++ ++ if ( UsingTimerFD() ) + { +- gpuvis_trace_printf( "sent vblank" ); ++ m_TimerFDSchedule = CalcNextWakeupTime( bPreemptive ); ++ ++ itimerspec timerspec = ++ { ++ .it_interval = timespec{}, ++ .it_value = nanos_to_timespec( m_TimerFDSchedule.ulScheduledWakeupPoint ), ++ }; ++ if ( timerfd_settime( m_nTimerFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) ++ g_VBlankLog.errorf_errno( "timerfd_settime failed!" ); + } +- +- // Get on the other side of it now +- sleep_for_nanos( offset + 1'000'000 ); + } +-} + +-#if HAVE_OPENVR +-void vblankThreadVR() +-{ +- pthread_setname_np( pthread_self(), "gamescope-vblkvr" ); ++ bool CVBlankTimer::UsingTimerFD() const ++ { ++ return m_nTimerFD >= 0; ++ } ++ ++ int CVBlankTimer::GetFD() ++ { ++ return UsingTimerFD() ? m_nTimerFD : m_nNudgePipe[ 0 ]; ++ } + +- while ( true ) ++ void CVBlankTimer::OnPollIn() + { +- vrsession_wait_until_visible(); ++ if ( UsingTimerFD() ) ++ { ++ std::unique_lock lock( m_ScheduleMutex ); + +- // Includes redzone. +- vrsession_framesync( ~0u ); ++ // Disarm the timer if it was armed. ++ if ( !m_bArmed.exchange( false ) ) ++ return; + +- uint64_t now = get_time_in_nanos(); ++ uint64_t ulNow = get_time_in_nanos(); + +- VBlankTimeInfo_t time_info = +- { +- .target_vblank_time = now + 3'000'000, // not right. just a stop-gap for now. +- .pipe_write_time = now, +- }; ++ m_PendingVBlank = VBlankTime ++ { ++ .schedule = m_TimerFDSchedule, ++ .ulWakeupTime = ulNow, ++ }; ++#ifdef VBLANK_DEBUG ++ fprintf( stderr, "wakeup: %lu\n", ulNow ); ++#endif + +- ssize_t ret = write( g_vblankPipe[ 1 ], &time_info, sizeof( time_info ) ); +- if ( ret <= 0 ) +- { +- perror( "vblankmanager: write failed" ); ++ gpuvis_trace_printf( "vblank timerfd wakeup" ); ++ ++ // Disarm timer. ++ itimerspec timerspec{}; ++ if ( timerfd_settime( m_nTimerFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) ++ g_VBlankLog.errorf_errno( "timerfd_settime failed!" ); + } + else + { +- gpuvis_trace_printf( "sent vblank" ); ++ 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." ); ++ RearmTimer( true ); ++ return; ++ } ++ else if ( ret != sizeof( VBlankTime ) ) ++ { ++ g_VBlankLog.errorf( "Nudge pipe had less data than sizeof( VBlankTime ). Pre-emptively re-arming." ); ++ RearmTimer( 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." ); ++ RearmTimer( true ); ++ return; ++ } ++ ++ gpuvis_trace_printf( "got vblank" ); ++ m_PendingVBlank = time; + } + } +-} +-#endif + +-int vblank_init( void ) +-{ +- if ( pipe2( g_vblankPipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) ++ void CVBlankTimer::VBlankDebugSpew( uint64_t ulOffset, uint64_t ulDrawTime, uint64_t ulRedZone ) + { +- perror( "vblankmanager: pipe failed" ); +- return -1; ++#ifdef VBLANK_DEBUG ++ 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 ) ++ fprintf( stderr, " !! missed vblank " ); ++ ++ fprintf( stderr, "redZone: %.2fms decayRate: %lu%% - rollingMaxDrawTime: %.2fms lastDrawTime: %.2fms lastOffset: %.2fms - drawTime: %.2fms offset: %.2fms\n", ++ 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; ++#endif + } +- +- g_lastVblank = get_time_in_nanos(); + + #if HAVE_OPENVR +- if ( BIsVRSession() ) ++ void CVBlankTimer::VRNudgeThread() + { +- std::thread vblankThread( vblankThreadVR ); +- vblankThread.detach(); +- return g_vblankPipe[ 0 ]; ++ pthread_setname_np( pthread_self(), "gamescope-vblkvr" ); ++ ++ for ( ;; ) ++ { ++ vrsession_wait_until_visible(); ++ ++ // Includes redzone. ++ vrsession_framesync( ~0u ); ++ ++ uint64_t ulWakeupTime = get_time_in_nanos(); ++ ++ VBlankTime timeInfo = ++ { ++ .schedule = ++ { ++ .ulTargetVBlank = ulWakeupTime + 3'000'000, // Not right. just a stop-gap for now. ++ .ulScheduledWakeupPoint = ulWakeupTime, ++ }, ++ .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)" ); ++ } ++ } + } + #endif + +- std::thread vblankThread( vblankThreadRun ); +- vblankThread.detach(); +- return g_vblankPipe[ 0 ]; +-} ++ void CVBlankTimer::NudgeThread() ++ { ++ pthread_setname_np( pthread_self(), "gamescope-vblk" ); + +-void vblank_mark_possible_vblank( uint64_t nanos ) +-{ +- g_lastVblank = nanos; ++ for ( ;; ) ++ { ++ WaitToBeArmed(); ++ ++ if ( !m_bRunning ) ++ return; ++ ++ VBlankScheduleTime schedule = CalcNextWakeupTime( false ); ++ sleep_until_nanos( schedule.ulScheduledWakeupPoint ); ++ 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 g_VBlankTimer{}; ++ +diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp +index 3c36ce07a..30a8c4abc 100644 +--- a/src/vblankmanager.hpp ++++ b/src/vblankmanager.hpp +@@ -1,26 +1,139 @@ + #pragma once + +-// Try to figure out when vblank is and notify steamcompmgr to render some time before it ++#include ++#include "waitable.h" + +-struct VBlankTimeInfo_t ++namespace gamescope + { +- uint64_t target_vblank_time; +- uint64_t pipe_write_time; +-}; ++ struct VBlankScheduleTime ++ { ++ // The expected time for the vblank we want to target. ++ uint64_t ulTargetVBlank = 0; ++ // The vblank offset by the redzone/scheduling calculation. ++ // This is when we want to wake-up by to meet that vblank time above. ++ uint64_t ulScheduledWakeupPoint = 0; ++ }; + +-int vblank_init( void ); ++ struct VBlankTime ++ { ++ VBlankScheduleTime schedule; ++ // This is when we woke-up either by the timerfd poll ++ // or on the nudge thread. We use this to feed-back into ++ // the draw time so we automatically account for th ++ // CPU scheduler quantums. ++ uint64_t ulWakeupTime = 0; ++ }; + +-void vblank_mark_possible_vblank( uint64_t nanos ); ++ class CVBlankTimer : public gamescope::IWaitable ++ { ++ public: ++ static constexpr uint64_t kSecInNanoSecs = 1'000'000'000ul; ++ // 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 kVBlankRateOfDecayMax = 1000ul; // 100% + +-uint64_t vblank_next_target( uint64_t offset = 0 ); ++ static constexpr uint64_t kVRRFlushingDrawTime = 1'000'000; // Could possibly be lower, like 300'000 or something. + +-extern std::atomic g_uVblankDrawTimeNS; ++ CVBlankTimer(); ++ ~CVBlankTimer(); + +-const unsigned int g_uDefaultVBlankRedZone = 1'650'000; +-const unsigned int g_uDefaultMinVBlankTime = 350'000; // min vblank time for fps limiter to care about +-const unsigned int g_uDefaultVBlankRateOfDecayPercentage = 980; ++ int GetRefresh() const; ++ uint64_t GetLastVBlank() const; ++ uint64_t GetNextVBlank( uint64_t ulOffset ) const; + +-extern uint64_t g_uVblankDrawBufferRedZoneNS; +-extern uint64_t g_uVBlankRateOfDecayPercentage; ++ VBlankScheduleTime CalcNextWakeupTime( bool bPreemptive ); ++ void Reschedule(); + +-extern std::atomic g_bCurrentlyCompositing; ++ std::optional ProcessVBlank(); ++ void MarkVBlank( uint64_t ulNanos, bool bReArmTimer ); ++ ++ bool WasCompositing() const; ++ void UpdateWasCompositing( bool bCompositing ); ++ void UpdateLastDrawTime( uint64_t ulNanos ); ++ ++ void WaitToBeArmed(); ++ void RearmTimer( bool bPreemptive ); ++ ++ bool UsingTimerFD() const; ++ int GetFD() final; ++ void OnPollIn() final; ++ private: ++ 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 }; ++ ++ std::optional m_PendingVBlank; ++ ++ // Should have 0 contest, but just to be safe. ++ // This also covers setting of m_bArmed, etc ++ // so we keep in sequence. ++ // 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; ++ VBlankScheduleTime m_TimerFDSchedule{}; ++ int m_nTimerFD = -1; ++ ++ std::thread m_NudgeThread; ++ int m_nNudgePipe[2] = { -1, -1 }; ++ ++ ///////////////////////////// ++ // Scheduling bits and bobs. ++ ///////////////////////////// ++ ++ // Are we currently compositing? We may need ++ // to push back to avoid clock feedback loops if so. ++ // This is fed-back from steamcompmgr. ++ 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. ++ std::atomic m_ulLastDrawTime = { kStartingVBlankDrawTime }; ++ ++ ////////////////////////////////// ++ // VBlank timing tuneables below! ++ ////////////////////////////////// ++ ++ // Internal rolling peak exponential avg. draw time. ++ // This is updated in CalcNextWakeupTime when not ++ // 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. ++ // 1.3ms by default. (kDefaultMinVBlankTime) ++ uint64_t m_ulMinVBlankTime = kDefaultMinVBlankTime; ++ ++ // The leeway we always apply to our buffer. ++ // 0.3ms by default. (kDefaultVBlankRedZone) ++ 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; ++ ++#if HAVE_OPENVR ++ void VRNudgeThread(); ++#endif ++ void NudgeThread(); ++ }; ++} ++ ++extern gamescope::CVBlankTimer g_VBlankTimer; + +From 89cf3b0bb0eecf7ea1f5bd702a95d47b8bd86730 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Thu, 7 Dec 2023 16:26:38 +0000 +Subject: [PATCH 034/134] vblankmanager: Treat wakeup time as scheduled wakeup + point for timerfd path + +Because we are using timerfd, ulWakeupTime should actually be the target +point of the timerfd when using that, not the the current time from +OnPollIn, so we can account for the scheduling quantums from the target +wakeup time and other work (like we were wrt the pipe write before) +--- + src/vblankmanager.cpp | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp +index 9ccb130eb..df77bdf33 100644 +--- a/src/vblankmanager.cpp ++++ b/src/vblankmanager.cpp +@@ -297,14 +297,21 @@ namespace gamescope + if ( !m_bArmed.exchange( false ) ) + return; + +- uint64_t ulNow = get_time_in_nanos(); + + m_PendingVBlank = VBlankTime + { + .schedule = m_TimerFDSchedule, +- .ulWakeupTime = ulNow, ++ // 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, + }; + #ifdef VBLANK_DEBUG ++ uint64_t ulNow = get_time_in_nanos(); + fprintf( stderr, "wakeup: %lu\n", ulNow ); + #endif + + +From b7c828b38675a11d5d130024a8f6c9703fe5b7fd Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Thu, 7 Dec 2023 21:23:42 +0000 +Subject: [PATCH 035/134] steamcompmgr: Fix repaint checking for fade-outs + +--- + src/steamcompmgr.cpp | 12 +++++------- + 1 file changed, 5 insertions(+), 7 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 33fa43438..6add8f427 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -8021,6 +8021,11 @@ steamcompmgr_main(int argc, char **argv) + 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; ++ + static int nIgnoredOverlayRepaints = 0; + + const bool bVRR = drm_get_vrr_in_use( &g_DRM ); +@@ -8073,13 +8078,6 @@ steamcompmgr_main(int argc, char **argv) + hasRepaint = false; + hasRepaintNonBasePlane = false; + nIgnoredOverlayRepaints = 0; +- +- // If we're in the middle of a fade, pump an event into the loop to +- // make sure we keep pushing frames even if the app isn't updating. +- if ( is_fading_out() ) +- { +- nudge_steamcompmgr(); +- } + } + + if ( vblank ) + +From e4f1e14063e60094f278e8254c116a4bc45282d7 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 12 Dec 2023 22:13:24 +0000 +Subject: [PATCH 036/134] rendervulkan: Don't return incompatible format + screenshot textures + +--- + src/rendervulkan.cpp | 7 ++++++- + src/rendervulkan.hpp | 3 +++ + 2 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 3e179c4b1..77c173033 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -1770,6 +1770,7 @@ static VkImageViewType VulkanImageTypeToViewType(VkImageType type) + + 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 ) + { ++ m_drmFormat = drmFormat; + VkResult res = VK_ERROR_INITIALIZATION_FAILED; + + VkImageTiling tiling = (flags.bMappable || flags.bLinear) ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL; +@@ -2330,6 +2331,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + + 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; +@@ -3314,7 +3316,10 @@ std::shared_ptr vulkan_acquire_screenshot_texture(uint32_t width + assert( bSuccess ); + } + +- if (pScreenshotImage.use_count() > 1 || width != pScreenshotImage->width() || height != pScreenshotImage->height()) ++ if (pScreenshotImage.use_count() > 1 || ++ width != pScreenshotImage->width() || ++ height != pScreenshotImage->height() || ++ drmFormat != pScreenshotImage->drmFormat()) + continue; + + return pScreenshotImage; +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index c60b796a1..651a1ed1b 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -180,6 +180,7 @@ class CVulkanTexture + inline bool swapchainImage() { return m_bSwapchain; } + 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; } +@@ -206,6 +207,8 @@ class CVulkanTexture + bool m_bExternal = false; + bool m_bSwapchain = false; + ++ uint32_t m_drmFormat = DRM_FORMAT_INVALID; ++ + VkImage m_vkImage = VK_NULL_HANDLE; + VkDeviceMemory m_vkImageMemory = VK_NULL_HANDLE; + + +From 5ca516afd90eb8343fe0906851b805d04ba15b00 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 12 Dec 2023 22:19:23 +0000 +Subject: [PATCH 037/134] pipewire: Allocate buffers for pipewire buffers + directly + +Keep their lifetime in tandem with that, avoids running out of cached buffers and keeping those around too long. +--- + src/pipewire.cpp | 20 ++++++++++++++++++-- + 1 file changed, 18 insertions(+), 2 deletions(-) + +diff --git a/src/pipewire.cpp b/src/pipewire.cpp +index 236e8ac7e..9461dc6ca 100644 +--- a/src/pipewire.cpp ++++ b/src/pipewire.cpp +@@ -1,3 +1,4 @@ ++ + #include + #include + #include +@@ -473,8 +474,23 @@ static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffe + + uint32_t drmFormat = spa_format_to_drm(state->video_info.format); + +- buffer->texture = vulkan_acquire_screenshot_texture(s_nCaptureWidth, s_nCaptureHeight, is_dmabuf, drmFormat, colorspace); +- assert(buffer->texture != nullptr); ++ buffer->texture = std::make_shared(); ++ 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(); + +From 24bdce9e2e0e25024b1a65ebc974f901685b9f60 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 12 Dec 2023 22:19:38 +0000 +Subject: [PATCH 038/134] rendervulkan: Lower pScreenshotImages to 2 + +No need for as many now pipewire is not using this. +--- + src/rendervulkan.hpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index 651a1ed1b..d452c5bbc 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -495,7 +495,7 @@ struct VulkanOutput_t + VkFormat outputFormat = VK_FORMAT_UNDEFINED; + VkFormat outputFormatOverlay = VK_FORMAT_UNDEFINED; + +- std::array, 9> pScreenshotImages; ++ std::array, 2> pScreenshotImages; + + // NIS and FSR + std::shared_ptr tmpOutput; + +From 5caf3e139b587f2361d85f34ca881b9ce9dd5109 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 12 Dec 2023 22:25:02 +0000 +Subject: [PATCH 039/134] pipewire: Make push_pipewire_buffer in_buffer + exchange non-fatal + +--- + src/pipewire.cpp | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/pipewire.cpp b/src/pipewire.cpp +index 9461dc6ca..55505693b 100644 +--- a/src/pipewire.cpp ++++ b/src/pipewire.cpp +@@ -730,7 +730,10 @@ struct pipewire_buffer *dequeue_pipewire_buffer(void) + void push_pipewire_buffer(struct pipewire_buffer *buffer) + { + struct pipewire_buffer *old = in_buffer.exchange(buffer); +- assert(old == nullptr); ++ if ( old != nullptr ) ++ { ++ pwr_log.errorf_errno("push_pipewire_buffer: Already had a buffer?!"); ++ } + nudge_pipewire(); + } + + +From cbb1646359034b04bc36ce7788fded40429ccdbd Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 12 Dec 2023 22:33:29 +0000 +Subject: [PATCH 040/134] shaders: Fix rgb to nv12 being off by half a texel + +--- + src/shaders/cs_rgb_to_nv12.comp | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/shaders/cs_rgb_to_nv12.comp b/src/shaders/cs_rgb_to_nv12.comp +index 622965cb9..a15a3d4df 100644 +--- a/src/shaders/cs_rgb_to_nv12.comp ++++ b/src/shaders/cs_rgb_to_nv12.comp +@@ -49,12 +49,12 @@ void main() { + + // todo: fix + if (all(lessThan(thread_id.xy, ivec2(u_halfExtent.x, u_halfExtent.y)))) { +- ivec2 offset_table[4] = { +- ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(1, 1), ++ vec2 offset_table[4] = { ++ vec2(0, 0), vec2(1, 0), vec2(0, 1), vec2(1, 1), + }; + + ivec2 chroma_uv = thread_id.xy; +- ivec2 luma_uv = thread_id.xy * 2; ++ vec2 luma_uv = vec2(thread_id.xy * 2) + vec2(0.5f, 0.5f); + + vec3 color[4] = { + sampleLayer(0, vec2(luma_uv.x + offset_table[0].x, luma_uv.y + offset_table[0].y)).rgb, +@@ -69,7 +69,7 @@ void main() { + + for (int i = 0; i < 4; i++) { + float y = applyColorMatrix(color[i], u_outputCTM).x; +- imageStore(dst_luma, luma_uv + offset_table[i], vec4(y, 0.0f, 0.0f, 1.0f)); ++ imageStore(dst_luma, ivec2(luma_uv + offset_table[i]), vec4(y, 0.0f, 0.0f, 1.0f)); + } + } + } + +From d2a396ec235cd4e86437d1b3a205b84f93fc62b9 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 12 Dec 2023 23:13:46 +0000 +Subject: [PATCH 041/134] steamcompmgr: Handle pipewire stream after drm_commit + +Avoids it getting in the way of us doing the actual preparation on-device and missing vblank +--- + src/rendervulkan.cpp | 40 ++++------------ + src/rendervulkan.hpp | 5 +- + src/steamcompmgr.cpp | 108 +++++++++++++++++++++++-------------------- + 3 files changed, 69 insertions(+), 84 deletions(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 77c173033..8940b7704 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -3571,7 +3571,7 @@ void bind_all_layers(CVulkanCmdBuffer* cmdBuffer, const struct FrameInfo_t *fram + } + } + +-bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ) ++std::optional vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ) + { + auto cmdBuffer = g_device.commandBuffer(); + +@@ -3588,28 +3588,14 @@ bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptrdispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); + + uint64_t sequence = g_device.submit(std::move(cmdBuffer)); +- g_device.wait(sequence); +- +- return true; ++ return sequence; + } + + extern std::string g_reshade_effect; + extern uint32_t g_reshade_technique_idx; + +-std::unique_ptr defer_wait_thread; +-uint64_t defer_sequence = 0; +- +-bool vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture, bool partial, bool defer, std::shared_ptr pOutputOverride, bool increment ) ++std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture, bool partial, std::shared_ptr pOutputOverride, bool increment ) + { +- if ( defer_wait_thread ) +- { +- defer_wait_thread->join(); +- defer_wait_thread = nullptr; +- +- g_device.resetCmdBuffers(defer_sequence); +- defer_sequence = 0; +- } +- + EOTF outputTF = g_ColorMgmt.current.outputEncodingEOTF; + if (!frameInfo->applyOutputColorMgmt) + outputTF = EOTF_Count; //Disable blending stuff. +@@ -3830,25 +3816,17 @@ bool vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr([sequence] +- { +- g_device.wait(sequence, false); +- }); +- defer_sequence = sequence; +- } +- else +- { +- g_device.wait(sequence); +- } +- + if ( !BIsSDLSession() && pOutputOverride == nullptr && increment ) + { + g_output.nOutImage = ( g_output.nOutImage + 1 ) % 3; + } + +- return true; ++ return sequence; ++} ++ ++void vulkan_wait( uint64_t ulSeqNo, bool bReset ) ++{ ++ return g_device.wait( ulSeqNo, bReset ); + } + + std::shared_ptr vulkan_get_last_output_image( bool partial, bool defer ) +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index d452c5bbc..e9984695f 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -373,7 +373,8 @@ std::shared_ptr vulkan_create_texture_from_dmabuf( struct wlr_dm + std::shared_ptr 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 ); + std::shared_ptr vulkan_create_texture_from_wlr_buffer( struct wlr_buffer *buf ); + +-bool vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture, bool partial, bool deferred, std::shared_ptr pOutputOverride = nullptr, bool increment = true ); ++std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture, bool partial, std::shared_ptr pOutputOverride = nullptr, bool increment = true ); ++void vulkan_wait( uint64_t ulSeqNo, bool bReset ); + std::shared_ptr vulkan_get_last_output_image( bool partial, bool defer ); + std::shared_ptr vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); + +@@ -396,7 +397,7 @@ void vulkan_update_luts(const std::shared_ptr& lut1d, const std: + + std::shared_ptr vulkan_get_hacky_blank_texture(); + +-bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ); ++std::optional vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ); + + struct wlr_renderer *vulkan_renderer_create( void ); + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 6add8f427..b02fa331c 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -2640,11 +2640,6 @@ paint_all(bool async) + bool propertyRequestedScreenshot = g_bPropertyRequestedScreenshot; + g_bPropertyRequestedScreenshot = false; + +- struct pipewire_buffer *pw_buffer = nullptr; +-#if HAVE_PIPEWIRE +- pw_buffer = dequeue_pipewire_buffer(); +-#endif +- + update_app_target_refresh_cycle(); + + int nDynamicRefresh = g_nDynamicRefreshRate[drm_get_screen_type( &g_DRM )]; +@@ -2665,7 +2660,7 @@ paint_all(bool async) + + bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; + +- bool bDoMuraCompensation = is_mura_correction_enabled() && frameInfo.layerCount && !pw_buffer; ++ bool bDoMuraCompensation = is_mura_correction_enabled() && frameInfo.layerCount; + if ( bDoMuraCompensation ) + { + auto& MuraCorrectionImage = s_MuraCorrectionImage[drm_get_screen_type( &g_DRM )]; +@@ -2693,7 +2688,6 @@ paint_all(bool async) + + bool bNeedsFullComposite = BIsNested(); + bNeedsFullComposite |= alwaysComposite; +- bNeedsFullComposite |= pw_buffer != nullptr; + bNeedsFullComposite |= bWasFirstFrame; + bNeedsFullComposite |= frameInfo.useFSRLayer0; + bNeedsFullComposite |= frameInfo.useNISLayer0; +@@ -2749,14 +2743,6 @@ paint_all(bool async) + if ( kDisablePartialComposition ) + bNeedsFullComposite = true; + +- std::shared_ptr pPipewireTexture = nullptr; +-#if HAVE_PIPEWIRE +- if ( pw_buffer != nullptr ) +- { +- pPipewireTexture = pw_buffer->texture; +- } +-#endif +- + struct FrameInfo_t compositeFrameInfo = frameInfo; + + if ( compositeFrameInfo.layerCount == 1 ) +@@ -2819,38 +2805,20 @@ paint_all(bool async) + if ( bDefer && !!( g_uCompositeDebug & CompositeDebugFlag::Markers ) ) + g_uCompositeDebug |= CompositeDebugFlag::Markers_Partial; + +- bool bResult; +- // If using a pipewire stream, apply screenshot color management. +- if ( pPipewireTexture ) +- { +- for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) +- { +- compositeFrameInfo.lut3D[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut3d; +- compositeFrameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; +- } +- vulkan_composite( &compositeFrameInfo, pPipewireTexture, !bNeedsFullComposite, bDefer, nullptr, false ); +- for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) +- { +- if (g_ColorMgmtLuts[nInputEOTF].HasLuts()) +- { +- compositeFrameInfo.shaperLut[nInputEOTF] = g_ColorMgmtLuts[nInputEOTF].vk_lut1d; +- compositeFrameInfo.lut3D[nInputEOTF] = g_ColorMgmtLuts[nInputEOTF].vk_lut3d; +- } +- } +- } +- +- bResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite, bDefer ); ++ std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite ); + + g_bWasCompositing = true; + + g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; + +- if ( bResult != true ) ++ if ( !oCompositeResult ) + { + xwm_log.errorf("vulkan_composite failed"); + return; + } + ++ vulkan_wait( *oCompositeResult, true ); ++ + if ( BIsNested() == true ) + { + #if HAVE_OPENVR +@@ -2994,15 +2962,6 @@ paint_all(bool async) + + drm_commit( &g_DRM, &compositeFrameInfo ); + } +- +-#if HAVE_PIPEWIRE +- if ( pw_buffer != nullptr ) +- { +- push_pipewire_buffer(pw_buffer); +- // TODO: make sure the pw_buffer isn't lost in one of the failure +- // code-paths above +- } +-#endif + } + else + { +@@ -3011,6 +2970,51 @@ paint_all(bool async) + drm_commit( &g_DRM, &frameInfo ); + } + ++#if HAVE_PIPEWIRE ++ struct pipewire_buffer *pw_buffer = dequeue_pipewire_buffer(); ++ if ( pw_buffer ) ++ { ++ if ( pw_buffer->texture ) ++ { ++ struct FrameInfo_t pipewireFrameInfo = frameInfo; ++ ++ // If using a pipewire stream, apply screenshot color management. ++ for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) ++ { ++ pipewireFrameInfo.lut3D[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut3d; ++ pipewireFrameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; ++ } ++ ++ if ( is_mura_correction_enabled() ) ++ { ++ // Remove the last layer which is for mura... ++ for (int i = 0; i < pipewireFrameInfo.layerCount; i++) ++ { ++ if (pipewireFrameInfo.layers[i].zpos >= (int)g_zposMuraCorrection) ++ { ++ pipewireFrameInfo.layerCount = i; ++ break; ++ } ++ } ++ ++ // Re-enable output color management (blending) if it was disabled by mura. ++ pipewireFrameInfo.applyOutputColorMgmt = true; ++ } ++ ++ std::optional oPipewireSequence = vulkan_composite( &pipewireFrameInfo, pw_buffer->texture, false, nullptr, false ); ++ ++ if ( oPipewireSequence ) ++ { ++ vulkan_wait( *oPipewireSequence, true ); ++ ++ push_pipewire_buffer( pw_buffer ); ++ // TODO: make sure the pw_buffer isn't lost in one of the failure ++ // code-paths above ++ } ++ } ++ } ++#endif ++ + if ( takeScreenshot ) + { + uint32_t drmCaptureFormat = bHackForceNV12DumpScreenshot +@@ -3067,20 +3071,22 @@ paint_all(bool async) + + frameInfo.applyOutputColorMgmt = true; + +- bool bResult; ++ std::optional oScreenshotSeq; + if ( drmCaptureFormat == DRM_FORMAT_NV12 ) +- bResult = vulkan_composite( &frameInfo, pScreenshotTexture, false, false, nullptr ); ++ oScreenshotSeq = vulkan_composite( &frameInfo, pScreenshotTexture, false, nullptr ); + else if ( takeScreenshot == TAKE_SCREENSHOT_FULL_COMPOSITION || takeScreenshot == TAKE_SCREENSHOT_SCREEN_BUFFER ) +- bResult = vulkan_composite( &frameInfo, nullptr, false, false, pScreenshotTexture ); ++ oScreenshotSeq = vulkan_composite( &frameInfo, nullptr, false, pScreenshotTexture ); + else +- bResult = vulkan_screenshot( &frameInfo, pScreenshotTexture ); ++ oScreenshotSeq = vulkan_screenshot( &frameInfo, pScreenshotTexture ); + +- if ( bResult != true ) ++ if ( !oScreenshotSeq ) + { + xwm_log.errorf("vulkan_screenshot failed"); + return; + } + ++ vulkan_wait( *oScreenshotSeq, false ); ++ + std::thread screenshotThread = std::thread([=] { + pthread_setname_np( pthread_self(), "gamescope-scrsh" ); + + +From 52624de3616e8f64111334bfbf2ca69acbbf9ed9 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 12 Dec 2023 23:32:21 +0000 +Subject: [PATCH 042/134] pipewire: Nudge on stream_handle_remove_buffer if + copying + +--- + src/pipewire.cpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/pipewire.cpp b/src/pipewire.cpp +index 55505693b..bde1d87cb 100644 +--- a/src/pipewire.cpp ++++ b/src/pipewire.cpp +@@ -571,6 +571,8 @@ static void stream_handle_remove_buffer(void *data, struct pw_buffer *pw_buffer) + + if (!buffer->copying) { + destroy_buffer(buffer); ++ } else { ++ nudge_pipewire(); + } + } + + +From ed6cd455beca4c60dd6a1a53ce3c84643b5da6e0 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 13 Dec 2023 00:12:03 +0000 +Subject: [PATCH 043/134] pipewire: Make state change an infof, not a debugf + +--- + src/pipewire.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/pipewire.cpp b/src/pipewire.cpp +index bde1d87cb..6eab648ce 100644 +--- a/src/pipewire.cpp ++++ b/src/pipewire.cpp +@@ -296,7 +296,7 @@ static void stream_handle_state_changed(void *data, enum pw_stream_state old_str + { + struct pipewire_state *state = (struct pipewire_state *) data; + +- pwr_log.debugf("stream state changed: %s", pw_stream_state_as_string(stream_state)); ++ pwr_log.infof("stream state changed: %s", pw_stream_state_as_string(stream_state)); + + switch (stream_state) { + case PW_STREAM_STATE_PAUSED: + +From 4e0a42f2e09628ee1f664c23dd876b556a63b105 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 13 Dec 2023 01:38:50 +0000 +Subject: [PATCH 044/134] layer: Fix newline in GAMESCOPE_WSI_BYPASS_DEBUG log + +--- + layer/VkLayer_FROG_gamescope_wsi.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp +index 77636cbc9..c08caa2e7 100644 +--- a/layer/VkLayer_FROG_gamescope_wsi.cpp ++++ b/layer/VkLayer_FROG_gamescope_wsi.cpp +@@ -187,7 +187,7 @@ namespace GamescopeWSILayer { + iabs(int32_t(toplevelRect->extent.width) - int32_t(rect->extent.width)) > 1 || + iabs(int32_t(toplevelRect->extent.height) - int32_t(rect->extent.height)) > 1) { + #if GAMESCOPE_WSI_BYPASS_DEBUG +- fprintf(stderr, "[Gamescope WSI] Not within 1px margin of error. Offset: %d %d Extent: %u %u vs %u %u", ++ fprintf(stderr, "[Gamescope WSI] Not within 1px margin of error. Offset: %d %d Extent: %u %u vs %u %u\n", + rect->offset.x, rect->offset.y, + toplevelRect->extent.width, toplevelRect->extent.height, + rect->extent.width, rect->extent.height); + +From a2f8db9fdb20975582535828533619603705b257 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 13 Dec 2023 01:47:53 +0000 +Subject: [PATCH 045/134] wlserver: Fix destroying content overrides for active + windows + +--- + src/wlserver.cpp | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 44f2b6724..3fbc4ff58 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -542,6 +542,9 @@ void gamescope_xwayland_server_t::destroy_content_override( struct wlserver_x11_ + if (iter == content_overrides.end()) + return; + ++ if ( x11_surface->override_surface == surf ) ++ x11_surface->override_surface = nullptr; ++ + struct wlserver_content_override *co = iter->second; + if (co->surface == surf) + destroy_content_override(iter->second); + +From 3e14ef9c37266b19ba77fbef467d1b8a77d827f2 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 13 Dec 2023 02:14:32 +0000 +Subject: [PATCH 046/134] layer: Add GAMESCOPE_WSI_FORCE_BYPASS + +--- + layer/VkLayer_FROG_gamescope_wsi.cpp | 11 +++++++++++ + src/layer_defines.h | 1 + + 2 files changed, 12 insertions(+) + +diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp +index c08caa2e7..946f53802 100644 +--- a/layer/VkLayer_FROG_gamescope_wsi.cpp ++++ b/layer/VkLayer_FROG_gamescope_wsi.cpp +@@ -92,6 +92,10 @@ namespace GamescopeWSILayer { + static GamescopeLayerClient::Flags defaultLayerClientFlags(uint32_t appid) { + GamescopeLayerClient::Flags flags = 0; + ++ const char *bypassEnv = getenv("GAMESCOPE_WSI_FORCE_BYPASS"); ++ if (bypassEnv && *bypassEnv && atoi(bypassEnv) != 0) ++ flags |= GamescopeLayerClient::Flag::ForceBypass; ++ + // My Little Pony: A Maretime Bay Adventure picks a HDR colorspace if available, + // but does not render as HDR at all. + if (appid == 1600780) +@@ -169,6 +173,13 @@ namespace GamescopeWSILayer { + return false; + } + ++ // Some games do things like have a 1280x800 top-level window and ++ // a 1280x720 child window for "fullscreen". ++ // To avoid Glamor work on the XWayland side of things, have a ++ // flag to force bypassing this. ++ if (!!(flags & GamescopeLayerClient::Flag::ForceBypass)) ++ return true; ++ + // If we have any child windows obscuring us bigger than 1x1, + // then we cannot flip. + // (There can be dummy composite redirect windows and whatever.) +diff --git a/src/layer_defines.h b/src/layer_defines.h +index fb34465a9..ef9a10792 100644 +--- a/src/layer_defines.h ++++ b/src/layer_defines.h +@@ -8,6 +8,7 @@ namespace GamescopeLayerClient + // GAMESCOPE_LAYER_CLIENT_FLAGS + namespace Flag { + static constexpr uint32_t DisableHDR = 1u << 0; ++ static constexpr uint32_t ForceBypass = 1u << 1; + } + using Flags = uint32_t; + } +\ No newline at end of file + +From 243582c0c7625577a2535c3363ae345de713f323 Mon Sep 17 00:00:00 2001 +From: Lionel Landwerlin +Date: Thu, 14 Dec 2023 16:13:22 +0200 +Subject: [PATCH 047/134] renderervulkan: only consider modifiers support all + the image properties + +If you have a modifier with image compression that is only support for +some image formats like : + - B8G8R8A8_UNORM : compression supported + - B8G8R8A8_SRB : compression not supported + +Then the render will not use compression but the modifier indicates it +is present, so the compositor will try to make use of the side +compressed data and this will lead to corruptions. + +This fixes issues on Intel HW of Gfx9 generations. +--- + src/rendervulkan.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 65 insertions(+) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 8940b7704..936c1d6a4 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -2449,6 +2449,49 @@ int CVulkanTexture::memoryFence() + return fence; + } + ++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; ++} + + bool vulkan_init_format(VkFormat format, uint32_t drmFormat) + { +@@ -2461,6 +2504,25 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) + .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; ++ } ++ ++ + VkImageFormatProperties2 imageFormatProps = { + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, + }; +@@ -2518,6 +2580,9 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) + + 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; + +From 9888a50cbdf9f4538652ab616580f1e3dd3beb64 Mon Sep 17 00:00:00 2001 +From: Tatsuyuki Ishi +Date: Fri, 8 Dec 2023 14:02:41 +0900 +Subject: [PATCH 048/134] steamcompmgr: Fix calculated refresh cycle for + present timing + +The "target FPS" feature divides vblank to achieve a target refresh +rate. Previously, the target frame time was divided by the divisor +again, making it way too small and nonsensical. Correctly calculate +this by multiplying instead of dividing. + +Since the real refresh rate may be odd, apply the multiplier after +dividing to avoid rounding error. +--- + src/steamcompmgr.cpp | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index b02fa331c..2042b9125 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -7916,11 +7916,10 @@ steamcompmgr_main(int argc, char **argv) + int nRealRefresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; + int nTargetFPS = g_nSteamCompMgrTargetFPS ? g_nSteamCompMgrTargetFPS : nRealRefresh; + nTargetFPS = std::min( nTargetFPS, nRealRefresh ); +- int nMultiplier = nRealRefresh / nTargetFPS; ++ int nVblankDivisor = nRealRefresh / nTargetFPS; + +- int nAppRefresh = nRealRefresh * nMultiplier; + g_SteamCompMgrAppRefreshCycle = 1'000'000'000ul / nRealRefresh; +- g_SteamCompMgrLimitedAppRefreshCycle = 1'000'000'000ul / nAppRefresh; ++ g_SteamCompMgrLimitedAppRefreshCycle = 1'000'000'000ul / nRealRefresh * nVblankDivisor; + } + + // Handle presentation-time stuff + +From 618a154391032188bad2a36fb541b18c0abd1109 Mon Sep 17 00:00:00 2001 +From: Tatsuyuki Ishi +Date: Mon, 18 Dec 2023 20:30:56 +0900 +Subject: [PATCH 049/134] drm: Initialize the owned field of blob wrappers + properly + +This was leading to use-after-free of HDR metadata blobs which can show up +as modeset failures. +--- + src/drm.hpp | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/drm.hpp b/src/drm.hpp +index 681079700..a0893464a 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -67,7 +67,7 @@ struct wlserver_hdr_metadata + } + + wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) +- : blob(blob) ++ : blob(blob), owned(owned) + { + if (_metadata) + this->metadata = *_metadata; +@@ -94,6 +94,7 @@ struct wlserver_ctm + wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) + : matrix(ctm) + , blob(blob) ++ , owned(owned) + { + } + + +From 9a19aeeb55d809cdf34fe1f8dee7c12112409d12 Mon Sep 17 00:00:00 2001 +From: Tatsuyuki Ishi +Date: Mon, 18 Dec 2023 19:31:13 +0900 +Subject: [PATCH 050/134] drm: Introduce a drm_blob abstraction + +This becomes the base class of the existing wlserver blob structs, and +will be used for more DRM state tracking in future. + +The copy and move constructors are deleted to ensure we don't mishandle +lifetimes. + +One potential evolution direction is to make this take the blob data +struct as a generic parameter and make it handle creation as well, but +keep it simple for now. +--- + src/drm.hpp | 55 +++++++++++++++++++++++++++++++---------------------- + 1 file changed, 32 insertions(+), 23 deletions(-) + +diff --git a/src/drm.hpp b/src/drm.hpp +index a0893464a..608a4dc59 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -59,54 +59,63 @@ extern "C" + extern struct drm_t g_DRM; + void drm_destroy_blob(struct drm_t *drm, uint32_t blob); + +-struct wlserver_hdr_metadata ++class drm_blob + { +- wlserver_hdr_metadata() ++public: ++ drm_blob() : blob( 0 ), owned( false ) + { +- + } + +- wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) +- : blob(blob), owned(owned) ++ drm_blob(uint32_t blob, bool owned = true) ++ : blob( blob ), owned( owned ) + { +- if (_metadata) +- this->metadata = *_metadata; + } + +- ~wlserver_hdr_metadata() ++ ~drm_blob() + { +- if ( blob && owned ) ++ if (blob && owned) + drm_destroy_blob( &g_DRM, blob ); + } + +- hdr_output_metadata metadata = {}; +- uint32_t blob = 0; +- bool owned = true; ++ // No copy constructor, because we can't duplicate the blob handle. ++ drm_blob(const drm_blob&) = delete; ++ drm_blob& operator=(const drm_blob&) = delete; ++ // No move constructor, because we use shared_ptr anyway, but can be added if necessary. ++ drm_blob(drm_blob&&) = delete; ++ drm_blob& operator=(drm_blob&&) = delete; ++ ++ uint32_t blob; ++ bool owned; + }; + +-struct wlserver_ctm ++struct wlserver_hdr_metadata : drm_blob + { +- wlserver_ctm() ++ wlserver_hdr_metadata() + { ++ } + ++ wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) ++ : drm_blob( blob, owned ) ++ { ++ if (_metadata) ++ this->metadata = *_metadata; + } + +- wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) +- : matrix(ctm) +- , blob(blob) +- , owned(owned) ++ hdr_output_metadata metadata = {}; ++}; ++ ++struct wlserver_ctm : drm_blob ++{ ++ wlserver_ctm() + { + } + +- ~wlserver_ctm() ++ wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) ++ : drm_blob( blob, owned ), matrix( ctm ) + { +- if ( blob && owned ) +- drm_destroy_blob( &g_DRM, blob ); + } + + glm::mat3x4 matrix{}; +- uint32_t blob = 0; +- bool owned = true; + }; + + #include + +From 128951fcd0ec2d05445ffe2005681a40b9b784d8 Mon Sep 17 00:00:00 2001 +From: Tatsuyuki Ishi +Date: Mon, 18 Dec 2023 20:29:04 +0900 +Subject: [PATCH 051/134] drm: Port mode and lut tracking to drm_blob + +This also fixes an issue where blobs were leaked due to current = pending +assignment happening earlier than when it was supposed to be. +--- + src/drm.cpp | 25 ++++++++----------------- + src/drm.hpp | 6 +++--- + 2 files changed, 11 insertions(+), 20 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index c2694f00f..400109ac1 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -1733,18 +1733,9 @@ int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) + + drm->current = drm->pending; + +- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) ++ for (auto & crtc : drm->crtcs) + { +- if ( drm->pending.mode_id != drm->current.mode_id ) +- drmModeDestroyPropertyBlob(drm->fd, drm->current.mode_id); +- for ( uint32_t i = 0; i < EOTF_Count; i++ ) +- { +- if ( drm->pending.lut3d_id[i] != drm->current.lut3d_id[i] ) +- drmModeDestroyPropertyBlob(drm->fd, drm->current.lut3d_id[i]); +- if ( drm->pending.shaperlut_id[i] != drm->current.shaperlut_id[i] ) +- drmModeDestroyPropertyBlob(drm->fd, drm->current.shaperlut_id[i]); +- } +- drm->crtcs[i].current = drm->crtcs[i].pending; ++ crtc.current = crtc.pending; + } + + for (auto &kv : drm->connectors) { +@@ -2427,9 +2418,9 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + + if ( !g_bDisableShaperAnd3DLUT ) + { +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ] ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", shaper_tf ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ] ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); + // 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 +@@ -2708,7 +2699,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + return ret; + } + +- ret = add_crtc_property(drm->req, drm->crtc, "MODE_ID", drm->pending.mode_id); ++ ret = add_crtc_property(drm->req, drm->crtc, "MODE_ID", drm->pending.mode_id->blob); + if (ret < 0) + return ret; + +@@ -2952,14 +2943,14 @@ bool drm_update_color_mgmt(struct drm_t *drm) + drm_log.errorf_errno("Unable to create SHAPERLUT property blob"); + return false; + } +- drm->pending.shaperlut_id[ i ] = shaper_blob_id; ++ drm->pending.shaperlut_id[ i ] = std::make_shared( shaper_blob_id ); + + uint32_t lut3d_blob_id = 0; + if (drmModeCreatePropertyBlob(drm->fd, g_ColorMgmtLuts[i].lut3d, sizeof(g_ColorMgmtLuts[i].lut3d), &lut3d_blob_id) != 0) { + drm_log.errorf_errno("Unable to create LUT3D property blob"); + return false; + } +- drm->pending.lut3d_id[ i ] = lut3d_blob_id; ++ drm->pending.lut3d_id[ i ] = std::make_shared( lut3d_blob_id ); + } + + return true; +@@ -3015,7 +3006,7 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) + + drm_log.infof("selecting mode %dx%d@%uHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); + +- drm->pending.mode_id = mode_id; ++ drm->pending.mode_id = std::make_shared(mode_id); + drm->needs_modeset = true; + + g_nOutputRefresh = mode->vrefresh; +diff --git a/src/drm.hpp b/src/drm.hpp +index 608a4dc59..4840e2b8d 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -271,10 +271,10 @@ struct drm_t { + std::shared_ptr sdr_static_metadata; + + struct { +- uint32_t mode_id; ++ std::shared_ptr mode_id; + uint32_t color_mgmt_serial; +- uint32_t lut3d_id[ EOTF_Count ]; +- uint32_t shaperlut_id[ EOTF_Count ]; ++ std::shared_ptr lut3d_id[ EOTF_Count ]; ++ std::shared_ptr shaperlut_id[ EOTF_Count ]; + enum drm_screen_type screen_type = DRM_SCREEN_TYPE_INTERNAL; + bool vrr_enabled = false; + drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; + +From 37563f9f5139e9edb5e8ac8bdbae0707342e2bba Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 18 Dec 2023 03:04:46 +0000 +Subject: [PATCH 052/134] layer: Log more about minImageCount + +--- + layer/VkLayer_FROG_gamescope_wsi.cpp | 44 +++++++++++++++++++++------- + 1 file changed, 33 insertions(+), 11 deletions(-) + +diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp +index 946f53802..a2a93d1c4 100644 +--- a/layer/VkLayer_FROG_gamescope_wsi.cpp ++++ b/layer/VkLayer_FROG_gamescope_wsi.cpp +@@ -7,6 +7,7 @@ + #include "../src/color_helpers.h" + #include "../src/layer_defines.h" + ++#include + #include + #include + #include +@@ -699,20 +700,41 @@ namespace GamescopeWSILayer { + return s_isRunningUnderGamescope; + } + ++ template ++ static std::optional parseEnv(const char *envName) { ++ const char *str = std::getenv(envName); ++ if (!str || !*str) ++ return std::nullopt; ++ ++ T value; ++ auto result = std::from_chars(str, str + strlen(str), value); ++ if (result.ec != std::errc{}) ++ return std::nullopt; ++ ++ return value; ++ } ++ + static uint32_t getMinImageCount() { +- { +- const char *overrideStr = std::getenv("GAMESCOPE_WSI_MIN_IMAGE_COUNT"); +- if (overrideStr && *overrideStr) +- return uint32_t(std::atoi(overrideStr)); +- } ++ static uint32_t s_minImageCount = []() -> uint32_t { ++ if (auto minCount = parseEnv("GAMESCOPE_WSI_MIN_IMAGE_COUNT")) { ++ fprintf(stderr, "[Gamescope WSI] minImageCount overridden by GAMESCOPE_WSI_MIN_IMAGE_COUNT: %u\n", *minCount); ++ return *minCount; ++ } + +- { +- const char *overrideStr = std::getenv("vk_x11_override_min_image_count"); +- if (overrideStr && *overrideStr) +- return uint32_t(std::atoi(overrideStr)); +- } ++ if (auto minCount = parseEnv("vk_wsi_override_min_image_count")) { ++ fprintf(stderr, "[Gamescope WSI] minImageCount overridden by vk_wsi_override_min_image_count: %u\n", *minCount); ++ return *minCount; ++ } ++ ++ if (auto minCount = parseEnv("vk_x11_override_min_image_count")) { ++ fprintf(stderr, "[Gamescope WSI] minImageCount overridden by vk_x11_override_min_image_count: %u\n", *minCount); ++ return *minCount; ++ } ++ ++ return 3u; ++ }(); + +- return 3; ++ return s_minImageCount; + } + + static constexpr wl_registry_listener s_registryListener = { + +From b1c4c9685bfe14d2f8c964270578a95a86beacf8 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 19 Dec 2023 01:43:43 +0000 +Subject: [PATCH 053/134] layer: Print more info about VkHdrMetadata + +--- + layer/VkLayer_FROG_gamescope_wsi.cpp | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp +index a2a93d1c4..0bc339514 100644 +--- a/layer/VkLayer_FROG_gamescope_wsi.cpp ++++ b/layer/VkLayer_FROG_gamescope_wsi.cpp +@@ -1013,9 +1013,14 @@ namespace GamescopeWSILayer { + nits_to_u16(metadata.maxContentLightLevel), + nits_to_u16(metadata.maxFrameAverageLightLevel)); + +- fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: mastering luminance min %f nits, max %f nits\n", metadata.minLuminance, metadata.maxLuminance); +- fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: maxContentLightLevel %f nits\n", metadata.maxContentLightLevel); +- fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: maxFrameAverageLightLevel %f nits\n", metadata.maxFrameAverageLightLevel); ++ fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: display primaries:\n", metadata.minLuminance, metadata.maxLuminance); ++ fprintf(stderr, " r: %.4g %.4g\n", metadata.displayPrimaryRed.x, metadata.displayPrimaryRed.y); ++ fprintf(stderr, " g: %.4g %.4g\n", metadata.displayPrimaryGreen.x, metadata.displayPrimaryGreen.y); ++ fprintf(stderr, " b: %.4g %.4g\n", metadata.displayPrimaryBlue.x, metadata.displayPrimaryBlue.y); ++ fprintf(stderr, " w: %.4g %.4g\n", metadata.whitePoint.x, metadata.whitePoint.y); ++ fprintf(stderr, " mastering luminance: min %g nits, max %g nits\n", metadata.minLuminance, metadata.maxLuminance); ++ fprintf(stderr, " maxContentLightLevel: %g nits\n", metadata.maxContentLightLevel); ++ fprintf(stderr, " maxFrameAverageLightLevel: %g nits\n", metadata.maxFrameAverageLightLevel); + } + } + + +From 0868a4ab5e763b7e23ad97a84792a4fe60b0a74d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Cl=C3=A9ment=20Gu=C3=A9rin?= +Date: Tue, 19 Dec 2023 16:34:17 -0800 +Subject: [PATCH 054/134] drm: fix NPE while in headless mode + +caused by e810317 +--- + src/drm.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 400109ac1..2a7208f72 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -3232,6 +3232,7 @@ void drm_get_native_colorimetry( struct drm_t *drm, + *displayEOTF = EOTF_Gamma22; + *outputEncodingColorimetry = displaycolorimetry_709; + *outputEncodingEOTF = EOTF_Gamma22; ++ return; + } + + *displayColorimetry = drm->connector->metadata.colorimetry; + +From 38e21856103ae872e75e9c4c05b978e2a5ae743b Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 20 Dec 2023 09:05:47 +0000 +Subject: [PATCH 055/134] waitable: Factor out common ITimerWaitable + +--- + src/steamcompmgr.cpp | 4 +-- + src/vblankmanager.cpp | 40 ++++++------------------ + src/vblankmanager.hpp | 5 ++- + src/waitable.h | 72 +++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 85 insertions(+), 36 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 2042b9125..40f812531 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -7703,7 +7703,7 @@ steamcompmgr_main(int argc, char **argv) + + bool vblank = false; + g_SteamCompMgrWaiter.AddWaitable( &g_VBlankTimer ); +- g_VBlankTimer.RearmTimer( true ); ++ g_VBlankTimer.ArmNextVBlank( true ); + + { + gamescope_xwayland_server_t *pServer = NULL; +@@ -8092,7 +8092,7 @@ steamcompmgr_main(int argc, char **argv) + // + // Juuust in case pageflip handler doesn't happen + // so we don't stop vblanking forever. +- g_VBlankTimer.RearmTimer( true ); ++ g_VBlankTimer.ArmNextVBlank( true ); + } + + update_vrr_atoms(root_ctx, false, &flush_root); +diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp +index df77bdf33..2a730501a 100644 +--- a/src/vblankmanager.cpp ++++ b/src/vblankmanager.cpp +@@ -11,7 +11,6 @@ + #include + #include + #include +-#include + + #include "gpuvis_trace_utils.h" + +@@ -43,12 +42,6 @@ namespace gamescope + if ( bShouldUseTimerFD ) + { + g_VBlankLog.infof( "Using timerfd." ); +- m_nTimerFD = timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC ); +- if ( m_nTimerFD < 0 ) +- { +- g_VBlankLog.errorf_errno( "Failed to create VBlankTimer timerfd." ); +- abort(); +- } + } + else + { +@@ -84,12 +77,6 @@ namespace gamescope + m_bArmed = true; + m_bArmed.notify_all(); + +- if ( m_nTimerFD >= 0 ) +- { +- close( m_nTimerFD ); +- m_nTimerFD = 0; +- } +- + for ( int i = 0; i < 2; i++ ) + { + if ( m_nNudgePipe[ i ] >= 0 ) +@@ -226,7 +213,7 @@ namespace gamescope + if ( bReArmTimer ) + { + // Force timer re-arm with the new vblank timings. +- RearmTimer( false ); ++ ArmNextVBlank( false ); + } + } + +@@ -251,7 +238,7 @@ namespace gamescope + m_bArmed.wait( false ); + } + +- void CVBlankTimer::RearmTimer( bool bPreemptive ) ++ void CVBlankTimer::ArmNextVBlank( bool bPreemptive ) + { + std::unique_lock lock( m_ScheduleMutex ); + +@@ -267,24 +254,18 @@ namespace gamescope + { + m_TimerFDSchedule = CalcNextWakeupTime( bPreemptive ); + +- itimerspec timerspec = +- { +- .it_interval = timespec{}, +- .it_value = nanos_to_timespec( m_TimerFDSchedule.ulScheduledWakeupPoint ), +- }; +- if ( timerfd_settime( m_nTimerFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) +- g_VBlankLog.errorf_errno( "timerfd_settime failed!" ); ++ ITimerWaitable::ArmTimer( m_TimerFDSchedule.ulScheduledWakeupPoint ); + } + } + + bool CVBlankTimer::UsingTimerFD() const + { +- return m_nTimerFD >= 0; ++ return m_nNudgePipe[ 0 ] < 0; + } + + int CVBlankTimer::GetFD() + { +- return UsingTimerFD() ? m_nTimerFD : m_nNudgePipe[ 0 ]; ++ return UsingTimerFD() ? ITimerWaitable::GetFD() : m_nNudgePipe[ 0 ]; + } + + void CVBlankTimer::OnPollIn() +@@ -317,10 +298,7 @@ namespace gamescope + + gpuvis_trace_printf( "vblank timerfd wakeup" ); + +- // Disarm timer. +- itimerspec timerspec{}; +- if ( timerfd_settime( m_nTimerFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) +- g_VBlankLog.errorf_errno( "timerfd_settime failed!" ); ++ ITimerWaitable::DisarmTimer(); + } + else + { +@@ -335,13 +313,13 @@ namespace gamescope + continue; + + g_VBlankLog.errorf_errno( "Failed to read nudge pipe. Pre-emptively re-arming." ); +- RearmTimer( true ); ++ ArmNextVBlank( true ); + return; + } + else if ( ret != sizeof( VBlankTime ) ) + { + g_VBlankLog.errorf( "Nudge pipe had less data than sizeof( VBlankTime ). Pre-emptively re-arming." ); +- RearmTimer( true ); ++ ArmNextVBlank( true ); + return; + } + else +@@ -354,7 +332,7 @@ namespace gamescope + if ( ulDiff > 1'000'000ul ) + { + gpuvis_trace_printf( "Ignoring stale vblank... Pre-emptively re-arming." ); +- RearmTimer( true ); ++ ArmNextVBlank( true ); + return; + } + +diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp +index 30a8c4abc..0a7f1c428 100644 +--- a/src/vblankmanager.hpp ++++ b/src/vblankmanager.hpp +@@ -24,7 +24,7 @@ namespace gamescope + uint64_t ulWakeupTime = 0; + }; + +- class CVBlankTimer : public gamescope::IWaitable ++ class CVBlankTimer : public ITimerWaitable + { + public: + static constexpr uint64_t kSecInNanoSecs = 1'000'000'000ul; +@@ -57,7 +57,7 @@ namespace gamescope + void UpdateLastDrawTime( uint64_t ulNanos ); + + void WaitToBeArmed(); +- void RearmTimer( bool bPreemptive ); ++ void ArmNextVBlank( bool bPreemptive ); + + bool UsingTimerFD() const; + int GetFD() final; +@@ -80,7 +80,6 @@ namespace gamescope + // Does not cover m_ulLastVBlank, this is just atomic. + std::mutex m_ScheduleMutex; + VBlankScheduleTime m_TimerFDSchedule{}; +- int m_nTimerFD = -1; + + std::thread m_NudgeThread; + int m_nNudgePipe[2] = { -1, -1 }; +diff --git a/src/waitable.h b/src/waitable.h +index a8f1f212a..1882e6e4d 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -4,6 +4,7 @@ + #include + #include + #include ++#include + + #include + +@@ -11,6 +12,8 @@ + + extern LogScope g_WaitableLog; + ++timespec nanos_to_timespec( uint64_t ulNanos ); ++ + namespace gamescope + { + class IWaitable +@@ -128,6 +131,75 @@ namespace gamescope + std::function m_fnPollFunc; + }; + ++ class ITimerWaitable : public IWaitable ++ { ++ public: ++ ITimerWaitable() ++ { ++ 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(); ++ } ++ ++ void Shutdown() ++ { ++ if ( m_nFD >= 0 ) ++ { ++ close( m_nFD ); ++ m_nFD = -1; ++ } ++ } ++ ++ 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!" ); ++ } ++ ++ void DisarmTimer() ++ { ++ ArmTimer( 0ul, false ); ++ } ++ ++ int GetFD() ++ { ++ return m_nFD; ++ } ++ private: ++ int m_nFD = -1; ++ }; ++ ++ class CTimerFunction final : public ITimerWaitable ++ { ++ public: ++ CTimerFunction( std::function fnPollFunc ) ++ : m_fnPollFunc{ fnPollFunc } ++ { ++ } ++ ++ void OnPollIn() final ++ { ++ m_fnPollFunc(); ++ } ++ private: ++ std::function m_fnPollFunc; ++ }; ++ + template + class CWaiter + { + +From 3a97ba3e5cd459f1ab9513aab3b649b51f3dddc7 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 20 Dec 2023 09:06:44 +0000 +Subject: [PATCH 056/134] waitable: Fix some FD close settings + +--- + src/vblankmanager.cpp | 2 +- + src/waitable.h | 3 +++ + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp +index 2a730501a..b62fc9023 100644 +--- a/src/vblankmanager.cpp ++++ b/src/vblankmanager.cpp +@@ -82,7 +82,7 @@ namespace gamescope + if ( m_nNudgePipe[ i ] >= 0 ) + { + close ( m_nNudgePipe[ i ] ); +- m_nNudgePipe[ i ] = 0; ++ m_nNudgePipe[ i ] = -1; + } + } + } +diff --git a/src/waitable.h b/src/waitable.h +index 1882e6e4d..cd806f698 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -78,7 +78,10 @@ namespace gamescope + for ( int i = 0; i < 2; i++ ) + { + if ( m_nFDs[i] >= 0 ) ++ { + close( m_nFDs[i] ); ++ m_nFDs[i] = -1; ++ } + } + } + + +From b1879a3728d617fd3cc8eb4d3b07d833a7942822 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 20 Dec 2023 09:12:24 +0000 +Subject: [PATCH 057/134] layer: Fix warning on printf + +--- + layer/VkLayer_FROG_gamescope_wsi.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp +index 0bc339514..5844c2a63 100644 +--- a/layer/VkLayer_FROG_gamescope_wsi.cpp ++++ b/layer/VkLayer_FROG_gamescope_wsi.cpp +@@ -1013,7 +1013,7 @@ namespace GamescopeWSILayer { + nits_to_u16(metadata.maxContentLightLevel), + nits_to_u16(metadata.maxFrameAverageLightLevel)); + +- fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: display primaries:\n", metadata.minLuminance, metadata.maxLuminance); ++ fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: display primaries:\n"); + fprintf(stderr, " r: %.4g %.4g\n", metadata.displayPrimaryRed.x, metadata.displayPrimaryRed.y); + fprintf(stderr, " g: %.4g %.4g\n", metadata.displayPrimaryGreen.x, metadata.displayPrimaryGreen.y); + fprintf(stderr, " b: %.4g %.4g\n", metadata.displayPrimaryBlue.x, metadata.displayPrimaryBlue.y); + +From b0ce622a8ef095796daeb98f7882e19ba7d4f994 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Cl=C3=A9ment=20Gu=C3=A9rin?= +Date: Wed, 20 Dec 2023 17:18:32 -0800 +Subject: [PATCH 058/134] drm: fix other headless NPE + +fixes a980d912 +--- + src/drm.cpp | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 2a7208f72..352f8b11c 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -2519,10 +2519,10 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + assert( drm->req == nullptr ); + drm->req = drmModeAtomicAlloc(); + +- bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; +- bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; +- + if (drm->connector != nullptr) { ++ bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; ++ bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; ++ + if (drm->connector->has_colorspace) { + drm->connector->pending.colorspace = ( bConnectorHDR ) ? DRM_MODE_COLORIMETRY_BT2020_RGB : DRM_MODE_COLORIMETRY_DEFAULT; + } + +From 8dffcfa51049ebe235149a4a7f8ed295d27cde6e Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sat, 23 Dec 2023 11:27:30 +0000 +Subject: [PATCH 059/134] steamcompmgr: Support saving AVIF screenshots + +--- + src/drm.cpp | 15 +++- + src/drm.hpp | 4 +- + src/meson.build | 3 +- + src/rendervulkan.cpp | 8 ++- + src/rendervulkan.hpp | 1 + + src/steamcompmgr.cpp | 165 +++++++++++++++++++++++++++++++++++++++++-- + 6 files changed, 183 insertions(+), 13 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 352f8b11c..29682a356 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -486,6 +486,9 @@ drm_hdr_parse_edid(drm_t *drm, struct connector *connector, const struct di_edid + infoframe->max_cll = nits_to_u16(default_max_fall); + infoframe->max_fall = nits_to_u16(default_max_fall); + } ++ ++ metadata->maxCLL = (uint16_t)hdr_static_metadata->desired_content_max_luminance; ++ metadata->maxFALL = (uint16_t)hdr_static_metadata->desired_content_max_frame_avg_luminance; + } + + metadata->supportsST2084 = +@@ -3124,10 +3127,16 @@ bool drm_get_vrr_capable(struct drm_t *drm) + return false; + } + +-bool drm_supports_st2084(struct drm_t *drm) ++bool drm_supports_st2084(struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL) + { +- if ( drm->connector ) +- return drm->connector->metadata.supportsST2084; ++ if ( drm->connector && drm->connector->metadata.supportsST2084 ) ++ { ++ if ( maxCLL ) ++ *maxCLL = drm->connector->metadata.maxCLL; ++ if ( maxFALL ) ++ *maxFALL = drm->connector->metadata.maxFALL; ++ return true; ++ } + + return false; + } +diff --git a/src/drm.hpp b/src/drm.hpp +index 4840e2b8d..409c87c92 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -174,6 +174,8 @@ struct connector_metadata_t { + + displaycolorimetry_t colorimetry = displaycolorimetry_709; + EOTF eotf = EOTF_Gamma22; ++ uint16_t maxCLL = 0; ++ uint16_t maxFALL = 0; + }; + + struct connector { +@@ -363,7 +365,7 @@ drm_screen_type drm_get_screen_type(struct drm_t *drm); + char *find_drm_node_by_devid(dev_t devid); + int drm_get_default_refresh(struct drm_t *drm); + bool drm_get_vrr_capable(struct drm_t *drm); +-bool drm_supports_st2084(struct drm_t *drm); ++bool drm_supports_st2084(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); + void drm_set_vrr_enabled(struct drm_t *drm, bool enabled); + bool drm_get_vrr_in_use(struct drm_t *drm); + bool drm_supports_color_mgmt(struct drm_t *drm); +diff --git a/src/meson.build b/src/meson.build +index 5385dfb8b..1df4f027b 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -20,6 +20,7 @@ epoll_dep = dependency('epoll-shim', required: false) + glm_dep = dependency('glm') + sdl_dep = dependency('SDL2') + stb_dep = dependency('stb') ++avif_dep = dependency('libavif') + + wlroots_dep = dependency( + 'wlroots', +@@ -135,7 +136,7 @@ endif + dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, + xkbcommon, thread_dep, sdl_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, ++ stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, + ], + install: true, + ) +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 936c1d6a4..b202352a4 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -3638,12 +3638,16 @@ void bind_all_layers(CVulkanCmdBuffer* cmdBuffer, const struct FrameInfo_t *fram + + std::optional vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ) + { ++ 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(), EOTF_Gamma22 )); ++ 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); +@@ -3661,7 +3665,7 @@ extern uint32_t g_reshade_technique_idx; + + std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture, bool partial, std::shared_ptr pOutputOverride, bool increment ) + { +- EOTF outputTF = g_ColorMgmt.current.outputEncodingEOTF; ++ EOTF outputTF = frameInfo->outputEncodingEOTF; + if (!frameInfo->applyOutputColorMgmt) + outputTF = EOTF_Count; //Disable blending stuff. + +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index e9984695f..e10295208 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -270,6 +270,7 @@ struct FrameInfo_t + std::shared_ptr lut3D[EOTF_Count]; + + bool applyOutputColorMgmt; // drm only ++ EOTF outputEncodingEOTF; + + int layerCount; + struct Layer_t +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 40f812531..bf4a9871c 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -90,6 +90,8 @@ + #include "win32_styles.h" + #include "mwm_hints.h" + ++#include "avif/avif.h" ++ + static const int g_nBaseCursorScale = 36; + + #if HAVE_PIPEWIRE +@@ -115,6 +117,8 @@ LogScope g_WaitableLog("waitable"); + + bool g_bWasPartialComposite = false; + ++bool g_bUseAVIFScreenshots = false; ++ + /// + // Color Mgmt + // +@@ -128,6 +132,7 @@ static lut3d_t g_ColorMgmtLooks[ EOTF_Count ]; + gamescope_color_mgmt_luts g_ColorMgmtLuts[ EOTF_Count ]; + + gamescope_color_mgmt_luts g_ScreenshotColorMgmtLuts[ EOTF_Count ]; ++gamescope_color_mgmt_luts g_ScreenshotColorMgmtLutsHDR[ EOTF_Count ]; + + static lut1d_t g_tmpLut1d; + static lut3d_t g_tmpLut3d; +@@ -179,6 +184,15 @@ static const gamescope_color_mgmt_t k_ScreenshotColorMgmt = + .outputEncodingEOTF = EOTF_Gamma22, + }; + ++static const gamescope_color_mgmt_t k_ScreenshotColorMgmtHDR = ++{ ++ .enabled = true, ++ .displayColorimetry = displaycolorimetry_2020, ++ .displayEOTF = EOTF_PQ, ++ .outputEncodingColorimetry = displaycolorimetry_2020, ++ .outputEncodingEOTF = EOTF_PQ, ++}; ++ + //#define COLOR_MGMT_MICROBENCH + // sudo cpupower frequency-set --governor performance + +@@ -399,6 +413,7 @@ static void + update_screenshot_color_mgmt() + { + create_color_mgmt_luts(k_ScreenshotColorMgmt, g_ScreenshotColorMgmtLuts); ++ create_color_mgmt_luts(k_ScreenshotColorMgmtHDR, g_ScreenshotColorMgmtLutsHDR); + } + + bool set_color_sdr_gamut_wideness( float flVal ) +@@ -2431,6 +2446,7 @@ paint_all(bool async) + + struct FrameInfo_t frameInfo = {}; + frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; ++ frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; + + // 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 +@@ -3017,21 +3033,29 @@ paint_all(bool async) + + if ( takeScreenshot ) + { +- uint32_t drmCaptureFormat = bHackForceNV12DumpScreenshot +- ? DRM_FORMAT_NV12 +- : DRM_FORMAT_XRGB8888; ++ uint32_t drmCaptureFormat = DRM_FORMAT_XRGB8888; ++ ++ if ( bHackForceNV12DumpScreenshot ) ++ drmCaptureFormat = DRM_FORMAT_NV12; ++ else if ( g_bUseAVIFScreenshots ) ++ drmCaptureFormat = DRM_FORMAT_XRGB2101010; + + std::shared_ptr pScreenshotTexture = vulkan_acquire_screenshot_texture(g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat); + + if ( pScreenshotTexture ) + { ++ bool bHDRScreenshot = frameInfo.layerCount > 0 && ++ ColorspaceIsHDR( frameInfo.layers[0].colorspace ) && ++ takeScreenshot != TAKE_SCREENSHOT_SCREEN_BUFFER; ++ + if ( drmCaptureFormat == DRM_FORMAT_NV12 || takeScreenshot != TAKE_SCREENSHOT_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++ ) + { +- frameInfo.lut3D[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut3d; +- frameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; ++ auto& luts = bHDRScreenshot ? g_ScreenshotColorMgmtLutsHDR : g_ScreenshotColorMgmtLuts; ++ frameInfo.lut3D[nInputEOTF] = luts[nInputEOTF].vk_lut3d; ++ frameInfo.shaperLut[nInputEOTF] = luts[nInputEOTF].vk_lut1d; + } + + if ( takeScreenshot == TAKE_SCREENSHOT_BASEPLANE_ONLY ) +@@ -3071,6 +3095,11 @@ paint_all(bool async) + + frameInfo.applyOutputColorMgmt = true; + ++ frameInfo.outputEncodingEOTF = bHDRScreenshot ? EOTF_PQ : EOTF_Gamma22; ++ ++ uint32_t uCompositeDebugBackup = g_uCompositeDebug; ++ g_uCompositeDebug = 0; ++ + std::optional oScreenshotSeq; + if ( drmCaptureFormat == DRM_FORMAT_NV12 ) + oScreenshotSeq = vulkan_composite( &frameInfo, pScreenshotTexture, false, nullptr ); +@@ -3079,6 +3108,8 @@ paint_all(bool async) + else + oScreenshotSeq = vulkan_screenshot( &frameInfo, pScreenshotTexture ); + ++ g_uCompositeDebug = uCompositeDebugBackup; ++ + if ( !oScreenshotSeq ) + { + xwm_log.errorf("vulkan_screenshot failed"); +@@ -3087,12 +3118,134 @@ paint_all(bool async) + + 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 ) ++ { ++ drm_supports_st2084( &g_DRM, &maxCLLNits, &maxFALLNits ); ++ } ++ ++ 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(); + +- if (pScreenshotTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) ++ 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; ++ } ++ } ++ ++ 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 ( takeScreenshot == TAKE_SCREENSHOT_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 ); ++ ++ avifImageRGBToYUV( pAvifImage, &rgbAvifImage ); // Not really! See Matrix Coefficients IDENTITY above. ++ ++ 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; ++ } ++ ++ char pFileName[1024] = "/tmp/gamescope.avif"; ++ ++ if ( !propertyRequestedScreenshot ) ++ { ++ time_t currentTime = time(0); ++ struct tm *localTime = localtime( ¤tTime ); ++ strftime( pFileName, sizeof( pFileName ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.avif", localTime ); ++ } ++ ++ FILE *pScreenshotFile = nullptr; ++ if ( ( pScreenshotFile = fopen( pFileName, "wb" ) ) == nullptr ) ++ { ++ xwm_log.errorf( "Failed to fopen file: %s", pFileName ); ++ return; ++ } ++ ++ fwrite( avifOutput.data, 1, avifOutput.size, pScreenshotFile ); ++ fclose( pScreenshotFile ); ++ ++ xwm_log.infof( "Screenshot saved to %s", pFileName ); ++ } ++ 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); + +From a82befbde1f3c7756f257cf20bbadff01706d2f2 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sat, 23 Dec 2023 12:32:17 +0000 +Subject: [PATCH 060/134] protocols, steamcompmgr: Add take_screenshot + interface, refactor screenshot taking + +--- + protocol/gamescope-control.xml | 24 +++++- + src/main.cpp | 2 +- + src/sdlwindow.cpp | 2 +- + src/steamcompmgr.cpp | 152 +++++++++++++++------------------ + src/steamcompmgr.hpp | 9 -- + src/steamcompmgr_shared.hpp | 49 +++++++++++ + src/xwayland_ctx.hpp | 1 - + 7 files changed, 143 insertions(+), 96 deletions(-) + +diff --git a/protocol/gamescope-control.xml b/protocol/gamescope-control.xml +index 376de490a..4359eb148 100644 +--- a/protocol/gamescope-control.xml ++++ b/protocol/gamescope-control.xml +@@ -29,7 +29,7 @@ + it. + + +- ++ + + + +@@ -76,5 +76,27 @@ + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + +diff --git a/src/main.cpp b/src/main.cpp +index 76721d63d..5be060bf0 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -412,7 +412,7 @@ static void handle_signal( int sig ) + { + switch ( sig ) { + case SIGUSR2: +- take_screenshot( TAKE_SCREENSHOT_BASEPLANE_ONLY ); ++ gamescope::CScreenshotManager::Get().TakeScreenshot( true ); + break; + case SIGHUP: + case SIGQUIT: +diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp +index 2eb8c3c74..78912e15a 100644 +--- a/src/sdlwindow.cpp ++++ b/src/sdlwindow.cpp +@@ -286,7 +286,7 @@ void inputSDLThreadRun( void ) + g_upscaleFilterSharpness = std::max(0, g_upscaleFilterSharpness - 1); + break; + case KEY_S: +- take_screenshot(); ++ gamescope::CScreenshotManager::Get().TakeScreenshot( true ); + break; + case KEY_G: + g_bGrabbed = !g_bGrabbed; +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index bf4a9871c..b42eecf61 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -48,6 +48,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -117,8 +118,6 @@ LogScope g_WaitableLog("waitable"); + + bool g_bWasPartialComposite = false; + +-bool g_bUseAVIFScreenshots = false; +- + /// + // Color Mgmt + // +@@ -1092,8 +1091,15 @@ struct wlr_buffer_map_entry { + static std::mutex wlr_buffer_map_lock; + static std::unordered_map wlr_buffer_map; + +-static std::atomic< int > g_nTakeScreenshot{ 0 }; +-static bool g_bPropertyRequestedScreenshot; ++namespace gamescope ++{ ++ CScreenshotManager &CScreenshotManager::Get() ++ { ++ static CScreenshotManager s_Instance; ++ return s_Instance; ++ } ++} ++ + + static std::atomic g_bForceRepaint{false}; + +@@ -2651,11 +2657,6 @@ paint_all(bool async) + + bool bDoComposite = true; + +- // Handoff from whatever thread to this one since we check ours twice +- int takeScreenshot = g_nTakeScreenshot.exchange( 0 ); +- bool propertyRequestedScreenshot = g_bPropertyRequestedScreenshot; +- g_bPropertyRequestedScreenshot = false; +- + update_app_target_refresh_cycle(); + + int nDynamicRefresh = g_nDynamicRefreshRate[drm_get_screen_type( &g_DRM )]; +@@ -2714,8 +2715,6 @@ paint_all(bool async) + bNeedsFullComposite |= fadingOut; + bNeedsFullComposite |= !g_reshade_effect.empty(); + +- constexpr bool bHackForceNV12DumpScreenshot = false; +- + for (uint32_t i = 0; i < EOTF_Count; i++) + { + if (g_ColorMgmtLuts[i].HasLuts()) +@@ -3031,24 +3030,33 @@ paint_all(bool async) + } + #endif + +- if ( takeScreenshot ) ++ std::optional oScreenshotInfo = ++ gamescope::CScreenshotManager::Get().ProcessPendingScreenshot(); ++ ++ if ( oScreenshotInfo ) + { +- uint32_t drmCaptureFormat = DRM_FORMAT_XRGB8888; ++ std::filesystem::path path = std::filesystem::path{ oScreenshotInfo->szScreenshotPath }; + +- if ( bHackForceNV12DumpScreenshot ) +- drmCaptureFormat = DRM_FORMAT_NV12; +- else if ( g_bUseAVIFScreenshots ) ++ 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; + +- std::shared_ptr pScreenshotTexture = vulkan_acquire_screenshot_texture(g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat); ++ std::shared_ptr pScreenshotTexture; ++ if ( drmCaptureFormat != DRM_FORMAT_INVALID ) ++ pScreenshotTexture = vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat ); + + if ( pScreenshotTexture ) + { + bool bHDRScreenshot = frameInfo.layerCount > 0 && + ColorspaceIsHDR( frameInfo.layers[0].colorspace ) && +- takeScreenshot != TAKE_SCREENSHOT_SCREEN_BUFFER; ++ oScreenshotInfo->eScreenshotType != GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER; + +- if ( drmCaptureFormat == DRM_FORMAT_NV12 || takeScreenshot != TAKE_SCREENSHOT_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++ ) +@@ -3058,7 +3066,7 @@ paint_all(bool async) + frameInfo.shaperLut[nInputEOTF] = luts[nInputEOTF].vk_lut1d; + } + +- if ( takeScreenshot == TAKE_SCREENSHOT_BASEPLANE_ONLY ) ++ 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++) +@@ -3103,7 +3111,8 @@ paint_all(bool async) + std::optional oScreenshotSeq; + if ( drmCaptureFormat == DRM_FORMAT_NV12 ) + oScreenshotSeq = vulkan_composite( &frameInfo, pScreenshotTexture, false, nullptr ); +- else if ( takeScreenshot == TAKE_SCREENSHOT_FULL_COMPOSITION || takeScreenshot == TAKE_SCREENSHOT_SCREEN_BUFFER ) ++ 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 ); +@@ -3149,6 +3158,8 @@ paint_all(bool async) + + 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. +@@ -3179,7 +3190,7 @@ paint_all(bool async) + // This does not compress as well, but is always lossless! + pAvifImage->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; + +- if ( takeScreenshot == TAKE_SCREENSHOT_SCREEN_BUFFER ) ++ 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 +@@ -3224,26 +3235,18 @@ paint_all(bool async) + return; + } + +- char pFileName[1024] = "/tmp/gamescope.avif"; +- +- if ( !propertyRequestedScreenshot ) +- { +- time_t currentTime = time(0); +- struct tm *localTime = localtime( ¤tTime ); +- strftime( pFileName, sizeof( pFileName ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.avif", localTime ); +- } +- + FILE *pScreenshotFile = nullptr; +- if ( ( pScreenshotFile = fopen( pFileName, "wb" ) ) == nullptr ) ++ if ( ( pScreenshotFile = fopen( oScreenshotInfo->szScreenshotPath.c_str(), "wb" ) ) == nullptr ) + { +- xwm_log.errorf( "Failed to fopen file: %s", pFileName ); ++ 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", pFileName ); ++ xwm_log.infof( "Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str() ); ++ bScreenshotSuccess = true; + } + else if (pScreenshotTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) + { +@@ -3262,73 +3265,65 @@ paint_all(bool async) + imageData[y * pitch + x * comp + 3] = 255; + } + } +- +- char pTimeBuffer[1024] = "/tmp/gamescope.png"; +- +- if ( !propertyRequestedScreenshot ) ++ if ( stbi_write_png( oScreenshotInfo->szScreenshotPath.c_str(), currentOutputWidth, currentOutputHeight, 4, imageData.data(), pitch ) ) + { +- time_t currentTime = time(0); +- struct tm *localTime = localtime( ¤tTime ); +- strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.png", localTime ); +- } +- +- if ( stbi_write_png(pTimeBuffer, currentOutputWidth, currentOutputHeight, 4, imageData.data(), pitch) ) +- { +- xwm_log.infof("Screenshot saved to %s", pTimeBuffer); ++ xwm_log.infof( "Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str() ); ++ bScreenshotSuccess = true; + } + else + { +- xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); ++ xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); + } + } + else if (pScreenshotTexture->format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) + { +- char pTimeBuffer[1024] = "/tmp/gamescope.raw"; +- +- if ( !propertyRequestedScreenshot ) +- { +- time_t currentTime = time(0); +- struct tm *localTime = localtime( ¤tTime ); +- strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.raw", localTime ); +- } +- +- FILE *file = fopen(pTimeBuffer, "wb"); ++ FILE *file = fopen( oScreenshotInfo->szScreenshotPath.c_str(), "wb" ); + if (file) + { + fwrite(mappedData, 1, pScreenshotTexture->totalSize(), file ); + fclose(file); + + char cmd[4096]; +- sprintf(cmd, "ffmpeg -f rawvideo -pixel_format nv12 -video_size %dx%d -i %s %s_encoded.png", pScreenshotTexture->width(), pScreenshotTexture->height(), pTimeBuffer, pTimeBuffer); ++ 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) { ++ if (ret) { + xwm_log.infof("Ffmpeg call return status %i", ret); +- xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); ++ xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); + } else { +- xwm_log.infof("Screenshot saved to %s", pTimeBuffer); ++ xwm_log.infof("Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str()); ++ bScreenshotSuccess = true; + } + } + else + { +- xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); ++ xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); + } + } + +- XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); ++ if ( oScreenshotInfo->bX11PropertyRequested ) ++ XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); ++ ++ if ( bScreenshotSuccess ) ++ { ++ wlserver_lock(); ++ for ( const auto &control : wlserver.gamescope_controls ) ++ { ++ gamescope_control_send_screenshot_taken( control, oScreenshotInfo->szScreenshotPath.c_str() ); ++ } ++ wlserver_unlock(); ++ } + }); + + screenshotThread.detach(); +- +- takeScreenshot = 0; + } + else + { + xwm_log.errorf( "Oh no, we ran out of screenshot images. Not actually writing a screenshot." ); +- XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); +- takeScreenshot = 0; ++ if ( oScreenshotInfo->bX11PropertyRequested ) ++ XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); + } + } + +@@ -5602,15 +5597,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + { + if ( ev->state == PropertyNewValue ) + { +- g_nTakeScreenshot = (int)get_prop( ctx, ctx->root, ctx->atoms.gamescopeScreenShotAtom, None ); +- g_bPropertyRequestedScreenshot = true; +- } +- } +- if ( ev->atom == ctx->atoms.gamescopeDebugScreenShotAtom ) +- { +- if ( ev->state == PropertyNewValue ) +- { +- g_nTakeScreenshot = (int)get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugScreenShotAtom, None ); ++ 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.gameAtom) +@@ -6736,12 +6729,6 @@ void nudge_steamcompmgr( void ) + g_SteamCompMgrWaiter.Nudge(); + } + +-void take_screenshot( int flags ) +-{ +- g_nTakeScreenshot = flags; +- nudge_steamcompmgr(); +-} +- + void force_repaint( void ) + { + g_bForceRepaint = true; +@@ -7387,7 +7374,6 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + 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); +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index e73a70767..59ac3b283 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -49,14 +49,6 @@ extern EStreamColorspace g_ForcedNV12ColorSpace; + // use the proper libliftoff composite plane system. + static constexpr bool kDisablePartialComposition = true; + +-enum TakeScreenshotMode_t +-{ +- TAKE_SCREENSHOT_BASEPLANE_ONLY = 1, // Just the game/base plane +- TAKE_SCREENSHOT_ALL_REAL_LAYERS = 2, // Just the game + steam overlay/perf overlays +- TAKE_SCREENSHOT_FULL_COMPOSITION = 3, // Everything, but no display color management + no mura +- TAKE_SCREENSHOT_SCREEN_BUFFER = 4, // Yes, mura comp, color management! Exactly what we put on the screen. +-}; +- + class MouseCursor + { + public: +@@ -146,7 +138,6 @@ extern uint32_t inputCounter; + extern uint64_t g_lastWinSeq; + + void nudge_steamcompmgr( void ); +-void take_screenshot( int flags = TAKE_SCREENSHOT_BASEPLANE_ONLY ); + void force_repaint( void ); + + extern void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns ); +diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp +index 3bcdb16e8..7de514583 100644 +--- a/src/steamcompmgr_shared.hpp ++++ b/src/steamcompmgr_shared.hpp +@@ -2,6 +2,8 @@ + + #include "xwayland_ctx.hpp" + #include ++#include ++#include "gamescope-control-protocol.h" + + struct commit_t; + struct wlserver_vk_swapchain_feedback; +@@ -179,3 +181,50 @@ struct steamcompmgr_win_t { + return nullptr; + } + }; ++ ++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; ++ }; ++ ++ class CScreenshotManager ++ { ++ public: ++ void TakeScreenshot( GamescopeScreenshotInfo info = GamescopeScreenshotInfo{} ) ++ { ++ std::unique_lock lock{ m_ScreenshotInfoMutex }; ++ m_ScreenshotInfo = std::move( info ); ++ } ++ ++ 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; ++} +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 5764c4b7b..7a4030f5d 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -133,7 +133,6 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + Atom gamescopeCtrlWindowAtom; + Atom gamescopeInputCounterAtom; + Atom gamescopeScreenShotAtom; +- Atom gamescopeDebugScreenShotAtom; + + Atom gamescopeFocusDisplay; + Atom gamescopeMouseFocusDisplay; + +From 20e9bc1429b0648bf2da6f049f2988e9067a2e64 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sat, 23 Dec 2023 12:36:24 +0000 +Subject: [PATCH 061/134] ci: Add deps for AV1 + +--- + .github/workflows/main.yml | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml +index 9ab3b0d87..5c2967142 100644 +--- a/.github/workflows/main.yml ++++ b/.github/workflows/main.yml +@@ -17,7 +17,8 @@ jobs: + pacman -S --noconfirm git meson clang glslang libcap wlroots \ + sdl2 vulkan-headers libx11 libxmu libxcomposite libxrender libxres \ + libxtst libxkbcommon libdrm libinput wayland-protocols benchmark \ +- xorg-xwayland pipewire cmake ++ xorg-xwayland pipewire cmake \ ++ libavif libheif aom rav1e + - uses: actions/checkout@v2 + with: + submodules: recursive + +From d434d86c0d8a569af518c6bdd9ff2616f5a784c9 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sat, 23 Dec 2023 12:38:43 +0000 +Subject: [PATCH 062/134] steamcompmgr: Only save HDR screenshots if outputting + as AVIF + +--- + src/steamcompmgr.cpp | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index b42eecf61..86b57796f 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -3052,7 +3052,8 @@ paint_all(bool async) + + if ( pScreenshotTexture ) + { +- bool bHDRScreenshot = frameInfo.layerCount > 0 && ++ bool bHDRScreenshot = path.extension() == ".avif" && ++ frameInfo.layerCount > 0 && + ColorspaceIsHDR( frameInfo.layers[0].colorspace ) && + oScreenshotInfo->eScreenshotType != GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER; + + +From d7758a8d7eae69384a33263eee267d60fdcc98f9 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 26 Dec 2023 15:07:43 +0000 +Subject: [PATCH 063/134] steamcompmgr: Use XInput2 RawMotion events as a hint + to update cursor pos + +Taking a page out of XEyes' book to get the cursor position (even when the cursor is grabbed, etc) by only doing so when we get a RawMotion event -- and only register for those when we know there's a cursor image that we would want to render. + +This is a kinda crap workaround for the fact that we cannot directly just recieve information about the cursor from the XServer, even as the X11 WM due to cursor grabbing, input masking and other such nonsense. + +An alternative would be using something like libei and having XTest funnel through that -- however that needs work to be functional on nested compositors. +--- + src/meson.build | 3 +- + src/steamcompmgr.cpp | 169 ++++++++++++++++++++++++++----------------- + src/steamcompmgr.hpp | 30 ++++++-- + src/xwayland_ctx.hpp | 1 + + 4 files changed, 129 insertions(+), 74 deletions(-) + +diff --git a/src/meson.build b/src/meson.build +index 1df4f027b..4cb546696 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -8,6 +8,7 @@ dep_xxf86vm = dependency('xxf86vm') + dep_xtst = dependency('xtst') + dep_xres = dependency('xres') + dep_xmu = dependency('xmu') ++dep_xi = dependency('xi') + + drm_dep = dependency('libdrm', version: '>= 2.4.113') + +@@ -136,7 +137,7 @@ endif + dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, + xkbcommon, thread_dep, sdl_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, ++ stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi + ], + install: true, + ) +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 86b57796f..c24ba58ba 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -34,6 +34,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1579,35 +1580,9 @@ MouseCursor::MouseCursor(xwayland_ctx_t *ctx) + updateCursorFeedback( true ); + } + +-void MouseCursor::queryPositions(int &rootX, int &rootY, int &winX, int &winY) +-{ +- Window window, child; +- unsigned int mask; +- +- XQueryPointer(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), &window, &child, +- &rootX, &rootY, &winX, &winY, &mask); +- +-} +- +-void MouseCursor::queryGlobalPosition(int &x, int &y) +-{ +- int winX, winY; +- queryPositions(x, y, winX, winY); +-} +- +-void MouseCursor::queryButtonMask(unsigned int &mask) +-{ +- Window window, child; +- int rootX, rootY, winX, winY; +- +- XQueryPointer(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), &window, &child, +- &rootX, &rootY, &winX, &winY, &mask); +-} +- + void MouseCursor::checkSuspension() + { +- unsigned int buttonMask; +- queryButtonMask(buttonMask); ++ unsigned int buttonMask = 0; + + bool bWasHidden = m_hideForMovement; + +@@ -1666,11 +1641,6 @@ void MouseCursor::warp(int x, int y) + XWarpPointer(m_ctx->dpy, None, x11_win(m_ctx->focus.inputFocusWindow), 0, 0, 0, 0, x, y); + } + +-void MouseCursor::resetPosition() +-{ +- warp(m_x, m_y); +-} +- + void MouseCursor::setDirty() + { + // We can't prove it's empty until checking again +@@ -1767,23 +1737,28 @@ bool MouseCursor::setCursorImageByName(const char *name) + + void MouseCursor::constrainPosition() + { +- int i; + steamcompmgr_win_t *window = m_ctx->focus.inputFocusWindow; + steamcompmgr_win_t *override = m_ctx->focus.overrideWindow; + if (window == override) + window = m_ctx->focus.focusWindow; + +- // If we had barriers before, get rid of them. +- for (i = 0; i < 4; i++) { +- if (m_scaledFocusBarriers[i] != None) { +- XFixesDestroyPointerBarrier(m_ctx->dpy, m_scaledFocusBarriers[i]); +- m_scaledFocusBarriers[i] = None; ++ if (!window) ++ return; ++ ++ auto barricade = [this](CursorBarrier& barrier, const CursorBarrierInfo& info) { ++ if (barrier.info.x1 == info.x1 && barrier.info.x2 == info.x2 && ++ barrier.info.y1 == info.y1 && barrier.info.y2 == info.y2) ++ return; ++ ++ if (barrier.obj != None) ++ { ++ XFixesDestroyPointerBarrier(m_ctx->dpy, barrier.obj); ++ barrier.obj = None; + } +- } + +- auto barricade = [this](int x1, int y1, int x2, int y2) { +- return XFixesCreatePointerBarrier(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), +- x1, y1, x2, y2, 0, 0, NULL); ++ barrier.obj = XFixesCreatePointerBarrier(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), ++ info.x1, info.y1, info.x2, info.y2, 0, 0, NULL); ++ barrier.info = info; + }; + + int x1 = window->xwayland().a.x; +@@ -1802,14 +1777,13 @@ void MouseCursor::constrainPosition() + } + + // Constrain it to the window; careful, the corners will leak due to a known X server bug. +- m_scaledFocusBarriers[0] = barricade(0, y1, m_ctx->root_width, y1); +- m_scaledFocusBarriers[1] = barricade(x2, 0, x2, m_ctx->root_height); +- m_scaledFocusBarriers[2] = barricade(m_ctx->root_width, y2, 0, y2); +- m_scaledFocusBarriers[3] = barricade(x1, m_ctx->root_height, x1, 0); ++ barricade(m_barriers[0], CursorBarrierInfo{ 0, y1, m_ctx->root_width, y1 }); ++ barricade(m_barriers[1], CursorBarrierInfo{ x2, 0, x2, m_ctx->root_height }); ++ barricade(m_barriers[2], CursorBarrierInfo{ m_ctx->root_width, y2, 0, y2 }); ++ barricade(m_barriers[3], CursorBarrierInfo{ x1, m_ctx->root_height, x1, 0 }); + + // Make sure the cursor is somewhere in our jail +- int rootX, rootY; +- queryGlobalPosition(rootX, rootY); ++ int rootX = m_x, rootY = m_y; + + if ( rootX >= x2 || rootY >= y2 || rootX < x1 || rootY < y1 ) { + if ( window_wants_no_focus_when_mouse_hidden( window ) && m_hideForMovement ) +@@ -1865,14 +1839,6 @@ void MouseCursor::move(int x, int y) + updateCursorFeedback(); + } + +-void MouseCursor::updatePosition() +-{ +- int x,y; +- queryGlobalPosition(x, y); +- move(x, y); +- checkSuspension(); +-} +- + int MouseCursor::x() const + { + return m_x; +@@ -1987,12 +1953,15 @@ bool MouseCursor::getTexture() + + m_dirty = false; + updateCursorFeedback(); ++ UpdateXInputMotionMasks(); + + if (m_imageEmpty) { + + return false; + } + ++ UpdatePosition(); ++ + CVulkanTexture::createFlags texCreateFlags; + if ( BIsNested() == false ) + { +@@ -2022,15 +1991,46 @@ void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) + nHeight = nSize; + } + ++void MouseCursor::UpdateXInputMotionMasks() ++{ ++ bool bShouldMotionMask = !imageEmpty(); ++ ++ if ( m_bMotionMaskEnabled != bShouldMotionMask ) ++ { ++ XIEventMask xi_eventmask; ++ unsigned char xi_mask[ ( XI_LASTEVENT + 7 ) / 8 ]{}; ++ xi_eventmask.deviceid = XIAllDevices; ++ xi_eventmask.mask_len = sizeof( xi_mask ); ++ xi_eventmask.mask = xi_mask; ++ if ( bShouldMotionMask ) ++ XISetMask( xi_mask, XI_RawMotion ); ++ XISelectEvents( m_ctx->dpy, m_ctx->root, &xi_eventmask, 1 ); ++ ++ m_bMotionMaskEnabled = bShouldMotionMask; ++ } ++} ++ ++void MouseCursor::UpdatePosition() ++{ ++ Window root_return, child_return; ++ int root_x_return, root_y_return; ++ int win_x_return, win_y_return; ++ unsigned int mask_return; ++ XQueryPointer(m_ctx->dpy, m_ctx->root, &root_return, &child_return, ++ &root_x_return, &root_y_return, ++ &win_x_return, &win_y_return, ++ &mask_return); ++ ++ move(root_x_return, root_y_return); ++} ++ + void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, struct FrameInfo_t *frameInfo) + { + if ( m_hideForMovement || m_imageEmpty ) { + return; + } + +- int rootX, rootY, winX, winY; +- queryPositions(rootX, rootY, winX, winY); +- move(rootX, rootY); ++ int winX = m_x, winY = m_y; + + // Also need new texture + if (!getTexture()) { +@@ -6996,6 +6996,7 @@ void xwayland_ctx_t::Dispatch() + MouseCursor *cursor = ctx->cursor.get(); + bool bShouldResetCursor = false; + bool bSetFocus = false; ++ bool bShouldUpdateCursor = false; + + while (XPending(ctx->dpy)) + { +@@ -7135,6 +7136,16 @@ void xwayland_ctx_t::Dispatch() + case SelectionRequest: + handle_selection_request(ctx, &ev.xselectionrequest); + break; ++ case GenericEvent: ++ if (ev.xcookie.extension == ctx->xinput_opcode) ++ { ++ if (ev.xcookie.evtype == XI_RawMotion) ++ { ++ bShouldUpdateCursor = true; ++ } ++ } ++ break; ++ + default: + if (ev.type == ctx->damage_event + XDamageNotify) + { +@@ -7153,12 +7164,24 @@ void xwayland_ctx_t::Dispatch() + XFlush(ctx->dpy); + } + +- if ( bShouldResetCursor ) ++ if ( bShouldUpdateCursor ) + { +- // This shouldn't happen due to our pointer barriers, +- // but there is a known X server bug; warp to last good +- // position. +- cursor->resetPosition(); ++ cursor->UpdatePosition(); ++ ++ if ( bShouldResetCursor ) ++ { ++ // This shouldn't happen due to our pointer barriers, ++ // but there is a known X server bug; warp to last good ++ // position. ++ steamcompmgr_win_t *pInputWindow = ctx->focus.inputFocusWindow; ++ int nX = std::clamp( cursor->x(), pInputWindow->xwayland().a.x, pInputWindow->xwayland().a.x + pInputWindow->xwayland().a.width ); ++ int nY = std::clamp( cursor->y(), pInputWindow->xwayland().a.y, pInputWindow->xwayland().a.y + pInputWindow->xwayland().a.height ); ++ ++ if ( cursor->x() != nX || cursor->y() != nY ) ++ { ++ cursor->forcePosition( nX, nY ); ++ } ++ } + } + + if ( bSetFocus ) +@@ -7319,6 +7342,18 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + 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)) + { +@@ -7543,6 +7578,9 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + } + } + ++ ctx->cursor->undirty(); ++ ctx->cursor->UpdateXInputMotionMasks(); ++ + XFlush(ctx->dpy); + } + +@@ -8239,7 +8277,8 @@ steamcompmgr_main(int argc, char **argv) + + if (global_focus.cursor) + { +- global_focus.cursor->updatePosition(); ++ global_focus.cursor->constrainPosition(); ++ global_focus.cursor->checkSuspension(); + + if (global_focus.cursor->needs_server_flush()) + { +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index 59ac3b283..16ef13273 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -49,6 +49,20 @@ extern EStreamColorspace g_ForcedNV12ColorSpace; + // use the proper libliftoff composite plane system. + static constexpr bool kDisablePartialComposition = true; + ++struct CursorBarrierInfo ++{ ++ int x1 = 0; ++ int y1 = 0; ++ int x2 = 0; ++ int y2 = 0; ++}; ++ ++struct CursorBarrier ++{ ++ PointerBarrier obj = None; ++ CursorBarrierInfo info = {}; ++}; ++ + class MouseCursor + { + public: +@@ -58,9 +72,7 @@ class MouseCursor + int y() const; + + void move(int x, int y); +- void updatePosition(); + void constrainPosition(); +- void resetPosition(); + + void paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, FrameInfo_t *frameInfo); + void setDirty(); +@@ -72,6 +84,7 @@ class MouseCursor + void hide() { m_lastMovedTime = 0; checkSuspension(); } + + bool isHidden() { return m_hideForMovement || m_imageEmpty; } ++ bool imageEmpty() const { return m_imageEmpty; } + + void forcePosition(int x, int y) + { +@@ -89,13 +102,12 @@ class MouseCursor + + void GetDesiredSize( int& nWidth, int &nHeight ); + ++ void UpdateXInputMotionMasks(); ++ void UpdatePosition(); ++ ++ void checkSuspension(); + private: + void warp(int x, int y); +- void checkSuspension(); +- +- void queryGlobalPosition(int &x, int &y); +- void queryPositions(int &rootX, int &rootY, int &winX, int &winY); +- void queryButtonMask(unsigned int &mask); + + bool getTexture(); + +@@ -111,7 +123,9 @@ class MouseCursor + unsigned int m_lastMovedTime = 0; + bool m_hideForMovement; + +- PointerBarrier m_scaledFocusBarriers[4] = { None }; ++ bool m_bMotionMaskEnabled = false; ++ ++ CursorBarrier m_barriers[4] = {}; + + xwayland_ctx_t *m_ctx; + +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 7a4030f5d..11f9335de 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -70,6 +70,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + 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; + +From 962f0abf023cc83cf44f6c1f4acdce8d63668c2b Mon Sep 17 00:00:00 2001 +From: sharkautarch <128002472+sharkautarch@users.noreply.github.com> +Date: Wed, 27 Dec 2023 18:31:54 -0500 +Subject: [PATCH 064/134] move calls to bInit() outside of assert, so that + bInit() still runs when gamescope is built with NDEBUG set + +--- + src/rendervulkan.cpp | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index b202352a4..3dbbfe069 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -2765,7 +2765,8 @@ std::shared_ptr vulkan_create_1d_lut(uint32_t size) + + auto texture = std::make_shared(); + auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM ); +- assert( texture->BInit( size, 1u, 1u, drmFormat, flags ) ); ++ bool bRes = texture->BInit( size, 1u, 1u, drmFormat, flags ); ++ assert( bRes ); + + return texture; + } +@@ -2779,7 +2780,8 @@ std::shared_ptr vulkan_create_3d_lut(uint32_t width, uint32_t he + + auto texture = std::make_shared(); + auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM ); +- assert( texture->BInit( width, height, depth, drmFormat, flags ) ); ++ bool bRes = texture->BInit( width, height, depth, drmFormat, flags ); ++ assert( bRes ); + + return texture; + } +@@ -2820,7 +2822,8 @@ std::shared_ptr vulkan_create_debug_blank_texture() + int height = std::min( g_nOutputHeight, 1080 ); + + auto texture = std::make_shared(); +- assert( texture->BInit( width, height, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ) ); ++ bool bRes = texture->BInit( width, height, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ); ++ assert( bRes ); + + void* dst = g_device.uploadBufferData( width * height * 4 ); + memset( dst, 0x0, width * height * 4 ); +@@ -2843,7 +2846,8 @@ std::shared_ptr vulkan_create_debug_white_texture() + flags.bLinear = true; + + auto texture = std::make_shared(); +- assert( texture->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ) ); ++ bool bRes = texture->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags); ++ assert( bRes ); + + memset( texture->mappedData(), 0xFF, texture->width() * texture->height() * 4 ); + + +From 8f3f5c5b445a42409ed387442ad80fec8c5153d9 Mon Sep 17 00:00:00 2001 +From: Sandelinos <26635624+Sandelinos@users.noreply.github.com> +Date: Mon, 1 Jan 2024 21:05:53 +0200 +Subject: [PATCH 065/134] build: Define minimum libavif version + +Co-authored-by: Sandelinos +--- + src/meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/meson.build b/src/meson.build +index 4cb546696..4f88d6ecb 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -21,7 +21,7 @@ epoll_dep = dependency('epoll-shim', required: false) + glm_dep = dependency('glm') + sdl_dep = dependency('SDL2') + stb_dep = dependency('stb') +-avif_dep = dependency('libavif') ++avif_dep = dependency('libavif', version: '>=1.0.0') + + wlroots_dep = dependency( + 'wlroots', + +From aad7eed33bd2186f10ffc39213d6091a9764e65a Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 2 Jan 2024 18:10:12 +0000 +Subject: [PATCH 066/134] drm: Collection of DRM refactors + +--- + src/drm.cpp | 2136 +++++++++++++++++++--------------------- + src/drm.hpp | 377 +++++-- + src/gamescope_shared.h | 27 + + src/main.cpp | 21 +- + src/modegen.cpp | 17 +- + src/modegen.hpp | 3 +- + src/steamcompmgr.cpp | 85 +- + src/steamcompmgr.hpp | 2 +- + src/vblankmanager.cpp | 4 +- + src/wlserver.cpp | 10 +- + src/xwayland_ctx.hpp | 8 +- + 11 files changed, 1398 insertions(+), 1292 deletions(-) + create mode 100644 src/gamescope_shared.h + +diff --git a/src/drm.cpp b/src/drm.cpp +index 29682a356..717a5299f 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -19,6 +19,7 @@ extern "C" { + } + + #include "drm.hpp" ++#include "defer.hpp" + #include "main.hpp" + #include "modegen.hpp" + #include "vblankmanager.hpp" +@@ -40,12 +41,13 @@ extern "C" { + + #include "gamescope-control-protocol.h" + ++using namespace std::literals; ++ + struct drm_t g_DRM = {}; + + 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; +-bool g_bUseLayers = true; + bool g_bDebugLayers = false; + const char *g_sOutputName = nullptr; + +@@ -63,29 +65,28 @@ struct drm_color_ctm2 { + + bool g_bSupportsAsyncFlips = false; + +-enum drm_mode_generation g_drmModeGeneration = DRM_MODE_GENERATE_CVT; ++gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; + enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; +-std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; ++std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; + + bool g_bForceDisableColorMgmt = false; + + static LogScope drm_log("drm"); + static LogScope drm_verbose_log("drm", LOG_SILENT); + +-static std::map< std::string, std::string > pnps = {}; ++static std::unordered_map< std::string, std::string > pnps = {}; + +-drm_screen_type drm_get_connector_type(drmModeConnector *connector); + static void drm_unset_mode( struct drm_t *drm ); + static void drm_unset_connector( struct drm_t *drm ); + +-static uint32_t steam_deck_display_rates[] = ++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, + }; + +-static uint32_t galileo_display_rates[] = ++static constexpr uint32_t s_kSteamDeckOLEDRates[] = + { + 45,47,48,49, + 50,51,53,55,56,59, +@@ -95,17 +96,17 @@ static uint32_t galileo_display_rates[] = + 90, + }; + +-static uint32_t get_conn_display_info_flags(struct drm_t *drm, struct connector *connector) ++static uint32_t get_conn_display_info_flags( struct drm_t *drm, gamescope::CDRMConnector *pConnector ) + { +- if (!connector) ++ if ( !pConnector ) + return 0; + + uint32_t flags = 0; +- if ( drm_get_connector_type(connector->connector) == DRM_SCREEN_TYPE_INTERNAL ) ++ if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; +- if ( connector->vrr_capable ) ++ if ( pConnector->IsVRRCapable() ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; +- if ( connector->metadata.supportsST2084 ) ++ if ( pConnector->GetHDRInfo().bExposeHDRSupport ) + flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; + + return flags; +@@ -115,22 +116,22 @@ void drm_send_gamescope_control(wl_resource *control, struct drm_t *drm) + { + // assumes wlserver_lock HELD! + +- if ( !drm->connector ) ++ if ( !drm->pConnector ) + return; + +- auto& conn = drm->connector; ++ auto& conn = drm->pConnector; + +- uint32_t flags = get_conn_display_info_flags( drm, drm->connector ); ++ uint32_t flags = get_conn_display_info_flags( drm, drm->pConnector ); + + struct wl_array display_rates; + wl_array_init(&display_rates); +- if ( conn->valid_display_rates.size() ) ++ if ( conn->GetValidDynamicRefreshRates().size() ) + { +- size_t size = conn->valid_display_rates.size() * sizeof(uint32_t); ++ size_t size = conn->GetValidDynamicRefreshRates().size() * sizeof(uint32_t); + uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); +- memcpy( ptr, conn->valid_display_rates.data(), size ); ++ memcpy( ptr, conn->GetValidDynamicRefreshRates().data(), size ); + } +- gamescope_control_send_active_display_info( control, drm->connector->name, drm->connector->make, drm->connector->model, flags, &display_rates ); ++ gamescope_control_send_active_display_info( control, drm->pConnector->GetName(), drm->pConnector->GetMake(), drm->pConnector->GetModel(), flags, &display_rates ); + wl_array_release(&display_rates); + } + +@@ -175,60 +176,55 @@ static struct fb& get_fb( struct drm_t& drm, uint32_t id ) + return drm.fb_map[ id ]; + } + +-static struct crtc *find_crtc_for_connector(struct drm_t *drm, const struct connector *connector) { +- for (size_t i = 0; i < drm->crtcs.size(); i++) { +- uint32_t crtc_mask = 1 << i; +- if (connector->possible_crtcs & crtc_mask) +- return &drm->crtcs[i]; ++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(); + } + + return nullptr; + } + +-static bool get_plane_formats(struct drm_t *drm, struct plane *plane, struct wlr_drm_format_set *formats) { +- for (uint32_t k = 0; k < plane->plane->count_formats; k++) { +- uint32_t fmt = plane->plane->formats[k]; +- wlr_drm_format_set_add(formats, fmt, DRM_FORMAT_MOD_INVALID); ++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 (plane->props.count("IN_FORMATS") > 0) { +- uint64_t blob_id = plane->initial_prop_values["IN_FORMATS"]; ++ if ( pPlane->GetProperties().IN_FORMATS ) ++ { ++ const uint64_t ulBlobId = pPlane->GetProperties().IN_FORMATS->GetCurrentValue(); + +- drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(drm->fd, blob_id); +- if (!blob) { ++ drmModePropertyBlobRes *pBlob = drmModeGetPropertyBlob( drm->fd, ulBlobId ); ++ if ( !pBlob ) ++ { + drm_log.errorf_errno("drmModeGetPropertyBlob(IN_FORMATS) failed"); + return false; + } ++ defer( drmModeFreePropertyBlob( pBlob ) ); + +- struct drm_format_modifier_blob *data = +- (struct drm_format_modifier_blob *)blob->data; +- uint32_t *fmts = (uint32_t *)((char *)data + data->formats_offset); +- struct drm_format_modifier *mods = (struct drm_format_modifier *) +- ((char *)data + data->modifiers_offset); +- for (uint32_t i = 0; i < data->count_modifiers; ++i) { +- for (int j = 0; j < 64; ++j) { +- if (mods[i].formats & ((uint64_t)1 << j)) { +- wlr_drm_format_set_add(formats, +- fmts[j + mods[i].offset], mods[i].modifier); +- } ++ 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 ); + } + } +- +- drmModeFreePropertyBlob(blob); + } + + return true; + } + +-static const char *get_enum_name(const drmModePropertyRes *prop, uint64_t value) +-{ +- for (int i = 0; i < prop->count_enums; i++) { +- if (prop->enums[i].value == value) +- return prop->enums[i].name; +- } +- return nullptr; +-} +- + 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; +@@ -245,27 +241,21 @@ static uint32_t pick_plane_format( const struct wlr_drm_format_set *formats, uin + } + + /* Pick a primary plane that can be connected to the chosen CRTC. */ +-static struct plane *find_primary_plane(struct drm_t *drm) ++static gamescope::CDRMPlane *find_primary_plane(struct drm_t *drm) + { +- struct plane *primary = nullptr; +- +- for (size_t i = 0; i < drm->planes.size(); i++) { +- struct plane *plane = &drm->planes[i]; +- +- if (!(plane->plane->possible_crtcs & (1 << drm->crtc_index))) +- continue; ++ if ( !drm->pCRTC ) ++ return nullptr; + +- uint64_t plane_type = drm->planes[i].initial_prop_values["type"]; +- if (plane_type == DRM_PLANE_TYPE_PRIMARY) { +- primary = plane; +- break; ++ 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(); + } + } + +- if (primary == nullptr) +- return nullptr; +- +- return primary; ++ return nullptr; + } + + static void drm_unlock_fb_internal( struct drm_t *drm, struct fb *fb ); +@@ -278,7 +268,10 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi + uint64_t flipcount = (uint64_t)data; + g_nCompletedPageFlipCount = flipcount; + +- if ( g_DRM.crtc->id != crtc_id ) ++ if ( !g_DRM.pCRTC ) ++ return; ++ ++ if ( g_DRM.pCRTC->GetObjectId() != crtc_id ) + return; + + // This is the last vblank time +@@ -363,209 +356,6 @@ void flip_handler_thread_run(void) + } + } + +-static const drmModePropertyRes *get_prop(struct drm_t *drm, uint32_t prop_id) +-{ +- if (drm->props.count(prop_id) > 0) { +- return drm->props[prop_id]; +- } +- +- drmModePropertyRes *prop = drmModeGetProperty(drm->fd, prop_id); +- if (!prop) { +- drm_log.errorf_errno("drmModeGetProperty failed"); +- return nullptr; +- } +- +- drm->props[prop_id] = prop; +- return prop; +-} +- +-static bool get_object_properties(struct drm_t *drm, uint32_t obj_id, uint32_t obj_type, std::map &map, std::map &values) +-{ +- drmModeObjectProperties *props = drmModeObjectGetProperties(drm->fd, obj_id, obj_type); +- if (!props) { +- drm_log.errorf_errno("drmModeObjectGetProperties failed"); +- return false; +- } +- +- map = {}; +- values = {}; +- +- for (uint32_t i = 0; i < props->count_props; i++) { +- const drmModePropertyRes *prop = get_prop(drm, props->props[i]); +- if (!prop) { +- return false; +- } +- map[prop->name] = prop; +- values[prop->name] = props->prop_values[i]; +- } +- +- drmModeFreeObjectProperties(props); +- return true; +-} +- +-static bool compare_modes( drmModeModeInfo mode1, drmModeModeInfo mode2 ) +-{ +- bool goodRefresh1 = mode1.vrefresh >= 60; +- bool goodRefresh2 = mode2.vrefresh >= 60; +- if (goodRefresh1 != goodRefresh2) +- return goodRefresh1; +- +- bool preferred1 = mode1.type & DRM_MODE_TYPE_PREFERRED; +- bool preferred2 = mode2.type & DRM_MODE_TYPE_PREFERRED; +- if (preferred1 != preferred2) +- return preferred1; +- +- int area1 = mode1.hdisplay * mode1.vdisplay; +- int area2 = mode2.hdisplay * mode2.vdisplay; +- if (area1 != area2) +- return area1 > area2; +- +- return mode1.vrefresh > mode2.vrefresh; +-} +- +-static void +-drm_hdr_parse_edid(drm_t *drm, struct connector *connector, const struct di_edid *edid) +-{ +- struct connector_metadata_t *metadata = &connector->metadata; +- +- const struct di_edid_chromaticity_coords* chroma = di_edid_get_chromaticity_coords(edid); +- const struct di_cta_hdr_static_metadata_block* hdr_static_metadata = NULL; +- const struct di_cta_colorimetry_block* colorimetry = NULL; +- +- const struct di_edid_cta* cta = NULL; +- const struct di_edid_ext* const* exts = di_edid_get_extensions(edid); +- for (; *exts != NULL; exts++) { +- if ((cta = di_edid_ext_get_cta(*exts))) +- break; +- } +- +- if (cta) { +- const struct di_cta_data_block* const* blocks = di_edid_cta_get_data_blocks(cta); +- for (; *blocks != NULL; blocks++) { +- if (!hdr_static_metadata && (hdr_static_metadata = di_cta_data_block_get_hdr_static_metadata(*blocks))) +- continue; +- if (!colorimetry && (colorimetry = di_cta_data_block_get_colorimetry(*blocks))) +- continue; +- } +- } +- +- struct hdr_metadata_infoframe *infoframe = &metadata->defaultHdrMetadata.hdmi_metadata_type1; +- +- if (chroma) { +- infoframe->display_primaries[0].x = color_xy_to_u16(chroma->red_x); +- infoframe->display_primaries[0].y = color_xy_to_u16(chroma->red_y); +- infoframe->display_primaries[1].x = color_xy_to_u16(chroma->green_x); +- infoframe->display_primaries[1].y = color_xy_to_u16(chroma->green_y); +- infoframe->display_primaries[2].x = color_xy_to_u16(chroma->blue_x); +- infoframe->display_primaries[2].y = color_xy_to_u16(chroma->blue_y); +- infoframe->white_point.x = color_xy_to_u16(chroma->white_x); +- infoframe->white_point.y = color_xy_to_u16(chroma->white_y); +- } +- +- /* Some sane defaults for SDR for displays with missing data... */ +- infoframe->max_display_mastering_luminance = nits_to_u16(1000.0f); +- infoframe->min_display_mastering_luminance = nits_to_u16_dark(0.0f); +- infoframe->max_cll = nits_to_u16(400.0f); +- infoframe->max_fall = nits_to_u16(400.0f); +- +- if (hdr_static_metadata) { +- if (hdr_static_metadata->desired_content_max_luminance) +- infoframe->max_display_mastering_luminance = nits_to_u16(hdr_static_metadata->desired_content_max_luminance); +- if (hdr_static_metadata->desired_content_min_luminance) +- infoframe->min_display_mastering_luminance = nits_to_u16_dark(hdr_static_metadata->desired_content_min_luminance); +- /* 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. +- */ +- float default_max_fall = hdr_static_metadata->desired_content_max_frame_avg_luminance +- ? hdr_static_metadata->desired_content_max_frame_avg_luminance +- : hdr_static_metadata->desired_content_max_luminance; +- +- if (default_max_fall) { +- infoframe->max_cll = nits_to_u16(default_max_fall); +- infoframe->max_fall = nits_to_u16(default_max_fall); +- } +- +- metadata->maxCLL = (uint16_t)hdr_static_metadata->desired_content_max_luminance; +- metadata->maxFALL = (uint16_t)hdr_static_metadata->desired_content_max_frame_avg_luminance; +- } +- +- metadata->supportsST2084 = +- chroma && +- colorimetry && colorimetry->bt2020_rgb && +- hdr_static_metadata && hdr_static_metadata->eotfs && hdr_static_metadata->eotfs->pq; +- +- if (metadata->supportsST2084) { +- metadata->defaultHdrMetadata.metadata_type = 0; +- infoframe->metadata_type = 0; +- infoframe->eotf = HDMI_EOTF_ST2084; +- +- metadata->hdr10_metadata_blob = drm_create_hdr_metadata_blob(drm, &metadata->defaultHdrMetadata); +- +- if (metadata->hdr10_metadata_blob == nullptr) { +- fprintf(stderr, "Failed to create blob for HDR_OUTPUT_METADATA. Falling back to null blob.\n"); +- } +- } +- +- const char *coloroverride = getenv( "GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE" ); +- if (coloroverride && drm_get_connector_type(connector->connector) == DRM_SCREEN_TYPE_INTERNAL) +- { +- if (sscanf( coloroverride, "%f %f %f %f %f %f %f %f", +- &metadata->colorimetry.primaries.r.x, &metadata->colorimetry.primaries.r.y, +- &metadata->colorimetry.primaries.g.x, &metadata->colorimetry.primaries.g.y, +- &metadata->colorimetry.primaries.b.x, &metadata->colorimetry.primaries.b.y, +- &metadata->colorimetry.white.x, &metadata->colorimetry.white.y ) == 8 ) +- { +- drm_log.infof("[colorimetry]: GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE detected"); +- } +- else +- { +- drm_log.errorf("[colorimetry]: GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE specified, but could not parse \"rx ry gx gy bx by wx wy\""); +- } +- } +- else if (connector->is_steam_deck_display && !connector->is_galileo_display) +- { +- drm_log.infof("[colorimetry]: Steam Deck (internal display) detected."); +- +- // Hardcode Steam Deck display info to support +- // BIOSes with missing info for this in EDID. +- drm_log.infof("[colorimetry]: using default steamdeck colorimetry"); +- metadata->colorimetry = displaycolorimetry_steamdeck_measured; +- metadata->eotf = EOTF_Gamma22; +- } +- else if (chroma && chroma->red_x != 0.0f) +- { +- drm_log.infof("[colorimetry]: EDID with colorimetry detected. Using it"); +- metadata->colorimetry.primaries = { { chroma->red_x, chroma->red_y }, { chroma->green_x, chroma->green_y }, { chroma->blue_x, chroma->blue_y } }; +- metadata->colorimetry.white = { chroma->white_x, chroma->white_y }; +- metadata->eotf = infoframe->eotf == HDMI_EOTF_ST2084 ? EOTF_PQ : EOTF_Gamma22; +- } +- else +- { +- // No valid chroma data in the EDID, fill it in ourselves. +- if (infoframe->eotf == HDMI_EOTF_ST2084) +- { +- drm_log.infof("[colorimetry]: EDID does not define colorimetry. Assuming rec2020 based on HDMI_EOTF_ST2084 support"); +- // Fallback to 2020 primaries for HDR +- metadata->colorimetry = displaycolorimetry_2020; +- metadata->eotf = EOTF_PQ; +- } +- else +- { +- // Fallback to 709 primaries for SDR +- drm_log.infof("[colorimetry]: EDID does not define colorimetry. Assuming rec709 / gamma 2.2"); +- metadata->colorimetry = displaycolorimetry_709; +- metadata->eotf = EOTF_Gamma22; +- } +- } +- +- drm_log.infof("[colorimetry]: r %f %f", metadata->colorimetry.primaries.r.x, metadata->colorimetry.primaries.r.y); +- drm_log.infof("[colorimetry]: g %f %f", metadata->colorimetry.primaries.g.x, metadata->colorimetry.primaries.g.y); +- drm_log.infof("[colorimetry]: b %f %f", metadata->colorimetry.primaries.b.x, metadata->colorimetry.primaries.b.y); +- drm_log.infof("[colorimetry]: w %f %f", metadata->colorimetry.white.x, metadata->colorimetry.white.y); +-} +- + 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; +@@ -639,7 +429,7 @@ static uint8_t encode_max_luminance(float nits) + return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); + } + +-static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm_t *drm, struct connector *conn ) ++static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm_t *drm, gamescope::CDRMConnector *conn ) + { + // A zero length indicates that the edid parsing failed. + if (orig_size == 0) { +@@ -675,7 +465,7 @@ static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm + // 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 ( g_bForceHDRSupportDebug && !conn->metadata.supportsST2084 ) ++ if ( conn->GetHDRInfo().ShouldPatchEDID() ) + { + // TODO: Allow for override of min luminance + float flMaxPeakLuminance = g_ColorMgmt.pending.hdrTonemapDisplayMetadata.BIsValid() ? +@@ -824,302 +614,119 @@ static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm + + void drm_update_patched_edid( drm_t *drm ) + { +- if (!drm || !drm->connector) +- return; +- +- create_patched_edid(drm->connector->edid_data.data(), drm->connector->edid_data.size(), drm, drm->connector); +-} +- +-#define GALILEO_SDC_PID 0x3003 +-#define GALILEO_BOE_PID 0x3004 +- +-static void parse_edid( drm_t *drm, struct connector *conn) +-{ +- memset(conn->make_pnp, 0, sizeof(conn->make_pnp)); +- free(conn->make); +- conn->make = NULL; +- free(conn->model); +- conn->model = NULL; +- +- if (conn->props.count("EDID") == 0) { ++ if (!drm || !drm->pConnector) + return; +- } +- +- uint64_t blob_id = conn->initial_prop_values["EDID"]; +- if (blob_id == 0) { +- return; +- } +- +- drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(drm->fd, blob_id); +- if (!blob) { +- drm_log.errorf_errno("drmModeGetPropertyBlob(EDID) failed"); +- return; +- } +- +- struct di_info *info = di_info_parse_edid(blob->data, blob->length); +- if (!info) { +- drmModeFreePropertyBlob(blob); +- drm_log.errorf("Failed to parse edid"); +- return; +- } +- +- conn->edid_data = std::vector((const uint8_t*)blob->data, ((const uint8_t*)(blob->data)) + blob->length); +- +- drmModeFreePropertyBlob(blob); +- +- const struct di_edid *edid = di_info_get_edid(info); +- +- const struct di_edid_vendor_product *vendor_product = di_edid_get_vendor_product(edid); +- char pnp_id[] = { +- vendor_product->manufacturer[0], +- vendor_product->manufacturer[1], +- vendor_product->manufacturer[2], +- '\0', +- }; +- memcpy(conn->make_pnp, pnp_id, sizeof(pnp_id)); +- if (pnps.count(pnp_id) > 0) { +- conn->make = strdup(pnps[pnp_id].c_str()); +- } +- else { +- // Some vendors like AOC, don't have a PNP id listed, +- // but their name is literally just "AOC", so just +- // use the PNP name directly. +- conn->make = strdup(pnp_id); +- } +- +- const struct di_edid_display_descriptor *const *descriptors = di_edid_get_display_descriptors(edid); +- for (size_t i = 0; descriptors[i] != NULL; i++) { +- const struct di_edid_display_descriptor *desc = descriptors[i]; +- if (di_edid_display_descriptor_get_tag(desc) == DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME) { +- conn->model = strdup(di_edid_display_descriptor_get_string(desc)); +- } +- } +- +- drm_log.infof("Connector make %s model %s", conn->make_pnp, conn->model ); +- +- conn->is_steam_deck_display = +- (strcmp(conn->make_pnp, "WLC") == 0 && strcmp(conn->model, "ANX7530 U") == 0) || +- (strcmp(conn->make_pnp, "ANX") == 0 && strcmp(conn->model, "ANX7530 U") == 0) || +- (strcmp(conn->make_pnp, "VLV") == 0 && strcmp(conn->model, "ANX7530 U") == 0) || +- (strcmp(conn->make_pnp, "VLV") == 0 && strcmp(conn->model, "Jupiter") == 0); +- +- if ((vendor_product->product == GALILEO_SDC_PID) || (vendor_product->product == GALILEO_BOE_PID)) { +- conn->is_galileo_display = vendor_product->product; +- conn->valid_display_rates = std::span(galileo_display_rates); +- } else { +- conn->is_galileo_display = 0; +- if ( conn->is_steam_deck_display ) +- conn->valid_display_rates = std::span(steam_deck_display_rates); +- } +- +- drm_hdr_parse_edid(drm, conn, edid); + +- di_info_destroy(info); ++ create_patched_edid(drm->pConnector->GetRawEDID().data(), drm->pConnector->GetRawEDID().size(), drm, drm->pConnector); + } + + static bool refresh_state( drm_t *drm ) + { +- drmModeRes *resources = drmModeGetResources(drm->fd); +- if (resources == nullptr) { +- drm_log.errorf_errno("drmModeGetResources failed"); ++ 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 < resources->count_connectors; i++) { +- uint32_t conn_id = resources->connectors[i]; ++ for ( int i = 0; i < pResources->count_connectors; i++ ) ++ { ++ uint32_t uConnectorId = pResources->connectors[i]; + +- if (drm->connectors.count(conn_id) == 0) { +- struct connector conn = { .id = conn_id }; +- drm->connectors[conn_id] = conn; ++ drmModeConnector *pConnector = drmModeGetConnector( drm->fd, uConnectorId ); ++ if ( !pConnector ) ++ continue; ++ ++ if ( !drm->connectors.contains( uConnectorId ) ) ++ { ++ drm->connectors.emplace( ++ std::piecewise_construct, ++ std::forward_as_tuple( uConnectorId ), ++ std::forward_as_tuple( pConnector ) ); + } + } + + // Remove connectors which disappeared +- auto it = drm->connectors.begin(); +- while (it != drm->connectors.end()) { +- struct connector *conn = &it->second; +- +- bool found = false; +- for (int j = 0; j < resources->count_connectors; j++) { +- if (resources->connectors[j] == conn->id) { +- found = true; +- break; +- } +- } ++ 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 (!found) { +- drm_log.debugf("connector '%s' disappeared", conn->name); ++ if ( !bFound ) ++ { ++ drm_log.debugf( "Connector '%s' disappeared.", pConnector->GetName() ); + +- if (drm->connector == conn) { +- drm_log.infof("current connector '%s' disappeared", conn->name); +- drm->connector = nullptr; ++ if ( drm->pConnector == pConnector ) ++ { ++ drm_log.infof( "Current connector '%s' disappeared.", pConnector->GetName() ); ++ drm->pConnector = nullptr; + } + +- free(conn->name); +- conn->name = nullptr; +- conn->metadata.hdr10_metadata_blob = nullptr; +- drmModeFreeConnector(conn->connector); +- it = drm->connectors.erase(it); +- } else { +- it++; ++ iter = drm->connectors.erase( iter ); + } ++ else ++ iter++; + } + +- drmModeFreeResources(resources); +- +- // Re-probe connectors props and status +- for (auto &kv : drm->connectors) { +- struct connector *conn = &kv.second; +- if (conn->connector != nullptr) { +- conn->metadata.hdr10_metadata_blob = nullptr; +- drmModeFreeConnector(conn->connector); +- } +- +- conn->connector = drmModeGetConnector(drm->fd, conn->id); +- if (conn->connector == nullptr) { +- drm_log.errorf_errno("drmModeGetConnector failed"); +- return false; +- } +- +- if (!get_object_properties(drm, conn->id, DRM_MODE_OBJECT_CONNECTOR, conn->props, conn->initial_prop_values)) { +- return false; +- } +- +- /* sort modes by preference: preferred flag, then highest area, then +- * highest refresh rate */ +- std::stable_sort(conn->connector->modes, conn->connector->modes + conn->connector->count_modes, compare_modes); +- +- parse_edid(drm, conn); +- +- if ( conn->name != nullptr ) +- { +- free(conn->name); +- conn->name = nullptr; +- } +- +- const char *type_str = drmModeGetConnectorTypeName(conn->connector->connector_type); +- if (!type_str) +- type_str = "Unknown"; +- +- char name[128] = {}; +- snprintf(name, sizeof(name), "%s-%d", type_str, conn->connector->connector_type_id); +- conn->name = strdup(name); +- +- conn->possible_crtcs = drmModeConnectorGetPossibleCrtcs(drm->fd, conn->connector); +- if (!conn->possible_crtcs) +- drm_log.errorf_errno("drmModeConnectorGetPossibleCrtcs failed"); +- +- conn->has_colorspace = conn->props.contains( "Colorspace" ); +- conn->has_hdr_output_metadata = conn->props.contains( "HDR_OUTPUT_METADATA" ); +- conn->has_content_type = conn->props.contains( "content type" ); +- +- conn->current.crtc_id = conn->initial_prop_values["CRTC_ID"]; +- if (conn->has_colorspace) +- conn->current.colorspace = conn->initial_prop_values["Colorspace"]; +- if (conn->has_hdr_output_metadata) +- conn->current.hdr_output_metadata = std::make_shared(nullptr, conn->initial_prop_values["HDR_OUTPUT_METADATA"], false); +- if (conn->has_content_type) +- conn->current.content_type = conn->initial_prop_values["content type"]; +- +- conn->target_refresh = 0; +- +- conn->vrr_capable = !!conn->initial_prop_values["vrr_capable"]; +- +- drm_log.debugf("found new connector '%s'", conn->name); ++ // Re-probe connectors props and status) ++ for ( auto &iter : drm->connectors ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter.second; ++ pConnector->RefreshState(); + } + +- for (size_t i = 0; i < drm->crtcs.size(); i++) { +- struct crtc *crtc = &drm->crtcs[i]; +- if (!get_object_properties(drm, crtc->id, DRM_MODE_OBJECT_CRTC, crtc->props, crtc->initial_prop_values)) { +- return false; +- } ++ for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) ++ pCRTC->RefreshState(); + +- crtc->has_gamma_lut = crtc->props.contains( "GAMMA_LUT" ); +- if (!crtc->has_gamma_lut) +- drm_log.infof("CRTC %" PRIu32 " has no gamma LUT support", crtc->id); +- crtc->has_degamma_lut = crtc->props.contains( "DEGAMMA_LUT" ); +- if (!crtc->has_degamma_lut) +- drm_log.infof("CRTC %" PRIu32 " has no degamma LUT support", crtc->id); +- crtc->has_ctm = crtc->props.contains( "CTM" ); +- if (!crtc->has_ctm) +- drm_log.infof("CRTC %" PRIu32 " has no CTM support", crtc->id); +- crtc->has_vrr_enabled = crtc->props.contains( "VRR_ENABLED" ); +- if (!crtc->has_vrr_enabled) +- drm_log.infof("CRTC %" PRIu32 " has no VRR_ENABLED support", crtc->id); +- crtc->has_valve1_regamma_tf = crtc->props.contains( "VALVE1_CRTC_REGAMMA_TF" ); +- if (!crtc->has_valve1_regamma_tf) +- drm_log.infof("CRTC %" PRIu32 " has no VALVE1_CRTC_REGAMMA_TF support", crtc->id); +- +- crtc->current.active = crtc->initial_prop_values["ACTIVE"]; +- if (crtc->has_vrr_enabled) +- drm->current.vrr_enabled = crtc->initial_prop_values["VRR_ENABLED"]; +- if (crtc->has_valve1_regamma_tf) +- drm->current.output_tf = (drm_valve1_transfer_function) crtc->initial_prop_values["VALVE1_CRTC_REGAMMA_TF"]; +- } +- +- for (size_t i = 0; i < drm->planes.size(); i++) { +- struct plane *plane = &drm->planes[i]; +- if (!get_object_properties(drm, plane->id, DRM_MODE_OBJECT_PLANE, plane->props, plane->initial_prop_values)) { +- return false; +- } +- plane->has_color_mgmt = plane->props.contains( "VALVE1_PLANE_BLEND_TF" ); +- } ++ for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) ++ pPlane->RefreshState(); + + return true; + } + + static bool get_resources(struct drm_t *drm) + { +- drmModeRes *resources = drmModeGetResources(drm->fd); +- if (resources == nullptr) { +- drm_log.errorf_errno("drmModeGetResources failed"); +- return false; +- } +- +- for (int i = 0; i < resources->count_crtcs; i++) { +- struct crtc crtc = { .id = resources->crtcs[i] }; +- +- crtc.crtc = drmModeGetCrtc(drm->fd, crtc.id); +- if (crtc.crtc == nullptr) { +- drm_log.errorf_errno("drmModeGetCrtc failed"); ++ { ++ drmModeRes *pResources = drmModeGetResources( drm->fd ); ++ if ( !pResources ) ++ { ++ drm_log.errorf_errno( "drmModeGetResources failed" ); + return false; + } ++ defer( drmModeFreeResources( pResources ) ); + +- drm->crtcs.push_back(crtc); +- } +- +- drmModeFreeResources(resources); +- +- drmModePlaneRes *plane_resources = drmModeGetPlaneResources(drm->fd); +- if (!plane_resources) { +- drm_log.errorf_errno("drmModeGetPlaneResources failed"); +- return false; ++ 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 ) ); ++ } + } + +- for (uint32_t i = 0; i < plane_resources->count_planes; i++) { +- struct plane plane = { .id = plane_resources->planes[i] }; +- +- plane.plane = drmModeGetPlane(drm->fd, plane.id); +- if (plane.plane == nullptr) { +- drm_log.errorf_errno("drmModeGetPlane failed"); ++ { ++ drmModePlaneRes *pPlaneResources = drmModeGetPlaneResources( drm->fd ); ++ if ( !pPlaneResources ) ++ { ++ drm_log.errorf_errno( "drmModeGetPlaneResources failed" ); + return false; + } ++ defer( drmModeFreePlaneResources( pPlaneResources ) ); + +- drm->planes.push_back(plane); +- } +- +- drmModeFreePlaneResources(plane_resources); +- +- if (!refresh_state(drm)) +- return false; +- +- for (size_t i = 0; i < drm->crtcs.size(); i++) { +- struct crtc *crtc = &drm->crtcs[i]; +- crtc->pending = crtc->current; ++ 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 true; ++ return refresh_state( drm ); + } + + struct mode_blocklist_entry +@@ -1220,31 +827,33 @@ static bool get_saved_mode(const char *description, saved_mode &mode_info) + + static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + { +- if (drm->connector && drm->connector->connector->connection != DRM_MODE_CONNECTED) { +- drm_log.infof("current connector '%s' disconnected", drm->connector->name); +- drm->connector = nullptr; ++ if (drm->pConnector && drm->pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED) { ++ drm_log.infof("current connector '%s' disconnected", drm->pConnector->GetName()); ++ drm->pConnector = nullptr; + } + +- struct connector *best = nullptr; +- int best_priority = INT_MAX; +- for (auto &kv : drm->connectors) { +- struct connector *conn = &kv.second; ++ gamescope::CDRMConnector *best = nullptr; ++ int nBestPriority = INT_MAX; ++ for ( auto &iter : drm->connectors ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter.second; + +- if (conn->connector->connection != DRM_MODE_CONNECTED) ++ if ( pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED ) + continue; + +- if (drm->force_internal && drm_get_connector_type(conn->connector) == DRM_SCREEN_TYPE_EXTERNAL) ++ if ( drm->force_internal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) + continue; + +- int priority = get_connector_priority(drm, conn->name); +- if (priority < best_priority) { +- best = conn; +- best_priority = priority; ++ int nPriority = get_connector_priority( drm, pConnector->GetName() ); ++ if ( nPriority < nBestPriority ) ++ { ++ best = pConnector; ++ nBestPriority = nPriority; + } + } + + if (!force) { +- if ((!best && drm->connector) || (best && best == drm->connector)) { ++ if ((!best && drm->pConnector) || (best && best == drm->pConnector)) { + // Let's keep our current connector + return true; + } +@@ -1268,12 +877,12 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + } + + char description[256]; +- if (drm_get_connector_type(best->connector) == DRM_SCREEN_TYPE_INTERNAL) { ++ if (best->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL) { + snprintf(description, sizeof(description), "Internal screen"); +- } else if (best->make && best->model) { +- snprintf(description, sizeof(description), "%s %s", best->make, best->model); +- } else if (best->model) { +- snprintf(description, sizeof(description), "%s", best->model); ++ } 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"); + } +@@ -1281,17 +890,17 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + const drmModeModeInfo *mode = nullptr; + if ( drm->preferred_width != 0 || drm->preferred_height != 0 || drm->preferred_refresh != 0 ) + { +- mode = find_mode(best->connector, drm->preferred_width, drm->preferred_height, drm->preferred_refresh); ++ mode = find_mode(best->GetModeConnector(), drm->preferred_width, drm->preferred_height, drm->preferred_refresh); + } + +- if (!mode && drm_get_connector_type(best->connector) == DRM_SCREEN_TYPE_EXTERNAL) { ++ if (!mode && best->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL) { + saved_mode mode_info; + if (get_saved_mode(description, mode_info)) +- mode = find_mode(best->connector, mode_info.width, mode_info.height, mode_info.refresh); ++ mode = find_mode(best->GetModeConnector(), mode_info.width, mode_info.height, mode_info.refresh); + } + + if (!mode) { +- mode = find_mode(best->connector, 0, 0, 0); ++ mode = find_mode(best->GetModeConnector(), 0, 0, 0); + } + + if (!mode) { +@@ -1299,7 +908,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + return false; + } + +- best->target_refresh = mode->vrefresh; ++ best->SetBaseRefresh( mode->vrefresh ); + + if (!drm_set_mode(drm, mode)) { + return false; +@@ -1307,15 +916,15 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + + const struct wlserver_output_info wlserver_output_info = { + .description = description, +- .phys_width = (int) best->connector->mmWidth, +- .phys_height = (int) best->connector->mmHeight, ++ .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) +- create_patched_edid(best->edid_data.data(), best->edid_data.size(), drm, best); ++ create_patched_edid(best->GetRawEDID().data(), best->GetRawEDID().size(), drm, best); + + update_connector_display_info_wl( drm ); + +@@ -1366,6 +975,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ + { + load_pnps(); + ++ drm->bUseLiftoff = true; ++ + drm->wants_vrr_enabled = wants_adaptive_sync; + drm->preferred_width = width; + drm->preferred_height = height; +@@ -1435,14 +1046,15 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ + return false; + + drm_log.infof("Connectors:"); +- for (const auto &kv : drm->connectors) { +- const struct connector *conn = &kv.second; ++ for ( auto &iter : drm->connectors ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter.second; + + const char *status_str = "disconnected"; +- if ( conn->connector->connection == DRM_MODE_CONNECTED ) ++ if ( pConnector->GetModeConnector()->connection == DRM_MODE_CONNECTED ) + status_str = "connected"; + +- drm_log.infof(" %s (%s)", conn->name, status_str); ++ drm_log.infof(" %s (%s)", pConnector->GetName(), status_str); + } + + drm->connector_priorities = parse_connector_priorities( g_sOutputName ); +@@ -1452,23 +1064,24 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ + } + + // Fetch formats which can be scanned out +- for (size_t i = 0; i < drm->planes.size(); i++) { +- struct plane *plane = &drm->planes[i]; +- if (!get_plane_formats(drm, plane, &drm->formats)) ++ 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 +- struct plane *primary_plane = drm->primary; +- if (primary_plane == nullptr) { +- primary_plane = find_primary_plane(drm); +- } +- if (primary_plane == nullptr) { ++ 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, primary_plane, &drm->primary_formats)) { ++ if ( !get_plane_formats( drm, drm->pPrimaryPlane, &drm->primary_formats ) ) ++ { + return false; + } + +@@ -1511,9 +1124,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ + std::thread flip_handler_thread( flip_handler_thread_run ); + flip_handler_thread.detach(); + +- if (g_bUseLayers) { ++ if ( drm->bUseLiftoff ) + liftoff_log_set_priority(g_bDebugLayers ? LIFTOFF_DEBUG : LIFTOFF_ERROR); +- } + + hdr_output_metadata sdr_metadata; + memset(&sdr_metadata, 0, sizeof(sdr_metadata)); +@@ -1525,48 +1137,6 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ + return true; + } + +-static int add_property(drmModeAtomicReq *req, uint32_t obj_id, std::map &props, const char *name, uint64_t value) +-{ +- if ( props.count( name ) == 0 ) +- { +- drm_log.errorf("no property %s on object %u", name, obj_id); +- return -ENOENT; +- } +- +- const drmModePropertyRes *prop = props[ name ]; +- +- int ret = drmModeAtomicAddProperty(req, obj_id, prop->prop_id, value); +- if ( ret < 0 ) +- { +- drm_log.errorf_errno( "drmModeAtomicAddProperty failed" ); +- } +- return ret; +-} +- +-static int add_connector_property(drmModeAtomicReq *req, struct connector *conn, const char *name, uint64_t value) +-{ +- return add_property(req, conn->id, conn->props, name, value); +-} +- +-static int add_crtc_property(drmModeAtomicReq *req, struct crtc *crtc, const char *name, uint64_t value) +-{ +- return add_property(req, crtc->id, crtc->props, name, value); +-} +- +-static int add_plane_property(drmModeAtomicReq *req, struct plane *plane, const char *name, uint64_t value) +-{ +- return add_property(req, plane->id, plane->props, name, value); +-} +- +-static std::shared_ptr get_default_hdr_metadata(struct drm_t *drm, struct connector *connector) +-{ +- if ( !connector->has_hdr_output_metadata ) +- return nullptr; +- if ( !connector->metadata.supportsST2084 ) +- return nullptr; +- return drm->sdr_static_metadata; +-} +- + void finish_drm(struct drm_t *drm) + { + // Disable all connectors, CRTCs and planes. This is necessary to leave a +@@ -1575,88 +1145,122 @@ void finish_drm(struct drm_t *drm) + // together. + + drmModeAtomicReq *req = drmModeAtomicAlloc(); +- for ( auto &kv : drm->connectors ) { +- struct connector *conn = &kv.second; +- add_connector_property(req, conn, "CRTC_ID", 0); +- if (conn->has_colorspace) +- add_connector_property(req, conn, "Colorspace", 0); +- // HACK HACK: Setting to 0 doesn't disable HDR properly. +- // Set an SDR metadata blob. +- if (conn->has_hdr_output_metadata) +- { +- auto metadata = get_default_hdr_metadata( drm, conn ); +- add_connector_property(req, conn, "HDR_OUTPUT_METADATA", metadata ? metadata->blob : 0); +- } +- if (conn->has_content_type) +- add_connector_property(req, conn, "content type", 0); +- } +- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) { +- add_crtc_property(req, &drm->crtcs[i], "MODE_ID", 0); +- if ( drm->crtcs[i].has_gamma_lut ) +- add_crtc_property(req, &drm->crtcs[i], "GAMMA_LUT", 0); +- if ( drm->crtcs[i].has_degamma_lut ) +- add_crtc_property(req, &drm->crtcs[i], "DEGAMMA_LUT", 0); +- if ( drm->crtcs[i].has_ctm ) +- add_crtc_property(req, &drm->crtcs[i], "CTM", 0); +- if ( drm->crtcs[i].has_vrr_enabled ) +- add_crtc_property(req, &drm->crtcs[i], "VRR_ENABLED", 0); +- if ( drm->crtcs[i].has_valve1_regamma_tf ) +- add_crtc_property(req, &drm->crtcs[i], "VALVE1_CRTC_REGAMMA_TF", 0); +- add_crtc_property(req, &drm->crtcs[i], "ACTIVE", 0); +- } +- for ( size_t i = 0; i < drm->planes.size(); i++ ) { +- struct plane *plane = &drm->planes[i]; +- add_plane_property(req, plane, "FB_ID", 0); +- add_plane_property(req, plane, "CRTC_ID", 0); +- add_plane_property(req, plane, "SRC_X", 0); +- add_plane_property(req, plane, "SRC_Y", 0); +- add_plane_property(req, plane, "SRC_W", 0); +- add_plane_property(req, plane, "SRC_H", 0); +- add_plane_property(req, plane, "CRTC_X", 0); +- add_plane_property(req, plane, "CRTC_Y", 0); +- add_plane_property(req, plane, "CRTC_W", 0); +- add_plane_property(req, plane, "CRTC_H", 0); +- if (plane->props.count("rotation") > 0) +- add_plane_property(req, plane, "rotation", DRM_MODE_ROTATE_0); +- if (plane->props.count("alpha") > 0) +- add_plane_property(req, plane, "alpha", 0xFFFF); +- if (plane->props.count("VALVE1_PLANE_DEGAMMA_TF") > 0) +- add_plane_property(req, plane, "VALVE1_PLANE_DEGAMMA_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); +- if (plane->props.count("VALVE1_PLANE_HDR_MULT") > 0) +- add_plane_property(req, plane, "VALVE1_PLANE_HDR_MULT", 0x100000000ULL); +- if (plane->props.count("VALVE1_PLANE_SHAPER_TF") > 0) +- add_plane_property(req, plane, "VALVE1_PLANE_SHAPER_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); +- if (plane->props.count("VALVE1_PLANE_SHAPER_LUT") > 0) +- add_plane_property(req, plane, "VALVE1_PLANE_SHAPER_LUT", 0 ); +- if (plane->props.count("VALVE1_PLANE_LUT3D") > 0) +- add_plane_property(req, plane, "VALVE1_PLANE_LUT3D", 0 ); +- if (plane->props.count("VALVE1_PLANE_BLEND_TF") > 0) +- add_plane_property(req, plane, "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); +- if (plane->props.count("VALVE1_PLANE_BLEND_LUT") > 0) +- add_plane_property(req, plane, "VALVE1_PLANE_BLEND_LUT", 0 ); +- if (plane->props.count("VALVE1_PLANE_CTM") > 0) +- add_plane_property(req, plane, "VALVE1_PLANE_CTM", 0 ); +- } +- // 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); ++ for ( auto &iter : drm->connectors ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter.second; + +- // We can't close the DRM FD here, it might still be in use by the +- // page-flip handler thread. +-} ++ pConnector->GetProperties().CRTC_ID->SetPendingValue( req, 0, true ); + +-int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) +-{ +- int ret; ++ if ( pConnector->GetProperties().Colorspace ) ++ pConnector->GetProperties().Colorspace->SetPendingValue( req, 0, true ); + +- assert( drm->req != nullptr ); ++ 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->blob, 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< gamescope::CDRMCRTC > &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().VALVE1_CRTC_REGAMMA_TF ) ++ pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->SetPendingValue( req, 0, true ); ++ } ++ ++ for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) ++ { ++ pPlane->GetProperties().FB_ID->SetPendingValue( req, 0, 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().VALVE1_PLANE_DEGAMMA_TF ) ++ pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); ++ ++ if ( pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_LUT ) ++ pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_LUT->SetPendingValue( req, 0, true ); ++ ++ if ( pPlane->GetProperties().VALVE1_PLANE_CTM ) ++ pPlane->GetProperties().VALVE1_PLANE_CTM->SetPendingValue( req, 0, true ); ++ ++ if ( pPlane->GetProperties().VALVE1_PLANE_HDR_MULT ) ++ pPlane->GetProperties().VALVE1_PLANE_HDR_MULT->SetPendingValue( req, 0x100000000ULL, true ); ++ ++ if ( pPlane->GetProperties().VALVE1_PLANE_SHAPER_TF ) ++ pPlane->GetProperties().VALVE1_PLANE_SHAPER_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); ++ ++ if ( pPlane->GetProperties().VALVE1_PLANE_SHAPER_LUT ) ++ pPlane->GetProperties().VALVE1_PLANE_SHAPER_LUT->SetPendingValue( req, 0, true ); ++ ++ if ( pPlane->GetProperties().VALVE1_PLANE_LUT3D ) ++ pPlane->GetProperties().VALVE1_PLANE_LUT3D->SetPendingValue( req, 0, true ); ++ ++ if ( pPlane->GetProperties().VALVE1_PLANE_BLEND_TF ) ++ pPlane->GetProperties().VALVE1_PLANE_BLEND_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); ++ ++ if ( pPlane->GetProperties().VALVE1_PLANE_BLEND_LUT ) ++ pPlane->GetProperties().VALVE1_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); ++ ++ // We can't close the DRM FD here, it might still be in use by the ++ // page-flip handler thread. ++} ++ ++int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) ++{ ++ int ret; ++ ++ assert( drm->req != nullptr ); + + // if (drm->kms_in_fence_fd != -1) { + // add_plane_property(req, plane_id, "IN_FENCE_FD", drm->kms_in_fence_fd); +@@ -1707,14 +1311,32 @@ int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) + + drm->pending = drm->current; + +- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) ++ 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 ) + { +- drm->crtcs[i].pending = drm->crtcs[i].current; ++ for ( std::optional &oProperty : pPlane->GetProperties() ) ++ { ++ if ( oProperty ) ++ oProperty->Rollback(); ++ } + } + +- for (auto &kv : drm->connectors) { +- struct connector *conn = &kv.second; +- conn->pending = conn->current; ++ for ( auto &iter : drm->connectors ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter.second; ++ for ( std::optional &oProperty : pConnector->GetProperties() ) ++ { ++ if ( oProperty ) ++ oProperty->Rollback(); ++ } + } + + // Undo refcount if the commit didn't actually work +@@ -1736,14 +1358,32 @@ int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) + + drm->current = drm->pending; + +- for (auto & crtc : drm->crtcs) ++ for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) + { +- crtc.current = crtc.pending; ++ for ( std::optional &oProperty : pCRTC->GetProperties() ) ++ { ++ if ( oProperty ) ++ oProperty->OnCommit(); ++ } + } + +- for (auto &kv : drm->connectors) { +- struct connector *conn = &kv.second; +- conn->current = conn->pending; ++ 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(); ++ } + } + } + +@@ -1928,190 +1568,87 @@ void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ) + drm_unlock_fb_internal( drm, &fb ); + } + +-static uint64_t determine_drm_orientation(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) ++static uint64_t determine_drm_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) + { +- drm_screen_type screenType = drm_get_connector_type(conn->connector); +- +- if (conn && conn->props.count("panel orientation") > 0) ++ if ( pConnector && pConnector->GetProperties().panel_orientation ) + { +- const char *orientation = get_enum_name(conn->props["panel orientation"], conn->initial_prop_values["panel orientation"]); +- +- if (strcmp(orientation, "Normal") == 0) ++ switch ( pConnector->GetProperties().panel_orientation->GetCurrentValue() ) + { +- return DRM_MODE_ROTATE_0; +- } +- else if (strcmp(orientation, "Left Side Up") == 0) +- { +- return DRM_MODE_ROTATE_90; +- } +- else if (strcmp(orientation, "Upside Down") == 0) +- { +- return DRM_MODE_ROTATE_180; +- } +- else if (strcmp(orientation, "Right Side Up") == 0) +- { +- return DRM_MODE_ROTATE_270; ++ case DRM_MODE_PANEL_ORIENTATION_NORMAL: ++ return DRM_MODE_ROTATE_0; ++ case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: ++ return DRM_MODE_ROTATE_180; ++ case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: ++ return DRM_MODE_ROTATE_90; ++ case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: ++ return DRM_MODE_ROTATE_270; + } + } +- else ++ ++ if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && mode ) + { +- if (screenType == DRM_SCREEN_TYPE_INTERNAL && mode) +- { +- // Auto-detect portait mode for internal displays +- return mode->hdisplay < mode->vdisplay ? DRM_MODE_ROTATE_270 : DRM_MODE_ROTATE_0; +- } +- else +- { +- return DRM_MODE_ROTATE_0; +- } ++ // Auto-detect portait mode for internal displays ++ return mode->hdisplay < mode->vdisplay ? DRM_MODE_ROTATE_270 : DRM_MODE_ROTATE_0; + } + + return DRM_MODE_ROTATE_0; + } + + /* Handle the orientation of the display */ +-static void update_drm_effective_orientation(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) ++static void update_drm_effective_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) + { +- drm_screen_type screenType = drm_get_connector_type(conn->connector); ++ gamescope::GamescopeScreenType eScreenType = pConnector->GetScreenType(); + +- if (screenType == DRM_SCREEN_TYPE_INTERNAL) ++ if ( eScreenType == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + { + switch ( g_drmModeOrientation ) + { + case PANEL_ORIENTATION_0: +- g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_0; ++ g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_0; + break; + case PANEL_ORIENTATION_90: +- g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_90; ++ g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_90; + break; + case PANEL_ORIENTATION_180: +- g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_180; ++ g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_180; + break; + case PANEL_ORIENTATION_270: +- g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_270; ++ g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_270; + break; + case PANEL_ORIENTATION_AUTO: +- g_drmEffectiveOrientation[screenType] = determine_drm_orientation(drm, conn, mode); ++ g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); + break; + } + } + else + { +- g_drmEffectiveOrientation[screenType] = determine_drm_orientation(drm, conn, mode); ++ g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); + } + } + +-static void update_drm_effective_orientations(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) ++static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) + { +- drm_screen_type screenType = drm_get_connector_type(conn->connector); +- if (screenType == DRM_SCREEN_TYPE_INTERNAL) +- { +- update_drm_effective_orientation(drm, conn, mode); +- return; +- } +- else if (screenType == DRM_SCREEN_TYPE_EXTERNAL) +- { +- update_drm_effective_orientation(drm, conn, mode); ++ gamescope::CDRMConnector *pInternalConnector = nullptr; ++ if ( drm->pConnector && drm->pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) ++ pInternalConnector = drm->pConnector; + +- struct connector *internal_conn = nullptr; +- for ( auto &kv : drm->connectors ) { +- struct connector *kv_con = &kv.second; +- if (kv_con->connector) ++ if ( !pInternalConnector ) ++ { ++ for ( auto &iter : drm->connectors ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter.second; ++ if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + { +- drm_screen_type kv_screentype = drm_get_connector_type(kv_con->connector); +- if (kv_screentype == DRM_SCREEN_TYPE_INTERNAL) +- { +- internal_conn = kv_con; +- break; +- } ++ pInternalConnector = pConnector; ++ // Find mode for internal connector instead. ++ pMode = find_mode(pInternalConnector->GetModeConnector(), 0, 0, 0); ++ break; + } + } +- +- if (internal_conn) +- { +- const drmModeModeInfo *default_internal_mode = find_mode(internal_conn->connector, 0, 0, 0); +- update_drm_effective_orientation(drm, internal_conn, default_internal_mode); +- } +- } +-} +- +-/* Prepares an atomic commit without using libliftoff */ +-static int +-drm_prepare_basic( struct drm_t *drm, const struct FrameInfo_t *frameInfo ) +-{ +- // Discard cases where our non-liftoff path is known to fail +- +- drm_screen_type screenType = drm_get_screen_type(drm); +- +- // It only supports one layer +- if ( frameInfo->layerCount > 1 ) +- { +- drm_verbose_log.errorf("drm_prepare_basic: cannot handle %d layers", frameInfo->layerCount); +- return -EINVAL; +- } +- +- if ( frameInfo->layers[ 0 ].fbid == 0 ) +- { +- drm_verbose_log.errorf("drm_prepare_basic: layer has no FB"); +- return -EINVAL; +- } +- +- drmModeAtomicReq *req = drm->req; +- uint32_t fb_id = frameInfo->layers[ 0 ].fbid; +- +- drm->fbids_in_req.push_back( fb_id ); +- +- add_plane_property(req, drm->primary, "rotation", g_drmEffectiveOrientation[screenType] ); +- +- add_plane_property(req, drm->primary, "FB_ID", fb_id); +- add_plane_property(req, drm->primary, "CRTC_ID", drm->crtc->id); +- add_plane_property(req, drm->primary, "SRC_X", 0); +- add_plane_property(req, drm->primary, "SRC_Y", 0); +- +- const uint16_t srcWidth = frameInfo->layers[ 0 ].tex->width(); +- const uint16_t srcHeight = frameInfo->layers[ 0 ].tex->height(); +- +- add_plane_property(req, drm->primary, "SRC_W", srcWidth << 16); +- add_plane_property(req, drm->primary, "SRC_H", srcHeight << 16); +- +- gpuvis_trace_printf ( "legacy flip fb_id %u src %ix%i", fb_id, +- srcWidth, srcHeight ); +- +- int64_t crtcX = frameInfo->layers[ 0 ].offset.x * -1; +- int64_t crtcY = frameInfo->layers[ 0 ].offset.y * -1; +- int64_t crtcW = srcWidth / frameInfo->layers[ 0 ].scale.x; +- int64_t crtcH = srcHeight / frameInfo->layers[ 0 ].scale.y; +- +- if ( g_bRotated ) +- { +- int64_t imageH = frameInfo->layers[ 0 ].tex->contentHeight() / frameInfo->layers[ 0 ].scale.y; +- +- int64_t tmp = crtcX; +- crtcX = g_nOutputHeight - imageH - crtcY; +- crtcY = tmp; +- +- tmp = crtcW; +- crtcW = crtcH; +- crtcH = tmp; +- } +- +- add_plane_property(req, drm->primary, "CRTC_X", crtcX); +- add_plane_property(req, drm->primary, "CRTC_Y", crtcY); +- add_plane_property(req, drm->primary, "CRTC_W", crtcW); +- add_plane_property(req, drm->primary, "CRTC_H", crtcH); +- +- gpuvis_trace_printf ( "crtc %li,%li %lix%li", crtcX, crtcY, crtcW, crtcH ); +- +- // TODO: disable all planes except drm->primary +- +- unsigned test_flags = (drm->flags & DRM_MODE_ATOMIC_ALLOW_MODESET) | DRM_MODE_ATOMIC_TEST_ONLY; +- int ret = drmModeAtomicCommit( drm->fd, drm->req, test_flags, NULL ); +- +- if ( ret != 0 && ret != -EINVAL && ret != -ERANGE ) { +- drm_log.errorf_errno( "drmModeAtomicCommit failed" ); + } + +- return ret; ++ if ( pInternalConnector ) ++ update_drm_effective_orientation( drm, pInternalConnector, pMode ); + } + + // Only used for NV12 buffers +@@ -2333,10 +1870,498 @@ bool g_bDisableBlendTF = false; + + bool g_bSinglePlaneOptimizations = true; + ++namespace gamescope ++{ ++ //////////////////// ++ // 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.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.VALVE1_PLANE_DEGAMMA_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_DEGAMMA_TF", this, *rawProperties ); ++ m_Props.VALVE1_PLANE_DEGAMMA_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_DEGAMMA_LUT", this, *rawProperties ); ++ m_Props.VALVE1_PLANE_CTM = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_CTM", this, *rawProperties ); ++ m_Props.VALVE1_PLANE_HDR_MULT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_HDR_MULT", this, *rawProperties ); ++ m_Props.VALVE1_PLANE_SHAPER_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_SHAPER_LUT", this, *rawProperties ); ++ m_Props.VALVE1_PLANE_SHAPER_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_SHAPER_TF", this, *rawProperties ); ++ m_Props.VALVE1_PLANE_LUT3D = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_LUT3D", this, *rawProperties ); ++ m_Props.VALVE1_PLANE_BLEND_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_BLEND_TF", this, *rawProperties ); ++ m_Props.VALVE1_PLANE_BLEND_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_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.VALVE1_CRTC_REGAMMA_TF = CDRMAtomicProperty::Instantiate( "VALVE1_CRTC_REGAMMA_TF", this, *rawProperties ); ++ } ++ } ++ ++ ///////////////////////// ++ // CDRMConnector ++ ///////////////////////// ++ CDRMConnector::CDRMConnector( drmModeConnector *pConnector ) ++ : CDRMAtomicTypedObject( pConnector->connector_id ) ++ , 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; ++ } ); ++ ++ // 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'; ++ ++ 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 ); ++ } ++ ++ ParseEDID(); ++ } ++ ++ 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]; ++ if ( di_edid_display_descriptor_get_tag( pDesc ) == 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 ) ); ++ } ++ } ++ ++ drm_log.infof("Connector %s -> %s - %s", m_Mutable.szName, m_Mutable.szMakePNP, m_Mutable.szModel ); ++ ++ const bool bSteamDeckDisplay = ++ ( m_Mutable.szMakePNP == "WLC"sv && m_Mutable.szModel == "ANX7530 U"sv ) || ++ ( m_Mutable.szMakePNP == "ANX"sv && m_Mutable.szModel == "ANX7530 U"sv ) || ++ ( m_Mutable.szMakePNP == "VLV"sv && m_Mutable.szModel == "ANX7530 U"sv ) || ++ ( m_Mutable.szMakePNP == "VLV"sv && m_Mutable.szModel == "Jupiter"sv ) || ++ ( m_Mutable.szMakePNP == "VLV"sv && m_Mutable.szModel == "Galileo"sv ); ++ ++ if ( bSteamDeckDisplay ) ++ { ++ static constexpr uint32_t kPIDGalileoSDC = 0x3003; ++ static constexpr uint32_t kPIDGalileoBOE = 0x3004; ++ ++ if ( pProduct->product == kPIDGalileoSDC ) ++ { ++ m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC; ++ m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckOLEDRates ); ++ } ++ else if ( pProduct->product == kPIDGalileoBOE ) ++ { ++ m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE; ++ m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckOLEDRates ); ++ } ++ else ++ { ++ m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD; ++ m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckLCDRates ); ++ } ++ } ++ ++ // Colorimetry ++ const char *pszColorOverride = getenv( "GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE" ); ++ if ( pszColorOverride && *pszColorOverride && GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL ) ++ { ++ if ( sscanf( pszColorOverride, "%f %f %f %f %f %f %f %f", ++ &m_Mutable.DisplayColorimetry.primaries.r.x, &m_Mutable.DisplayColorimetry.primaries.r.y, ++ &m_Mutable.DisplayColorimetry.primaries.g.x, &m_Mutable.DisplayColorimetry.primaries.g.y, ++ &m_Mutable.DisplayColorimetry.primaries.b.x, &m_Mutable.DisplayColorimetry.primaries.b.y, ++ &m_Mutable.DisplayColorimetry.white.x, &m_Mutable.DisplayColorimetry.white.y ) == 8 ) ++ { ++ drm_log.infof( "[colorimetry]: GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE detected" ); ++ } ++ else ++ { ++ drm_log.errorf( "[colorimetry]: GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE specified, but could not parse \"rx ry gx gy bx by wx wy\"" ); ++ } ++ } ++ else if ( m_Mutable.eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) ++ { ++ drm_log.infof( "[colorimetry]: Steam Deck LCD detected. Using known colorimetry" ); ++ m_Mutable.DisplayColorimetry = displaycolorimetry_steamdeck_measured; ++ } ++ else ++ { ++ // 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 }, ++ }; ++ } ++ } ++ ++ 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. ++ ///////////////////// ++ std::optional oKnownHDRInfo = GetKnownDisplayHDRInfo( m_Mutable.eKnownDisplay ); ++ if ( oKnownHDRInfo ) ++ { ++ m_Mutable.HDR = *oKnownHDRInfo; ++ } ++ else ++ { ++ 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 ); ++ ++ // Generate a default HDR10 infoframe. ++ 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 = drm_create_hdr_metadata_blob( &g_DRM, &defaultHDRMetadata ); ++ } ++ else ++ { ++ m_Mutable.HDR.bExposeHDRSupport = false; ++ } ++ } ++ } ++ ++ /*static*/ std::optional CDRMConnector::GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ) ++ { ++ if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE || eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC ) ++ { ++ // The stuff in the EDID for the HDR metadata does not fully ++ // reflect what we can achieve on the display by poking at more ++ // things out-of-band. ++ return HDRInfo ++ { ++ .bExposeHDRSupport = true, ++ .eOutputEncodingEOTF = EOTF_Gamma22, ++ .uMaxContentLightLevel = nits_to_u16( 1000.0f ), ++ .uMaxFrameAverageLuminance = nits_to_u16( 800.0f ), // Full-frame sustained. ++ .uMinContentLightLevel = nits_to_u16_dark( 0 ), ++ }; ++ } ++ else if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) ++ { ++ // Set up some HDR fallbacks for undocking ++ return HDRInfo ++ { ++ .bExposeHDRSupport = false, ++ .eOutputEncodingEOTF = EOTF_Gamma22, ++ .uMaxContentLightLevel = nits_to_u16( 500.0f ), ++ .uMaxFrameAverageLuminance = nits_to_u16( 500.0f ), ++ .uMinContentLightLevel = nits_to_u16_dark( 0.5f ), ++ }; ++ } ++ ++ return std::nullopt; ++ } ++} ++ + static int + drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, bool needs_modeset ) + { +- drm_screen_type screenType = drm_get_screen_type(drm); ++ gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); + auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); + + // If we are modesetting, reset the state cache, we might +@@ -2510,8 +2535,6 @@ bool g_bForceAsyncFlips = false; + * 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 ) + { +- drm->pending.screen_type = drm_get_screen_type(drm); +- + drm_update_vrr_state(drm); + drm_update_color_mgmt(drm); + +@@ -2522,43 +2545,17 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + assert( drm->req == nullptr ); + drm->req = drmModeAtomicAlloc(); + +- if (drm->connector != nullptr) { +- bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; +- bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; +- +- if (drm->connector->has_colorspace) { +- drm->connector->pending.colorspace = ( bConnectorHDR ) ? DRM_MODE_COLORIMETRY_BT2020_RGB : DRM_MODE_COLORIMETRY_DEFAULT; +- } +- +- if (drm->connector->has_content_type) { +- drm->connector->pending.content_type = DRM_MODE_CONTENT_TYPE_GAME; +- } +- +- if ( bConnectorHDR ) ++ wlserver_hdr_metadata *pHDRMetadata = nullptr; ++ if ( drm->pConnector && drm->pConnector->GetHDRInfo().IsHDR10() ) ++ { ++ if ( g_bOutputHDREnabled ) + { +- if (drm->connector->has_hdr_output_metadata) { +- auto hdr_output_metadata = get_default_hdr_metadata( drm, drm->connector ); +- +- if ( drm->connector->metadata.hdr10_metadata_blob ) +- hdr_output_metadata = drm->connector->metadata.hdr10_metadata_blob; +- +- auto feedback = steamcompmgr_get_base_layer_swapchain_feedback(); +- if (feedback && feedback->hdr_metadata_blob) +- hdr_output_metadata = feedback->hdr_metadata_blob; +- +- drm->connector->pending.hdr_output_metadata = hdr_output_metadata; +- } ++ wlserver_vk_swapchain_feedback* pFeedback = steamcompmgr_get_base_layer_swapchain_feedback(); ++ pHDRMetadata = pFeedback ? pFeedback->hdr_metadata_blob.get() : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); + } + else + { +- if (drm->connector->has_hdr_output_metadata && bConnectorSupportsHDR) +- { +- drm->connector->pending.hdr_output_metadata = drm->sdr_static_metadata; +- } +- else +- { +- drm->connector->pending.hdr_output_metadata = nullptr; +- } ++ pHDRMetadata = drm->sdr_static_metadata.get(); + } + } + +@@ -2585,196 +2582,116 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; + + // We do internal refcounting with these events +- if ( drm->crtc != nullptr ) ++ if ( drm->pCRTC != nullptr ) + flags |= DRM_MODE_PAGE_FLIP_EVENT; + + if ( async || g_bForceAsyncFlips ) + flags |= DRM_MODE_PAGE_FLIP_ASYNC; + +- if ( needs_modeset ) { ++ bool bForceInRequest = needs_modeset; ++ ++ if ( needs_modeset ) ++ { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + + // Disable all connectors and CRTCs + +- for ( auto &kv : drm->connectors ) { +- struct connector *conn = &kv.second; +- +- if ( conn->current.crtc_id == 0 ) ++ for ( auto &iter : drm->connectors ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter.second; ++ if ( pConnector->GetProperties().CRTC_ID->GetCurrentValue() == 0 ) + continue; + +- conn->pending.crtc_id = 0; +- int ret = add_connector_property( drm->req, conn, "CRTC_ID", 0 ); +- if (ret < 0) +- return ret; ++ pConnector->GetProperties().CRTC_ID->SetPendingValue( drm->req, 0, bForceInRequest ); + +- if (conn->has_colorspace) { +- ret = add_connector_property( drm->req, conn, "Colorspace", 0 ); +- if (ret < 0) +- return ret; +- } ++ if ( pConnector->GetProperties().Colorspace ) ++ pConnector->GetProperties().Colorspace->SetPendingValue( drm->req, 0, bForceInRequest ); + +- if (conn->has_hdr_output_metadata) { +- ret = add_connector_property( drm->req, conn, "HDR_OUTPUT_METADATA", 0 ); +- if (ret < 0) +- return ret; +- } ++ if ( pConnector->GetProperties().HDR_OUTPUT_METADATA ) ++ pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, 0, bForceInRequest ); + +- if (conn->has_content_type) { +- ret = add_connector_property( drm->req, conn, "content type", 0 ); +- if (ret < 0) +- return ret; +- } ++ if ( pConnector->GetProperties().content_type ) ++ pConnector->GetProperties().content_type->SetPendingValue( drm->req, 0, bForceInRequest ); + } +- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) { +- struct crtc *crtc = &drm->crtcs[i]; + ++ 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 (crtc->current.active == 0) ++ if ( pCRTC->GetProperties().ACTIVE->GetCurrentValue() == 0 ) + continue; + +- int ret = add_crtc_property(drm->req, crtc, "MODE_ID", 0); +- if (ret < 0) +- return ret; +- if (crtc->has_gamma_lut) +- { +- int ret = add_crtc_property(drm->req, crtc, "GAMMA_LUT", 0); +- if (ret < 0) +- return ret; +- } +- if (crtc->has_degamma_lut) +- { +- int ret = add_crtc_property(drm->req, crtc, "DEGAMMA_LUT", 0); +- if (ret < 0) +- return ret; +- } +- if (crtc->has_ctm) +- { +- int ret = add_crtc_property(drm->req, crtc, "CTM", 0); +- if (ret < 0) +- return ret; +- } +- if (crtc->has_vrr_enabled) +- { +- int ret = add_crtc_property(drm->req, crtc, "VRR_ENABLED", 0); +- if (ret < 0) +- return ret; +- } +- if (crtc->has_valve1_regamma_tf) +- { +- int ret = add_crtc_property(drm->req, crtc, "VALVE1_CRTC_REGAMMA_TF", 0); +- if (ret < 0) +- return ret; +- } ++ pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 0, bForceInRequest ); ++ pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, 0, bForceInRequest ); + +- ret = add_crtc_property(drm->req, crtc, "ACTIVE", 0); +- if (ret < 0) +- return ret; +- crtc->pending.active = 0; +- } ++ if ( pCRTC->GetProperties().GAMMA_LUT ) ++ pCRTC->GetProperties().GAMMA_LUT->SetPendingValue( drm->req, 0, bForceInRequest ); + +- // Then enable the one we've picked +- int ret = 0; +- if (drm->connector != nullptr) { +- // Always set our CRTC_ID for the modeset, especially +- // as we zero-ed it above. +- drm->connector->pending.crtc_id = drm->crtc->id; +- ret = add_connector_property(drm->req, drm->connector, "CRTC_ID", drm->crtc->id); +- if (ret < 0) +- return ret; +- +- if (drm->connector->has_colorspace) { +- ret = add_connector_property(drm->req, drm->connector, "Colorspace", drm->connector->pending.colorspace); +- if (ret < 0) +- return ret; +- } ++ if ( pCRTC->GetProperties().DEGAMMA_LUT ) ++ pCRTC->GetProperties().DEGAMMA_LUT->SetPendingValue( drm->req, 0, bForceInRequest ); + +- if (drm->connector->has_hdr_output_metadata) { +- uint32_t value = drm->connector->pending.hdr_output_metadata ? drm->connector->pending.hdr_output_metadata->blob : 0; +- ret = add_connector_property(drm->req, drm->connector, "HDR_OUTPUT_METADATA", value); +- if (ret < 0) +- return ret; +- } ++ if ( pCRTC->GetProperties().CTM ) ++ pCRTC->GetProperties().CTM->SetPendingValue( drm->req, 0, bForceInRequest ); + +- if (drm->connector->has_content_type) { +- ret = add_connector_property(drm->req, drm->connector, "content type", drm->connector->pending.content_type); +- if (ret < 0) +- return ret; +- } ++ if ( pCRTC->GetProperties().VRR_ENABLED ) ++ pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, 0, bForceInRequest ); + +- ret = add_crtc_property(drm->req, drm->crtc, "MODE_ID", drm->pending.mode_id->blob); +- if (ret < 0) +- return ret; ++ if ( pCRTC->GetProperties().OUT_FENCE_PTR ) ++ pCRTC->GetProperties().OUT_FENCE_PTR->SetPendingValue( drm->req, 0, bForceInRequest ); + +- if (drm->crtc->has_vrr_enabled) +- { +- ret = add_crtc_property(drm->req, drm->crtc, "VRR_ENABLED", drm->pending.vrr_enabled); +- if (ret < 0) +- return ret; +- } ++ if ( pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) ++ pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->SetPendingValue( drm->req, 0, bForceInRequest ); ++ } ++ ++ if ( drm->pConnector ) ++ { ++ // 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->crtc->has_valve1_regamma_tf) ++ if ( drm->pConnector->GetProperties().Colorspace ) + { +- ret = add_crtc_property(drm->req, drm->crtc, "VALVE1_CRTC_REGAMMA_TF", drm->pending.output_tf); +- if (ret < 0) +- return ret; ++ uint32_t uColorimetry = g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() ++ ? DRM_MODE_COLORIMETRY_BT2020_RGB ++ : DRM_MODE_COLORIMETRY_DEFAULT; ++ drm->pConnector->GetProperties().Colorspace->SetPendingValue( drm->req, uColorimetry, bForceInRequest ); + } +- +- ret = add_crtc_property(drm->req, drm->crtc, "ACTIVE", 1); +- if (ret < 0) +- return ret; +- drm->crtc->pending.active = 1; + } +- } +- else +- { +- if (drm->connector != nullptr) { +- if (drm->connector->has_colorspace && drm->connector->pending.colorspace != drm->connector->current.colorspace) { +- int ret = add_connector_property(drm->req, drm->connector, "Colorspace", drm->connector->pending.colorspace); +- if (ret < 0) +- return ret; +- } + +- if (drm->connector->has_hdr_output_metadata && drm->connector->pending.hdr_output_metadata != drm->connector->current.hdr_output_metadata) { +- uint32_t value = drm->connector->pending.hdr_output_metadata ? drm->connector->pending.hdr_output_metadata->blob : 0; +- int ret = add_connector_property(drm->req, drm->connector, "HDR_OUTPUT_METADATA", value); +- if (ret < 0) +- return ret; +- } ++ if ( drm->pCRTC ) ++ { ++ 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->blob : 0lu, true ); + +- if (drm->connector->has_content_type && drm->connector->pending.content_type != drm->connector->current.content_type) { +- int ret = add_connector_property(drm->req, drm->connector, "content type", drm->connector->pending.content_type); +- if (ret < 0) +- return ret; +- } ++ if ( drm->pCRTC->GetProperties().VRR_ENABLED ) ++ drm->pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, drm->pending.vrr_enabled, true ); + } ++ } + +- if (drm->crtc != nullptr) { +- if ( drm->crtc->has_vrr_enabled && drm->pending.vrr_enabled != drm->current.vrr_enabled ) +- { +- int ret = add_crtc_property(drm->req, drm->crtc, "VRR_ENABLED", drm->pending.vrr_enabled ); +- if (ret < 0) +- return ret; +- } ++ if ( drm->pConnector ) ++ { ++ if ( drm->pConnector->GetProperties().HDR_OUTPUT_METADATA ) ++ drm->pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, pHDRMetadata ? pHDRMetadata->blob : 0lu, bForceInRequest ); + +- if ( drm->crtc->has_valve1_regamma_tf && drm->pending.output_tf != drm->current.output_tf ) +- { +- int ret = add_crtc_property(drm->req, drm->crtc, "VALVE1_CRTC_REGAMMA_TF", drm->pending.output_tf ); +- if (ret < 0) +- return ret; +- } +- } ++ if ( drm->pConnector->GetProperties().content_type ) ++ drm->pConnector->GetProperties().content_type->SetPendingValue( drm->req, DRM_MODE_CONTENT_TYPE_GAME, bForceInRequest ); ++ } ++ ++ if ( drm->pCRTC ) ++ { ++ if ( drm->pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) ++ drm->pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->SetPendingValue( drm->req, drm->pending.output_tf, bForceInRequest ); + } + + drm->flags = flags; + + int ret; +- if ( drm->crtc == nullptr ) { ++ if ( drm->pCRTC == nullptr ) { + ret = 0; +- } else if ( g_bUseLayers == true ) { ++ } else if ( drm->bUseLiftoff ) { + ret = drm_prepare_liftoff( drm, frameInfo, needs_modeset ); + } else { +- ret = drm_prepare_basic( drm, frameInfo ); ++ ret = 0; + } + + if ( ret != 0 ) { +@@ -2790,21 +2707,6 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + return ret; + } + +-void drm_rollback( struct drm_t *drm ) +-{ +- drm->pending = drm->current; +- +- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) +- { +- drm->crtcs[i].pending = drm->crtcs[i].current; +- } +- +- for (auto &kv : drm->connectors) { +- struct connector *conn = &kv.second; +- conn->pending = conn->current; +- } +-} +- + bool drm_poll_state( struct drm_t *drm ) + { + int out_of_date = drm->out_of_date.exchange(false); +@@ -2818,25 +2720,18 @@ bool drm_poll_state( struct drm_t *drm ) + return true; + } + +-static bool drm_set_crtc( struct drm_t *drm, struct crtc *crtc ) ++static bool drm_set_crtc( struct drm_t *drm, gamescope::CDRMCRTC *pCRTC ) + { +- drm->crtc = crtc; ++ drm->pCRTC = pCRTC; + drm->needs_modeset = true; + +- for (size_t i = 0; i < drm->crtcs.size(); i++) { +- if (drm->crtcs[i].id == drm->crtc->id) { +- drm->crtc_index = i; +- break; +- } +- } +- +- drm->primary = find_primary_plane( drm ); +- if ( drm->primary == nullptr ) { ++ 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, crtc->id ); ++ struct liftoff_output *lo_output = liftoff_output_create( drm->lo_device, pCRTC->GetObjectId() ); + if ( lo_output == nullptr ) + return false; + +@@ -2854,21 +2749,22 @@ static bool drm_set_crtc( struct drm_t *drm, struct crtc *crtc ) + return true; + } + +-bool drm_set_connector( struct drm_t *drm, struct connector *conn ) ++bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ) + { +- drm_log.infof("selecting connector %s", conn->name); ++ drm_log.infof("selecting connector %s", conn->GetName()); + +- struct crtc *crtc = find_crtc_for_connector(drm, conn); +- if (crtc == nullptr) { ++ 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, crtc)) { ++ if (!drm_set_crtc(drm, pCRTC)) { + return false; + } + +- drm->connector = conn; ++ drm->pConnector = conn; + drm->needs_modeset = true; + + return true; +@@ -2876,8 +2772,8 @@ bool drm_set_connector( struct drm_t *drm, struct connector *conn ) + + static void drm_unset_connector( struct drm_t *drm ) + { +- drm->crtc = nullptr; +- drm->primary = nullptr; ++ drm->pCRTC = nullptr; ++ drm->pPrimaryPlane = nullptr; + + for ( int i = 0; i < k_nMaxLayers; i++ ) + { +@@ -2888,7 +2784,7 @@ static void drm_unset_connector( struct drm_t *drm ) + liftoff_output_destroy(drm->lo_output); + drm->lo_output = nullptr; + +- drm->connector = nullptr; ++ drm->pConnector = nullptr; + drm->needs_modeset = true; + } + +@@ -2902,22 +2798,12 @@ bool drm_get_vrr_in_use(struct drm_t *drm) + return drm->current.vrr_enabled; + } + +-drm_screen_type drm_get_connector_type(drmModeConnector *connector) ++gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm) + { +- if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || +- connector->connector_type == DRM_MODE_CONNECTOR_LVDS || +- connector->connector_type == DRM_MODE_CONNECTOR_DSI) +- return DRM_SCREEN_TYPE_INTERNAL; ++ if ( !drm->pConnector ) ++ return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; + +- return DRM_SCREEN_TYPE_EXTERNAL; +-} +- +-drm_screen_type drm_get_screen_type(struct drm_t *drm) +-{ +- if (!drm->connector || !drm->connector->connector) +- return DRM_SCREEN_TYPE_INTERNAL; +- +- return drm_get_connector_type(drm->connector->connector); ++ return drm->pConnector->GetScreenType(); + } + + bool drm_update_color_mgmt(struct drm_t *drm) +@@ -2963,9 +2849,9 @@ bool drm_update_vrr_state(struct drm_t *drm) + { + drm->pending.vrr_enabled = false; + +- if ( drm->connector && drm->crtc && drm->crtc->has_vrr_enabled ) ++ if ( drm->pConnector && drm->pCRTC && drm->pCRTC->GetProperties().VRR_ENABLED ) + { +- if ( drm->wants_vrr_enabled && drm->connector->vrr_capable ) ++ if ( drm->wants_vrr_enabled && drm->pConnector->IsVRRCapable() ) + drm->pending.vrr_enabled = true; + } + +@@ -2991,21 +2877,21 @@ static void drm_unset_mode( struct drm_t *drm ) + if (g_nOutputRefresh == 0) + g_nOutputRefresh = 60; + +- g_drmEffectiveOrientation[DRM_SCREEN_TYPE_INTERNAL] = DRM_MODE_ROTATE_0; +- g_drmEffectiveOrientation[DRM_SCREEN_TYPE_EXTERNAL] = DRM_MODE_ROTATE_0; ++ g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = DRM_MODE_ROTATE_0; ++ g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = DRM_MODE_ROTATE_0; + g_bRotated = false; + } + + bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) + { +- if (!drm->connector || !drm->connector->connector) ++ if (!drm->pConnector || !drm->pConnector->GetModeConnector()) + return false; + + uint32_t mode_id = 0; + if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode), &mode_id) != 0) + return false; + +- drm_screen_type screenType = drm_get_screen_type(drm); ++ gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); + + drm_log.infof("selecting mode %dx%d@%uHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); + +@@ -3014,7 +2900,7 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) + + g_nOutputRefresh = mode->vrefresh; + +- update_drm_effective_orientations(drm, drm->connector, mode); ++ update_drm_effective_orientations(drm, mode); + + switch ( g_drmEffectiveOrientation[screenType] ) + { +@@ -3045,10 +2931,10 @@ bool drm_set_refresh( struct drm_t *drm, int refresh ) + width = height; + height = tmp; + } +- if (!drm->connector || !drm->connector->connector) ++ if (!drm->pConnector || !drm->pConnector->GetModeConnector()) + return false; + +- drmModeConnector *connector = drm->connector->connector; ++ drmModeConnector *connector = drm->pConnector->GetModeConnector(); + const drmModeModeInfo *existing_mode = find_mode(connector, width, height, refresh); + drmModeModeInfo mode = {0}; + if ( existing_mode ) +@@ -3058,15 +2944,15 @@ bool drm_set_refresh( struct drm_t *drm, int refresh ) + else + { + /* TODO: check refresh is within the EDID limits */ +- switch ( g_drmModeGeneration ) ++ switch ( g_eGamescopeModeGeneration ) + { +- case DRM_MODE_GENERATE_CVT: ++ case gamescope::GAMESCOPE_MODE_GENERATE_CVT: + generate_cvt_mode( &mode, width, height, refresh, true, false ); + break; +- case DRM_MODE_GENERATE_FIXED: ++ case gamescope::GAMESCOPE_MODE_GENERATE_FIXED: + { + const drmModeModeInfo *preferred_mode = find_mode(connector, 0, 0, 0); +- generate_fixed_mode( &mode, preferred_mode, refresh, drm->connector->is_steam_deck_display, drm->connector->is_galileo_display ); ++ generate_fixed_mode( &mode, preferred_mode, refresh, drm->pConnector->GetKnownDisplayType() ); + break; + } + } +@@ -3079,10 +2965,10 @@ bool drm_set_refresh( struct drm_t *drm, int refresh ) + + bool drm_set_resolution( struct drm_t *drm, int width, int height ) + { +- if (!drm->connector || !drm->connector->connector) ++ if (!drm->pConnector || !drm->pConnector->GetModeConnector()) + return false; + +- drmModeConnector *connector = drm->connector->connector; ++ drmModeConnector *connector = drm->pConnector->GetModeConnector(); + const drmModeModeInfo *mode = find_mode(connector, width, height, 0); + if ( !mode ) + { +@@ -3097,10 +2983,10 @@ int drm_get_default_refresh(struct drm_t *drm) + if ( drm->preferred_refresh ) + return drm->preferred_refresh; + +- if ( drm->connector && drm->connector->target_refresh ) +- return drm->connector->target_refresh; ++ if ( drm->pConnector && drm->pConnector->GetBaseRefresh() ) ++ return drm->pConnector->GetBaseRefresh(); + +- if ( drm->connector && drm->connector->connector ) ++ if ( drm->pConnector && drm->pConnector->GetModeConnector() ) + { + int width = g_nOutputWidth; + int height = g_nOutputHeight; +@@ -3110,7 +2996,7 @@ int drm_get_default_refresh(struct drm_t *drm) + height = tmp; + } + +- drmModeConnector *connector = drm->connector->connector; ++ drmModeConnector *connector = drm->pConnector->GetModeConnector(); + const drmModeModeInfo *mode = find_mode( connector, width, height, 0); + if ( mode ) + return mode->vrefresh; +@@ -3121,20 +3007,20 @@ int drm_get_default_refresh(struct drm_t *drm) + + bool drm_get_vrr_capable(struct drm_t *drm) + { +- if ( drm->connector ) +- return drm->connector->vrr_capable; ++ if ( drm->pConnector ) ++ return drm->pConnector->IsVRRCapable(); + + return false; + } + +-bool drm_supports_st2084(struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL) ++bool drm_supports_hdr( struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL ) + { +- if ( drm->connector && drm->connector->metadata.supportsST2084 ) ++ if ( drm->pConnector && drm->pConnector->GetHDRInfo().SupportsHDR() ) + { + if ( maxCLL ) +- *maxCLL = drm->connector->metadata.maxCLL; ++ *maxCLL = drm->pConnector->GetHDRInfo().uMaxContentLightLevel; + if ( maxFALL ) +- *maxFALL = drm->connector->metadata.maxFALL; ++ *maxFALL = drm->pConnector->GetHDRInfo().uMaxFrameAverageLuminance; + return true; + } + +@@ -3150,10 +3036,10 @@ void drm_set_hdr_state(struct drm_t *drm, bool enabled) { + + const char *drm_get_connector_name(struct drm_t *drm) + { +- if ( !drm->connector ) ++ if ( !drm->pConnector ) + return nullptr; + +- return drm->connector->name; ++ return drm->pConnector->GetName(); + } + + const char *drm_get_device_name(struct drm_t *drm) +@@ -3163,10 +3049,10 @@ const char *drm_get_device_name(struct drm_t *drm) + + std::pair drm_get_connector_identifier(struct drm_t *drm) + { +- if ( !drm->connector ) ++ if ( !drm->pConnector ) + return { 0u, 0u }; + +- return std::make_pair(drm->connector->connector->connector_type, drm->connector->connector->connector_type_id); ++ return std::make_pair(drm->pConnector->GetModeConnector()->connector_type, drm->pConnector->GetModeConnector()->connector_type_id); + } + + std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata) +@@ -3222,20 +3108,20 @@ std::shared_ptr drm_create_ctm(struct drm_t *drm, glm::mat3x4 ctm) + + bool drm_supports_color_mgmt(struct drm_t *drm) + { +- if (g_bForceDisableColorMgmt) ++ if ( g_bForceDisableColorMgmt ) + return false; + +- if (!drm->primary) ++ if ( !drm->pPrimaryPlane ) + return false; + +- return drm->primary->has_color_mgmt; ++ return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); + } + + void drm_get_native_colorimetry( struct drm_t *drm, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) + { +- if ( !drm || !drm->connector ) ++ if ( !drm || !drm->pConnector ) + { + *displayColorimetry = displaycolorimetry_709; + *displayEOTF = EOTF_Gamma22; +@@ -3244,33 +3130,27 @@ void drm_get_native_colorimetry( struct drm_t *drm, + return; + } + +- *displayColorimetry = drm->connector->metadata.colorimetry; +- *displayEOTF = drm->connector->metadata.eotf; ++ *displayColorimetry = drm->pConnector->GetDisplayColorimetry(); ++ *displayEOTF = EOTF_Gamma22; + +- // For HDR output, expected content colorspace != native colorspace. +- if (drm->connector->metadata.supportsST2084 && g_bOutputHDREnabled) ++ // For HDR10 output, expected content colorspace != native colorspace. ++ if ( g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() ) + { + *outputEncodingColorimetry = displaycolorimetry_2020; +- *outputEncodingEOTF = EOTF_PQ; ++ *outputEncodingEOTF = drm->pConnector->GetHDRInfo().eOutputEncodingEOTF; + } + else + { +- *outputEncodingColorimetry = drm->connector->metadata.colorimetry; +- *outputEncodingEOTF = drm->connector->metadata.eotf; +- } +- +- if (!g_bOutputHDREnabled) +- { +- *displayEOTF = EOTF_Gamma22; ++ *outputEncodingColorimetry = drm->pConnector->GetDisplayColorimetry(); + *outputEncodingEOTF = EOTF_Gamma22; + } + } + + +-std::span drm_get_valid_refresh_rates( struct drm_t *drm ) ++std::span drm_get_valid_refresh_rates( struct drm_t *drm ) + { +- if (drm && drm->connector) +- return drm->connector->valid_display_rates; ++ if ( drm && drm->pConnector ) ++ return drm->pConnector->GetValidDynamicRefreshRates(); + +- return std::span{}; ++ return std::span{}; + } +diff --git a/src/drm.hpp b/src/drm.hpp +index 409c87c92..1ab4fc387 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -11,6 +11,7 @@ + #include + + #include "color_helpers.h" ++#include "gamescope_shared.h" + + // Josh: Okay whatever, this header isn't + // available for whatever stupid reason. :v +@@ -28,14 +29,6 @@ enum drm_color_range { + DRM_COLOR_RANGE_MAX, + }; + +-enum drm_screen_type { +- DRM_SCREEN_TYPE_INTERNAL = 0, +- DRM_SCREEN_TYPE_EXTERNAL = 1, +- +- DRM_SCREEN_TYPE_COUNT +-}; +- +- + enum GamescopeAppTextureColorspace { + GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR = 0, + GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, +@@ -137,79 +130,275 @@ extern "C" { + #include + #include + +-struct saved_mode { +- int width; +- int height; +- int refresh; +-}; +- +-struct plane { +- uint32_t id; +- drmModePlane *plane; +- std::map props; +- std::map initial_prop_values; +- bool has_color_mgmt; +-}; +- +-struct crtc { +- uint32_t id; +- drmModeCrtc *crtc; +- std::map props; +- std::map initial_prop_values; +- bool has_gamma_lut; +- bool has_degamma_lut; +- bool has_ctm; +- bool has_vrr_enabled; +- bool has_valve1_regamma_tf; +- +- struct { +- bool active; +- } current, pending; +-}; ++namespace gamescope ++{ ++ template ++ using CAutoDeletePtr = std::unique_ptr; + +-struct connector_metadata_t { +- struct hdr_output_metadata defaultHdrMetadata = {}; +- std::shared_ptr hdr10_metadata_blob; +- bool supportsST2084 = false; ++ //////////////////////////////////////// ++ // DRM Object Wrappers + State Trackers ++ //////////////////////////////////////// ++ struct DRMObjectRawProperty ++ { ++ uint32_t uPropertyId = 0ul; ++ uint64_t ulValue = 0ul; ++ }; ++ using DRMObjectRawProperties = std::unordered_map; + +- displaycolorimetry_t colorimetry = displaycolorimetry_709; +- EOTF eotf = EOTF_Gamma22; +- uint16_t maxCLL = 0; +- uint16_t maxFALL = 0; +-}; ++ 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(); ++ }; + +-struct connector { +- uint32_t id; +- char *name; +- drmModeConnector *connector; +- uint32_t possible_crtcs; +- std::map props; +- std::map initial_prop_values; ++ class CDRMAtomicProperty ++ { ++ public: ++ CDRMAtomicProperty( CDRMAtomicObject *pObject, DRMObjectRawProperty rawProperty ); + +- char make_pnp[4]; +- char *make; +- char *model; +- bool is_steam_deck_display; +- std::span valid_display_rates{}; +- uint16_t is_galileo_display; ++ static std::optional Instantiate( const char *pszName, CDRMAtomicObject *pObject, const DRMObjectRawProperties& rawProperties ); + +- int target_refresh; +- bool vrr_capable; ++ uint64_t GetPendingValue() const { return m_ulPendingValue; } ++ uint64_t GetCurrentValue() const { return m_ulCurrentValue; } ++ int SetPendingValue( drmModeAtomicReq *pRequest, uint64_t ulValue, bool bForce ); + +- connector_metadata_t metadata; ++ void OnCommit(); ++ void Rollback(); ++ private: ++ CDRMAtomicObject *m_pObject = nullptr; ++ uint32_t m_uPropertyId = 0u; + +- struct { +- uint32_t crtc_id; +- uint32_t colorspace; +- uint32_t content_type; +- std::shared_ptr hdr_output_metadata; +- } current, pending; ++ uint64_t m_ulPendingValue = 0ul; ++ uint64_t m_ulCurrentValue = 0ul; ++ uint64_t m_ulInitialValue = 0ul; ++ }; + +- std::vector edid_data; ++ 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 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 VALVE1_PLANE_DEGAMMA_TF; ++ std::optional VALVE1_PLANE_DEGAMMA_LUT; ++ std::optional VALVE1_PLANE_CTM; ++ std::optional VALVE1_PLANE_HDR_MULT; ++ std::optional VALVE1_PLANE_SHAPER_LUT; ++ std::optional VALVE1_PLANE_SHAPER_TF; ++ std::optional VALVE1_PLANE_LUT3D; ++ std::optional VALVE1_PLANE_BLEND_TF; ++ std::optional VALVE1_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 VALVE1_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 CDRMAtomicTypedObject ++ { ++ public: ++ CDRMConnector( 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 DUMMY_END; ++ }; ++ ConnectorProperties &GetProperties() { return m_Props; } ++ const ConnectorProperties &GetProperties() const { return m_Props; } ++ ++ struct HDRInfo ++ { ++ // 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. ++ bool bExposeHDRSupport = 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. ++ EOTF eOutputEncodingEOTF = EOTF_Gamma22; ++ ++ uint16_t uMaxContentLightLevel = 500; // Nits ++ uint16_t uMaxFrameAverageLuminance = 500; // Nits ++ uint16_t uMinContentLightLevel = 0; // Nits / 10000 ++ std::shared_ptr pDefaultMetadataBlob; ++ ++ bool ShouldPatchEDID() const ++ { ++ return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_Gamma22; ++ } ++ ++ bool SupportsHDR() const ++ { ++ // Note: Different to IsHDR10, as we can expose HDR10 on G2.2 displays ++ // using LUTs and CTMs. ++ return bExposeHDRSupport; ++ } ++ ++ bool IsHDR10() const ++ { ++ // PQ output encoding is always HDR10 (PQ + 2020) for us. ++ // If that assumption changes, update me. ++ return eOutputEncodingEOTF == EOTF_PQ; ++ } ++ }; ++ ++ drmModeConnector *GetModeConnector() { return m_pConnector.get(); } ++ const char *GetName() const { return m_Mutable.szName; } ++ const char *GetMake() const { return m_Mutable.pszMake; } ++ const char *GetModel() const { return m_Mutable.szModel; } ++ uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } ++ const HDRInfo &GetHDRInfo() const { return m_Mutable.HDR; } ++ std::span GetValidDynamicRefreshRates() const { return m_Mutable.ValidDynamicRefreshRates; } ++ GamescopeKnownDisplays GetKnownDisplayType() const { return m_Mutable.eKnownDisplay; } ++ GamescopeScreenType GetScreenType() const ++ { ++ 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; ++ } ++ bool IsVRRCapable() const ++ { ++ return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); ++ } ++ const displaycolorimetry_t& GetDisplayColorimetry() const { return m_Mutable.DisplayColorimetry; } ++ ++ const std::vector &GetRawEDID() const { return m_Mutable.EdidData; } ++ ++ // TODO: Remove ++ void SetBaseRefresh( int nRefresh ) { m_nBaseRefresh = nRefresh; } ++ int GetBaseRefresh() const { return m_nBaseRefresh; } ++ private: ++ void ParseEDID(); ++ ++ static std::optional GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ); ++ ++ CAutoDeletePtr m_pConnector; ++ ++ struct MutableConnectorState ++ { ++ int nDefaultRefresh = 0; ++ ++ uint32_t uPossibleCRTCMask = 0u; ++ char szName[32]{}; ++ char szMakePNP[4]{}; ++ char szModel[16]{}; ++ const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. ++ GamescopeKnownDisplays eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_UNKNOWN; ++ std::span ValidDynamicRefreshRates{}; ++ std::vector EdidData; // Raw, unmodified. ++ ++ displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; ++ HDRInfo HDR; ++ } m_Mutable; ++ ++ // TODO: Remove ++ int m_nBaseRefresh = 0; ++ ++ ConnectorProperties m_Props; ++ }; ++} + +- bool has_colorspace; +- bool has_content_type; +- bool has_hdr_output_metadata; ++struct saved_mode { ++ int width; ++ int height; ++ int refresh; + }; + + struct fb { +@@ -240,6 +429,8 @@ enum drm_valve1_transfer_function { + }; + + struct drm_t { ++ bool bUseLiftoff; ++ + int fd; + + int preferred_width, preferred_height, preferred_refresh; +@@ -248,16 +439,15 @@ struct drm_t { + bool allow_modifiers; + struct wlr_drm_format_set formats; + +- std::vector< struct plane > planes; +- std::vector< struct crtc > crtcs; +- std::unordered_map< uint32_t, struct connector > connectors; ++ 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::map< uint32_t, drmModePropertyRes * > props; + +- struct plane *primary; +- struct crtc *crtc; +- struct connector *connector; +- int crtc_index; ++ gamescope::CDRMPlane *pPrimaryPlane; ++ gamescope::CDRMCRTC *pCRTC; ++ gamescope::CDRMConnector *pConnector; + int kms_in_fence_fd; + int kms_out_fence_fd; + +@@ -277,7 +467,7 @@ struct drm_t { + uint32_t color_mgmt_serial; + std::shared_ptr lut3d_id[ EOTF_Count ]; + std::shared_ptr shaperlut_id[ EOTF_Count ]; +- enum drm_screen_type screen_type = DRM_SCREEN_TYPE_INTERNAL; ++ // TODO: Remove me, this should be some generic setting. + bool vrr_enabled = false; + drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; + } current, pending; +@@ -318,17 +508,11 @@ extern struct drm_t g_DRM; + extern uint32_t g_nDRMFormat; + extern uint32_t g_nDRMFormatOverlay; + +-extern bool g_bUseLayers; + extern bool g_bRotated; + extern bool g_bFlipped; + extern bool g_bDebugLayers; + extern const char *g_sOutputName; + +-enum drm_mode_generation { +- DRM_MODE_GENERATE_CVT, +- DRM_MODE_GENERATE_FIXED, +-}; +- + enum g_panel_orientation { + PANEL_ORIENTATION_0, /* NORMAL */ + PANEL_ORIENTATION_270, /* RIGHT */ +@@ -337,10 +521,18 @@ enum g_panel_orientation { + PANEL_ORIENTATION_AUTO, + }; + +-extern enum drm_mode_generation g_drmModeGeneration; ++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, ++}; ++ ++extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; + extern enum g_panel_orientation g_drmModeOrientation; + +-extern std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* ++extern std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* + + extern bool g_bForceDisableColorMgmt; + +@@ -348,24 +540,23 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ + void finish_drm(struct drm_t *drm); + int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); + int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ); +-void drm_rollback( struct drm_t *drm ); + bool drm_poll_state(struct drm_t *drm); + uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ); + void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); + void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); + void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); +-bool drm_set_connector( struct drm_t *drm, struct connector *conn ); ++bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ); + bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); + bool drm_set_refresh( struct drm_t *drm, int refresh ); + bool drm_set_resolution( struct drm_t *drm, int width, int height ); + bool drm_update_color_mgmt(struct drm_t *drm); + bool drm_update_vrr_state(struct drm_t *drm); +-drm_screen_type drm_get_screen_type(struct drm_t *drm); ++gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm); + + char *find_drm_node_by_devid(dev_t devid); + int drm_get_default_refresh(struct drm_t *drm); + bool drm_get_vrr_capable(struct drm_t *drm); +-bool drm_supports_st2084(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); ++bool drm_supports_hdr(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); + void drm_set_vrr_enabled(struct drm_t *drm, bool enabled); + bool drm_get_vrr_in_use(struct drm_t *drm); + bool drm_supports_color_mgmt(struct drm_t *drm); +@@ -383,7 +574,7 @@ void drm_get_native_colorimetry( struct drm_t *drm, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, + displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ); + +-std::span drm_get_valid_refresh_rates( struct drm_t *drm ); ++std::span drm_get_valid_refresh_rates( struct drm_t *drm ); + + extern bool g_bSupportsAsyncFlips; + +diff --git a/src/gamescope_shared.h b/src/gamescope_shared.h +new file mode 100644 +index 000000000..523f58ed9 +--- /dev/null ++++ b/src/gamescope_shared.h +@@ -0,0 +1,27 @@ ++#pragma once ++ ++namespace gamescope ++{ ++ enum GamescopeKnownDisplays ++ { ++ GAMESCOPE_KNOWN_DISPLAY_UNKNOWN, ++ GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD, // Jupiter ++ GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC, // Galileo SDC ++ GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE, // Galileo BOE ++ }; ++ ++ enum GamescopeModeGeneration ++ { ++ GAMESCOPE_MODE_GENERATE_CVT, ++ GAMESCOPE_MODE_GENERATE_FIXED, ++ }; ++ ++ enum GamescopeScreenType ++ { ++ GAMESCOPE_SCREEN_TYPE_INTERNAL, ++ GAMESCOPE_SCREEN_TYPE_EXTERNAL, ++ ++ GAMESCOPE_SCREEN_TYPE_COUNT ++ }; ++} ++ +diff --git a/src/main.cpp b/src/main.cpp +index 5be060bf0..5bbb01bde 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -341,13 +341,18 @@ static std::string build_optstring(const struct option *options) + return optstring; + } + +-static enum drm_mode_generation parse_drm_mode_generation(const char *str) ++static gamescope::GamescopeModeGeneration parse_gamescope_mode_generation( const char *str ) + { +- if (strcmp(str, "cvt") == 0) { +- return DRM_MODE_GENERATE_CVT; +- } else if (strcmp(str, "fixed") == 0) { +- return DRM_MODE_GENERATE_FIXED; +- } else { ++ 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); + } +@@ -594,8 +599,6 @@ int main(int argc, char **argv) + if (strcmp(opt_name, "help") == 0) { + fprintf(stderr, "%s", usage); + return 0; +- } else if (strcmp(opt_name, "disable-layers") == 0) { +- g_bUseLayers = false; + } else if (strcmp(opt_name, "debug-layers") == 0) { + g_bDebugLayers = true; + } else if (strcmp(opt_name, "disable-color-management") == 0) { +@@ -611,7 +614,7 @@ int main(int argc, char **argv) + g_nDefaultTouchClickMode = (enum wlserver_touch_click_mode) atoi( optarg ); + g_nTouchClickMode = g_nDefaultTouchClickMode; + } else if (strcmp(opt_name, "generate-drm-mode") == 0) { +- g_drmModeGeneration = parse_drm_mode_generation( optarg ); ++ g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg ); + } else if (strcmp(opt_name, "force-orientation") == 0) { + g_drmModeOrientation = force_orientation( optarg ); + } else if (strcmp(opt_name, "sharpness") == 0 || +diff --git a/src/modegen.cpp b/src/modegen.cpp +index 197641c21..d174c2d21 100644 +--- a/src/modegen.cpp ++++ b/src/modegen.cpp +@@ -312,15 +312,16 @@ unsigned int get_galileo_vfp( int vrefresh, unsigned int * vfp_array, unsigned i + return 0; + } + +-void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int vrefresh, +- bool use_tuned_clocks, unsigned int use_vfp ) ++void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int vrefresh, gamescope::GamescopeKnownDisplays eKnownDisplay ) + { + *mode = *base; + if (!vrefresh) + vrefresh = 60; +- if ( use_vfp ) { +- unsigned int vfp, vsync, vbp = 0; +- if (GALILEO_SDC_PID == use_vfp) { ++ if ( eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC || ++ eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE ) { ++ unsigned int vfp = 0, vsync = 0, vbp = 0; ++ if ( eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC ) ++ { + vfp = get_galileo_vfp( vrefresh, galileo_sdc_vfp, ARRAY_SIZE(galileo_sdc_vfp) ); + // if we did not find a matching rate then we default to 60 Hz + if ( !vfp ) { +@@ -329,7 +330,7 @@ void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int + } + vsync = GALILEO_SDC_VSYNC; + vbp = GALILEO_SDC_VBP; +- } else { // BOE Panel ++ } else if ( eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE ) { // BOE Panel + vfp = get_galileo_vfp( vrefresh, galileo_boe_vfp, ARRAY_SIZE(galileo_boe_vfp) ); + // if we did not find a matching rate then we default to 60 Hz + if ( !vfp ) { +@@ -338,12 +339,12 @@ void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int + } + vsync = GALILEO_BOE_VSYNC; + vbp = GALILEO_BOE_VBP; +- } ++ } + mode->vsync_start = mode->vdisplay + vfp; + mode->vsync_end = mode->vsync_start + vsync; + mode->vtotal = mode->vsync_end + vbp; + } else { +- if ( use_tuned_clocks ) ++ if ( eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) + { + mode->hdisplay = 800; + mode->hsync_start = 840; +diff --git a/src/modegen.hpp b/src/modegen.hpp +index 2513d34e9..010d9cab3 100644 +--- a/src/modegen.hpp ++++ b/src/modegen.hpp +@@ -5,8 +5,9 @@ + #include + #include + #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, bool use_tuned_clocks, unsigned int use_vfp); ++ int vrefresh, gamescope::GamescopeKnownDisplays eKnownDisplay); +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index c24ba58ba..f63222f9d 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -328,7 +328,7 @@ int g_nAsyncFlipsEnabled = 0; + int g_nSteamMaxHeight = 0; + bool g_bVRRCapable_CachedValue = false; + bool g_bVRRInUse_CachedValue = false; +-bool g_bSupportsST2084_CachedValue = false; ++bool g_bSupportsHDR_CachedValue = false; + bool g_bForceHDR10OutputDebug = false; + bool g_bHDREnabled = false; + bool g_bHDRItmEnable = false; +@@ -505,8 +505,8 @@ bool set_color_mgmt_enabled( bool bEnabled ) + return true; + } + +-static std::shared_ptr s_MuraCorrectionImage[DRM_SCREEN_TYPE_COUNT]; +-static std::shared_ptr s_MuraCTMBlob[DRM_SCREEN_TYPE_COUNT]; ++static std::shared_ptr 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; + +@@ -517,8 +517,8 @@ bool is_mura_correction_enabled() + + void update_mura_ctm() + { +- s_MuraCTMBlob[DRM_SCREEN_TYPE_INTERNAL] = nullptr; +- if (s_MuraCorrectionImage[DRM_SCREEN_TYPE_INTERNAL] == nullptr) ++ s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = nullptr; ++ if (s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] == nullptr) + return; + + static constexpr float kMuraMapScale = 0.0625f; +@@ -533,7 +533,7 @@ void update_mura_ctm() + 0, flScale, 0, kMuraOffset * flScale, + 0, 0, 0, 0, // No mura comp for blue channel. + }; +- s_MuraCTMBlob[DRM_SCREEN_TYPE_INTERNAL] = drm_create_ctm(&g_DRM, mura_scale_offset); ++ s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = drm_create_ctm(&g_DRM, mura_scale_offset); + } + + bool g_bMuraDebugFullColor = false; +@@ -541,7 +541,7 @@ 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[DRM_SCREEN_TYPE_INTERNAL] = nullptr; ++ s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = nullptr; + update_mura_ctm(); + + std::string red_path = std::string(path) + "_red.png"; +@@ -577,7 +577,7 @@ bool set_mura_overlay( const char *path ) + CVulkanTexture::createFlags texCreateFlags; + texCreateFlags.bFlippable = !BIsNested(); + texCreateFlags.bSampled = true; +- s_MuraCorrectionImage[DRM_SCREEN_TYPE_INTERNAL] = vulkan_create_texture_from_bits(w, h, w, h, DRM_FORMAT_ABGR8888, texCreateFlags, (void*)data); ++ 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!"); +@@ -912,26 +912,26 @@ bool g_bCurrentBasePlaneIsFifo = false; + + static int g_nSteamCompMgrTargetFPS = 0; + static uint64_t g_uDynamicRefreshEqualityTime = 0; +-static int g_nDynamicRefreshRate[DRM_SCREEN_TYPE_COUNT] = { 0, 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[DRM_SCREEN_TYPE_COUNT] = { 0, 0 }; ++static int g_nCombinedAppRefreshCycleOverride[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT] = { 0, 0 }; + + static void _update_app_target_refresh_cycle() + { + if ( BIsNested() ) + { +- g_nDynamicRefreshRate[ DRM_SCREEN_TYPE_INTERNAL ] = 0; +- g_nSteamCompMgrTargetFPS = g_nCombinedAppRefreshCycleOverride[ DRM_SCREEN_TYPE_INTERNAL ]; ++ g_nDynamicRefreshRate[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = 0; ++ g_nSteamCompMgrTargetFPS = g_nCombinedAppRefreshCycleOverride[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ]; + return; + } + +- static drm_screen_type last_type; ++ static gamescope::GamescopeScreenType last_type; + static int last_target_fps; + static bool first = true; + +- drm_screen_type type = drm_get_screen_type( &g_DRM ); ++ gamescope::GamescopeScreenType type = drm_get_screen_type( &g_DRM ); + int target_fps = g_nCombinedAppRefreshCycleOverride[type]; + + if ( !first && type == last_type && last_target_fps == target_fps ) +@@ -975,7 +975,7 @@ static void update_app_target_refresh_cycle() + update_runtime_info(); + } + +-void steamcompmgr_set_app_refresh_cycle_override( drm_screen_type type, int override_fps ) ++void steamcompmgr_set_app_refresh_cycle_override( gamescope::GamescopeScreenType type, int override_fps ) + { + g_nCombinedAppRefreshCycleOverride[ type ] = override_fps; + update_app_target_refresh_cycle(); +@@ -2950,8 +2950,6 @@ paint_all(bool async) + + xwm_log.errorf("Failed to prepare 1-layer flip (%s), trying again with previous mode if modeset needed", strerror( -ret )); + +- drm_rollback( &g_DRM ); +- + // Try once again to in case we need to fall back to another mode. + ret = drm_prepare( &g_DRM, async, &compositeFrameInfo ); + +@@ -3144,7 +3142,7 @@ paint_all(bool async) + + if ( !maxCLLNits && !maxFALLNits ) + { +- drm_supports_st2084( &g_DRM, &maxCLLNits, &maxFALLNits ); ++ drm_supports_hdr( &g_DRM, &maxCLLNits, &maxFALLNits ); + } + + if ( !maxCLLNits && !maxFALLNits ) +@@ -5832,7 +5830,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + g_nSteamCompMgrTargetFPS = get_prop( ctx, ctx->root, ctx->atoms.gamescopeFPSLimit, 0 ); + update_runtime_info(); + } +- for (int i = 0; i < DRM_SCREEN_TYPE_COUNT; i++) ++ for (int i = 0; i < gamescope::GAMESCOPE_SCREEN_TYPE_COUNT; i++) + { + if ( ev->atom == ctx->atoms.gamescopeDynamicRefresh[i] ) + { +@@ -6131,24 +6129,24 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + g_ColorMgmt.pending.chromaticAdaptationMode = ( EChromaticAdaptationMethod ) val; + } + // TODO: Hook up gamescopeColorMuraCorrectionImage for external. +- if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionImage[DRM_SCREEN_TYPE_INTERNAL] ) ++ if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) + { +- std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorMuraCorrectionImage[DRM_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[DRM_SCREEN_TYPE_INTERNAL] ) ++ if ( ev->atom == ctx->atoms.gamescopeColorMuraScale[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) + { +- uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorMuraScale[DRM_SCREEN_TYPE_INTERNAL], 0); ++ 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[DRM_SCREEN_TYPE_INTERNAL] ) ++ if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionDisabled[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) + { +- bool disabled = !!get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorMuraCorrectionDisabled[DRM_SCREEN_TYPE_INTERNAL], 0); ++ 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; +@@ -7425,8 +7423,8 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + + ctx->atoms.gamescopeXWaylandModeControl = XInternAtom( ctx->dpy, "GAMESCOPE_XWAYLAND_MODE_CONTROL", false ); + ctx->atoms.gamescopeFPSLimit = XInternAtom( ctx->dpy, "GAMESCOPE_FPS_LIMIT", false ); +- ctx->atoms.gamescopeDynamicRefresh[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH", false ); +- ctx->atoms.gamescopeDynamicRefresh[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH_EXTERNAL", 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 ); +@@ -7490,12 +7488,12 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + 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[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_IMAGE", false ); +- ctx->atoms.gamescopeColorMuraCorrectionImage[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_IMAGE_EXTERNAL", false ); +- ctx->atoms.gamescopeColorMuraScale[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_SCALE", false ); +- ctx->atoms.gamescopeColorMuraScale[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_SCALE_EXTERNAL", false ); +- ctx->atoms.gamescopeColorMuraCorrectionDisabled[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_DISABLED", false ); +- ctx->atoms.gamescopeColorMuraCorrectionDisabled[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_DISABLED_EXTERNAL", 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 ); +@@ -7597,13 +7595,13 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = + *needs_flush = true; + } + +- bool st2084 = BIsNested() ? vulkan_supports_hdr10() : drm_supports_st2084( &g_DRM ); +- if ( st2084 != g_bSupportsST2084_CachedValue || force ) ++ bool HDR = BIsNested() ? vulkan_supports_hdr10() : drm_supports_hdr( &g_DRM ); ++ if ( HDR != g_bSupportsHDR_CachedValue || force ) + { +- uint32_t hdr_value = st2084 ? 1 : 0; ++ 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_bSupportsST2084_CachedValue = st2084; ++ g_bSupportsHDR_CachedValue = HDR; + if (needs_flush) + *needs_flush = true; + } +@@ -7646,7 +7644,7 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) + if (needs_flush) + *needs_flush = true; + +- if ( drm_get_screen_type(&g_DRM) == DRM_SCREEN_TYPE_INTERNAL ) ++ if ( drm_get_screen_type(&g_DRM) == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + { + XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayModeListExternal); + +@@ -7656,12 +7654,17 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) + return; + } + ++ if ( !g_DRM.pConnector || !g_DRM.pConnector->GetModeConnector() ) ++ { ++ return; ++ } ++ + char modes[4096] = ""; + int remaining_size = sizeof(modes) - 1; + int len = 0; +- for (int i = 0; remaining_size > 0 && i < g_DRM.connector->connector->count_modes; i++) ++ for (int i = 0; remaining_size > 0 && i < g_DRM.pConnector->GetModeConnector()->count_modes; i++) + { +- const auto& mode = g_DRM.connector->connector->modes[i]; ++ const auto& mode = g_DRM.pConnector->GetModeConnector()->modes[i]; + int mode_len = snprintf(&modes[len], remaining_size, "%s%dx%d@%d", + i == 0 ? "" : " ", + int(mode.hdisplay), int(mode.vdisplay), int(mode.vrefresh)); +@@ -7974,7 +7977,7 @@ steamcompmgr_main(int argc, char **argv) + } + } + +- g_bOutputHDREnabled = (g_bSupportsST2084_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; ++ g_bOutputHDREnabled = (g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; + + // 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 +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index 16ef13273..d1a209d48 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -172,4 +172,4 @@ MouseCursor *steamcompmgr_get_server_cursor(uint32_t serverId); + + extern int g_nAsyncFlipsEnabled; + +-extern void steamcompmgr_set_app_refresh_cycle_override( drm_screen_type type, int override_fps ); ++extern void steamcompmgr_set_app_refresh_cycle_override( gamescope::GamescopeScreenType type, int override_fps ); +diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp +index b62fc9023..4b617584e 100644 +--- a/src/vblankmanager.cpp ++++ b/src/vblankmanager.cpp +@@ -112,7 +112,7 @@ namespace gamescope + + VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) + { +- const drm_screen_type eScreenType = drm_get_screen_type( &g_DRM ); ++ const GamescopeScreenType eScreenType = drm_get_screen_type( &g_DRM ); + + const int nRefreshRate = GetRefresh(); + const uint64_t ulRefreshInterval = kSecInNanoSecs / nRefreshRate; +@@ -125,7 +125,7 @@ namespace gamescope + // 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 == DRM_SCREEN_TYPE_INTERNAL ++ const uint64_t ulRedZone = eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL + ? m_ulVBlankDrawBufferRedZone + : ( m_ulVBlankDrawBufferRedZone * 60 * kSecInNanoSecs ) / ( nRefreshRate * kSecInNanoSecs ); + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 3fbc4ff58..2eb0f9c43 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -875,9 +875,9 @@ static void create_gamescope_pipewire( void ) + + 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 = DRM_SCREEN_TYPE_EXTERNAL; ++ auto display_type = gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL; + if ( flags & GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_INTERNAL_DISPLAY ) +- display_type = DRM_SCREEN_TYPE_INTERNAL; ++ display_type = gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; + + steamcompmgr_set_app_refresh_cycle_override( display_type, fps ); + } +@@ -1956,7 +1956,7 @@ static void apply_touchscreen_orientation(double *x, double *y ) + double ty = 0; + + // Use internal screen always for orientation purposes. +- switch ( g_drmEffectiveOrientation[DRM_SCREEN_TYPE_INTERNAL] ) ++ switch ( g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) + { + default: + case DRM_MODE_ROTATE_0: +@@ -1987,8 +1987,8 @@ int get_effective_touch_mode() + { + if (!BIsNested() && g_bTrackpadTouchExternalDisplay) + { +- drm_screen_type screenType = drm_get_screen_type(&g_DRM); +- if ( screenType == DRM_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) ++ gamescope::GamescopeScreenType screenType = drm_get_screen_type(&g_DRM); ++ if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) + return WLSERVER_TOUCH_CLICK_TRACKPAD; + } + +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 11f9335de..c558387e6 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -149,7 +149,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + Atom gamescopeXWaylandModeControl; + + Atom gamescopeFPSLimit; +- Atom gamescopeDynamicRefresh[DRM_SCREEN_TYPE_COUNT]; ++ Atom gamescopeDynamicRefresh[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; + Atom gamescopeLowLatency; + + Atom gamescopeFSRFeedback; +@@ -216,9 +216,9 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + Atom gamescopeColorAppHDRMetadataFeedback; + Atom gamescopeColorSliderInUse; + Atom gamescopeColorChromaticAdaptationMode; +- Atom gamescopeColorMuraCorrectionImage[DRM_SCREEN_TYPE_COUNT]; +- Atom gamescopeColorMuraScale[DRM_SCREEN_TYPE_COUNT]; +- Atom gamescopeColorMuraCorrectionDisabled[DRM_SCREEN_TYPE_COUNT]; ++ 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; + +From 7b126cb383c53118ff1f37b8f64874dc172a3623 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 7 Jan 2024 01:17:57 +0000 +Subject: [PATCH 067/134] rendervulkan: Add GAMESCOPE_FORCE_GENERAL_QUEUE + +--- + src/rendervulkan.cpp | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 3dbbfe069..96e0ea53f 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -269,6 +269,8 @@ bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface) + return true; + } + ++extern bool env_to_bool(const char *env); ++ + bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) + { + uint32_t deviceCount = 0; +@@ -339,6 +341,9 @@ bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) + m_queueFamily = computeOnlyIndex == ~0u ? generalIndex : computeOnlyIndex; + m_generalQueueFamily = generalIndex; + m_physDev = cphysDev; ++ ++ if ( env_to_bool( "GAMESCOPE_FORCE_GENERAL_QUEUE" ) ) ++ m_queueFamily = generalIndex; + } + } + } +@@ -552,7 +557,7 @@ bool CVulkanDevice::createDevice() + VkDeviceCreateInfo deviceCreateInfo = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &features2, +- .queueCreateInfoCount = std::size(queueCreateInfos), ++ .queueCreateInfoCount = m_queueFamily == m_generalQueueFamily ? 1u : 2u, + .pQueueCreateInfos = queueCreateInfos, + .enabledExtensionCount = (uint32_t)enabledExtensions.size(), + .ppEnabledExtensionNames = enabledExtensions.data(), +@@ -606,7 +611,10 @@ bool CVulkanDevice::createDevice() + #undef VK_FUNC + + vk.GetDeviceQueue(device(), m_queueFamily, 0, &m_queue); +- vk.GetDeviceQueue(device(), m_generalQueueFamily, 0, &m_generalQueue); ++ if ( m_queueFamily == m_generalQueueFamily ) ++ m_queue = m_generalQueue; ++ else ++ vk.GetDeviceQueue(device(), m_generalQueueFamily, 0, &m_generalQueue); + + return true; + } + +From db714154452c43af24fdb544bbdaf80a6c34cf79 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 7 Jan 2024 01:22:59 +0000 +Subject: [PATCH 068/134] rendervulkan: Fix general queue assignment + +--- + src/rendervulkan.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 96e0ea53f..a2e054f46 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -612,7 +612,7 @@ bool CVulkanDevice::createDevice() + + vk.GetDeviceQueue(device(), m_queueFamily, 0, &m_queue); + if ( m_queueFamily == m_generalQueueFamily ) +- m_queue = m_generalQueue; ++ m_generalQueue = m_queue; + else + vk.GetDeviceQueue(device(), m_generalQueueFamily, 0, &m_generalQueue); + + +From 8bc99b78b2b386c162603d4f179882a697784e11 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 7 Jan 2024 01:23:26 +0000 +Subject: [PATCH 069/134] rendervulkan: Fix getenv call for + GAMESCOPE_FORCE_GENERAL_QUEUE + +--- + src/rendervulkan.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index a2e054f46..f31d8a931 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -342,7 +342,7 @@ bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) + m_generalQueueFamily = generalIndex; + m_physDev = cphysDev; + +- if ( env_to_bool( "GAMESCOPE_FORCE_GENERAL_QUEUE" ) ) ++ if ( env_to_bool( getenv( "GAMESCOPE_FORCE_GENERAL_QUEUE" ) ) ) + m_queueFamily = generalIndex; + } + } + +From 1a008fafbd6cb5291eda160b9dcbfa1c13785921 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 7 Jan 2024 01:33:21 +0000 +Subject: [PATCH 070/134] steamcompmgr: Don't scale cursor if no set cursor + scale height + +Fixes: #1054 +--- + src/steamcompmgr.cpp | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index f63222f9d..0091f5dd3 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -1864,8 +1864,12 @@ bool MouseCursor::getTexture() + m_hotspotX = image->xhot; + m_hotspotY = image->yhot; + +- int nDesiredWidth, nDesiredHeight; +- GetDesiredSize( nDesiredWidth, nDesiredHeight ); ++ int nDesiredWidth = image->width; ++ int nDesiredHeight = image->height; ++ if ( g_nCursorScaleHeight > 0 ) ++ { ++ GetDesiredSize( nDesiredWidth, nDesiredHeight ); ++ } + + uint32_t surfaceWidth; + uint32_t surfaceHeight; + +From 58509f03ba00f7adaca77b27515e585412e440cc Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 7 Jan 2024 01:38:49 +0000 +Subject: [PATCH 071/134] main: Only set XCURSOR_SIZE if cursor scale height is + set + +--- + src/main.cpp | 10 +++++++++- + src/steamcompmgr.cpp | 4 +--- + 2 files changed, 10 insertions(+), 4 deletions(-) + +diff --git a/src/main.cpp b/src/main.cpp +index 5bbb01bde..220ee1583 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -45,6 +45,8 @@ const char *gamescope_optstring = nullptr; + const char *g_pOriginalDisplay = nullptr; + const char *g_pOriginalWaylandDisplay = nullptr; + ++int g_nCursorScaleHeight = -1; ++ + const struct option *gamescope_options = (struct option[]){ + { "help", no_argument, nullptr, 0 }, + { "nested-width", required_argument, nullptr, 'w' }, +@@ -639,6 +641,8 @@ int main(int argc, char **argv) + } else if (strcmp(opt_name, "headless") == 0) { + g_bHeadless = true; + g_bIsNested = true; ++ } else if (strcmp(opt_name, "cursor-scale-height") == 0) { ++ g_nCursorScaleHeight = atoi(optarg); + } + #if HAVE_OPENVR + else if (strcmp(opt_name, "openvr") == 0) { +@@ -700,7 +704,11 @@ int main(int argc, char **argv) + #endif + + setenv( "XWAYLAND_FORCE_ENABLE_EXTRA_MODES", "1", 1 ); +- setenv( "XCURSOR_SIZE", "256", 1 ); ++ ++ if ( g_nCursorScaleHeight > 0 ) ++ { ++ setenv( "XCURSOR_SIZE", "256", 1 ); ++ } + + raise_fd_limit(); + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 0091f5dd3..177530c70 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -1104,7 +1104,7 @@ namespace gamescope + + static std::atomic g_bForceRepaint{false}; + +-static int g_nCursorScaleHeight = -1; ++extern int g_nCursorScaleHeight; + + // poor man's semaphore + class sem +@@ -7797,8 +7797,6 @@ steamcompmgr_main(int argc, char **argv) + g_FadeOutDuration = atoi(optarg); + } else if (strcmp(opt_name, "force-windows-fullscreen") == 0) { + bForceWindowsFullscreen = true; +- } else if (strcmp(opt_name, "cursor-scale-height") == 0) { +- g_nCursorScaleHeight = atoi(optarg); + } else if (strcmp(opt_name, "hdr-enabled") == 0) { + g_bHDREnabled = true; + } else if (strcmp(opt_name, "hdr-debug-force-support") == 0) { + +From 20cac0d3356c8a83afa8a5001e771c26fb9a5424 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 10 Jan 2024 01:06:32 +0000 +Subject: [PATCH 072/134] steamcompmgr: Add back + GAMESCOPECTRL_DEBUG_REQUEST_SCREENSHOT + +Short term debugging +--- + src/steamcompmgr.cpp | 20 ++++++++++++++++++++ + src/xwayland_ctx.hpp | 1 + + 2 files changed, 21 insertions(+) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 177530c70..7c8d2eeaf 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -3307,7 +3307,10 @@ paint_all(bool async) + } + + 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 ) + { +@@ -3326,7 +3329,10 @@ paint_all(bool async) + { + 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 ); ++ } + } + } + +@@ -5609,6 +5615,19 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + } ); + } + } ++ 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); +@@ -7412,6 +7431,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + 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); +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index c558387e6..229a0884a 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -134,6 +134,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + Atom gamescopeCtrlWindowAtom; + Atom gamescopeInputCounterAtom; + Atom gamescopeScreenShotAtom; ++ Atom gamescopeDebugScreenShotAtom; + + Atom gamescopeFocusDisplay; + Atom gamescopeMouseFocusDisplay; + +From 7fcfbf00f4bde7cefc6ab03966dea4371ee0b44c Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Fri, 12 Jan 2024 16:33:34 +0000 +Subject: [PATCH 073/134] rendervulkan: Better logging/handling of device + loss/fatal errors + +Log vk result and such so we can track what happened. +--- + src/rendervulkan.cpp | 35 ++++++++++++++++++----------------- + 1 file changed, 18 insertions(+), 17 deletions(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index f31d8a931..8194ca2bc 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -116,6 +116,18 @@ static void vk_errorf(VkResult result, const char *fmt, ...) { + 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 ) ++ + template + Target *pNextFind(const Base *base, VkStructureType sType) + { +@@ -1217,12 +1229,7 @@ uint64_t CVulkanDevice::submitInternal( CVulkanCmdBuffer* cmdBuffer ) + .pSignalSemaphores = &m_scratchTimelineSemaphore, + }; + +- VkResult res = vk.QueueSubmit( cmdBuffer->queue(), 1, &submitInfo, VK_NULL_HANDLE ); +- +- if ( res != VK_SUCCESS ) +- { +- assert( 0 ); +- } ++ vk_check( vk.QueueSubmit( cmdBuffer->queue(), 1, &submitInfo, VK_NULL_HANDLE ) ); + + return nextSeqNo; + } +@@ -1237,8 +1244,7 @@ uint64_t CVulkanDevice::submit( std::unique_ptr cmdBuffer) + void CVulkanDevice::garbageCollect( void ) + { + uint64_t currentSeqNo; +- VkResult res = vk.GetSemaphoreCounterValue(device(), m_scratchTimelineSemaphore, ¤tSeqNo); +- assert( res == VK_SUCCESS ); ++ vk_check( vk.GetSemaphoreCounterValue(device(), m_scratchTimelineSemaphore, ¤tSeqNo) ); + + resetCmdBuffers(currentSeqNo); + } +@@ -1255,9 +1261,7 @@ void CVulkanDevice::wait(uint64_t sequence, bool reset) + .pValues = &sequence, + } ; + +- VkResult res = vk.WaitSemaphores(device(), &waitInfo, ~0ull); +- if (res != VK_SUCCESS) +- assert( 0 ); ++ vk_check( vk.WaitSemaphores( device(), &waitInfo, ~0ull ) ); + + if (reset) + resetCmdBuffers(sequence); +@@ -1297,8 +1301,7 @@ CVulkanCmdBuffer::~CVulkanCmdBuffer() + + void CVulkanCmdBuffer::reset() + { +- VkResult res = m_device->vk.ResetCommandBuffer(m_cmdBuffer, 0); +- assert(res == VK_SUCCESS); ++ vk_check( m_device->vk.ResetCommandBuffer(m_cmdBuffer, 0) ); + m_textureRefs.clear(); + m_textureState.clear(); + } +@@ -1310,8 +1313,7 @@ void CVulkanCmdBuffer::begin() + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + +- VkResult res = m_device->vk.BeginCommandBuffer(m_cmdBuffer, &commandBufferBeginInfo); +- assert(res == VK_SUCCESS); ++ vk_check( m_device->vk.BeginCommandBuffer(m_cmdBuffer, &commandBufferBeginInfo) ); + + clearState(); + } +@@ -1319,8 +1321,7 @@ void CVulkanCmdBuffer::begin() + void CVulkanCmdBuffer::end() + { + insertBarrier(true); +- VkResult res = m_device->vk.EndCommandBuffer(m_cmdBuffer); +- assert(res == VK_SUCCESS); ++ vk_check( m_device->vk.EndCommandBuffer(m_cmdBuffer) ); + } + + void CVulkanCmdBuffer::bindTexture(uint32_t slot, std::shared_ptr texture) + +From 52c8cd7e852ff207dd6b6acf875dc53a4677e9c7 Mon Sep 17 00:00:00 2001 +From: Jeremy Selan +Date: Wed, 10 Jan 2024 14:46:08 -0800 +Subject: [PATCH 074/134] [color_helpers]: do not do color remapping for PQ + source content + +Fixes a bug where HDR colors were not being rendered faithfully at the edges of the color gamut. + +Thanks to JDSP for reporting. +--- + src/color_helpers.cpp | 24 ++++++------------------ + 1 file changed, 6 insertions(+), 18 deletions(-) + +diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp +index 7a6e046f3..4ec148a65 100644 +--- a/src/color_helpers.cpp ++++ b/src/color_helpers.cpp +@@ -866,24 +866,12 @@ void buildPQColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *pM + { + *pColorimetry = displaycolorimetry_2020; + +- if ( BIsWideGamut( nativeDisplayOutput) ) +- { +- colormapping_t smoothRemap; +- smoothRemap.blendEnableMinSat = 0.7f; +- smoothRemap.blendEnableMaxSat = 1.0f; +- smoothRemap.blendAmountMin = 0.0f; +- smoothRemap.blendAmountMax = 1.f; +- *pMapping = smoothRemap; +- } +- else +- { +- colormapping_t noRemap; +- noRemap.blendEnableMinSat = 0.7f; +- noRemap.blendEnableMaxSat = 1.0f; +- noRemap.blendAmountMin = 0.0f; +- noRemap.blendAmountMax = 0.0f; +- *pMapping = noRemap; +- } ++ colormapping_t noRemap; ++ noRemap.blendEnableMinSat = 0.0f; ++ noRemap.blendEnableMaxSat = 1.0f; ++ noRemap.blendAmountMin = 0.0f; ++ noRemap.blendAmountMax = 0.0f; ++ *pMapping = noRemap; + } + + bool approxEqual( const glm::vec3 & a, const glm::vec3 & b, float flTolerance = 1e-5f ) + +From 308a0638e5ac39d92aa01a5f31c7a1c3cca9d3c0 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Fri, 19 Jan 2024 23:55:11 +0000 +Subject: [PATCH 075/134] steamcompmgr: Fix rare crash in + handle_presented_for_window + +--- + src/steamcompmgr.cpp | 12 +++++------- + 1 file changed, 5 insertions(+), 7 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 7c8d2eeaf..94880c82c 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -6670,6 +6670,8 @@ void handle_done_commits_xdg() + + 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 ) +@@ -6681,8 +6683,6 @@ void handle_presented_for_window( steamcompmgr_win_t* w ) + { + if (!lastCommit->presentation_feedbacks.empty() || lastCommit->present_id) + { +- wlserver_lock(); +- + if (!lastCommit->presentation_feedbacks.empty()) + { + wlserver_presentation_feedback_presented( +@@ -6703,17 +6703,14 @@ void handle_presented_for_window( steamcompmgr_win_t* w ) + lastCommit->present_margin); + lastCommit->present_id = std::nullopt; + } +- +- wlserver_unlock(); + } + } + + if (struct wlr_surface *surface = w->current_surface()) + { + auto info = get_wl_surface_info(surface); +- if (info->gamescope_swapchain != nullptr && info->last_refresh_cycle != refresh_cycle) ++ if (info != nullptr && info->gamescope_swapchain != nullptr && info->last_refresh_cycle != refresh_cycle) + { +- wlserver_lock(); + if (info->gamescope_swapchain != nullptr) + { + // Could have got the override set in this bubble.s +@@ -6725,7 +6722,6 @@ void handle_presented_for_window( steamcompmgr_win_t* w ) + wlserver_refresh_cycle(surface, refresh_cycle); + } + } +- wlserver_unlock(); + } + } + } +@@ -8147,9 +8143,11 @@ steamcompmgr_main(int argc, char **argv) + + 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(); + } + + // + +From 868206c1e89bcc1069e1129755f834d62e987732 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sat, 20 Jan 2024 00:36:52 +0000 +Subject: [PATCH 076/134] rendervulkan: Add VkExternalMemoryImageCreateInfo for + any flippable surface + +Fixes some corruption on NVIDIA +--- + src/rendervulkan.cpp | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 8194ca2bc..4715970db 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -1965,6 +1965,12 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + .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; + } + + +From 8a6fc5c5f539439e141da00eccdda885439b8127 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sat, 20 Jan 2024 00:50:38 +0000 +Subject: [PATCH 077/134] steamcompmgr: Limit desired size by drm cursor size + +--- + src/steamcompmgr.cpp | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 94880c82c..1bbc1dce8 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -1991,6 +1991,12 @@ void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) + nSize = std::clamp( nSize, g_nBaseCursorScale, 256 ); + } + ++ if ( BIsNested() == false && alwaysComposite == false ) ++ { ++ nSize = std::min( nSize, g_DRM.cursor_width ); ++ nSize = std::min( nSize, g_DRM.cursor_height ); ++ } ++ + nWidth = nSize; + nHeight = nSize; + } + +From d61dba14be618384a9f824c5d91f81dcc614725d Mon Sep 17 00:00:00 2001 +From: Sefa Eyeoglu +Date: Tue, 19 Dec 2023 11:43:23 +0100 +Subject: [PATCH 078/134] vr_session: update to OpenVR 2 + +Signed-off-by: Sefa Eyeoglu +--- + src/vr_session.cpp | 3 --- + subprojects/openvr | 2 +- + 2 files changed, 1 insertion(+), 4 deletions(-) + +diff --git a/src/vr_session.cpp b/src/vr_session.cpp +index 439a54f07..8696b62d2 100644 +--- a/src/vr_session.cpp ++++ b/src/vr_session.cpp +@@ -116,9 +116,6 @@ bool vr_init(int argc, char **argv) + // Not in public headers yet. + namespace vr + { +- const VROverlayFlags VROverlayFlags_EnableControlBar = (VROverlayFlags)(1 << 23); +- const VROverlayFlags VROverlayFlags_EnableControlBarKeyboard = (VROverlayFlags)(1 << 24); +- const VROverlayFlags VROverlayFlags_EnableControlBarClose = (VROverlayFlags)(1 << 25); + const VROverlayFlags VROverlayFlags_EnableControlBarSteamUI = (VROverlayFlags)(1 << 26); + + const EVRButtonId k_EButton_Steam = (EVRButtonId)(50); +diff --git a/subprojects/openvr b/subprojects/openvr +index 1a0ea2664..15f0838a0 160000 +--- a/subprojects/openvr ++++ b/subprojects/openvr +@@ -1 +1 @@ +-Subproject commit 1a0ea26642e517824b66871e6a12280a426cfec3 ++Subproject commit 15f0838a0487feb7da60acd39aab8099b994234c + +From b050e3d0d1e1f395ba47c241fde7cc0c06f85e81 Mon Sep 17 00:00:00 2001 +From: tcoyvwac <53616399+tcoyvwac@users.noreply.github.com> +Date: Tue, 3 Oct 2023 14:48:30 +0200 +Subject: [PATCH 079/134] reshade: Break function into smaller chunks & common + params + +* Lambdas use common variable array name +--- + src/reshade_effect_manager.cpp | 74 +++++++++++++++++++++------------- + 1 file changed, 45 insertions(+), 29 deletions(-) + +diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp +index 3597ca12d..697974997 100644 +--- a/src/reshade_effect_manager.cpp ++++ b/src/reshade_effect_manager.cpp +@@ -199,71 +199,87 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) + + uint32_t zero_data[16] = {}; + +- switch (m_info.type.base) +- { +- case reshadefx::type::t_bool: ++ const auto inner_bool = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ + if (thing) + { +- VkBool32 VkBool32_stuff[16]; ++ decltype(temp_type) array_stuff[16]; + for (uint32_t i = 0; i < m_info.type.components(); i++) +- VkBool32_stuff[i] = !!thing[i]; +- copy(mappedBuffer, VkBool32_stuff, sizeof(VkBool32) * m_info.type.components()); ++ array_stuff[i] = pred(thing[i]); ++ copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); + } + else + { + if (m_info.has_initializer_value) +- copy(mappedBuffer, (const void*)&m_info.initializer_value.as_uint[0], sizeof(VkBool32) * m_info.type.components() ); ++ copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); + else +- copy(mappedBuffer, (const void*)zero_data, sizeof(VkBool32) * m_info.type.components() ); ++ copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); + } +- break; +- case reshadefx::type::t_int: ++ }; ++ const auto inner_int = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ + if (thing) + { +- int32_t int32_t_stuff[16]; ++ decltype(temp_type) array_stuff[16]; + for (uint32_t i = 0; i < m_info.type.components(); i++) +- int32_t_stuff[i] = thing[i]; +- copy(mappedBuffer, int32_t_stuff, sizeof(int32_t) * m_info.type.components()); ++ array_stuff[i] = pred(thing[i]); ++ copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); + } + else + { + if (m_info.has_initializer_value) +- copy(mappedBuffer, (const void*)&m_info.initializer_value.as_int[0], sizeof(int32_t) * m_info.type.components() ); ++ copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); + else +- copy(mappedBuffer, (const void*)zero_data, sizeof(int32_t) * m_info.type.components() ); ++ copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); + } +- break; +- case reshadefx::type::t_uint: ++ }; ++ const auto inner_uint = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ + if (thing) + { +- uint32_t uint32_t_stuff[16]; ++ decltype(temp_type) array_stuff[16]; + for (uint32_t i = 0; i < m_info.type.components(); i++) +- uint32_t_stuff[i] = thing[i]; +- copy(mappedBuffer, uint32_t_stuff, sizeof(uint32_t) * m_info.type.components()); ++ array_stuff[i] = pred(thing[i]); ++ copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); + } + else + { + if (m_info.has_initializer_value) +- copy(mappedBuffer, (const void*)&m_info.initializer_value.as_uint[0], sizeof(uint32_t) * m_info.type.components() ); ++ copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); + else +- copy(mappedBuffer, (const void*)zero_data, sizeof(uint32_t) * m_info.type.components() ); ++ copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); + } +- break; +- case reshadefx::type::t_float: ++ }; ++ const auto inner_float = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ + if (thing) + { +- float float_stuff[16]; ++ decltype(temp_type) array_stuff[16]; + for (uint32_t i = 0; i < m_info.type.components(); i++) +- float_stuff[i] = thing[i]; +- copy(mappedBuffer, float_stuff, sizeof(float) * m_info.type.components()); ++ array_stuff[i] = pred(thing[i]); ++ copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); + } + else + { + if (m_info.has_initializer_value) +- copy(mappedBuffer, (const void*)&m_info.initializer_value.as_float[0], sizeof(float) * m_info.type.components() ); ++ copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); + else +- copy(mappedBuffer, (const void*)zero_data, sizeof(float) * m_info.type.components() ); ++ copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); + } ++ }; ++ ++ const auto double_negate = [](const T item){return !!(item);}; ++ const auto id = [](const T item){return item;}; // C++20 = std::identity ++ ++ switch (m_info.type.base) ++ { ++ case reshadefx::type::t_bool: ++ inner_bool(VkBool32{}, &reshadefx::constant::as_uint, double_negate, mappedBuffer, thing); ++ break; ++ case reshadefx::type::t_int: ++ inner_int(int32_t{}, &reshadefx::constant::as_int, id, mappedBuffer, thing); ++ break; ++ case reshadefx::type::t_uint: ++ inner_uint(uint32_t{}, &reshadefx::constant::as_uint, id, mappedBuffer, thing); ++ break; ++ case reshadefx::type::t_float: ++ inner_float(float{}, &reshadefx::constant::as_float, id, mappedBuffer, thing); + break; + default: + reshade_log.errorf("Unknown uniform type!"); + +From 0c02396633e19b90ace76c29f62bfe54cacfe20f Mon Sep 17 00:00:00 2001 +From: tcoyvwac <53616399+tcoyvwac@users.noreply.github.com> +Date: Tue, 3 Oct 2023 14:52:29 +0200 +Subject: [PATCH 080/134] reshade: Condense to one common lambda function + +--- + src/reshade_effect_manager.cpp | 59 +++------------------------------- + 1 file changed, 5 insertions(+), 54 deletions(-) + +diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp +index 697974997..33d65f538 100644 +--- a/src/reshade_effect_manager.cpp ++++ b/src/reshade_effect_manager.cpp +@@ -198,56 +198,7 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) + assert(m_info.type.array_length == 0 || m_info.type.array_length == 1); + + uint32_t zero_data[16] = {}; +- +- const auto inner_bool = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ +- if (thing) +- { +- decltype(temp_type) array_stuff[16]; +- for (uint32_t i = 0; i < m_info.type.components(); i++) +- array_stuff[i] = pred(thing[i]); +- copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); +- } +- else +- { +- if (m_info.has_initializer_value) +- copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); +- else +- copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); +- } +- }; +- const auto inner_int = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ +- if (thing) +- { +- decltype(temp_type) array_stuff[16]; +- for (uint32_t i = 0; i < m_info.type.components(); i++) +- array_stuff[i] = pred(thing[i]); +- copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); +- } +- else +- { +- if (m_info.has_initializer_value) +- copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); +- else +- copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); +- } +- }; +- const auto inner_uint = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ +- if (thing) +- { +- decltype(temp_type) array_stuff[16]; +- for (uint32_t i = 0; i < m_info.type.components(); i++) +- array_stuff[i] = pred(thing[i]); +- copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); +- } +- else +- { +- if (m_info.has_initializer_value) +- copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); +- else +- copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); +- } +- }; +- const auto inner_float = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ ++ const auto inner_common = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ + if (thing) + { + decltype(temp_type) array_stuff[16]; +@@ -270,16 +221,16 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) + switch (m_info.type.base) + { + case reshadefx::type::t_bool: +- inner_bool(VkBool32{}, &reshadefx::constant::as_uint, double_negate, mappedBuffer, thing); ++ inner_common(VkBool32{}, &reshadefx::constant::as_uint, double_negate, mappedBuffer, thing); + break; + case reshadefx::type::t_int: +- inner_int(int32_t{}, &reshadefx::constant::as_int, id, mappedBuffer, thing); ++ inner_common(int32_t{}, &reshadefx::constant::as_int, id, mappedBuffer, thing); + break; + case reshadefx::type::t_uint: +- inner_uint(uint32_t{}, &reshadefx::constant::as_uint, id, mappedBuffer, thing); ++ inner_common(uint32_t{}, &reshadefx::constant::as_uint, id, mappedBuffer, thing); + break; + case reshadefx::type::t_float: +- inner_float(float{}, &reshadefx::constant::as_float, id, mappedBuffer, thing); ++ inner_common(float{}, &reshadefx::constant::as_float, id, mappedBuffer, thing); + break; + default: + reshade_log.errorf("Unknown uniform type!"); + +From 658dd989f5facd2ee854fa53fd76286955a3940b Mon Sep 17 00:00:00 2001 +From: tcoyvwac <53616399+tcoyvwac@users.noreply.github.com> +Date: Tue, 3 Oct 2023 15:20:30 +0200 +Subject: [PATCH 081/134] reshade: Removed explicit datatype parameter for + inferred typename T (of function) + +--- + src/reshade_effect_manager.cpp | 33 +++++++++++++++------------------ + 1 file changed, 15 insertions(+), 18 deletions(-) + +diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp +index 33d65f538..0495f0434 100644 +--- a/src/reshade_effect_manager.cpp ++++ b/src/reshade_effect_manager.cpp +@@ -198,21 +198,18 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) + assert(m_info.type.array_length == 0 || m_info.type.array_length == 1); + + uint32_t zero_data[16] = {}; +- const auto inner_common = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ +- if (thing) +- { +- decltype(temp_type) array_stuff[16]; ++ const auto inner_common = [&](const auto accessor, const auto pred){ ++ T array_stuff[16] = {}; ++ const auto path_thing = [&](){ + for (uint32_t i = 0; i < m_info.type.components(); i++) + array_stuff[i] = pred(thing[i]); +- copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); +- } +- else +- { +- if (m_info.has_initializer_value) +- copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); +- else +- copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); +- } ++ return array_stuff; ++ }; ++ const auto path_zero = (m_info.has_initializer_value) ++ ? static_cast(&((m_info.initializer_value).*accessor)[0]) ++ : zero_data; ++ const auto sourceBuffer = (thing) ? path_thing() : path_zero; ++ copy(mappedBuffer, sourceBuffer, sizeof(T) * m_info.type.components()); + }; + + const auto double_negate = [](const T item){return !!(item);}; +@@ -220,17 +217,17 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) + + switch (m_info.type.base) + { +- case reshadefx::type::t_bool: +- inner_common(VkBool32{}, &reshadefx::constant::as_uint, double_negate, mappedBuffer, thing); ++ case reshadefx::type::t_bool: // VkBool32 = uint32_t; ++ inner_common(&reshadefx::constant::as_uint, double_negate); + break; + case reshadefx::type::t_int: +- inner_common(int32_t{}, &reshadefx::constant::as_int, id, mappedBuffer, thing); ++ inner_common(&reshadefx::constant::as_int, id); + break; + case reshadefx::type::t_uint: +- inner_common(uint32_t{}, &reshadefx::constant::as_uint, id, mappedBuffer, thing); ++ inner_common(&reshadefx::constant::as_uint, id); + break; + case reshadefx::type::t_float: +- inner_common(float{}, &reshadefx::constant::as_float, id, mappedBuffer, thing); ++ inner_common(&reshadefx::constant::as_float, id); + break; + default: + reshade_log.errorf("Unknown uniform type!"); + +From 425eb07fc530afa8df45c6910f172c42a10e7401 Mon Sep 17 00:00:00 2001 +From: tcoyvwac <53616399+tcoyvwac@users.noreply.github.com> +Date: Sun, 8 Oct 2023 21:13:44 +0200 +Subject: [PATCH 082/134] reshade: Make changeset as small using inlining + +* Inlined multi-lined statements into single-lined expressions +* Use understore to denote x_detail lambda function +--- + src/reshade_effect_manager.cpp | 24 +++++++++--------------- + 1 file changed, 9 insertions(+), 15 deletions(-) + +diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp +index 0495f0434..382a016bf 100644 +--- a/src/reshade_effect_manager.cpp ++++ b/src/reshade_effect_manager.cpp +@@ -198,36 +198,30 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) + assert(m_info.type.array_length == 0 || m_info.type.array_length == 1); + + uint32_t zero_data[16] = {}; +- const auto inner_common = [&](const auto accessor, const auto pred){ ++ const auto copy_ = [&](const auto constantDatatype){ + T array_stuff[16] = {}; +- const auto path_thing = [&](){ ++ const auto thingBuffer = [&](){ + for (uint32_t i = 0; i < m_info.type.components(); i++) +- array_stuff[i] = pred(thing[i]); ++ array_stuff[i] = (m_info.type.base == reshadefx::type::t_bool) ? !!(thing[i]) : thing[i]; + return array_stuff; + }; +- const auto path_zero = (m_info.has_initializer_value) +- ? static_cast(&((m_info.initializer_value).*accessor)[0]) +- : zero_data; +- const auto sourceBuffer = (thing) ? path_thing() : path_zero; +- copy(mappedBuffer, sourceBuffer, sizeof(T) * m_info.type.components()); ++ const auto defaultOrZeroBuffer = (m_info.has_initializer_value) ? static_cast(std::begin((m_info.initializer_value).*constantDatatype)) : zero_data; ++ copy(mappedBuffer, (thing) ? thingBuffer() : defaultOrZeroBuffer, sizeof(T) * m_info.type.components()); + }; + +- const auto double_negate = [](const T item){return !!(item);}; +- const auto id = [](const T item){return item;}; // C++20 = std::identity +- + switch (m_info.type.base) + { + case reshadefx::type::t_bool: // VkBool32 = uint32_t; +- inner_common(&reshadefx::constant::as_uint, double_negate); ++ copy_(&reshadefx::constant::as_uint); + break; + case reshadefx::type::t_int: +- inner_common(&reshadefx::constant::as_int, id); ++ copy_(&reshadefx::constant::as_int); + break; + case reshadefx::type::t_uint: +- inner_common(&reshadefx::constant::as_uint, id); ++ copy_(&reshadefx::constant::as_uint); + break; + case reshadefx::type::t_float: +- inner_common(&reshadefx::constant::as_float, id); ++ copy_(&reshadefx::constant::as_float); + break; + default: + reshade_log.errorf("Unknown uniform type!"); + +From 87834648e61964889de7971a370b084b00de83fb Mon Sep 17 00:00:00 2001 +From: Dexter Gaon-Shatford +Date: Mon, 24 Jul 2023 22:09:17 -0400 +Subject: [PATCH 083/134] Add configurable mouse sensitivity + +The `--mouse-sensitivity`/`-s` command line option allows the user to +set the value `g_mouseSensitivity` which is used to scale the mouse +movement in `wlserver_mousemotion`. +--- + src/main.cpp | 7 +++++++ + src/main.hpp | 2 ++ + src/wlserver.cpp | 13 ++++--------- + 3 files changed, 13 insertions(+), 9 deletions(-) + +diff --git a/src/main.cpp b/src/main.cpp +index 220ee1583..451c02705 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -62,6 +62,7 @@ const struct option *gamescope_options = (struct option[]){ + { "rt", no_argument, nullptr, 0 }, + { "prefer-vk-device", required_argument, 0 }, + { "expose-wayland", no_argument, 0 }, ++ { "mouse-sensitivity", required_argument, nullptr, 's' }, + + { "headless", no_argument, 0 }, + +@@ -159,6 +160,7 @@ const char usage[] = + " 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" + " --headless use headless backend (no window, no DRM output)\n" + " --cursor path to default cursor image\n" + " -R, --ready-fd notify FD when ready\n" +@@ -267,6 +269,8 @@ bool g_bHeadless = false; + + bool g_bGrabbed = false; + ++float g_mouseSensitivity = 1.0; ++ + GamescopeUpscaleFilter g_upscaleFilter = GamescopeUpscaleFilter::LINEAR; + GamescopeUpscaleScaler g_upscaleScaler = GamescopeUpscaleScaler::AUTO; + +@@ -596,6 +600,9 @@ int main(int argc, char **argv) + case 'g': + g_bGrabbed = true; + break; ++ case 's': ++ g_mouseSensitivity = atof( optarg ); ++ break; + case 0: // long options without a short option + opt_name = gamescope_options[opt_index].name; + if (strcmp(opt_name, "help") == 0) { +diff --git a/src/main.hpp b/src/main.hpp +index 7d8e9f1be..7cf1c4088 100644 +--- a/src/main.hpp ++++ b/src/main.hpp +@@ -23,6 +23,8 @@ extern bool g_bFullscreen; + + extern bool g_bGrabbed; + ++extern float g_mouseSensitivity; ++ + enum class GamescopeUpscaleFilter : uint32_t + { + LINEAR = 0, +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 2eb0f9c43..4644f667f 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -270,6 +270,9 @@ static void wlserver_perform_rel_pointer_motion(double unaccel_dx, double unacce + auto server = steamcompmgr_get_focused_server(); + if ( server != NULL ) + { ++ unaccel_dx *= g_mouseSensitivity; ++ unaccel_dy *= g_mouseSensitivity; ++ + server->ctx->accum_x += unaccel_dx; + server->ctx->accum_y += unaccel_dy; + +@@ -1861,15 +1864,7 @@ void wlserver_mousefocus( struct wlr_surface *wlrsurface, int x /* = 0 */, int y + + void wlserver_mousemotion( int x, int y, uint32_t time ) + { +- assert( wlserver_is_lock_held() ); +- +- // TODO: Pick the xwayland_server with active focus +- auto server = steamcompmgr_get_focused_server(); +- if ( server != NULL ) +- { +- XTestFakeRelativeMotionEvent( server->get_xdisplay(), x, y, CurrentTime ); +- XFlush( server->get_xdisplay() ); +- } ++ wlserver_perform_rel_pointer_motion( x, y ); + } + + void wlserver_mousewarp( int x, int y, uint32_t time ) + +From bca7990e61a1eb8198e54d86a4a9a44d41d9b07e Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sat, 20 Jan 2024 01:09:32 +0000 +Subject: [PATCH 084/134] main: Pick correct Vulkan device for DRM + +Closes: #1080 +--- + src/main.cpp | 59 +++++++++++++++++++++++++++----------------- + src/rendervulkan.cpp | 7 +++--- + src/rendervulkan.hpp | 2 +- + 3 files changed, 40 insertions(+), 28 deletions(-) + +diff --git a/src/main.cpp b/src/main.cpp +index 451c02705..1ec1f48e3 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -765,9 +765,6 @@ int main(int argc, char **argv) + } + } + +- VkInstance instance = vulkan_create_instance(); +- VkSurfaceKHR surface = VK_NULL_HANDLE; +- + if ( !BIsNested() ) + { + g_bForceRelativeMouse = false; +@@ -790,30 +787,15 @@ int main(int argc, char **argv) + return 1; + } + +- if ( BIsSDLSession() ) +- { +- if ( !SDL_Vulkan_CreateSurface( g_SDLWindow, instance, &surface ) ) +- { +- fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); +- return 1; +- } +- } +- + g_ForcedNV12ColorSpace = parse_colorspace_string( getenv( "GAMESCOPE_NV12_COLORSPACE" ) ); + +- if ( !vulkan_init(instance, surface) ) +- { +- fprintf( stderr, "Failed to initialize Vulkan\n" ); +- return 1; +- } +- + if ( !vulkan_init_formats() ) + { + fprintf( stderr, "vulkan_init_formats failed\n" ); + return 1; + } + +- if ( !vulkan_make_output(surface) ) ++ if ( !vulkan_make_output() ) + { + fprintf( stderr, "vulkan_make_output failed\n" ); + return 1; +@@ -925,6 +907,8 @@ static void steamCompMgrThreadRun(int argc, char **argv) + + static bool initOutput( int preferredWidth, int preferredHeight, int preferredRefresh ) + { ++ VkInstance instance = vulkan_create_instance(); ++ + if ( BIsNested() ) + { + g_nOutputWidth = preferredWidth; +@@ -936,7 +920,7 @@ static bool initOutput( int preferredWidth, int preferredHeight, int preferredRe + if ( g_nOutputWidth != 0 ) + { + fprintf( stderr, "Cannot specify -W without -H\n" ); +- return 1; ++ return false; + } + g_nOutputHeight = 720; + } +@@ -948,16 +932,45 @@ static bool initOutput( int preferredWidth, int preferredHeight, int preferredRe + if ( BIsVRSession() ) + { + #if HAVE_OPENVR +- return vrsession_init(); ++ if ( !vrsession_init() ) ++ return false; + #else + return false; + #endif + } + else if ( BIsSDLSession() ) + { +- return sdlwindow_init(); ++ if ( !sdlwindow_init() ) ++ return false; ++ } ++ ++ VkSurfaceKHR surface = VK_NULL_HANDLE; ++ ++ if ( BIsSDLSession() ) ++ { ++ if ( !SDL_Vulkan_CreateSurface( g_SDLWindow, instance, &surface ) ) ++ { ++ fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); ++ return false; ++ } ++ } ++ ++ if ( !vulkan_init( instance, surface ) ) ++ { ++ fprintf( stderr, "Failed to initialize Vulkan\n" ); ++ return false; + } ++ + return true; + } +- return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh, s_bInitialWantsVRREnabled ); ++ else ++ { ++ if ( !vulkan_init( instance, VK_NULL_HANDLE ) ) ++ { ++ fprintf( stderr, "Failed to initialize Vulkan\n" ); ++ return false; ++ } ++ ++ return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh, s_bInitialWantsVRREnabled ); ++ } + } +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 4715970db..357018607 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -253,6 +253,8 @@ bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface) + assert(instance); + assert(!m_bInitialized); + ++ g_output.surface = surface; ++ + m_instance = instance; + #define VK_FUNC(x) vk.x = (PFN_vk##x) vkGetInstanceProcAddr(instance, "vk"#x); + VULKAN_INSTANCE_FUNCTIONS +@@ -3129,7 +3131,7 @@ bool vulkan_remake_output_images() + return bRet; + } + +-bool vulkan_make_output( VkSurfaceKHR surface ) ++bool vulkan_make_output() + { + VulkanOutput_t *pOutput = &g_output; + +@@ -3142,9 +3144,6 @@ bool vulkan_make_output( VkSurfaceKHR surface ) + } + else if ( BIsSDLSession() ) + { +- assert(surface); +- pOutput->surface = surface; +- + result = g_device.vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( g_device.physDev(), pOutput->surface, &pOutput->surfaceCaps ); + if ( result != VK_SUCCESS ) + { +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index e10295208..98b3936f1 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -368,7 +368,7 @@ namespace CompositeDebugFlag + VkInstance vulkan_create_instance(void); + bool vulkan_init(VkInstance instance, VkSurfaceKHR surface); + bool vulkan_init_formats(void); +-bool vulkan_make_output(VkSurfaceKHR surface); ++bool vulkan_make_output(); + + std::shared_ptr vulkan_create_texture_from_dmabuf( struct wlr_dmabuf_attributes *pDMA ); + std::shared_ptr 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 ); + +From dc81258cec01ad137056191b8db990adcbba6341 Mon Sep 17 00:00:00 2001 +From: LuK1337 +Date: Sat, 20 Jan 2024 10:21:47 +0100 +Subject: [PATCH 085/134] readme: Remove dead shortcut + +This shortcut was removed in 371fcf0. +--- + README.md | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/README.md b/README.md +index 128089123..a56075a91 100644 +--- a/README.md ++++ b/README.md +@@ -40,7 +40,6 @@ meson install -C build/ --skip-subprojects + * **Super + O** : Decrease FSR sharpness by 1 + * **Super + S** : Take screenshot (currently goes to `/tmp/gamescope_$DATE.png`) + * **Super + G** : Toggle keyboard grab +-* **Super + C** : Update clipboard + + ## Examples + + +From 6b89dc2f615ce1bc39470e9c330c3f66a4d980e2 Mon Sep 17 00:00:00 2001 +From: Simon Ser +Date: Sun, 28 Jan 2024 15:11:05 +0100 +Subject: [PATCH 086/134] Centralize wlroots include guards + +--- + src/drm.cpp | 4 ++-- + src/drm.hpp | 12 +++--------- + src/ime.cpp | 6 ++---- + src/rendervulkan.hpp | 6 ++---- + src/steamcompmgr.hpp | 7 +++---- + src/wlr_begin.hpp | 7 +++++++ + src/wlr_end.hpp | 5 +++++ + src/wlserver.cpp | 10 ++-------- + 8 files changed, 26 insertions(+), 31 deletions(-) + create mode 100644 src/wlr_begin.hpp + create mode 100644 src/wlr_end.hpp + +diff --git a/src/drm.cpp b/src/drm.cpp +index 717a5299f..6272238f5 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -14,9 +14,9 @@ + #include + #include + +-extern "C" { ++#include "wlr_begin.hpp" + #include +-} ++#include "wlr_end.hpp" + + #include "drm.hpp" + #include "defer.hpp" +diff --git a/src/drm.hpp b/src/drm.hpp +index 1ab4fc387..048b84a8f 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -7,6 +7,7 @@ + #include + #include + #include ++#include + + #include + +@@ -44,11 +45,6 @@ inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) + colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; + } + +-extern "C" +-{ +- struct wl_resource; +-} +- + extern struct drm_t g_DRM; + void drm_destroy_blob(struct drm_t *drm, uint32_t blob); + +@@ -111,13 +107,11 @@ struct wlserver_ctm : drm_blob + glm::mat3x4 matrix{}; + }; + +-#include +- +-extern "C" { ++#include "wlr_begin.hpp" + #include + #include + #include +-} ++#include "wlr_end.hpp" + + #include "rendervulkan.hpp" + +diff --git a/src/ime.cpp b/src/ime.cpp +index c72e8ae7a..0c8f082aa 100644 +--- a/src/ime.cpp ++++ b/src/ime.cpp +@@ -12,13 +12,11 @@ + + #include + +-extern "C" { +-#define delete delete_ ++#include "wlr_begin.hpp" + #include + #include + #include +-#undef delete +-} ++#include "wlr_end.hpp" + + #include "gamescope-input-method-protocol.h" + +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index 98b3936f1..992438d53 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -64,12 +64,10 @@ enum EStreamColorspace : int + #include + #include + +-extern "C" { +-#define static ++#include "wlr_begin.hpp" + #include + #include +-#undef static +-} ++#include "wlr_end.hpp" + + #define VK_NO_PROTOTYPES + #include +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index d1a209d48..2429ee066 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -1,9 +1,10 @@ + #include + +-extern "C" { ++#include "wlr_begin.hpp" + #include + #include +-} ++#include ++#include "wlr_end.hpp" + + extern uint32_t currentOutputWidth; + extern uint32_t currentOutputHeight; +@@ -23,8 +24,6 @@ void steamcompmgr_main(int argc, char **argv); + #include + #include + +-#include +- + #include + + struct _XDisplay; +diff --git a/src/wlr_begin.hpp b/src/wlr_begin.hpp +new file mode 100644 +index 000000000..c0936fc9b +--- /dev/null ++++ b/src/wlr_begin.hpp +@@ -0,0 +1,7 @@ ++#include ++ ++extern "C" { ++#define static ++#define class class_ ++#define namespace _namespace ++#define delete delete_ +diff --git a/src/wlr_end.hpp b/src/wlr_end.hpp +new file mode 100644 +index 000000000..b1afdff37 +--- /dev/null ++++ b/src/wlr_end.hpp +@@ -0,0 +1,5 @@ ++#undef static ++#undef class ++#undef namespace ++#undef delete ++} +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 4644f667f..cc5e68be0 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -14,11 +14,7 @@ + #include + #include + +-#include +- +-extern "C" { +-#define static +-#define class class_ ++#include "wlr_begin.hpp" + #include + #include + #include +@@ -33,9 +29,7 @@ extern "C" { + #include + #include + #include +-#undef static +-#undef class +-} ++#include "wlr_end.hpp" + + #include "gamescope-xwayland-protocol.h" + #include "gamescope-pipewire-protocol.h" + +From 1bd20cebd2980e629161669caec5c5306c63da70 Mon Sep 17 00:00:00 2001 +From: Etaash Mathamsetty +Date: Sat, 27 Jan 2024 22:02:35 -0500 +Subject: [PATCH 087/134] Support to put nested window on a certain display + +--- + src/main.cpp | 5 +++++ + src/main.hpp | 1 + + src/sdlwindow.cpp | 4 ++-- + 3 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/main.cpp b/src/main.cpp +index 1ec1f48e3..4bf7c3336 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -72,6 +72,7 @@ const struct option *gamescope_options = (struct option[]){ + { "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 }, +@@ -190,6 +191,7 @@ const char usage[] = + " -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\n" +@@ -255,6 +257,7 @@ int g_nNestedWidth = 0; + int g_nNestedHeight = 0; + int g_nNestedRefresh = 0; + int g_nNestedUnfocusedRefresh = 0; ++int g_nNestedDisplayIndex = 0; + + uint32_t g_nOutputWidth = 0; + uint32_t g_nOutputHeight = 0; +@@ -641,6 +644,8 @@ int main(int argc, char **argv) + g_nAsyncFlipsEnabled = 1; + } else if (strcmp(opt_name, "force-grab-cursor") == 0) { + g_bForceRelativeMouse = true; ++ } else if (strcmp(opt_name, "display-index") == 0) { ++ g_nNestedDisplayIndex = atoi( optarg ); + } else if (strcmp(opt_name, "adaptive-sync") == 0) { + s_bInitialWantsVRREnabled = true; + } else if (strcmp(opt_name, "expose-wayland") == 0) { +diff --git a/src/main.hpp b/src/main.hpp +index 7cf1c4088..b3de6b847 100644 +--- a/src/main.hpp ++++ b/src/main.hpp +@@ -13,6 +13,7 @@ extern int g_nNestedWidth; + extern int g_nNestedHeight; + extern int g_nNestedRefresh; // Hz + extern int g_nNestedUnfocusedRefresh; // Hz ++extern int g_nNestedDisplayIndex; + + extern uint32_t g_nOutputWidth; + extern uint32_t g_nOutputHeight; +diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp +index 78912e15a..1e51eab6c 100644 +--- a/src/sdlwindow.cpp ++++ b/src/sdlwindow.cpp +@@ -136,8 +136,8 @@ void inputSDLThreadRun( void ) + } + + g_SDLWindow = SDL_CreateWindow( DEFAULT_TITLE, +- SDL_WINDOWPOS_UNDEFINED, +- SDL_WINDOWPOS_UNDEFINED, ++ SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), ++ SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), + g_nOutputWidth, + g_nOutputHeight, + nSDLWindowFlags ); + +From 6309cddde5c2d7339e49546f0e32203fedd51697 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 28 Jan 2024 22:39:36 +0000 +Subject: [PATCH 088/134] drm: Some more refactoring + +--- + src/color_helpers.h | 2 + + src/drm.hpp | 126 ++++++----------------------------------- + src/drm_include.h | 73 ++++++++++++++++++++++++ + src/gamescope_shared.h | 16 ++++++ + src/rendervulkan.hpp | 7 ++- + 5 files changed, 113 insertions(+), 111 deletions(-) + create mode 100644 src/drm_include.h + +diff --git a/src/color_helpers.h b/src/color_helpers.h +index 51aaedc74..591119015 100644 +--- a/src/color_helpers.h ++++ b/src/color_helpers.h +@@ -1,5 +1,7 @@ + #pragma once + ++#define GLM_ENABLE_EXPERIMENTAL 1 ++ + #include + #include + #include +diff --git a/src/drm.hpp b/src/drm.hpp +index 048b84a8f..1c64f3c6e 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -1,49 +1,26 @@ +-// DRM output stuff +- + #pragma once + +-#include +-#include +-#include +-#include +-#include +-#include +- +-#include +- ++#include "drm_include.h" + #include "color_helpers.h" + #include "gamescope_shared.h" ++#include "rendervulkan.hpp" + +-// 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, +-}; +- +-enum drm_color_range { +- DRM_COLOR_YCBCR_LIMITED_RANGE, +- DRM_COLOR_YCBCR_FULL_RANGE, +- DRM_COLOR_RANGE_MAX, +-}; +- +-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, +-}; +-const uint32_t GamescopeAppTextureColorspace_Bits = 3; ++#include "wlr_begin.hpp" ++#include ++#include ++#include ++#include "wlr_end.hpp" + +-inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) +-{ +- return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB || +- colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; +-} ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + + extern struct drm_t g_DRM; + void drm_destroy_blob(struct drm_t *drm, uint32_t blob); +@@ -106,24 +83,6 @@ struct wlserver_ctm : drm_blob + + glm::mat3x4 matrix{}; + }; +- +-#include "wlr_begin.hpp" +-#include +-#include +-#include +-#include "wlr_end.hpp" +- +-#include "rendervulkan.hpp" +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- + namespace gamescope + { + template +@@ -407,21 +366,6 @@ struct fb { + std::atomic< uint32_t > n_refs; + }; + +-enum drm_valve1_transfer_function { +- DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, +- +- DRM_VALVE1_TRANSFER_FUNCTION_SRGB, +- DRM_VALVE1_TRANSFER_FUNCTION_BT709, +- DRM_VALVE1_TRANSFER_FUNCTION_PQ, +- DRM_VALVE1_TRANSFER_FUNCTION_LINEAR, +- DRM_VALVE1_TRANSFER_FUNCTION_UNITY, +- DRM_VALVE1_TRANSFER_FUNCTION_HLG, +- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA22, +- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA24, +- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA26, +- DRM_VALVE1_TRANSFER_FUNCTION_MAX, +-}; +- + struct drm_t { + bool bUseLiftoff; + +@@ -572,42 +516,6 @@ std::span drm_get_valid_refresh_rates( struct drm_t *drm ); + + extern bool g_bSupportsAsyncFlips; + +-/* from CTA-861-G */ +-#define HDMI_EOTF_SDR 0 +-#define HDMI_EOTF_TRADITIONAL_HDR 1 +-#define HDMI_EOTF_ST2084 2 +-#define HDMI_EOTF_HLG 3 +- +-/* For Default case, driver will set the colorspace */ +-#define DRM_MODE_COLORIMETRY_DEFAULT 0 +-/* CEA 861 Normal Colorimetry options */ +-#define DRM_MODE_COLORIMETRY_NO_DATA 0 +-#define DRM_MODE_COLORIMETRY_SMPTE_170M_YCC 1 +-#define DRM_MODE_COLORIMETRY_BT709_YCC 2 +-/* CEA 861 Extended Colorimetry Options */ +-#define DRM_MODE_COLORIMETRY_XVYCC_601 3 +-#define DRM_MODE_COLORIMETRY_XVYCC_709 4 +-#define DRM_MODE_COLORIMETRY_SYCC_601 5 +-#define DRM_MODE_COLORIMETRY_OPYCC_601 6 +-#define DRM_MODE_COLORIMETRY_OPRGB 7 +-#define DRM_MODE_COLORIMETRY_BT2020_CYCC 8 +-#define DRM_MODE_COLORIMETRY_BT2020_RGB 9 +-#define DRM_MODE_COLORIMETRY_BT2020_YCC 10 +-/* Additional Colorimetry extension added as part of CTA 861.G */ +-#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 11 +-#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER 12 +-/* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format */ +-#define DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED 13 +-#define DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT 14 +-#define DRM_MODE_COLORIMETRY_BT601_YCC 15 +- +-/* 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 +- + const char* drm_get_patched_edid_path(); + void drm_update_patched_edid(drm_t *drm); + +diff --git a/src/drm_include.h b/src/drm_include.h +new file mode 100644 +index 000000000..cf4a7cb5c +--- /dev/null ++++ b/src/drm_include.h +@@ -0,0 +1,73 @@ ++#pragma once ++ ++#include ++#include ++#include ++#include ++ ++// 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, ++}; ++ ++enum drm_color_range { ++ DRM_COLOR_YCBCR_LIMITED_RANGE, ++ DRM_COLOR_YCBCR_FULL_RANGE, ++ DRM_COLOR_RANGE_MAX, ++}; ++ ++enum drm_valve1_transfer_function { ++ DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, ++ ++ DRM_VALVE1_TRANSFER_FUNCTION_SRGB, ++ DRM_VALVE1_TRANSFER_FUNCTION_BT709, ++ DRM_VALVE1_TRANSFER_FUNCTION_PQ, ++ DRM_VALVE1_TRANSFER_FUNCTION_LINEAR, ++ DRM_VALVE1_TRANSFER_FUNCTION_UNITY, ++ DRM_VALVE1_TRANSFER_FUNCTION_HLG, ++ DRM_VALVE1_TRANSFER_FUNCTION_GAMMA22, ++ DRM_VALVE1_TRANSFER_FUNCTION_GAMMA24, ++ DRM_VALVE1_TRANSFER_FUNCTION_GAMMA26, ++ DRM_VALVE1_TRANSFER_FUNCTION_MAX, ++}; ++ ++/* from CTA-861-G */ ++#define HDMI_EOTF_SDR 0 ++#define HDMI_EOTF_TRADITIONAL_HDR 1 ++#define HDMI_EOTF_ST2084 2 ++#define HDMI_EOTF_HLG 3 ++ ++/* For Default case, driver will set the colorspace */ ++#define DRM_MODE_COLORIMETRY_DEFAULT 0 ++/* CEA 861 Normal Colorimetry options */ ++#define DRM_MODE_COLORIMETRY_NO_DATA 0 ++#define DRM_MODE_COLORIMETRY_SMPTE_170M_YCC 1 ++#define DRM_MODE_COLORIMETRY_BT709_YCC 2 ++/* CEA 861 Extended Colorimetry Options */ ++#define DRM_MODE_COLORIMETRY_XVYCC_601 3 ++#define DRM_MODE_COLORIMETRY_XVYCC_709 4 ++#define DRM_MODE_COLORIMETRY_SYCC_601 5 ++#define DRM_MODE_COLORIMETRY_OPYCC_601 6 ++#define DRM_MODE_COLORIMETRY_OPRGB 7 ++#define DRM_MODE_COLORIMETRY_BT2020_CYCC 8 ++#define DRM_MODE_COLORIMETRY_BT2020_RGB 9 ++#define DRM_MODE_COLORIMETRY_BT2020_YCC 10 ++/* Additional Colorimetry extension added as part of CTA 861.G */ ++#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 11 ++#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER 12 ++/* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format */ ++#define DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED 13 ++#define DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT 14 ++#define DRM_MODE_COLORIMETRY_BT601_YCC 15 ++ ++/* 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 +diff --git a/src/gamescope_shared.h b/src/gamescope_shared.h +index 523f58ed9..fdbcfa481 100644 +--- a/src/gamescope_shared.h ++++ b/src/gamescope_shared.h +@@ -25,3 +25,19 @@ 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, ++}; ++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; ++} ++ +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index 992438d53..59e986d3f 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -13,11 +13,16 @@ + #include + + #include "main.hpp" ++#include "color_helpers.h" ++#include "gamescope_shared.h" + + #include "shaders/descriptor_set_constants.h" + + class CVulkanCmdBuffer; + ++struct wlserver_ctm; ++struct wlserver_hdr_metadata; ++ + // 1: Fade Plane (Fade outs between switching focus) + // 2: Video Underlay (The actual video) + // 3: Video Streaming UI (Game, App) +@@ -56,8 +61,6 @@ enum EStreamColorspace : int + k_EStreamColorspace_BT709_Full = 4 + }; + +-#include "drm.hpp" +- + #include + #include + #include + +From e8f3b355875dbd1eb5e9cff164428d6f733023b7 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 29 Jan 2024 02:15:40 +0000 +Subject: [PATCH 089/134] drm: Refactor out VRR state from DRM + +--- + src/drm.cpp | 41 +++++++++++++++-------------------------- + src/drm.hpp | 6 +----- + src/main.cpp | 6 +++--- + src/rendervulkan.hpp | 1 + + src/steamcompmgr.cpp | 7 +++++-- + 5 files changed, 25 insertions(+), 36 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 6272238f5..000633fbc 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -971,13 +971,12 @@ bool env_to_bool(const char *env) + return !!atoi(env); + } + +-bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_adaptive_sync) ++bool init_drm(struct drm_t *drm, int width, int height, int refresh) + { + load_pnps(); + + drm->bUseLiftoff = true; + +- drm->wants_vrr_enabled = wants_adaptive_sync; + drm->preferred_width = width; + drm->preferred_height = height; + drm->preferred_refresh = refresh; +@@ -2535,9 +2534,17 @@ bool g_bForceAsyncFlips = false; + * 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 ) + { +- drm_update_vrr_state(drm); + drm_update_color_mgmt(drm); + ++ const bool bVRRCapable = drm->pConnector && drm->pConnector->GetProperties().vrr_capable && ++ drm->pCRTC && drm->pCRTC->GetProperties().VRR_ENABLED; ++ const bool bVRREnabled = bVRRCapable && frameInfo->allowVRR; ++ if ( bVRRCapable ) ++ { ++ if ( bVRREnabled != !!drm->pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue() ) ++ drm->needs_modeset = true; ++ } ++ + drm->fbids_in_req.clear(); + + bool needs_modeset = drm->needs_modeset.exchange(false); +@@ -2664,7 +2671,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + drm->pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, drm->pending.mode_id ? drm->pending.mode_id->blob : 0lu, true ); + + if ( drm->pCRTC->GetProperties().VRR_ENABLED ) +- drm->pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, drm->pending.vrr_enabled, true ); ++ drm->pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, bVRREnabled, true ); + } + } + +@@ -2788,14 +2795,12 @@ static void drm_unset_connector( struct drm_t *drm ) + drm->needs_modeset = true; + } + +-void drm_set_vrr_enabled(struct drm_t *drm, bool enabled) +-{ +- drm->wants_vrr_enabled = enabled; +-} +- + bool drm_get_vrr_in_use(struct drm_t *drm) + { +- return drm->current.vrr_enabled; ++ if ( !drm->pCRTC || !drm->pCRTC->GetProperties().VRR_ENABLED ) ++ return false; ++ ++ return !!drm->pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue(); + } + + gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm) +@@ -2845,22 +2850,6 @@ bool drm_update_color_mgmt(struct drm_t *drm) + return true; + } + +-bool drm_update_vrr_state(struct drm_t *drm) +-{ +- drm->pending.vrr_enabled = false; +- +- if ( drm->pConnector && drm->pCRTC && drm->pCRTC->GetProperties().VRR_ENABLED ) +- { +- if ( drm->wants_vrr_enabled && drm->pConnector->IsVRRCapable() ) +- drm->pending.vrr_enabled = true; +- } +- +- if (drm->pending.vrr_enabled != drm->current.vrr_enabled) +- drm->needs_modeset = true; +- +- return true; +-} +- + static void drm_unset_mode( struct drm_t *drm ) + { + drm->pending.mode_id = 0; +diff --git a/src/drm.hpp b/src/drm.hpp +index 1c64f3c6e..3271c088d 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -405,11 +405,8 @@ struct drm_t { + uint32_t color_mgmt_serial; + std::shared_ptr lut3d_id[ EOTF_Count ]; + std::shared_ptr shaperlut_id[ EOTF_Count ]; +- // TODO: Remove me, this should be some generic setting. +- bool vrr_enabled = false; + drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; + } current, pending; +- bool wants_vrr_enabled = false; + + /* FBs in the atomic request, but not yet submitted to KMS */ + std::vector < uint32_t > fbids_in_req; +@@ -474,7 +471,7 @@ extern std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCRE + + extern bool g_bForceDisableColorMgmt; + +-bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_adaptive_sync); ++bool init_drm(struct drm_t *drm, int width, int height, int refresh); + void finish_drm(struct drm_t *drm); + int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); + int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ); +@@ -495,7 +492,6 @@ char *find_drm_node_by_devid(dev_t devid); + int drm_get_default_refresh(struct drm_t *drm); + bool drm_get_vrr_capable(struct drm_t *drm); + bool drm_supports_hdr(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); +-void drm_set_vrr_enabled(struct drm_t *drm, bool enabled); + bool drm_get_vrr_in_use(struct drm_t *drm); + bool drm_supports_color_mgmt(struct drm_t *drm); + std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata); +diff --git a/src/main.cpp b/src/main.cpp +index 4bf7c3336..02cc5ce3a 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -39,7 +39,7 @@ + using namespace std::literals; + + EStreamColorspace g_ForcedNV12ColorSpace = k_EStreamColorspace_Unknown; +-static bool s_bInitialWantsVRREnabled = false; ++extern bool g_bAllowVRR; + + const char *gamescope_optstring = nullptr; + const char *g_pOriginalDisplay = nullptr; +@@ -647,7 +647,7 @@ int main(int argc, char **argv) + } else if (strcmp(opt_name, "display-index") == 0) { + g_nNestedDisplayIndex = atoi( optarg ); + } else if (strcmp(opt_name, "adaptive-sync") == 0) { +- s_bInitialWantsVRREnabled = true; ++ g_bAllowVRR = true; + } else if (strcmp(opt_name, "expose-wayland") == 0) { + g_bExposeWayland = true; + } else if (strcmp(opt_name, "headless") == 0) { +@@ -976,6 +976,6 @@ static bool initOutput( int preferredWidth, int preferredHeight, int preferredRe + return false; + } + +- return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh, s_bInitialWantsVRREnabled ); ++ return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh ); + } + } +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index 59e986d3f..f090344ae 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -270,6 +270,7 @@ struct FrameInfo_t + std::shared_ptr shaperLut[EOTF_Count]; + std::shared_ptr lut3D[EOTF_Count]; + ++ bool allowVRR; + bool applyOutputColorMgmt; // drm only + EOTF outputEncodingEOTF; + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 1bbc1dce8..d3f0ef42c 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -172,6 +172,8 @@ timespec nanos_to_timespec( uint64_t ulNanos ) + static void + update_runtime_info(); + ++bool g_bAllowVRR = false; ++ + static uint64_t g_SteamCompMgrLimitedAppRefreshCycle = 16'666'666; + static uint64_t g_SteamCompMgrAppRefreshCycle = 16'666'666; + +@@ -2463,6 +2465,7 @@ paint_all(bool async) + struct FrameInfo_t frameInfo = {}; + frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; + frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; ++ frameInfo.allowVRR = g_bAllowVRR; + + // 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 +@@ -5917,7 +5920,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + if ( ev->atom == ctx->atoms.gamescopeVRREnabled ) + { + bool enabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeVRREnabled, 0 ); +- drm_set_vrr_enabled( &g_DRM, enabled ); ++ g_bAllowVRR = enabled; + } + if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal ) + { +@@ -7656,7 +7659,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = + // Keep this as a preference, starting with off. + if ( force ) + { +- bool wants_vrr = g_DRM.wants_vrr_enabled; ++ bool wants_vrr = g_bAllowVRR; + 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 ); + +From 9e46c89ffc56c0bc6359ef4269274804f0c9d382 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 29 Jan 2024 02:35:16 +0000 +Subject: [PATCH 090/134] drm: Refactor HDR10 decisionmaking + +--- + src/drm.cpp | 42 +++++++++++++++++++++--------------------- + src/drm.hpp | 30 +++++++++++++++++++----------- + src/steamcompmgr.cpp | 8 -------- + 3 files changed, 40 insertions(+), 40 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 000633fbc..dc232620f 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -55,6 +55,10 @@ const char *g_sOutputName = nullptr; + #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 + #endif + ++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 ); ++ + struct drm_color_ctm2 { + /* + * Conversion matrix in S31.32 sign-magnitude +@@ -2545,27 +2549,35 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + drm->needs_modeset = true; + } + +- drm->fbids_in_req.clear(); +- +- bool needs_modeset = drm->needs_modeset.exchange(false); +- +- assert( drm->req == nullptr ); +- drm->req = drmModeAtomicAlloc(); ++ uint32_t uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; + ++ const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; + wlserver_hdr_metadata *pHDRMetadata = nullptr; +- if ( drm->pConnector && drm->pConnector->GetHDRInfo().IsHDR10() ) ++ if ( drm->pConnector && drm->pConnector->SupportsHDR10() ) + { +- if ( g_bOutputHDREnabled ) ++ if ( bWantsHDR10 ) + { + wlserver_vk_swapchain_feedback* pFeedback = steamcompmgr_get_base_layer_swapchain_feedback(); + pHDRMetadata = pFeedback ? pFeedback->hdr_metadata_blob.get() : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.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->fbids_in_req.clear(); ++ ++ bool needs_modeset = drm->needs_modeset.exchange(false); ++ ++ assert( drm->req == nullptr ); ++ drm->req = drmModeAtomicAlloc(); ++ + bool bSinglePlane = frameInfo->layerCount < 2 && g_bSinglePlaneOptimizations; + + if ( drm_supports_color_mgmt( &g_DRM ) && frameInfo->applyOutputColorMgmt ) +@@ -2657,12 +2669,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + drm->pConnector->GetProperties().CRTC_ID->SetPendingValue( drm->req, drm->pCRTC->GetObjectId(), bForceInRequest ); + + if ( drm->pConnector->GetProperties().Colorspace ) +- { +- uint32_t uColorimetry = g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() +- ? DRM_MODE_COLORIMETRY_BT2020_RGB +- : DRM_MODE_COLORIMETRY_DEFAULT; + drm->pConnector->GetProperties().Colorspace->SetPendingValue( drm->req, uColorimetry, bForceInRequest ); +- } + } + + if ( drm->pCRTC ) +@@ -3004,7 +3011,7 @@ bool drm_get_vrr_capable(struct drm_t *drm) + + bool drm_supports_hdr( struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL ) + { +- if ( drm->pConnector && drm->pConnector->GetHDRInfo().SupportsHDR() ) ++ if ( drm->pConnector && drm->pConnector->SupportsHDR() ) + { + if ( maxCLL ) + *maxCLL = drm->pConnector->GetHDRInfo().uMaxContentLightLevel; +@@ -3016,13 +3023,6 @@ bool drm_supports_hdr( struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL ) + return false; + } + +-void drm_set_hdr_state(struct drm_t *drm, bool enabled) { +- if (drm->enable_hdr != enabled) { +- drm->needs_modeset = true; +- drm->enable_hdr = enabled; +- } +-} +- + const char *drm_get_connector_name(struct drm_t *drm) + { + if ( !drm->pConnector ) +diff --git a/src/drm.hpp b/src/drm.hpp +index 3271c088d..79c88050a 100644 +--- a/src/drm.hpp ++++ b/src/drm.hpp +@@ -269,23 +269,21 @@ namespace gamescope + uint16_t uMinContentLightLevel = 0; // Nits / 10000 + std::shared_ptr pDefaultMetadataBlob; + +- bool ShouldPatchEDID() const ++ bool IsHDRG22() const + { + return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_Gamma22; + } + +- bool SupportsHDR() const ++ bool ShouldPatchEDID() const + { +- // Note: Different to IsHDR10, as we can expose HDR10 on G2.2 displays +- // using LUTs and CTMs. +- return bExposeHDRSupport; ++ return IsHDRG22(); + } + + bool IsHDR10() const + { + // PQ output encoding is always HDR10 (PQ + 2020) for us. + // If that assumption changes, update me. +- return eOutputEncodingEOTF == EOTF_PQ; ++ return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_PQ; + } + }; + +@@ -317,6 +315,21 @@ namespace gamescope + // TODO: Remove + void SetBaseRefresh( int nRefresh ) { m_nBaseRefresh = nRefresh; } + int GetBaseRefresh() const { return m_nBaseRefresh; } ++ ++ bool SupportsHDR10() const ++ { ++ return !!GetProperties().Colorspace && !!GetProperties().HDR_OUTPUT_METADATA && GetHDRInfo().IsHDR10(); ++ } ++ ++ bool SupportsHDRG22() const ++ { ++ return GetHDRInfo().IsHDRG22(); ++ } ++ ++ bool SupportsHDR() const ++ { ++ return SupportsHDR10() || SupportsHDRG22(); ++ } + private: + void ParseEDID(); + +@@ -433,7 +446,6 @@ struct drm_t { + std::unordered_map< std::string, int > connector_priorities; + + bool force_internal = false; +- bool enable_hdr = false; + + char *device_name = nullptr; + }; +@@ -480,12 +492,9 @@ uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct + void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); + void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); + void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); +-bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ); + bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); + bool drm_set_refresh( struct drm_t *drm, int refresh ); + bool drm_set_resolution( struct drm_t *drm, int width, int height ); +-bool drm_update_color_mgmt(struct drm_t *drm); +-bool drm_update_vrr_state(struct drm_t *drm); + gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm); + + char *find_drm_node_by_devid(dev_t devid); +@@ -502,7 +511,6 @@ const char *drm_get_connector_name(struct drm_t *drm); + const char *drm_get_device_name(struct drm_t *drm); + + std::pair drm_get_connector_identifier(struct drm_t *drm); +-void drm_set_hdr_state(struct drm_t *drm, bool enabled); + + void drm_get_native_colorimetry( struct drm_t *drm, + displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index d3f0ef42c..49aa6fbd3 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -8032,14 +8032,6 @@ steamcompmgr_main(int argc, char **argv) + } + else + { +- if ( !BIsNested() ) +- { +- if (g_bOutputHDREnabled != currentHDROutput) +- { +- drm_set_hdr_state(&g_DRM, g_bOutputHDREnabled); +- } +- } +- + vulkan_remake_output_images(); + } + + +From 88eb1b477d8b1efbe6d7087dcde74052dad84049 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 09:08:35 +0000 +Subject: [PATCH 091/134] all: Refactor and add a generic backend interface + +Part 1 of a major refactor to make this more generic. +--- + src/backend.cpp | 60 ++ + src/backend.h | 284 +++++ + src/backends.h | 20 + + src/color_bench.cpp | 12 +- + src/color_helpers.h | 3 +- + src/color_tests.cpp | 12 +- + src/drm.cpp | 2040 +++++++++++++++++++++-------------- + src/drm.hpp | 526 --------- + src/drm_include.h | 19 +- + src/edid.cpp | 296 +++++ + src/edid.h | 16 + + src/gamescope_shared.h | 24 + + src/hdmi.h | 7 + + src/headless.cpp | 248 +++++ + src/main.cpp | 245 ++--- + src/main.hpp | 7 +- + src/meson.build | 3 + + src/rendervulkan.cpp | 230 ++-- + src/rendervulkan.hpp | 25 +- + src/sdlscancodetable.hpp | 24 +- + src/sdlwindow.cpp | 1274 ++++++++++++++-------- + src/sdlwindow.hpp | 23 - + src/steamcompmgr.cpp | 802 ++++---------- + src/steamcompmgr.hpp | 7 +- + src/steamcompmgr_shared.hpp | 4 +- + src/vblankmanager.cpp | 80 +- + src/vblankmanager.hpp | 6 +- + src/vr_session.cpp | 1037 +++++++++++------- + src/vr_session.hpp | 31 - + src/waitable.h | 1 + + src/wlserver.cpp | 96 +- + src/wlserver.hpp | 8 +- + src/xwayland_ctx.hpp | 3 +- + 33 files changed, 4223 insertions(+), 3250 deletions(-) + create mode 100644 src/backend.cpp + create mode 100644 src/backend.h + create mode 100644 src/backends.h + delete mode 100644 src/drm.hpp + create mode 100644 src/edid.cpp + create mode 100644 src/edid.h + create mode 100644 src/hdmi.h + create mode 100644 src/headless.cpp + delete mode 100644 src/sdlwindow.hpp + delete mode 100644 src/vr_session.hpp + +diff --git a/src/backend.cpp b/src/backend.cpp +new file mode 100644 +index 000000000..b8eeaa9a9 +--- /dev/null ++++ b/src/backend.cpp +@@ -0,0 +1,60 @@ ++#include "backend.h" ++#include "vblankmanager.hpp" ++ ++extern void sleep_until_nanos(uint64_t nanos); ++extern bool env_to_bool(const char *env); ++ ++namespace gamescope ++{ ++ ///////////// ++ // IBackend ++ ///////////// ++ ++ static IBackend *s_pBackend = nullptr; ++ ++ IBackend *IBackend::Get() ++ { ++ return s_pBackend; ++ } ++ ++ bool IBackend::Set( IBackend *pBackend ) ++ { ++ if ( s_pBackend ) ++ { ++ delete s_pBackend; ++ s_pBackend = nullptr; ++ } ++ ++ s_pBackend = pBackend; ++ if ( !s_pBackend->Init() ) ++ { ++ delete s_pBackend; ++ s_pBackend = nullptr; ++ return false; ++ } ++ ++ return true; ++ } ++ ++ ///////////////// ++ // CBaseBackend ++ ///////////////// ++ ++ bool CBaseBackend::NeedsFrameSync() const ++ { ++ const bool bForceTimerFd = env_to_bool( getenv( "GAMESCOPE_DISABLE_TIMERFD" ) ); ++ return bForceTimerFd; ++ } ++ ++ INestedHints *CBaseBackend::GetNestedHints() ++ { ++ return nullptr; ++ } ++ ++ VBlankScheduleTime CBaseBackend::FrameSync() ++ { ++ VBlankScheduleTime schedule = GetVBlankTimer().CalcNextWakeupTime( false ); ++ sleep_until_nanos( schedule.ulScheduledWakeupPoint ); ++ return schedule; ++ } ++} +diff --git a/src/backend.h b/src/backend.h +new file mode 100644 +index 000000000..37e93345b +--- /dev/null ++++ b/src/backend.h +@@ -0,0 +1,284 @@ ++#pragma once ++ ++#include "color_helpers.h" ++#include "gamescope_shared.h" ++ ++#include "vulkan/vulkan_core.h" ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct wlr_buffer; ++struct wlr_dmabuf_attributes; ++ ++struct FrameInfo_t; ++ ++namespace gamescope ++{ ++ struct VBlankScheduleTime; ++ class BackendBlob; ++ ++ 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. ++ bool bExposeHDRSupport = 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. ++ EOTF eOutputEncodingEOTF = EOTF_Gamma22; ++ ++ 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 ShouldPatchEDID() const ++ { ++ return IsHDRG22(); ++ } ++ ++ bool IsHDR10() const ++ { ++ // PQ output encoding is always HDR10 (PQ + 2020) for us. ++ // If that assumption changes, update me. ++ return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_PQ; ++ } ++ }; ++ ++ struct BackendMode ++ { ++ uint32_t uWidth; ++ uint32_t uHeight; ++ uint32_t uRefresh; // Hz ++ }; ++ ++ class IBackendConnector ++ { ++ public: ++ virtual ~IBackendConnector() {} ++ ++ 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 std::span GetModes() const = 0; ++ ++ virtual bool SupportsVRR() 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; ++ ++ virtual const char *GetName() const = 0; ++ virtual const char *GetMake() const = 0; ++ virtual const char *GetModel() const = 0; ++ }; ++ ++ class INestedHints ++ { ++ public: ++ virtual ~INestedHints() {} ++ ++ struct CursorInfo ++ { ++ std::vector pPixels; ++ 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 std::optional GetHostCursor() = 0; ++ }; ++ ++ struct BackendPresentFeedback ++ { ++ public: ++ 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(); } ++ ++ std::atomic m_uQueuedPresents = { 0u }; ++ std::atomic m_uCompletedPresents = { 0u }; ++ }; ++ ++ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const = 0; ++ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const = 0; ++ ++ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) = 0; ++ virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) = 0; ++ virtual bool PollState() = 0; ++ ++ virtual std::shared_ptr CreateBackendBlob( 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( std::span( pBegin, pEnd ) ); ++ } ++ ++ // For DRM, this is ++ // dmabuf -> fb_id. ++ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) = 0; ++ virtual void LockBackendFb( uint32_t uFbId ) = 0; ++ virtual void UnlockBackendFb( uint32_t uFbId ) = 0; ++ virtual void DropBackendFb( uint32_t uFbId ) = 0; ++ ++ virtual bool UsesModifiers() const = 0; ++ virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const = 0; ++ ++ virtual IBackendConnector *GetCurrentConnector() = 0; ++ virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) = 0; ++ ++ // Might want to move this to connector someday, but it lives in CRTC. ++ virtual bool IsVRRActive() const = 0; ++ ++ virtual bool SupportsPlaneHardwareCursor() const = 0; ++ virtual bool SupportsTearing() const = 0; ++ ++ virtual bool UsesVulkanSwapchain() const = 0; ++ virtual bool IsSessionBased() const = 0; ++ ++ // Dumb helper we should remove to support multi display someday. ++ gamescope::GamescopeScreenType GetScreenType() ++ { ++ if ( GetCurrentConnector() ) ++ return GetCurrentConnector()->GetScreenType(); ++ ++ return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; ++ } ++ ++ virtual bool IsVisible() const = 0; ++ virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const = 0; ++ ++ virtual INestedHints *GetNestedHints() = 0; ++ ++ // This will move to the connector and be deprecated soon. ++ virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) = 0; ++ virtual void HackUpdatePatchedEdid() = 0; ++ ++ virtual bool NeedsFrameSync() const = 0; ++ virtual VBlankScheduleTime FrameSync() = 0; ++ ++ // TODO: Make me const someday. ++ virtual BackendPresentFeedback& PresentationFeedback() = 0; ++ ++ static IBackend *Get(); ++ template ++ static bool Set(); ++ protected: ++ friend BackendBlob; ++ ++ virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) = 0; ++ private: ++ static bool Set( IBackend *pBackend ); ++ }; ++ ++ ++ class CBaseBackend : public IBackend ++ { ++ public: ++ virtual INestedHints *GetNestedHints() override; ++ ++ virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override { return false; } ++ virtual void HackUpdatePatchedEdid() override {} ++ ++ virtual bool NeedsFrameSync() const override; ++ virtual VBlankScheduleTime FrameSync() override; ++ ++ virtual BackendPresentFeedback& PresentationFeedback() override { return m_PresentFeedback; } ++ protected: ++ BackendPresentFeedback m_PresentFeedback{}; ++ }; ++ ++ // This is a blob of data that may be associated with ++ // a backend if it needs to be. ++ // Currently on non-DRM backends this is basically a ++ // no-op. ++ class BackendBlob ++ { ++ public: ++ BackendBlob() ++ { ++ } ++ ++ 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() ++ { ++ if ( m_bOwned ) ++ IBackend::Get()->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 ++ { ++ assert( sizeof( T ) == m_Data.size() ); ++ return *reinterpret_cast( m_Data.data() ); ++ } ++ uint32_t GetBlobValue() const { return m_uBlob; } ++ ++ private: ++ std::vector m_Data; ++ uint32_t m_uBlob = 0; ++ bool m_bOwned = false; ++ }; ++} ++ ++inline gamescope::IBackend *GetBackend() ++{ ++ return gamescope::IBackend::Get(); ++} ++ +diff --git a/src/backends.h b/src/backends.h +new file mode 100644 +index 000000000..11a54bd04 +--- /dev/null ++++ b/src/backends.h +@@ -0,0 +1,20 @@ ++#pragma once ++ ++namespace gamescope ++{ ++ // Backend enum. ++ enum GamescopeBackend ++ { ++ Auto, ++ DRM, ++ SDL, ++ OpenVR, ++ Headless, ++ }; ++ ++ // Backend forward declarations. ++ class CSDLBackend; ++ class CDRMBackend; ++ class COpenVRBackend; ++ class CHeadlessBackend; ++} +diff --git a/src/color_bench.cpp b/src/color_bench.cpp +index bde6dd19c..33dff78c0 100644 +--- a/src/color_bench.cpp ++++ b/src/color_bench.cpp +@@ -40,16 +40,16 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) + colorMapping, nightmode, tonemapping, nullptr, flGain ); + for ( size_t i=0, end = lut1d_float.dataR.size(); i // glm::vec2 + #include // glm::vec3 + #include // glm::mat3 ++#include + + // Color utils + inline int quantize( float fVal, float fMaxVal ) +@@ -17,7 +18,7 @@ inline int quantize( float fVal, float fMaxVal ) + return std::max( 0.f, std::min( fMaxVal, rintf( fVal * fMaxVal ) ) ); + } + +-inline uint16_t drm_quantize_lut_value( float flValue ) ++inline uint16_t quantize_lut_value_16bit( float flValue ) + { + return (uint16_t)quantize( flValue, (float)UINT16_MAX ); + } +diff --git a/src/color_tests.cpp b/src/color_tests.cpp +index 61da1b74b..66aae90d7 100644 +--- a/src/color_tests.cpp ++++ b/src/color_tests.cpp +@@ -41,16 +41,16 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) + colorMapping, nightmode, tonemapping, nullptr, flGain ); + for ( size_t i=0, end = lut1d_float.dataR.size(); i +-#include +-#include +-#include +-#include +- + #include + #include + #include +@@ -14,33 +8,430 @@ + #include + #include + +-#include "wlr_begin.hpp" +-#include +-#include "wlr_end.hpp" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include + +-#include "drm.hpp" ++#include "backend.h" ++#include "color_helpers.h" + #include "defer.hpp" ++#include "drm_include.h" ++#include "edid.h" ++#include "gamescope_shared.h" ++#include "gpuvis_trace_utils.h" ++#include "log.hpp" + #include "main.hpp" + #include "modegen.hpp" ++#include "rendervulkan.hpp" ++#include "steamcompmgr.hpp" + #include "vblankmanager.hpp" + #include "wlserver.hpp" +-#include "log.hpp" +- +-#include "gpuvis_trace_utils.h" +-#include "steamcompmgr.hpp" + +-#include +-#include +-#include +- +-extern "C" { ++#include "wlr_begin.hpp" ++#include ++#include + #include "libdisplay-info/info.h" + #include "libdisplay-info/edid.h" + #include "libdisplay-info/cta.h" +-} ++#include "wlr_end.hpp" + + #include "gamescope-control-protocol.h" + ++ ++namespace gamescope ++{ ++ 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 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 VALVE1_PLANE_DEGAMMA_TF; ++ std::optional VALVE1_PLANE_DEGAMMA_LUT; ++ std::optional VALVE1_PLANE_CTM; ++ std::optional VALVE1_PLANE_HDR_MULT; ++ std::optional VALVE1_PLANE_SHAPER_LUT; ++ std::optional VALVE1_PLANE_SHAPER_TF; ++ std::optional VALVE1_PLANE_LUT3D; ++ std::optional VALVE1_PLANE_BLEND_TF; ++ std::optional VALVE1_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 VALVE1_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 IBackendConnector, public CDRMAtomicTypedObject ++ { ++ public: ++ CDRMConnector( 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 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; } ++ uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } ++ std::span GetValidDynamicRefreshRates() const override { return m_Mutable.ValidDynamicRefreshRates; } ++ GamescopeKnownDisplays GetKnownDisplayType() const { return m_Mutable.eKnownDisplay; } ++ 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 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 std::span GetModes() const override { return m_Mutable.BackendModes; } ++ ++ bool SupportsVRR() const override ++ { ++ return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); ++ } ++ ++ 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; ++ } ++ } ++ ++ void UpdateEffectiveOrientation( const drmModeModeInfo *pMode ); ++ ++ private: ++ void ParseEDID(); ++ ++ static std::optional GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ); ++ ++ CAutoDeletePtr m_pConnector; ++ ++ struct MutableConnectorState ++ { ++ int nDefaultRefresh = 0; ++ ++ uint32_t uPossibleCRTCMask = 0u; ++ char szName[32]{}; ++ char szMakePNP[4]{}; ++ char szModel[16]{}; ++ const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. ++ GamescopeKnownDisplays eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_UNKNOWN; ++ std::span ValidDynamicRefreshRates{}; ++ std::vector EdidData; // Raw, unmodified. ++ std::vector BackendModes; ++ ++ displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; ++ BackendConnectorHDRInfo HDR; ++ } m_Mutable; ++ ++ GamescopePanelOrientation m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_AUTO; ++ ++ ConnectorProperties m_Props; ++ }; ++} ++ ++struct saved_mode { ++ int width; ++ int height; ++ int refresh; ++}; ++ ++struct fb { ++ uint32_t id; ++ /* Client buffer, if any */ ++ struct wlr_buffer *buf; ++ /* A FB is held if it's being used by steamcompmgr ++ * doesn't need to be atomic as it's only ever ++ * modified/read from the steamcompmgr thread */ ++ int held_refs; ++ /* Number of page-flips using the FB */ ++ std::atomic< uint32_t > n_refs; ++}; ++ ++struct drm_t { ++ bool bUseLiftoff; ++ ++ int fd; ++ ++ int preferred_width, preferred_height, preferred_refresh; ++ ++ 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::map< uint32_t, drmModePropertyRes * > props; ++ ++ gamescope::CDRMPlane *pPrimaryPlane; ++ gamescope::CDRMCRTC *pCRTC; ++ gamescope::CDRMConnector *pConnector; ++ int kms_in_fence_fd; ++ int kms_out_fence_fd; ++ ++ struct wlr_drm_format_set primary_formats; ++ ++ drmModeAtomicReq *req; ++ uint32_t flags; ++ ++ struct liftoff_device *lo_device; ++ struct liftoff_output *lo_output; ++ struct liftoff_layer *lo_layers[ k_nMaxLayers ]; ++ ++ std::shared_ptr sdr_static_metadata; ++ ++ struct { ++ std::shared_ptr mode_id; ++ uint32_t color_mgmt_serial; ++ std::shared_ptr lut3d_id[ EOTF_Count ]; ++ std::shared_ptr shaperlut_id[ EOTF_Count ]; ++ drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; ++ } current, pending; ++ ++ /* FBs in the atomic request, but not yet submitted to KMS */ ++ std::vector < uint32_t > fbids_in_req; ++ /* FBs submitted to KMS, but not yet displayed on screen */ ++ std::vector < uint32_t > fbids_queued; ++ /* FBs currently on screen */ ++ std::vector < uint32_t > fbids_on_screen; ++ ++ std::unordered_map< uint32_t, struct fb > fb_map; ++ std::mutex fb_map_mutex; ++ ++ std::mutex free_queue_lock; ++ std::vector< uint32_t > fbid_unlock_queue; ++ std::vector< uint32_t > fbid_free_queue; ++ ++ std::mutex flip_lock; ++ ++ std::atomic < bool > paused; ++ std::atomic < int > out_of_date; ++ std::atomic < bool > needs_modeset; ++ ++ std::unordered_map< std::string, int > connector_priorities; ++ ++ 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 = {}; +@@ -48,8 +439,17 @@ struct drm_t g_DRM = {}; + 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; +-bool g_bDebugLayers = false; +-const char *g_sOutputName = nullptr; ++extern bool g_bDebugLayers; ++ ++struct DRMPresentCtx ++{ ++ uint64_t ulPendingFlipCount = 0; ++}; ++ ++extern bool alwaysComposite; ++extern bool g_bColorSliderInUse; ++extern bool fadingOut; ++extern std::string g_reshade_effect; + + #ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP + #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 +@@ -69,11 +469,10 @@ struct drm_color_ctm2 { + + bool g_bSupportsAsyncFlips = false; + +-gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; +-enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; +-std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; ++extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; ++extern GamescopePanelOrientation g_DesiredInternalOrientation; + +-bool g_bForceDisableColorMgmt = false; ++extern bool g_bForceDisableColorMgmt; + + static LogScope drm_log("drm"); + static LogScope drm_verbose_log("drm", LOG_SILENT); +@@ -100,51 +499,12 @@ static constexpr uint32_t s_kSteamDeckOLEDRates[] = + 90, + }; + +-static uint32_t get_conn_display_info_flags( struct drm_t *drm, gamescope::CDRMConnector *pConnector ) +-{ +- if ( !pConnector ) +- return 0; +- +- uint32_t flags = 0; +- if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) +- flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; +- if ( pConnector->IsVRRCapable() ) +- flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; +- if ( pConnector->GetHDRInfo().bExposeHDRSupport ) +- flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; +- +- return flags; +-} +- +-void drm_send_gamescope_control(wl_resource *control, struct drm_t *drm) +-{ +- // assumes wlserver_lock HELD! +- +- if ( !drm->pConnector ) +- return; +- +- auto& conn = drm->pConnector; +- +- uint32_t flags = get_conn_display_info_flags( drm, drm->pConnector ); +- +- struct wl_array display_rates; +- wl_array_init(&display_rates); +- if ( conn->GetValidDynamicRefreshRates().size() ) +- { +- size_t size = conn->GetValidDynamicRefreshRates().size() * sizeof(uint32_t); +- uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); +- memcpy( ptr, conn->GetValidDynamicRefreshRates().data(), size ); +- } +- gamescope_control_send_active_display_info( control, drm->pConnector->GetName(), drm->pConnector->GetMake(), drm->pConnector->GetModel(), flags, &display_rates ); +- wl_array_release(&display_rates); +-} +- + static void update_connector_display_info_wl(struct drm_t *drm) + { + wlserver_lock(); + for ( const auto &control : wlserver.gamescope_controls ) + { +- drm_send_gamescope_control(control, drm); ++ wlserver_send_gamescope_control( control ); + } + wlserver_unlock(); + } +@@ -264,13 +624,13 @@ static gamescope::CDRMPlane *find_primary_plane(struct drm_t *drm) + + static void drm_unlock_fb_internal( struct drm_t *drm, struct fb *fb ); + +-std::atomic g_nCompletedPageFlipCount = { 0u }; +- + 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) + { +- uint64_t flipcount = (uint64_t)data; +- g_nCompletedPageFlipCount = flipcount; ++ DRMPresentCtx *pCtx = reinterpret_cast( data ); ++ ++ // Make this const when we move into CDRMBackend. ++ GetBackend()->PresentationFeedback().m_uCompletedPresents = pCtx->ulPendingFlipCount; + + if ( !g_DRM.pCRTC ) + return; +@@ -280,12 +640,12 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi + + // This is the last vblank time + uint64_t vblanktime = sec * 1'000'000'000lu + usec * 1'000lu; +- g_VBlankTimer.MarkVBlank( vblanktime, true ); ++ GetVBlankTimer().MarkVBlank( vblanktime, true ); + + // TODO: get the fbids_queued instance from data if we ever have more than one in flight + +- drm_verbose_log.debugf("page_flip_handler %" PRIu64, flipcount); +- gpuvis_trace_printf("page_flip_handler %" PRIu64, flipcount); ++ drm_verbose_log.debugf("page_flip_handler %" PRIu64, pCtx->ulPendingFlipCount); ++ gpuvis_trace_printf("page_flip_handler %" PRIu64, pCtx->ulPendingFlipCount); + + for ( uint32_t i = 0; i < g_DRM.fbids_on_screen.size(); i++ ) + { +@@ -360,362 +720,98 @@ void flip_handler_thread_run(void) + } + } + +-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_CVT_TIMING_CODES_COUNT = 4; +- +-static inline uint8_t get_bit_range(uint8_t val, size_t high, size_t low) +-{ +- size_t n; +- uint8_t bitmask; +- +- assert(high <= 7 && high >= low); +- +- 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 bool refresh_state( drm_t *drm ) + { +- size_t n; +- uint8_t bitmask; +- +- assert(high <= 7 && high >= low); ++ drmModeRes *pResources = drmModeGetResources( drm->fd ); ++ if ( pResources == nullptr ) ++ { ++ drm_log.errorf_errno( "drmModeGetResources failed" ); ++ return false; ++ } ++ defer( drmModeFreeResources( pResources ) ); + +- n = high - low + 1; +- bitmask = (uint8_t) ((1 << n) - 1); +- assert((bits & ~bitmask) == 0); ++ // Add connectors which appeared ++ for ( int i = 0; i < pResources->count_connectors; i++ ) ++ { ++ uint32_t uConnectorId = pResources->connectors[i]; + +- *val |= (uint8_t)(bits << low); +-} ++ drmModeConnector *pConnector = drmModeGetConnector( drm->fd, uConnectorId ); ++ if ( !pConnector ) ++ continue; + ++ if ( !drm->connectors.contains( uConnectorId ) ) ++ { ++ drm->connectors.emplace( ++ std::piecewise_construct, ++ std::forward_as_tuple( uConnectorId ), ++ std::forward_as_tuple( pConnector ) ); ++ } ++ } + +-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]; ++ // Remove connectors which disappeared ++ for ( auto iter = drm->connectors.begin(); iter != drm->connectors.end(); ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter->second; + +- uint8_t checksum = uint32_t(256) - uint32_t(sum); ++ const bool bFound = std::any_of( ++ pResources->connectors, ++ pResources->connectors + pResources->count_connectors, ++ std::bind_front( std::equal_to{}, pConnector->GetObjectId() ) ); + +- block[127] = checksum; +-} ++ if ( !bFound ) ++ { ++ drm_log.debugf( "Connector '%s' disappeared.", pConnector->GetName() ); + +-static bool validate_block_checksum(const uint8_t* data) +-{ +- uint8_t sum = 0; +- size_t i; ++ if ( drm->pConnector == pConnector ) ++ { ++ drm_log.infof( "Current connector '%s' disappeared.", pConnector->GetName() ); ++ drm->pConnector = nullptr; ++ } + +- for (i = 0; i < EDID_BLOCK_SIZE; i++) { +- sum += data[i]; ++ iter = drm->connectors.erase( iter ); ++ } ++ else ++ iter++; + } + +- return sum == 0; +-} ++ // Re-probe connectors props and status) ++ for ( auto &iter : drm->connectors ) ++ { ++ gamescope::CDRMConnector *pConnector = &iter.second; ++ pConnector->RefreshState(); ++ } + +-const char *drm_get_patched_edid_path() +-{ +- return getenv("GAMESCOPE_PATCHED_EDID_FILE"); +-} ++ for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) ++ pCRTC->RefreshState(); + +-static uint8_t encode_max_luminance(float nits) +-{ +- if (nits == 0.0f) +- return 0; ++ for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) ++ pPlane->RefreshState(); + +- return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); ++ return true; + } + +-static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm_t *drm, gamescope::CDRMConnector *conn ) ++static bool get_resources(struct drm_t *drm) + { +- // A zero length indicates that the edid parsing failed. +- if (orig_size == 0) { +- return; +- } +- +- std::vector edid(orig_data, orig_data + orig_size); +- +- if ( g_bRotated ) + { +- // Patch width, height. +- drm_log.infof("[patched edid] 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++) ++ drmModeRes *pResources = drmModeGetResources( drm->fd ); ++ if ( !pResources ) + { +- 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]; +- drm_log.infof("[patched edid] 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; +- } ++ drm_log.errorf_errno( "drmModeGetResources failed" ); ++ return false; + } ++ defer( drmModeFreeResources( pResources ) ); + +- patch_edid_checksum(&edid[0]); ++ 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 ) ); ++ } + } + +- // 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 ( conn->GetHDRInfo().ShouldPatchEDID() ) + { +- // TODO: Allow for override of min luminance +- float flMaxPeakLuminance = g_ColorMgmt.pending.hdrTonemapDisplayMetadata.BIsValid() ? +- g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits : +- g_ColorMgmt.pending.flInternalDisplayBrightness; +- drm_log.infof("[edid] Patching HDR static metadata. max peak luminance/max frame avg luminance = %f nits", flMaxPeakLuminance ); +- 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(flMaxPeakLuminance), /* desired content max peak luminance */ +- encode_max_luminance(flMaxPeakLuminance * 0.8f), /* 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; +- bool has_hdr_metadata_block = false; +- +- 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) +- { +- has_cta_block = true; +- uint8_t dtd_start = ext_data[2]; +- uint8_t flags = ext_data[3]; +- if (dtd_start == 0) +- { +- drm_log.infof("[josh edid] Hmmmm.... dtd start is 0. Interesting... Not going further! :-("); +- continue; +- } +- if (flags != 0) +- { +- drm_log.infof("[josh edid] 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) +- { +- 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) +- { +- drm_log.infof("[josh edid] Hmmmm.... CTA malformatted. Interesting... Not going further! :-("); +- break; +- } +- +- 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; +- +- if (extended_tag == 6) // hdr static +- { +- if (extended_block_size >= sizeof(new_hdr_static_metadata_block)) +- { +- drm_log.infof("[josh edid] 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; +- } +- } +- } +- +- j += 1 + data_block_size; // account for header size. +- } +- +- 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 +- drm_log.infof("[josh edid] No HDR metadata block to patch... Trying to insert one."); +- +- // Assert that the end of the data blocks == dtd_start +- if (dtd_start != j) +- { +- drm_log.infof("[josh edid] 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); +- 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) +- +- // 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)); +- } +- +- patch_edid_checksum(ext_data); +- bool sum_valid = validate_block_checksum(ext_data); +- drm_log.infof("[josh edid] CTA Checksum valid? %s", sum_valid ? "Y" : "N"); +- } +- } +- +- if (!has_cta_block) +- { +- drm_log.infof("[josh edid] Couldn't patch for HDR metadata as we had no CTA block! Womp womp =c"); +- } +- } +- +- bool sum_valid = validate_block_checksum(&edid[0]); +- drm_log.infof("[josh edid] BASE Checksum valid? %s", sum_valid ? "Y" : "N"); +- +- // Write it out then flip it over atomically. +- +- const char *filename = drm_get_patched_edid_path(); +- if (!filename) +- { +- drm_log.errorf("[josh edid] Couldn't write patched edid. No Path."); +- return; +- } +- +- char filename_tmp[PATH_MAX]; +- snprintf(filename_tmp, sizeof(filename_tmp), "%s.tmp", filename); +- +- FILE *file = fopen(filename_tmp, "wb"); +- if (!file) +- { +- drm_log.errorf("[josh edid] Couldn't open file: %s", filename_tmp); +- return; +- } +- +- fwrite(edid.data(), 1, edid.size(), file); +- fflush(file); +- fclose(file); +- +- rename(filename_tmp, filename); +- drm_log.infof("[josh edid] Wrote new edid to: %s", filename); +-} +- +-void drm_update_patched_edid( drm_t *drm ) +-{ +- if (!drm || !drm->pConnector) +- return; +- +- create_patched_edid(drm->pConnector->GetRawEDID().data(), drm->pConnector->GetRawEDID().size(), drm, drm->pConnector); +-} +- +-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 ) ); +- +- // 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 ( !drm->connectors.contains( uConnectorId ) ) +- { +- drm->connectors.emplace( +- std::piecewise_construct, +- std::forward_as_tuple( uConnectorId ), +- std::forward_as_tuple( 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; +-} +- +-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 ) ++ drmModePlaneRes *pPlaneResources = drmModeGetPlaneResources( drm->fd ); ++ if ( !pPlaneResources ) + { + drm_log.errorf_errno( "drmModeGetPlaneResources failed" ); + return false; +@@ -845,7 +941,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + if ( pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED ) + continue; + +- if ( drm->force_internal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) ++ if ( g_bForceInternal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) + continue; + + int nPriority = get_connector_priority( drm, pConnector->GetName() ); +@@ -912,8 +1008,6 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + return false; + } + +- best->SetBaseRefresh( mode->vrefresh ); +- + if (!drm_set_mode(drm, mode)) { + return false; + } +@@ -928,7 +1022,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + wlserver_unlock(); + + if (!initial) +- create_patched_edid(best->GetRawEDID().data(), best->GetRawEDID().size(), drm, best); ++ WritePatchedEdid( best->GetRawEDID(), best->GetHDRInfo() ); + + update_connector_display_info_wl( drm ); + +@@ -1132,9 +1226,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh) + + hdr_output_metadata sdr_metadata; + memset(&sdr_metadata, 0, sizeof(sdr_metadata)); +- drm->sdr_static_metadata = drm_create_hdr_metadata_blob(drm, &sdr_metadata); ++ drm->sdr_static_metadata = GetBackend()->CreateBackendBlob( sdr_metadata ); + +- drm->flipcount = 0; + drm->needs_modeset = true; + + return true; +@@ -1161,7 +1254,7 @@ void finish_drm(struct drm_t *drm) + 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->blob, true ); ++ pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, drm->sdr_static_metadata->GetBlobValue(), true ); + else + pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, 0, true ); + } +@@ -1259,164 +1352,6 @@ void finish_drm(struct drm_t *drm) + // page-flip handler thread. + } + +-int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) +-{ +- int ret; +- +- assert( drm->req != nullptr ); +- +-// if (drm->kms_in_fence_fd != -1) { +-// add_plane_property(req, plane_id, "IN_FENCE_FD", drm->kms_in_fence_fd); +-// } +- +-// drm->kms_out_fence_fd = -1; +- +-// add_crtc_property(req, drm->crtc_id, "OUT_FENCE_PTR", +-// (uint64_t)(unsigned long)&drm->kms_out_fence_fd); +- +- +- assert( drm->fbids_queued.size() == 0 ); +- +- bool isPageFlip = drm->flags & DRM_MODE_PAGE_FLIP_EVENT; +- +- if ( isPageFlip ) { +- drm->flip_lock.lock(); +- +- // Do it before the commit, as otherwise the pageflip handler could +- // potentially beat us to the refcount checks. +- for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) +- { +- struct fb &fb = get_fb( g_DRM, drm->fbids_in_req[ i ] ); +- assert( fb.held_refs ); +- fb.n_refs++; +- } +- +- drm->fbids_queued = drm->fbids_in_req; +- } +- +- g_DRM.flipcount++; +- +- drm_verbose_log.debugf("flip commit %" PRIu64, (uint64_t)g_DRM.flipcount); +- gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)g_DRM.flipcount ); +- +- ret = drmModeAtomicCommit(drm->fd, drm->req, drm->flags, (void*)(uint64_t)g_DRM.flipcount ); +- if ( ret != 0 ) +- { +- drm_log.errorf_errno( "flip error" ); +- +- if ( ret != -EBUSY && ret != -EACCES ) +- { +- drm_log.errorf( "fatal flip error, aborting" ); +- if ( isPageFlip ) +- drm->flip_lock.unlock(); +- abort(); +- } +- +- 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(); +- } +- } +- +- // Undo refcount if the commit didn't actually work +- for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) +- { +- get_fb( g_DRM, drm->fbids_in_req[ i ] ).n_refs--; +- } +- +- drm->fbids_queued.clear(); +- +- g_DRM.flipcount--; +- +- if ( isPageFlip ) +- drm->flip_lock.unlock(); +- +- goto out; +- } else { +- drm->fbids_in_req.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. +- g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); +- +- if ( isPageFlip ) { +- // Wait for flip handler to unlock +- drm->flip_lock.lock(); +- drm->flip_lock.unlock(); +- } +- +-// if (drm->kms_in_fence_fd != -1) { +-// close(drm->kms_in_fence_fd); +-// drm->kms_in_fence_fd = -1; +-// } +-// +-// drm->kms_in_fence_fd = drm->kms_out_fence_fd; +- +-out: +- drmModeAtomicFree( drm->req ); +- drm->req = nullptr; +- +- return ret; +-} +- + uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ) + { + uint32_t fb_id = 0; +@@ -1571,93 +1506,35 @@ void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ) + drm_unlock_fb_internal( drm, &fb ); + } + +-static uint64_t determine_drm_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) ++static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) + { +- if ( pConnector && pConnector->GetProperties().panel_orientation ) ++ gamescope::IBackendConnector *pInternalConnector = GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ); ++ if ( pInternalConnector ) + { +- switch ( pConnector->GetProperties().panel_orientation->GetCurrentValue() ) +- { +- case DRM_MODE_PANEL_ORIENTATION_NORMAL: +- return DRM_MODE_ROTATE_0; +- case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: +- return DRM_MODE_ROTATE_180; +- case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: +- return DRM_MODE_ROTATE_90; +- case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: +- return DRM_MODE_ROTATE_270; +- } ++ 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 ); + } + +- if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && mode ) ++ gamescope::IBackendConnector *pExternalConnector = GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ); ++ if ( pExternalConnector ) + { +- // Auto-detect portait mode for internal displays +- return mode->hdisplay < mode->vdisplay ? DRM_MODE_ROTATE_270 : DRM_MODE_ROTATE_0; +- } ++ gamescope::CDRMConnector *pDRMExternalConnector = static_cast( pExternalConnector ); ++ const drmModeModeInfo *pExternalMode = pMode; ++ if ( pDRMExternalConnector != drm->pConnector ) ++ pExternalMode = find_mode( pDRMExternalConnector->GetModeConnector(), 0, 0, 0 ); + +- return DRM_MODE_ROTATE_0; ++ pDRMExternalConnector->UpdateEffectiveOrientation( pExternalMode ); ++ } + } + +-/* Handle the orientation of the display */ +-static void update_drm_effective_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) ++// Only used for NV12 buffers ++static drm_color_encoding drm_get_color_encoding(EStreamColorspace colorspace) + { +- gamescope::GamescopeScreenType eScreenType = pConnector->GetScreenType(); +- +- if ( eScreenType == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) +- { +- switch ( g_drmModeOrientation ) +- { +- case PANEL_ORIENTATION_0: +- g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_0; +- break; +- case PANEL_ORIENTATION_90: +- g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_90; +- break; +- case PANEL_ORIENTATION_180: +- g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_180; +- break; +- case PANEL_ORIENTATION_270: +- g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_270; +- break; +- case PANEL_ORIENTATION_AUTO: +- g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); +- break; +- } +- } +- else +- { +- g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); +- } +-} +- +-static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) +-{ +- gamescope::CDRMConnector *pInternalConnector = nullptr; +- if ( drm->pConnector && drm->pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) +- pInternalConnector = drm->pConnector; +- +- if ( !pInternalConnector ) +- { +- for ( auto &iter : drm->connectors ) +- { +- gamescope::CDRMConnector *pConnector = &iter.second; +- if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) +- { +- pInternalConnector = pConnector; +- // Find mode for internal connector instead. +- pMode = find_mode(pInternalConnector->GetModeConnector(), 0, 0, 0); +- break; +- } +- } +- } +- +- if ( pInternalConnector ) +- update_drm_effective_orientation( drm, pInternalConnector, pMode ); +-} +- +-// Only used for NV12 buffers +-static drm_color_encoding drm_get_color_encoding(EStreamColorspace colorspace) +-{ +- switch (colorspace) ++ switch (colorspace) + { + default: + case k_EStreamColorspace_Unknown: +@@ -2095,6 +1972,17 @@ namespace gamescope + 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 ) + { +@@ -2110,6 +1998,49 @@ namespace gamescope + ParseEDID(); + } + ++ 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 ) +@@ -2240,7 +2171,7 @@ namespace gamescope + ///////////////////// + // Parse HDR stuff. + ///////////////////// +- std::optional oKnownHDRInfo = GetKnownDisplayHDRInfo( m_Mutable.eKnownDisplay ); ++ std::optional oKnownHDRInfo = GetKnownDisplayHDRInfo( m_Mutable.eKnownDisplay ); + if ( oKnownHDRInfo ) + { + m_Mutable.HDR = *oKnownHDRInfo; +@@ -2319,7 +2250,7 @@ namespace gamescope + pInfoframe->max_fall = uDefaultInfoframeLuminances; + pInfoframe->eotf = HDMI_EOTF_ST2084; + +- m_Mutable.HDR.pDefaultMetadataBlob = drm_create_hdr_metadata_blob( &g_DRM, &defaultHDRMetadata ); ++ m_Mutable.HDR.pDefaultMetadataBlob = GetBackend()->CreateBackendBlob( defaultHDRMetadata ); + } + else + { +@@ -2328,14 +2259,14 @@ namespace gamescope + } + } + +- /*static*/ std::optional CDRMConnector::GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ) ++ /*static*/ std::optional CDRMConnector::GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ) + { + if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE || eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC ) + { + // The stuff in the EDID for the HDR metadata does not fully + // reflect what we can achieve on the display by poking at more + // things out-of-band. +- return HDRInfo ++ return BackendConnectorHDRInfo + { + .bExposeHDRSupport = true, + .eOutputEncodingEOTF = EOTF_Gamma22, +@@ -2347,7 +2278,7 @@ namespace gamescope + else if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) + { + // Set up some HDR fallbacks for undocking +- return HDRInfo ++ return BackendConnectorHDRInfo + { + .bExposeHDRSupport = false, + .eOutputEncodingEOTF = EOTF_Gamma22, +@@ -2364,7 +2295,6 @@ namespace gamescope + static int + drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, bool needs_modeset ) + { +- gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); + auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); + + // If we are modesetting, reset the state cache, we might +@@ -2402,7 +2332,24 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + 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 ); + +- liftoff_layer_set_property( drm->lo_layers[ i ], "rotation", g_drmEffectiveOrientation[screenType] ); ++ 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); +@@ -2449,9 +2396,9 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + + if ( !g_bDisableShaperAnd3DLUT ) + { +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", shaper_tf ); +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_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 +@@ -2482,7 +2429,7 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); + + if (frameInfo->layers[i].ctm != nullptr) +- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", frameInfo->layers[i].ctm->blob ); ++ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", frameInfo->layers[i].ctm->GetBlobValue() ); + else + liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); + } +@@ -2552,13 +2499,15 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + uint32_t uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; + + const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; +- wlserver_hdr_metadata *pHDRMetadata = nullptr; ++ gamescope::BackendBlob *pHDRMetadata = nullptr; + if ( drm->pConnector && drm->pConnector->SupportsHDR10() ) + { + if ( bWantsHDR10 ) + { + wlserver_vk_swapchain_feedback* pFeedback = steamcompmgr_get_base_layer_swapchain_feedback(); +- pHDRMetadata = pFeedback ? pFeedback->hdr_metadata_blob.get() : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); ++ pHDRMetadata = pFeedback ++ ? pFeedback->hdr_metadata_blob.get() ++ : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); + uColorimetry = DRM_MODE_COLORIMETRY_BT2020_RGB; + } + else +@@ -2675,7 +2624,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + if ( drm->pCRTC ) + { + drm->pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 1u, true ); +- drm->pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, drm->pending.mode_id ? drm->pending.mode_id->blob : 0lu, 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 ); +@@ -2685,7 +2634,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + if ( drm->pConnector ) + { + if ( drm->pConnector->GetProperties().HDR_OUTPUT_METADATA ) +- drm->pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, pHDRMetadata ? pHDRMetadata->blob : 0lu, bForceInRequest ); ++ 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 ); +@@ -2839,19 +2788,8 @@ bool drm_update_color_mgmt(struct drm_t *drm) + if ( !g_ColorMgmtLuts[i].HasLuts() ) + continue; + +- uint32_t shaper_blob_id = 0; +- if (drmModeCreatePropertyBlob(drm->fd, g_ColorMgmtLuts[i].lut1d, sizeof(g_ColorMgmtLuts[i].lut1d), &shaper_blob_id) != 0) { +- drm_log.errorf_errno("Unable to create SHAPERLUT property blob"); +- return false; +- } +- drm->pending.shaperlut_id[ i ] = std::make_shared( shaper_blob_id ); +- +- uint32_t lut3d_blob_id = 0; +- if (drmModeCreatePropertyBlob(drm->fd, g_ColorMgmtLuts[i].lut3d, sizeof(g_ColorMgmtLuts[i].lut3d), &lut3d_blob_id) != 0) { +- drm_log.errorf_errno("Unable to create LUT3D property blob"); +- return false; +- } +- drm->pending.lut3d_id[ i ] = std::make_shared( lut3d_blob_id ); ++ drm->pending.shaperlut_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut1d ); ++ drm->pending.lut3d_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut3d ); + } + + return true; +@@ -2873,8 +2811,6 @@ static void drm_unset_mode( struct drm_t *drm ) + if (g_nOutputRefresh == 0) + g_nOutputRefresh = 60; + +- g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = DRM_MODE_ROTATE_0; +- g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = DRM_MODE_ROTATE_0; + g_bRotated = false; + } + +@@ -2883,31 +2819,26 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) + if (!drm->pConnector || !drm->pConnector->GetModeConnector()) + return false; + +- uint32_t mode_id = 0; +- if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode), &mode_id) != 0) +- return false; +- +- gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); +- + drm_log.infof("selecting mode %dx%d@%uHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); + +- drm->pending.mode_id = std::make_shared(mode_id); ++ drm->pending.mode_id = GetBackend()->CreateBackendBlob( *mode ); + drm->needs_modeset = true; + + g_nOutputRefresh = mode->vrefresh; + + update_drm_effective_orientations(drm, mode); + +- switch ( g_drmEffectiveOrientation[screenType] ) ++ switch ( drm->pConnector->GetCurrentOrientation() ) + { +- case DRM_MODE_ROTATE_0: +- case DRM_MODE_ROTATE_180: ++ default: ++ case GAMESCOPE_PANEL_ORIENTATION_0: ++ case GAMESCOPE_PANEL_ORIENTATION_180: + g_bRotated = false; + g_nOutputWidth = mode->hdisplay; + g_nOutputHeight = mode->vdisplay; + break; +- case DRM_MODE_ROTATE_90: +- case DRM_MODE_ROTATE_270: ++ case GAMESCOPE_PANEL_ORIENTATION_90: ++ case GAMESCOPE_PANEL_ORIENTATION_270: + g_bRotated = true; + g_nOutputWidth = mode->vdisplay; + g_nOutputHeight = mode->hdisplay; +@@ -2974,37 +2905,10 @@ bool drm_set_resolution( struct drm_t *drm, int width, int height ) + return drm_set_mode(drm, mode); + } + +-int drm_get_default_refresh(struct drm_t *drm) +-{ +- if ( drm->preferred_refresh ) +- return drm->preferred_refresh; +- +- if ( drm->pConnector && drm->pConnector->GetBaseRefresh() ) +- return drm->pConnector->GetBaseRefresh(); +- +- if ( drm->pConnector && drm->pConnector->GetModeConnector() ) +- { +- int width = g_nOutputWidth; +- int height = g_nOutputHeight; +- if ( g_bRotated ) { +- int tmp = width; +- width = height; +- height = tmp; +- } +- +- drmModeConnector *connector = drm->pConnector->GetModeConnector(); +- const drmModeModeInfo *mode = find_mode( connector, width, height, 0); +- if ( mode ) +- return mode->vrefresh; +- } +- +- return 60; +-} +- + bool drm_get_vrr_capable(struct drm_t *drm) + { + if ( drm->pConnector ) +- return drm->pConnector->IsVRRCapable(); ++ return drm->pConnector->SupportsVRR(); + + return false; + } +@@ -3044,102 +2948,662 @@ std::pair drm_get_connector_identifier(struct drm_t *drm) + return std::make_pair(drm->pConnector->GetModeConnector()->connector_type, drm->pConnector->GetModeConnector()->connector_type_id); + } + +-std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata) ++bool drm_supports_color_mgmt(struct drm_t *drm) + { +- uint32_t blob = 0; +- if (!BIsNested()) +- { +- int ret = drmModeCreatePropertyBlob(drm->fd, metadata, sizeof(*metadata), &blob); +- +- if (ret != 0) { +- drm_log.errorf("Failed to create blob for HDR_OUTPUT_METADATA. (%s) Falling back to null blob.", strerror(-ret)); +- blob = 0; +- } +- ++ if ( g_bForceDisableColorMgmt ) ++ return false; + +- if (!blob) +- return nullptr; +- } ++ if ( !drm->pPrimaryPlane ) ++ return false; + +- return std::make_shared(metadata, blob); ++ return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); + } +-void drm_destroy_blob(struct drm_t *drm, uint32_t blob) ++ ++std::span drm_get_valid_refresh_rates( struct drm_t *drm ) + { +- drmModeDestroyPropertyBlob(drm->fd, blob); ++ if ( drm && drm->pConnector ) ++ return drm->pConnector->GetValidDynamicRefreshRates(); ++ ++ return std::span{}; + } + +-std::shared_ptr drm_create_ctm(struct drm_t *drm, glm::mat3x4 ctm) ++namespace gamescope + { +- uint32_t blob = 0; +- if (!BIsNested()) ++ class CDRMBackend; ++ ++ class CDRMBackend final : public CBaseBackend + { +- drm_color_ctm2 ctm2; +- for (uint32_t i = 0; i < 12; i++) ++ public: ++ CDRMBackend() ++ { ++ } ++ ++ virtual ~CDRMBackend() ++ { ++ } ++ ++ virtual bool Init() override + { +- float *data = (float*)&ctm; +- ctm2.matrix[i] = drm_calc_s31_32(data[i]); ++ 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, 0, 0, 0 ); + } + +- int ret = drmModeCreatePropertyBlob(drm->fd, &ctm2, sizeof(ctm2), &blob); ++ virtual bool PostInit() override ++ { ++ if ( g_DRM.pConnector ) ++ WritePatchedEdid( g_DRM.pConnector->GetRawEDID(), g_DRM.pConnector->GetHDRInfo() ); ++ return true; ++ } + +- if (ret != 0) { +- drm_log.errorf("Failed to create blob for CTM. (%s) Falling back to null blob.", strerror(-ret)); +- blob = 0; ++ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override ++ { ++ *pPrimaryPlaneFormat = DRMFormatToVulkan( g_nDRMFormat, false ); ++ *pOverlayPlaneFormat = DRMFormatToVulkan( g_nDRMFormatOverlay, false ); ++ } ++ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override ++ { ++ return true; ++ } ++ ++ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override ++ { ++ 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 |= alwaysComposite; ++ bNeedsFullComposite |= bWasFirstFrame; ++ bNeedsFullComposite |= pFrameInfo->useFSRLayer0; ++ bNeedsFullComposite |= pFrameInfo->useNISLayer0; ++ bNeedsFullComposite |= pFrameInfo->blurLayer0; ++ bNeedsFullComposite |= bNeedsCompositeFromFilter; ++ bNeedsFullComposite |= 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 ); ++ } ++ ++ 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_bWasPartialCompsiting = 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_bWasPartialCompsiting ); ++ ++ // 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 = {}; ++ ++ 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->fbid = baseLayer->tex->fbid(); ++ baseLayer->applyColorMgmt = false; ++ ++ baseLayer->filter = GamescopeUpscaleFilter::NEAREST; ++ baseLayer->ctm = nullptr; ++ baseLayer->colorspace = g_bOutputHDREnabled ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; ++ ++ m_bWasPartialCompsiting = false; ++ } ++ else ++ { ++ if ( m_bWasPartialCompsiting || !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->fbid = overlayLayer->tex->fbid(); ++ 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_bWasPartialCompsiting = 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)); ++ abort(); ++ } ++ ++ 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( std::span data ) override ++ { ++ uint32_t uBlob = 0; ++ if ( drmModeCreatePropertyBlob( g_DRM.fd, data.data(), data.size(), &uBlob ) != 0 ) ++ return nullptr; ++ ++ return std::make_shared( data, uBlob, true ); ++ } ++ ++ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override ++ { ++ return drm_fbid_from_dmabuf( &g_DRM, pBuffer, pDmaBuf ); ++ } ++ ++ virtual void LockBackendFb( uint32_t uFbId ) override ++ { ++ drm_lock_fbid( &g_DRM, uFbId ); ++ } ++ virtual void UnlockBackendFb( uint32_t uFbId ) override ++ { ++ drm_unlock_fbid( &g_DRM, uFbId ); ++ } ++ virtual void DropBackendFb( uint32_t uFbId ) override ++ { ++ drm_drop_fbid( &g_DRM, uFbId ); ++ } ++ ++ virtual bool UsesModifiers() const override ++ { ++ return true; ++ } ++ 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; ++ } ++ } + +- if (!blob) + return nullptr; +- } ++ } + +- return std::make_shared(ctm, blob); +-} ++ 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(); ++ } + +-bool drm_supports_color_mgmt(struct drm_t *drm) +-{ +- if ( g_bForceDisableColorMgmt ) +- return false; ++ virtual bool SupportsPlaneHardwareCursor() const override ++ { ++ return true; ++ } + +- if ( !drm->pPrimaryPlane ) +- return false; ++ virtual bool SupportsTearing() const override ++ { ++ return g_bSupportsAsyncFlips; ++ } + +- return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); +-} ++ virtual bool UsesVulkanSwapchain() const override ++ { ++ return false; ++ } + +-void drm_get_native_colorimetry( struct drm_t *drm, +- displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, +- displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) +-{ +- if ( !drm || !drm->pConnector ) +- { +- *displayColorimetry = displaycolorimetry_709; +- *displayEOTF = EOTF_Gamma22; +- *outputEncodingColorimetry = displaycolorimetry_709; +- *outputEncodingEOTF = EOTF_Gamma22; +- return; +- } ++ virtual bool IsSessionBased() const override ++ { ++ return true; ++ } + +- *displayColorimetry = drm->pConnector->GetDisplayColorimetry(); +- *displayEOTF = EOTF_Gamma22; ++ virtual bool IsVisible() const override ++ { ++ return !g_DRM.paused; ++ } + +- // For HDR10 output, expected content colorspace != native colorspace. +- if ( g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() ) +- { +- *outputEncodingColorimetry = displaycolorimetry_2020; +- *outputEncodingEOTF = drm->pConnector->GetHDRInfo().eOutputEncodingEOTF; +- } +- else +- { +- *outputEncodingColorimetry = drm->pConnector->GetDisplayColorimetry(); +- *outputEncodingEOTF = EOTF_Gamma22; +- } +-} ++ virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override ++ { ++ return glm::uvec2{ g_DRM.cursor_width, g_DRM.cursor_height }; ++ } + ++ virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override ++ { ++ return drm_set_refresh( &g_DRM, nRefresh ); ++ } + +-std::span drm_get_valid_refresh_rates( struct drm_t *drm ) +-{ +- if ( drm && drm->pConnector ) +- return drm->pConnector->GetValidDynamicRefreshRates(); ++ virtual void HackUpdatePatchedEdid() override ++ { ++ if ( !GetCurrentConnector() ) ++ return; + +- return std::span{}; ++ WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo() ); ++ } ++ ++ protected: ++ ++ virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override ++ { ++ if ( pBlob->GetBlobValue() ) ++ drmModeDestroyPropertyBlob( g_DRM.fd, pBlob->GetBlobValue() ); ++ } ++ ++ private: ++ bool m_bWasCompositing = false; ++ bool m_bWasPartialCompsiting = 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 ); ++ assert( drm->fbids_queued.size() == 0 ); ++ ++ defer( if ( drm->req != nullptr ) { drmModeAtomicFree( drm->req ); drm->req = nullptr; } ); ++ ++ bool isPageFlip = drm->flags & DRM_MODE_PAGE_FLIP_EVENT; ++ ++ if ( isPageFlip ) ++ { ++ drm->flip_lock.lock(); ++ ++ // Do it before the commit, as otherwise the pageflip handler could ++ // potentially beat us to the refcount checks. ++ for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) ++ { ++ struct fb &fb = get_fb( g_DRM, drm->fbids_in_req[ i ] ); ++ assert( fb.held_refs ); ++ fb.n_refs++; ++ } ++ ++ drm->fbids_queued = drm->fbids_in_req; ++ } ++ ++ m_PresentFeedback.m_uQueuedPresents++; ++ ++ uint32_t uCurrentPresentCtx = m_uNextPresentCtx; ++ m_uNextPresentCtx = ( m_uNextPresentCtx + 1 ) % 3; ++ m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = m_PresentFeedback.m_uQueuedPresents; ++ ++ drm_verbose_log.debugf("flip commit %" PRIu64, (uint64_t)m_PresentFeedback.m_uQueuedPresents); ++ gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)m_PresentFeedback.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->flip_lock.unlock(); ++ abort(); ++ } ++ ++ 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(); ++ } ++ } ++ ++ // Undo refcount if the commit didn't actually work ++ for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) ++ { ++ get_fb( g_DRM, drm->fbids_in_req[ i ] ).n_refs--; ++ } ++ ++ drm->fbids_queued.clear(); ++ ++ m_PresentFeedback.m_uQueuedPresents--; ++ ++ if ( isPageFlip ) ++ drm->flip_lock.unlock(); ++ ++ return ret; ++ } else { ++ drm->fbids_in_req.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 flip handler to unlock ++ drm->flip_lock.lock(); ++ drm->flip_lock.unlock(); ++ } ++ ++ return ret; ++ } ++ ++ }; ++ ++ ///////////////////////// ++ // Backend Instantiator ++ ///////////////////////// ++ ++ template <> ++ bool IBackend::Set() ++ { ++ return Set( new CDRMBackend{} ); ++ } + } +diff --git a/src/drm.hpp b/src/drm.hpp +deleted file mode 100644 +index 79c88050a..000000000 +--- a/src/drm.hpp ++++ /dev/null +@@ -1,526 +0,0 @@ +-#pragma once +- +-#include "drm_include.h" +-#include "color_helpers.h" +-#include "gamescope_shared.h" +-#include "rendervulkan.hpp" +- +-#include "wlr_begin.hpp" +-#include +-#include +-#include +-#include "wlr_end.hpp" +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-extern struct drm_t g_DRM; +-void drm_destroy_blob(struct drm_t *drm, uint32_t blob); +- +-class drm_blob +-{ +-public: +- drm_blob() : blob( 0 ), owned( false ) +- { +- } +- +- drm_blob(uint32_t blob, bool owned = true) +- : blob( blob ), owned( owned ) +- { +- } +- +- ~drm_blob() +- { +- if (blob && owned) +- drm_destroy_blob( &g_DRM, blob ); +- } +- +- // No copy constructor, because we can't duplicate the blob handle. +- drm_blob(const drm_blob&) = delete; +- drm_blob& operator=(const drm_blob&) = delete; +- // No move constructor, because we use shared_ptr anyway, but can be added if necessary. +- drm_blob(drm_blob&&) = delete; +- drm_blob& operator=(drm_blob&&) = delete; +- +- uint32_t blob; +- bool owned; +-}; +- +-struct wlserver_hdr_metadata : drm_blob +-{ +- wlserver_hdr_metadata() +- { +- } +- +- wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) +- : drm_blob( blob, owned ) +- { +- if (_metadata) +- this->metadata = *_metadata; +- } +- +- hdr_output_metadata metadata = {}; +-}; +- +-struct wlserver_ctm : drm_blob +-{ +- wlserver_ctm() +- { +- } +- +- wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) +- : drm_blob( blob, owned ), matrix( ctm ) +- { +- } +- +- glm::mat3x4 matrix{}; +-}; +-namespace gamescope +-{ +- 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; } +- 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 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 VALVE1_PLANE_DEGAMMA_TF; +- std::optional VALVE1_PLANE_DEGAMMA_LUT; +- std::optional VALVE1_PLANE_CTM; +- std::optional VALVE1_PLANE_HDR_MULT; +- std::optional VALVE1_PLANE_SHAPER_LUT; +- std::optional VALVE1_PLANE_SHAPER_TF; +- std::optional VALVE1_PLANE_LUT3D; +- std::optional VALVE1_PLANE_BLEND_TF; +- std::optional VALVE1_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 VALVE1_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 CDRMAtomicTypedObject +- { +- public: +- CDRMConnector( 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 DUMMY_END; +- }; +- ConnectorProperties &GetProperties() { return m_Props; } +- const ConnectorProperties &GetProperties() const { return m_Props; } +- +- struct HDRInfo +- { +- // 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. +- bool bExposeHDRSupport = 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. +- EOTF eOutputEncodingEOTF = EOTF_Gamma22; +- +- 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 ShouldPatchEDID() const +- { +- return IsHDRG22(); +- } +- +- bool IsHDR10() const +- { +- // PQ output encoding is always HDR10 (PQ + 2020) for us. +- // If that assumption changes, update me. +- return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_PQ; +- } +- }; +- +- drmModeConnector *GetModeConnector() { return m_pConnector.get(); } +- const char *GetName() const { return m_Mutable.szName; } +- const char *GetMake() const { return m_Mutable.pszMake; } +- const char *GetModel() const { return m_Mutable.szModel; } +- uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } +- const HDRInfo &GetHDRInfo() const { return m_Mutable.HDR; } +- std::span GetValidDynamicRefreshRates() const { return m_Mutable.ValidDynamicRefreshRates; } +- GamescopeKnownDisplays GetKnownDisplayType() const { return m_Mutable.eKnownDisplay; } +- GamescopeScreenType GetScreenType() const +- { +- 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; +- } +- bool IsVRRCapable() const +- { +- return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); +- } +- const displaycolorimetry_t& GetDisplayColorimetry() const { return m_Mutable.DisplayColorimetry; } +- +- const std::vector &GetRawEDID() const { return m_Mutable.EdidData; } +- +- // TODO: Remove +- void SetBaseRefresh( int nRefresh ) { m_nBaseRefresh = nRefresh; } +- int GetBaseRefresh() const { return m_nBaseRefresh; } +- +- bool SupportsHDR10() const +- { +- return !!GetProperties().Colorspace && !!GetProperties().HDR_OUTPUT_METADATA && GetHDRInfo().IsHDR10(); +- } +- +- bool SupportsHDRG22() const +- { +- return GetHDRInfo().IsHDRG22(); +- } +- +- bool SupportsHDR() const +- { +- return SupportsHDR10() || SupportsHDRG22(); +- } +- private: +- void ParseEDID(); +- +- static std::optional GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ); +- +- CAutoDeletePtr m_pConnector; +- +- struct MutableConnectorState +- { +- int nDefaultRefresh = 0; +- +- uint32_t uPossibleCRTCMask = 0u; +- char szName[32]{}; +- char szMakePNP[4]{}; +- char szModel[16]{}; +- const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. +- GamescopeKnownDisplays eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_UNKNOWN; +- std::span ValidDynamicRefreshRates{}; +- std::vector EdidData; // Raw, unmodified. +- +- displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; +- HDRInfo HDR; +- } m_Mutable; +- +- // TODO: Remove +- int m_nBaseRefresh = 0; +- +- ConnectorProperties m_Props; +- }; +-} +- +-struct saved_mode { +- int width; +- int height; +- int refresh; +-}; +- +-struct fb { +- uint32_t id; +- /* Client buffer, if any */ +- struct wlr_buffer *buf; +- /* A FB is held if it's being used by steamcompmgr +- * doesn't need to be atomic as it's only ever +- * modified/read from the steamcompmgr thread */ +- int held_refs; +- /* Number of page-flips using the FB */ +- std::atomic< uint32_t > n_refs; +-}; +- +-struct drm_t { +- bool bUseLiftoff; +- +- int fd; +- +- int preferred_width, preferred_height, preferred_refresh; +- +- 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::map< uint32_t, drmModePropertyRes * > props; +- +- gamescope::CDRMPlane *pPrimaryPlane; +- gamescope::CDRMCRTC *pCRTC; +- gamescope::CDRMConnector *pConnector; +- int kms_in_fence_fd; +- int kms_out_fence_fd; +- +- struct wlr_drm_format_set primary_formats; +- +- drmModeAtomicReq *req; +- uint32_t flags; +- +- struct liftoff_device *lo_device; +- struct liftoff_output *lo_output; +- struct liftoff_layer *lo_layers[ k_nMaxLayers ]; +- +- std::shared_ptr sdr_static_metadata; +- +- struct { +- std::shared_ptr mode_id; +- uint32_t color_mgmt_serial; +- std::shared_ptr lut3d_id[ EOTF_Count ]; +- std::shared_ptr shaperlut_id[ EOTF_Count ]; +- drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; +- } current, pending; +- +- /* FBs in the atomic request, but not yet submitted to KMS */ +- std::vector < uint32_t > fbids_in_req; +- /* FBs submitted to KMS, but not yet displayed on screen */ +- std::vector < uint32_t > fbids_queued; +- /* FBs currently on screen */ +- std::vector < uint32_t > fbids_on_screen; +- +- std::unordered_map< uint32_t, struct fb > fb_map; +- std::mutex fb_map_mutex; +- +- std::mutex free_queue_lock; +- std::vector< uint32_t > fbid_unlock_queue; +- std::vector< uint32_t > fbid_free_queue; +- +- std::mutex flip_lock; +- +- std::atomic < uint64_t > flipcount; +- +- std::atomic < bool > paused; +- std::atomic < int > out_of_date; +- std::atomic < bool > needs_modeset; +- +- std::unordered_map< std::string, int > connector_priorities; +- +- bool force_internal = false; +- +- char *device_name = nullptr; +-}; +- +-extern struct drm_t g_DRM; +- +-extern uint32_t g_nDRMFormat; +-extern uint32_t g_nDRMFormatOverlay; +- +-extern bool g_bRotated; +-extern bool g_bFlipped; +-extern bool g_bDebugLayers; +-extern const char *g_sOutputName; +- +-enum g_panel_orientation { +- PANEL_ORIENTATION_0, /* NORMAL */ +- PANEL_ORIENTATION_270, /* RIGHT */ +- PANEL_ORIENTATION_90, /* LEFT */ +- PANEL_ORIENTATION_180, /* UPSIDE DOWN */ +- PANEL_ORIENTATION_AUTO, +-}; +- +-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, +-}; +- +-extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; +-extern enum g_panel_orientation g_drmModeOrientation; +- +-extern std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* +- +-extern bool g_bForceDisableColorMgmt; +- +-bool init_drm(struct drm_t *drm, int width, int height, int refresh); +-void finish_drm(struct drm_t *drm); +-int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); +-int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ); +-bool drm_poll_state(struct drm_t *drm); +-uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ); +-void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); +-void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); +-void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); +-bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); +-bool drm_set_refresh( struct drm_t *drm, int refresh ); +-bool drm_set_resolution( struct drm_t *drm, int width, int height ); +-gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm); +- +-char *find_drm_node_by_devid(dev_t devid); +-int drm_get_default_refresh(struct drm_t *drm); +-bool drm_get_vrr_capable(struct drm_t *drm); +-bool drm_supports_hdr(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); +-bool drm_get_vrr_in_use(struct drm_t *drm); +-bool drm_supports_color_mgmt(struct drm_t *drm); +-std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata); +-std::shared_ptr drm_create_ctm(struct drm_t *drm, glm::mat3x4 ctm); +-void drm_destroy_blob(struct drm_t *drm, uint32_t blob); +- +-const char *drm_get_connector_name(struct drm_t *drm); +-const char *drm_get_device_name(struct drm_t *drm); +- +-std::pair drm_get_connector_identifier(struct drm_t *drm); +- +-void drm_get_native_colorimetry( struct drm_t *drm, +- displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, +- displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ); +- +-std::span drm_get_valid_refresh_rates( struct drm_t *drm ); +- +-extern bool g_bSupportsAsyncFlips; +- +-const char* drm_get_patched_edid_path(); +-void drm_update_patched_edid(drm_t *drm); +- +-void drm_send_gamescope_control(wl_resource *control, struct drm_t *drm); +diff --git a/src/drm_include.h b/src/drm_include.h +index cf4a7cb5c..500c3040a 100644 +--- a/src/drm_include.h ++++ b/src/drm_include.h +@@ -5,6 +5,13 @@ + #include + #include + ++#include "wlr_begin.hpp" ++#include ++#include ++#include "wlr_end.hpp" ++ ++#include "hdmi.h" ++ + // Josh: Okay whatever, this header isn't + // available for whatever stupid reason. :v + //#include +@@ -36,11 +43,13 @@ enum drm_valve1_transfer_function { + DRM_VALVE1_TRANSFER_FUNCTION_MAX, + }; + +-/* from CTA-861-G */ +-#define HDMI_EOTF_SDR 0 +-#define HDMI_EOTF_TRADITIONAL_HDR 1 +-#define HDMI_EOTF_ST2084 2 +-#define HDMI_EOTF_HLG 3 ++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, ++}; + + /* For Default case, driver will set the colorspace */ + #define DRM_MODE_COLORIMETRY_DEFAULT 0 +diff --git a/src/edid.cpp b/src/edid.cpp +new file mode 100644 +index 000000000..3f499fc98 +--- /dev/null ++++ b/src/edid.cpp +@@ -0,0 +1,296 @@ ++#include "edid.h" ++ ++#include "backend.h" ++#include "log.hpp" ++#include "hdmi.h" ++ ++#include ++#include ++#include ++ ++extern "C" ++{ ++#include "libdisplay-info/info.h" ++#include "libdisplay-info/edid.h" ++#include "libdisplay-info/cta.h" ++} ++ ++extern bool g_bRotated; ++ ++static LogScope edid_log("josh 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_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_CVT_TIMING_CODES_COUNT = 4; ++ ++ static inline uint8_t get_bit_range(uint8_t val, size_t high, size_t low) ++ { ++ size_t n; ++ uint8_t bitmask; ++ ++ assert(high <= 7 && high >= low); ++ ++ 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) ++ { ++ size_t n; ++ uint8_t bitmask; ++ ++ assert(high <= 7 && high >= low); ++ ++ n = high - low + 1; ++ bitmask = (uint8_t) ((1 << n) - 1); ++ assert((bits & ~bitmask) == 0); ++ ++ *val |= (uint8_t)(bits << low); ++ } ++ ++ ++ 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]; ++ ++ uint8_t checksum = uint32_t(256) - uint32_t(sum); ++ ++ block[127] = checksum; ++ } ++ ++ static bool validate_block_checksum(const uint8_t* data) ++ { ++ uint8_t sum = 0; ++ size_t i; ++ ++ for (i = 0; i < EDID_BLOCK_SIZE; i++) { ++ sum += data[i]; ++ } ++ ++ return sum == 0; ++ } ++ ++ static uint8_t encode_max_luminance(float nits) ++ { ++ if (nits == 0.0f) ++ return 0; ++ ++ return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); ++ } ++ ++ std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) ++ { ++ // A zero length indicates that the edid parsing failed. ++ if ( pEdid.empty() ) ++ return std::nullopt; ++ ++ std::vector edid( pEdid.begin(), pEdid.end() ); ++ ++ if ( g_bRotated ) ++ { ++ // 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++) ++ { ++ 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]); ++ break; ++ } ++ } ++ ++ 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() ) ++ return std::nullopt; ++ ++ // TODO: Allow for override of min luminance ++#if 0 ++ float flMaxPeakLuminance = g_ColorMgmt.pending.hdrTonemapDisplayMetadata.BIsValid() ? ++ g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits : ++ g_ColorMgmt.pending.flInternalDisplayBrightness; ++#endif ++ // TODO(JoshA): Need to resolve flInternalDisplayBrightness vs new connector hdrinfo mechanism. ++ ++ 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" */ ++ }; ++ ++ 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++) ++ { ++ 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) ++ { ++ edid_log.infof("Hmmmm.... dtd start is 0. Interesting... Not going further! :-("); ++ continue; ++ } ++ if (flags != 0) ++ { ++ 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) ++ { ++ 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) ++ { ++ 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 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_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)); ++ has_hdr_metadata_block = true; ++ } ++ } ++ } ++ ++ j += 1 + data_block_size; // account for header size. ++ } ++ ++ 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."); ++ ++ // Assert that the end of the data blocks == dtd_start ++ if (dtd_start != j) ++ { ++ 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); ++ 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) ++ ++ // 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)); ++ } ++ ++ 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) ++ { ++ 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"); ++ ++ return edid; ++ } ++ ++ const char *GetPatchedEdidPath() ++ { ++ const char *pszPatchedEdidPath = getenv( "GAMESCOPE_PATCHED_EDID_FILE" ); ++ if ( !pszPatchedEdidPath || !*pszPatchedEdidPath ) ++ return nullptr; ++ ++ return pszPatchedEdidPath; ++ } ++ ++ void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) ++ { ++ const char *pszPatchedEdidPath = GetPatchedEdidPath(); ++ if ( !pszPatchedEdidPath ) ++ return; ++ ++ std::span pEdidToWrite = pEdid; ++ ++ auto oPatchedEdid = PatchEdid( pEdid, hdrInfo ); ++ if ( oPatchedEdid ) ++ pEdidToWrite = std::span{ oPatchedEdid->begin(), oPatchedEdid->end() }; ++ ++ char szTmpFilename[PATH_MAX]; ++ snprintf( szTmpFilename, sizeof( szTmpFilename ), "%s.tmp", pszPatchedEdidPath ); ++ ++ FILE *pFile = fopen( szTmpFilename, "wb" ); ++ if ( !pFile ) ++ { ++ edid_log.errorf( "Couldn't open file: %s", szTmpFilename ); ++ return; ++ } ++ ++ fwrite( pEdidToWrite.data(), 1, pEdidToWrite.size(), pFile ); ++ fflush( pFile ); ++ fclose( pFile ); ++ ++ // Flip it over. ++ rename( szTmpFilename, pszPatchedEdidPath ); ++ edid_log.infof( "Wrote new edid to: %s", pszPatchedEdidPath ); ++ } ++} +diff --git a/src/edid.h b/src/edid.h +new file mode 100644 +index 000000000..e6ab74688 +--- /dev/null ++++ b/src/edid.h +@@ -0,0 +1,16 @@ ++#pragma once ++ ++#include ++#include ++#include ++#include ++ ++namespace gamescope ++{ ++ struct BackendConnectorHDRInfo; ++ ++ const char *GetPatchedEdidPath(); ++ void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); ++ ++ std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); ++} +\ No newline at end of file +diff --git a/src/gamescope_shared.h b/src/gamescope_shared.h +index fdbcfa481..f34174e59 100644 +--- a/src/gamescope_shared.h ++++ b/src/gamescope_shared.h +@@ -2,6 +2,8 @@ + + namespace gamescope + { ++ class BackendBlob; ++ + enum GamescopeKnownDisplays + { + GAMESCOPE_KNOWN_DISPLAY_UNKNOWN, +@@ -41,3 +43,25 @@ inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) + colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; + } + ++enum GamescopeSelection ++{ ++ GAMESCOPE_SELECTION_CLIPBOARD, ++ GAMESCOPE_SELECTION_PRIMARY, ++ ++ 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_AUTO, ++}; ++ ++// Disable partial composition for now until we get ++// composite priorities working in libliftoff + also ++// use the proper libliftoff composite plane system. ++static constexpr bool kDisablePartialComposition = true; +diff --git a/src/hdmi.h b/src/hdmi.h +new file mode 100644 +index 000000000..57d5257e2 +--- /dev/null ++++ b/src/hdmi.h +@@ -0,0 +1,7 @@ ++#pragma once ++ ++/* from CTA-861-G */ ++#define HDMI_EOTF_SDR 0 ++#define HDMI_EOTF_TRADITIONAL_HDR 1 ++#define HDMI_EOTF_ST2084 2 ++#define HDMI_EOTF_HLG 3 +diff --git a/src/headless.cpp b/src/headless.cpp +new file mode 100644 +index 000000000..493aea13e +--- /dev/null ++++ b/src/headless.cpp +@@ -0,0 +1,248 @@ ++#include "backend.h" ++ ++namespace gamescope ++{ ++ class CHeadlessConnector final : public IBackendConnector ++ { ++ public: ++ 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 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 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; ++ } ++ ++ 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"; ++ } ++ ++ private: ++ BackendConnectorHDRInfo m_HDRInfo{}; ++ }; ++ ++ class CHeadlessBackend final : public CBaseBackend ++ { ++ public: ++ CHeadlessBackend() ++ { ++ } ++ ++ virtual ~CHeadlessBackend() ++ { ++ } ++ ++ virtual bool Init() override ++ { ++ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override ++ { ++ *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; ++ *pOverlayPlaneFormat = VK_FORMAT_B8G8R8A8_UNORM; ++ } ++ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override ++ { ++ return true; ++ } ++ ++ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override ++ { ++ return 0; ++ } ++ ++ virtual void DirtyState( bool bForce, bool bForceModeset ) override ++ { ++ } ++ ++ virtual bool PollState() override ++ { ++ return false; ++ } ++ ++ virtual std::shared_ptr CreateBackendBlob( std::span data ) override ++ { ++ return std::make_shared( data ); ++ } ++ ++ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override ++ { ++ return 0; ++ } ++ ++ virtual void LockBackendFb( uint32_t uFbId ) override ++ { ++ abort(); ++ } ++ virtual void UnlockBackendFb( uint32_t uFbId ) override ++ { ++ abort(); ++ } ++ virtual void DropBackendFb( uint32_t uFbId ) override ++ { ++ abort(); ++ } ++ ++ 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 IsVRRActive() const override ++ { ++ return false; ++ } ++ ++ 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 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 ++ ///////////////////////// ++ ++ template <> ++ bool IBackend::Set() ++ { ++ return Set( new CHeadlessBackend{} ); ++ } ++ ++} +\ No newline at end of file +diff --git a/src/main.cpp b/src/main.cpp +index 02cc5ce3a..42110675d 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -20,15 +20,11 @@ + + #include "main.hpp" + #include "steamcompmgr.hpp" +-#include "drm.hpp" + #include "rendervulkan.hpp" +-#include "sdlwindow.hpp" + #include "wlserver.hpp" + #include "gpuvis_trace_utils.h" + +-#if HAVE_OPENVR +-#include "vr_session.hpp" +-#endif ++#include "backends.h" + + #if HAVE_PIPEWIRE + #include "pipewire.hpp" +@@ -267,9 +263,6 @@ bool g_bOutputHDREnabled = false; + bool g_bFullscreen = false; + bool g_bForceRelativeMouse = false; + +-bool g_bIsNested = false; +-bool g_bHeadless = false; +- + bool g_bGrabbed = false; + + float g_mouseSensitivity = 1.0; +@@ -281,6 +274,8 @@ GamescopeUpscaleFilter g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; + GamescopeUpscaleScaler g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; + int g_upscaleFilterSharpness = 2; + ++gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; ++ + bool g_bBorderlessOutputWindow = false; + + int g_nXWaylandCount = 1; +@@ -300,36 +295,6 @@ uint32_t g_preferDeviceID = 0; + + pthread_t g_mainThread; + +-bool BIsNested() +-{ +- return g_bIsNested; +-} +- +-bool BIsHeadless() +-{ +- return g_bHeadless; +-} +- +-#if HAVE_OPENVR +-bool g_bUseOpenVR = false; +-bool BIsVRSession( void ) +-{ +- return g_bUseOpenVR; +-} +-#else +-bool BIsVRSession( void ) +-{ +- return false; +-} +-#endif +- +-bool BIsSDLSession( void ) +-{ +- return g_bIsNested && !g_bHeadless && !BIsVRSession(); +-} +- +- +-static bool initOutput(int preferredWidth, int preferredHeight, int preferredRefresh); + static void steamCompMgrThreadRun(int argc, char **argv); + + static std::string build_optstring(const struct option *options) +@@ -367,16 +332,17 @@ static gamescope::GamescopeModeGeneration parse_gamescope_mode_generation( const + } + } + +-static enum g_panel_orientation 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 PANEL_ORIENTATION_0; ++ return GAMESCOPE_PANEL_ORIENTATION_0; + } else if (strcmp(str, "right") == 0) { +- return PANEL_ORIENTATION_270; ++ return GAMESCOPE_PANEL_ORIENTATION_270; + } else if (strcmp(str, "left") == 0) { +- return PANEL_ORIENTATION_90; ++ return GAMESCOPE_PANEL_ORIENTATION_90; + } else if (strcmp(str, "upsidedown") == 0) { +- return PANEL_ORIENTATION_180; ++ return GAMESCOPE_PANEL_ORIENTATION_180; + } else { + fprintf( stderr, "gamescope: invalid value for --force-orientation\n" ); + exit(1); +@@ -545,19 +511,62 @@ static bool CheckWaylandPresentationTime() + return g_bSupportsWaylandPresentationTime; + } + ++#if 0 ++static bool IsInDebugSession() ++{ ++ static FILE *fp; ++ if ( !fp ) ++ { ++ fp = fopen( "/proc/self/status", "r" ); ++ } ++ ++ char rgchLine[256]; rgchLine[0] = '\0'; ++ int nTracePid = 0; ++ if ( fp ) ++ { ++ const char *pszSearchString = "TracerPid:"; ++ const uint cchSearchString = strlen( pszSearchString ); ++ rewind( fp ); ++ fflush( fp ); ++ while ( fgets( rgchLine, sizeof(rgchLine), fp ) ) ++ { ++ if ( !strncasecmp( pszSearchString, rgchLine, cchSearchString ) ) ++ { ++ char *pszVal = rgchLine+cchSearchString+1; ++ nTracePid = atoi( pszVal ); ++ break; ++ } ++ } ++ } ++ return nTracePid != 0; ++} ++#endif + + 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; ++ ++// This will go away when we remove the getopt stuff from vr session. ++// For now... ++int g_argc; ++char **g_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; ++ + int o; + int opt_index = -1; + while ((o = getopt_long(argc, argv, gamescope_optstring, gamescope_options, &opt_index)) != -1) +@@ -628,7 +637,7 @@ int main(int argc, char **argv) + } 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_drmModeOrientation = force_orientation( optarg ); ++ g_DesiredInternalOrientation = force_orientation( optarg ); + } else if (strcmp(opt_name, "sharpness") == 0 || + strcmp(opt_name, "fsr-sharpness") == 0) { + g_upscaleFilterSharpness = atoi( optarg ); +@@ -651,15 +660,13 @@ int main(int argc, char **argv) + } else if (strcmp(opt_name, "expose-wayland") == 0) { + g_bExposeWayland = true; + } else if (strcmp(opt_name, "headless") == 0) { +- g_bHeadless = true; +- g_bIsNested = true; ++ eCurrentBackend = gamescope::GamescopeBackend::Headless; + } else if (strcmp(opt_name, "cursor-scale-height") == 0) { + g_nCursorScaleHeight = atoi(optarg); + } + #if HAVE_OPENVR + else if (strcmp(opt_name, "openvr") == 0) { +- g_bUseOpenVR = true; +- g_bIsNested = true; ++ eCurrentBackend = gamescope::GamescopeBackend::OpenVR; + } + #endif + break; +@@ -722,6 +729,13 @@ int main(int argc, char **argv) + setenv( "XCURSOR_SIZE", "256", 1 ); + } + ++#if 0 ++ while( !IsInDebugSession() ) ++ { ++ usleep( 100 ); ++ } ++#endif ++ + raise_fd_limit(); + + if ( gpuvis_trace_init() != -1 ) +@@ -734,9 +748,16 @@ int main(int argc, char **argv) + + g_pOriginalDisplay = getenv("DISPLAY"); + g_pOriginalWaylandDisplay = getenv("WAYLAND_DISPLAY"); +- g_bIsNested = g_pOriginalDisplay != NULL || g_pOriginalWaylandDisplay != NULL; + +- if ( BIsSDLSession() && g_pOriginalWaylandDisplay != NULL ) ++ if ( eCurrentBackend == gamescope::GamescopeBackend::Auto ) ++ { ++ if ( g_pOriginalDisplay != NULL || g_pOriginalWaylandDisplay != NULL ) ++ eCurrentBackend = gamescope::GamescopeBackend::SDL; ++ else ++ eCurrentBackend = gamescope::GamescopeBackend::DRM; ++ } ++ ++ if ( g_pOriginalWaylandDisplay != NULL ) + { + if (CheckWaylandPresentationTime()) + { +@@ -755,40 +776,29 @@ int main(int argc, char **argv) + } + } + +- if ( !wlsession_init() ) +- { +- fprintf( stderr, "Failed to initialize Wayland session\n" ); +- return 1; +- } +- +- if ( BIsSDLSession() ) ++ switch ( eCurrentBackend ) + { +- if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS ) != 0 ) +- { +- fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); +- return 1; +- } +- } +- +- if ( !BIsNested() ) +- { +- g_bForceRelativeMouse = false; +- } +- ++ case gamescope::GamescopeBackend::DRM: ++ gamescope::IBackend::Set(); ++ break; ++ case gamescope::GamescopeBackend::SDL: ++ gamescope::IBackend::Set(); ++ break; + #if HAVE_OPENVR +- if ( BIsVRSession() ) +- { +- if ( !vr_init( argc, argv ) ) +- { +- fprintf( stderr, "Failed to initialize OpenVR runtime\n" ); +- return 1; +- } +- } ++ case gamescope::GamescopeBackend::OpenVR: ++ gamescope::IBackend::Set(); ++ break; + #endif ++ case gamescope::GamescopeBackend::Headless: ++ gamescope::IBackend::Set(); ++ break; ++ default: ++ abort(); ++ } + +- if ( !initOutput( g_nPreferredOutputWidth, g_nPreferredOutputHeight, g_nNestedRefresh ) ) ++ if ( !GetBackend() ) + { +- fprintf( stderr, "Failed to initialize output\n" ); ++ fprintf( stderr, "Failed to create backend.\n" ); + return 1; + } + +@@ -846,13 +856,6 @@ int main(int argc, char **argv) + return 1; + } + +-#if HAVE_OPENVR +- if ( BIsVRSession() ) +- { +- vrsession_ime_init(); +- } +-#endif +- + gamescope_xwayland_server_t *base_server = wlserver_get_xwayland_server(0); + + setenv("DISPLAY", base_server->get_nested_display_name(), 1); +@@ -909,73 +912,3 @@ static void steamCompMgrThreadRun(int argc, char **argv) + + pthread_kill( g_mainThread, SIGINT ); + } +- +-static bool initOutput( int preferredWidth, int preferredHeight, int preferredRefresh ) +-{ +- VkInstance instance = vulkan_create_instance(); +- +- if ( BIsNested() ) +- { +- g_nOutputWidth = preferredWidth; +- g_nOutputHeight = preferredHeight; +- g_nOutputRefresh = preferredRefresh; +- +- 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 = 60; +- +- if ( BIsVRSession() ) +- { +-#if HAVE_OPENVR +- if ( !vrsession_init() ) +- return false; +-#else +- return false; +-#endif +- } +- else if ( BIsSDLSession() ) +- { +- if ( !sdlwindow_init() ) +- return false; +- } +- +- VkSurfaceKHR surface = VK_NULL_HANDLE; +- +- if ( BIsSDLSession() ) +- { +- if ( !SDL_Vulkan_CreateSurface( g_SDLWindow, instance, &surface ) ) +- { +- fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); +- return false; +- } +- } +- +- if ( !vulkan_init( instance, surface ) ) +- { +- fprintf( stderr, "Failed to initialize Vulkan\n" ); +- return false; +- } +- +- return true; +- } +- else +- { +- if ( !vulkan_init( instance, VK_NULL_HANDLE ) ) +- { +- fprintf( stderr, "Failed to initialize Vulkan\n" ); +- return false; +- } +- +- return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh ); +- } +-} +diff --git a/src/main.hpp b/src/main.hpp +index b3de6b847..a87030b0f 100644 +--- a/src/main.hpp ++++ b/src/main.hpp +@@ -17,14 +17,17 @@ extern int g_nNestedDisplayIndex; + + extern uint32_t g_nOutputWidth; + extern uint32_t g_nOutputHeight; ++extern bool g_bForceRelativeMouse; + extern int g_nOutputRefresh; // Hz + extern bool g_bOutputHDREnabled; ++extern bool g_bForceInternal; + + extern bool g_bFullscreen; + + extern bool g_bGrabbed; + + extern float g_mouseSensitivity; ++extern const char *g_sOutputName; + + enum class GamescopeUpscaleFilter : uint32_t + { +@@ -70,7 +73,3 @@ extern uint32_t g_preferVendorID; + extern uint32_t g_preferDeviceID; + + void restore_fd_limit( void ); +-bool BIsNested( void ); +-bool BIsHeadless( void ); +-bool BIsSDLSession( void ); +-bool BIsVRSession( void ); +diff --git a/src/meson.build b/src/meson.build +index 4f88d6ecb..a9e64990c 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -105,6 +105,8 @@ src = [ + 'steamcompmgr.cpp', + 'color_helpers.cpp', + 'main.cpp', ++ 'edid.cpp', ++ 'headless.cpp', + 'wlserver.cpp', + 'drm.cpp', + 'modegen.cpp', +@@ -115,6 +117,7 @@ src = [ + 'ime.cpp', + 'mangoapp.cpp', + 'reshade_effect_manager.cpp', ++ 'backend.cpp', + ] + + src += spirv_shaders +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 357018607..51a5d5c7f 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -21,14 +21,12 @@ + // 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 "drm_include.h" ++ + #include "rendervulkan.hpp" + #include "main.hpp" + #include "steamcompmgr.hpp" +-#include "sdlwindow.hpp" + #include "log.hpp" +-#if HAVE_OPENVR +-#include "vr_session.hpp" +-#endif + + #include "cs_composite_blit.h" + #include "cs_composite_blur.h" +@@ -47,6 +45,9 @@ + + #include "reshade_effect_manager.hpp" + ++#include "SDL.h" ++#include "SDL_vulkan.h" ++ + extern bool g_bWasPartialComposite; + + static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601_limited = {{ +@@ -100,6 +101,12 @@ VulkanOutput_t g_output; + + uint32_t g_uCompositeDebug = 0u; + ++template ++static bool Contains( const std::span x, T value ) ++{ ++ return std::ranges::any_of( x, std::bind_front(std::equal_to{}, value) ); ++} ++ + static std::map< VkFormat, std::map< uint64_t, VkDrmFormatModifierPropertiesEXT > > DRMModifierProps = {}; + static std::vector< uint32_t > sampledShmFormats{}; + static struct wlr_drm_format_set sampledDRMFormats = {}; +@@ -409,6 +416,11 @@ bool CVulkanDevice::createDevice() + + vk_log.infof( "physical device %s DRM format modifiers", m_bSupportsModifiers ? "supports" : "does not support" ); + ++ if ( !GetBackend()->ValidPhysicalDevice( physDev() ) ) ++ return false; ++ ++ // XXX(JoshA): Move this to ValidPhysicalDevice. ++ // We need to refactor some Vulkan stuff to do that though. + if ( hasDrmProps ) { + VkPhysicalDeviceDrmPropertiesEXT drmProps = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, +@@ -419,7 +431,7 @@ bool CVulkanDevice::createDevice() + }; + vk.GetPhysicalDeviceProperties2( physDev(), &props2 ); + +- if ( !BIsNested() && !drmProps.hasPrimary ) { ++ if ( !GetBackend()->UsesVulkanSwapchain() && !drmProps.hasPrimary ) { + vk_log.errorf( "physical device has no primary node" ); + return false; + } +@@ -500,7 +512,7 @@ bool CVulkanDevice::createDevice() + + std::vector< const char * > enabledExtensions; + +- if ( BIsNested() == true ) ++ if ( GetBackend()->UsesVulkanSwapchain() ) + { + enabledExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME ); + enabledExtensions.push_back( VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME ); +@@ -526,12 +538,8 @@ bool CVulkanDevice::createDevice() + if ( supportsHDRMetadata ) + enabledExtensions.push_back( VK_EXT_HDR_METADATA_EXTENSION_NAME ); + +- if ( BIsVRSession() ) +- { +-#if HAVE_OPENVR +- vrsession_append_device_exts( physDev(), enabledExtensions ); +-#endif +- } ++ for ( auto& extension : GetBackend()->GetDeviceExtensions( physDev() ) ) ++ enabledExtensions.push_back( extension ); + + #if 0 + VkPhysicalDeviceMaintenance5FeaturesKHR maintenance5 = { +@@ -1609,7 +1617,7 @@ void CVulkanCmdBuffer::prepareSrcImage(CVulkanTexture *image) + if (!result.second) + return; + // using the swapchain image as a source without writing to it doesn't make any sense +- assert(image->swapchainImage() == false); ++ assert(image->outputImage() == false); + result.first->second.needsImport = image->externalImage(); + result.first->second.needsExport = image->externalImage(); + } +@@ -1622,7 +1630,7 @@ void CVulkanCmdBuffer::prepareDestImage(CVulkanTexture *image) + return; + result.first->second.discarded = true; + result.first->second.needsExport = image->externalImage(); +- result.first->second.needsPresentLayout = image->swapchainImage(); ++ result.first->second.needsPresentLayout = image->outputImage(); + } + + void CVulkanCmdBuffer::discardImage(CVulkanTexture *image) +@@ -1663,8 +1671,6 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) + bool isExport = flush && state.needsExport; + bool isPresent = flush && state.needsPresentLayout; + +- VkImageLayout presentLayout = BIsVRSession() ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +- + if (!state.discarded && !state.dirty && !state.needsImport && !isExport && !isPresent) + continue; + +@@ -1680,7 +1686,7 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) + .srcAccessMask = state.dirty ? write_bits : 0u, + .dstAccessMask = flush ? 0u : read_bits | write_bits, + .oldLayout = (state.discarded || state.needsImport) ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL, +- .newLayout = isPresent ? presentLayout : 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(), +@@ -1699,7 +1705,7 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) + 0, 0, nullptr, 0, nullptr, barriers.size(), barriers.data()); + } + +-static CVulkanDevice g_device; ++CVulkanDevice g_device; + + static bool allDMABUFsEqual( wlr_dmabuf_attributes *pDMA ) + { +@@ -1827,9 +1833,9 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + +- if ( flags.bSwapchain == true ) ++ if ( flags.bOutputImage == true ) + { +- m_bSwapchain = true; ++ m_bOutputImage = true; + } + + m_bExternal = pDMA || flags.bExportable == true; +@@ -1916,7 +1922,8 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + } + + std::vector modifiers = {}; +- if ( flags.bFlippable == true && g_device.supportsModifiers() && !pDMA ) ++ // 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 ); + +@@ -1931,10 +1938,10 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + } + else + { +- const struct wlr_drm_format *drmFormatDesc = wlr_drm_format_set_get( &g_DRM.primary_formats, drmFormat ); +- assert( drmFormatDesc != nullptr ); +- possibleModifiers = drmFormatDesc->modifiers; +- numPossibleModifiers = drmFormatDesc->len; ++ std::span modifiers = GetBackend()->GetSupportedModifiers( drmFormat ); ++ assert( !modifiers.empty() ); ++ possibleModifiers = modifiers.data(); ++ numPossibleModifiers = modifiers.size(); + } + + for ( size_t i = 0; i < numPossibleModifiers; i++ ) +@@ -1976,7 +1983,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + imageInfo.tiling = tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + } + +- if ( flags.bFlippable == true && tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) ++ if ( GetBackend()->UsesModifiers() && flags.bFlippable == true && tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) + { + // We want to scan-out the image + wsiImageCreateInfo = { +@@ -2240,11 +2247,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + + if ( flags.bFlippable == true ) + { +- m_FBID = drm_fbid_from_dmabuf( &g_DRM, nullptr, &m_dmabuf ); +- if ( m_FBID == 0 ) { +- vk_log.errorf( "drm_fbid_from_dmabuf failed" ); +- return false; +- } ++ m_FBID = GetBackend()->ImportDmabufToBackend( nullptr, &m_dmabuf ); + } + + bool bHasAlpha = pDMA ? DRMFormatHasAlpha( pDMA->format ) : true; +@@ -2357,7 +2360,7 @@ bool CVulkanTexture::BInitFromSwapchain( VkImage image, uint32_t width, uint32_t + m_format = format; + m_contentWidth = width; + m_contentHeight = height; +- m_bSwapchain = true; ++ m_bOutputImage = true; + + VkImageViewCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, +@@ -2430,7 +2433,7 @@ CVulkanTexture::~CVulkanTexture( void ) + + if ( m_FBID != 0 ) + { +- drm_drop_fbid( &g_DRM, m_FBID ); ++ GetBackend()->DropBackendFb( m_FBID ); + m_FBID = 0; + } + +@@ -2560,10 +2563,12 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) + + if ( !g_device.supportsModifiers() ) + { +- if ( BIsNested() == false && !wlr_drm_format_set_has( &g_DRM.formats, drmFormat, DRM_FORMAT_MOD_INVALID ) ) ++ if ( GetBackend()->UsesModifiers() ) + { +- return false; ++ if ( !Contains( GetBackend()->GetSupportedModifiers( drmFormat ), DRM_FORMAT_MOD_INVALID ) ) ++ return false; + } ++ + wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, DRM_FORMAT_MOD_INVALID ); + return false; + } +@@ -2604,18 +2609,13 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) + { + continue; + } +- if ( BIsNested() == false && !wlr_drm_format_set_has( &g_DRM.formats, drmFormat, modifier ) ) +- { +- continue; +- } +- if ( BIsNested() == false && drmFormat == DRM_FORMAT_NV12 && modifier == DRM_FORMAT_MOD_LINEAR && g_bRotated ) ++ ++ if ( GetBackend()->UsesModifiers() ) + { +- // If embedded and rotated, blacklist NV12 LINEAR because +- // amdgpu won't support direct scan-out. Since only pure +- // Wayland clients can submit NV12 buffers, this should only +- // affect streaming_client. +- continue; ++ if ( !Contains( GetBackend()->GetSupportedModifiers( drmFormat ), modifier ) ) ++ continue; + } ++ + wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, modifier ); + } + +@@ -2684,7 +2684,7 @@ static void present_wait_thread_func( void ) + { + g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu ); + uint64_t vblanktime = get_time_in_nanos(); +- g_VBlankTimer.MarkVBlank( vblanktime, true ); ++ GetVBlankTimer().MarkVBlank( vblanktime, true ); + mangoapp_output_update( vblanktime ); + } + } +@@ -2707,7 +2707,7 @@ void vulkan_update_swapchain_hdr_metadata( VulkanOutput_t *pOutput ) + return; + } + +- hdr_metadata_infoframe &infoframe = g_output.swapchainHDRMetadata->metadata.hdmi_metadata_type1; ++ const hdr_metadata_infoframe &infoframe = g_output.swapchainHDRMetadata->View().hdmi_metadata_type1; + VkHdrMetadataEXT metadata = + { + .sType = VK_STRUCTURE_TYPE_HDR_METADATA_EXT, +@@ -2830,7 +2830,7 @@ std::shared_ptr vulkan_get_hacky_blank_texture() + std::shared_ptr vulkan_create_debug_blank_texture() + { + CVulkanTexture::createFlags flags; +- flags.bFlippable = !BIsNested(); ++ flags.bFlippable = true; + flags.bSampled = true; + flags.bTransferDst = true; + +@@ -2853,46 +2853,6 @@ std::shared_ptr vulkan_create_debug_blank_texture() + return texture; + } + +-#if HAVE_OPENVR +-std::shared_ptr vulkan_create_debug_white_texture() +-{ +- CVulkanTexture::createFlags flags; +- flags.bMappable = true; +- flags.bSampled = true; +- flags.bTransferSrc = true; +- flags.bLinear = true; +- +- auto texture = std::make_shared(); +- bool bRes = texture->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags); +- assert( bRes ); +- +- memset( texture->mappedData(), 0xFF, texture->width() * texture->height() * 4 ); +- +- return texture; +-} +- +-void vulkan_present_to_openvr( void ) +-{ +- //static auto texture = vulkan_create_debug_white_texture(); +- auto texture = vulkan_get_last_output_image( false, false ); +- +- vr::VRVulkanTextureData_t data = +- { +- .m_nImage = (uint64_t)(uintptr_t)texture->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 = texture->width(), +- .m_nHeight = texture->height(), +- .m_nFormat = texture->format(), +- .m_nSampleCount = 1, +- }; +- vrsession_present(&data); +-} +-#endif +- + bool vulkan_supports_hdr10() + { + for ( auto& format : g_output.surfaceFormats ) +@@ -3038,11 +2998,11 @@ bool vulkan_remake_swapchain( void ) + static bool vulkan_make_output_images( VulkanOutput_t *pOutput ) + { + CVulkanTexture::createFlags outputImageflags; +- outputImageflags.bFlippable = !BIsNested(); ++ outputImageflags.bFlippable = true; + outputImageflags.bStorage = true; + outputImageflags.bTransferSrc = true; // for screenshots + outputImageflags.bSampled = true; // for pipewire blits +- outputImageflags.bSwapchain = BIsVRSession(); ++ outputImageflags.bOutputImage = true; + + pOutput->outputImages.resize(3); // extra image for partial composition. + pOutput->outputImagesPartialOverlay.resize(3); +@@ -3137,12 +3097,7 @@ bool vulkan_make_output() + + VkResult result; + +- if ( BIsVRSession() || BIsHeadless() ) +- { +- pOutput->outputFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; +- vulkan_make_output_images( pOutput ); +- } +- else if ( BIsSDLSession() ) ++ if ( GetBackend()->UsesVulkanSwapchain() ) + { + result = g_device.vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( g_device.physDev(), pOutput->surface, &pOutput->surfaceCaps ); + if ( result != VK_SUCCESS ) +@@ -3195,9 +3150,8 @@ bool vulkan_make_output() + } + else + { +- pOutput->outputFormat = DRMFormatToVulkan( g_nDRMFormat, false ); +- pOutput->outputFormatOverlay = DRMFormatToVulkan( g_nDRMFormatOverlay, false ); +- ++ GetBackend()->GetPreferredOutputFormat( &pOutput->outputFormat, &pOutput->outputFormatOverlay ); ++ + if ( pOutput->outputFormat == VK_FORMAT_UNDEFINED ) + { + vk_log.errorf( "failed to find Vulkan format suitable for KMS" ); +@@ -3261,55 +3215,41 @@ static bool init_nis_data() + return true; + } + +-VkInstance vulkan_create_instance( void ) ++VkInstance vulkan_get_instance( void ) + { +- VkResult result = VK_ERROR_INITIALIZATION_FAILED; +- +- std::vector< const char * > sdlExtensions; +- if ( BIsVRSession() ) +- { +-#if HAVE_OPENVR +- vrsession_append_instance_exts( sdlExtensions ); +-#endif +- } +- else if ( BIsSDLSession() ) ++ static VkInstance s_pVkInstance = []() -> VkInstance + { +- if ( SDL_Vulkan_LoadLibrary( nullptr ) != 0 ) +- { +- fprintf(stderr, "SDL_Vulkan_LoadLibrary failed: %s\n", SDL_GetError()); +- return nullptr; +- } ++ VkResult result = VK_ERROR_INITIALIZATION_FAILED; + +- unsigned int extCount = 0; +- SDL_Vulkan_GetInstanceExtensions( nullptr, &extCount, nullptr ); +- sdlExtensions.resize( extCount ); +- SDL_Vulkan_GetInstanceExtensions( nullptr, &extCount, sdlExtensions.data() ); +- } ++ 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 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)sdlExtensions.size(), +- .ppEnabledExtensionNames = sdlExtensions.data(), +- }; ++ const VkInstanceCreateInfo createInfo = { ++ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, ++ .pApplicationInfo = &appInfo, ++ .enabledExtensionCount = (uint32_t)instanceExtensions.size(), ++ .ppEnabledExtensionNames = instanceExtensions.data(), ++ }; + +- VkInstance instance = nullptr; +- result = vkCreateInstance(&createInfo, 0, &instance); +- if ( result != VK_SUCCESS ) +- { +- vk_errorf( result, "vkCreateInstance failed" ); +- } ++ VkInstance instance = nullptr; ++ result = vkCreateInstance(&createInfo, 0, &instance); ++ if ( result != VK_SUCCESS ) ++ { ++ vk_errorf( result, "vkCreateInstance failed" ); ++ } ++ ++ return instance; ++ }(); + +- return instance; ++ return s_pVkInstance; + } + + bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ) +@@ -3320,7 +3260,7 @@ bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ) + if (!init_nis_data()) + return false; + +- if (BIsNested() && !BIsVRSession()) ++ if ( GetBackend()->UsesVulkanSwapchain() ) + { + std::thread present_wait_thread( present_wait_thread_func ); + present_wait_thread.detach(); +@@ -3451,7 +3391,7 @@ struct BlitPushData_t + + if (layer->ctm) + { +- ctm[i] = layer->ctm->matrix; ++ ctm[i] = layer->ctm->View(); + } + else + { +@@ -3586,7 +3526,7 @@ struct RcasPushData_t + + if (layer->ctm) + { +- ctm[i] = layer->ctm->matrix; ++ ctm[i] = layer->ctm->View(); + } + else + { +@@ -3903,7 +3843,7 @@ std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::sh + + uint64_t sequence = g_device.submit(std::move(cmdBuffer)); + +- if ( !BIsSDLSession() && pOutputOverride == nullptr && increment ) ++ if ( !GetBackend()->UsesVulkanSwapchain() && pOutputOverride == nullptr && increment ) + { + g_output.nOutImage = ( g_output.nOutImage + 1 ) % 3; + } +diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp +index f090344ae..e07da598c 100644 +--- a/src/rendervulkan.hpp ++++ b/src/rendervulkan.hpp +@@ -20,9 +20,6 @@ + + class CVulkanCmdBuffer; + +-struct wlserver_ctm; +-struct wlserver_hdr_metadata; +- + // 1: Fade Plane (Fade outs between switching focus) + // 2: Video Underlay (The actual video) + // 3: Video Streaming UI (Game, App) +@@ -141,7 +138,7 @@ class CVulkanTexture + bTransferDst = false; + bLinear = false; + bExportable = false; +- bSwapchain = false; ++ bOutputImage = false; + bColorAttachment = false; + imageType = VK_IMAGE_TYPE_2D; + } +@@ -154,7 +151,7 @@ class CVulkanTexture + bool bTransferDst : 1; + bool bLinear : 1; + bool bExportable : 1; +- bool bSwapchain : 1; ++ bool bOutputImage : 1; + bool bColorAttachment : 1; + VkImageType imageType; + }; +@@ -178,7 +175,7 @@ class CVulkanTexture + 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 swapchainImage() { return m_bSwapchain; } ++ 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; } +@@ -206,7 +203,7 @@ class CVulkanTexture + private: + bool m_bInitialized = false; + bool m_bExternal = false; +- bool m_bSwapchain = false; ++ bool m_bOutputImage = false; + + uint32_t m_drmFormat = DRM_FORMAT_INVALID; + +@@ -264,6 +261,7 @@ struct FrameInfo_t + { + bool useFSRLayer0; + bool useNISLayer0; ++ bool bFadingOut; + BlurMode blurLayer0; + int blurRadius; + +@@ -291,7 +289,7 @@ struct FrameInfo_t + bool blackBorder; + bool applyColorMgmt; // drm only + +- std::shared_ptr ctm; ++ std::shared_ptr ctm; + + GamescopeAppTextureColorspace colorspace; + +@@ -367,7 +365,7 @@ namespace CompositeDebugFlag + static constexpr uint32_t Tonemap_Reinhard = 1u << 7; + }; + +-VkInstance vulkan_create_instance(void); ++VkInstance vulkan_get_instance(void); + bool vulkan_init(VkInstance instance, VkSurfaceKHR surface); + bool vulkan_init_formats(void); + bool vulkan_make_output(); +@@ -382,9 +380,6 @@ std::shared_ptr vulkan_get_last_output_image( bool partial, bool + std::shared_ptr 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 ); +-#if HAVE_OPENVR +-void vulkan_present_to_openvr( void ); +-#endif + + void vulkan_garbage_collect( void ); + bool vulkan_remake_swapchain( void ); +@@ -438,7 +433,7 @@ struct gamescope_color_mgmt_t + glm::vec2 outputVirtualWhite = { 0.f, 0.f }; + EChromaticAdaptationMethod chromaticAdaptationMode = k_EChromaticAdapatationMethod_Bradford; + +- std::shared_ptr appHDRMetadata = nullptr; ++ std::shared_ptr appHDRMetadata; + + bool operator == (const gamescope_color_mgmt_t&) const = default; + bool operator != (const gamescope_color_mgmt_t&) const = default; +@@ -487,7 +482,7 @@ struct VulkanOutput_t + std::vector< VkPresentModeKHR > presentModes; + + +- std::shared_ptr swapchainHDRMetadata; ++ std::shared_ptr swapchainHDRMetadata; + VkSwapchainKHR swapChain; + VkFence acquireFence; + +@@ -918,3 +913,5 @@ uint32_t DRMFormatGetBPP( uint32_t nDRMFormat ); + bool vulkan_supports_hdr10(); + + void vulkan_wait_idle(); ++ ++extern CVulkanDevice g_device; +diff --git a/src/sdlscancodetable.hpp b/src/sdlscancodetable.hpp +index 17aa90256..9ae0a38ad 100644 +--- a/src/sdlscancodetable.hpp ++++ b/src/sdlscancodetable.hpp +@@ -1,5 +1,5 @@ + +-static uint32_t s_ScancodeTable[] = ++static const uint32_t s_ScancodeTable[] = + { + KEY_RESERVED, /* SDL_SCANCODE_UNKNOWN 0 */ + KEY_RESERVED, /* SDL_SCANCODE_UNKNOWN 1 */ +@@ -287,3 +287,25 @@ static uint32_t s_ScancodeTable[] = + KEY_PROG1, /* SDL_SCANCODE_APP1 283 */ + KEY_RESERVED, /* SDL_SCANCODE_APP2 284 */ + }; ++ ++inline uint32_t SDLScancodeToLinuxKey( uint32_t nScancode ) ++{ ++ if ( nScancode < sizeof( s_ScancodeTable ) / sizeof( s_ScancodeTable[0] ) ) ++ { ++ return s_ScancodeTable[ nScancode ]; ++ } ++ return KEY_RESERVED; ++} ++ ++inline int SDLButtonToLinuxButton( int SDLButton ) ++{ ++ switch ( SDLButton ) ++ { ++ case SDL_BUTTON_LEFT: return BTN_LEFT; ++ case SDL_BUTTON_MIDDLE: return BTN_MIDDLE; ++ case SDL_BUTTON_RIGHT: return BTN_RIGHT; ++ case SDL_BUTTON_X1: return BTN_SIDE; ++ case SDL_BUTTON_X2: return BTN_EXTRA; ++ default: return 0; ++ } ++} +diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp +index 1e51eab6c..1974bc229 100644 +--- a/src/sdlwindow.cpp ++++ b/src/sdlwindow.cpp +@@ -13,587 +13,963 @@ + #include "SDL_events.h" + #include "main.hpp" + #include "wlserver.hpp" +-#include "sdlwindow.hpp" ++#include ++#include + #include "rendervulkan.hpp" + #include "steamcompmgr.hpp" ++#include "defer.hpp" + + #include "sdlscancodetable.hpp" + +-#define DEFAULT_TITLE "gamescope" +- +-static bool g_bSDLInitOK = false; +-static std::mutex g_SDLInitLock; +- +-static bool g_bWindowShown = false; +- + static int g_nOldNestedRefresh = 0; + static bool g_bWindowFocused = true; + + static int g_nOutputWidthPts = 0; + static int g_nOutputHeightPts = 0; + +- ++extern const char *g_pOriginalDisplay; ++extern bool g_bForceHDR10OutputDebug; + extern bool steamMode; + extern bool g_bFirstFrame; ++extern int g_nPreferredOutputWidth; ++extern int g_nPreferredOutputHeight; + +-SDL_Window *g_SDLWindow; +- +-enum UserEvents ++namespace gamescope + { +- USER_EVENT_TITLE, +- USER_EVENT_VISIBLE, +- USER_EVENT_GRAB, +- USER_EVENT_CURSOR, ++ enum class SDLInitState ++ { ++ SDLInit_Waiting, ++ SDLInit_Success, ++ SDLInit_Failure, ++ }; + +- USER_EVENT_COUNT +-}; ++ enum SDLCustomEvents ++ { ++ GAMESCOPE_SDL_EVENT_TITLE, ++ GAMESCOPE_SDL_EVENT_ICON, ++ GAMESCOPE_SDL_EVENT_VISIBLE, ++ GAMESCOPE_SDL_EVENT_GRAB, ++ GAMESCOPE_SDL_EVENT_CURSOR, + +-static uint32_t g_unSDLUserEventID; ++ GAMESCOPE_SDL_EVENT_COUNT, ++ }; + +-static std::mutex g_SDLWindowTitleLock; +-static std::shared_ptr g_SDLWindowTitle; +-static std::shared_ptr> g_SDLWindowIcon; +-static bool g_bUpdateSDLWindowTitle = false; +-static bool g_bUpdateSDLWindowIcon = false; ++ class CSDLConnector final : public IBackendConnector ++ { ++ public: ++ CSDLConnector(); ++ virtual bool Init(); + +-struct SDLPendingCursor +-{ +- uint32_t width, height, xhot, yhot; +- std::shared_ptr> data; +-}; +-static std::mutex g_SDLCursorLock; +-static SDLPendingCursor g_SDLPendingCursorData; +-static bool g_bUpdateSDLCursor = false; +- +-static void set_gamescope_selections(); +- +-//----------------------------------------------------------------------------- +-// Purpose: Convert from the remote scancode to a Linux event keycode +-//----------------------------------------------------------------------------- +-static inline uint32_t SDLScancodeToLinuxKey( uint32_t nScancode ) +-{ +- if ( nScancode < sizeof( s_ScancodeTable ) / sizeof( s_ScancodeTable[0] ) ) ++ 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 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; ++ ++ 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"; ++ } ++ ++ //-- ++ ++ SDL_Window *GetSDLWindow() const { return m_pWindow; } ++ VkSurfaceKHR GetVulkanSurface() const { return m_pVkSurface; } ++ private: ++ SDL_Window *m_pWindow = nullptr; ++ VkSurfaceKHR m_pVkSurface = VK_NULL_HANDLE; ++ BackendConnectorHDRInfo m_HDRInfo{}; ++ }; ++ ++ class CSDLBackend : public CBaseBackend, public INestedHints ++ { ++ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override; ++ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override; ++ ++ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; ++ virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override; ++ virtual bool PollState() override; ++ ++ virtual std::shared_ptr CreateBackendBlob( std::span data ) override; ++ ++ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override; ++ virtual void LockBackendFb( uint32_t uFbId ) override; ++ virtual void UnlockBackendFb( uint32_t uFbId ) override; ++ virtual void DropBackendFb( uint32_t uFbId ) 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 IsVRRActive() const override; ++ virtual bool SupportsPlaneHardwareCursor() const override; ++ ++ virtual bool SupportsTearing() const override; ++ virtual bool UsesVulkanSwapchain() const override; ++ ++ virtual bool IsSessionBased() const override; ++ ++ virtual bool IsVisible() const override; ++ ++ virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override; ++ ++ virtual INestedHints *GetNestedHints() override; ++ ++ /////////////////// ++ // INestedHints ++ /////////////////// ++ ++ 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 std::optional GetHostCursor() override; ++ 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() + { +- return s_ScancodeTable[ nScancode ]; + } +- return KEY_RESERVED; +-} + +-static inline int SDLButtonToLinuxButton( int SDLButton ) +-{ +- switch ( SDLButton ) ++ CSDLConnector::~CSDLConnector() + { +- case SDL_BUTTON_LEFT: return BTN_LEFT; +- case SDL_BUTTON_MIDDLE: return BTN_MIDDLE; +- case SDL_BUTTON_RIGHT: return BTN_RIGHT; +- case SDL_BUTTON_X1: return BTN_SIDE; +- case SDL_BUTTON_X2: return BTN_EXTRA; +- default: return 0; ++ if ( m_pWindow ) ++ SDL_DestroyWindow( m_pWindow ); + } +-} + +-void updateOutputRefresh( void ) +-{ +- int display_index = 0; +- SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 }; ++ 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 = 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; ++ } + +- display_index = SDL_GetWindowDisplayIndex( g_SDLWindow ); +- if ( SDL_GetDesktopDisplayMode( display_index, &mode ) == 0 ) ++ return true; ++ } ++ ++ GamescopeScreenType CSDLConnector::GetScreenType() const + { +- g_nOutputRefresh = mode.refresh_rate; ++ 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; ++ } ++ std::span CSDLConnector::GetModes() const ++ { ++ return std::span{}; + } +-} + +-extern bool g_bForceRelativeMouse; ++ bool CSDLConnector::SupportsVRR() const ++ { ++ return false; ++ } + +-static std::string gamescope_str = DEFAULT_TITLE; ++ std::span CSDLConnector::GetRawEDID() const ++ { ++ return std::span{}; ++ } ++ std::span CSDLConnector::GetValidDynamicRefreshRates() const ++ { ++ return std::span{}; ++ } + +-void inputSDLThreadRun( void ) +-{ +- pthread_setname_np( pthread_self(), "gamescope-sdl" ); ++ 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; ++ } ++ } + +- SDL_Event event; +- uint32_t key; +- bool bRelativeMouse = false; ++ //////////////// ++ // CSDLBackend ++ //////////////// + +- g_unSDLUserEventID = SDL_RegisterEvents( USER_EVENT_COUNT ); ++ CSDLBackend::CSDLBackend() ++ : m_SDLThread{ [this](){ this->SDLThreadFunc(); } } ++ { ++ } + +- uint32_t nSDLWindowFlags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI; ++ bool CSDLBackend::Init() ++ { ++ m_eSDLInit.wait( SDLInitState::SDLInit_Waiting ); + +- if ( g_bBorderlessOutputWindow == true ) ++ return m_eSDLInit == SDLInitState::SDLInit_Success; ++ } ++ ++ bool CSDLBackend::PostInit() + { +- nSDLWindowFlags |= SDL_WINDOW_BORDERLESS; ++ return true; + } + +- if ( g_bFullscreen == true ) ++ std::span CSDLBackend::GetInstanceExtensions() const + { +- nSDLWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; ++ return std::span{ m_pszInstanceExtensions.begin(), m_pszInstanceExtensions.end() }; + } + +- if ( g_bGrabbed == true ) ++ std::span CSDLBackend::GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const + { +- nSDLWindowFlags |= SDL_WINDOW_KEYBOARD_GRABBED; ++ return std::span{}; + } + +- g_SDLWindow = SDL_CreateWindow( DEFAULT_TITLE, +- SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), +- SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), +- g_nOutputWidth, +- g_nOutputHeight, +- nSDLWindowFlags ); ++ VkImageLayout CSDLBackend::GetPresentLayout() const ++ { ++ return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; ++ } + +- if ( g_SDLWindow == nullptr ) ++ void CSDLBackend::GetPreferredOutputFormat( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const + { +- fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); +- g_SDLInitLock.unlock(); +- return; ++ *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; ++ *pOverlayPlaneFormat = VK_FORMAT_B8G8R8A8_UNORM; + } + +- // Update g_nOutputWidthPts. ++ bool CSDLBackend::ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const + { +- int width, height; +- SDL_GetWindowSize( g_SDLWindow, &width, &height ); +- g_nOutputWidthPts = width; +- g_nOutputHeightPts = height; ++ return true; ++ } ++ ++ int CSDLBackend::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(); + +- #if SDL_VERSION_ATLEAST(2, 26, 0) +- SDL_GetWindowSizeInPixels( g_SDLWindow, &width, &height ); +- #endif +- g_nOutputWidth = width; +- g_nOutputHeight = height; ++ // 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 CSDLBackend::DirtyState( bool bForce, bool bForceModeset ) ++ { ++ } ++ bool CSDLBackend::PollState() ++ { ++ return false; ++ } ++ ++ std::shared_ptr CSDLBackend::CreateBackendBlob( std::span data ) ++ { ++ return std::make_shared( data ); ++ } ++ ++ uint32_t CSDLBackend::ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) ++ { ++ return 0; ++ } ++ void CSDLBackend::LockBackendFb( uint32_t uFbId ) ++ { ++ abort(); ++ } ++ void CSDLBackend::UnlockBackendFb( uint32_t uFbId ) ++ { ++ abort(); ++ } ++ void CSDLBackend::DropBackendFb( uint32_t uFbId ) ++ { ++ abort(); ++ } ++ ++ 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::IsVRRActive() const ++ { ++ return false; ++ } ++ ++ 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::IsVisible() const ++ { ++ return true; ++ } ++ ++ glm::uvec2 CSDLBackend::CursorSurfaceSize( glm::uvec2 uvecSize ) const ++ { ++ return uvecSize; + } + +- if ( g_bForceRelativeMouse ) ++ INestedHints *CSDLBackend::GetNestedHints() + { +- SDL_SetRelativeMouseMode( SDL_TRUE ); +- bRelativeMouse = true; ++ return this; + } + +- SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); ++ /////////////////// ++ // INestedHints ++ /////////////////// + +- g_nOldNestedRefresh = g_nNestedRefresh; ++ 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 ); ++ } ++ ++ std::optional CSDLBackend::GetHostCursor() ++ { ++ if ( !g_pOriginalDisplay ) ++ return std::nullopt; ++ ++ Display *display = XOpenDisplay( g_pOriginalDisplay ); ++ if ( !display ) ++ return std::nullopt; ++ 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 std::nullopt; ++ } ++ ++ XFixesCursorImage *image = XFixesGetCursorImage( display ); ++ if ( !image ) ++ return std::nullopt; ++ 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] ); ++ } ++ } + +- g_bSDLInitOK = true; +- g_SDLInitLock.unlock(); ++ return CursorInfo ++ { ++ .pPixels = std::move( cursorData ), ++ .uWidth = image->width, ++ .uHeight = image->height, ++ .uXHotspot = image->xhot, ++ .uYHotspot = image->yhot, ++ }; ++ } + +- static uint32_t fake_timestamp = 0; +- SDL_Surface *cursor_surface = nullptr; +- SDL_Surface *icon_surface = nullptr; +- SDL_Cursor *cursor = nullptr; ++ void CSDLBackend::OnBackendBlobDestroyed( BackendBlob *pBlob ) ++ { ++ // Do nothing. ++ } + +- while( SDL_WaitEvent( &event ) ) ++ void CSDLBackend::SDLThreadFunc() + { +- fake_timestamp++; ++ pthread_setname_np( pthread_self(), "gamescope-sdl" ); ++ ++ m_uUserEventIdBase = SDL_RegisterEvents( GAMESCOPE_SDL_EVENT_COUNT ); ++ ++ 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; ++ } ++ ++ bool bRelativeMouse = false; ++ if ( g_bForceRelativeMouse ) ++ { ++ SDL_SetRelativeMouseMode( SDL_TRUE ); ++ bRelativeMouse = 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; + +- switch( event.type ) ++ SDL_Event event; ++ while( SDL_WaitEvent( &event ) ) + { +- case SDL_CLIPBOARDUPDATE: +- set_gamescope_selections(); ++ 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 ( bRelativeMouse ) ++ ++ case SDL_MOUSEMOTION: + { +- if ( g_bWindowFocused ) ++ if ( bRelativeMouse ) ++ { ++ if ( g_bWindowFocused ) ++ { ++ wlserver_lock(); ++ wlserver_mousemotion( event.motion.xrel, event.motion.yrel, fake_timestamp ); ++ wlserver_unlock(); ++ } ++ } ++ else + { + wlserver_lock(); +- wlserver_mousemotion( event.motion.xrel, event.motion.yrel, fake_timestamp ); ++ wlserver_touchmotion( ++ event.motion.x / float(g_nOutputWidthPts), ++ event.motion.y / float(g_nOutputHeightPts), ++ 0, ++ fake_timestamp ); + wlserver_unlock(); + } + } +- else ++ break; ++ ++ case SDL_MOUSEBUTTONDOWN: ++ case SDL_MOUSEBUTTONUP: + { + wlserver_lock(); +- wlserver_touchmotion( +- event.motion.x / float(g_nOutputWidthPts), +- event.motion.y / float(g_nOutputHeightPts), +- 0, +- fake_timestamp ); ++ wlserver_mousebutton( SDLButtonToLinuxButton( event.button.button ), ++ event.button.state == SDL_PRESSED, ++ 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(); ++ ++ case SDL_MOUSEWHEEL: ++ { ++ wlserver_lock(); ++ wlserver_mousewheel( -event.wheel.x, -event.wheel.y, 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(); ++ ++ case SDL_FINGERMOTION: ++ { ++ wlserver_lock(); ++ wlserver_touchmotion( 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(); ++ ++ case SDL_FINGERDOWN: ++ { ++ wlserver_lock(); ++ wlserver_touchdown( event.tfinger.x, event.tfinger.y, 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 ) ++ ++ case SDL_FINGERUP: + { +- 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; +- } ++ wlserver_lock(); ++ wlserver_touchup( event.tfinger.fingerId, fake_timestamp ); ++ wlserver_unlock(); + } +- [[fallthrough]]; +- case SDL_KEYUP: +- key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); ++ break; + +- if ( event.type == SDL_KEYUP && ( event.key.keysym.mod & KMOD_LGUI ) ) ++ case SDL_KEYDOWN: + { +- bool handled = true; +- switch ( key ) ++ // 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 ) + { +- case KEY_F: +- g_bFullscreen = !g_bFullscreen; +- SDL_SetWindowFullscreen( g_SDLWindow, 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( g_SDLWindow, g_bGrabbed ? SDL_TRUE : SDL_FALSE ); +- g_bUpdateSDLWindowTitle = true; +- +- SDL_Event event; +- event.type = g_unSDLUserEventID + USER_EVENT_TITLE; +- SDL_PushEvent( &event ); ++ 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; +- default: +- handled = false; ++ } + } +- if ( handled ) ++ } ++ [[fallthrough]]; ++ case SDL_KEYUP: ++ { ++ uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); ++ ++ if ( event.type == SDL_KEYUP && ( event.key.keysym.mod & KMOD_LGUI ) ) + { +- break; ++ 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; ++ // On Wayland, clients handle key repetition ++ if ( event.key.repeat ) ++ break; + +- wlserver_lock(); +- wlserver_key( key, event.type == SDL_KEYDOWN, fake_timestamp ); +- wlserver_unlock(); ++ wlserver_lock(); ++ wlserver_key( key, event.type == SDL_KEYDOWN, fake_timestamp ); ++ wlserver_unlock(); ++ } + break; +- case SDL_WINDOWEVENT: +- switch( event.window.event ) ++ ++ case SDL_WINDOWEVENT: + { +- case SDL_WINDOWEVENT_CLOSE: +- raise( SIGTERM ); +- break; +- default: +- break; +- case SDL_WINDOWEVENT_MOVED: +- case SDL_WINDOWEVENT_SHOWN: +- updateOutputRefresh(); +- break; +- case SDL_WINDOWEVENT_SIZE_CHANGED: +- int width, height; +- SDL_GetWindowSize( g_SDLWindow, &width, &height ); +- g_nOutputWidthPts = width; +- g_nOutputHeightPts = height; ++ 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( g_SDLWindow, &width, &height ); ++ SDL_GetWindowSizeInPixels( m_Connector.GetSDLWindow(), &width, &height ); + #endif +- g_nOutputWidth = width; +- g_nOutputHeight = height; +- +- updateOutputRefresh(); +- +- 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; ++ 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 = 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 == g_unSDLUserEventID + USER_EVENT_TITLE ) ++ ++ default: + { +- g_SDLWindowTitleLock.lock(); +- if ( g_bUpdateSDLWindowTitle ) ++ if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_VISIBLE ) ) + { +- std::string tmp_title; ++ bool bVisible = m_bApplicationVisible; + +- const std::string *window_title = g_SDLWindowTitle.get(); +- if (!window_title) +- window_title = &gamescope_str; ++ // 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; + +- g_bUpdateSDLWindowTitle = false; +- if ( g_bGrabbed ) ++ if ( m_bShown != bVisible ) + { +- tmp_title = *window_title; +- tmp_title += " (grabbed)"; +- +- window_title = &tmp_title; ++ m_bShown = bVisible; ++ ++ if ( m_bShown ) ++ { ++ SDL_ShowWindow( m_Connector.GetSDLWindow() ); ++ } ++ else ++ { ++ SDL_HideWindow( m_Connector.GetSDLWindow() ); ++ } + } +- SDL_SetWindowTitle( g_SDLWindow, window_title->c_str() ); + } +- +- if ( g_bUpdateSDLWindowIcon ) ++ else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE ) ) + { +- if ( icon_surface ) ++ 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() ); ++ } ++ else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_ICON ) ) ++ { ++ std::shared_ptr> pIcon = m_pApplicationIcon; ++ ++ if ( m_pIconSurface ) + { +- SDL_FreeSurface( icon_surface ); +- icon_surface = nullptr; ++ SDL_FreeSurface( m_pIconSurface ); ++ m_pIconSurface = nullptr; + } + +- if ( g_SDLWindowIcon && g_SDLWindowIcon->size() >= 3 ) ++ if ( pIcon && pIcon->size() >= 3 ) + { +- const uint32_t width = (*g_SDLWindowIcon)[0]; +- const uint32_t height = (*g_SDLWindowIcon)[1]; ++ const uint32_t uWidth = (*pIcon)[0]; ++ const uint32_t uHeight = (*pIcon)[1]; + +- icon_surface = SDL_CreateRGBSurfaceFrom( +- &(*g_SDLWindowIcon)[2], +- width, height, +- 32, width * sizeof(uint32_t), ++ m_pIconSurface = SDL_CreateRGBSurfaceFrom( ++ &(*pIcon)[2], ++ uWidth, uHeight, ++ 32, uWidth * sizeof(uint32_t), + 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + } + +- SDL_SetWindowIcon( g_SDLWindow, icon_surface ); ++ SDL_SetWindowIcon( m_Connector.GetSDLWindow(), m_pIconSurface ); + } +- g_SDLWindowTitleLock.unlock(); +- } +- if ( event.type == g_unSDLUserEventID + USER_EVENT_VISIBLE ) +- { +- bool should_show = !!event.user.code; +- +- // 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 ) +- should_show |= !g_bFirstFrame; +- +- if ( g_bWindowShown != should_show ) ++ else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_GRAB ) ) + { +- g_bWindowShown = should_show; ++ 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 ( g_bWindowShown ) ++ if ( m_pCursor ) + { +- SDL_ShowWindow( g_SDLWindow ); ++ SDL_FreeCursor( m_pCursor ); ++ m_pCursor = nullptr; + } +- else ++ ++ if ( pCursorInfo ) + { +- SDL_HideWindow( g_SDLWindow ); ++ 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 ); + } +- } +- } +- if ( event.type == g_unSDLUserEventID + USER_EVENT_GRAB ) +- { +- bool grab = !!event.user.code; +- if ( grab != bRelativeMouse ) +- { +- SDL_SetRelativeMouseMode( grab ? SDL_TRUE : SDL_FALSE ); +- bRelativeMouse = grab; +- } +- } +- if ( event.type == g_unSDLUserEventID + USER_EVENT_CURSOR ) +- { +- std::unique_lock lock(g_SDLCursorLock); +- if ( g_bUpdateSDLCursor ) +- { +- if (cursor_surface) +- SDL_FreeSurface(cursor_surface); +- +- cursor_surface = SDL_CreateRGBSurfaceFrom( +- g_SDLPendingCursorData.data->data(), +- g_SDLPendingCursorData.width, +- g_SDLPendingCursorData.height, +- 32, +- g_SDLPendingCursorData.width * sizeof(uint32_t), +- 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); +- +- if (cursor) +- SDL_FreeCursor(cursor); +- +- cursor = SDL_CreateColorCursor( cursor_surface, g_SDLPendingCursorData.xhot, g_SDLPendingCursorData.yhot ); +- SDL_SetCursor( cursor ); +- g_bUpdateSDLCursor = false; ++ ++ SDL_SetCursor( m_pCursor ); + } + } + break; ++ } + } + } +-} +- +-std::optional sdlwindow_thread; +- +-bool sdlwindow_init( void ) +-{ +- g_SDLInitLock.lock(); +- +- std::thread inputSDLThread( inputSDLThreadRun ); +- sdlwindow_thread = inputSDLThread.native_handle(); +- inputSDLThread.detach(); +- +- // When this returns SDL_Init should be over +- g_SDLInitLock.lock(); +- +- return g_bSDLInitOK; +-} + +-void sdlwindow_shutdown( void ) +-{ +- if ( sdlwindow_thread ) ++ uint32_t CSDLBackend::GetUserEventIndex( SDLCustomEvents eEvent ) const + { +- pthread_cancel(*sdlwindow_thread); +- sdlwindow_thread = std::nullopt; ++ return m_uUserEventIdBase + uint32_t( eEvent ); + } +-} +- +-void sdlwindow_title( std::shared_ptr title, std::shared_ptr> icon ) +-{ +- if ( !BIsSDLSession() ) +- return; + ++ void CSDLBackend::PushUserEvent( SDLCustomEvents eEvent ) + { +- std::unique_lock lock(g_SDLWindowTitleLock); +- +- if ( g_SDLWindowTitle != title ) ++ SDL_Event event = + { +- g_SDLWindowTitle = title; +- g_bUpdateSDLWindowTitle = true; +- } +- +- if ( g_SDLWindowIcon != icon ) +- { +- g_SDLWindowIcon = icon; +- g_bUpdateSDLWindowIcon = true; +- } +- +- if ( g_bUpdateSDLWindowTitle || g_bUpdateSDLWindowIcon ) +- { +- SDL_Event event; +- event.type = g_unSDLUserEventID + USER_EVENT_TITLE; +- SDL_PushEvent( &event ); +- } +- } +-} +- +-void sdlwindow_set_selection(std::string contents, int selection) +-{ +- if (selection == CLIPBOARD) +- { +- SDL_SetClipboardText(contents.c_str()); ++ .user = ++ { ++ .type = GetUserEventIndex( eEvent ), ++ }, ++ }; ++ SDL_PushEvent( &event ); + } +- else if (selection == PRIMARYSELECTION) +- { +- SDL_SetPrimarySelectionText(contents.c_str()); +- } +-} + +-static void set_gamescope_selections() +-{ +- char *_clipboard = SDL_GetClipboardText(); +- +- char *_primarySelection = SDL_GetPrimarySelectionText(); +- +- gamescope_set_selection(_clipboard, CLIPBOARD); +- gamescope_set_selection(_primarySelection, PRIMARYSELECTION); +- +- SDL_free(_clipboard); +- SDL_free(_primarySelection); +-} +- +-void sdlwindow_visible( bool bVisible ) +-{ +- if ( !BIsSDLSession() ) +- return; +- +- SDL_Event event; +- event.type = g_unSDLUserEventID + USER_EVENT_VISIBLE; +- event.user.code = bVisible ? 1 : 0; +- SDL_PushEvent( &event ); +-} +- +-void sdlwindow_grab( bool bGrab ) +-{ +- if ( !BIsSDLSession() ) +- return; +- +- if ( g_bForceRelativeMouse ) +- return; +- +- static bool s_bWasGrabbed = false; +- +- if ( s_bWasGrabbed == bGrab ) +- return; +- +- s_bWasGrabbed = bGrab; +- +- SDL_Event event; +- event.type = g_unSDLUserEventID + USER_EVENT_GRAB; +- event.user.code = bGrab ? 1 : 0; +- SDL_PushEvent( &event ); +-} +- +-void sdlwindow_cursor(std::shared_ptr> pixels, uint32_t width, uint32_t height, uint32_t xhot, uint32_t yhot) +-{ +- if ( !BIsSDLSession() ) +- return; +- +- if ( g_bForceRelativeMouse ) +- return; ++ ///////////////////////// ++ // Backend Instantiator ++ ///////////////////////// + ++ template <> ++ bool IBackend::Set() + { +- std::unique_lock lock( g_SDLCursorLock ); +- g_SDLPendingCursorData.width = width; +- g_SDLPendingCursorData.height = height; +- g_SDLPendingCursorData.xhot = xhot; +- g_SDLPendingCursorData.yhot = yhot; +- g_SDLPendingCursorData.data = pixels; +- g_bUpdateSDLCursor = true; ++ return Set( new CSDLBackend{} ); + } +- +- SDL_Event event; +- event.type = g_unSDLUserEventID + USER_EVENT_CURSOR; +- SDL_PushEvent( &event ); + } +diff --git a/src/sdlwindow.hpp b/src/sdlwindow.hpp +deleted file mode 100644 +index bb88fd30a..000000000 +--- a/src/sdlwindow.hpp ++++ /dev/null +@@ -1,23 +0,0 @@ +-// For the nested case, manages SDL window for input/output +- +-#pragma once +- +-#include +-#include +- +-#define CLIPBOARD 0 +-#define PRIMARYSELECTION 1 +- +-bool sdlwindow_init( void ); +-void sdlwindow_shutdown( void ); +- +-void sdlwindow_update( void ); +-void sdlwindow_title( std::shared_ptr title, std::shared_ptr> icon ); +-void sdlwindow_set_selection(std::string, int selection); +- +-// called from other threads with interesting things have happened with clients that might warrant updating the nested window +-void sdlwindow_visible( bool bVisible ); +-void sdlwindow_grab( bool bGrab ); +-void sdlwindow_cursor(std::shared_ptr> pixels, uint32_t width, uint32_t height, uint32_t xhot, uint32_t yhot); +- +-extern SDL_Window *g_SDLWindow; +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 49aa6fbd3..e3dae6154 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -78,19 +78,16 @@ + #include + #include "waitable.h" + +-#include "steamcompmgr_shared.hpp" +- + #include "main.hpp" + #include "wlserver.hpp" +-#include "drm.hpp" + #include "rendervulkan.hpp" + #include "steamcompmgr.hpp" + #include "vblankmanager.hpp" +-#include "sdlwindow.hpp" + #include "log.hpp" + #include "defer.hpp" + #include "win32_styles.h" + #include "mwm_hints.h" ++#include "edid.h" + + #include "avif/avif.h" + +@@ -100,10 +97,6 @@ static const int g_nBaseCursorScale = 36; + #include "pipewire.hpp" + #endif + +-#if HAVE_OPENVR +-#include "vr_session.hpp" +-#endif +- + #define STB_IMAGE_IMPLEMENTATION + #define STB_IMAGE_WRITE_IMPLEMENTATION + #include +@@ -144,7 +137,7 @@ extern float g_flHDRItmTargetNits; + + uint64_t g_lastWinSeq = 0; + +-static std::shared_ptr s_scRGB709To2020Matrix; ++static std::shared_ptr s_scRGB709To2020Matrix; + + std::string clipboard; + std::string primarySelection; +@@ -153,6 +146,7 @@ std::string g_reshade_effect{}; + uint32_t g_reshade_technique_idx = 0; + + bool g_bSteamIsActiveWindow = false; ++bool g_bForceInternal = false; + + uint64_t timespec_to_nanos(struct timespec& spec) + { +@@ -304,17 +298,17 @@ create_color_mgmt_luts(const gamescope_color_mgmt_t& newColorMgmt, gamescope_col + // Create quantized output luts + for ( size_t i=0, end = g_tmpLut1d.dataR.size(); iGetCurrentConnector() ) ++ return; ++ ++ GetBackend()->GetCurrentConnector()->GetNativeColorimetry( ++ g_bHDREnabled, ++ &g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF, ++ &g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF ); + + #ifdef COLOR_MGMT_MICROBENCH + struct timespec t0, t1; +@@ -508,13 +488,16 @@ bool set_color_mgmt_enabled( bool bEnabled ) + } + + static std::shared_ptr s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; +-static std::shared_ptr s_MuraCTMBlob[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() + { +- return s_MuraCorrectionImage[drm_get_screen_type( &g_DRM )] != nullptr && !g_bMuraCompensationDisabled; ++ if ( !GetBackend()->GetCurrentConnector() ) ++ return false; ++ ++ return s_MuraCorrectionImage[GetBackend()->GetCurrentConnector()->GetScreenType()] != nullptr && !g_bMuraCompensationDisabled; + } + + void update_mura_ctm() +@@ -535,7 +518,7 @@ void update_mura_ctm() + 0, flScale, 0, kMuraOffset * flScale, + 0, 0, 0, 0, // No mura comp for blue channel. + }; +- s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = drm_create_ctm(&g_DRM, mura_scale_offset); ++ s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = GetBackend()->CreateBackendBlob( mura_scale_offset ); + } + + bool g_bMuraDebugFullColor = false; +@@ -577,7 +560,7 @@ bool set_mura_overlay( const char *path ) + free(green_data); + + CVulkanTexture::createFlags texCreateFlags; +- texCreateFlags.bFlippable = !BIsNested(); ++ 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); +@@ -703,7 +686,7 @@ struct commit_t : public gamescope::IWaitable + + if ( fb_id != 0 ) + { +- drm_unlock_fbid( &g_DRM, fb_id ); ++ GetBackend()->UnlockBackendFb( fb_id ); + fb_id = 0; + } + +@@ -922,18 +905,14 @@ static int g_nCombinedAppRefreshCycleOverride[gamescope::GAMESCOPE_SCREEN_TYPE_C + + static void _update_app_target_refresh_cycle() + { +- if ( BIsNested() ) +- { +- g_nDynamicRefreshRate[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = 0; +- g_nSteamCompMgrTargetFPS = g_nCombinedAppRefreshCycleOverride[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ]; ++ if ( !GetBackend()->GetCurrentConnector() ) + return; +- } + + static gamescope::GamescopeScreenType last_type; + static int last_target_fps; + static bool first = true; + +- gamescope::GamescopeScreenType type = drm_get_screen_type( &g_DRM ); ++ gamescope::GamescopeScreenType type = GetBackend()->GetCurrentConnector()->GetScreenType(); + int target_fps = g_nCombinedAppRefreshCycleOverride[type]; + + if ( !first && type == last_type && last_target_fps == target_fps ) +@@ -952,7 +931,7 @@ static void _update_app_target_refresh_cycle() + return; + } + +- auto rates = drm_get_valid_refresh_rates( &g_DRM ); ++ auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates(); + + g_nDynamicRefreshRate[ type ] = 0; + g_nSteamCompMgrTargetFPS = target_fps; +@@ -1081,7 +1060,7 @@ static bool debugFocus = false; + static bool drawDebugInfo = false; + static bool debugEvents = false; + bool steamMode = false; +-static bool alwaysComposite = false; ++bool alwaysComposite = false; + static bool useXRes = true; + + struct wlr_buffer_map_entry { +@@ -1355,7 +1334,7 @@ destroy_buffer( struct wl_listener *listener, void * ) + + if ( entry->fb_id != 0 ) + { +- drm_drop_fbid( &g_DRM, entry->fb_id ); ++ GetBackend()->DropBackendFb( entry->fb_id ); + } + + wl_list_remove( &entry->listener.link ); +@@ -1400,7 +1379,7 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff + + if (commit->fb_id) + { +- drm_lock_fbid( &g_DRM, commit->fb_id ); ++ GetBackend()->LockBackendFb( commit->fb_id ); + } + + return commit; +@@ -1425,20 +1404,17 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff + commit->vulkanTex = vulkan_create_texture_from_wlr_buffer( buf ); + assert( commit->vulkanTex ); + ++ commit->fb_id = 0; + struct wlr_dmabuf_attributes dmabuf = {0}; +- if ( BIsNested() == false && wlr_buffer_get_dmabuf( buf, &dmabuf ) ) ++ if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) + { +- commit->fb_id = drm_fbid_from_dmabuf( &g_DRM, buf, &dmabuf ); ++ commit->fb_id = GetBackend()->ImportDmabufToBackend( buf, &dmabuf ); + + if ( commit->fb_id ) + { +- drm_lock_fbid( &g_DRM, commit->fb_id ); ++ GetBackend()->LockBackendFb( commit->fb_id ); + } + } +- else +- { +- commit->fb_id = 0; +- } + + entry.listener.notify = destroy_buffer; + entry.buf = buf; +@@ -1875,23 +1851,16 @@ bool MouseCursor::getTexture() + + uint32_t surfaceWidth; + uint32_t surfaceHeight; +- if ( BIsNested() == false && alwaysComposite == false ) +- { +- surfaceWidth = g_DRM.cursor_width; +- surfaceHeight = g_DRM.cursor_height; +- } +- else +- { +- surfaceWidth = nDesiredWidth; +- surfaceHeight = nDesiredHeight; +- } ++ 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::shared_ptr> cursorBuffer = nullptr; ++ std::vector cursorBuffer; + + int nContentWidth = image->width; + int nContentHeight = image->height; +@@ -1913,14 +1882,12 @@ bool MouseCursor::getTexture() + (unsigned char *)resizeBuffer.data(), nDesiredWidth, nDesiredHeight, 0, + 4, 3, STBIR_FLAG_ALPHA_PREMULTIPLIED ); + +- cursorBuffer = std::make_shared>(surfaceWidth * surfaceHeight); +- for (int i = 0; i < nDesiredHeight; i++) { +- for (int j = 0; j < nDesiredWidth; j++) { +- (*cursorBuffer)[i * surfaceWidth + j] = resizeBuffer[i * nDesiredWidth + j]; +- +- if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { +- bNoCursor = false; +- } ++ 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]; + } + } + +@@ -1932,29 +1899,39 @@ bool MouseCursor::getTexture() + } + else + { +- cursorBuffer = std::make_shared>(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]; +- +- if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { +- bNoCursor = false; +- } ++ 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 (int i = 0; i < image->height; i++) ++ { ++ for (int j = 0; j < image->width; j++) ++ { ++ if ( cursorBuffer[i * surfaceWidth + j] & 0xff000000 ) ++ { ++ bNoCursor = false; ++ break; ++ } ++ } ++ } + + if (bNoCursor) +- cursorBuffer = nullptr; ++ cursorBuffer.clear(); + + m_imageEmpty = bNoCursor; + +- if ( !g_bForceRelativeMouse ) ++ if ( !GetBackend()->GetNestedHints() || !g_bForceRelativeMouse ) + { +- sdlwindow_grab( m_imageEmpty ); +- bSteamCompMgrGrab = BIsNested() && m_imageEmpty; ++ if ( GetBackend()->GetNestedHints() ) ++ GetBackend()->GetNestedHints()->SetRelativeMouseMode( m_imageEmpty ); ++ bSteamCompMgrGrab = GetBackend()->GetNestedHints() && m_imageEmpty; + } + + m_dirty = false; +@@ -1969,15 +1946,28 @@ bool MouseCursor::getTexture() + UpdatePosition(); + + CVulkanTexture::createFlags texCreateFlags; +- if ( BIsNested() == false ) ++ texCreateFlags.bFlippable = true; ++ if ( GetBackend()->SupportsPlaneHardwareCursor() ) + { +- texCreateFlags.bFlippable = true; + 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()); +- sdlwindow_cursor(std::move(cursorBuffer), nDesiredWidth, nDesiredHeight, image->xhot, image->yhot); ++ m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer.data()); ++ if ( GetBackend()->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()->GetNestedHints()->SetCursorImage( std::move( info ) ); ++ } ++ + assert(m_texture); + XFree(image); + +@@ -1993,11 +1983,7 @@ void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) + nSize = std::clamp( nSize, g_nBaseCursorScale, 256 ); + } + +- if ( BIsNested() == false && alwaysComposite == false ) +- { +- nSize = std::min( nSize, g_DRM.cursor_width ); +- nSize = std::min( nSize, g_DRM.cursor_height ); +- } ++ nSize = std::min( nSize, glm::compMin( GetBackend()->CursorSurfaceSize( glm::uvec2{ (uint32_t)nSize, (uint32_t)nSize } ) ) ); + + nWidth = nSize; + nHeight = nSize; +@@ -2108,7 +2094,7 @@ void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, str + layer->applyColorMgmt = false; + + layer->tex = m_texture; +- layer->fbid = BIsNested() ? 0 : m_texture->fbid(); ++ layer->fbid = m_texture->fbid(); + + layer->filter = cursor_scale != 1.0f ? GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NEAREST; + layer->blackBorder = false; +@@ -2466,6 +2452,7 @@ paint_all(bool async) + frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; + frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; + frameInfo.allowVRR = g_bAllowVRR; ++ 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 +@@ -2578,7 +2565,7 @@ paint_all(bool async) + else + { + auto tex = vulkan_get_hacky_blank_texture(); +- if ( !BIsNested() && tex != nullptr ) ++ if ( !GetBackend()->UsesVulkanSwapchain() && tex != nullptr ) + { + // HACK! HACK HACK HACK + // To avoid stutter when toggling the overlay on +@@ -2620,21 +2607,16 @@ paint_all(bool async) + global_focus.cursor->undirty(); + } + +- bool bForceHideCursor = BIsSDLSession() && !bSteamCompMgrGrab; +- +- bool bDrewCursor = false; ++ bool bForceHideCursor = GetBackend()->GetNestedHints() && !bSteamCompMgrGrab; + + // Draw cursor if we need to + if (input && !bForceHideCursor) { +- int nLayerCountBefore = frameInfo.layerCount; + global_focus.cursor->paint( + input, w == input ? override : nullptr, + &frameInfo); +- int nLayerCountAfter = frameInfo.layerCount; +- bDrewCursor = nLayerCountAfter > nLayerCountBefore; + } + +- if ( !bValidContents || ( BIsNested() == false && g_DRM.paused == true ) ) ++ if ( !bValidContents || !GetBackend()->IsVisible() ) + { + return; + } +@@ -2665,35 +2647,34 @@ paint_all(bool async) + + g_bFSRActive = frameInfo.useFSRLayer0; + +- bool bWasFirstFrame = g_bFirstFrame; + g_bFirstFrame = false; + +- bool bDoComposite = true; +- + update_app_target_refresh_cycle(); + +- int nDynamicRefresh = g_nDynamicRefreshRate[drm_get_screen_type( &g_DRM )]; +- +- int nTargetRefresh = nDynamicRefresh && steamcompmgr_window_should_refresh_switch( global_focus.focusWindow )// && !global_focus.overlayWindow +- ? nDynamicRefresh +- : drm_get_default_refresh( &g_DRM ); ++ const bool bSupportsDynamicRefresh = GetBackend()->GetCurrentConnector() && !GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates().empty(); ++ if ( bSupportsDynamicRefresh ) ++ { ++ auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates(); + +- uint64_t now = get_time_in_nanos(); ++ int nDynamicRefresh = g_nDynamicRefreshRate[GetBackend()->GetScreenType()]; + +- if ( g_nOutputRefresh == nTargetRefresh ) +- g_uDynamicRefreshEqualityTime = now; ++ int nTargetRefresh = nDynamicRefresh && steamcompmgr_window_should_refresh_switch( global_focus.focusWindow )// && !global_focus.overlayWindow ++ ? nDynamicRefresh ++ : int( rates[ rates.size() - 1 ] ); + +- if ( !BIsNested() && g_nOutputRefresh != nTargetRefresh && g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now ) +- drm_set_refresh( &g_DRM, nTargetRefresh ); ++ uint64_t now = get_time_in_nanos(); + +- bool bLayer0ScreenSize = close_enough(frameInfo.layers[0].scale.x, 1.0f) && close_enough(frameInfo.layers[0].scale.y, 1.0f); ++ if ( g_nOutputRefresh == nTargetRefresh ) ++ g_uDynamicRefreshEqualityTime = now; + +- bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; ++ if ( g_nOutputRefresh != nTargetRefresh && g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now ) ++ GetBackend()->HackTemporarySetDynamicRefresh( nTargetRefresh ); ++ } + + bool bDoMuraCompensation = is_mura_correction_enabled() && frameInfo.layerCount; + if ( bDoMuraCompensation ) + { +- auto& MuraCorrectionImage = s_MuraCorrectionImage[drm_get_screen_type( &g_DRM )]; ++ auto& MuraCorrectionImage = s_MuraCorrectionImage[GetBackend()->GetScreenType()]; + int curLayer = frameInfo.layerCount++; + + FrameInfo_t::Layer_t *layer = &frameInfo.layers[ curLayer ]; +@@ -2708,292 +2689,24 @@ paint_all(bool async) + layer->zpos = g_zposMuraCorrection; + layer->filter = GamescopeUpscaleFilter::NEAREST; + layer->tex = MuraCorrectionImage; +- layer->ctm = s_MuraCTMBlob[drm_get_screen_type( &g_DRM )]; ++ layer->ctm = s_MuraCTMBlob[GetBackend()->GetScreenType()]; + + // Blending needs to be done in Gamma 2.2 space for mura correction to work. + frameInfo.applyOutputColorMgmt = false; + } + +- bool bWantsPartialComposite = frameInfo.layerCount >= 3 && !kDisablePartialComposition; +- +- bool bNeedsFullComposite = BIsNested(); +- bNeedsFullComposite |= alwaysComposite; +- bNeedsFullComposite |= bWasFirstFrame; +- bNeedsFullComposite |= frameInfo.useFSRLayer0; +- bNeedsFullComposite |= frameInfo.useNISLayer0; +- bNeedsFullComposite |= frameInfo.blurLayer0; +- bNeedsFullComposite |= bNeedsCompositeFromFilter; +- bNeedsFullComposite |= bDrewCursor; +- bNeedsFullComposite |= g_bColorSliderInUse; +- bNeedsFullComposite |= fadingOut; +- bNeedsFullComposite |= !g_reshade_effect.empty(); +- + for (uint32_t i = 0; i < EOTF_Count; i++) + { +- if (g_ColorMgmtLuts[i].HasLuts()) ++ if ( g_ColorMgmtLuts[i].HasLuts() ) + { + frameInfo.shaperLut[i] = g_ColorMgmtLuts[i].vk_lut1d; + frameInfo.lut3D[i] = g_ColorMgmtLuts[i].vk_lut3d; + } + } + +- if ( !BIsNested() && g_bOutputHDREnabled ) ++ if ( GetBackend()->Present( &frameInfo, async ) != 0 ) + { +- bNeedsFullComposite |= g_bHDRItmEnable; +- if ( !drm_supports_color_mgmt(&g_DRM) ) +- bNeedsFullComposite |= ( frameInfo.layerCount > 1 || frameInfo.layers[0].colorspace != GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ); +- } +- bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); +- +- static int g_nLastSingleOverlayZPos = 0; +- static bool g_bWasCompositing = false; +- +- if ( !bNeedsFullComposite && !bWantsPartialComposite ) +- { +- int ret = drm_prepare( &g_DRM, async, &frameInfo ); +- if ( ret == 0 ) +- { +- bDoComposite = false; +- g_bWasPartialComposite = false; +- g_bWasCompositing = false; +- if ( frameInfo.layerCount == 2 ) +- g_nLastSingleOverlayZPos = frameInfo.layers[1].zpos; +- } +- else if ( ret == -EACCES ) +- return; +- } +- +- // Update to let the vblank manager know we are currently compositing. +- g_VBlankTimer.UpdateWasCompositing( bDoComposite ); +- +- if ( bDoComposite == true ) +- { +- if ( kDisablePartialComposition ) +- bNeedsFullComposite = true; +- +- struct FrameInfo_t compositeFrameInfo = frameInfo; +- +- 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 ( frameInfo.layers[i - 1].colorspace != frameInfo.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 && ( !g_bWasCompositing || g_bWasPartialComposite ); +- +- // 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 ); +- +- g_bWasCompositing = true; +- +- g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; +- +- if ( !oCompositeResult ) +- { +- xwm_log.errorf("vulkan_composite failed"); +- return; +- } +- +- vulkan_wait( *oCompositeResult, true ); +- +- if ( BIsNested() == true ) +- { +-#if HAVE_OPENVR +- if ( BIsVRSession() ) +- { +- vulkan_present_to_openvr(); +- } +- else if ( BIsSDLSession() ) +-#endif +- { +- vulkan_present_to_window(); +- } +- +- // Update the time it took us to commit +- g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); +- } +- else +- { +- struct FrameInfo_t presentCompFrameInfo = {}; +- +- 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->fbid = baseLayer->tex->fbid(); +- baseLayer->applyColorMgmt = false; +- +- baseLayer->filter = GamescopeUpscaleFilter::NEAREST; +- baseLayer->ctm = nullptr; +- baseLayer->colorspace = g_bOutputHDREnabled ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; +- +- g_bWasPartialComposite = false; +- } +- else +- { +- if ( g_bWasPartialComposite || !bDefer ) +- { +- presentCompFrameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; +- presentCompFrameInfo.layerCount = 2; +- +- presentCompFrameInfo.layers[ 0 ] = frameInfo.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->fbid = overlayLayer->tex->fbid(); +- 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 ] = frameInfo.layers[ 0 ]; +- presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; +- +- FrameInfo_t::Layer_t *lastPresentedOverlayLayer = nullptr; +- for (int i = 0; i < frameInfo.layerCount; i++) +- { +- if (frameInfo.layers[i].zpos == g_nLastSingleOverlayZPos) +- { +- lastPresentedOverlayLayer = &frameInfo.layers[i]; +- break; +- } +- } +- +- if (lastPresentedOverlayLayer) +- { +- FrameInfo_t::Layer_t *overlayLayer = &presentCompFrameInfo.layers[ 1 ]; +- *overlayLayer = *lastPresentedOverlayLayer; +- overlayLayer->zpos = g_zposOverlay; +- +- presentCompFrameInfo.layerCount = 2; +- } +- } +- +- g_bWasPartialComposite = true; +- } +- +- int ret = drm_prepare( &g_DRM, async, &presentCompFrameInfo ); +- +- // Happens when we're VT-switched away +- if ( ret == -EACCES ) +- return; +- +- 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)); +- abort(); +- } +- +- 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, async, &compositeFrameInfo ); +- +- // Happens when we're VT-switched away +- if ( ret == -EACCES ) +- return; +- +- 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. +- g_DRM.out_of_date = 2; +- drm_poll_state( &g_DRM ); +- return; +- } +- } +- +- drm_commit( &g_DRM, &compositeFrameInfo ); +- } +- } +- else +- { +- assert( BIsNested() == false ); +- +- drm_commit( &g_DRM, &frameInfo ); ++ return; + } + + #if HAVE_PIPEWIRE +@@ -3155,7 +2868,11 @@ paint_all(bool async) + + if ( !maxCLLNits && !maxFALLNits ) + { +- drm_supports_hdr( &g_DRM, &maxCLLNits, &maxFALLNits ); ++ if ( GetBackend()->GetCurrentConnector() ) ++ { ++ maxCLLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxContentLightLevel; ++ maxFALLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxFrameAverageLuminance; ++ } + } + + if ( !maxCLLNits && !maxFALLNits ) +@@ -3347,7 +3064,7 @@ paint_all(bool async) + + + gpuvis_trace_end_ctx_printf( paintID, "paint_all" ); +- gpuvis_trace_printf( "paint_all %i layers, composite %i", (int)frameInfo.layerCount, bDoComposite ); ++ gpuvis_trace_printf( "paint_all %i layers", (int)frameInfo.layerCount ); + } + + /* Get prop from window +@@ -4308,35 +4025,17 @@ determine_and_apply_focus() + } + + // Set SDL window title +- if ( global_focus.focusWindow ) ++ if ( GetBackend()->GetNestedHints() ) + { +-#if HAVE_OPENVR +- if ( BIsVRSession() ) +- { +- const char *title = global_focus.focusWindow->title +- ? global_focus.focusWindow->title->c_str() +- : nullptr; +- vrsession_title( title, global_focus.focusWindow->icon ); +- } +-#endif +- +- if ( BIsSDLSession() ) ++ if ( global_focus.focusWindow ) + { +- sdlwindow_title( global_focus.focusWindow->title, global_focus.focusWindow->icon ); ++ GetBackend()->GetNestedHints()->SetVisible( true ); ++ GetBackend()->GetNestedHints()->SetTitle( global_focus.focusWindow->title ); ++ GetBackend()->GetNestedHints()->SetIcon( global_focus.focusWindow->icon ); + } +- } +- +-#if HAVE_OPENVR +- if ( BIsVRSession() ) +- { +- vrsession_set_dashboard_visible( global_focus.focusWindow != nullptr ); +- } +- else +-#endif +- { +- if ( BIsSDLSession() ) ++ else + { +- sdlwindow_visible( global_focus.focusWindow != nullptr ); ++ GetBackend()->GetNestedHints()->SetVisible( false ); + } + } + +@@ -5263,14 +4962,14 @@ handle_client_message(xwayland_ctx_t *ctx, XClientMessageEvent *ev) + } + } + +-static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, int selectionTarget) ++static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, GamescopeSelection eSelectionTarget) + { + Atom target; +- if (selectionTarget == CLIPBOARD) ++ if (eSelectionTarget == GAMESCOPE_SELECTION_CLIPBOARD) + { + target = ctx->atoms.clipboard; + } +- else if (selectionTarget == PRIMARYSELECTION) ++ else if (eSelectionTarget == GAMESCOPE_SELECTION_PRIMARY) + { + target = ctx->atoms.primarySelection; + } +@@ -5282,13 +4981,13 @@ static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, i + XSetSelectionOwner(ctx->dpy, target, ctx->ourWindow, CurrentTime); + } + +-void gamescope_set_selection(std::string contents, int selection) ++void gamescope_set_selection(std::string contents, GamescopeSelection eSelection) + { +- if (selection == CLIPBOARD) ++ if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD) + { + clipboard = contents; + } +- else if (selection == PRIMARYSELECTION) ++ else if (eSelection == GAMESCOPE_SELECTION_PRIMARY) + { + primarySelection = contents; + } +@@ -5296,7 +4995,7 @@ void gamescope_set_selection(std::string contents, int selection) + gamescope_xwayland_server_t *server = NULL; + for (int i = 0; (server = wlserver_get_xwayland_server(i)); i++) + { +- x11_set_selection_owner(server->ctx.get(), contents, selection); ++ x11_set_selection_owner(server->ctx.get(), contents, eSelection); + } + } + +@@ -5356,8 +5055,6 @@ handle_selection_request(xwayland_ctx_t *ctx, XSelectionRequestEvent *ev) + static void + handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) + { +- int selection; +- + Atom actual_type; + int actual_format; + unsigned long nitems; +@@ -5375,37 +5072,34 @@ handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) + &actual_type, &actual_format, &nitems, &bytes_after, &data); + if (data) { + const char *contents = (const char *) data; ++ defer( XFree( data ); ); + + if (ev->selection == ctx->atoms.clipboard) + { +- selection = CLIPBOARD; ++ if ( GetBackend()->GetNestedHints() ) ++ { ++ //GetBackend()->GetNestedHints()->SetSelection() ++ } ++ else ++ { ++ gamescope_set_selection( contents, GAMESCOPE_SELECTION_CLIPBOARD ); ++ } + } + else if (ev->selection == ctx->atoms.primarySelection) + { +- selection = PRIMARYSELECTION; ++ if ( GetBackend()->GetNestedHints() ) ++ { ++ //GetBackend()->GetNestedHints()->SetSelection() ++ } ++ else ++ { ++ gamescope_set_selection( contents, GAMESCOPE_SELECTION_PRIMARY ); ++ } + } + else + { + xwm_log.errorf( "Selection '%s' not supported. Ignoring", XGetAtomName(ctx->dpy, ev->selection) ); +- goto done; +- } +- +- if (BIsNested()) +- { +- /* +- * gamescope_set_selection() doesn't need to be called here. +- * sdlwindow_set_selection triggers a clipboard update, which +- * then indirectly ccalls gamescope_set_selection() +- */ +- sdlwindow_set_selection(contents, selection); +- } +- else +- { +- gamescope_set_selection(contents, selection); + } +- +-done: +- XFree(data); + } + } + } +@@ -5578,10 +5272,6 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + if (ev->atom == ctx->atoms.steamTouchClickModeAtom ) + { + g_nTouchClickMode = (enum wlserver_touch_click_mode) get_prop(ctx, ctx->root, ctx->atoms.steamTouchClickModeAtom, g_nDefaultTouchClickMode ); +-#if HAVE_OPENVR +- if (BIsVRSession()) +- vrsession_update_touch_mode(); +-#endif + } + if (ev->atom == ctx->atoms.steamStreamingClientAtom) + { +@@ -5750,7 +5440,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + + if (ev->window == x11_win(global_focus.focusWindow)) + { +- sdlwindow_title( w->title, w->icon ); ++ if ( GetBackend()->GetNestedHints() ) ++ GetBackend()->GetNestedHints()->SetTitle( w->title ); + } + } + } +@@ -5764,7 +5455,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + + if (ev->window == x11_win(global_focus.focusWindow)) + { +- sdlwindow_title( w->title, w->icon ); ++ if ( GetBackend()->GetNestedHints() ) ++ GetBackend()->GetNestedHints()->SetIcon( w->icon ); + } + } + } +@@ -5924,19 +5616,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + } + if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal ) + { +- if ( !BIsNested() ) +- { +- g_DRM.force_internal = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayForceInternal, 0 ); +- g_DRM.out_of_date = 1; +- } ++ g_bForceInternal = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayForceInternal, 0 ); ++ GetBackend()->DirtyState(); + } + if ( ev->atom == ctx->atoms.gamescopeDisplayModeNudge ) + { +- if ( !BIsNested() ) +- { +- g_DRM.out_of_date = 2; +- XDeleteProperty( ctx->dpy, ctx->root, ctx->atoms.gamescopeDisplayModeNudge ); +- } ++ GetBackend()->DirtyState( true ); ++ XDeleteProperty( ctx->dpy, ctx->root, ctx->atoms.gamescopeDisplayModeNudge ); + } + if ( ev->atom == ctx->atoms.gamescopeNewScalingFilter ) + { +@@ -5969,7 +5655,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + if ( ev->atom == ctx->atoms.gamescopeDebugForceHDRSupport ) + { + g_bForceHDRSupportDebug = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugForceHDRSupport, 0 ); +- drm_update_patched_edid(&g_DRM); ++ GetBackend()->HackUpdatePatchedEdid(); + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeDebugHDRHeatmap ) +@@ -6079,15 +5765,6 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + } + hasRepaint = true; + } +- if ( ev->atom == ctx->atoms.gamescopeInternalDisplayBrightness ) +- { +- uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeInternalDisplayBrightness, 0 ); +- if ( set_internal_display_brightness( bit_cast(val) ) ) +- { +- drm_update_patched_edid(&g_DRM); +- hasRepaint = true; +- } +- } + if ( ev->atom == ctx->atoms.gamescopeHDRInputGain ) + { + uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRInputGain, 0 ); +@@ -6398,14 +6075,12 @@ steamcompmgr_exit(void) + statsThreadSem.signal(); + } + +- sdlwindow_shutdown(); ++ //sdlwindow_shutdown(); + + wlserver_lock(); + wlserver_force_shutdown(); + wlserver_unlock(false); + +- finish_drm( &g_DRM ); +- + pthread_exit(NULL); + } + +@@ -7246,46 +6921,6 @@ load_mouse_cursor( MouseCursor *cursor, const char *path, int hx, int hy ) + return cursor->setCursorImage((char *)data, w, h, hx, hy); + } + +-static bool +-load_host_cursor( MouseCursor *cursor ) +-{ +- extern const char *g_pOriginalDisplay; +- +- if ( !g_pOriginalDisplay ) +- return false; +- +- Display *display = XOpenDisplay( g_pOriginalDisplay ); +- if ( !display ) +- return false; +- 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 false; +- } +- +- XFixesCursorImage *image = XFixesGetCursorImage( display ); +- if ( !image ) +- return false; +- defer( XFree( image ) ); +- +- // image->pixels is `unsigned long*` :/ +- // Thanks X11. +- std::vector cursorData; +- for (uint32_t y = 0; y < image->height; y++) +- { +- for (uint32_t x = 0; x < image->width; x++) +- { +- cursorData.push_back((uint32_t)image->pixels[image->height * y + x]); +- } +- } +- +- cursor->setCursorImage((char *)cursorData.data(), image->width, image->height, image->xhot, image->yhot); +- return true; +-} +- + const char* g_customCursorPath = nullptr; + int g_customCursorHotspotX = 0; + int g_customCursorHotspotY = 0; +@@ -7493,7 +7128,6 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + 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.gamescopeInternalDisplayBrightness = XInternAtom( ctx->dpy, "GAMESCOPE_INTERNAL_DISPLAY_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 ); +@@ -7588,20 +7222,21 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + } + else + { +- if ( BIsNested() ) ++ std::optional oHostCursor = std::nullopt; ++ if ( GetBackend()->GetNestedHints() && ( oHostCursor = GetBackend()->GetNestedHints()->GetHostCursor() ) ) + { +- if ( !load_host_cursor( ctx->cursor.get() ) ) +- { +- xwm_log.errorf("Failed to load host cursor. Falling back to left_ptr."); +- if (!ctx->cursor->setCursorImageByName("left_ptr")) +- xwm_log.errorf("Failed to load mouse cursor: left_ptr"); +- } ++ ctx->cursor->setCursorImage( ++ reinterpret_cast( oHostCursor->pPixels.data() ), ++ oHostCursor->uWidth, ++ oHostCursor->uHeight, ++ oHostCursor->uXHotspot, ++ oHostCursor->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"); ++ 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" ); + } + } + +@@ -7613,7 +7248,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ + + void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = nullptr) + { +- bool capable = drm_get_vrr_capable( &g_DRM ); ++ bool capable = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->SupportsVRR(); + if ( capable != g_bVRRCapable_CachedValue || force ) + { + uint32_t capable_value = capable ? 1 : 0; +@@ -7624,7 +7259,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = + *needs_flush = true; + } + +- bool HDR = BIsNested() ? vulkan_supports_hdr10() : drm_supports_hdr( &g_DRM ); ++ bool HDR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->SupportsHDR(); + if ( HDR != g_bSupportsHDR_CachedValue || force ) + { + uint32_t hdr_value = HDR ? 1 : 0; +@@ -7635,7 +7270,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = + *needs_flush = true; + } + +- bool in_use = drm_get_vrr_in_use( &g_DRM ); ++ bool in_use = GetBackend()->IsVRRActive(); + if ( in_use != g_bVRRInUse_CachedValue || force ) + { + uint32_t in_use_value = in_use ? 1 : 0; +@@ -7673,7 +7308,7 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) + if (needs_flush) + *needs_flush = true; + +- if ( drm_get_screen_type(&g_DRM) == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) ++ if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) + { + XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayModeListExternal); + +@@ -7683,20 +7318,20 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) + return; + } + +- if ( !g_DRM.pConnector || !g_DRM.pConnector->GetModeConnector() ) +- { ++ 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 < g_DRM.pConnector->GetModeConnector()->count_modes; i++) ++ for (int i = 0; remaining_size > 0 && i < (int)connectorModes.size(); i++) + { +- const auto& mode = g_DRM.pConnector->GetModeConnector()->modes[i]; ++ const auto& mode = connectorModes[i]; + int mode_len = snprintf(&modes[len], remaining_size, "%s%dx%d@%d", + i == 0 ? "" : " ", +- int(mode.hdisplay), int(mode.vdisplay), int(mode.vrefresh)); ++ int(mode.uWidth), int(mode.uHeight), int(mode.uRefresh)); + len += mode_len; + remaining_size -= mode_len; + } +@@ -7713,8 +7348,6 @@ extern int g_nPreferredOutputHeight; + + static bool g_bWasFSRActive = false; + +-extern std::atomic g_nCompletedPageFlipCount; +- + void steamcompmgr_check_xdg(bool vblank) + { + if (wlserver_xdg_dirty()) +@@ -7741,25 +7374,22 @@ void steamcompmgr_check_xdg(bool vblank) + + void update_edid_prop() + { +- if ( !BIsNested() ) +- { +- const char *filename = drm_get_patched_edid_path(); +- if (!filename) +- return; ++ 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++) ++ gamescope_xwayland_server_t *server = NULL; ++ for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) ++ { ++ XTextProperty text_property = + { +- XTextProperty text_property = +- { +- .value = (unsigned char *)filename, +- .encoding = server->ctx->atoms.utf8StringAtom, +- .format = 8, +- .nitems = strlen(filename), +- }; ++ .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 ); +- } ++ XSetTextProperty( server->ctx->dpy, server->ctx->root, &text_property, server->ctx->atoms.gamescopeDisplayEdidPath ); + } + } + +@@ -7771,7 +7401,7 @@ steamcompmgr_main(int argc, char **argv) + // Reset getopt() state + optind = 1; + +- bSteamCompMgrGrab = BIsNested() && g_bForceRelativeMouse; ++ bSteamCompMgrGrab = GetBackend()->GetNestedHints() && g_bForceRelativeMouse; + + int o; + int opt_index = -1; +@@ -7872,10 +7502,6 @@ steamcompmgr_main(int argc, char **argv) + currentOutputHeight = g_nPreferredOutputHeight; + + init_runtime_info(); +-#if HAVE_OPENVR +- if ( BIsVRSession() ) +- vrsession_steam_mode( steamMode ); +-#endif + + std::unique_lock xwayland_server_guard(g_SteamCompMgrXWaylandServerMutex); + +@@ -7910,8 +7536,8 @@ steamcompmgr_main(int argc, char **argv) + } + + bool vblank = false; +- g_SteamCompMgrWaiter.AddWaitable( &g_VBlankTimer ); +- g_VBlankTimer.ArmNextVBlank( true ); ++ g_SteamCompMgrWaiter.AddWaitable( &GetVBlankTimer() ); ++ GetVBlankTimer().ArmNextVBlank( true ); + + { + gamescope_xwayland_server_t *pServer = NULL; +@@ -7928,18 +7554,16 @@ steamcompmgr_main(int argc, char **argv) + update_mode_atoms(root_ctx); + XFlush(root_ctx->dpy); + +- if ( !BIsNested() ) +- { +- drm_update_patched_edid(&g_DRM); +- update_edid_prop(); +- } ++ GetBackend()->PostInit(); ++ ++ update_edid_prop(); + + update_screenshot_color_mgmt(); + + // 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 = drm_create_ctm(&g_DRM, glm::mat3x4(glm::transpose(k_2020_from_709))); ++ s_scRGB709To2020Matrix = GetBackend()->CreateBackendBlob( glm::mat3x4( glm::transpose( k_2020_from_709 ) ) ); + + for (;;) + { +@@ -7957,7 +7581,7 @@ steamcompmgr_main(int argc, char **argv) + + g_SteamCompMgrWaiter.PollEvents(); + +- if ( std::optional pendingVBlank = g_VBlankTimer.ProcessVBlank() ) ++ if ( std::optional pendingVBlank = GetVBlankTimer().ProcessVBlank() ) + { + g_SteamCompMgrVBlankTime = *pendingVBlank; + vblank = true; +@@ -7994,14 +7618,11 @@ steamcompmgr_main(int argc, char **argv) + + // If our DRM state is out-of-date, refresh it. This might update + // the output size. +- if ( BIsNested() == false ) ++ if ( GetBackend()->PollState() ) + { +- if ( drm_poll_state( &g_DRM ) ) +- { +- hasRepaint = true; ++ hasRepaint = true; + +- update_mode_atoms(root_ctx, &flush_root); +- } ++ update_mode_atoms(root_ctx, &flush_root); + } + + g_bOutputHDREnabled = (g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; +@@ -8023,7 +7644,8 @@ steamcompmgr_main(int argc, char **argv) + wlserver_unlock(); + } + +- if ( BIsSDLSession() ) ++ // XXX(JoshA): Remake this. It sucks. ++ if ( GetBackend()->UsesVulkanSwapchain() ) + { + vulkan_remake_swapchain(); + +@@ -8162,7 +7784,7 @@ steamcompmgr_main(int argc, char **argv) + + { + GamescopeAppTextureColorspace current_app_colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; +- std::shared_ptr app_hdr_metadata = nullptr; ++ std::shared_ptr app_hdr_metadata = nullptr; + if ( g_HeldCommits[HELD_COMMIT_BASE] ) + { + current_app_colorspace = g_HeldCommits[HELD_COMMIT_BASE]->colorspace(); +@@ -8192,7 +7814,7 @@ steamcompmgr_main(int argc, char **argv) + 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->metadata, sizeof(hdr_metadata_infoframe)); ++ memcpy(app_hdr_metadata_blob.data(), &app_hdr_metadata->View(), 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() ); +@@ -8235,7 +7857,7 @@ steamcompmgr_main(int argc, char **argv) + + static int nIgnoredOverlayRepaints = 0; + +- const bool bVRR = drm_get_vrr_in_use( &g_DRM ); ++ const bool bVRR = GetBackend()->IsVRRActive(); + + // HACK: Disable tearing if we have an overlay to avoid stutters right now + // TODO: Fix properly. +@@ -8252,13 +7874,13 @@ steamcompmgr_main(int argc, char **argv) + // 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 bNeedsSyncFlip = bForceSyncFlip || g_VBlankTimer.WasCompositing() || nIgnoredOverlayRepaints; +- const bool bDoAsyncFlip = ( ((g_nAsyncFlipsEnabled >= 1) && g_bSupportsAsyncFlips && bSurfaceWantsAsync && !bHasOverlay) || bVRR ) && !bSteamOverlayOpen && !bNeedsSyncFlip; ++ const bool bNeedsSyncFlip = bForceSyncFlip || GetVBlankTimer().WasCompositing() || nIgnoredOverlayRepaints; ++ const bool bDoAsyncFlip = ( ((g_nAsyncFlipsEnabled >= 1) && GetBackend()->SupportsTearing() && bSurfaceWantsAsync && !bHasOverlay) || bVRR ) && !bSteamOverlayOpen && !bNeedsSyncFlip; + + bool bShouldPaint = false; + if ( bDoAsyncFlip ) + { +- if ( hasRepaint && !g_VBlankTimer.WasCompositing() ) ++ if ( hasRepaint && !GetVBlankTimer().WasCompositing() ) + bShouldPaint = true; + } + else +@@ -8267,16 +7889,14 @@ steamcompmgr_main(int argc, char **argv) + } + + // If we have a pending page flip and doing VRR, lets not do another... +- if ( bVRR && g_nCompletedPageFlipCount != g_DRM.flipcount ) ++ if ( bVRR && GetBackend()->PresentationFeedback().CurrentPresentsInFlight() != 0 ) + bShouldPaint = false; + + if ( !bShouldPaint && hasRepaintNonBasePlane && vblank ) + nIgnoredOverlayRepaints++; + +-#if HAVE_OPENVR +- if ( BIsVRSession() && !vrsession_visible() ) ++ if ( !GetBackend()->IsVisible() ) + bShouldPaint = false; +-#endif + + if ( bShouldPaint ) + { +@@ -8294,7 +7914,7 @@ steamcompmgr_main(int argc, char **argv) + // + // Juuust in case pageflip handler doesn't happen + // so we don't stop vblanking forever. +- g_VBlankTimer.ArmNextVBlank( true ); ++ GetVBlankTimer().ArmNextVBlank( true ); + } + + update_vrr_atoms(root_ctx, false, &flush_root); +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index 2429ee066..69ed2cb4c 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -43,11 +43,6 @@ extern bool g_bForceHDRSupportDebug; + + extern EStreamColorspace g_ForcedNV12ColorSpace; + +-// Disable partial composition for now until we get +-// composite priorities working in libliftoff + also +-// use the proper libliftoff composite plane system. +-static constexpr bool kDisablePartialComposition = true; +- + struct CursorBarrierInfo + { + int x1 = 0; +@@ -164,7 +159,7 @@ extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; + extern pid_t focusWindow_pid; + + void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server); +-void gamescope_set_selection(std::string contents, int selection); ++void gamescope_set_selection(std::string contents, GamescopeSelection eSelection); + + MouseCursor *steamcompmgr_get_current_cursor(); + MouseCursor *steamcompmgr_get_server_cursor(uint32_t serverId); +diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp +index 7de514583..7f7e6ea93 100644 +--- a/src/steamcompmgr_shared.hpp ++++ b/src/steamcompmgr_shared.hpp +@@ -1,8 +1,10 @@ + #pragma once + +-#include "xwayland_ctx.hpp" + #include + #include ++#include ++ ++#include "xwayland_ctx.hpp" + #include "gamescope-control-protocol.h" + + struct commit_t; +diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp +index 4b617584e..74cfe3dca 100644 +--- a/src/vblankmanager.cpp ++++ b/src/vblankmanager.cpp +@@ -16,20 +16,12 @@ + + #include "vblankmanager.hpp" + #include "steamcompmgr.hpp" +-#include "wlserver.hpp" + #include "main.hpp" +-#include "drm.hpp" +- +-#if HAVE_OPENVR +-#include "vr_session.hpp" +-#endif + + LogScope g_VBlankLog("vblank"); + + // #define VBLANK_DEBUG + +-extern bool env_to_bool(const char *env); +- + namespace gamescope + { + CVBlankTimer::CVBlankTimer() +@@ -37,10 +29,10 @@ namespace gamescope + m_ulTargetVBlank = get_time_in_nanos(); + m_ulLastVBlank = m_ulTargetVBlank; + +- const bool bShouldUseTimerFD = !BIsVRSession() || env_to_bool( "GAMESCOPE_DISABLE_TIMERFD" ); +- +- if ( bShouldUseTimerFD ) ++ if ( !GetBackend()->NeedsFrameSync() ) + { ++ // Majority of backends fall down this optimal ++ // timerfd path, vs nudge thread. + g_VBlankLog.infof( "Using timerfd." ); + } + else +@@ -53,18 +45,8 @@ namespace gamescope + abort(); + } + +-#if HAVE_OPENVR +- if ( BIsVRSession() ) +- { +- std::thread vblankThread( [this]() { this->VRNudgeThread(); } ); +- vblankThread.detach(); +- } +- else +-#endif +- { +- std::thread vblankThread( [this]() { this->NudgeThread(); } ); +- vblankThread.detach(); +- } ++ std::thread vblankThread( [this]() { this->NudgeThread(); } ); ++ vblankThread.detach(); + } + } + +@@ -112,7 +94,7 @@ namespace gamescope + + VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) + { +- const GamescopeScreenType eScreenType = drm_get_screen_type( &g_DRM ); ++ const GamescopeScreenType eScreenType = GetBackend()->GetScreenType(); + + const int nRefreshRate = GetRefresh(); + const uint64_t ulRefreshInterval = kSecInNanoSecs / nRefreshRate; +@@ -129,7 +111,7 @@ namespace gamescope + ? m_ulVBlankDrawBufferRedZone + : ( m_ulVBlankDrawBufferRedZone * 60 * kSecInNanoSecs ) / ( nRefreshRate * kSecInNanoSecs ); + +- bool bVRR = drm_get_vrr_in_use( &g_DRM ); ++ bool bVRR = GetBackend()->IsVRRActive(); + uint64_t ulOffset = 0; + if ( !bVRR ) + { +@@ -368,43 +350,6 @@ namespace gamescope + #endif + } + +-#if HAVE_OPENVR +- void CVBlankTimer::VRNudgeThread() +- { +- pthread_setname_np( pthread_self(), "gamescope-vblkvr" ); +- +- for ( ;; ) +- { +- vrsession_wait_until_visible(); +- +- // Includes redzone. +- vrsession_framesync( ~0u ); +- +- uint64_t ulWakeupTime = get_time_in_nanos(); +- +- VBlankTime timeInfo = +- { +- .schedule = +- { +- .ulTargetVBlank = ulWakeupTime + 3'000'000, // Not right. just a stop-gap for now. +- .ulScheduledWakeupPoint = ulWakeupTime, +- }, +- .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)" ); +- } +- } +- } +-#endif +- + void CVBlankTimer::NudgeThread() + { + pthread_setname_np( pthread_self(), "gamescope-vblk" ); +@@ -416,10 +361,9 @@ namespace gamescope + if ( !m_bRunning ) + return; + +- VBlankScheduleTime schedule = CalcNextWakeupTime( false ); +- sleep_until_nanos( schedule.ulScheduledWakeupPoint ); +- const uint64_t ulWakeupTime = get_time_in_nanos(); ++ VBlankScheduleTime schedule = GetBackend()->FrameSync(); + ++ const uint64_t ulWakeupTime = get_time_in_nanos(); + { + std::unique_lock lock( m_ScheduleMutex ); + +@@ -446,5 +390,9 @@ namespace gamescope + } + } + +-gamescope::CVBlankTimer g_VBlankTimer{}; ++gamescope::CVBlankTimer &GetVBlankTimer() ++{ ++ static gamescope::CVBlankTimer s_VBlankTimer; ++ return s_VBlankTimer; ++} + +diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp +index 0a7f1c428..a08af1700 100644 +--- a/src/vblankmanager.hpp ++++ b/src/vblankmanager.hpp +@@ -128,11 +128,9 @@ namespace gamescope + // 93% by default. (kDefaultVBlankRateOfDecayPercentage) + uint64_t m_ulVBlankRateOfDecayPercentage = kDefaultVBlankRateOfDecayPercentage; + +-#if HAVE_OPENVR +- void VRNudgeThread(); +-#endif + void NudgeThread(); + }; + } + +-extern gamescope::CVBlankTimer g_VBlankTimer; ++gamescope::CVBlankTimer &GetVBlankTimer(); ++ +diff --git a/src/vr_session.cpp b/src/vr_session.cpp +index 8696b62d2..9bf8085cd 100644 +--- a/src/vr_session.cpp ++++ b/src/vr_session.cpp +@@ -1,7 +1,16 @@ +-#include "vr_session.hpp" ++#include ++#include ++#define VK_NO_PROTOTYPES ++#include ++ ++#pragma GCC diagnostic push ++#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" ++#include ++#pragma GCC diagnostic pop ++ ++#include "backend.h" + #include "main.hpp" + #include "openvr.h" +-#include "rendervulkan.hpp" + #include "steamcompmgr.hpp" + #include "wlserver.hpp" + #include "log.hpp" +@@ -11,107 +20,18 @@ + #include + #include + #include +-#include ++ ++struct wlserver_input_method; ++ ++extern bool steamMode; ++extern int g_argc; ++extern char **g_argv; + + static LogScope openvr_log("openvr"); + + static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList ); + static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, std::vector< std::string > &outDeviceExtensionList ); +-static void vrsession_input_thread(); +- +-struct OpenVRSession +-{ +- const char *pchOverlayKey = nullptr; +- const char *pchOverlayName = nullptr; +- const char *pchOverlayIcon = nullptr; +- bool bExplicitOverlayName = false; +- bool bNudgeToVisible = false; +- bool bEnableControlBar = false; +- bool bEnableControlBarKeyboard = false; +- bool bEnableControlBarClose = false; +- bool bModal = false; +- float flPhysicalWidth = 2.0f; +- float flPhysicalCurvature = 0.0f; +- float flPhysicalPreCurvePitch = 0.0f; +- float flScrollSpeed = 8.0f; +- float flScrollAccum[2] = { 0.0f, 0.0f }; +- vr::VROverlayHandle_t hOverlay = vr::k_ulOverlayHandleInvalid; +- vr::VROverlayHandle_t hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; +- struct wlserver_input_method *pIME = nullptr; +-}; +- +-OpenVRSession &GetVR() +-{ +- static OpenVRSession s_Global; +- return s_Global; +-} +- +-bool vr_init(int argc, char **argv) +-{ +- vr::EVRInitError error = vr::VRInitError_None; +- VR_Init(&error, vr::VRApplication_Background); +- +- if ( error != vr::VRInitError_None ) +- { +- openvr_log.errorf("Unable to init VR runtime: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription( error )); +- return false; +- } +- +- // Reset getopt() state +- optind = 1; +- +- 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 0: // long options without a short option +- opt_name = gamescope_options[opt_index].name; +- if (strcmp(opt_name, "vr-overlay-key") == 0) { +- GetVR().pchOverlayKey = optarg; +- } else if (strcmp(opt_name, "vr-overlay-explicit-name") == 0) { +- GetVR().pchOverlayName = optarg; +- GetVR().bExplicitOverlayName = true; +- } else if (strcmp(opt_name, "vr-overlay-default-name") == 0) { +- GetVR().pchOverlayName = optarg; +- } else if (strcmp(opt_name, "vr-overlay-icon") == 0) { +- GetVR().pchOverlayIcon = optarg; +- } else if (strcmp(opt_name, "vr-overlay-show-immediately") == 0) { +- GetVR().bNudgeToVisible = true; +- } else if (strcmp(opt_name, "vr-overlay-enable-control-bar") == 0) { +- GetVR().bEnableControlBar = true; +- } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-keyboard") == 0) { +- GetVR().bEnableControlBarKeyboard = true; +- } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-close") == 0) { +- GetVR().bEnableControlBarClose = true; +- } else if (strcmp(opt_name, "vr-overlay-modal") == 0) { +- GetVR().bModal = true; +- } else if (strcmp(opt_name, "vr-overlay-physical-width") == 0) { +- GetVR().flPhysicalWidth = atof( optarg ); +- if ( GetVR().flPhysicalWidth <= 0.0f ) +- GetVR().flPhysicalWidth = 2.0f; +- } else if (strcmp(opt_name, "vr-overlay-physical-curvature") == 0) { +- GetVR().flPhysicalCurvature = atof( optarg ); +- } else if (strcmp(opt_name, "vr-overlay-physical-pre-curve-pitch") == 0) { +- GetVR().flPhysicalPreCurvePitch = atof( optarg ); +- } else if (strcmp(opt_name, "vr-scroll-speed") == 0) { +- GetVR().flScrollSpeed = atof( optarg ); +- } +- break; +- case '?': +- assert(false); // unreachable +- } +- } +- +- if (!GetVR().pchOverlayKey) +- GetVR().pchOverlayKey = wlserver_get_wl_display_name(); +- +- if (!GetVR().pchOverlayName) +- GetVR().pchOverlayName = "Gamescope"; + +- return true; +-} + + // Not in public headers yet. + namespace vr +@@ -122,302 +42,6 @@ namespace vr + const EVRButtonId k_EButton_QAM = (EVRButtonId)(51); + } + +-bool vrsession_init() +-{ +- // Setup the overlay. +- +- if ( !vr::VROverlay() ) +- { +- openvr_log.errorf("SteamVR runtime version mismatch!\n"); +- return false; +- } +- +- vr::VROverlay()->CreateDashboardOverlay( +- GetVR().pchOverlayKey, +- GetVR().pchOverlayName, +- &GetVR().hOverlay, &GetVR().hOverlayThumbnail ); +- +- vr::VROverlay()->SetOverlayInputMethod( GetVR().hOverlay, vr::VROverlayInputMethod_Mouse ); +- +- vr::HmdVector2_t vMouseScale = { { (float)g_nOutputWidth, (float)g_nOutputHeight } }; +- vr::VROverlay()->SetOverlayMouseScale( GetVR().hOverlay, &vMouseScale ); +- +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, true ); +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBar, GetVR().bEnableControlBar ); +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarKeyboard, GetVR().bEnableControlBarKeyboard ); +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarClose, GetVR().bEnableControlBarClose ); +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_WantsModalBehavior, GetVR().bModal ); +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); +- vrsession_update_touch_mode(); +- +- vr::VROverlay()->SetOverlayWidthInMeters( GetVR().hOverlay, GetVR().flPhysicalWidth ); +- vr::VROverlay()->SetOverlayCurvature ( GetVR().hOverlay, GetVR().flPhysicalCurvature ); +- vr::VROverlay()->SetOverlayPreCurvePitch( GetVR().hOverlay, GetVR().flPhysicalPreCurvePitch ); +- +- if ( GetVR().pchOverlayIcon ) +- { +- vr::EVROverlayError err = vr::VROverlay()->SetOverlayFromFile( GetVR().hOverlayThumbnail, GetVR().pchOverlayIcon ); +- if( err != vr::VROverlayError_None ) +- { +- openvr_log.errorf( "Unable to set thumbnail to %s: %s\n", GetVR().pchOverlayIcon, vr::VROverlay()->GetOverlayErrorNameFromEnum( err ) ); +- } +- } +- +- // Setup misc. stuff +- +- g_nOutputRefresh = (int) vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ); +- +- std::thread input_thread_vrinput( vrsession_input_thread ); +- input_thread_vrinput.detach(); +- +- return true; +-} +- +-std::mutex g_OverlayVisibleMutex; +-std::condition_variable g_OverlayVisibleCV; +-std::atomic g_bOverlayVisible = { false }; +- +-bool vrsession_visible() +-{ +- return g_bOverlayVisible.load(); +-} +- +-void vrsession_set_dashboard_visible( bool bVisible ) +-{ +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_VisibleInDashboard, bVisible ); +-} +- +-void vrsession_wait_until_visible() +-{ +- if (vrsession_visible()) +- return; +- +- std::unique_lock lock(g_OverlayVisibleMutex); +- g_OverlayVisibleCV.wait( lock, []{ return g_bOverlayVisible.load(); } ); +-} +- +-void vrsession_present( vr::VRVulkanTextureData_t *pTextureData ) +-{ +- vr::Texture_t texture = { pTextureData, vr::TextureType_Vulkan, vr::ColorSpace_Gamma }; +- vr::VROverlay()->SetOverlayTexture( GetVR().hOverlay, &texture ); +- if ( GetVR().bNudgeToVisible ) +- { +- vr::VROverlay()->ShowDashboard( GetVR().pchOverlayKey ); +- GetVR().bNudgeToVisible = false; +- } +-} +- +-static void vector_append_unique_str( std::vector& exts, const char *str ) +-{ +- for ( auto &c_str : exts ) +- { +- if ( !strcmp( c_str, str ) ) +- return; +- } +- +- exts.push_back( str ); +-} +- +-void vrsession_append_instance_exts( std::vector& exts ) +-{ +- static std::vector s_exts; +- GetVulkanInstanceExtensionsRequired( s_exts ); +- +- for (const auto &str : s_exts) +- vector_append_unique_str( exts, str.c_str() ); +-} +- +-void vrsession_append_device_exts( VkPhysicalDevice physDev, std::vector& exts ) +-{ +- static std::vector s_exts; +- GetVulkanDeviceExtensionsRequired( physDev, s_exts ); +- +- for (const auto &str : s_exts) +- vector_append_unique_str( exts, str.c_str() ); +-} +- +-bool vrsession_framesync( uint32_t timeoutMS ) +-{ +- return vr::VROverlay()->WaitFrameSync( timeoutMS ) != vr::VROverlayError_None; +-} +- +-/* +-static int VRButtonToWLButton( vr::EVRMouseButton mb ) +-{ +- switch( mb ) +- { +- default: +- case vr::VRMouseButton_Left: +- return BTN_LEFT; +- case vr::VRMouseButton_Right: +- return BTN_RIGHT; +- case vr::VRMouseButton_Middle: +- return BTN_MIDDLE; +- } +-} +-*/ +- +-bool vrsession_ime_init() +-{ +- GetVR().pIME = create_local_ime(); +- return true; +-} +- +-static void vrsession_input_thread() +-{ +- pthread_setname_np( pthread_self(), "gamescope-vrinp" ); +- +- // Josh: PollNextOverlayEvent sucks. +- // I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep. +- while (true) +- { +- vr::VREvent_t vrEvent; +- while( vr::VROverlay()->PollNextOverlayEvent( GetVR().hOverlay, &vrEvent, sizeof( vrEvent ) ) ) +- { +- uint32_t timestamp = vrEvent.eventAgeSeconds * 1'000'000; +- +- switch( vrEvent.eventType ) +- { +- case vr::VREvent_OverlayClosed: +- case vr::VREvent_Quit: +- raise( SIGTERM ); +- break; +- +- case vr::VREvent_KeyboardCharInput: +- { +- if (GetVR().pIME) +- { +- type_text(GetVR().pIME, vrEvent.data.keyboard.cNewInput); +- } +- break; +- } +- +- case vr::VREvent_MouseMove: +- { +- float x = vrEvent.data.mouse.x; +- float y = g_nOutputHeight - vrEvent.data.mouse.y; +- +- x /= (float)g_nOutputWidth; +- y /= (float)g_nOutputHeight; +- +- wlserver_lock(); +- wlserver_touchmotion( x, y, 0, timestamp ); +- wlserver_unlock(); +- break; +- } +- case vr::VREvent_MouseButtonUp: +- case vr::VREvent_MouseButtonDown: +- { +- float x = vrEvent.data.mouse.x; +- float y = g_nOutputHeight - vrEvent.data.mouse.y; +- +- x /= (float)g_nOutputWidth; +- y /= (float)g_nOutputHeight; +- +- wlserver_lock(); +- if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) +- wlserver_touchdown( x, y, 0, timestamp ); +- else +- wlserver_touchup( 0, timestamp ); +- wlserver_unlock(); +- break; +- } +- +- case vr::VREvent_ScrollSmooth: +- { +- wlserver_lock(); +- +- GetVR().flScrollAccum[0] += -vrEvent.data.scroll.xdelta * GetVR().flScrollSpeed; +- GetVR().flScrollAccum[1] += -vrEvent.data.scroll.ydelta * GetVR().flScrollSpeed; +- +- float dx, dy; +- GetVR().flScrollAccum[0] = modf( GetVR().flScrollAccum[0], &dx ); +- GetVR().flScrollAccum[1] = modf( GetVR().flScrollAccum[1], &dy ); +- +- wlserver_mousewheel( dx, dy, timestamp ); +- wlserver_unlock(); +- break; +- } +- +- case vr::VREvent_ButtonPress: +- { +- vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button; +- +- if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM) +- break; +- +- if (button == vr::k_EButton_Steam) +- openvr_log.infof("STEAM button pressed."); +- else +- openvr_log.infof("QAM button pressed."); +- +- wlserver_open_steam_menu( button == vr::k_EButton_QAM ); +- break; +- } +- +- case vr::VREvent_OverlayShown: +- case vr::VREvent_OverlayHidden: +- { +- { +- std::unique_lock lock(g_OverlayVisibleMutex); +- g_bOverlayVisible = vrEvent.eventType == vr::VREvent_OverlayShown; +- } +- g_OverlayVisibleCV.notify_all(); +- } +- } +- } +- sleep_for_nanos(2'000'000); +- } +-} +- +-void vrsession_update_touch_mode() +-{ +- const bool bHideLaserIntersection = g_nTouchClickMode != WLSERVER_TOUCH_CLICK_PASSTHROUGH; +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_HideLaserIntersection, bHideLaserIntersection ); +-} +- +-struct rgba_t +-{ +- uint8_t r,g,b,a; +-}; +- +-void vrsession_title( const char *title, std::shared_ptr> icon ) +-{ +- if ( !GetVR().bExplicitOverlayName ) +- { +- vr::VROverlay()->SetOverlayName( GetVR().hOverlay, (title && *title) ? title : GetVR().pchOverlayName ); +- } +- +- if ( icon && icon->size() >= 3 ) +- { +- const uint32_t width = (*icon)[0]; +- const uint32_t height = (*icon)[1]; +- +- for (uint32_t& val : *icon) +- { +- rgba_t rgb = *((rgba_t*)&val); +- std::swap(rgb.r, rgb.b); +- val = *((uint32_t*)&rgb); +- } +- +- vr::VROverlay()->SetOverlayRaw( GetVR().hOverlayThumbnail, &(*icon)[2], width, height, sizeof(uint32_t) ); +- } +- else if ( GetVR().pchOverlayName ) +- { +- vr::VROverlay()->SetOverlayFromFile( GetVR().hOverlayThumbnail, GetVR().pchOverlayIcon ); +- } +- else +- { +- vr::VROverlay()->ClearOverlayTexture( GetVR().hOverlayThumbnail ); +- } +-} +- +-void vrsession_steam_mode( bool steamMode ) +-{ +- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode ); +-} +- + /////////////////////////////////////////////// + // Josh: + // GetVulkanInstanceExtensionsRequired and GetVulkanDeviceExtensionsRequired return *space separated* exts :( +@@ -508,3 +132,630 @@ static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, + + return true; + } ++ ++namespace gamescope ++{ ++ class CVROverlayConnector final : public IBackendConnector ++ { ++ public: ++ ++ ////////////////////// ++ // IBackendConnector ++ ////////////////////// ++ ++ CVROverlayConnector() ++ { ++ } ++ virtual ~CVROverlayConnector() ++ { ++ } ++ ++ virtual 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 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 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; ++ } ++ ++ virtual const char *GetName() const override ++ { ++ return "OpenVR"; ++ } ++ virtual const char *GetMake() const override ++ { ++ return "Gamescope"; ++ } ++ virtual const char *GetModel() const override ++ { ++ return "Virtual Display"; ++ } ++ ++ private: ++ BackendConnectorHDRInfo m_HDRInfo{}; ++ }; ++ ++ class COpenVRBackend final : public CBaseBackend, public INestedHints ++ { ++ public: ++ COpenVRBackend() ++ { ++ } ++ ++ virtual ~COpenVRBackend() ++ { ++ } ++ ++ ///////////// ++ // IBackend ++ ///////////// ++ ++ virtual bool Init() override ++ { ++ vr::EVRInitError error = vr::VRInitError_None; ++ VR_Init(&error, vr::VRApplication_Background); ++ ++ if ( error != vr::VRInitError_None ) ++ { ++ openvr_log.errorf("Unable to init VR runtime: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription( error )); ++ return false; ++ } ++ ++ // 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) ++ { ++ const char *opt_name; ++ 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_pchOverlayKey = 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) { ++ m_pchOverlayName = optarg; ++ } else if (strcmp(opt_name, "vr-overlay-icon") == 0) { ++ m_pchOverlayIcon = optarg; ++ } else if (strcmp(opt_name, "vr-overlay-show-immediately") == 0) { ++ m_bNudgeToVisible = true; ++ } 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) { ++ m_bEnableControlBarKeyboard = true; ++ } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-close") == 0) { ++ m_bEnableControlBarClose = true; ++ } else if (strcmp(opt_name, "vr-overlay-modal") == 0) { ++ m_bModal = true; ++ } 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) { ++ m_flPhysicalCurvature = atof( optarg ); ++ } 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) { ++ m_flScrollSpeed = atof( optarg ); ++ } ++ break; ++ case '?': ++ assert(false); // unreachable ++ } ++ } ++ ++ if ( m_pchOverlayKey ) ++ m_pchOverlayKey = wlserver_get_wl_display_name(); ++ ++ if ( m_pchOverlayName ) ++ m_pchOverlayName = "Gamescope"; ++ ++ if ( !vr::VROverlay() ) ++ { ++ openvr_log.errorf( "SteamVR runtime version mismatch!\n" ); ++ return false; ++ } ++ ++ vr::VROverlay()->CreateDashboardOverlay( ++ m_pchOverlayKey, ++ m_pchOverlayName, ++ &m_hOverlay, &m_hOverlayThumbnail ); ++ ++ vr::VROverlay()->SetOverlayInputMethod( m_hOverlay, vr::VROverlayInputMethod_Mouse ); ++ ++ vr::HmdVector2_t vMouseScale = { { (float)g_nOutputWidth, (float)g_nOutputHeight } }; ++ vr::VROverlay()->SetOverlayMouseScale( m_hOverlay, &vMouseScale ); ++ ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, true ); ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBar, m_bEnableControlBar ); ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarKeyboard, m_bEnableControlBarKeyboard ); ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarClose, m_bEnableControlBarClose ); ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_WantsModalBehavior, m_bModal ); ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); ++ ++ vr::VROverlay()->SetOverlayWidthInMeters( m_hOverlay, m_flPhysicalWidth ); ++ vr::VROverlay()->SetOverlayCurvature ( m_hOverlay, m_flPhysicalCurvature ); ++ vr::VROverlay()->SetOverlayPreCurvePitch( m_hOverlay, m_flPhysicalPreCurvePitch ); ++ ++ if ( m_pchOverlayIcon ) ++ { ++ vr::EVROverlayError err = vr::VROverlay()->SetOverlayFromFile( m_hOverlayThumbnail, m_pchOverlayIcon ); ++ if( err != vr::VROverlayError_None ) ++ { ++ openvr_log.errorf( "Unable to set thumbnail to %s: %s\n", m_pchOverlayIcon, vr::VROverlay()->GetOverlayErrorNameFromEnum( err ) ); ++ } ++ } ++ ++ // Setup misc. stuff ++ g_nOutputRefresh = (int) vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ); ++ ++ std::thread input_thread_vrinput( [this](){ this->VRInputThread(); } ); ++ input_thread_vrinput.detach(); ++ ++ return true; ++ } ++ ++ virtual bool PostInit() override ++ { ++ m_pIME = create_local_ime(); ++ if ( !m_pIME ) ++ return false; ++ ++ return true; ++ } ++ ++ 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 ++ { ++ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override ++ { ++ *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; ++ *pOverlayPlaneFormat = VK_FORMAT_B8G8R8A8_UNORM; ++ } ++ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override ++ { ++ return true; ++ } ++ ++ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override ++ { ++ // TODO: Resolve const crap ++ std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); ++ if ( !oCompositeResult ) ++ return -EINVAL; ++ ++ UpdateTouchMode(); ++ ++ auto outputImage = vulkan_get_last_output_image( false, false ); ++ ++ vr::VRVulkanTextureData_t data = ++ { ++ .m_nImage = (uint64_t)(uintptr_t)outputImage->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 = outputImage->width(), ++ .m_nHeight = outputImage->height(), ++ .m_nFormat = outputImage->format(), ++ .m_nSampleCount = 1, ++ }; ++ ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode ); ++ ++ // Wait for the composite result on our side *after* we ++ // commit the buffer to the compositor to avoid a bubble. ++ vulkan_wait( *oCompositeResult, true ); ++ ++ vr::Texture_t texture = { &data, vr::TextureType_Vulkan, vr::ColorSpace_Gamma }; ++ vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture ); ++ if ( m_bNudgeToVisible ) ++ { ++ vr::VROverlay()->ShowDashboard( m_pchOverlayKey ); ++ m_bNudgeToVisible = false; ++ } ++ ++ return 0; ++ } ++ ++ virtual void DirtyState( bool bForce, bool bForceModeset ) override ++ { ++ } ++ ++ virtual bool PollState() override ++ { ++ return false; ++ } ++ ++ virtual std::shared_ptr CreateBackendBlob( std::span data ) override ++ { ++ return std::make_shared( data ); ++ } ++ ++ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override ++ { ++ return 0; ++ } ++ ++ virtual void LockBackendFb( uint32_t uFbId ) override ++ { ++ abort(); ++ } ++ virtual void UnlockBackendFb( uint32_t uFbId ) override ++ { ++ abort(); ++ } ++ virtual void DropBackendFb( uint32_t uFbId ) override ++ { ++ abort(); ++ } ++ ++ 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 IsVRRActive() const override ++ { ++ return false; ++ } ++ ++ 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 IsVisible() const override ++ { ++ return m_bOverlayVisible.load(); ++ } ++ ++ virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override ++ { ++ return uvecSize; ++ } ++ ++ virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override ++ { ++ return false; ++ } ++ ++ virtual void HackUpdatePatchedEdid() override ++ { ++ } ++ ++ virtual bool NeedsFrameSync() const override ++ { ++ return true; ++ } ++ virtual VBlankScheduleTime FrameSync() override ++ { ++ WaitUntilVisible(); ++ ++ if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None ) ++ openvr_log.errorf( "WaitFrameSync failed!" ); ++ ++ uint64_t ulNow = get_time_in_nanos(); ++ return VBlankScheduleTime ++ { ++ .ulTargetVBlank = ulNow + 3'000'000, // Not right. just a stop-gap for now. ++ .ulScheduledWakeupPoint = ulNow, ++ }; ++ } ++ ++ virtual INestedHints *GetNestedHints() override ++ { ++ return this; ++ } ++ ++ /////////////////// ++ // INestedHints ++ /////////////////// ++ ++ virtual void SetCursorImage( std::shared_ptr info ) override ++ { ++ } ++ virtual void SetRelativeMouseMode( bool bRelative ) override ++ { ++ } ++ virtual void SetVisible( bool bVisible ) override ++ { ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, bVisible ); ++ } ++ virtual void SetTitle( std::shared_ptr szTitle ) override ++ { ++ if ( !m_bExplicitOverlayName ) ++ vr::VROverlay()->SetOverlayName( m_hOverlay, szTitle ? szTitle->c_str() : m_pchOverlayName ); ++ ++ } ++ virtual void SetIcon( std::shared_ptr> uIconPixels ) override ++ { ++ if ( uIconPixels && uIconPixels->size() >= 3 ) ++ { ++ const uint32_t uWidth = (*uIconPixels)[0]; ++ const uint32_t uHeight = (*uIconPixels)[1]; ++ ++ struct rgba_t ++ { ++ uint8_t r,g,b,a; ++ }; ++ ++ for ( uint32_t& val : *uIconPixels ) ++ { ++ rgba_t rgb = *((rgba_t*)&val); ++ std::swap(rgb.r, rgb.b); ++ val = *((uint32_t*)&rgb); ++ } ++ ++ vr::VROverlay()->SetOverlayRaw( m_hOverlayThumbnail, &(*uIconPixels)[2], uWidth, uHeight, sizeof(uint32_t) ); ++ } ++ else if ( m_pchOverlayName ) ++ { ++ vr::VROverlay()->SetOverlayFromFile( m_hOverlayThumbnail, m_pchOverlayIcon ); ++ } ++ else ++ { ++ vr::VROverlay()->ClearOverlayTexture( m_hOverlayThumbnail ); ++ } ++ } ++ virtual std::optional GetHostCursor() override ++ { ++ return std::nullopt; ++ } ++ ++ protected: ++ ++ virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override ++ { ++ } ++ ++ private: ++ ++ void UpdateTouchMode() ++ { ++ const bool bHideLaserIntersection = g_nTouchClickMode != WLSERVER_TOUCH_CLICK_PASSTHROUGH; ++ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_HideLaserIntersection, bHideLaserIntersection ); ++ } ++ ++ void WaitUntilVisible() ++ { ++ m_bOverlayVisible.wait( false ); ++ } ++ ++ void VRInputThread() ++ { ++ pthread_setname_np( pthread_self(), "gamescope-vrinp" ); ++ ++ // Josh: PollNextOverlayEvent sucks. ++ // I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep. ++ while (true) ++ { ++ vr::VREvent_t vrEvent; ++ while( vr::VROverlay()->PollNextOverlayEvent( m_hOverlay, &vrEvent, sizeof( vrEvent ) ) ) ++ { ++ uint32_t timestamp = vrEvent.eventAgeSeconds * 1'000'000; ++ ++ switch( vrEvent.eventType ) ++ { ++ case vr::VREvent_OverlayClosed: ++ case vr::VREvent_Quit: ++ raise( SIGTERM ); ++ break; ++ ++ case vr::VREvent_KeyboardCharInput: ++ { ++ if (m_pIME) ++ { ++ type_text(m_pIME, vrEvent.data.keyboard.cNewInput); ++ } ++ break; ++ } ++ ++ case vr::VREvent_MouseMove: ++ { ++ float x = vrEvent.data.mouse.x; ++ float y = g_nOutputHeight - vrEvent.data.mouse.y; ++ ++ x /= (float)g_nOutputWidth; ++ y /= (float)g_nOutputHeight; ++ ++ wlserver_lock(); ++ wlserver_touchmotion( x, y, 0, timestamp ); ++ wlserver_unlock(); ++ break; ++ } ++ case vr::VREvent_MouseButtonUp: ++ case vr::VREvent_MouseButtonDown: ++ { ++ float x = vrEvent.data.mouse.x; ++ float y = g_nOutputHeight - vrEvent.data.mouse.y; ++ ++ x /= (float)g_nOutputWidth; ++ y /= (float)g_nOutputHeight; ++ ++ wlserver_lock(); ++ if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) ++ wlserver_touchdown( x, y, 0, timestamp ); ++ else ++ wlserver_touchup( 0, timestamp ); ++ wlserver_unlock(); ++ break; ++ } ++ ++ case vr::VREvent_ScrollSmooth: ++ { ++ wlserver_lock(); ++ ++ m_flScrollAccum[0] += -vrEvent.data.scroll.xdelta * m_flScrollSpeed; ++ m_flScrollAccum[1] += -vrEvent.data.scroll.ydelta * m_flScrollSpeed; ++ ++ float dx, dy; ++ m_flScrollAccum[0] = modf( m_flScrollAccum[0], &dx ); ++ m_flScrollAccum[1] = modf( m_flScrollAccum[1], &dy ); ++ ++ wlserver_mousewheel( dx, dy, timestamp ); ++ wlserver_unlock(); ++ break; ++ } ++ ++ case vr::VREvent_ButtonPress: ++ { ++ vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button; ++ ++ if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM) ++ break; ++ ++ if (button == vr::k_EButton_Steam) ++ openvr_log.infof("STEAM button pressed."); ++ else ++ openvr_log.infof("QAM button pressed."); ++ ++ wlserver_open_steam_menu( button == vr::k_EButton_QAM ); ++ break; ++ } ++ ++ case vr::VREvent_OverlayShown: ++ case vr::VREvent_OverlayHidden: ++ { ++ m_bOverlayVisible = vrEvent.eventType == vr::VREvent_OverlayShown; ++ m_bOverlayVisible.notify_all(); ++ break; ++ } ++ ++ default: ++ break; ++ } ++ } ++ sleep_for_nanos( 2'000'000ul ); ++ } ++ } ++ ++ CVROverlayConnector m_Connector; ++ const char *m_pchOverlayKey = nullptr; ++ 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_bModal = false; ++ float m_flPhysicalWidth = 2.0f; ++ float m_flPhysicalCurvature = 0.0f; ++ float m_flPhysicalPreCurvePitch = 0.0f; ++ float m_flScrollSpeed = 8.0f; ++ float m_flScrollAccum[2] = { 0.0f, 0.0f }; ++ vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid; ++ vr::VROverlayHandle_t m_hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; ++ wlserver_input_method *m_pIME = nullptr; ++ std::atomic m_bOverlayVisible = { false }; ++ }; ++ ++ ///////////////////////// ++ // Backend Instantiator ++ ///////////////////////// ++ ++ template <> ++ bool IBackend::Set() ++ { ++ return Set( new COpenVRBackend{} ); ++ } ++} +diff --git a/src/vr_session.hpp b/src/vr_session.hpp +deleted file mode 100644 +index f8007ae6d..000000000 +--- a/src/vr_session.hpp ++++ /dev/null +@@ -1,31 +0,0 @@ +-#pragma once +- +-#include +-#include +-#define VK_NO_PROTOTYPES +-#include +- +-#pragma GCC diagnostic push +-#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +-#include +-#pragma GCC diagnostic pop +- +-bool vr_init(int argc, char **argv); +- +-bool vrsession_init(); +-bool vrsession_visible(); +-void vrsession_wait_until_visible(); +-void vrsession_present( vr::VRVulkanTextureData_t *pTextureData ); +- +-void vrsession_append_instance_exts( std::vector& exts ); +-void vrsession_append_device_exts( VkPhysicalDevice physDev, std::vector& exts ); +- +-bool vrsession_framesync( uint32_t timeoutMS ); +-void vrsession_update_touch_mode(); +- +-void vrsession_title( const char *title, std::shared_ptr> icon ); +-bool vrsession_ime_init(); +- +-void vrsession_steam_mode( bool bSteamMode ); +- +-void vrsession_set_dashboard_visible( bool bVisible ); +diff --git a/src/waitable.h b/src/waitable.h +index cd806f698..fa7022fbe 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -7,6 +7,7 @@ + #include + + #include ++#include + + #include "log.hpp" + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index cc5e68be0..3a3a8a05e 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -40,13 +40,12 @@ + #include "presentation-time-protocol.h" + + #include "wlserver.hpp" +-#include "drm.hpp" ++#include "drm_include.h" + #include "main.hpp" + #include "steamcompmgr.hpp" + #include "log.hpp" + #include "ime.hpp" + #include "xwayland_ctx.hpp" +-#include "sdlwindow.hpp" + + #if HAVE_PIPEWIRE + #include "pipewire.hpp" +@@ -766,8 +765,7 @@ static void gamescope_swapchain_set_hdr_metadata( struct wl_client *client, stru + infoframe.max_cll = max_cll; + infoframe.max_fall = max_fall; + +- wl_info->swapchain_feedback->hdr_metadata_blob = +- drm_create_hdr_metadata_blob( &g_DRM, &metadata ); ++ wl_info->swapchain_feedback->hdr_metadata_blob = GetBackend()->CreateBackendBlob( metadata ); + } + } + +@@ -889,6 +887,46 @@ static const struct gamescope_control_interface gamescope_control_impl = { + .set_app_target_refresh_cycle = gamescope_control_set_app_target_refresh_cycle, + }; + ++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() ) ++ { ++ size_t size = pConn->GetValidDynamicRefreshRates().size() * sizeof(uint32_t); ++ uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); ++ memcpy( ptr, pConn->GetValidDynamicRefreshRates().data(), size ); ++ } ++ 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 ); +@@ -904,10 +942,7 @@ static void gamescope_control_bind( struct wl_client *client, void *data, uint32 + gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_PIXEL_FILTER, 1, 0 ); + gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DONE, 0, 0 ); + +- if ( !BIsNested() ) +- { +- drm_send_gamescope_control( resource, &g_DRM ); +- } ++ wlserver_send_gamescope_control( resource ); + + wlserver.gamescope_controls.push_back(resource); + } +@@ -1249,14 +1284,16 @@ void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle + + /////////////////////// + ++bool wlsession_active() ++{ ++ return wlserver.wlr.session->active; ++} ++ + static void handle_session_active( struct wl_listener *listener, void *data ) + { +- if (wlserver.wlr.session->active) { +- g_DRM.out_of_date = 1; +- g_DRM.needs_modeset = 1; +- } +- g_DRM.paused = !wlserver.wlr.session->active; +- wl_log.infof( "Session %s", g_DRM.paused ? "paused" : "resumed" ); ++ if (wlserver.wlr.session->active) ++ GetBackend()->DirtyState( true, true ); ++ wl_log.infof( "Session %s", wlserver.wlr.session->active ? "resumed" : "paused" ); + } + + static void handle_wlr_log(enum wlr_log_importance importance, const char *fmt, va_list args) +@@ -1336,7 +1373,7 @@ bool wlsession_init( void ) { + }; + wlserver_set_output_info( &output_info ); + +- if ( BIsNested() ) ++ if ( !GetBackend()->IsSessionBased() ) + return true; + + wlserver.wlr.session = wlr_session_create( wlserver.display ); +@@ -1354,7 +1391,7 @@ bool wlsession_init( void ) { + + static void kms_device_handle_change( struct wl_listener *listener, void *data ) + { +- g_DRM.out_of_date = 1; ++ GetBackend()->DirtyState(); + wl_log.infof( "Got change event for KMS device" ); + + nudge_steamcompmgr(); +@@ -1570,8 +1607,6 @@ void xdg_surface_new(struct wl_listener *listener, void *data) + bool wlserver_init( void ) { + assert( wlserver.display != nullptr ); + +- bool bIsDRM = !BIsNested(); +- + wl_list_init(&pending_surfaces); + + wlserver.event_loop = wl_display_get_event_loop(wlserver.display); +@@ -1583,7 +1618,7 @@ bool wlserver_init( void ) { + + wl_signal_add( &wlserver.wlr.multi_backend->events.new_input, &new_input_listener ); + +- if ( bIsDRM == True ) ++ if ( GetBackend()->IsSessionBased() ) + { + wlserver.wlr.libinput_backend = wlr_libinput_backend_create( wlserver.display, wlserver.wlr.session ); + if ( wlserver.wlr.libinput_backend == NULL) +@@ -1945,22 +1980,23 @@ static void apply_touchscreen_orientation(double *x, double *y ) + double ty = 0; + + // Use internal screen always for orientation purposes. +- switch ( g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) ++ switch ( GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL )->GetCurrentOrientation() ) + { + default: +- case DRM_MODE_ROTATE_0: ++ case GAMESCOPE_PANEL_ORIENTATION_AUTO: ++ case GAMESCOPE_PANEL_ORIENTATION_0: + tx = *x; + ty = *y; + break; +- case DRM_MODE_ROTATE_90: ++ case GAMESCOPE_PANEL_ORIENTATION_90: + tx = 1.0 - *y; + ty = *x; + break; +- case DRM_MODE_ROTATE_180: ++ case GAMESCOPE_PANEL_ORIENTATION_180: + tx = 1.0 - *x; + ty = 1.0 - *y; + break; +- case DRM_MODE_ROTATE_270: ++ case GAMESCOPE_PANEL_ORIENTATION_270: + tx = *y; + ty = 1.0 - *x; + break; +@@ -1974,12 +2010,12 @@ bool g_bTrackpadTouchExternalDisplay = false; + + int get_effective_touch_mode() + { +- if (!BIsNested() && g_bTrackpadTouchExternalDisplay) +- { +- gamescope::GamescopeScreenType screenType = drm_get_screen_type(&g_DRM); +- if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) +- return WLSERVER_TOUCH_CLICK_TRACKPAD; +- } ++ if ( !GetBackend() || !GetBackend()->GetCurrentConnector() ) ++ return g_nTouchClickMode; ++ ++ gamescope::GamescopeScreenType screenType = GetBackend()->GetCurrentConnector()->GetScreenType(); ++ if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) ++ return WLSERVER_TOUCH_CLICK_TRACKPAD; + + return g_nTouchClickMode; + } +diff --git a/src/wlserver.hpp b/src/wlserver.hpp +index 852a5a15d..c884a3ab9 100644 +--- a/src/wlserver.hpp ++++ b/src/wlserver.hpp +@@ -13,7 +13,6 @@ + #include + #include + +-#include "drm.hpp" + #include "steamcompmgr_shared.hpp" + + #define WLSERVER_BUTTON_COUNT 7 +@@ -31,7 +30,7 @@ struct wlserver_vk_swapchain_feedback + VkPresentModeKHR vk_present_mode; + VkBool32 vk_clipped; + +- std::shared_ptr hdr_metadata_blob; ++ std::shared_ptr hdr_metadata_blob; + }; + + struct ResListEntry_t { +@@ -270,3 +269,8 @@ void wlserver_past_present_timing( struct wlr_surface *surface, uint32_t present + void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle ); + + void wlserver_force_shutdown(); ++ ++void wlserver_send_gamescope_control( wl_resource *control ); ++ ++bool wlsession_active(); ++ +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 229a0884a..d77a1844b 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -1,6 +1,6 @@ + #pragma once + +-#include "drm.hpp" ++#include "backend.h" + #include "waitable.h" + + #include +@@ -192,7 +192,6 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + Atom gamescopeDebugHDRHeatmap_MSWCG; + Atom gamescopeHDROutputFeedback; + Atom gamescopeSDROnHDRContentBrightness; +- Atom gamescopeInternalDisplayBrightness; + Atom gamescopeHDRInputGain; + Atom gamescopeSDRInputGain; + Atom gamescopeHDRItmEnable; + +From 1c31291a5c0325df1ade35110f1b0f0387c0e291 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 10:02:37 +0000 +Subject: [PATCH 092/134] rendervulkan: Remove SDL includes + +No longer needed +--- + src/rendervulkan.cpp | 3 --- + 1 file changed, 3 deletions(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 51a5d5c7f..8afe07788 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -45,9 +45,6 @@ + + #include "reshade_effect_manager.hpp" + +-#include "SDL.h" +-#include "SDL_vulkan.h" +- + extern bool g_bWasPartialComposite; + + static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601_limited = {{ + +From 5f6fab9f46e705635ad8d94cff99a9ad470b9c0f Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 10:14:20 +0000 +Subject: [PATCH 093/134] wlserver: Remove dependency on drm_include.h + +--- + src/drm.cpp | 7 +++++++ + src/wlserver.cpp | 24 +++++++++++------------- + src/wlserver.hpp | 3 +++ + 3 files changed, 21 insertions(+), 13 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 88b93b685..135a7efaa 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -1103,6 +1103,13 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh) + 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; +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 3a3a8a05e..d15dd7c19 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -40,7 +40,7 @@ + #include "presentation-time-protocol.h" + + #include "wlserver.hpp" +-#include "drm_include.h" ++#include "hdmi.h" + #include "main.hpp" + #include "steamcompmgr.hpp" + #include "log.hpp" +@@ -1398,22 +1398,15 @@ static void kms_device_handle_change( struct wl_listener *listener, void *data ) + } + + int wlsession_open_kms( const char *device_name ) { +- struct wlr_device *device = nullptr; + if ( device_name != nullptr ) + { +- device = wlr_session_open_file( wlserver.wlr.session, device_name ); +- if ( device == nullptr ) ++ wlserver.wlr.device = wlr_session_open_file( wlserver.wlr.session, device_name ); ++ if ( wlserver.wlr.device == nullptr ) + return -1; +- if ( !drmIsKMS( device->fd ) ) +- { +- wl_log.errorf( "'%s' is not a KMS device", device_name ); +- wlr_session_close_file( wlserver.wlr.session, device ); +- return -1; +- } + } + else + { +- ssize_t n = wlr_session_find_gpus( wlserver.wlr.session, 1, &device ); ++ ssize_t n = wlr_session_find_gpus( wlserver.wlr.session, 1, &wlserver.wlr.device ); + if ( n < 0 ) + { + wl_log.errorf( "Failed to list GPUs" ); +@@ -1428,9 +1421,14 @@ int wlsession_open_kms( const char *device_name ) { + + struct wl_listener *listener = new wl_listener(); + listener->notify = kms_device_handle_change; +- wl_signal_add( &device->events.change, listener ); ++ wl_signal_add( &wlserver.wlr.device->events.change, listener ); + +- return device->fd; ++ return wlserver.wlr.device->fd; ++} ++ ++void wlsession_close_kms() ++{ ++ wlr_session_close_file( wlserver.wlr.session, wlserver.wlr.device ); + } + + gamescope_xwayland_server_t::gamescope_xwayland_server_t(wl_display *display) +diff --git a/src/wlserver.hpp b/src/wlserver.hpp +index c884a3ab9..6c41843ee 100644 +--- a/src/wlserver.hpp ++++ b/src/wlserver.hpp +@@ -110,6 +110,8 @@ struct wlserver_t { + // Used to simulate key events and set the keymap + struct wlr_keyboard *virtual_keyboard_device; + ++ struct wlr_device *device; ++ + std::vector> xwayland_servers; + } wlr; + +@@ -185,6 +187,7 @@ 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(); + + bool wlserver_init( void ); + + +From 5e6177f34c4a05f8a8047265bbc1cd678fd71cdf Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 11:52:14 +0000 +Subject: [PATCH 094/134] build: Make hwdata optional + +--- + meson.build | 12 ++++++++++-- + src/drm.cpp | 2 ++ + 2 files changed, 12 insertions(+), 2 deletions(-) + +diff --git a/meson.build b/meson.build +index 7af6884a0..04bd47208 100644 +--- a/meson.build ++++ b/meson.build +@@ -38,7 +38,7 @@ add_project_arguments(cppc.get_supported_arguments([ + + pipewire_dep = dependency('libpipewire-0.3', required: get_option('pipewire')) + librt_dep = cppc.find_library('rt', required : get_option('pipewire')) +-hwdata_dep = dependency('hwdata') ++hwdata_dep = dependency('hwdata', required : false) + + dep_x11 = dependency('x11') + dep_wayland = dependency('wayland-client') +@@ -62,10 +62,18 @@ endif + add_project_arguments( + '-DHAVE_PIPEWIRE=@0@'.format(pipewire_dep.found().to_int()), + '-DHAVE_OPENVR=@0@'.format(openvr_dep.found().to_int()), +- '-DHWDATA_PNP_IDS="@0@"'.format(hwdata_dep.get_variable('pkgdatadir') / 'pnp.ids'), + language: 'cpp', + ) + ++if hwdata_dep.found() ++ 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.') ++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) +diff --git a/src/drm.cpp b/src/drm.cpp +index 135a7efaa..98883e426 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -1031,6 +1031,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + + void load_pnps(void) + { ++#ifdef HWDATA_PNP_IDS + const char *filename = HWDATA_PNP_IDS; + FILE *f = fopen(filename, "r"); + if (!f) { +@@ -1059,6 +1060,7 @@ void load_pnps(void) + + free(line); + fclose(f); ++#endif + } + + bool env_to_bool(const char *env) + +From 67984a89c5092c4164baef3c1d27fb5d5f1e9fb3 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 11:58:20 +0000 +Subject: [PATCH 095/134] build: Make drm backend optional + +--- + meson_options.txt | 1 + + src/main.cpp | 2 ++ + src/meson.build | 11 +++++++++-- + 3 files changed, 12 insertions(+), 2 deletions(-) + +diff --git a/meson_options.txt b/meson_options.txt +index 1d403fec4..c2c90b612 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -1,4 +1,5 @@ + option('pipewire', type: 'feature', description: 'Screen capture via PipeWire') ++option('drm_backend', type: 'feature', description: 'DRM Atomic Backend') + 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') +diff --git a/src/main.cpp b/src/main.cpp +index 42110675d..a2665d112 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -778,9 +778,11 @@ int main(int argc, char **argv) + + switch ( eCurrentBackend ) + { ++#if HAVE_DRM + case gamescope::GamescopeBackend::DRM: + gamescope::IBackend::Set(); + break; ++#endif + case gamescope::GamescopeBackend::SDL: + gamescope::IBackend::Set(); + break; +diff --git a/src/meson.build b/src/meson.build +index a9e64990c..d23176781 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -10,7 +10,7 @@ dep_xres = dependency('xres') + dep_xmu = dependency('xmu') + dep_xi = dependency('xi') + +-drm_dep = dependency('libdrm', version: '>= 2.4.113') ++drm_dep = dependency('libdrm', version: '>= 2.4.113', required: get_option('drm_backend')) + + wayland_server = dependency('wayland-server', version: '>=1.21') + wayland_protos = dependency('wayland-protocols', version: '>=1.17') +@@ -101,6 +101,13 @@ reshade_include = include_directories([ + '../thirdparty/SPIRV-Headers/include/spirv/unified1' + ]) + ++gamescope_cpp_args = [] ++if drm_dep.found() ++ src += 'drm.cpp' ++ gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) ++endif ++ ++ + src = [ + 'steamcompmgr.cpp', + 'color_helpers.cpp', +@@ -108,7 +115,6 @@ src = [ + 'edid.cpp', + 'headless.cpp', + 'wlserver.cpp', +- 'drm.cpp', + 'modegen.cpp', + 'sdlwindow.cpp', + 'vblankmanager.cpp', +@@ -143,6 +149,7 @@ endif + stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi + ], + install: true, ++ cpp_args: gamescope_cpp_args, + ) + + + +From e2828babbed0b58e462f06713687565d8b881170 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 12:05:04 +0000 +Subject: [PATCH 096/134] build: Make libavif and SDL2 optional + +--- + meson_options.txt | 2 ++ + src/main.cpp | 2 ++ + src/meson.build | 13 ++++++++++--- + src/steamcompmgr.cpp | 5 +++++ + 4 files changed, 19 insertions(+), 3 deletions(-) + +diff --git a/meson_options.txt b/meson_options.txt +index c2c90b612..1e7754e8e 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -1,5 +1,7 @@ + option('pipewire', type: 'feature', description: 'Screen capture via PipeWire') + 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('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') +diff --git a/src/main.cpp b/src/main.cpp +index a2665d112..a37899df9 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -783,9 +783,11 @@ int main(int argc, char **argv) + gamescope::IBackend::Set(); + break; + #endif ++#if HAVE_SDL2 + case gamescope::GamescopeBackend::SDL: + gamescope::IBackend::Set(); + break; ++#endif + #if HAVE_OPENVR + case gamescope::GamescopeBackend::OpenVR: + gamescope::IBackend::Set(); +diff --git a/src/meson.build b/src/meson.build +index d23176781..4b2a5bcb1 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -19,9 +19,9 @@ thread_dep = dependency('threads') + cap_dep = dependency('libcap', required: false) + epoll_dep = dependency('epoll-shim', required: false) + glm_dep = dependency('glm') +-sdl_dep = dependency('SDL2') ++sdl_dep = dependency('SDL2', required: get_option('sdl2_backend')) + stb_dep = dependency('stb') +-avif_dep = dependency('libavif', version: '>=1.0.0') ++avif_dep = dependency('libavif', version: '>=1.0.0', required: get_option('avif_screenshots')) + + wlroots_dep = dependency( + 'wlroots', +@@ -107,6 +107,14 @@ if drm_dep.found() + gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) + endif + ++if sdl_dep.found() ++ src += 'sdlwindow.cpp' ++ gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) ++endif ++ ++if avif_dep.found() ++ gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) ++endif + + src = [ + 'steamcompmgr.cpp', +@@ -116,7 +124,6 @@ src = [ + 'headless.cpp', + 'wlserver.cpp', + 'modegen.cpp', +- 'sdlwindow.cpp', + 'vblankmanager.cpp', + 'rendervulkan.cpp', + 'log.cpp', +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index e3dae6154..a01e91c60 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -89,7 +89,9 @@ + #include "mwm_hints.h" + #include "edid.h" + ++#if HAVE_AVIF + #include "avif/avif.h" ++#endif + + static const int g_nBaseCursorScale = 36; + +@@ -2908,6 +2910,8 @@ paint_all(bool async) + } + } + ++ assert( HAVE_AVIF ); ++#if HAVE_AVIF + avifResult avifResult = AVIF_RESULT_OK; + + avifImage *pAvifImage = avifImageCreate( g_nOutputWidth, g_nOutputHeight, 10, AVIF_PIXEL_FORMAT_YUV444 ); +@@ -2976,6 +2980,7 @@ paint_all(bool async) + + xwm_log.infof( "Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str() ); + bScreenshotSuccess = true; ++#endif + } + else if (pScreenshotTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) + { + +From fd1e9c6b958cb4497557ae644d3cb06ca0c07142 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 12:21:20 +0000 +Subject: [PATCH 097/134] build: Fix build + +Derp. +--- + src/meson.build | 30 +++++++++++++++--------------- + 1 file changed, 15 insertions(+), 15 deletions(-) + +diff --git a/src/meson.build b/src/meson.build +index 4b2a5bcb1..1df9c47fc 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -101,21 +101,6 @@ reshade_include = include_directories([ + '../thirdparty/SPIRV-Headers/include/spirv/unified1' + ]) + +-gamescope_cpp_args = [] +-if drm_dep.found() +- src += 'drm.cpp' +- gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) +-endif +- +-if sdl_dep.found() +- src += 'sdlwindow.cpp' +- gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) +-endif +- +-if avif_dep.found() +- gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) +-endif +- + src = [ + 'steamcompmgr.cpp', + 'color_helpers.cpp', +@@ -133,6 +118,21 @@ src = [ + 'backend.cpp', + ] + ++gamescope_cpp_args = [] ++if drm_dep.found() ++ src += 'drm.cpp' ++ gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) ++endif ++ ++if sdl_dep.found() ++ src += 'sdlwindow.cpp' ++ gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) ++endif ++ ++if avif_dep.found() ++ gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) ++endif ++ + src += spirv_shaders + src += protocols_server_src + + +From f53f825e7e7322cf7e363429f5fc4661b345be97 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:00:04 +0000 +Subject: [PATCH 098/134] build: Don't require libinput backend if building + without DRM + +--- + src/meson.build | 16 +++++++++------- + 1 file changed, 9 insertions(+), 7 deletions(-) + +diff --git a/src/meson.build b/src/meson.build +index 1df9c47fc..6762640f2 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -37,13 +37,6 @@ displayinfo_dep = dependency( + default_options: ['default_library=static'], + ) + +-required_wlroots_features = ['xwayland', 'libinput_backend'] +-foreach feat : required_wlroots_features +- if wlroots_dep.get_variable('have_' + feat) != 'true' +- error('Cannot use wlroots built without ' + feat + ' support') +- endif +-endforeach +- + glsl_compiler = find_program('glslang', 'glslangValidator', native: true) + + # Use --depfile to rebuild shaders when included files have changed. Sadly debian based +@@ -101,6 +94,8 @@ reshade_include = include_directories([ + '../thirdparty/SPIRV-Headers/include/spirv/unified1' + ]) + ++required_wlroots_features = ['xwayland'] ++ + src = [ + 'steamcompmgr.cpp', + 'color_helpers.cpp', +@@ -121,6 +116,7 @@ src = [ + gamescope_cpp_args = [] + if drm_dep.found() + src += 'drm.cpp' ++ required_wlroots_features += 'libinput_backend' + gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) + endif + +@@ -144,6 +140,12 @@ if openvr_dep.found() + src += 'vr_session.cpp' + endif + ++foreach feat : required_wlroots_features ++ if wlroots_dep.get_variable('have_' + feat) != 'true' ++ error('Cannot use wlroots built without ' + feat + ' support') ++ endif ++endforeach ++ + executable( + 'gamescope', + src, reshade_src, + +From da4f94c23cf005f309803a0a07e3bbe597ab94dc Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:00:39 +0000 +Subject: [PATCH 099/134] build: Don't require libliftoff if building without + DRM + +--- + src/meson.build | 15 ++++++++------- + 1 file changed, 8 insertions(+), 7 deletions(-) + +diff --git a/src/meson.build b/src/meson.build +index 6762640f2..0e47df375 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -71,13 +71,6 @@ shader_src = [ + + spirv_shaders = glsl_generator.process(shader_src) + +-liftoff_dep = dependency( +- 'libliftoff', +- version: ['>= 0.4.0', '< 0.5.0'], +- fallback: ['libliftoff', 'liftoff'], +- default_options: ['default_library=static'], +-) +- + reshade_src = [ + 'reshade/source/effect_codegen_spirv.cpp', + 'reshade/source/effect_expression.cpp', +@@ -118,6 +111,14 @@ if drm_dep.found() + src += 'drm.cpp' + required_wlroots_features += 'libinput_backend' + gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) ++ liftoff_dep = dependency( ++ 'libliftoff', ++ version: ['>= 0.4.0', '< 0.5.0'], ++ fallback: ['libliftoff', 'liftoff'], ++ default_options: ['default_library=static'], ++ ) ++else ++ liftoff_dep = dependency('', required: false) + endif + + if sdl_dep.found() + +From 9f9c666aadf12ea4e6174f5eadcffcff04161c18 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 17:55:02 +0000 +Subject: [PATCH 100/134] waitable: Add unistd.h include + +Gets closer building on some platforms +--- + src/waitable.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/waitable.h b/src/waitable.h +index fa7022fbe..f956a2c98 100644 +--- a/src/waitable.h ++++ b/src/waitable.h +@@ -3,6 +3,7 @@ + #include + #include + #include ++#include + #include + #include + + +From 350b0c3eb5798050d00d6ea95c37f982cfbcaba4 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 17:55:40 +0000 +Subject: [PATCH 101/134] wlserver: Get libinput stuff behind HAVE_DRM + +--- + src/wlserver.cpp | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index d15dd7c19..27ce45a70 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -17,7 +17,9 @@ + #include "wlr_begin.hpp" + #include + #include ++#if HAVE_DRM + #include ++#endif + #include + #include + #include +@@ -1618,12 +1620,14 @@ bool wlserver_init( void ) { + + if ( GetBackend()->IsSessionBased() ) + { ++#if HAVE_DRM + wlserver.wlr.libinput_backend = wlr_libinput_backend_create( wlserver.display, 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 + +From 2096b6882c1b2551a59114e07d5dc57e6e4f3a7c Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:01:12 +0000 +Subject: [PATCH 102/134] steamcompmgr: Remove drm_mode.h include + +--- + src/steamcompmgr.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index a01e91c60..304d0d91b 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -36,7 +36,6 @@ + #include + #include + #include +-#include + #include + #include + #include + +From bb3e4d0c9bfc141aff8fc4b82e25467c5bf3bf4e Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:01:31 +0000 +Subject: [PATCH 103/134] build: Only build modegen.cpp with DRM backend + enabled + +--- + src/meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/meson.build b/src/meson.build +index 0e47df375..9f067640a 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -96,7 +96,6 @@ src = [ + 'edid.cpp', + 'headless.cpp', + 'wlserver.cpp', +- 'modegen.cpp', + 'vblankmanager.cpp', + 'rendervulkan.cpp', + 'log.cpp', +@@ -109,6 +108,7 @@ src = [ + gamescope_cpp_args = [] + if drm_dep.found() + src += 'drm.cpp' ++ src += 'modegen.cpp' + required_wlroots_features += 'libinput_backend' + gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) + liftoff_dep = dependency( + +From fbb8dd20e0c7de06eac3a463375928b175b4b354 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:02:36 +0000 +Subject: [PATCH 104/134] rendervulkan: Only include drm_fourcc.h, not all of + drm_include.h + +--- + src/rendervulkan.cpp | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 8afe07788..175e9abc3 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -21,7 +21,13 @@ + // 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 ++#if HAVE_DRM + #include "drm_include.h" ++#endif ++#include "wlr_begin.hpp" ++#include ++#include "wlr_end.hpp" + + #include "rendervulkan.hpp" + #include "main.hpp" +@@ -416,9 +422,11 @@ bool CVulkanDevice::createDevice() + if ( !GetBackend()->ValidPhysicalDevice( physDev() ) ) + return false; + ++#if HAVE_DRM + // XXX(JoshA): Move this to ValidPhysicalDevice. + // We need to refactor some Vulkan stuff to do that though. +- if ( hasDrmProps ) { ++ if ( hasDrmProps ) ++ { + VkPhysicalDeviceDrmPropertiesEXT drmProps = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, + }; +@@ -457,7 +465,10 @@ bool CVulkanDevice::createDevice() + m_bHasDrmPrimaryDevId = true; + m_drmPrimaryDevId = makedev( drmProps.primaryMajor, drmProps.primaryMinor ); + } +- } else { ++ } ++ else ++#endif ++ { + vk_log.errorf( "physical device doesn't support VK_EXT_physical_device_drm" ); + return false; + } +@@ -2640,9 +2651,11 @@ bool vulkan_init_formats() + 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); ++#endif + } + + return true; + +From ab16b8ce6b2fd3b2323c8db8ba35218c4ea8b56f Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:23:22 +0000 +Subject: [PATCH 105/134] build: Add HAVE_LIBCAP definition + +--- + meson_options.txt | 1 + + src/meson.build | 6 +++++- + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/meson_options.txt b/meson_options.txt +index 1e7754e8e..3160b58c5 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -1,4 +1,5 @@ + 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') +diff --git a/src/meson.build b/src/meson.build +index 9f067640a..2afcd3aef 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -16,7 +16,7 @@ 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: false) ++cap_dep = dependency('libcap', required: get_option('rt_cap')) + epoll_dep = dependency('epoll-shim', required: false) + glm_dep = dependency('glm') + sdl_dep = dependency('SDL2', required: get_option('sdl2_backend')) +@@ -130,6 +130,10 @@ if avif_dep.found() + gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) + endif + ++if cap_dep.found() ++ gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(avif_dep.found().to_int()) ++endif ++ + src += spirv_shaders + src += protocols_server_src + + +From 8b16bdbcc630a4ad577e4bb4d707e08fec57bcfc Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:23:51 +0000 +Subject: [PATCH 106/134] build: Simplify HAVE_* definition setting + +--- + src/meson.build | 13 ++++--------- + 1 file changed, 4 insertions(+), 9 deletions(-) + +diff --git a/src/meson.build b/src/meson.build +index 2afcd3aef..559054ddb 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -110,7 +110,6 @@ if drm_dep.found() + src += 'drm.cpp' + src += 'modegen.cpp' + required_wlroots_features += 'libinput_backend' +- gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) + liftoff_dep = dependency( + 'libliftoff', + version: ['>= 0.4.0', '< 0.5.0'], +@@ -123,16 +122,12 @@ endif + + if sdl_dep.found() + src += 'sdlwindow.cpp' +- gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) + endif + +-if avif_dep.found() +- gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) +-endif +- +-if cap_dep.found() +- gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(avif_dep.found().to_int()) +-endif ++gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) ++gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) ++gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) ++gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(avif_dep.found().to_int()) + + src += spirv_shaders + src += protocols_server_src + +From 085f95fa30af544aebc2312f88c04fb4912fee32 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:24:17 +0000 +Subject: [PATCH 107/134] main: Check for libcap in the cap code + +--- + src/main.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/main.cpp b/src/main.cpp +index a37899df9..bbc3649de 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -676,7 +676,7 @@ int main(int argc, char **argv) + } + } + +-#if defined(__linux__) ++#if defined(__linux__) && HAVE_LIBCAP + cap_t caps = cap_get_proc(); + if ( caps != nullptr ) + { + +From 4ea2f847f06560f3bb947ef3d4975247c356dd41 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:26:23 +0000 +Subject: [PATCH 108/134] wlserver: Ifdef some session stuff behind HAVE_DRM + +--- + src/wlserver.cpp | 10 ++++++++++ + src/wlserver.hpp | 4 ++++ + 2 files changed, 14 insertions(+) + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 27ce45a70..b1a6ff41d 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -229,11 +229,13 @@ static void wlserver_handle_key(struct wl_listener *listener, void *data) + xkb_keycode_t keycode = event->keycode + 8; + xkb_keysym_t keysym = xkb_state_key_get_one_sym(keyboard->wlr->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; + } ++#endif + + bool forbidden_key = + keysym == XKB_KEY_XF86AudioLowerVolume || +@@ -1286,6 +1288,7 @@ void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle + + /////////////////////// + ++#if HAVE_SESSION + bool wlsession_active() + { + return wlserver.wlr.session->active; +@@ -1297,6 +1300,7 @@ static void handle_session_active( struct wl_listener *listener, void *data ) + GetBackend()->DirtyState( true, true ); + 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) + { +@@ -1375,6 +1379,7 @@ bool wlsession_init( void ) { + }; + wlserver_set_output_info( &output_info ); + ++#if HAVE_SESSION + if ( !GetBackend()->IsSessionBased() ) + return true; + +@@ -1387,10 +1392,13 @@ bool wlsession_init( void ) { + + wlserver.session_active.notify = handle_session_active; + wl_signal_add( &wlserver.wlr.session->events.active, &wlserver.session_active ); ++#endif + + return true; + } + ++#if HAVE_SESSION ++ + static void kms_device_handle_change( struct wl_listener *listener, void *data ) + { + GetBackend()->DirtyState(); +@@ -1433,6 +1441,8 @@ 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) + { + struct wlr_xwayland_server_options xwayland_options = { +diff --git a/src/wlserver.hpp b/src/wlserver.hpp +index 6c41843ee..c148847fb 100644 +--- a/src/wlserver.hpp ++++ b/src/wlserver.hpp +@@ -15,6 +15,10 @@ + + #include "steamcompmgr_shared.hpp" + ++#if HAVE_DRM ++#define HAVE_SESSION 1 ++#endif ++ + #define WLSERVER_BUTTON_COUNT 7 + + struct _XDisplay; + +From ec6470e835f809b12fe4421c44443bbbfc042d0b Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:59:41 +0000 +Subject: [PATCH 109/134] rendervulkan: Add some hdr_metadata_infoframe stubs + for when we are not using DRM + +--- + src/hdmi.h | 93 ++++++++++++++++++++++++++++++++++++++++++++ + src/rendervulkan.cpp | 1 + + 2 files changed, 94 insertions(+) + +diff --git a/src/hdmi.h b/src/hdmi.h +index 57d5257e2..92d149519 100644 +--- a/src/hdmi.h ++++ b/src/hdmi.h +@@ -1,7 +1,100 @@ + #pragma once + ++#include ++ + /* from CTA-861-G */ + #define HDMI_EOTF_SDR 0 + #define HDMI_EOTF_TRADITIONAL_HDR 1 + #define HDMI_EOTF_ST2084 2 + #define HDMI_EOTF_HLG 3 ++ ++#if HAVE_DRM ++#include ++#else ++/** ++ * struct hdr_metadata_infoframe - HDR Metadata Infoframe Data. ++ * ++ * HDR Metadata Infoframe as per CTA 861.G spec. This is expected ++ * to match exactly with the spec. ++ * ++ * 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_output_metadata - HDR output metadata ++ * ++ * 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; ++ }; ++}; ++ ++#endif +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 175e9abc3..7cb3c1df3 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -22,6 +22,7 @@ + #include "shaders/NVIDIAImageScaling/NIS/NIS_Config.h" + + #include ++#include "hdmi.h" + #if HAVE_DRM + #include "drm_include.h" + #endif + +From 55e80c580f7ed17e9e3708162587ba62eb1c9862 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 18:59:52 +0000 +Subject: [PATCH 110/134] drm: Move env_to_bool out of here. + +--- + src/drm.cpp | 8 +------- + src/steamcompmgr.cpp | 9 +++++++++ + 2 files changed, 10 insertions(+), 7 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 98883e426..95a0853ee 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -1063,13 +1063,7 @@ void load_pnps(void) + #endif + } + +-bool env_to_bool(const char *env) +-{ +- if (!env || !*env) +- return false; +- +- return !!atoi(env); +-} ++extern bool env_to_bool(const char *env); + + bool init_drm(struct drm_t *drm, int width, int height, int refresh) + { +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 304d0d91b..370b45f7f 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -87,6 +87,7 @@ + #include "win32_styles.h" + #include "mwm_hints.h" + #include "edid.h" ++#include "hdmi.h" + + #if HAVE_AVIF + #include "avif/avif.h" +@@ -149,6 +150,14 @@ uint32_t g_reshade_technique_idx = 0; + bool g_bSteamIsActiveWindow = false; + bool g_bForceInternal = false; + ++bool env_to_bool(const char *env) ++{ ++ if (!env || !*env) ++ return false; ++ ++ return !!atoi(env); ++} ++ + uint64_t timespec_to_nanos(struct timespec& spec) + { + return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec; + +From 434074a2484d40308446be2ce2f4bdd8869dd4a5 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 19:02:00 +0000 +Subject: [PATCH 111/134] edid: Avoid using g_bRotated + +--- + src/drm.cpp | 6 +++--- + src/edid.cpp | 10 ++++------ + src/edid.h | 4 ++-- + 3 files changed, 9 insertions(+), 11 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 95a0853ee..5ff3cafcc 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -1022,7 +1022,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) + wlserver_unlock(); + + if (!initial) +- WritePatchedEdid( best->GetRawEDID(), best->GetHDRInfo() ); ++ WritePatchedEdid( best->GetRawEDID(), best->GetHDRInfo(), g_bRotated ); + + update_connector_display_info_wl( drm ); + +@@ -3005,7 +3005,7 @@ namespace gamescope + virtual bool PostInit() override + { + if ( g_DRM.pConnector ) +- WritePatchedEdid( g_DRM.pConnector->GetRawEDID(), g_DRM.pConnector->GetHDRInfo() ); ++ WritePatchedEdid( g_DRM.pConnector->GetRawEDID(), g_DRM.pConnector->GetHDRInfo(), g_bRotated ); + return true; + } + +@@ -3429,7 +3429,7 @@ namespace gamescope + if ( !GetCurrentConnector() ) + return; + +- WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo() ); ++ WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), g_bRotated ); + } + + protected: +diff --git a/src/edid.cpp b/src/edid.cpp +index 3f499fc98..eb855096a 100644 +--- a/src/edid.cpp ++++ b/src/edid.cpp +@@ -15,8 +15,6 @@ extern "C" + #include "libdisplay-info/cta.h" + } + +-extern bool g_bRotated; +- + static LogScope edid_log("josh edid"); + + namespace gamescope +@@ -89,7 +87,7 @@ namespace gamescope + return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); + } + +- std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) ++ std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ) + { + // A zero length indicates that the edid parsing failed. + if ( pEdid.empty() ) +@@ -97,7 +95,7 @@ namespace gamescope + + std::vector edid( pEdid.begin(), pEdid.end() ); + +- if ( g_bRotated ) ++ if ( bRotate ) + { + // Patch width, height. + edid_log.infof("Patching dims %ux%u -> %ux%u", edid[0x15], edid[0x16], edid[0x16], edid[0x15]); +@@ -263,7 +261,7 @@ namespace gamescope + return pszPatchedEdidPath; + } + +- void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) ++ void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ) + { + const char *pszPatchedEdidPath = GetPatchedEdidPath(); + if ( !pszPatchedEdidPath ) +@@ -271,7 +269,7 @@ namespace gamescope + + std::span pEdidToWrite = pEdid; + +- auto oPatchedEdid = PatchEdid( pEdid, hdrInfo ); ++ auto oPatchedEdid = PatchEdid( pEdid, hdrInfo, bRotate ); + if ( oPatchedEdid ) + pEdidToWrite = std::span{ oPatchedEdid->begin(), oPatchedEdid->end() }; + +diff --git a/src/edid.h b/src/edid.h +index e6ab74688..2f821548d 100644 +--- a/src/edid.h ++++ b/src/edid.h +@@ -10,7 +10,7 @@ namespace gamescope + struct BackendConnectorHDRInfo; + + const char *GetPatchedEdidPath(); +- void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); ++ void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ); + +- std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); ++ std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ); + } +\ No newline at end of file + +From 332f348a1f595d92528a86f9c8feaed45217b82d Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 14:03:54 +0000 +Subject: [PATCH 112/134] steamcompmgr: Fix GetNativeColorimetry when HDR is + enabled but not available + +--- + src/steamcompmgr.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 370b45f7f..cd5877e77 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -347,7 +347,7 @@ update_color_mgmt() + return; + + GetBackend()->GetCurrentConnector()->GetNativeColorimetry( +- g_bHDREnabled, ++ g_bOutputHDREnabled, + &g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF, + &g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF ); + + +From d70530cb94bbb1be957f04aad3bd400f53f1a90c Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 14:10:25 +0000 +Subject: [PATCH 113/134] convar: Add small convar system + +Co-authored-by: Alpyne +--- + src/convar.cpp | 10 ++++ + src/convar.h | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ + src/meson.build | 1 + + 3 files changed, 156 insertions(+) + create mode 100644 src/convar.cpp + create mode 100644 src/convar.h + +diff --git a/src/convar.cpp b/src/convar.cpp +new file mode 100644 +index 000000000..22a4b68ff +--- /dev/null ++++ b/src/convar.cpp +@@ -0,0 +1,10 @@ ++#include "convar.h" ++ ++namespace gamescope ++{ ++ Dict& ConCommand::GetCommands() ++ { ++ static Dict s_Commands; ++ return s_Commands; ++ } ++} +\ No newline at end of file +diff --git a/src/convar.h b/src/convar.h +new file mode 100644 +index 000000000..4e2372cf1 +--- /dev/null ++++ b/src/convar.h +@@ -0,0 +1,145 @@ ++#pragma once ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++namespace gamescope ++{ ++ class ConCommand; ++ ++ 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; ++ else ++ return std::nullopt; ++ } ++ ++ template <> ++ inline std::optional Parse( std::string_view chars ) ++ { ++ std::optional oNumber = Parse( chars ); ++ if ( oNumber ) ++ return !!*oNumber; ++ ++ if ( chars == "true" ) ++ return true; ++ else ++ return false; ++ } ++ ++ 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 ); } ++ }; ++ ++ template ++ using Dict = std::unordered_map>; ++ ++ class ConCommand ++ { ++ using ConCommandFunc = std::function )>; ++ ++ public: ++ ConCommand( std::string_view pszName, std::string_view pszDescription, ConCommandFunc func ) ++ : m_pszName{ pszName } ++ , m_pszDescription{ pszDescription } ++ , m_Func{ func } ++ { ++ assert( !GetCommands().contains( pszName ) ); ++ GetCommands()[ std::string( pszName ) ] = this; ++ } ++ ++ ~ConCommand() ++ { ++ GetCommands().erase( GetCommands().find( m_pszName ) ); ++ } ++ ++ void Invoke( std::span args ) ++ { ++ if ( m_Func ) ++ m_Func( args ); ++ } ++ ++ static Dict& GetCommands(); ++ protected: ++ std::string_view m_pszName; ++ std::string_view m_pszDescription; ++ ConCommandFunc m_Func; ++ }; ++ ++ template ++ class ConVar : public ConCommand ++ { ++ using ConVarCallbackFunc = std::function; ++ public: ++ ConVar( std::string_view pszName, T defaultValue = T{}, std::string_view pszDescription = "", ConVarCallbackFunc func = nullptr ) ++ : ConCommand( pszName, pszDescription, [this]( std::span pArgs ){ this->InvokeFunc( pArgs ); } ) ++ , m_Value{ defaultValue } ++ , m_Callback{ func } ++ { ++ } ++ ++ const T& Get() const ++ { ++ return m_Value; ++ } ++ ++ template ++ void SetValue( const J &newValue ) ++ { ++ m_Value = T{ newValue }; ++ ++ if ( !m_bInCallback && m_Callback ) ++ { ++ m_bInCallback = true; ++ m_Callback(); ++ m_bInCallback = false; ++ } ++ } ++ ++ template ++ ConVar& operator =( const J &newValue ) { SetValue( newValue ); return *this; } ++ ++ operator T() 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 bool operator <=>( const J &other ) const { return m_Value <=> other; } ++ ++ void InvokeFunc( std::span pArgs ) ++ { ++ if ( pArgs.size() != 2 ) ++ return; ++ ++ if constexpr ( std::is_integral::value ) ++ { ++ std::optional oResult = Parse( pArgs[1] ); ++ SetValue( oResult ? *oResult : T{} ); ++ } ++ else ++ { ++ SetValue( pArgs[1] ); ++ } ++ } ++ private: ++ T m_Value{}; ++ ConVarCallbackFunc m_Callback; ++ bool m_bInCallback; ++ }; ++} +diff --git a/src/meson.build b/src/meson.build +index 559054ddb..9e2ee20e8 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -91,6 +91,7 @@ required_wlroots_features = ['xwayland'] + + src = [ + 'steamcompmgr.cpp', ++ 'convar.cpp', + 'color_helpers.cpp', + 'main.cpp', + 'edid.cpp', + +From b14d851137f00a1dade22279ed6aeaed72e21337 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 5 Feb 2024 14:10:38 +0000 +Subject: [PATCH 114/134] steamcompmgr: Use convar for hdr_enabled + +Just an example for now +--- + src/steamcompmgr.cpp | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index cd5877e77..80094e997 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -88,6 +88,7 @@ + #include "mwm_hints.h" + #include "edid.h" + #include "hdmi.h" ++#include "convar.h" + + #if HAVE_AVIF + #include "avif/avif.h" +@@ -336,7 +337,7 @@ bool g_bVRRCapable_CachedValue = false; + bool g_bVRRInUse_CachedValue = false; + bool g_bSupportsHDR_CachedValue = false; + bool g_bForceHDR10OutputDebug = false; +-bool g_bHDREnabled = 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; + +@@ -5657,7 +5658,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) + } + if ( ev->atom == ctx->atoms.gamescopeDisplayHDREnabled ) + { +- g_bHDREnabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayHDREnabled, 0 ); ++ cv_hdr_enabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayHDREnabled, 0 ); + hasRepaint = true; + } + if ( ev->atom == ctx->atoms.gamescopeDebugForceHDR10Output ) +@@ -7466,7 +7467,7 @@ steamcompmgr_main(int argc, char **argv) + } else if (strcmp(opt_name, "force-windows-fullscreen") == 0) { + bForceWindowsFullscreen = true; + } else if (strcmp(opt_name, "hdr-enabled") == 0) { +- g_bHDREnabled = true; ++ 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) { +@@ -7638,7 +7639,7 @@ steamcompmgr_main(int argc, char **argv) + update_mode_atoms(root_ctx, &flush_root); + } + +- g_bOutputHDREnabled = (g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; ++ 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 + +From 5618b6ce04f56c3065510424893e462d05799ee4 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 6 Feb 2024 08:41:04 +0000 +Subject: [PATCH 115/134] rendervulkan: Use getprocaddr for vkCreateImageView + +--- + src/backend.h | 2 +- + src/rendervulkan.cpp | 6 +++--- + src/vulkan_include.h | 4 ++++ + src/wlserver.hpp | 3 ++- + 4 files changed, 10 insertions(+), 5 deletions(-) + create mode 100644 src/vulkan_include.h + +diff --git a/src/backend.h b/src/backend.h +index 37e93345b..f19456a75 100644 +--- a/src/backend.h ++++ b/src/backend.h +@@ -2,8 +2,8 @@ + + #include "color_helpers.h" + #include "gamescope_shared.h" ++#include "vulkan_include.h" + +-#include "vulkan/vulkan_core.h" + #include + #include + #include +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 7cb3c1df3..d476d443b 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -10,7 +10,7 @@ + #include + #include + #include +-#include ++#include "vulkan_include.h" + + #if defined(__linux__) + #include +@@ -2315,7 +2315,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + createInfo.format = VK_FORMAT_R8_UNORM; + + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT; +- res = vkCreateImageView(g_device.device(), &createInfo, nullptr, &m_lumaView); ++ res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_lumaView); + if ( res != VK_SUCCESS ) { + vk_errorf( res, "vkCreateImageView failed" ); + return false; +@@ -2324,7 +2324,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin + createInfo.pNext = NULL; + createInfo.format = VK_FORMAT_R8G8_UNORM; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; +- res = vkCreateImageView(g_device.device(), &createInfo, nullptr, &m_chromaView); ++ res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_chromaView); + if ( res != VK_SUCCESS ) { + vk_errorf( res, "vkCreateImageView failed" ); + return false; +diff --git a/src/vulkan_include.h b/src/vulkan_include.h +new file mode 100644 +index 000000000..1f78e5232 +--- /dev/null ++++ b/src/vulkan_include.h +@@ -0,0 +1,4 @@ ++#pragma once ++ ++#define VK_NO_PROTOTYPES ++#include +diff --git a/src/wlserver.hpp b/src/wlserver.hpp +index c148847fb..f0b8e1e1c 100644 +--- a/src/wlserver.hpp ++++ b/src/wlserver.hpp +@@ -11,7 +11,8 @@ + #include + #include + #include +-#include ++ ++#include "vulkan_include.h" + + #include "steamcompmgr_shared.hpp" + + +From 8dcc1f363dfd1601778a9042b339f972c8fe6f98 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 6 Feb 2024 08:46:06 +0000 +Subject: [PATCH 116/134] rendervulkan: dlopen Vulkan ourselves + +--- + src/rendervulkan.cpp | 43 ++++++++++++++++++++++++++++++++----------- + 1 file changed, 32 insertions(+), 11 deletions(-) + +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index d476d443b..09a92d7c0 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include "vulkan_include.h" + + #if defined(__linux__) +@@ -88,18 +89,32 @@ static const mat3x4& colorspace_to_conversion_from_srgb_matrix(EStreamColorspace + } + } + +-extern "C" ++PFN_vkGetInstanceProcAddr g_pfn_vkGetInstanceProcAddr; ++PFN_vkCreateInstance g_pfn_vkCreateInstance; ++ ++static VkResult vulkan_load_module() + { +-VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance( +- const VkInstanceCreateInfo* pCreateInfo, +- const VkAllocationCallbacks* pAllocator, +- VkInstance* pInstance); ++ 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; + +-VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr( +- VkInstance instance, +- const char* pName); +-} ++ 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; + +@@ -267,7 +282,7 @@ bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface) + g_output.surface = surface; + + m_instance = instance; +- #define VK_FUNC(x) vk.x = (PFN_vk##x) vkGetInstanceProcAddr(instance, "vk"#x); ++ #define VK_FUNC(x) vk.x = (PFN_vk##x) g_pfn_vkGetInstanceProcAddr(instance, "vk"#x); + VULKAN_INSTANCE_FUNCTIONS + #undef VK_FUNC + +@@ -3232,6 +3247,12 @@ VkInstance vulkan_get_instance( void ) + { + 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 = { +@@ -3251,7 +3272,7 @@ VkInstance vulkan_get_instance( void ) + }; + + VkInstance instance = nullptr; +- result = vkCreateInstance(&createInfo, 0, &instance); ++ result = g_pfn_vkCreateInstance(&createInfo, 0, &instance); + if ( result != VK_SUCCESS ) + { + vk_errorf( result, "vkCreateInstance failed" ); + +From 532907af938f8a6bd8062ab71144186a610051b3 Mon Sep 17 00:00:00 2001 +From: Andres Rodriguez +Date: Thu, 8 Feb 2024 18:03:24 -0500 +Subject: [PATCH 117/134] wlserver: always broadcast a refresh rate in + gamescope_control + +If we know that the display only supports one refresh rate (the current +refresh rate), send that as part of active display info. + +This is useful so clients can determine valid framelimit values. +--- + src/wlserver.cpp | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index b1a6ff41d..0f972edf9 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -927,6 +927,11 @@ void wlserver_send_gamescope_control( wl_resource *control ) + uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); + memcpy( ptr, pConn->GetValidDynamicRefreshRates().data(), size ); + } ++ else if ( g_nOutputRefresh > 0 ) ++ { ++ uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, sizeof(uint32_t) ); ++ *ptr = (uint32_t)g_nOutputRefresh; ++ } + gamescope_control_send_active_display_info( control, pConn->GetName(), pConn->GetMake(), pConn->GetModel(), flags, &display_rates ); + wl_array_release(&display_rates); + } + +From 7664011cbc1d65eeb494dd05aade914923a3f0c7 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Sun, 11 Feb 2024 21:38:58 +0000 +Subject: [PATCH 118/134] headless: Fix init + +--- + src/headless.cpp | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/src/headless.cpp b/src/headless.cpp +index 493aea13e..5e6c7c04e 100644 +--- a/src/headless.cpp ++++ b/src/headless.cpp +@@ -1,4 +1,9 @@ + #include "backend.h" ++#include "rendervulkan.hpp" ++#include "wlserver.hpp" ++ ++extern int g_nPreferredOutputWidth; ++extern int g_nPreferredOutputHeight; + + namespace gamescope + { +@@ -92,6 +97,35 @@ namespace gamescope + + 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 = 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; + } + + +From 2428f274b8873f6454ad069a1068ea29d42fce2e Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Tue, 13 Feb 2024 01:53:45 +0000 +Subject: [PATCH 119/134] mangoapp: Flip app_frametime + visible_frametime + +We decided that we didn't want users to think things regressed by having the graph be more barcode-y. Flip the graphs around so visible frametimes are on the bottom on Level 4 instead. + +Gratuitiously ignoring the WARNING in mangoapp.cpp :-) +--- + src/mangoapp.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/mangoapp.cpp b/src/mangoapp.cpp +index 2d0c59f72..6726e0581 100644 +--- a/src/mangoapp.cpp ++++ b/src/mangoapp.cpp +@@ -18,10 +18,10 @@ struct mangoapp_msg_v1 { + struct mangoapp_msg_header hdr; + + uint32_t pid; +- uint64_t visible_frametime_ns; ++ uint64_t app_frametime_ns; + uint8_t fsrUpscale; + uint8_t fsrSharpness; +- uint64_t app_frametime_ns; ++ uint64_t visible_frametime_ns; + uint64_t latency_ns; + uint32_t outputWidth; + uint32_t outputHeight; + +From 9113c844726b7443561d31b2980da000b89f27fd Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 14 Feb 2024 20:03:04 +0000 +Subject: [PATCH 120/134] protocol: Add MURA_CORRECTION feature + +--- + protocol/gamescope-control.xml | 1 + + src/wlserver.cpp | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/protocol/gamescope-control.xml b/protocol/gamescope-control.xml +index 4359eb148..012c48c2b 100644 +--- a/protocol/gamescope-control.xml ++++ b/protocol/gamescope-control.xml +@@ -41,6 +41,7 @@ + + + ++ + + + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 0f972edf9..4ee931b15 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -949,6 +949,7 @@ static void gamescope_control_bind( struct wl_client *client, void *data, uint32 + 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_MURA_CORRECTION, 1, 0 ); + gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DONE, 0, 0 ); + + wlserver_send_gamescope_control( resource ); + +From 7d2a46c192573d1dc878cbbdc11b00d3051f8f50 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 14 Feb 2024 21:47:17 +0000 +Subject: [PATCH 121/134] vr_session: Setup vulkan + session stuff properly + +--- + src/vr_session.cpp | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/src/vr_session.cpp b/src/vr_session.cpp +index 9bf8085cd..654789aa8 100644 +--- a/src/vr_session.cpp ++++ b/src/vr_session.cpp +@@ -234,6 +234,35 @@ namespace gamescope + + 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; ++ ++ if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) ++ { ++ return false; ++ } ++ ++ if ( !wlsession_init() ) ++ { ++ fprintf( stderr, "Failed to initialize Wayland session\n" ); ++ return false; ++ } ++ ++ // + vr::EVRInitError error = vr::VRInitError_None; + VR_Init(&error, vr::VRApplication_Background); + + +From 1bea9196edd0d0542272846f78f78ece63023056 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 14 Feb 2024 21:48:14 +0000 +Subject: [PATCH 122/134] build: Fix libcap define + +--- + src/meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/meson.build b/src/meson.build +index 9e2ee20e8..7f7e79305 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -128,7 +128,7 @@ endif + gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) + gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) + gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) +-gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(avif_dep.found().to_int()) ++gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(cap_dep.found().to_int()) + + src += spirv_shaders + src += protocols_server_src + +From 677dabfee377f29c0a6c2ea3ae2498da8d78cc4a Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 14 Feb 2024 22:00:35 +0000 +Subject: [PATCH 123/134] shaders: Don't require int8 stuff + +--- + src/main.hpp | 2 +- + src/rendervulkan.cpp | 22 ++++++++++---------- + src/shaders/blit_push_data.h | 4 ++-- + src/shaders/composite.h | 6 ++++-- + src/shaders/cs_composite_blit.comp | 1 - + src/shaders/cs_composite_blur.comp | 1 - + src/shaders/cs_composite_blur_cond.comp | 1 - + src/shaders/cs_composite_rcas.comp | 4 +--- + src/shaders/cs_easu.comp | 1 - + src/shaders/cs_easu_fp16.comp | 1 - + src/shaders/cs_gaussian_blur_horizontal.comp | 4 +--- + src/shaders/cs_nis.comp | 1 - + src/shaders/cs_nis_fp16.comp | 1 - + src/shaders/cs_rgb_to_nv12.comp | 3 +-- + src/shaders/shaderfilter.h | 10 +++++++++ + 15 files changed, 31 insertions(+), 31 deletions(-) + create mode 100644 src/shaders/shaderfilter.h + +diff --git a/src/main.hpp b/src/main.hpp +index a87030b0f..71e2c1227 100644 +--- a/src/main.hpp ++++ b/src/main.hpp +@@ -37,7 +37,7 @@ enum class GamescopeUpscaleFilter : uint32_t + NIS, + PIXEL, + +- FROM_VIEW = 255, // internal ++ FROM_VIEW = 0xF, // internal + }; + + enum class GamescopeUpscaleScaler : uint32_t +diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp +index 09a92d7c0..036d5e2a1 100644 +--- a/src/rendervulkan.cpp ++++ b/src/rendervulkan.cpp +@@ -3401,8 +3401,7 @@ struct BlitPushData_t + uint32_t frameId; + uint32_t blurRadius; + +- uint8_t u_shaderFilter[k_nMaxLayers]; +- uint8_t u_padding[2]; ++ uint32_t u_shaderFilter; + + float u_linearToNits; // unset + float u_nitsToLinear; // unset +@@ -3411,15 +3410,17 @@ struct BlitPushData_t + + explicit BlitPushData_t(const struct FrameInfo_t *frameInfo) + { ++ u_shaderFilter = 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[i] = (uint32_t)GamescopeUpscaleFilter::FROM_VIEW; ++ u_shaderFilter |= ((uint32_t)GamescopeUpscaleFilter::FROM_VIEW) << (i * 4); + else +- u_shaderFilter[i] = (uint32_t)layer->filter; ++ u_shaderFilter |= ((uint32_t)layer->filter) << (i * 4); + + if (layer->ctm) + { +@@ -3450,7 +3451,7 @@ struct BlitPushData_t + scale[0] = { blit_scale, blit_scale }; + offset[0] = { 0.5f, 0.5f }; + opacity[0] = 1.0f; +- u_shaderFilter[0] = (uint32_t)GamescopeUpscaleFilter::LINEAR; ++ u_shaderFilter = (uint32_t)GamescopeUpscaleFilter::LINEAR; + ctm[0] = glm::mat3x4 + { + 1, 0, 0, 0, +@@ -3529,8 +3530,7 @@ struct RcasPushData_t + uint32_t u_frameId; + uint32_t u_c1; + +- uint8_t u_shaderFilter[k_nMaxLayers]; +- uint8_t u_padding[2]; ++ uint32_t u_shaderFilter; + + float u_linearToNits; // unset + float u_nitsToLinear; // unset +@@ -3546,15 +3546,16 @@ struct RcasPushData_t + u_borderMask = frameInfo->borderMask() >> 1u; + u_frameId = s_frameId++; + u_c1 = tmp.x; ++ u_shaderFilter = 0; + + for (int i = 0; i < frameInfo->layerCount; i++) + { + const FrameInfo_t::Layer_t *layer = &frameInfo->layers[i]; + +- if (layer->isScreenSize() || (layer->filter == GamescopeUpscaleFilter::LINEAR && layer->viewConvertsToLinearAutomatically())) +- u_shaderFilter[i] = (uint32_t)GamescopeUpscaleFilter::FROM_VIEW; ++ if (i == 0 || layer->isScreenSize() || (layer->filter == GamescopeUpscaleFilter::LINEAR && layer->viewConvertsToLinearAutomatically())) ++ u_shaderFilter |= ((uint32_t)GamescopeUpscaleFilter::FROM_VIEW) << (i * 4); + else +- u_shaderFilter[i] = (uint32_t)layer->filter; ++ u_shaderFilter |= ((uint32_t)layer->filter) << (i * 4); + + if (layer->ctm) + { +@@ -3572,7 +3573,6 @@ struct RcasPushData_t + + u_opacity[i] = frameInfo->layers[i].opacity; + } +- u_shaderFilter[0] = (uint32_t)GamescopeUpscaleFilter::FROM_VIEW; + + u_linearToNits = g_flInternalDisplayBrightnessNits; + u_nitsToLinear = 1.0f / g_flInternalDisplayBrightnessNits; +diff --git a/src/shaders/blit_push_data.h b/src/shaders/blit_push_data.h +index 3a4a32343..9395312ce 100644 +--- a/src/shaders/blit_push_data.h ++++ b/src/shaders/blit_push_data.h +@@ -8,8 +8,7 @@ uniform layers_t { + uint u_frameId; + uint u_blur_radius; + +- uint8_t u_shaderFilter[VKR_MAX_LAYERS]; +- uint8_t u_padding[2]; ++ uint u_shaderFilter; + + // hdr + float u_linearToNits; // sdr -> hdr +@@ -17,3 +16,4 @@ uniform layers_t { + float u_itmSdrNits; + float u_itmTargetNits; + }; ++ +diff --git a/src/shaders/composite.h b/src/shaders/composite.h +index 76c6cea65..884da244a 100644 +--- a/src/shaders/composite.h ++++ b/src/shaders/composite.h +@@ -1,5 +1,7 @@ + #include "colorimetry.h" + ++#include "shaderfilter.h" ++ + vec4 sampleRegular(sampler2D tex, vec2 coord, uint colorspace) { + vec4 color = textureLod(tex, coord, 0); + color.rgb = colorspace_plane_degamma_tf(color.rgb, colorspace); +@@ -162,12 +164,12 @@ vec4 sampleLayerEx(sampler2D layerSampler, uint offsetLayerIdx, uint colorspaceL + + uint colorspace = get_layer_colorspace(colorspaceLayerIdx); + vec4 color; +- if (u_shaderFilter[offsetLayerIdx] == filter_pixel) { ++ if (get_layer_shaderfilter(offsetLayerIdx) == filter_pixel) { + vec2 output_res = texSize / u_scale[offsetLayerIdx]; + vec2 extent = max((texSize / output_res), vec2(1.0 / 256.0)); + color = sampleBandLimited(layerSampler, coord, unnormalized ? vec2(1.0f) : texSize, unnormalized ? vec2(1.0f) : vec2(1.0f) / texSize, extent, colorspace, unnormalized); + } +- else if (u_shaderFilter[offsetLayerIdx] == filter_linear_emulated) { ++ else if (get_layer_shaderfilter(offsetLayerIdx) == filter_linear_emulated) { + color = sampleBilinear(layerSampler, coord, colorspace, unnormalized); + } + else { +diff --git a/src/shaders/cs_composite_blit.comp b/src/shaders/cs_composite_blit.comp +index 52bd9d517..51727fe86 100644 +--- a/src/shaders/cs_composite_blit.comp ++++ b/src/shaders/cs_composite_blit.comp +@@ -1,7 +1,6 @@ + #version 450 + + #extension GL_GOOGLE_include_directive : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #include "descriptor_set.h" +diff --git a/src/shaders/cs_composite_blur.comp b/src/shaders/cs_composite_blur.comp +index 52062bdca..a9326aa1a 100644 +--- a/src/shaders/cs_composite_blur.comp ++++ b/src/shaders/cs_composite_blur.comp +@@ -1,7 +1,6 @@ + #version 450 + + #extension GL_GOOGLE_include_directive : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #include "descriptor_set.h" +diff --git a/src/shaders/cs_composite_blur_cond.comp b/src/shaders/cs_composite_blur_cond.comp +index ffed4d5cf..7538cb06a 100644 +--- a/src/shaders/cs_composite_blur_cond.comp ++++ b/src/shaders/cs_composite_blur_cond.comp +@@ -1,7 +1,6 @@ + #version 450 + + #extension GL_GOOGLE_include_directive : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #include "descriptor_set.h" +diff --git a/src/shaders/cs_composite_rcas.comp b/src/shaders/cs_composite_rcas.comp +index 8c14920f7..cee2a45c2 100644 +--- a/src/shaders/cs_composite_rcas.comp ++++ b/src/shaders/cs_composite_rcas.comp +@@ -1,7 +1,6 @@ + #version 460 + + #extension GL_GOOGLE_include_directive : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #include "descriptor_set.h" +@@ -22,8 +21,7 @@ uniform layers_t { + uint u_frameId; + uint u_c1; + +- uint8_t u_shaderFilter[VKR_MAX_LAYERS]; +- uint8_t u_padding[2]; ++ uint u_shaderFilter; + + // hdr + float u_linearToNits; +diff --git a/src/shaders/cs_easu.comp b/src/shaders/cs_easu.comp +index 3eb883d8d..d13ee9577 100644 +--- a/src/shaders/cs_easu.comp ++++ b/src/shaders/cs_easu.comp +@@ -1,7 +1,6 @@ + #version 460 + + #extension GL_GOOGLE_include_directive : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #include "descriptor_set.h" +diff --git a/src/shaders/cs_easu_fp16.comp b/src/shaders/cs_easu_fp16.comp +index 344485404..403c2b28d 100644 +--- a/src/shaders/cs_easu_fp16.comp ++++ b/src/shaders/cs_easu_fp16.comp +@@ -2,7 +2,6 @@ + + #extension GL_GOOGLE_include_directive : require + #extension GL_EXT_shader_explicit_arithmetic_types_float16 : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #include "descriptor_set.h" +diff --git a/src/shaders/cs_gaussian_blur_horizontal.comp b/src/shaders/cs_gaussian_blur_horizontal.comp +index fac80d1e7..a4f6a92cb 100644 +--- a/src/shaders/cs_gaussian_blur_horizontal.comp ++++ b/src/shaders/cs_gaussian_blur_horizontal.comp +@@ -1,7 +1,6 @@ + #version 460 + + #extension GL_GOOGLE_include_directive : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #include "descriptor_set.h" +@@ -21,8 +20,7 @@ uniform layers_t { + uint u_frameId; + uint u_blur_radius; + +- uint8_t u_shaderFilter[VKR_MAX_LAYERS]; +- uint8_t u_padding[2]; ++ uint u_shaderFilter; + + // hdr + float u_linearToNits; +diff --git a/src/shaders/cs_nis.comp b/src/shaders/cs_nis.comp +index 78d0a85f5..9fe69e2f7 100644 +--- a/src/shaders/cs_nis.comp ++++ b/src/shaders/cs_nis.comp +@@ -1,7 +1,6 @@ + #version 450 + + #extension GL_GOOGLE_include_directive : enable +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #define NIS_GLSL 1 +diff --git a/src/shaders/cs_nis_fp16.comp b/src/shaders/cs_nis_fp16.comp +index 5827c83b3..f846d5d70 100644 +--- a/src/shaders/cs_nis_fp16.comp ++++ b/src/shaders/cs_nis_fp16.comp +@@ -2,7 +2,6 @@ + + #extension GL_GOOGLE_include_directive : enable + #extension GL_EXT_shader_explicit_arithmetic_types_float16 : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #define NIS_GLSL 1 +diff --git a/src/shaders/cs_rgb_to_nv12.comp b/src/shaders/cs_rgb_to_nv12.comp +index a15a3d4df..3702b8160 100644 +--- a/src/shaders/cs_rgb_to_nv12.comp ++++ b/src/shaders/cs_rgb_to_nv12.comp +@@ -1,7 +1,6 @@ + #version 450 + + #extension GL_GOOGLE_include_directive : require +-#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require + #extension GL_EXT_scalar_block_layout : require + + #include "descriptor_set.h" +@@ -17,7 +16,7 @@ layout( + // UVUVUVUVUVUVUVU... + + const uint u_frameId = 0; +-const uint8_t u_shaderFilter[VKR_MAX_LAYERS] = { uint8_t(filter_linear_emulated), uint8_t(0), uint8_t(0), uint8_t(0), uint8_t(0), uint8_t(0) }; ++const uint u_shaderFilter = filter_linear_emulated; + const float u_linearToNits = 400.0f; + const float u_nitsToLinear = 1.0f / 100.0f; + const float u_itmSdrNits = 100.f; +diff --git a/src/shaders/shaderfilter.h b/src/shaders/shaderfilter.h +new file mode 100644 +index 000000000..0d6fa4a98 +--- /dev/null ++++ b/src/shaders/shaderfilter.h +@@ -0,0 +1,10 @@ ++#ifndef SHADERFILTER_H ++#define SHADERFILTER_H ++ ++const int shader_filter_max_bits = 4; ++ ++uint get_layer_shaderfilter(uint layerIdx) { ++ return bitfieldExtract(u_shaderFilter, int(layerIdx) * shader_filter_max_bits, shader_filter_max_bits); ++} ++ ++#endif +\ No newline at end of file + +From 2a630934037dfebccf8307b947590646693206e6 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 14 Feb 2024 22:00:55 +0000 +Subject: [PATCH 124/134] vr: Fix build + +--- + src/vr_session.cpp | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/vr_session.cpp b/src/vr_session.cpp +index 654789aa8..c1a5444cd 100644 +--- a/src/vr_session.cpp ++++ b/src/vr_session.cpp +@@ -27,6 +27,9 @@ extern bool steamMode; + extern int g_argc; + extern char **g_argv; + ++extern int g_nPreferredOutputWidth; ++extern int g_nPreferredOutputHeight; ++ + static LogScope openvr_log("openvr"); + + static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList ); + +From b5670a2e8501efbd2e593dfb4426c823990dc421 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 14 Feb 2024 23:50:28 +0000 +Subject: [PATCH 125/134] drm: Fix request rollback + +--- + src/drm.cpp | 65 +++++++++++++++++++++++++++++------------------------ + 1 file changed, 36 insertions(+), 29 deletions(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 5ff3cafcc..5c7ebf7d4 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -2484,6 +2484,39 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo + + bool g_bForceAsyncFlips = false; + ++void drm_rollback( struct drm_t *drm ) ++{ ++ 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(); ++ } ++ } ++} ++ + /* 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 ) +@@ -2661,6 +2694,8 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI + } + + if ( ret != 0 ) { ++ drm_rollback( drm ); ++ + drmModeAtomicFree( drm->req ); + drm->req = nullptr; + +@@ -3503,35 +3538,7 @@ namespace gamescope + abort(); + } + +- 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(); +- } +- } ++ drm_rollback( drm ); + + // Undo refcount if the commit didn't actually work + for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) + +From f1cdbd1402717c8bf7e470c756620cae3b12301b Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Thu, 15 Feb 2024 00:10:39 +0000 +Subject: [PATCH 126/134] drm: Fix CTM matrix stuff, add typeinfo to backend + system + +--- + src/backend.h | 4 ++-- + src/drm.cpp | 22 +++++++++++++++++++--- + src/headless.cpp | 2 +- + src/sdlwindow.cpp | 4 ++-- + src/vr_session.cpp | 2 +- + 5 files changed, 25 insertions(+), 9 deletions(-) + +diff --git a/src/backend.h b/src/backend.h +index f19456a75..66b3ff964 100644 +--- a/src/backend.h ++++ b/src/backend.h +@@ -142,13 +142,13 @@ namespace gamescope + virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) = 0; + virtual bool PollState() = 0; + +- virtual std::shared_ptr CreateBackendBlob( std::span data ) = 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( std::span( pBegin, pEnd ) ); ++ return CreateBackendBlob( typeid( T ), std::span( pBegin, pEnd ) ); + } + + // For DRM, this is +diff --git a/src/drm.cpp b/src/drm.cpp +index 5c7ebf7d4..0fcf68efc 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -3353,11 +3353,27 @@ namespace gamescope + return drm_poll_state( &g_DRM ); + } + +- virtual std::shared_ptr CreateBackendBlob( std::span data ) override ++ virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override + { + uint32_t uBlob = 0; +- if ( drmModeCreatePropertyBlob( g_DRM.fd, data.data(), data.size(), &uBlob ) != 0 ) +- return nullptr; ++ 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] ); ++ ++ fprintf( stderr, " !!!! MAKING CTM BLOB!!!!!!\n "); ++ 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 ); + } +diff --git a/src/headless.cpp b/src/headless.cpp +index 5e6c7c04e..b6767cdd4 100644 +--- a/src/headless.cpp ++++ b/src/headless.cpp +@@ -170,7 +170,7 @@ namespace gamescope + return false; + } + +- virtual std::shared_ptr CreateBackendBlob( std::span data ) override ++ virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override + { + return std::make_shared( data ); + } +diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp +index 1974bc229..50c3b95b3 100644 +--- a/src/sdlwindow.cpp ++++ b/src/sdlwindow.cpp +@@ -127,7 +127,7 @@ namespace gamescope + virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override; + virtual bool PollState() override; + +- virtual std::shared_ptr CreateBackendBlob( std::span data ) override; ++ virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override; + + virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override; + virtual void LockBackendFb( uint32_t uFbId ) override; +@@ -390,7 +390,7 @@ namespace gamescope + return false; + } + +- std::shared_ptr CSDLBackend::CreateBackendBlob( std::span data ) ++ std::shared_ptr CSDLBackend::CreateBackendBlob( const std::type_info &type, std::span data ) + { + return std::make_shared( data ); + } +diff --git a/src/vr_session.cpp b/src/vr_session.cpp +index c1a5444cd..686d853fc 100644 +--- a/src/vr_session.cpp ++++ b/src/vr_session.cpp +@@ -466,7 +466,7 @@ namespace gamescope + return false; + } + +- virtual std::shared_ptr CreateBackendBlob( std::span data ) override ++ virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override + { + return std::make_shared( data ); + } + +From a6155bafaf38f5011f8bcd1eba90702f3498514c Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Thu, 15 Feb 2024 21:41:33 +0000 +Subject: [PATCH 127/134] steamcompmgr: Fix flInternalDisplayBrightness + +We should maybe remove this at some point...? +--- + src/steamcompmgr.cpp | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 80094e997..b2571ae6e 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -352,6 +352,9 @@ update_color_mgmt() + &g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF, + &g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF ); + ++ g_ColorMgmt.pending.flInternalDisplayBrightness = ++ GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxContentLightLevel; ++ + #ifdef COLOR_MGMT_MICROBENCH + struct timespec t0, t1; + #else + +From ba5f9e8e0c490ebadc1012ff1e6f5a38cb4a00f7 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Thu, 15 Feb 2024 21:43:42 +0000 +Subject: [PATCH 128/134] color_helpers: Add some extra color toys + +--- + src/color_helpers.cpp | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp +index 4ec148a65..18d3ee69d 100644 +--- a/src/color_helpers.cpp ++++ b/src/color_helpers.cpp +@@ -647,6 +647,8 @@ inline T calcLinearToEOTF( const T & input, EOTF eotf, const tonemapping_t & ton + // input is from 0->1 + // TODO: use tone-mapping for white, black, contrast ratio + ++bool g_bUseSourceEOTFForShaper = false; ++ + template + inline T applyShaper( const T & input, EOTF source, EOTF dest, const tonemapping_t & tonemapping, float flGain ) + { +@@ -658,9 +660,11 @@ inline T applyShaper( const T & input, EOTF source, EOTF dest, const tonemapping + T flLinear = flGain * calcEOTFToLinear( input, source, tonemapping ); + flLinear = tonemapping.apply( flLinear ); + +- return calcLinearToEOTF( flLinear, dest, tonemapping ); ++ return calcLinearToEOTF( flLinear, g_bUseSourceEOTFForShaper ? source : dest, tonemapping ); + } + ++bool g_bHuePreservationWhenClipping = false; ++ + void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, + lut3d_t * pLut3d, int nLutEdgeSize3d, + const displaycolorimetry_t & source, EOTF sourceEOTF, +@@ -778,6 +782,18 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, + // Apply tonemapping + destColorLinear = tonemapping.apply( destColorLinear ); + ++ // 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. ++ if ( flMax > tonemapping.g22_luminance + 1.0f ) ++ { ++ destColorLinear /= flMax; ++ destColorLinear *= tonemapping.g22_luminance; ++ } ++ } ++ + // Apply dest EOTF + glm::vec3 destColorEOTFEncoded = calcLinearToEOTF( destColorLinear, destEOTF, tonemapping ); + + +From a3472f6f0518190215a3df3e41caee7e83338021 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 19 Feb 2024 17:46:46 -0800 +Subject: [PATCH 129/134] steamcompmgr: Fix cursor buffer empty check + +--- + src/steamcompmgr.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index b2571ae6e..5e3a5956b 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -1924,9 +1924,9 @@ bool MouseCursor::getTexture() + } + } + +- for (int i = 0; i < image->height; i++) ++ for (int i = 0; i < surfaceHeight; i++) + { +- for (int j = 0; j < image->width; j++) ++ for (int j = 0; j < surfaceWidth; j++) + { + if ( cursorBuffer[i * surfaceWidth + j] & 0xff000000 ) + { + +From bd225aa20662f5a8c9b862a8df197241df0e2649 Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 19 Feb 2024 17:54:11 -0800 +Subject: [PATCH 130/134] drm: Fix external HDR when compositing + +--- + src/drm.cpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 0fcf68efc..31bbe465e 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -3217,6 +3217,8 @@ namespace gamescope + vulkan_wait( *oCompositeResult, true ); + + FrameInfo_t presentCompFrameInfo = {}; ++ presentCompFrameInfo.allowVRR = pFrameInfo->allowVRR; ++ presentCompFrameInfo.outputEncodingEOTF = pFrameInfo->outputEncodingEOTF; + + if ( bNeedsFullComposite ) + { + +From e79d79a419402861952780ecd507ed6da56ea8ed Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Mon, 19 Feb 2024 17:57:05 -0800 +Subject: [PATCH 131/134] drm: Don't change cursor plane size if we are not + using the plane + +--- + src/drm.cpp | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/src/drm.cpp b/src/drm.cpp +index 31bbe465e..9420ccbe4 100644 +--- a/src/drm.cpp ++++ b/src/drm.cpp +@@ -50,6 +50,7 @@ + + #include "gamescope-control-protocol.h" + ++static constexpr bool k_bUseCursorPlane = false; + + namespace gamescope + { +@@ -3098,7 +3099,7 @@ namespace gamescope + bNeedsFullComposite |= pFrameInfo->useNISLayer0; + bNeedsFullComposite |= pFrameInfo->blurLayer0; + bNeedsFullComposite |= bNeedsCompositeFromFilter; +- bNeedsFullComposite |= bDrewCursor; ++ bNeedsFullComposite |= !k_bUseCursorPlane && bDrewCursor; + bNeedsFullComposite |= g_bColorSliderInUse; + bNeedsFullComposite |= pFrameInfo->bFadingOut; + bNeedsFullComposite |= !g_reshade_effect.empty(); +@@ -3469,6 +3470,9 @@ namespace gamescope + + 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 }; + } + + +From 6db5d2d06baaf4ed0e8dd0551ea109ebce78e56f Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 21 Feb 2024 22:11:20 +0000 +Subject: [PATCH 132/134] wlserver: Hook up take_screenshot and expose ver 3 + +--- + src/wlserver.cpp | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 4ee931b15..a0727924b 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -881,6 +881,16 @@ static void gamescope_control_set_app_target_refresh_cycle( struct wl_client *cl + steamcompmgr_set_app_refresh_cycle_override( display_type, fps ); + } + ++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, ++ } ); ++} ++ + static void gamescope_control_handle_destroy( struct wl_client *client, struct wl_resource *resource ) + { + wl_resource_destroy( resource ); +@@ -889,6 +899,7 @@ static void gamescope_control_handle_destroy( struct wl_client *client, struct w + 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, + }; + + static uint32_t get_conn_display_info_flags() +@@ -959,7 +970,7 @@ static void gamescope_control_bind( struct wl_client *client, void *data, uint32 + + static void create_gamescope_control( void ) + { +- uint32_t version = 2; ++ uint32_t version = 3; + wl_global_create( wlserver.display, &gamescope_control_interface, version, NULL, gamescope_control_bind ); + } + + +From d0d23c4c3010c81add1bd90cbe478ce4a386e28d Mon Sep 17 00:00:00 2001 +From: Joshua Ashton +Date: Wed, 21 Feb 2024 22:11:46 +0000 +Subject: [PATCH 133/134] steamcompmgr: Fix warning in cursor code + +--- + src/steamcompmgr.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 5e3a5956b..12136714d 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -1924,9 +1924,9 @@ bool MouseCursor::getTexture() + } + } + +- for (int i = 0; i < surfaceHeight; i++) ++ for (uint32_t i = 0; i < surfaceHeight; i++) + { +- for (int j = 0; j < surfaceWidth; j++) ++ for (uint32_t j = 0; j < surfaceWidth; j++) + { + if ( cursorBuffer[i * surfaceWidth + j] & 0xff000000 ) + { + From 85432af61b779a02b636fdc29d98aba5e89fcff7 Mon Sep 17 00:00:00 2001 From: Andrew O'Neil Date: Sat, 24 Feb 2024 11:40:08 +1100 -Subject: [PATCH] Update AMD color management for Linux 6.8 +Subject: [PATCH 134/134] Update AMD color management for Linux 6.8 --- src/drm.cpp | 258 ++++++++++++++++++++++++++-------------------- From c342c551dcd3a58b07a08f474c1d7899bc524804 Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Mon, 18 Mar 2024 14:41:01 -0700 Subject: [PATCH 05/12] chore: Add coolercontrol and lact to System76-Scheduler ignore list --- system_files/deck/shared/usr/etc/system76-scheduler/config.kdl | 3 +++ .../desktop/shared/usr/etc/system76-scheduler/config.kdl | 3 +++ 2 files changed, 6 insertions(+) diff --git a/system_files/deck/shared/usr/etc/system76-scheduler/config.kdl b/system_files/deck/shared/usr/etc/system76-scheduler/config.kdl index 5a347878a7..135b152518 100644 --- a/system_files/deck/shared/usr/etc/system76-scheduler/config.kdl +++ b/system_files/deck/shared/usr/etc/system76-scheduler/config.kdl @@ -76,6 +76,8 @@ process-scheduler enable=true { include descends="taskset" include descends="schedtool" chrt + coolercontrold + coolercontrol-liqctld dbus dbus-broker ds-inhibit @@ -86,6 +88,7 @@ process-scheduler enable=true { input-remapper-service ionice joystickwake + lact mangoapp nice otd-daemon diff --git a/system_files/desktop/shared/usr/etc/system76-scheduler/config.kdl b/system_files/desktop/shared/usr/etc/system76-scheduler/config.kdl index d79b59788a..402d0bfd94 100644 --- a/system_files/desktop/shared/usr/etc/system76-scheduler/config.kdl +++ b/system_files/desktop/shared/usr/etc/system76-scheduler/config.kdl @@ -76,6 +76,8 @@ process-scheduler enable=true { include descends="taskset" include descends="schedtool" chrt + coolercontrold + coolercontrol-liqctld dbus dbus-broker gamemoderun @@ -83,6 +85,7 @@ process-scheduler enable=true { input-remapper-service ionice joystickwake + lact nice otd-daemon oversteer From 7c395fad8d0366a08b3a94f3853625b41316a246 Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Mon, 18 Mar 2024 14:42:04 -0700 Subject: [PATCH 06/12] Revert "chore: Add F40 gamescope spec file w/ needed HDR patch" This reverts commit 5aada39077bf83f67a434572242216bd5162efe2. --- spec_files/gamescope/40/1149.patch | 26110 ---------------- spec_files/gamescope/40/add_720p_var.patch | 35 - spec_files/gamescope/40/chimeraos.patch | 974 - spec_files/gamescope/40/crashfix.patch | 57 - spec_files/gamescope/40/gamescope.spec | 112 - spec_files/gamescope/40/legion_go.patch | 30 - spec_files/gamescope/40/stb.pc | 7 - .../gamescope/40/touch_gestures_env.patch | 77 - .../gamescope/{39 => }/add_720p_var.patch | 0 spec_files/gamescope/{39 => }/chimeraos.patch | 0 spec_files/gamescope/{39 => }/crashfix.patch | 0 spec_files/gamescope/{39 => }/gamescope.spec | 0 spec_files/gamescope/{39 => }/legion_go.patch | 0 spec_files/gamescope/{39 => }/stb.pc | 0 .../{39 => }/touch_gestures_env.patch | 0 15 files changed, 27402 deletions(-) delete mode 100644 spec_files/gamescope/40/1149.patch delete mode 100644 spec_files/gamescope/40/add_720p_var.patch delete mode 100644 spec_files/gamescope/40/chimeraos.patch delete mode 100644 spec_files/gamescope/40/crashfix.patch delete mode 100644 spec_files/gamescope/40/gamescope.spec delete mode 100644 spec_files/gamescope/40/legion_go.patch delete mode 100644 spec_files/gamescope/40/stb.pc delete mode 100644 spec_files/gamescope/40/touch_gestures_env.patch rename spec_files/gamescope/{39 => }/add_720p_var.patch (100%) rename spec_files/gamescope/{39 => }/chimeraos.patch (100%) rename spec_files/gamescope/{39 => }/crashfix.patch (100%) rename spec_files/gamescope/{39 => }/gamescope.spec (100%) rename spec_files/gamescope/{39 => }/legion_go.patch (100%) rename spec_files/gamescope/{39 => }/stb.pc (100%) rename spec_files/gamescope/{39 => }/touch_gestures_env.patch (100%) diff --git a/spec_files/gamescope/40/1149.patch b/spec_files/gamescope/40/1149.patch deleted file mode 100644 index ec6dc63c6b..0000000000 --- a/spec_files/gamescope/40/1149.patch +++ /dev/null @@ -1,26110 +0,0 @@ -From 146da86c89a392d61ab562c1ef355c5097086fed Mon Sep 17 00:00:00 2001 -From: Simon Ser -Date: Fri, 24 Nov 2023 14:31:52 +0100 -Subject: [PATCH 001/134] steamcompmgr: don't erase multiple commits with - future desired time - -If the queue contains two commits with a desired time set in the -future, keep the second one in the queue instead of discarding it. ---- - src/steamcompmgr.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 081d342e5..1c6ed8d63 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -6384,7 +6384,7 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) - if ( entry.desiredPresentTime > next_refresh_time ) - { - commits_before_their_time.push_back( entry ); -- break; -+ continue; - } - - for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next ) - -From 592f7780e043cfb0d32600343e7c7adf35729ce9 Mon Sep 17 00:00:00 2001 -From: Simon Ser -Date: Fri, 24 Nov 2023 13:29:37 +0100 -Subject: [PATCH 002/134] steamcompmgr: add per-window sequence number - -This is guaranteed to never be re-used. ---- - src/steamcompmgr.cpp | 3 +++ - src/steamcompmgr.hpp | 1 + - src/steamcompmgr_shared.hpp | 2 ++ - src/wlserver.cpp | 1 + - 4 files changed, 7 insertions(+) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 1c6ed8d63..925d0dc59 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -130,6 +130,8 @@ extern float g_flInternalDisplayBrightnessNits; - extern float g_flHDRItmSdrNits; - extern float g_flHDRItmTargetNits; - -+uint64_t g_lastWinSeq = 0; -+ - extern std::atomic g_lastVblank; - - static std::shared_ptr s_scRGB709To2020Matrix; -@@ -4580,6 +4582,7 @@ add_win(xwayland_ctx_t *ctx, Window id, Window prev, unsigned long sequence) - if (!new_win) - return; - -+ new_win->seq = ++g_lastWinSeq; - new_win->type = steamcompmgr_win_type_t::XWAYLAND; - new_win->_window_types.emplace(); - -diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp -index fb8a5d281..e77665afc 100644 ---- a/src/steamcompmgr.hpp -+++ b/src/steamcompmgr.hpp -@@ -140,6 +140,7 @@ extern float focusedWindowOffsetY; - extern bool g_bFSRActive; - - extern uint32_t inputCounter; -+extern uint64_t g_lastWinSeq; - - void nudge_steamcompmgr( void ); - void take_screenshot( int flags = TAKE_SCREENSHOT_BASEPLANE_ONLY ); -diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp -index 2b0f2e4ef..3bcdb16e8 100644 ---- a/src/steamcompmgr_shared.hpp -+++ b/src/steamcompmgr_shared.hpp -@@ -90,6 +90,8 @@ struct steamcompmgr_xdg_win_t - struct steamcompmgr_win_t { - unsigned int opacity; - -+ uint64_t seq; -+ - std::shared_ptr title; - bool utf8_title; - pid_t pid; -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 49f96ed62..5547f347f 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -1367,6 +1367,7 @@ void xdg_surface_new(struct wl_listener *listener, void *data) - wlserver.xdg_wins.emplace_back(window); - } - -+ window->seq = ++g_lastWinSeq; - window->type = steamcompmgr_win_type_t::XDG; - window->_window_types.emplace(); - - -From a1d021441d843b199d4ae6cfff32380e5fb9cfb8 Mon Sep 17 00:00:00 2001 -From: Simon Ser -Date: Fri, 24 Nov 2023 13:34:37 +0100 -Subject: [PATCH 003/134] steamcompmgr: track window for each commit - ---- - src/steamcompmgr.cpp | 10 +++++++--- - src/xwayland_ctx.hpp | 1 + - 2 files changed, 8 insertions(+), 3 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 925d0dc59..5263cf92d 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -757,6 +757,7 @@ struct commit_t : public gamescope::IWaitable - bool async = false; - std::optional feedback = std::nullopt; - -+ uint64_t win_seq = 0; - struct wlr_surface *surf = nullptr; - std::vector presentation_feedbacks; - -@@ -793,7 +794,7 @@ struct commit_t : public gamescope::IWaitable - // When we get the new IWaitable stuff in there. - { - std::unique_lock< std::mutex > lock( pDoneCommits->listCommitsDoneLock ); -- pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ commitID, desired_present_time } ); -+ pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ win_seq, commitID, desired_present_time } ); - } - - if ( m_bMangoNudge ) -@@ -1366,11 +1367,12 @@ destroy_buffer( struct wl_listener *listener, void * ) - } - - static std::shared_ptr --import_commit ( 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 ) -+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 ) - { - std::shared_ptr commit = std::make_shared(); - std::unique_lock lock( wlr_buffer_map_lock ); - -+ commit->win_seq = w->seq; - commit->surf = surf; - commit->buf = buf; - commit->async = async; -@@ -6392,6 +6394,8 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) - - 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)) - break; - } -@@ -6578,7 +6582,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re - return; - } - -- std::shared_ptr newCommit = import_commit( reslistentry.surf, buf, reslistentry.async, std::move(reslistentry.feedback), std::move(reslistentry.presentation_feedbacks), reslistentry.present_id, reslistentry.desired_present_time ); -+ std::shared_ptr 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 ); - - int fence = -1; - if ( newCommit ) -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index f4704f869..c57630d65 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -35,6 +35,7 @@ struct focus_t - - struct CommitDoneEntry_t - { -+ uint64_t winSeq; - uint64_t commitID; - uint64_t desiredPresentTime; - uint64_t earliestPresentTime; - -From 3a13b35411f43397c6defb2eccaeb34d0de2036e Mon Sep 17 00:00:00 2001 -From: Simon Ser -Date: Fri, 24 Nov 2023 14:35:41 +0100 -Subject: [PATCH 004/134] Add support for vendored commit-queue-v1 - ---- - protocol/gamescope-commit-queue-v1.xml | 181 +++++++++++++++++++++++++ - protocol/meson.build | 1 + - src/steamcompmgr.cpp | 31 ++++- - src/wlserver.cpp | 148 ++++++++++++++++++++ - src/wlserver.hpp | 1 + - src/xwayland_ctx.hpp | 1 + - 6 files changed, 358 insertions(+), 5 deletions(-) - create mode 100644 protocol/gamescope-commit-queue-v1.xml - -diff --git a/protocol/gamescope-commit-queue-v1.xml b/protocol/gamescope-commit-queue-v1.xml -new file mode 100644 -index 000000000..d460e0bc1 ---- /dev/null -+++ b/protocol/gamescope-commit-queue-v1.xml -@@ -0,0 +1,181 @@ -+ -+ -+ -+ Copyright © 2023 Valve Corporation -+ -+ Permission is hereby granted, free of charge, to any person obtaining a -+ copy of this software and associated documentation files (the "Software"), -+ to deal in the Software without restriction, including without limitation -+ the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ and/or sell copies of the Software, and to permit persons to whom the -+ Software is furnished to do so, subject to the following conditions: -+ -+ The above copyright notice and this permission notice (including the next -+ paragraph) shall be included in all copies or substantial portions of the -+ Software. -+ -+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -+ DEALINGS IN THE SOFTWARE. -+ -+ -+ -+ -+ By design Wayland uses a "mailbox" style presentation model. Under -+ the mailbox model, when wl_surface.commit is called, the currently -+ pending state is intended to replace the current state immediately. -+ -+ If state is committed many times before the compositor repaints a -+ scene, each commit takes place immediately, updating the existing -+ state. When the compositor repaints the display only the most -+ recent accumulation of state is visible. This may lead to client -+ buffers being released without presentation if they were replaced -+ before being displayed. -+ -+ There are other presentation models such as FIFO (First In First -+ Out) in which state commits are explicitly queued for future -+ repaint intervals, and client buffers should not be released -+ without being displayed. -+ -+ Graphics APIs such as Vulkan aim to support these presentation -+ models, but they are not implementable on top of our mailbox model -+ without the ability to change the default surface state handling -+ behaviour. -+ -+ This interface provides a way to control the compositor's surface -+ state handling to enable presentation models other than mailbox. -+ -+ It does so by exposing control of a compositor surface state queue, -+ and specifying for each call of wl_surface.commit whether the -+ pending state should be handled in a mailbox or a FIFO fashion. -+ -+ Warning! The protocol described in this file is currently in the testing -+ phase. Backward compatible changes may be added together with the -+ corresponding interface version bump. Backward incompatible changes can -+ only be done by creating a new major version of the extension. -+ -+ -+ -+ These fatal protocol errors may be emitted in response to -+ illegal requests. -+ -+ -+ -+ -+ -+ -+ Informs the server that the client will no longer be using -+ this protocol object. Existing objects created by this object -+ are not affected. -+ -+ -+ -+ -+ -+ Establish a queue controller for a surface. -+ -+ Graphics APIs (EGL, Vulkan) will likely use this protocol -+ internally, so clients using them shouldn't directly use this -+ protocol on surfaces managed by those APIs, or a -+ queue_controller_already_exists protocol error will occur. -+ -+ -+ -+ -+ -+ -+ -+ -+ A queue controller for a surface. -+ -+ A wayland compositor may implicitly queue surface state to -+ allow it to pick the most recently ready state at repaint time, -+ or to allow surface state to contain timing information. -+ -+ The commit queue controller object allows explicit control over -+ the queue of upcoming surface state by allowing a client to attach -+ a queue drain mode to pending surface state before it calls -+ wl_surface.commit. -+ -+ -+ -+ -+ These fatal protocol errors may be emitted in response to -+ illegal requests. -+ -+ -+ -+ -+ -+ -+ This enum is used to choose how the compositor processes a queue -+ entry at output repaint time. -+ -+ -+ -+ State from this queue slot may be updated immediately (without -+ completing a repaint) if newer state is ready to display at -+ repaint time. -+ -+ -+ -+ -+ This queue slot will be the last state update for this surface -+ that the compositor will process during the repaint in which -+ it is ready for display. -+ -+ If the compositor is presenting with tearing, the surface state -+ must be made current for an iteration of the compositor's repaint -+ loop. This may result in the state being visible for a very short -+ duration, with visible artifacts, or even not visible at all for -+ surfaces that aren't full screen. -+ -+ The compositor must not cause state processing to stall indefinitely -+ for a surface that is occluded or otherwise not visible. Instead, -+ if the compositor is choosing not to present a surface for reasons -+ unrelated to state readiness, the FIFO condition must be considered -+ satisfied at the moment new state becomes ready to replace the -+ undisplayed state. -+ -+ -+ -+ -+ -+ -+ This request adds a queue drain mode to the pending surface -+ state, which will be commit by the next wl_surface.commit. -+ -+ This request tells the compositor how to process the state -+ from that commit when handling its internal state queue. -+ -+ If the drain mode is "mailbox", the compositor may continue -+ processing the next state in the queue before it repaints -+ the display. -+ -+ If the drain mode is "fifo", the compositor should ensure the -+ queue is not advanced until after this state has been current -+ for a repaint. The queue may be advance without repaint in the -+ case of off-screen or occluded surfaces. -+ -+ The default drain mode when none is specified is "mailbox". -+ -+ -+ -+ -+ -+ -+ Informs the server that the client will no longer be using -+ this protocol object. -+ -+ Surface state changes previously made by this protocol are -+ unaffected by this object's destruction. -+ -+ -+ -+ -diff --git a/protocol/meson.build b/protocol/meson.build -index df11a728b..034b0be73 100644 ---- a/protocol/meson.build -+++ b/protocol/meson.build -@@ -22,6 +22,7 @@ protocols = [ - 'gamescope-tearing-control-unstable-v1.xml', - 'gamescope-control.xml', - 'gamescope-swapchain.xml', -+ 'gamescope-commit-queue-v1.xml', - ] - - protocols_client_src = [] -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 5263cf92d..6821d2d09 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -48,6 +48,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -755,6 +756,7 @@ struct commit_t : public gamescope::IWaitable - uint64_t commitID = 0; - bool done = false; - bool async = false; -+ bool fifo = false; - std::optional feedback = std::nullopt; - - uint64_t win_seq = 0; -@@ -794,7 +796,12 @@ struct commit_t : public gamescope::IWaitable - // When we get the new IWaitable stuff in there. - { - std::unique_lock< std::mutex > lock( pDoneCommits->listCommitsDoneLock ); -- pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ win_seq, commitID, desired_present_time } ); -+ pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ -+ .winSeq = win_seq, -+ .commitID = commitID, -+ .desiredPresentTime = desired_present_time, -+ .fifo = fifo, -+ } ); - } - - if ( m_bMangoNudge ) -@@ -1367,7 +1374,7 @@ destroy_buffer( struct wl_listener *listener, void * ) - } - - static std::shared_ptr --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 ) -+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 ) - { - std::shared_ptr commit = std::make_shared(); - std::unique_lock lock( wlr_buffer_map_lock ); -@@ -1376,6 +1383,7 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff - commit->surf = surf; - commit->buf = buf; - commit->async = async; -+ commit->fifo = fifo; - commit->presentation_feedbacks = std::move(presentation_feedbacks); - if (swapchain_feedback) - commit->feedback = *swapchain_feedback; -@@ -6366,7 +6374,7 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co - } - - // TODO: Merge these two functions. --void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) -+void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) - { - std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); - -@@ -6375,11 +6383,20 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) - // commits that were not ready to be presented based on their display timing. - std::vector< CommitDoneEntry_t > commits_before_their_time; - -+ // windows in FIFO mode we got a new frame to present for this vblank -+ std::unordered_set< uint64_t > fifo_win_seqs; -+ - uint64_t now = get_time_in_nanos(); - - // very fast loop yes - for ( auto& entry : ctx->doneCommits.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; -@@ -6397,7 +6414,11 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx ) - 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; -+ } - } - } - -@@ -6582,7 +6603,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re - return; - } - -- std::shared_ptr 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 ); -+ std::shared_ptr 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 ) -@@ -7925,7 +7946,7 @@ steamcompmgr_main(int argc, char **argv) - 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()); -+ handle_done_commits_xwayland(server->ctx.get(), vblank); - - // When we have observed both a complete commit and a VBlank, we should request a new frame. - if (vblank) -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 5547f347f..dceb8727e 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -42,6 +42,7 @@ extern "C" { - #include "gamescope-control-protocol.h" - #include "gamescope-swapchain-protocol.h" - #include "gamescope-tearing-control-unstable-v1-protocol.h" -+#include "gamescope-commit-queue-v1-protocol.h" - #include "presentation-time-protocol.h" - - #include "wlserver.hpp" -@@ -86,6 +87,7 @@ static struct wl_list pending_surfaces = {0}; - - 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 enum gamescope_commit_queue_v1_queue_mode gamescope_commit_queue_v1_get_surface_mode(struct wlr_surface *surface); - - std::vector gamescope_xwayland_server_t::retrieve_commits() - { -@@ -104,10 +106,13 @@ void gamescope_xwayland_server_t::wayland_commit(struct wlr_surface *surf, struc - - auto wl_surf = get_wl_surface_info( surf ); - -+ auto queue_mode = gamescope_commit_queue_v1_get_surface_mode(surf); -+ - ResListEntry_t newEntry = { - .surf = surf, - .buf = buf, - .async = wlserver_surface_is_async(surf), -+ .fifo = queue_mode == GAMESCOPE_COMMIT_QUEUE_V1_QUEUE_MODE_FIFO, - .feedback = wlserver_surface_swapchain_feedback(surf), - .presentation_feedbacks = std::move(wl_surf->pending_presentation_feedbacks), - .present_id = wl_surf->present_id, -@@ -960,6 +965,147 @@ static void create_gamescope_tearing( void ) - } - - -+struct gamescope_commit_queue_v1 { -+ struct wl_resource *resource; -+ struct wlr_surface *surface; -+ -+ struct { -+ enum gamescope_commit_queue_v1_queue_mode mode; -+ } current, pending; -+ -+ struct wlr_addon surface_addon; -+ struct wl_listener surface_commit; -+}; -+ -+extern const struct gamescope_commit_queue_v1_interface queue_impl; -+ -+// Returns NULL if inert -+static struct gamescope_commit_queue_v1 *queue_from_resource(struct wl_resource *resource) { -+ assert(wl_resource_instance_of(resource, -+ &gamescope_commit_queue_v1_interface, &queue_impl)); -+ return (struct gamescope_commit_queue_v1 *) wl_resource_get_user_data(resource); -+} -+ -+static void resource_handle_destroy(struct wl_client *client, struct wl_resource *resource) { -+ wl_resource_destroy(resource); -+} -+ -+static void queue_destroy(struct gamescope_commit_queue_v1 *queue) { -+ if (queue == NULL) { -+ return; -+ } -+ wl_list_remove(&queue->surface_commit.link); -+ wlr_addon_finish(&queue->surface_addon); -+ wl_resource_set_user_data(queue->resource, NULL); // make inert -+ free(queue); -+} -+ -+static void surface_addon_handle_destroy(struct wlr_addon *addon) { -+ struct gamescope_commit_queue_v1 *queue = wl_container_of(addon, queue, surface_addon); -+ queue_destroy(queue); -+} -+ -+static const struct wlr_addon_interface surface_addon_impl = { -+ .name = "gamescope_commit_queue_v1", -+ .destroy = surface_addon_handle_destroy, -+}; -+ -+static void queue_handle_set_queue_mode(struct wl_client *client, -+ struct wl_resource *resource, uint32_t mode) { -+ struct gamescope_commit_queue_v1 *queue = queue_from_resource(resource); -+ -+ if (mode > GAMESCOPE_COMMIT_QUEUE_V1_QUEUE_MODE_FIFO) { -+ wl_resource_post_error(resource, GAMESCOPE_COMMIT_QUEUE_V1_ERROR_INVALID_QUEUE_MODE, -+ "Invalid queue mode"); -+ return; -+ } -+ -+ queue->pending.mode = (enum gamescope_commit_queue_v1_queue_mode) mode; -+} -+ -+const struct gamescope_commit_queue_v1_interface queue_impl = { -+ .set_queue_mode = queue_handle_set_queue_mode, -+ .destroy = resource_handle_destroy, -+}; -+ -+static void queue_handle_surface_commit(struct wl_listener *listener, void *data) { -+ struct gamescope_commit_queue_v1 *queue = wl_container_of(listener, queue, surface_commit); -+ queue->current = queue->pending; -+} -+ -+static void queue_handle_resource_destroy(struct wl_resource *resource) { -+ struct gamescope_commit_queue_v1 *queue = queue_from_resource(resource); -+ queue_destroy(queue); -+} -+ -+static void manager_handle_get_queue_controller(struct wl_client *client, -+ struct wl_resource *manager_resource, uint32_t id, -+ struct wl_resource *surface_resource) { -+ struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); -+ -+ if (wlr_addon_find(&surface->addons, NULL, &surface_addon_impl) != NULL) { -+ wl_resource_post_error(manager_resource, -+ GAMESCOPE_COMMIT_QUEUE_MANAGER_V1_ERROR_QUEUE_CONTROLLER_ALREADY_EXISTS, -+ "A gamescope_commit_queue_v1 object already exists for this surface"); -+ return; -+ } -+ -+ struct gamescope_commit_queue_v1 *queue = (struct gamescope_commit_queue_v1 *) calloc(1, sizeof(*queue)); -+ if (queue == NULL) { -+ wl_resource_post_no_memory(manager_resource); -+ return; -+ } -+ -+ queue->surface = surface; -+ -+ uint32_t version = wl_resource_get_version(manager_resource); -+ queue->resource = wl_resource_create(client, -+ &gamescope_commit_queue_v1_interface, version, id); -+ if (queue->resource == NULL) { -+ free(queue); -+ wl_resource_post_no_memory(manager_resource); -+ return; -+ } -+ wl_resource_set_implementation(queue->resource, -+ &queue_impl, queue, queue_handle_resource_destroy); -+ -+ wlr_addon_init(&queue->surface_addon, &surface->addons, NULL, &surface_addon_impl); -+ -+ queue->surface_commit.notify = queue_handle_surface_commit; -+ wl_signal_add(&surface->events.commit, &queue->surface_commit); -+} -+ -+static const struct gamescope_commit_queue_manager_v1_interface manager_impl = { -+ .destroy = resource_handle_destroy, -+ .get_queue_controller = manager_handle_get_queue_controller, -+}; -+ -+static void commit_queue_manager_bind(struct wl_client *client, void *data, -+ uint32_t version, uint32_t id) { -+ struct wl_resource *resource = wl_resource_create(client, -+ &gamescope_commit_queue_manager_v1_interface, version, id); -+ if (resource == NULL) { -+ wl_client_post_no_memory(client); -+ return; -+ } -+ wl_resource_set_implementation(resource, &manager_impl, NULL, NULL); -+} -+ -+static void commit_queue_manager_v1_create(struct wl_display *display) { -+ wl_global_create(display, &gamescope_commit_queue_manager_v1_interface, 1, NULL, commit_queue_manager_bind); -+} -+ -+static enum gamescope_commit_queue_v1_queue_mode gamescope_commit_queue_v1_get_surface_mode(struct wlr_surface *surface) { -+ struct wlr_addon *addon = -+ wlr_addon_find(&surface->addons, NULL, &surface_addon_impl); -+ if (addon == NULL) { -+ return GAMESCOPE_COMMIT_QUEUE_V1_QUEUE_MODE_MAILBOX; -+ } -+ struct gamescope_commit_queue_v1 *queue = wl_container_of(addon, queue, surface_addon); -+ return queue->current.mode; -+} -+ -+ - //////////////////////// - // presentation-time - //////////////////////// -@@ -1462,6 +1608,8 @@ bool wlserver_init( void ) { - - create_presentation_time(); - -+ commit_queue_manager_v1_create(wlserver.display); -+ - wlserver.xdg_shell = wlr_xdg_shell_create(wlserver.display, 3); - if (!wlserver.xdg_shell) - { -diff --git a/src/wlserver.hpp b/src/wlserver.hpp -index 07f36d614..d63325298 100644 ---- a/src/wlserver.hpp -+++ b/src/wlserver.hpp -@@ -38,6 +38,7 @@ 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; -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index c57630d65..52f42ef42 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -40,6 +40,7 @@ struct CommitDoneEntry_t - uint64_t desiredPresentTime; - uint64_t earliestPresentTime; - uint64_t earliestLatchTime; -+ bool fifo; - }; - - struct CommitDoneList_t - -From 183e632631eb401f8cf9cc98b92f40e0dbe7326b Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 11:02:38 +0000 -Subject: [PATCH 005/134] steamcompmgr: Avoid allocations in - handle_done_commits_xwayland - ---- - src/steamcompmgr.cpp | 10 +++++++--- - 1 file changed, 7 insertions(+), 3 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 6821d2d09..451004409 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -6381,10 +6381,14 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) - uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time; - - // commits that were not ready to be presented based on their display timing. -- std::vector< CommitDoneEntry_t > commits_before_their_time; -+ 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 -- std::unordered_set< uint64_t > fifo_win_seqs; -+ 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(); - -@@ -6422,7 +6426,7 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) - } - } - -- ctx->doneCommits.listCommitsDone = std::move( commits_before_their_time ); -+ ctx->doneCommits.listCommitsDone.swap( commits_before_their_time ); - } - - void handle_done_commits_xdg() - -From c455413bb272c3274a0fe2baa150bbefba86155c Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 18:22:26 +0000 -Subject: [PATCH 006/134] drm: Fix scanout gamma for Steam In-Home Streaming - ---- - src/drm.cpp | 39 +++++++++++++++++++++++++-------------- - 1 file changed, 25 insertions(+), 14 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 575a79841..65676ffb9 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -2382,27 +2382,38 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - 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 ( entry.layerState[i].ycbcr ) -+ if ( frameInfo->layers[i].applyColorMgmt ) - { -- liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_ENCODING", entry.layerState[i].colorEncoding ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_RANGE", entry.layerState[i].colorRange ); -- if ( drm_supports_color_mgmt( drm ) ) -+ if ( entry.layerState[i].ycbcr ) - { -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", DRM_VALVE1_TRANSFER_FUNCTION_BT709 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_ENCODING", entry.layerState[i].colorEncoding ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "COLOR_RANGE", entry.layerState[i].colorRange ); - } -- } -- else if ( frameInfo->layers[i].applyColorMgmt ) -- { -- liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_ENCODING" ); -- liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" ); -+ else -+ { -+ liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_ENCODING" ); -+ liftoff_layer_unset_property( drm->lo_layers[ i ], "COLOR_RANGE" ); -+ } -+ - if ( drm_supports_color_mgmt( drm ) ) - { - drm_valve1_transfer_function degamma_tf = colorspace_to_plane_degamma_tf( entry.layerState[i].colorspace ); - drm_valve1_transfer_function shaper_tf = colorspace_to_plane_shaper_tf( entry.layerState[i].colorspace ); -+ -+ if ( entry.layerState[i].ycbcr ) -+ { -+ // 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 = DRM_VALVE1_TRANSFER_FUNCTION_BT709; -+ shaper_tf = DRM_VALVE1_TRANSFER_FUNCTION_BT709; -+ } -+ - if (!g_bDisableDegamma) - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", degamma_tf ); - else - -From 5caef73bcf422598d62d7c3c44c498d05fd296dc Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 18:23:01 +0000 -Subject: [PATCH 007/134] rendervulkan: Fix Steam In-Home Streaming gamma on - composite - ---- - src/rendervulkan.hpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index 3e33bdce8..c60b796a1 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -113,7 +113,7 @@ inline GamescopeAppTextureColorspace VkColorSpaceToGamescopeAppTextureColorSpace - default: - case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: - // We will use image view conversions for these 8888 formats. -- if (ToSrgbVulkanFormat(format) != ToLinearVulkanFormat(format)) -+ if (ToSrgbVulkanFormat(format) != ToLinearVulkanFormat(format) || format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) - return GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; - return GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; - - -From 54d3c0bbbba7a55fd31f3db8651e8e44e236d68b Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 18:26:08 +0000 -Subject: [PATCH 008/134] shaders: Add some extra toys - ---- - src/shaders/colorimetry.h | 22 ++++++++++++++++++++++ - 1 file changed, 22 insertions(+) - -diff --git a/src/shaders/colorimetry.h b/src/shaders/colorimetry.h -index 8be73ec1d..00adebf4f 100644 ---- a/src/shaders/colorimetry.h -+++ b/src/shaders/colorimetry.h -@@ -28,6 +28,28 @@ vec4 linearToSrgb(vec4 color) { - return vec4(linearToSrgb(color.rgb), color.a); - } - -+///////////////////////////// -+// Extra Helpers -+///////////////////////////// -+ -+vec3 g24ToLinear(vec3 color) { -+ return pow(color, vec3(2.4f)); -+} -+ -+vec4 g24ToLinear(vec4 color) { -+ return vec4(g24ToLinear(color.rgb), color.a); -+} -+ -+ -+vec3 g22ToLinear(vec3 color) { -+ return pow(color, vec3(2.2f)); -+} -+ -+vec4 g22ToLinear(vec4 color) { -+ return vec4(g22ToLinear(color.rgb), color.a); -+} -+ -+ - ///////////////////////////// - // PQ Encoding Helpers - ///////////////////////////// - -From 59053562e5ef8a1421e8501fc47d9ef584ef8898 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 18:28:49 +0000 -Subject: [PATCH 009/134] waitable: Use EPOLL_CLOEXEC - ---- - src/waitable.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/waitable.h b/src/waitable.h -index 76b0620a3..3c9a7ec4d 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -87,7 +87,7 @@ namespace gamescope - { - public: - CWaiter() -- : m_nEpollFD{ epoll_create1( 0 ) } -+ : m_nEpollFD{ epoll_create1( EPOLL_CLOEXEC ) } - { - AddWaitable( &m_NudgeWaitable, EPOLLIN ); - } - -From 1acdadcc57414d96d87b2737d8ea3108d5900f79 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 18:44:47 +0000 -Subject: [PATCH 010/134] steamcompmgr: Fix some issues with image thread on - window close - ---- - src/steamcompmgr.cpp | 19 ++++++++++++++++--- - 1 file changed, 16 insertions(+), 3 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 451004409..ae040aa8f 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -711,6 +711,8 @@ struct ignore { - unsigned long sequence; - }; - -+struct commit_t; -+ - gamescope::CAsyncWaiter g_ImageWaiter{ "gamescope_img" }; - - struct commit_t : public gamescope::IWaitable -@@ -722,6 +724,8 @@ struct commit_t : public gamescope::IWaitable - } - ~commit_t() - { -+ CloseFence(); -+ - if ( fb_id != 0 ) - { - drm_unlock_fbid( &g_DRM, fb_id ); -@@ -778,9 +782,7 @@ struct commit_t : public gamescope::IWaitable - { - gpuvis_trace_end_ctx_printf( commitID, "wait fence" ); - -- g_ImageWaiter.RemoveWaitable( this ); -- close( m_nCommitFence ); -- m_nCommitFence = -1; -+ CloseFence(); - - uint64_t frametime; - if ( m_bMangoNudge ) -@@ -809,6 +811,17 @@ struct commit_t : public gamescope::IWaitable - - nudge_steamcompmgr(); - } -+ -+ void CloseFence() -+ { -+ if ( m_nCommitFence < 0 ) -+ return; -+ -+ g_ImageWaiter.RemoveWaitable( this ); -+ close( m_nCommitFence ); -+ m_nCommitFence = -1; -+ } -+ - int m_nCommitFence = -1; - bool m_bMangoNudge = false; - CommitDoneList_t *pDoneCommits = nullptr; // I hate this - -From a2a91e3a409b6d1f0400f63c81af1c4f887b7b46 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 19:42:31 +0000 -Subject: [PATCH 011/134] waitable: Add lazy GC for async waitables to avoid - bubble for free-ing. - -I have never ever hit this case in any testing, but it's technically possible, so handle it. ---- - src/steamcompmgr.cpp | 63 ++++++++++++++++++++++++++++++++++---------- - src/waitable.h | 28 +++++++++++++++++++- - 2 files changed, 76 insertions(+), 15 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index ae040aa8f..f95845af9 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -713,7 +713,7 @@ struct ignore { - - struct commit_t; - --gamescope::CAsyncWaiter g_ImageWaiter{ "gamescope_img" }; -+gamescope::CAsyncWaiter> g_ImageWaiter{ "gamescope_img" }; - - struct commit_t : public gamescope::IWaitable - { -@@ -724,7 +724,11 @@ struct commit_t : public gamescope::IWaitable - } - ~commit_t() - { -- CloseFence(); -+ { -+ std::unique_lock lock( m_WaitableCommitStateMutex ); -+ g_ImageWaiter.RemoveWaitable( this ); -+ CloseFenceInternal(); -+ } - - if ( fb_id != 0 ) - { -@@ -782,7 +786,14 @@ struct commit_t : public gamescope::IWaitable - { - gpuvis_trace_end_ctx_printf( commitID, "wait fence" ); - -- CloseFence(); -+ { -+ std::unique_lock lock( m_WaitableCommitStateMutex ); -+ if ( m_nCommitFence < 0 ) -+ return; -+ -+ g_ImageWaiter.RemoveWaitable( this ); -+ CloseFenceInternal(); -+ } - - uint64_t frametime; - if ( m_bMangoNudge ) -@@ -797,8 +808,8 @@ struct commit_t : public gamescope::IWaitable - // Instead of looping over all the windows like before. - // When we get the new IWaitable stuff in there. - { -- std::unique_lock< std::mutex > lock( pDoneCommits->listCommitsDoneLock ); -- pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ -+ 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, -@@ -812,21 +823,42 @@ struct commit_t : public gamescope::IWaitable - nudge_steamcompmgr(); - } - -- void CloseFence() -+ void CloseFenceInternal() - { - if ( m_nCommitFence < 0 ) - return; - -- g_ImageWaiter.RemoveWaitable( this ); - close( m_nCommitFence ); - m_nCommitFence = -1; - } - -+ void SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ) -+ { -+ std::unique_lock lock( m_WaitableCommitStateMutex ); -+ CloseFenceInternal(); -+ -+ m_nCommitFence = nFence; -+ m_bMangoNudge = bMangoNudge; -+ m_pDoneCommits = pDoneCommits; -+ } -+ -+ std::mutex m_WaitableCommitStateMutex; - int m_nCommitFence = -1; - bool m_bMangoNudge = false; -- CommitDoneList_t *pDoneCommits = nullptr; // I hate this -+ CommitDoneList_t *m_pDoneCommits = nullptr; // I hate this - }; - -+static inline void GarbageCollectWaitableCommit( std::shared_ptr &commit ) -+{ -+ std::unique_lock lock( commit->m_WaitableCommitStateMutex ); -+ -+ if ( commit->m_nCommitFence >= 0 ) -+ { -+ g_ImageWaiter.GCWaitable( commit, commit.get() ); -+ commit->CloseFenceInternal(); -+ } -+} -+ - static std::vector pollfds; - - #define MWM_HINTS_FUNCTIONS 1 -@@ -4843,6 +4875,9 @@ finish_destroy_win(xwayland_ctx_t *ctx, Window id, bool gone) - - if (gone) - { -+ // Manually GC this here to avoid bubbles on RemoveWaitable -+ for ( auto& commit : w->commit_queue ) -+ GarbageCollectWaitableCommit( commit ); - // release all commits now we are closed. - w->commit_queue.clear(); - } -@@ -6183,6 +6218,8 @@ error(Display *dpy, XErrorEvent *ev) - [[noreturn]] static void - steamcompmgr_exit(void) - { -+ g_ImageWaiter.Shutdown(); -+ - // Clean up any commits. - { - gamescope_xwayland_server_t *server = NULL; -@@ -6196,8 +6233,6 @@ steamcompmgr_exit(void) - g_HeldCommits[ HELD_COMMIT_BASE ] = nullptr; - g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; - -- g_ImageWaiter.Shutdown(); -- - if ( statsThreadRun == true ) - { - statsThreadRun = false; -@@ -6377,6 +6412,9 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co - if ( j > 0 ) - { - // we can release all commits prior to done ones -+ // GC to be safe -+ for ( auto it = w->commit_queue.begin(); it != w->commit_queue.begin() + j; it++ ) -+ GarbageCollectWaitableCommit( *it ); - w->commit_queue.erase( w->commit_queue.begin(), w->commit_queue.begin() + j ); - } - w->receivedDoneCommit = true; -@@ -6641,10 +6679,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re - - gpuvis_trace_printf( "pushing wait for commit %lu win %lx", newCommit->commitID, w->type == steamcompmgr_win_type_t::XWAYLAND ? w->xwayland().id : 0 ); - { -- newCommit->m_nCommitFence = fence; -- newCommit->m_bMangoNudge = mango_nudge; -- newCommit->pDoneCommits = doneCommits; -- -+ newCommit->SetFence( fence, mango_nudge, doneCommits ); - g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN ); - } - -diff --git a/src/waitable.h b/src/waitable.h -index 3c9a7ec4d..6b4b20cf8 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -178,13 +178,14 @@ namespace gamescope - int m_nEpollFD = -1; - }; - -- template -+ template - class CAsyncWaiter : public CWaiter - { - public: - CAsyncWaiter( const char *pszThreadName ) - : m_Thread{ [cWaiter = this, cName = pszThreadName](){ cWaiter->WaiterThreadFunc(cName); } } - { -+ m_WaitableGarbageCollector.reserve( 32 ); - } - - ~CAsyncWaiter() -@@ -198,6 +199,19 @@ namespace gamescope - - if ( m_Thread.joinable() ) - m_Thread.join(); -+ -+ std::unique_lock lock( m_WaitableGCMutex ); -+ m_WaitableGarbageCollector.clear(); -+ } -+ -+ void GCWaitable( GCWaitableType GCWaitable, IWaitable *pWaitable ) -+ { -+ std::unique_lock lock( m_WaitableGCMutex ); -+ -+ m_WaitableGarbageCollector.emplace_back( std::move( GCWaitable ) ); -+ -+ this->RemoveWaitable( pWaitable ); -+ this->Nudge(); - } - - void WaiterThreadFunc( const char *pszThreadName ) -@@ -205,10 +219,22 @@ namespace gamescope - pthread_setname_np( pthread_self(), pszThreadName ); - - while ( this->IsRunning() ) -+ { - CWaiter::PollEvents(); -+ -+ std::unique_lock lock( m_WaitableGCMutex ); -+ m_WaitableGarbageCollector.clear(); -+ } - } - private: - std::thread m_Thread; -+ -+ // Avoids bubble in the waiter thread func where lifetimes -+ // of objects (eg. shared_ptr) could be too short. -+ // Eg. RemoveWaitable but still processing events, or about -+ // to start processing events. -+ std::mutex m_WaitableGCMutex; -+ std::vector m_WaitableGarbageCollector; - }; - - - -From 036866c7d1dee0e932f6bd5443bf527252a3aa78 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 20:16:55 +0000 -Subject: [PATCH 012/134] wlserver: Fix crash on some game exits - ---- - src/wlserver.cpp | 16 ++++++++++++++++ - 1 file changed, 16 insertions(+) - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index dceb8727e..473156803 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -1151,6 +1151,9 @@ void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std: - { - wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface); - -+ if ( !wl_surface_info ) -+ return; -+ - uint32_t flags = 0; - - // Don't know when we want to send this. -@@ -1194,6 +1197,9 @@ void wlserver_presentation_feedback_discard( struct wlr_surface *surface, std::v - { - wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface); - -+ if ( !wl_surface_info ) -+ return; -+ - wl_surface_info->sequence++; - - for (auto& feedback : presentation_feedbacks) -@@ -1209,6 +1215,9 @@ void wlserver_presentation_feedback_discard( struct wlr_surface *surface, std::v - 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; -+ - gamescope_swapchain_send_past_present_timing( - wl_info->gamescope_swapchain, - present_id, -@@ -1225,6 +1234,9 @@ void wlserver_past_present_timing( struct wlr_surface *surface, uint32_t present - 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; -+ - gamescope_swapchain_send_refresh_cycle( - wl_info->gamescope_swapchain, - refresh_cycle >> 32, -@@ -1917,11 +1929,15 @@ bool wlserver_surface_is_async( struct wlr_surface *surf ) - return wl_surf->presentation_hint != 0; - } - -+static std::shared_ptr s_NullFeedback; -+ - const std::shared_ptr& wlserver_surface_swapchain_feedback( struct wlr_surface *surf ) - { - assert( wlserver_is_lock_held() ); - - auto wl_surf = get_wl_surface_info( surf ); -+ if ( !wl_surf ) -+ return s_NullFeedback; - - return wl_surf->swapchain_feedback; - } - -From a5a4e364dfb9d079e40543b05db0d75a82b440a2 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 20:33:54 +0000 -Subject: [PATCH 013/134] steamcompmgr: Fix fifo queue fps limit - ---- - src/steamcompmgr.cpp | 29 +++++++++++++++++++++-------- - 1 file changed, 21 insertions(+), 8 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index f95845af9..c0f749e69 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -5330,14 +5330,13 @@ steamcompmgr_flush_frame_done( steamcompmgr_win_t *w ) - } - } - --static void --steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx ) -+static bool steamcompmgr_should_vblank_window( bool bShouldLimitFPS, uint64_t vblank_idx ) - { - bool bSendCallback = true; - - int nRefresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; - int nTargetFPS = g_nSteamCompMgrTargetFPS; -- if ( g_nSteamCompMgrTargetFPS && steamcompmgr_window_should_limit_fps( w ) && nRefresh > nTargetFPS ) -+ if ( g_nSteamCompMgrTargetFPS && bShouldLimitFPS && nRefresh > nTargetFPS ) - { - int nVblankDivisor = nRefresh / nTargetFPS; - -@@ -5345,7 +5344,18 @@ steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx ) - bSendCallback = false; - } - -- if ( bSendCallback ) -+ return bSendCallback; -+} -+ -+static bool steamcompmgr_should_vblank_window( steamcompmgr_win_t *w, uint64_t vblank_idx ) -+{ -+ return steamcompmgr_should_vblank_window( steamcompmgr_window_should_limit_fps( w ), vblank_idx ); -+} -+ -+static void -+steamcompmgr_latch_frame_done( steamcompmgr_win_t *w, uint64_t vblank_idx ) -+{ -+ if ( steamcompmgr_should_vblank_window( w, vblank_idx ) ) - { - w->unlockedForFrameCallback = true; - } -@@ -6425,7 +6435,7 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co - } - - // TODO: Merge these two functions. --void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) -+void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vblank_idx ) - { - std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); - -@@ -6443,6 +6453,8 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank ) - - uint64_t now = get_time_in_nanos(); - -+ vblank = vblank && steamcompmgr_should_vblank_window( true, vblank_idx ); -+ - // very fast loop yes - for ( auto& entry : ctx->doneCommits.listCommitsDone ) - { -@@ -7973,9 +7985,9 @@ steamcompmgr_main(int argc, char **argv) - // 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 == true ) - { -- static int vblank_idx = 0; - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) -@@ -7991,14 +8003,13 @@ steamcompmgr_main(int argc, char **argv) - steamcompmgr_latch_frame_done( xdg_win.get(), vblank_idx ); - } - } -- vblank_idx++; - } - - { - 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); -+ 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) -@@ -8013,6 +8024,8 @@ steamcompmgr_main(int argc, char **argv) - - if ( vblank ) - { -+ vblank_idx++; -+ - int nRealRefresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; - int nTargetFPS = g_nSteamCompMgrTargetFPS ? g_nSteamCompMgrTargetFPS : nRealRefresh; - nTargetFPS = std::min( nTargetFPS, nRealRefresh ); - -From ce089200e49526456d0855c7e5c518ebf66ccf9b Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 29 Nov 2023 22:09:07 +0000 -Subject: [PATCH 014/134] drm: Fix vrr_capable and other metadata not being - updated - -Fixes Deck Dock VRR capable getting passed to Steam ---- - src/drm.cpp | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 65676ffb9..39c641b7e 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -987,7 +987,10 @@ static bool refresh_state( drm_t *drm ) - parse_edid(drm, conn); - - if ( conn->name != nullptr ) -- continue; -+ { -+ free(conn->name); -+ conn->name = nullptr; -+ } - - const char *type_str = drmModeGetConnectorTypeName(conn->connector->connector_type); - if (!type_str) - -From 639260814b584a2e38560fbe1845a97322672bff Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 27 Nov 2023 21:14:35 -0800 -Subject: [PATCH 015/134] mangoapp: Use visible frames for reporting to - mangohud if FIFO - ---- - src/drm.cpp | 3 +++ - src/mangoapp.cpp | 23 +++++++++++++++++++++++ - src/rendervulkan.cpp | 5 ++++- - src/steamcompmgr.cpp | 15 ++++++++++++++- - 4 files changed, 44 insertions(+), 2 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 39c641b7e..3c2999401 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -272,6 +272,7 @@ static void drm_unlock_fb_internal( struct drm_t *drm, struct fb *fb ); - - std::atomic g_nCompletedPageFlipCount = { 0u }; - -+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) - { - uint64_t flipcount = (uint64_t)data; -@@ -333,6 +334,8 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi - g_DRM.fbids_queued.clear(); - - g_DRM.flip_lock.unlock(); -+ -+ mangoapp_output_update( vblanktime ); - } - - void flip_handler_thread_run(void) -diff --git a/src/mangoapp.cpp b/src/mangoapp.cpp -index e751e5ff2..2d0c59f72 100644 ---- a/src/mangoapp.cpp -+++ b/src/mangoapp.cpp -@@ -52,3 +52,26 @@ void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uin - mangoapp_msg_v1.outputHeight = g_nOutputHeight; - 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; -+void mangoapp_output_update( uint64_t vblanktime ) -+{ -+ 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; -+ mangoapp_update( frametime, uint64_t(~0ull), uint64_t(~0ull) ); -+ } -+} -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index a1805edb6..f18b91fe3 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -2581,6 +2581,7 @@ bool acquire_next_image( void ) - 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; -@@ -2598,7 +2599,9 @@ static void present_wait_thread_func( void ) - if (present_wait_id != 0) - { - g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu ); -- vblank_mark_possible_vblank( get_time_in_nanos() ); -+ uint64_t vblanktime = get_time_in_nanos(); -+ vblank_mark_possible_vblank( vblanktime ); -+ mangoapp_output_update( vblanktime ); - } - } - } -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index c0f749e69..0df145ea2 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -765,6 +765,7 @@ struct commit_t : public gamescope::IWaitable - bool done = false; - bool async = false; - bool fifo = false; -+ bool is_steam = false; - std::optional feedback = std::nullopt; - - uint64_t win_seq = 0; -@@ -818,11 +819,16 @@ struct commit_t : public gamescope::IWaitable - } - - if ( m_bMangoNudge ) -- mangoapp_update( frametime, uint64_t(~0ull), uint64_t(~0ull) ); -+ mangoapp_update( IsPerfOverlayFIFO() ? uint64_t(~0ull) : frametime, frametime, uint64_t(~0ull) ); - - nudge_steamcompmgr(); - } - -+ bool IsPerfOverlayFIFO() -+ { -+ return fifo || is_steam; -+ } -+ - void CloseFenceInternal() - { - if ( m_nCommitFence < 0 ) -@@ -949,6 +955,9 @@ std::mutex g_SteamCompMgrXWaylandServerMutex; - - VBlankTimeInfo_t g_SteamCompMgrVBlankTime = {}; - -+uint64_t g_uCurrentBasePlaneCommitID = 0; -+bool g_bCurrentBasePlaneIsFifo = false; -+ - static int g_nSteamCompMgrTargetFPS = 0; - static uint64_t g_uDynamicRefreshEqualityTime = 0; - static int g_nDynamicRefreshRate[DRM_SCREEN_TYPE_COUNT] = { 0, 0 }; -@@ -1429,6 +1438,7 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff - commit->buf = buf; - commit->async = async; - commit->fifo = fifo; -+ commit->is_steam = window_is_steam( w ); - commit->presentation_feedbacks = std::move(presentation_feedbacks); - if (swapchain_feedback) - commit->feedback = *swapchain_feedback; -@@ -2392,6 +2402,9 @@ paint_window(steamcompmgr_win_t *w, steamcompmgr_win_t *scaleW, struct FrameInfo - g_CachedPlanes[ HELD_COMMIT_BASE ] = basePlane; - if ( !(flags & PaintWindowFlag::FadeTarget) ) - g_CachedPlanes[ HELD_COMMIT_FADE ] = basePlane; -+ -+ g_uCurrentBasePlaneCommitID = lastCommit->commitID; -+ g_bCurrentBasePlaneIsFifo = lastCommit->IsPerfOverlayFIFO(); - } - } - - -From ce34c432172f16ff52230debd49227e8209cb6e4 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Fri, 1 Dec 2023 10:37:47 +0000 -Subject: [PATCH 016/134] drm: Reset CTM on layers with applyColorMgmt = false - ---- - src/drm.cpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 3c2999401..8b62b4c61 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -2448,6 +2448,7 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", 0 ); - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); - } - } - - -From 38bfd04be4e24f7a28517cabbbce1ea5095bc934 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Fri, 1 Dec 2023 21:03:44 +0000 -Subject: [PATCH 017/134] steamcompmgr: Reduce alloc overhead of new xwayland - resources - ---- - src/steamcompmgr.cpp | 4 ++-- - src/wlserver.cpp | 10 +++++++--- - src/wlserver.hpp | 2 +- - 3 files changed, 10 insertions(+), 6 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 0df145ea2..f5c51a5fa 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -6705,7 +6705,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re - 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 ); -- g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN ); -+ g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN | EPOLLHUP ); - } - - w->commit_queue.push_back( std::move(newCommit) ); -@@ -6717,7 +6717,7 @@ 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(); -+ std::vector& tmp_queue = ctx->xwayland_server->retrieve_commits(); - - for ( uint32_t i = 0; i < tmp_queue.size(); i++ ) - { -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 473156803..44f2b6724 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -89,12 +89,15 @@ static void wlserver_x11_surface_info_set_wlr( struct wlserver_x11_surface_info - wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf); - static enum gamescope_commit_queue_v1_queue_mode gamescope_commit_queue_v1_get_surface_mode(struct wlr_surface *surface); - --std::vector gamescope_xwayland_server_t::retrieve_commits() -+std::vector& gamescope_xwayland_server_t::retrieve_commits() - { -- std::vector commits; -+ static std::vector commits; -+ commits.clear(); -+ commits.reserve(16); -+ - { - std::lock_guard lock( wayland_commit_lock ); -- commits = std::move(wayland_commit_queue); -+ commits.swap(wayland_commit_queue); - } - return commits; - } -@@ -121,6 +124,7 @@ void gamescope_xwayland_server_t::wayland_commit(struct wlr_surface *surf, struc - wl_surf->present_id = std::nullopt; - wl_surf->desired_present_time = 0; - wl_surf->pending_presentation_feedbacks.clear(); -+ - wayland_commit_queue.push_back( newEntry ); - } - -diff --git a/src/wlserver.hpp b/src/wlserver.hpp -index d63325298..852a5a15d 100644 ---- a/src/wlserver.hpp -+++ b/src/wlserver.hpp -@@ -67,7 +67,7 @@ class gamescope_xwayland_server_t - - 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 *resource, struct wlr_surface *surface, uint32_t x11_window ); - void destroy_content_override( struct wlserver_x11_surface_info *x11_surface, struct wlr_surface *surf); - -From 7fa6ae8bd538c320c8227273fad489840f3a39ba Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Fri, 1 Dec 2023 21:15:32 +0000 -Subject: [PATCH 018/134] steamcompmgr: Remove dmabuf waitable on HUP - -Otherwise we can get in a bad situation where we start spinning as epoll returns instantly. ---- - src/steamcompmgr.cpp | 25 +++++++++++++++++-------- - src/waitable.h | 8 +++++--- - 2 files changed, 22 insertions(+), 11 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index f5c51a5fa..7adf9842d 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -726,7 +726,6 @@ struct commit_t : public gamescope::IWaitable - { - { - std::unique_lock lock( m_WaitableCommitStateMutex ); -- g_ImageWaiter.RemoveWaitable( this ); - CloseFenceInternal(); - } - -@@ -789,11 +788,8 @@ struct commit_t : public gamescope::IWaitable - - { - std::unique_lock lock( m_WaitableCommitStateMutex ); -- if ( m_nCommitFence < 0 ) -+ if ( !CloseFenceInternal() ) - return; -- -- g_ImageWaiter.RemoveWaitable( this ); -- CloseFenceInternal(); - } - - uint64_t frametime; -@@ -824,18 +820,27 @@ struct commit_t : public gamescope::IWaitable - nudge_steamcompmgr(); - } - -+ void OnPollHangUp() final -+ { -+ std::unique_lock lock( m_WaitableCommitStateMutex ); -+ CloseFenceInternal(); -+ } -+ - bool IsPerfOverlayFIFO() - { - return fifo || is_steam; - } - -- void CloseFenceInternal() -+ // Returns true if we had a fence that was closed. -+ bool CloseFenceInternal() - { - if ( m_nCommitFence < 0 ) -- return; -+ return false; - -+ // Will automatically remove from epoll! - close( m_nCommitFence ); - m_nCommitFence = -1; -+ return true; - } - - void SetFence( int nFence, bool bMangoNudge, CommitDoneList_t *pDoneCommits ) -@@ -858,6 +863,10 @@ static inline void GarbageCollectWaitableCommit( std::shared_ptr &comm - { - std::unique_lock lock( commit->m_WaitableCommitStateMutex ); - -+ // This case is basically never ever hit. -+ // But we should handle it just in case. -+ // I have not seen it ever trigger even in extensive -+ // stress testing. - if ( commit->m_nCommitFence >= 0 ) - { - g_ImageWaiter.GCWaitable( commit, commit.get() ); -@@ -4888,7 +4897,7 @@ finish_destroy_win(xwayland_ctx_t *ctx, Window id, bool gone) - - if (gone) - { -- // Manually GC this here to avoid bubbles on RemoveWaitable -+ // Manually GC this here to avoid bubbles on close/RemoveWaitable - for ( auto& commit : w->commit_queue ) - GarbageCollectWaitableCommit( commit ); - // release all commits now we are closed. -diff --git a/src/waitable.h b/src/waitable.h -index 6b4b20cf8..29d5050fe 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -21,6 +21,8 @@ namespace gamescope - virtual void OnPollOut() {} - virtual void OnPollHangUp() {} - -+ virtual bool ShouldCloseOnHangUp() const { return true; } -+ - void HandleEvents( uint32_t nEvents ) - { - if ( nEvents & EPOLLIN ) -@@ -89,7 +91,7 @@ namespace gamescope - CWaiter() - : m_nEpollFD{ epoll_create1( EPOLL_CLOEXEC ) } - { -- AddWaitable( &m_NudgeWaitable, EPOLLIN ); -+ AddWaitable( &m_NudgeWaitable, EPOLLIN | EPOLLHUP ); - } - - ~CWaiter() -@@ -141,7 +143,7 @@ namespace gamescope - { - epoll_event events[MaxEvents]; - -- int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, -1); -+ int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, -1 ); - - if ( !m_bRunning ) - return; -@@ -156,7 +158,7 @@ namespace gamescope - { - epoll_event &event = events[i]; - -- IWaitable *pWaitable = reinterpret_cast(event.data.ptr); -+ IWaitable *pWaitable = reinterpret_cast( event.data.ptr ); - pWaitable->HandleEvents( event.events ); - } - } - -From 9a53b6eb37817ef403c89c104bcb73e617799114 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Fri, 1 Dec 2023 21:16:20 +0000 -Subject: [PATCH 019/134] waitable: Remove unused func - ---- - src/waitable.h | 2 -- - 1 file changed, 2 deletions(-) - -diff --git a/src/waitable.h b/src/waitable.h -index 29d5050fe..8748f861c 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -21,8 +21,6 @@ namespace gamescope - virtual void OnPollOut() {} - virtual void OnPollHangUp() {} - -- virtual bool ShouldCloseOnHangUp() const { return true; } -- - void HandleEvents( uint32_t nEvents ) - { - if ( nEvents & EPOLLIN ) - -From 08f01b9dec31843364592800494d5d871312c15a Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Fri, 1 Dec 2023 21:57:25 +0000 -Subject: [PATCH 020/134] steamcompmgr: Workaround kernel NULL pointer bug with - epoll + dmabuf + close - -https://lists.freedesktop.org/archives/dri-devel/2023-December/433308.html ---- - src/steamcompmgr.cpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 7adf9842d..dd89310e4 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -838,6 +838,7 @@ struct commit_t : public gamescope::IWaitable - return false; - - // Will automatically remove from epoll! -+ g_ImageWaiter.RemoveWaitable( this ); - close( m_nCommitFence ); - m_nCommitFence = -1; - return true; - -From baf211f5d11dbe84d9df8687055e7f51fc1de626 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 3 Dec 2023 07:38:03 +0000 -Subject: [PATCH 021/134] drm: Handle rotated screens properly in - drm_get_default_refresh - -Fixes getting stuck in 60Hz mode after sleep ---- - src/drm.cpp | 12 ++++++++++-- - 1 file changed, 10 insertions(+), 2 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 8b62b4c61..c975bfd8d 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -3045,12 +3045,12 @@ 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->connector || !drm->connector->connector) - return false; - -@@ -3108,8 +3108,16 @@ int drm_get_default_refresh(struct drm_t *drm) - - if ( drm->connector && drm->connector->connector ) - { -+ int width = g_nOutputWidth; -+ int height = g_nOutputHeight; -+ if ( g_bRotated ) { -+ int tmp = width; -+ width = height; -+ height = tmp; -+ } -+ - drmModeConnector *connector = drm->connector->connector; -- const drmModeModeInfo *mode = find_mode( connector, g_nOutputWidth, g_nOutputHeight, 0); -+ const drmModeModeInfo *mode = find_mode( connector, width, height, 0); - if ( mode ) - return mode->vrefresh; - } - -From b5e14ba6c3f778f3e6192b59b4f556bfcc60280e Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 3 Dec 2023 07:48:06 +0000 -Subject: [PATCH 022/134] steamcompmgr: Handle external overlays better for - steamcompmgr_user_has_any_game_open - ---- - src/steamcompmgr.cpp | 12 ++++++------ - 1 file changed, 6 insertions(+), 6 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index dd89310e4..d9c4f6bb4 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -1061,6 +1061,11 @@ window_is_steam( steamcompmgr_win_t *w ) - - bool g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive = false; - -+bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w ) -+{ -+ return w && !window_is_steam( w ) && !w->isOverlay && !w->isExternalOverlay; -+} -+ - static bool - steamcompmgr_user_has_any_game_open() - { -@@ -1070,18 +1075,13 @@ steamcompmgr_user_has_any_game_open() - if (!server->ctx) - continue; - -- if (server->ctx->focus.focusWindow && !window_is_steam(server->ctx->focus.focusWindow)) -+ if (steamcompmgr_window_should_limit_fps( server->ctx->focus.focusWindow )) - return true; - } - - return false; - } - --bool steamcompmgr_window_should_limit_fps( steamcompmgr_win_t *w ) --{ -- return w && !window_is_steam( w ) && !w->isOverlay && !w->isExternalOverlay; --} -- - bool steamcompmgr_window_should_refresh_switch( steamcompmgr_win_t *w ) - { - if ( g_bChangeDynamicRefreshBasedOnGameOpenRatherThanActive ) - -From 6935dfe947334a41c3786eba1dbb16d3bf5c5a9c Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 4 Dec 2023 06:17:17 +0000 -Subject: [PATCH 023/134] waitable: Fix draining CNudgeWaitable - ---- - src/waitable.h | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/src/waitable.h b/src/waitable.h -index 8748f861c..1fe78aff2 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -72,6 +72,11 @@ namespace gamescope - } - } - -+ void OnPollIn() final -+ { -+ Drain(); -+ } -+ - bool Nudge() - { - return write( m_nFDs[1], "\n", 1 ) >= 0; - -From 936d86ed4dd09ce1c13170964c12e4700c48e861 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 4 Dec 2023 06:38:15 +0000 -Subject: [PATCH 024/134] waitable: Set running to false before nudge - -Fixes bubble where we can stay open ---- - src/waitable.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/waitable.h b/src/waitable.h -index 1fe78aff2..292312ec9 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -107,8 +107,8 @@ namespace gamescope - if ( !m_bRunning ) - return; - -- Nudge(); - m_bRunning = false; -+ Nudge(); - - if ( m_nEpollFD >= 0 ) - { - -From 3dc93b70984d89c2953b1d1194a7bafbd632440f Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 5 Dec 2023 21:03:36 +0000 -Subject: [PATCH 025/134] steamcompmgr: Add support for cursor resize-based - scaling - -X11/XCursor is not good at dynamically changing the requested cursor theme size. Always request the largest and downsample it ourselves. ---- - src/meson.build | 3 +- - src/steamcompmgr.cpp | 105 ++++++++++++++++++++++++++++++------------- - src/steamcompmgr.hpp | 2 + - 3 files changed, 79 insertions(+), 31 deletions(-) - -diff --git a/src/meson.build b/src/meson.build -index d3dec0d2f..5385dfb8b 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -1,5 +1,6 @@ - dep_xdamage = dependency('xdamage') - dep_xcomposite = dependency('xcomposite') -+dep_xcursor = dependency('xcursor') - dep_xrender = dependency('xrender') - dep_xext = dependency('xext') - dep_xfixes = dependency('xfixes') -@@ -134,7 +135,7 @@ endif - dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, - xkbcommon, thread_dep, sdl_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, -+ stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, - ], - install: true, - ) -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index d9c4f6bb4..8345bfa1b 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -32,6 +32,7 @@ - #include "xwayland_ctx.hpp" - #include - #include -+#include - #include - #include - #include -@@ -87,6 +88,8 @@ - #include "log.hpp" - #include "defer.hpp" - -+static const int g_nBaseCursorScale = 36; -+ - #if HAVE_PIPEWIRE - #include "pipewire.hpp" - #endif -@@ -99,6 +102,7 @@ - #define STB_IMAGE_WRITE_IMPLEMENTATION - #include - #include -+#include - - #define GPUVIS_TRACE_IMPLEMENTATION - #include "gpuvis_trace_utils.h" -@@ -1825,29 +1829,11 @@ bool MouseCursor::setCursorImage(char *data, int w, int h, int hx, int hy) - - bool MouseCursor::setCursorImageByName(const char *name) - { -- int screen = DefaultScreen(m_ctx->dpy); -- -- XColor fg; -- fg.pixel = WhitePixel(m_ctx->dpy, screen); -- XQueryColor(m_ctx->dpy, DefaultColormap(m_ctx->dpy, screen), &fg); -- -- XColor bg; -- bg.pixel = BlackPixel(m_ctx->dpy, screen); -- XQueryColor(m_ctx->dpy, DefaultColormap(m_ctx->dpy, screen), &bg); -- - int index = XmuCursorNameToIndex(name); - if (index < 0) - return false; - -- Font font = XLoadFont(m_ctx->dpy, "cursor"); -- if (!font) -- return false; -- defer( XUnloadFont(m_ctx->dpy, font) ); -- -- Cursor cursor = XCreateGlyphCursor(m_ctx->dpy, font, font, index, index + 1, &fg, &bg); -- if ( !cursor ) -- return false; -- defer( XFreeCursor(m_ctx->dpy, cursor) ); -+ Cursor cursor = XcursorShapeLoadCursor( m_ctx->dpy, index ); - - XDefineCursor(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), cursor); - XFlush(m_ctx->dpy); -@@ -1988,6 +1974,9 @@ bool MouseCursor::getTexture() - m_hotspotX = image->xhot; - m_hotspotY = image->yhot; - -+ int nDesiredWidth, nDesiredHeight; -+ GetDesiredSize( nDesiredWidth, nDesiredHeight ); -+ - uint32_t surfaceWidth; - uint32_t surfaceHeight; - if ( BIsNested() == false && alwaysComposite == false ) -@@ -1997,8 +1986,8 @@ bool MouseCursor::getTexture() - } - else - { -- surfaceWidth = image->width; -- surfaceHeight = image->height; -+ surfaceWidth = nDesiredWidth; -+ surfaceHeight = nDesiredHeight; - } - - m_texture = nullptr; -@@ -2008,20 +1997,59 @@ bool MouseCursor::getTexture() - - std::shared_ptr> cursorBuffer = nullptr; - -+ int nContentWidth = image->width; -+ int nContentHeight = image->height; -+ - if (image->width && image->height) - { -- cursorBuffer = std::make_shared>(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]; -+ 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::make_shared>(surfaceWidth * surfaceHeight); -+ for (int i = 0; i < nDesiredHeight; i++) { -+ for (int j = 0; j < nDesiredWidth; j++) { -+ (*cursorBuffer)[i * surfaceWidth + j] = resizeBuffer[i * nDesiredWidth + j]; -+ -+ if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { -+ bNoCursor = false; -+ } -+ } -+ } - -- if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { -- bNoCursor = false; -+ m_hotspotX = ( m_hotspotX * nDesiredWidth ) / image->width; -+ m_hotspotY = ( m_hotspotY * nDesiredHeight ) / image->height; -+ -+ nContentWidth = nDesiredWidth; -+ nContentHeight = nDesiredHeight; -+ } -+ else -+ { -+ cursorBuffer = std::make_shared>(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]; -+ -+ if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { -+ bNoCursor = false; -+ } - } - } - } - } - -+ - if (bNoCursor) - cursorBuffer = nullptr; - -@@ -2049,14 +2077,27 @@ bool MouseCursor::getTexture() - // TODO: choose format & modifiers from cursor plane - } - -- m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, image->width, image->height, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer->data()); -- sdlwindow_cursor(std::move(cursorBuffer), image->width, image->height, image->xhot, image->yhot); -+ m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer->data()); -+ sdlwindow_cursor(std::move(cursorBuffer), nDesiredWidth, nDesiredHeight, image->xhot, image->yhot); - 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 ); -+ } -+ -+ nWidth = nSize; -+ nHeight = nSize; -+} -+ - void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, struct FrameInfo_t *frameInfo) - { - if ( m_hideForMovement || m_imageEmpty ) { -@@ -2085,7 +2126,9 @@ void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, str - float cursor_scale = 1.0f; - if ( g_nCursorScaleHeight > 0 ) - { -- cursor_scale = floor(currentOutputHeight / (float)g_nCursorScaleHeight); -+ int nDesiredWidth, nDesiredHeight; -+ GetDesiredSize( nDesiredWidth, nDesiredHeight ); -+ cursor_scale = nDesiredHeight / (float)m_texture->contentHeight(); - } - cursor_scale = std::max(cursor_scale, 1.0f); - -@@ -7982,6 +8025,8 @@ steamcompmgr_main(int argc, char **argv) - 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; -diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp -index e77665afc..b1701c97c 100644 ---- a/src/steamcompmgr.hpp -+++ b/src/steamcompmgr.hpp -@@ -94,6 +94,8 @@ class MouseCursor - bool needs_server_flush() const { return m_needs_server_flush; } - void inform_flush() { m_needs_server_flush = false; } - -+ void GetDesiredSize( int& nWidth, int &nHeight ); -+ - private: - void warp(int x, int y); - void checkSuspension(); - -From 9894245aa7a98063bf99fc78269d5d7a6f0a358b Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 5 Dec 2023 21:03:45 +0000 -Subject: [PATCH 026/134] main: Default XCURSOR_SIZE to 256 - ---- - src/main.cpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/main.cpp b/src/main.cpp -index 46e343ccd..76721d63d 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -697,6 +697,7 @@ int main(int argc, char **argv) - #endif - - setenv( "XWAYLAND_FORCE_ENABLE_EXTRA_MODES", "1", 1 ); -+ setenv( "XCURSOR_SIZE", "256", 1 ); - - raise_fd_limit(); - - -From 2b9c739d20179b294fac4adb311d3d79516a9e5c Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 6 Dec 2023 16:43:42 +0000 -Subject: [PATCH 027/134] pipewire: Make dmabuf.n_planes != 1 non-fatal - ---- - src/pipewire.cpp | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/src/pipewire.cpp b/src/pipewire.cpp -index a05346ce4..236e8ac7e 100644 ---- a/src/pipewire.cpp -+++ b/src/pipewire.cpp -@@ -478,7 +478,11 @@ static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffe - - if (is_dmabuf) { - const struct wlr_dmabuf_attributes dmabuf = buffer->texture->dmabuf(); -- assert(dmabuf.n_planes == 1); -+ 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) { - -From 13381d23ec845e108759a70dd212bdd9fc94d2e5 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Thu, 7 Dec 2023 15:39:43 +0000 -Subject: [PATCH 028/134] waitable: Default to EPOLLIN | EPOLLHUP - ---- - src/steamcompmgr.cpp | 2 +- - src/waitable.h | 4 ++-- - 2 files changed, 3 insertions(+), 3 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 8345bfa1b..65a2e2aa2 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -6758,7 +6758,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re - 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 ); -- g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN | EPOLLHUP ); -+ g_ImageWaiter.AddWaitable( newCommit.get() ); - } - - w->commit_queue.push_back( std::move(newCommit) ); -diff --git a/src/waitable.h b/src/waitable.h -index 292312ec9..4bc77ff62 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -94,7 +94,7 @@ namespace gamescope - CWaiter() - : m_nEpollFD{ epoll_create1( EPOLL_CLOEXEC ) } - { -- AddWaitable( &m_NudgeWaitable, EPOLLIN | EPOLLHUP ); -+ AddWaitable( &m_NudgeWaitable ); - } - - ~CWaiter() -@@ -117,7 +117,7 @@ namespace gamescope - } - } - -- bool AddWaitable( IWaitable *pWaitable, uint32_t nEvents = EPOLLIN | EPOLLOUT | EPOLLHUP ) -+ bool AddWaitable( IWaitable *pWaitable, uint32_t nEvents = EPOLLIN | EPOLLHUP ) - { - epoll_event event = - { - -From 499cd6f4982333775ba232729cd5c44096aca490 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 6 Dec 2023 23:18:37 +0000 -Subject: [PATCH 029/134] steamcompmgr: Move win32 styles to win32_styles.h - ---- - src/steamcompmgr.cpp | 60 +------------------------------------------- - src/win32_styles.h | 56 +++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 57 insertions(+), 59 deletions(-) - create mode 100644 src/win32_styles.h - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 65a2e2aa2..c71516d1d 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -87,6 +87,7 @@ - #include "sdlwindow.hpp" - #include "log.hpp" - #include "defer.hpp" -+#include "win32_styles.h" - - static const int g_nBaseCursorScale = 36; - -@@ -640,65 +641,6 @@ bool set_color_look_g22(const char *path) - - bool g_bColorSliderInUse = false; - --// --// --// -- --const uint32_t WS_OVERLAPPED = 0x00000000u; --const uint32_t WS_POPUP = 0x80000000u; --const uint32_t WS_CHILD = 0x40000000u; --const uint32_t WS_MINIMIZE = 0x20000000u; --const uint32_t WS_VISIBLE = 0x10000000u; --const uint32_t WS_DISABLED = 0x08000000u; --const uint32_t WS_CLIPSIBLINGS = 0x04000000u; --const uint32_t WS_CLIPCHILDREN = 0x02000000u; --const uint32_t WS_MAXIMIZE = 0x01000000u; --const uint32_t WS_BORDER = 0x00800000u; --const uint32_t WS_DLGFRAME = 0x00400000u; --const uint32_t WS_VSCROLL = 0x00200000u; --const uint32_t WS_HSCROLL = 0x00100000u; --const uint32_t WS_SYSMENU = 0x00080000u; --const uint32_t WS_THICKFRAME = 0x00040000u; --const uint32_t WS_GROUP = 0x00020000u; --const uint32_t WS_TABSTOP = 0x00010000u; --const uint32_t WS_MINIMIZEBOX = 0x00020000u; --const uint32_t WS_MAXIMIZEBOX = 0x00010000u; --const uint32_t WS_CAPTION = WS_BORDER | WS_DLGFRAME; --const uint32_t WS_TILED = WS_OVERLAPPED; --const uint32_t WS_ICONIC = WS_MINIMIZE; --const uint32_t WS_SIZEBOX = WS_THICKFRAME; --const uint32_t WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME| WS_MINIMIZEBOX | WS_MAXIMIZEBOX; --const uint32_t WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU; --const uint32_t WS_CHILDWINDOW = WS_CHILD; --const uint32_t WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW; -- --const uint32_t WS_EX_DLGMODALFRAME = 0x00000001u; --const uint32_t WS_EX_DRAGDETECT = 0x00000002u; // Undocumented --const uint32_t WS_EX_NOPARENTNOTIFY = 0x00000004u; --const uint32_t WS_EX_TOPMOST = 0x00000008u; --const uint32_t WS_EX_ACCEPTFILES = 0x00000010u; --const uint32_t WS_EX_TRANSPARENT = 0x00000020u; --const uint32_t WS_EX_MDICHILD = 0x00000040u; --const uint32_t WS_EX_TOOLWINDOW = 0x00000080u; --const uint32_t WS_EX_WINDOWEDGE = 0x00000100u; --const uint32_t WS_EX_CLIENTEDGE = 0x00000200u; --const uint32_t WS_EX_CONTEXTHELP = 0x00000400u; --const uint32_t WS_EX_RIGHT = 0x00001000u; --const uint32_t WS_EX_LEFT = 0x00000000u; --const uint32_t WS_EX_RTLREADING = 0x00002000u; --const uint32_t WS_EX_LTRREADING = 0x00000000u; --const uint32_t WS_EX_LEFTSCROLLBAR = 0x00004000u; --const uint32_t WS_EX_RIGHTSCROLLBAR = 0x00000000u; --const uint32_t WS_EX_CONTROLPARENT = 0x00010000u; --const uint32_t WS_EX_STATICEDGE = 0x00020000u; --const uint32_t WS_EX_APPWINDOW = 0x00040000u; --const uint32_t WS_EX_LAYERED = 0x00080000u; --const uint32_t WS_EX_NOINHERITLAYOUT = 0x00100000u; --const uint32_t WS_EX_NOREDIRECTIONBITMAP = 0x00200000u; --const uint32_t WS_EX_LAYOUTRTL = 0x00400000u; --const uint32_t WS_EX_COMPOSITED = 0x02000000u; --const uint32_t WS_EX_NOACTIVATE = 0x08000000u; -- - template< typename T > - constexpr const T& clamp( const T& x, const T& min, const T& max ) - { -diff --git a/src/win32_styles.h b/src/win32_styles.h -new file mode 100644 -index 000000000..88741e255 ---- /dev/null -+++ b/src/win32_styles.h -@@ -0,0 +1,56 @@ -+#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_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; - -From 2e1dd0b8e21c95b9895a622af52fe16bf78038c7 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 6 Dec 2023 23:21:02 +0000 -Subject: [PATCH 030/134] steamcompmgr: Move mwm hints to mwm_hints.h - ---- - src/mwm_hints.h | 29 +++++++++++++++++++++++++++++ - src/steamcompmgr.cpp | 29 +---------------------------- - 2 files changed, 30 insertions(+), 28 deletions(-) - create mode 100644 src/mwm_hints.h - -diff --git a/src/mwm_hints.h b/src/mwm_hints.h -new file mode 100644 -index 000000000..68c204e16 ---- /dev/null -+++ b/src/mwm_hints.h -@@ -0,0 +1,29 @@ -+#pragma once -+ -+#define MWM_HINTS_FUNCTIONS 1 -+#define MWM_HINTS_DECORATIONS 2 -+#define MWM_HINTS_INPUT_MODE 4 -+#define MWM_HINTS_STATUS 8 -+ -+#define MWM_FUNC_ALL 0x01 -+#define MWM_FUNC_RESIZE 0x02 -+#define MWM_FUNC_MOVE 0x04 -+#define MWM_FUNC_MINIMIZE 0x08 -+#define MWM_FUNC_MAXIMIZE 0x10 -+#define MWM_FUNC_CLOSE 0x20 -+ -+#define MWM_DECOR_ALL 0x01 -+#define MWM_DECOR_BORDER 0x02 -+#define MWM_DECOR_RESIZEH 0x04 -+#define MWM_DECOR_TITLE 0x08 -+#define MWM_DECOR_MENU 0x10 -+#define MWM_DECOR_MINIMIZE 0x20 -+#define MWM_DECOR_MAXIMIZE 0x40 -+ -+#define MWM_INPUT_MODELESS 0 -+#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 -+#define MWM_INPUT_SYSTEM_MODAL 2 -+#define MWM_INPUT_FULL_APPLICATION_MODAL 3 -+#define MWM_INPUT_APPLICATION_MODAL 1 -+ -+#define MWM_TEAROFF_WINDOW 1 -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index c71516d1d..49b316d63 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -88,6 +88,7 @@ - #include "log.hpp" - #include "defer.hpp" - #include "win32_styles.h" -+#include "mwm_hints.h" - - static const int g_nBaseCursorScale = 36; - -@@ -823,34 +824,6 @@ static inline void GarbageCollectWaitableCommit( std::shared_ptr &comm - - static std::vector pollfds; - --#define MWM_HINTS_FUNCTIONS 1 --#define MWM_HINTS_DECORATIONS 2 --#define MWM_HINTS_INPUT_MODE 4 --#define MWM_HINTS_STATUS 8 -- --#define MWM_FUNC_ALL 0x01 --#define MWM_FUNC_RESIZE 0x02 --#define MWM_FUNC_MOVE 0x04 --#define MWM_FUNC_MINIMIZE 0x08 --#define MWM_FUNC_MAXIMIZE 0x10 --#define MWM_FUNC_CLOSE 0x20 -- --#define MWM_DECOR_ALL 0x01 --#define MWM_DECOR_BORDER 0x02 --#define MWM_DECOR_RESIZEH 0x04 --#define MWM_DECOR_TITLE 0x08 --#define MWM_DECOR_MENU 0x10 --#define MWM_DECOR_MINIMIZE 0x20 --#define MWM_DECOR_MAXIMIZE 0x40 -- --#define MWM_INPUT_MODELESS 0 --#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 --#define MWM_INPUT_SYSTEM_MODAL 2 --#define MWM_INPUT_FULL_APPLICATION_MODAL 3 --#define MWM_INPUT_APPLICATION_MODAL 1 -- --#define MWM_TEAROFF_WINDOW 1 -- - Window x11_win(steamcompmgr_win_t *w) { - if (w == nullptr) - return None; - -From 07024b8979b57bfc9c2b86c820bbbf7b9fe66934 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Thu, 7 Dec 2023 13:21:04 +0000 -Subject: [PATCH 031/134] rendervulkan: Notify on g_currentPresentWaitId change - ---- - src/rendervulkan.cpp | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index f18b91fe3..32b87ba5b 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -2678,7 +2678,10 @@ void vulkan_present_to_window( void ) - }; - - if ( g_device.vk.QueuePresentKHR( g_device.queue(), &presentInfo ) == VK_SUCCESS ) -+ { - g_currentPresentWaitId = presentId; -+ g_currentPresentWaitId.notify_all(); -+ } - else - vulkan_remake_swapchain(); - -@@ -2925,6 +2928,7 @@ bool vulkan_remake_swapchain( void ) - { - std::unique_lock lock(present_wait_lock); - g_currentPresentWaitId = 0; -+ g_currentPresentWaitId.notify_all(); - - VulkanOutput_t *pOutput = &g_output; - g_device.waitIdle(); - -From 9376d530d8ee28c0556a53915be85e860ade2834 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 6 Dec 2023 22:51:33 +0000 -Subject: [PATCH 032/134] steamcompmgr: Move main poll event loop to new - waitable system - ---- - src/steamcompmgr.cpp | 130 ++++++++++--------------------------------- - src/waitable.h | 105 ++++++++++++++++++++++++---------- - src/xwayland_ctx.hpp | 25 ++++++++- - 3 files changed, 129 insertions(+), 131 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 49b316d63..40961877a 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -110,7 +110,7 @@ static const int g_nBaseCursorScale = 36; - #include "gpuvis_trace_utils.h" - - --static LogScope xwm_log("xwm"); -+LogScope xwm_log("xwm"); - LogScope g_WaitableLog("waitable"); - - bool g_bWasPartialComposite = false; -@@ -822,7 +822,7 @@ static inline void GarbageCollectWaitableCommit( std::shared_ptr &comm - } - } - --static std::vector pollfds; -+gamescope::CWaiter g_SteamCompMgrWaiter; - - Window x11_win(steamcompmgr_win_t *w) { - if (w == nullptr) -@@ -1074,8 +1074,6 @@ static bool g_bPropertyRequestedScreenshot; - - static std::atomic g_bForceRepaint{false}; - --static int g_nudgePipe[2] = {-1, -1}; -- - static int g_nCursorScaleHeight = -1; - - // poor man's semaphore -@@ -1105,21 +1103,6 @@ class sem - int count = 0; - }; - --static void --dispatch_nudge( int fd ) --{ -- for (;;) -- { -- static char buf[1024]; -- if ( read( fd, buf, sizeof(buf) ) < 0 ) -- { -- if ( errno != EAGAIN ) -- xwm_log.errorf_errno(" steamcompmgr: dispatch_nudge: read failed" ); -- break; -- } -- } --} -- - sem statsThreadSem; - std::mutex statsEventQueueLock; - std::vector< std::string > statsEventQueue; -@@ -1268,12 +1251,11 @@ should_ignore(xwayland_ctx_t *ctx, unsigned long sequence) - return ctx->ignore_head && ctx->ignore_head->sequence == sequence; - } - --static bool --x_events_queued(xwayland_ctx_t* ctx) -+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(ctx->dpy, QueuedAlready) != 0; -+ return XEventsQueued( dpy, QueuedAlready ) != 0; - } - - static steamcompmgr_win_t * -@@ -6030,10 +6012,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - .format = 8, - .nitems = strlen(propertyString), - }; -- pollfds.push_back(pollfd { -- .fd = XConnectionNumber( server->ctx->dpy ), -- .events = POLLIN, -- }); -+ g_SteamCompMgrWaiter.AddWaitable( server->ctx.get() ); - XSetTextProperty( ctx->dpy, ctx->root, &text_property, ctx->atoms.gamescopeCreateXWaylandServerFeedback ); - wlserver_unlock(); - } -@@ -6090,9 +6069,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - global_focus.cursor = nullptr; - - wlserver_lock(); -- std::erase_if(pollfds, [=](const auto& other){ -- return other.fd == XConnectionNumber( server->ctx->dpy ); -- }); -+ g_SteamCompMgrWaiter.RemoveWaitable( server->ctx.get() ); - wlserver_destroy_xwayland_server(server); - wlserver_unlock(); - -@@ -6589,8 +6566,7 @@ void handle_presented_xdg() - - void nudge_steamcompmgr( void ) - { -- if ( write( g_nudgePipe[ 1 ], "\n", 1 ) < 0 ) -- xwm_log.errorf_errno( "nudge_steamcompmgr: write failed" ); -+ g_SteamCompMgrWaiter.Nudge(); - } - - void take_screenshot( int flags ) -@@ -6858,9 +6834,10 @@ handle_xfixes_selection_notify( xwayland_ctx_t *ctx, XFixesSelectionNotifyEvent - XFlush(ctx->dpy); - } - --static void --dispatch_x11( xwayland_ctx_t *ctx ) -+void xwayland_ctx_t::Dispatch() - { -+ xwayland_ctx_t *ctx = this; -+ - MouseCursor *cursor = ctx->cursor.get(); - bool bShouldResetCursor = false; - bool bSetFocus = false; -@@ -7140,13 +7117,6 @@ load_host_cursor( MouseCursor *cursor ) - return true; - } - --enum steamcompmgr_event_type { -- EVENT_VBLANK, -- EVENT_NUDGE, -- EVENT_X11, -- // Any past here are X11 --}; -- - const char* g_customCursorPath = nullptr; - int g_customCursorHotspotX = 0; - int g_customCursorHotspotY = 0; -@@ -7702,12 +7672,6 @@ steamcompmgr_main(int argc, char **argv) - subCommandArg = optind; - } - -- if ( pipe2( g_nudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) -- { -- xwm_log.errorf_errno( "steamcompmgr: pipe2 failed" ); -- exit( 1 ); -- } -- - const char *pchEnableVkBasalt = getenv( "ENABLE_VKBASALT" ); - if ( pchEnableVkBasalt != nullptr && pchEnableVkBasalt[0] == '1' ) - { -@@ -7761,27 +7725,22 @@ steamcompmgr_main(int argc, char **argv) - spawn_client( &argv[ subCommandArg ] ); - } - -- // EVENT_VBLANK -- pollfds.push_back(pollfd { -- .fd = vblankFD, -- .events = POLLIN, -- }); -- // EVENT_NUDGE -- pollfds.push_back(pollfd { -- .fd = g_nudgePipe[ 0 ], -- .events = POLLIN, -- }); -- // EVENT_X11 -+ bool vblank = false; -+ g_SteamCompMgrWaiter.AddWaitable( -+ new gamescope::CFunctionWaitable{ vblankFD, [ vblankFD, &vblank ]() -+ { -+ vblank = dispatch_vblank( vblankFD ); -+ }} -+ ); -+ - { -- gamescope_xwayland_server_t *server = NULL; -- for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) -+ gamescope_xwayland_server_t *pServer = NULL; -+ for (size_t i = 0; (pServer = wlserver_get_xwayland_server(i)); i++) - { -- pollfds.push_back(pollfd { -- .fd = XConnectionNumber( server->ctx->dpy ), -- .events = POLLIN, -- }); -+ xwayland_ctx_t *pXWaylandCtx = pServer->ctx.get(); -+ g_SteamCompMgrWaiter.AddWaitable( pXWaylandCtx ); - -- server->ctx->force_windows_fullscreen = bForceWindowsFullscreen; -+ pServer->ctx->force_windows_fullscreen = bForceWindowsFullscreen; - } - } - -@@ -7804,52 +7763,19 @@ steamcompmgr_main(int argc, char **argv) - - for (;;) - { -- bool vblank = false; -+ vblank = false; - - { - gamescope_xwayland_server_t *server = NULL; - for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { -- assert(server); -- if (x_events_queued(server->ctx.get())) -- dispatch_x11(server->ctx.get()); -+ assert(server->ctx); -+ if (server->ctx->HasQueuedEvents()) -+ server->ctx->Dispatch(); - } - } - -- if ( poll( pollfds.data(), pollfds.size(), -1 ) < 0) -- { -- if ( errno == EAGAIN ) -- continue; -- -- xwm_log.errorf_errno( "poll failed" ); -- break; -- } -- -- for (size_t i = EVENT_X11; i < pollfds.size(); i++) -- { -- if ( pollfds[ i ].revents & POLLHUP ) -- { -- xwm_log.errorf( "Lost connection to the X11 server %zd", i - EVENT_X11 ); -- break; -- } -- } -- -- assert( !( pollfds[ EVENT_VBLANK ].revents & POLLHUP ) ); -- assert( !( pollfds[ EVENT_NUDGE ].revents & POLLHUP ) ); -- -- for (size_t i = EVENT_X11; i < pollfds.size(); i++) -- { -- if ( pollfds[ i ].revents & POLLIN ) -- { -- gamescope_xwayland_server_t *server = wlserver_get_xwayland_server(i - EVENT_X11); -- assert(server); -- dispatch_x11( server->ctx.get() ); -- } -- } -- if ( pollfds[ EVENT_VBLANK ].revents & POLLIN ) -- vblank = dispatch_vblank( vblankFD ); -- if ( pollfds[ EVENT_NUDGE ].revents & POLLIN ) -- dispatch_nudge( g_nudgePipe[ 0 ] ); -+ g_SteamCompMgrWaiter.PollEvents(); - - if ( g_bRun == false ) - { -diff --git a/src/waitable.h b/src/waitable.h -index 4bc77ff62..a8f1f212a 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -2,8 +2,11 @@ - - #include - #include -+#include - #include - -+#include -+ - #include "log.hpp" - - extern LogScope g_WaitableLog; -@@ -19,7 +22,11 @@ namespace gamescope - - virtual void OnPollIn() {} - virtual void OnPollOut() {} -- virtual void OnPollHangUp() {} -+ virtual void OnPollHangUp() -+ { -+ g_WaitableLog.errorf( "IWaitable hung up. Aborting." ); -+ abort(); -+ } - - void HandleEvents( uint32_t nEvents ) - { -@@ -30,6 +37,23 @@ namespace gamescope - if ( nEvents & EPOLLHUP ) - this->OnPollHangUp(); - } -+ -+ static void Drain( int nFD ) -+ { -+ if ( nFD < 0 ) -+ return; -+ -+ char buf[1024]; -+ for (;;) -+ { -+ if ( read( nFD, buf, sizeof( buf ) ) < 0 ) -+ { -+ if ( errno != EAGAIN ) -+ g_WaitableLog.errorf_errno( "Failed to drain CNudgeWaitable" ); -+ break; -+ } -+ } -+ } - }; - - class CNudgeWaitable final : public IWaitable -@@ -57,19 +81,7 @@ namespace gamescope - - void Drain() - { -- if ( m_nFDs[0] < 0 ) -- return; -- -- char buf[1024]; -- for (;;) -- { -- if ( read( m_nFDs[0], buf, sizeof( buf ) ) < 0 ) -- { -- if ( errno != EAGAIN ) -- g_WaitableLog.errorf_errno( "Failed to drain CNudgeWaitable" ); -- break; -- } -- } -+ IWaitable::Drain( m_nFDs[0] ); - } - - void OnPollIn() final -@@ -87,6 +99,35 @@ namespace gamescope - int m_nFDs[2] = { -1, -1 }; - }; - -+ -+ class CFunctionWaitable final : public IWaitable -+ { -+ public: -+ CFunctionWaitable( int nFD, std::function fnPollFunc ) -+ : m_nFD{ nFD } -+ , m_fnPollFunc{ fnPollFunc } -+ { -+ } -+ -+ void OnPollIn() final -+ { -+ m_fnPollFunc(); -+ } -+ -+ void Drain() -+ { -+ IWaitable::Drain( m_nFD ); -+ } -+ -+ int GetFD() final -+ { -+ return m_nFD; -+ } -+ private: -+ int m_nFD; -+ std::function m_fnPollFunc; -+ }; -+ - template - class CWaiter - { -@@ -142,27 +183,35 @@ namespace gamescope - epoll_ctl( m_nEpollFD, EPOLL_CTL_DEL, pWaitable->GetFD(), nullptr ); - } - -- void PollEvents() -+ void PollEvents( int nTimeOut = -1 ) - { - epoll_event events[MaxEvents]; - -- int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, -1 ); -+ for ( ;; ) -+ { -+ int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, nTimeOut ); - -- if ( !m_bRunning ) -- return; -+ if ( !m_bRunning ) -+ return; - -- if ( nEventCount < 0 ) -- { -- g_WaitableLog.errorf_errno( "Failed to epoll_wait in CAsyncWaiter" ); -- return; -- } -+ if ( nEventCount < 0 ) -+ { -+ if ( errno == EAGAIN ) -+ continue; - -- for ( int i = 0; i < nEventCount; i++ ) -- { -- epoll_event &event = events[i]; -+ g_WaitableLog.errorf_errno( "Failed to epoll_wait in CAsyncWaiter" ); -+ return; -+ } -+ -+ for ( int i = 0; i < nEventCount; i++ ) -+ { -+ epoll_event &event = events[i]; - -- IWaitable *pWaitable = reinterpret_cast( event.data.ptr ); -- pWaitable->HandleEvents( event.events ); -+ IWaitable *pWaitable = reinterpret_cast( event.data.ptr ); -+ pWaitable->HandleEvents( event.events ); -+ } -+ -+ return; - } - } - -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index 52f42ef42..5764c4b7b 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -1,6 +1,7 @@ - #pragma once - - #include "drm.hpp" -+#include "waitable.h" - - #include - #include -@@ -21,6 +22,8 @@ struct ignore; - struct steamcompmgr_win_t; - class MouseCursor; - -+extern LogScope xwm_log; -+ - struct focus_t - { - steamcompmgr_win_t *focusWindow; -@@ -49,7 +52,7 @@ struct CommitDoneList_t - std::vector< CommitDoneEntry_t > listCommitsDone; - }; - --struct xwayland_ctx_t -+struct xwayland_ctx_t final : public gamescope::IWaitable - { - gamescope_xwayland_server_t *xwayland_server; - Display *dpy; -@@ -234,4 +237,24 @@ struct xwayland_ctx_t - Atom primarySelection; - Atom targets; - } atoms; -+ -+ bool HasQueuedEvents(); -+ -+ void Dispatch(); -+ -+ int GetFD() final -+ { -+ return XConnectionNumber( dpy ); -+ } -+ -+ void OnPollIn() final -+ { -+ Dispatch(); -+ } -+ -+ void OnPollHangUp() final -+ { -+ xwm_log.errorf( "XWayland server hung up! This is fatal. Aborting..." ); -+ abort(); -+ } - }; - -From ed6d387602e5da9cb1b8be3245a64d296371d9b6 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Thu, 7 Dec 2023 15:28:57 +0000 -Subject: [PATCH 033/134] vblankmanager: Port to timerfd - -Ports vblankmanager to be timerfd based, and also fxies issues -with re-arming at higher refresh rates. - -The old nudge thread method still exists (and is needed for VR), and -it has also been improved to fix the re-arming issue. - -The old method can be enabled with GAMESCOPE_DISABLE_TIMERFD. ---- - src/drm.cpp | 6 +- - src/rendervulkan.cpp | 2 +- - src/steamcompmgr.cpp | 98 ++++---- - src/steamcompmgr.hpp | 3 +- - src/vblankmanager.cpp | 521 +++++++++++++++++++++++++++++------------- - src/vblankmanager.hpp | 143 ++++++++++-- - 6 files changed, 538 insertions(+), 235 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index c975bfd8d..c2694f00f 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -283,7 +283,7 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi - - // This is the last vblank time - uint64_t vblanktime = sec * 1'000'000'000lu + usec * 1'000lu; -- vblank_mark_possible_vblank(vblanktime); -+ g_VBlankTimer.MarkVBlank( vblanktime, true ); - - // TODO: get the fbids_queued instance from data if we ever have more than one in flight - -@@ -1351,7 +1351,7 @@ void load_pnps(void) - fclose(f); - } - --static bool env_to_bool(const char *env) -+bool env_to_bool(const char *env) - { - if (!env || !*env) - return false; -@@ -1758,7 +1758,7 @@ int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) - // 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. -- g_uVblankDrawTimeNS = get_time_in_nanos() - g_SteamCompMgrVBlankTime.pipe_write_time; -+ g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - - if ( isPageFlip ) { - // Wait for flip handler to unlock -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 32b87ba5b..3e179c4b1 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -2600,7 +2600,7 @@ static void present_wait_thread_func( void ) - { - g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu ); - uint64_t vblanktime = get_time_in_nanos(); -- vblank_mark_possible_vblank( vblanktime ); -+ g_VBlankTimer.MarkVBlank( vblanktime, true ); - mangoapp_output_update( vblanktime ); - } - } -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 40961877a..33fa43438 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -139,8 +139,6 @@ extern float g_flHDRItmTargetNits; - - uint64_t g_lastWinSeq = 0; - --extern std::atomic g_lastVblank; -- - static std::shared_ptr s_scRGB709To2020Matrix; - - std::string clipboard; -@@ -156,6 +154,16 @@ uint64_t timespec_to_nanos(struct timespec& spec) - return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec; - } - -+timespec nanos_to_timespec( uint64_t ulNanos ) -+{ -+ 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(); - -@@ -882,7 +890,7 @@ bool synchronize; - - std::mutex g_SteamCompMgrXWaylandServerMutex; - --VBlankTimeInfo_t g_SteamCompMgrVBlankTime = {}; -+gamescope::VBlankTime g_SteamCompMgrVBlankTime = {}; - - uint64_t g_uCurrentBasePlaneCommitID = 0; - bool g_bCurrentBasePlaneIsFifo = false; -@@ -1194,9 +1202,7 @@ uint64_t get_time_in_nanos() - - void sleep_for_nanos(uint64_t nanos) - { -- timespec ts; -- ts.tv_sec = time_t(nanos / 1'000'000'000ul); -- ts.tv_nsec = long(nanos % 1'000'000'000ul); -+ timespec ts = nanos_to_timespec( nanos ); - nanosleep(&ts, nullptr); - } - -@@ -2736,7 +2742,7 @@ paint_all(bool async) - } - - // Update to let the vblank manager know we are currently compositing. -- g_bCurrentlyCompositing = bDoComposite; -+ g_VBlankTimer.UpdateWasCompositing( bDoComposite ); - - if ( bDoComposite == true ) - { -@@ -2859,7 +2865,7 @@ paint_all(bool async) - } - - // Update the time it took us to commit -- g_uVblankDrawTimeNS = get_time_in_nanos() - g_SteamCompMgrVBlankTime.pipe_write_time; -+ g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); - } - else - { -@@ -3139,7 +3145,7 @@ paint_all(bool async) - int ret = system(cmd); - - /* Above call may fail, ffmpeg returns 0 on success */ -- if (ret) { -+ if (ret) { - xwm_log.infof("Ffmpeg call return status %i", ret); - xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); - } else { -@@ -5587,6 +5593,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - focusDirty = true; - } - } -+#if 0 - if ( ev->atom == ctx->atoms.gamescopeTuneableVBlankRedZone ) - { - g_uVblankDrawBufferRedZoneNS = (uint64_t)get_prop( ctx, ctx->root, ctx->atoms.gamescopeTuneableVBlankRedZone, g_uDefaultVBlankRedZone ); -@@ -5595,6 +5602,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - { - 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 ); -@@ -6397,7 +6405,7 @@ void handle_done_commits_xwayland( xwayland_ctx_t *ctx, bool vblank, uint64_t vb - { - std::lock_guard lock( ctx->doneCommits.listCommitsDoneLock ); - -- uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time; -+ 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; -@@ -6454,7 +6462,7 @@ void handle_done_commits_xdg() - { - std::lock_guard lock( g_steamcompmgr_xdg_done_commits.listCommitsDoneLock ); - -- uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time; -+ uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; - - // commits that were not ready to be presented based on their display timing. - std::vector< CommitDoneEntry_t > commits_before_their_time; -@@ -6488,7 +6496,7 @@ void handle_done_commits_xdg() - - void handle_presented_for_window( steamcompmgr_win_t* w ) - { -- uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time; -+ uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.schedule.ulTargetVBlank; - - uint64_t refresh_cycle = g_nSteamCompMgrTargetFPS && steamcompmgr_window_should_limit_fps( w ) - ? g_SteamCompMgrLimitedAppRefreshCycle -@@ -7012,41 +7020,6 @@ void xwayland_ctx_t::Dispatch() - } - } - --static bool --dispatch_vblank( int fd ) --{ -- bool vblank = false; -- for (;;) -- { -- VBlankTimeInfo_t vblanktime = {}; -- ssize_t ret = read( fd, &vblanktime, sizeof( vblanktime ) ); -- if ( ret < 0 ) -- { -- if ( errno == EAGAIN ) -- break; -- -- xwm_log.errorf_errno( "steamcompmgr: dispatch_vblank: read failed" ); -- break; -- } -- -- g_SteamCompMgrVBlankTime = vblanktime; -- -- uint64_t diff = get_time_in_nanos() - vblanktime.pipe_write_time; -- -- // give it 1 ms of slack from pipe to steamcompmgr... maybe too long -- if ( diff > 1'000'000ul ) -- { -- gpuvis_trace_printf( "ignored stale vblank" ); -- } -- else -- { -- gpuvis_trace_printf( "got vblank" ); -- vblank = true; -- } -- } -- return vblank; --} -- - struct rgba_t - { - uint8_t r,g,b,a; -@@ -7690,9 +7663,6 @@ steamcompmgr_main(int argc, char **argv) - vrsession_steam_mode( steamMode ); - #endif - -- int vblankFD = vblank_init(); -- assert( vblankFD >= 0 ); -- - std::unique_lock xwayland_server_guard(g_SteamCompMgrXWaylandServerMutex); - - // Initialize any xwayland ctxs we have -@@ -7726,12 +7696,8 @@ steamcompmgr_main(int argc, char **argv) - } - - bool vblank = false; -- g_SteamCompMgrWaiter.AddWaitable( -- new gamescope::CFunctionWaitable{ vblankFD, [ vblankFD, &vblank ]() -- { -- vblank = dispatch_vblank( vblankFD ); -- }} -- ); -+ g_SteamCompMgrWaiter.AddWaitable( &g_VBlankTimer ); -+ g_VBlankTimer.RearmTimer( true ); - - { - gamescope_xwayland_server_t *pServer = NULL; -@@ -7777,6 +7743,12 @@ steamcompmgr_main(int argc, char **argv) - - g_SteamCompMgrWaiter.PollEvents(); - -+ if ( std::optional pendingVBlank = g_VBlankTimer.ProcessVBlank() ) -+ { -+ g_SteamCompMgrVBlankTime = *pendingVBlank; -+ vblank = true; -+ } -+ - if ( g_bRun == false ) - { - break; -@@ -8068,13 +8040,13 @@ steamcompmgr_main(int argc, char **argv) - // 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 bNeedsSyncFlip = bForceSyncFlip || g_bCurrentlyCompositing || nIgnoredOverlayRepaints; -+ const bool bNeedsSyncFlip = bForceSyncFlip || g_VBlankTimer.WasCompositing() || nIgnoredOverlayRepaints; - const bool bDoAsyncFlip = ( ((g_nAsyncFlipsEnabled >= 1) && g_bSupportsAsyncFlips && bSurfaceWantsAsync && !bHasOverlay) || bVRR ) && !bSteamOverlayOpen && !bNeedsSyncFlip; - - bool bShouldPaint = false; - if ( bDoAsyncFlip ) - { -- if ( hasRepaint && !g_bCurrentlyCompositing ) -+ if ( hasRepaint && !g_VBlankTimer.WasCompositing() ) - bShouldPaint = true; - } - else -@@ -8110,6 +8082,16 @@ steamcompmgr_main(int argc, char **argv) - } - } - -+ if ( vblank ) -+ { -+ // 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. -+ g_VBlankTimer.RearmTimer( true ); -+ } -+ - update_vrr_atoms(root_ctx, false, &flush_root); - - if (global_focus.cursor) -diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp -index b1701c97c..e73a70767 100644 ---- a/src/steamcompmgr.hpp -+++ b/src/steamcompmgr.hpp -@@ -12,6 +12,7 @@ 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); - -@@ -155,7 +156,7 @@ 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 ); - --extern VBlankTimeInfo_t g_SteamCompMgrVBlankTime; -+extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; - extern pid_t focusWindow_pid; - - void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server); -diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp -index 925fda3a9..9ccb130eb 100644 ---- a/src/vblankmanager.cpp -+++ b/src/vblankmanager.cpp -@@ -11,6 +11,7 @@ - #include - #include - #include -+#include - - #include "gpuvis_trace_utils.h" - -@@ -24,235 +25,441 @@ - #include "vr_session.hpp" - #endif - --static int g_vblankPipe[2]; -+LogScope g_VBlankLog("vblank"); - --std::atomic g_lastVblank; -+// #define VBLANK_DEBUG - --// 3ms by default -- a good starting value. --const uint64_t g_uStartingDrawTime = 3'000'000; -+extern bool env_to_bool(const char *env); - --// This is the last time a draw took. --std::atomic g_uVblankDrawTimeNS = { g_uStartingDrawTime }; -+namespace gamescope -+{ -+ CVBlankTimer::CVBlankTimer() -+ { -+ m_ulTargetVBlank = get_time_in_nanos(); -+ m_ulLastVBlank = m_ulTargetVBlank; - --// 1.3ms by default. (g_uDefaultMinVBlankTime) --// 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. --uint64_t g_uMinVblankTime = g_uDefaultMinVBlankTime; -+ const bool bShouldUseTimerFD = !BIsVRSession() || env_to_bool( "GAMESCOPE_DISABLE_TIMERFD" ); - --// Tuneable --// 0.3ms by default. (g_uDefaultVBlankRedZone) --// This is the leeway we always apply to our buffer. --uint64_t g_uVblankDrawBufferRedZoneNS = g_uDefaultVBlankRedZone; -+ if ( bShouldUseTimerFD ) -+ { -+ g_VBlankLog.infof( "Using timerfd." ); -+ m_nTimerFD = timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC ); -+ if ( m_nTimerFD < 0 ) -+ { -+ g_VBlankLog.errorf_errno( "Failed to create VBlankTimer timerfd." ); -+ abort(); -+ } -+ } -+ else -+ { -+ g_VBlankLog.infof( "Using nudge thread." ); - --// Tuneable --// 93% by default. (g_uVBlankRateOfDecayPercentage) --// The rate of decay (as a percentage) of the rolling average -> current draw time --uint64_t g_uVBlankRateOfDecayPercentage = g_uDefaultVBlankRateOfDecayPercentage; -+ if ( pipe2( m_nNudgePipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) -+ { -+ g_VBlankLog.errorf_errno( "Failed to create VBlankTimer pipe." ); -+ abort(); -+ } - --const uint64_t g_uVBlankRateOfDecayMax = 1000; -+#if HAVE_OPENVR -+ if ( BIsVRSession() ) -+ { -+ std::thread vblankThread( [this]() { this->VRNudgeThread(); } ); -+ vblankThread.detach(); -+ } -+ else -+#endif -+ { -+ std::thread vblankThread( [this]() { this->NudgeThread(); } ); -+ vblankThread.detach(); -+ } -+ } -+ } - --static std::atomic g_uRollingMaxDrawTime = { g_uStartingDrawTime }; -+ CVBlankTimer::~CVBlankTimer() -+ { -+ std::unique_lock lock( m_ScheduleMutex ); - --std::atomic g_bCurrentlyCompositing = { false }; -+ m_bRunning = false; - --// 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 clocks. Pick a sane minimum draw time. --const uint64_t g_uVBlankDrawTimeMinCompositing = 2'400'000; -+ m_bArmed = true; -+ m_bArmed.notify_all(); - --//#define VBLANK_DEBUG -+ if ( m_nTimerFD >= 0 ) -+ { -+ close( m_nTimerFD ); -+ m_nTimerFD = 0; -+ } - --uint64_t vblank_next_target( uint64_t offset ) --{ -- const int refresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; -- const uint64_t nsecInterval = 1'000'000'000ul / refresh; -+ for ( int i = 0; i < 2; i++ ) -+ { -+ if ( m_nNudgePipe[ i ] >= 0 ) -+ { -+ close ( m_nNudgePipe[ i ] ); -+ m_nNudgePipe[ i ] = 0; -+ } -+ } -+ } - -- uint64_t lastVblank = g_lastVblank - offset; -+ int CVBlankTimer::GetRefresh() const -+ { -+ return g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; -+ } - -- uint64_t now = get_time_in_nanos(); -- uint64_t targetPoint = lastVblank + nsecInterval; -- while ( targetPoint < now ) -- targetPoint += nsecInterval; -+ uint64_t CVBlankTimer::GetLastVBlank() const -+ { -+ return m_ulLastVBlank; -+ } - -- return targetPoint; --} -+ uint64_t CVBlankTimer::GetNextVBlank( uint64_t ulOffset ) const -+ { -+ const uint64_t ulIntervalNSecs = kSecInNanoSecs / GetRefresh(); -+ const uint64_t ulNow = get_time_in_nanos(); - --void vblankThreadRun( void ) --{ -- pthread_setname_np( pthread_self(), "gamescope-vblk" ); -+ uint64_t ulTargetPoint = GetLastVBlank() + ulIntervalNSecs - ulOffset; - -- // Start off our average with our starting draw time. -- uint64_t rollingMaxDrawTime = g_uStartingDrawTime; -+ while ( ulTargetPoint < ulNow ) -+ ulTargetPoint += ulIntervalNSecs; - -- const uint64_t range = g_uVBlankRateOfDecayMax; -- while ( true ) -+ return ulTargetPoint; -+ } -+ -+ VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) - { -- const int refresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; -- const uint64_t nsecInterval = 1'000'000'000ul / refresh; -- // The redzone is relative to 60Hz, scale it by our -- // target refresh so we don't miss submitting for vblank in DRM. -- // (This fixes 4K@30Hz screens) -- const uint64_t nsecToSec = 1'000'000'000ul; -- const drm_screen_type screen_type = drm_get_screen_type( &g_DRM ); -- const uint64_t redZone = screen_type == DRM_SCREEN_TYPE_INTERNAL -- ? g_uVblankDrawBufferRedZoneNS -- : ( g_uVblankDrawBufferRedZoneNS * 60 * nsecToSec ) / ( refresh * nsecToSec ); -- -- uint64_t offset; -+ const drm_screen_type eScreenType = drm_get_screen_type( &g_DRM ); -+ -+ const int nRefreshRate = GetRefresh(); -+ const uint64_t ulRefreshInterval = kSecInNanoSecs / nRefreshRate; -+ // 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 == DRM_SCREEN_TYPE_INTERNAL -+ ? m_ulVBlankDrawBufferRedZone -+ : ( m_ulVBlankDrawBufferRedZone * 60 * kSecInNanoSecs ) / ( nRefreshRate * kSecInNanoSecs ); -+ - bool bVRR = drm_get_vrr_in_use( &g_DRM ); -+ uint64_t ulOffset = 0; - if ( !bVRR ) - { -- const uint64_t alpha = g_uVBlankRateOfDecayPercentage; -+ const uint64_t ulDecayAlpha = m_ulVBlankRateOfDecayPercentage; // eg. 980 = 98% - -- uint64_t drawTime = g_uVblankDrawTimeNS; -+ uint64_t ulDrawTime = m_ulLastDrawTime; -+ /// See comment of m_ulVBlankDrawTimeMinCompositing. -+ if ( m_bCurrentlyCompositing ) -+ ulDrawTime = std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); - -- if ( g_bCurrentlyCompositing ) -- drawTime = std::max(drawTime, g_uVBlankDrawTimeMinCompositing); -- // This is a rolling average when drawTime < rollingMaxDrawTime, -- // and a a max when drawTime > rollingMaxDrawTime. -+ 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. -- if ( int64_t(drawTime) - int64_t(redZone / 2) > int64_t(rollingMaxDrawTime) ) -- rollingMaxDrawTime = drawTime; -+ // 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 -- rollingMaxDrawTime = ( ( alpha * rollingMaxDrawTime ) + ( range - alpha ) * drawTime ) / range; -+ 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. -- rollingMaxDrawTime = std::min( rollingMaxDrawTime, nsecInterval - redZone ); -+ ulNewRollingDrawTime = std::min( ulNewRollingDrawTime, ulRefreshInterval - ulRedZone ); - -- g_uRollingMaxDrawTime = rollingMaxDrawTime; -+ // 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; - -- offset = rollingMaxDrawTime + redZone; -+ ulOffset = ulNewRollingDrawTime + ulRedZone; -+ -+ if ( !bPreemptive ) -+ VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); - } - else - { -- // VRR: -- // Just ensure that if we missed a frame due to already -- // having a page flip in-flight, that we flush it out with this. -- // Nothing fancy needed, just need to get on the other side of the page flip. -- // -- // We don't use any of the rolling times due to them varying given our -- // 'vblank' time is varying. -- g_uRollingMaxDrawTime = g_uStartingDrawTime; -- -- offset = 1'000'000 + redZone; -+ // See above. -+ if ( !bPreemptive ) -+ { -+ // Reset the max draw time to default, it is unused for VRR. -+ m_ulRollingMaxDrawTime = kStartingVBlankDrawTime; -+ } -+ -+ // TODO(Josh): We can probably do better than this for VRR. -+ uint64_t ulDrawTime = kVRRFlushingDrawTime; -+ /// See comment of m_ulVBlankDrawTimeMinCompositing. -+ if ( m_bCurrentlyCompositing ) -+ ulDrawTime = std::max( ulDrawTime, m_ulVBlankDrawTimeMinCompositing ); -+ -+ ulOffset = ulDrawTime + ulRedZone; -+ -+ if ( !bPreemptive ) -+ VBlankDebugSpew( ulOffset, ulDrawTime, ulRedZone ); - } - --#ifdef VBLANK_DEBUG -- // Debug stuff for logging missed vblanks -- static uint64_t vblankIdx = 0; -- static uint64_t lastDrawTime = g_uVblankDrawTimeNS; -- static uint64_t lastOffset = g_uVblankDrawTimeNS + redZone; -+ const uint64_t ulScheduledWakeupPoint = GetNextVBlank( ulOffset ); -+ const uint64_t ulTargetVBlank = ulScheduledWakeupPoint + ulOffset; - -- if ( vblankIdx++ % 300 == 0 || drawTime > lastOffset ) -+ VBlankScheduleTime schedule = - { -- if ( drawTime > lastOffset ) -- fprintf( stderr, " !! missed vblank " ); -+ .ulTargetVBlank = ulTargetVBlank, -+ .ulScheduledWakeupPoint = ulScheduledWakeupPoint, -+ }; -+ return schedule; -+ } - -- fprintf( stderr, "redZone: %.2fms decayRate: %lu%% - rollingMaxDrawTime: %.2fms lastDrawTime: %.2fms lastOffset: %.2fms - drawTime: %.2fms offset: %.2fms\n", -- redZone / 1'000'000.0, -- g_uVBlankRateOfDecayPercentage, -- rollingMaxDrawTime / 1'000'000.0, -- lastDrawTime / 1'000'000.0, -- lastOffset / 1'000'000.0, -- drawTime / 1'000'000.0, -- offset / 1'000'000.0 ); -+ 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. -+ RearmTimer( false ); - } -+ } - -- lastDrawTime = drawTime; -- lastOffset = offset; --#endif -+ bool CVBlankTimer::WasCompositing() const -+ { -+ return m_bCurrentlyCompositing; -+ } -+ -+ void CVBlankTimer::UpdateWasCompositing( bool bCompositing ) -+ { -+ m_bCurrentlyCompositing = bCompositing; -+ } -+ -+ void CVBlankTimer::UpdateLastDrawTime( uint64_t ulNanos ) -+ { -+ m_ulLastDrawTime = ulNanos; -+ } - -- uint64_t targetPoint = vblank_next_target( offset ); -+ void CVBlankTimer::WaitToBeArmed() -+ { -+ // Wait for m_bArmed to change *from* false. -+ m_bArmed.wait( false ); -+ } - -- sleep_until_nanos( targetPoint ); -+ void CVBlankTimer::RearmTimer( bool bPreemptive ) -+ { -+ std::unique_lock lock( m_ScheduleMutex ); - -- VBlankTimeInfo_t time_info = -- { -- .target_vblank_time = targetPoint + offset, -- .pipe_write_time = get_time_in_nanos(), -- }; -+ // If we're pre-emptively re-arming, don't -+ // do anything if we are already armed. -+ if ( bPreemptive && m_bArmed ) -+ return; - -- ssize_t ret = write( g_vblankPipe[ 1 ], &time_info, sizeof( time_info ) ); -- if ( ret <= 0 ) -- { -- perror( "vblankmanager: write failed" ); -- } -- else -+ m_bArmed = true; -+ m_bArmed.notify_all(); -+ -+ if ( UsingTimerFD() ) - { -- gpuvis_trace_printf( "sent vblank" ); -+ m_TimerFDSchedule = CalcNextWakeupTime( bPreemptive ); -+ -+ itimerspec timerspec = -+ { -+ .it_interval = timespec{}, -+ .it_value = nanos_to_timespec( m_TimerFDSchedule.ulScheduledWakeupPoint ), -+ }; -+ if ( timerfd_settime( m_nTimerFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) -+ g_VBlankLog.errorf_errno( "timerfd_settime failed!" ); - } -- -- // Get on the other side of it now -- sleep_for_nanos( offset + 1'000'000 ); - } --} - --#if HAVE_OPENVR --void vblankThreadVR() --{ -- pthread_setname_np( pthread_self(), "gamescope-vblkvr" ); -+ bool CVBlankTimer::UsingTimerFD() const -+ { -+ return m_nTimerFD >= 0; -+ } -+ -+ int CVBlankTimer::GetFD() -+ { -+ return UsingTimerFD() ? m_nTimerFD : m_nNudgePipe[ 0 ]; -+ } - -- while ( true ) -+ void CVBlankTimer::OnPollIn() - { -- vrsession_wait_until_visible(); -+ if ( UsingTimerFD() ) -+ { -+ std::unique_lock lock( m_ScheduleMutex ); - -- // Includes redzone. -- vrsession_framesync( ~0u ); -+ // Disarm the timer if it was armed. -+ if ( !m_bArmed.exchange( false ) ) -+ return; - -- uint64_t now = get_time_in_nanos(); -+ uint64_t ulNow = get_time_in_nanos(); - -- VBlankTimeInfo_t time_info = -- { -- .target_vblank_time = now + 3'000'000, // not right. just a stop-gap for now. -- .pipe_write_time = now, -- }; -+ m_PendingVBlank = VBlankTime -+ { -+ .schedule = m_TimerFDSchedule, -+ .ulWakeupTime = ulNow, -+ }; -+#ifdef VBLANK_DEBUG -+ fprintf( stderr, "wakeup: %lu\n", ulNow ); -+#endif - -- ssize_t ret = write( g_vblankPipe[ 1 ], &time_info, sizeof( time_info ) ); -- if ( ret <= 0 ) -- { -- perror( "vblankmanager: write failed" ); -+ gpuvis_trace_printf( "vblank timerfd wakeup" ); -+ -+ // Disarm timer. -+ itimerspec timerspec{}; -+ if ( timerfd_settime( m_nTimerFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) -+ g_VBlankLog.errorf_errno( "timerfd_settime failed!" ); - } - else - { -- gpuvis_trace_printf( "sent vblank" ); -+ 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." ); -+ RearmTimer( true ); -+ return; -+ } -+ else if ( ret != sizeof( VBlankTime ) ) -+ { -+ g_VBlankLog.errorf( "Nudge pipe had less data than sizeof( VBlankTime ). Pre-emptively re-arming." ); -+ RearmTimer( 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." ); -+ RearmTimer( true ); -+ return; -+ } -+ -+ gpuvis_trace_printf( "got vblank" ); -+ m_PendingVBlank = time; - } - } --} --#endif - --int vblank_init( void ) --{ -- if ( pipe2( g_vblankPipe, O_CLOEXEC | O_NONBLOCK ) != 0 ) -+ void CVBlankTimer::VBlankDebugSpew( uint64_t ulOffset, uint64_t ulDrawTime, uint64_t ulRedZone ) - { -- perror( "vblankmanager: pipe failed" ); -- return -1; -+#ifdef VBLANK_DEBUG -+ 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 ) -+ fprintf( stderr, " !! missed vblank " ); -+ -+ fprintf( stderr, "redZone: %.2fms decayRate: %lu%% - rollingMaxDrawTime: %.2fms lastDrawTime: %.2fms lastOffset: %.2fms - drawTime: %.2fms offset: %.2fms\n", -+ 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; -+#endif - } -- -- g_lastVblank = get_time_in_nanos(); - - #if HAVE_OPENVR -- if ( BIsVRSession() ) -+ void CVBlankTimer::VRNudgeThread() - { -- std::thread vblankThread( vblankThreadVR ); -- vblankThread.detach(); -- return g_vblankPipe[ 0 ]; -+ pthread_setname_np( pthread_self(), "gamescope-vblkvr" ); -+ -+ for ( ;; ) -+ { -+ vrsession_wait_until_visible(); -+ -+ // Includes redzone. -+ vrsession_framesync( ~0u ); -+ -+ uint64_t ulWakeupTime = get_time_in_nanos(); -+ -+ VBlankTime timeInfo = -+ { -+ .schedule = -+ { -+ .ulTargetVBlank = ulWakeupTime + 3'000'000, // Not right. just a stop-gap for now. -+ .ulScheduledWakeupPoint = ulWakeupTime, -+ }, -+ .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)" ); -+ } -+ } - } - #endif - -- std::thread vblankThread( vblankThreadRun ); -- vblankThread.detach(); -- return g_vblankPipe[ 0 ]; --} -+ void CVBlankTimer::NudgeThread() -+ { -+ pthread_setname_np( pthread_self(), "gamescope-vblk" ); - --void vblank_mark_possible_vblank( uint64_t nanos ) --{ -- g_lastVblank = nanos; -+ for ( ;; ) -+ { -+ WaitToBeArmed(); -+ -+ if ( !m_bRunning ) -+ return; -+ -+ VBlankScheduleTime schedule = CalcNextWakeupTime( false ); -+ sleep_until_nanos( schedule.ulScheduledWakeupPoint ); -+ 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 g_VBlankTimer{}; -+ -diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp -index 3c36ce07a..30a8c4abc 100644 ---- a/src/vblankmanager.hpp -+++ b/src/vblankmanager.hpp -@@ -1,26 +1,139 @@ - #pragma once - --// Try to figure out when vblank is and notify steamcompmgr to render some time before it -+#include -+#include "waitable.h" - --struct VBlankTimeInfo_t -+namespace gamescope - { -- uint64_t target_vblank_time; -- uint64_t pipe_write_time; --}; -+ struct VBlankScheduleTime -+ { -+ // The expected time for the vblank we want to target. -+ uint64_t ulTargetVBlank = 0; -+ // The vblank offset by the redzone/scheduling calculation. -+ // This is when we want to wake-up by to meet that vblank time above. -+ uint64_t ulScheduledWakeupPoint = 0; -+ }; - --int vblank_init( void ); -+ struct VBlankTime -+ { -+ VBlankScheduleTime schedule; -+ // This is when we woke-up either by the timerfd poll -+ // or on the nudge thread. We use this to feed-back into -+ // the draw time so we automatically account for th -+ // CPU scheduler quantums. -+ uint64_t ulWakeupTime = 0; -+ }; - --void vblank_mark_possible_vblank( uint64_t nanos ); -+ class CVBlankTimer : public gamescope::IWaitable -+ { -+ public: -+ static constexpr uint64_t kSecInNanoSecs = 1'000'000'000ul; -+ // 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 kVBlankRateOfDecayMax = 1000ul; // 100% - --uint64_t vblank_next_target( uint64_t offset = 0 ); -+ static constexpr uint64_t kVRRFlushingDrawTime = 1'000'000; // Could possibly be lower, like 300'000 or something. - --extern std::atomic g_uVblankDrawTimeNS; -+ CVBlankTimer(); -+ ~CVBlankTimer(); - --const unsigned int g_uDefaultVBlankRedZone = 1'650'000; --const unsigned int g_uDefaultMinVBlankTime = 350'000; // min vblank time for fps limiter to care about --const unsigned int g_uDefaultVBlankRateOfDecayPercentage = 980; -+ int GetRefresh() const; -+ uint64_t GetLastVBlank() const; -+ uint64_t GetNextVBlank( uint64_t ulOffset ) const; - --extern uint64_t g_uVblankDrawBufferRedZoneNS; --extern uint64_t g_uVBlankRateOfDecayPercentage; -+ VBlankScheduleTime CalcNextWakeupTime( bool bPreemptive ); -+ void Reschedule(); - --extern std::atomic g_bCurrentlyCompositing; -+ std::optional ProcessVBlank(); -+ void MarkVBlank( uint64_t ulNanos, bool bReArmTimer ); -+ -+ bool WasCompositing() const; -+ void UpdateWasCompositing( bool bCompositing ); -+ void UpdateLastDrawTime( uint64_t ulNanos ); -+ -+ void WaitToBeArmed(); -+ void RearmTimer( bool bPreemptive ); -+ -+ bool UsingTimerFD() const; -+ int GetFD() final; -+ void OnPollIn() final; -+ private: -+ 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 }; -+ -+ std::optional m_PendingVBlank; -+ -+ // Should have 0 contest, but just to be safe. -+ // This also covers setting of m_bArmed, etc -+ // so we keep in sequence. -+ // 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; -+ VBlankScheduleTime m_TimerFDSchedule{}; -+ int m_nTimerFD = -1; -+ -+ std::thread m_NudgeThread; -+ int m_nNudgePipe[2] = { -1, -1 }; -+ -+ ///////////////////////////// -+ // Scheduling bits and bobs. -+ ///////////////////////////// -+ -+ // Are we currently compositing? We may need -+ // to push back to avoid clock feedback loops if so. -+ // This is fed-back from steamcompmgr. -+ 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. -+ std::atomic m_ulLastDrawTime = { kStartingVBlankDrawTime }; -+ -+ ////////////////////////////////// -+ // VBlank timing tuneables below! -+ ////////////////////////////////// -+ -+ // Internal rolling peak exponential avg. draw time. -+ // This is updated in CalcNextWakeupTime when not -+ // 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. -+ // 1.3ms by default. (kDefaultMinVBlankTime) -+ uint64_t m_ulMinVBlankTime = kDefaultMinVBlankTime; -+ -+ // The leeway we always apply to our buffer. -+ // 0.3ms by default. (kDefaultVBlankRedZone) -+ 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; -+ -+#if HAVE_OPENVR -+ void VRNudgeThread(); -+#endif -+ void NudgeThread(); -+ }; -+} -+ -+extern gamescope::CVBlankTimer g_VBlankTimer; - -From 89cf3b0bb0eecf7ea1f5bd702a95d47b8bd86730 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Thu, 7 Dec 2023 16:26:38 +0000 -Subject: [PATCH 034/134] vblankmanager: Treat wakeup time as scheduled wakeup - point for timerfd path - -Because we are using timerfd, ulWakeupTime should actually be the target -point of the timerfd when using that, not the the current time from -OnPollIn, so we can account for the scheduling quantums from the target -wakeup time and other work (like we were wrt the pipe write before) ---- - src/vblankmanager.cpp | 11 +++++++++-- - 1 file changed, 9 insertions(+), 2 deletions(-) - -diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp -index 9ccb130eb..df77bdf33 100644 ---- a/src/vblankmanager.cpp -+++ b/src/vblankmanager.cpp -@@ -297,14 +297,21 @@ namespace gamescope - if ( !m_bArmed.exchange( false ) ) - return; - -- uint64_t ulNow = get_time_in_nanos(); - - m_PendingVBlank = VBlankTime - { - .schedule = m_TimerFDSchedule, -- .ulWakeupTime = ulNow, -+ // 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, - }; - #ifdef VBLANK_DEBUG -+ uint64_t ulNow = get_time_in_nanos(); - fprintf( stderr, "wakeup: %lu\n", ulNow ); - #endif - - -From b7c828b38675a11d5d130024a8f6c9703fe5b7fd Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Thu, 7 Dec 2023 21:23:42 +0000 -Subject: [PATCH 035/134] steamcompmgr: Fix repaint checking for fade-outs - ---- - src/steamcompmgr.cpp | 12 +++++------- - 1 file changed, 5 insertions(+), 7 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 33fa43438..6add8f427 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -8021,6 +8021,11 @@ steamcompmgr_main(int argc, char **argv) - 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; -+ - static int nIgnoredOverlayRepaints = 0; - - const bool bVRR = drm_get_vrr_in_use( &g_DRM ); -@@ -8073,13 +8078,6 @@ steamcompmgr_main(int argc, char **argv) - hasRepaint = false; - hasRepaintNonBasePlane = false; - nIgnoredOverlayRepaints = 0; -- -- // If we're in the middle of a fade, pump an event into the loop to -- // make sure we keep pushing frames even if the app isn't updating. -- if ( is_fading_out() ) -- { -- nudge_steamcompmgr(); -- } - } - - if ( vblank ) - -From e4f1e14063e60094f278e8254c116a4bc45282d7 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 12 Dec 2023 22:13:24 +0000 -Subject: [PATCH 036/134] rendervulkan: Don't return incompatible format - screenshot textures - ---- - src/rendervulkan.cpp | 7 ++++++- - src/rendervulkan.hpp | 3 +++ - 2 files changed, 9 insertions(+), 1 deletion(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 3e179c4b1..77c173033 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -1770,6 +1770,7 @@ static VkImageViewType VulkanImageTypeToViewType(VkImageType type) - - 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 ) - { -+ m_drmFormat = drmFormat; - VkResult res = VK_ERROR_INITIALIZATION_FAILED; - - VkImageTiling tiling = (flags.bMappable || flags.bLinear) ? VK_IMAGE_TILING_LINEAR : VK_IMAGE_TILING_OPTIMAL; -@@ -2330,6 +2331,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - - 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; -@@ -3314,7 +3316,10 @@ std::shared_ptr vulkan_acquire_screenshot_texture(uint32_t width - assert( bSuccess ); - } - -- if (pScreenshotImage.use_count() > 1 || width != pScreenshotImage->width() || height != pScreenshotImage->height()) -+ if (pScreenshotImage.use_count() > 1 || -+ width != pScreenshotImage->width() || -+ height != pScreenshotImage->height() || -+ drmFormat != pScreenshotImage->drmFormat()) - continue; - - return pScreenshotImage; -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index c60b796a1..651a1ed1b 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -180,6 +180,7 @@ class CVulkanTexture - inline bool swapchainImage() { return m_bSwapchain; } - 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; } -@@ -206,6 +207,8 @@ class CVulkanTexture - bool m_bExternal = false; - bool m_bSwapchain = false; - -+ uint32_t m_drmFormat = DRM_FORMAT_INVALID; -+ - VkImage m_vkImage = VK_NULL_HANDLE; - VkDeviceMemory m_vkImageMemory = VK_NULL_HANDLE; - - -From 5ca516afd90eb8343fe0906851b805d04ba15b00 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 12 Dec 2023 22:19:23 +0000 -Subject: [PATCH 037/134] pipewire: Allocate buffers for pipewire buffers - directly - -Keep their lifetime in tandem with that, avoids running out of cached buffers and keeping those around too long. ---- - src/pipewire.cpp | 20 ++++++++++++++++++-- - 1 file changed, 18 insertions(+), 2 deletions(-) - -diff --git a/src/pipewire.cpp b/src/pipewire.cpp -index 236e8ac7e..9461dc6ca 100644 ---- a/src/pipewire.cpp -+++ b/src/pipewire.cpp -@@ -1,3 +1,4 @@ -+ - #include - #include - #include -@@ -473,8 +474,23 @@ static void stream_handle_add_buffer(void *user_data, struct pw_buffer *pw_buffe - - uint32_t drmFormat = spa_format_to_drm(state->video_info.format); - -- buffer->texture = vulkan_acquire_screenshot_texture(s_nCaptureWidth, s_nCaptureHeight, is_dmabuf, drmFormat, colorspace); -- assert(buffer->texture != nullptr); -+ buffer->texture = std::make_shared(); -+ 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(); - -From 24bdce9e2e0e25024b1a65ebc974f901685b9f60 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 12 Dec 2023 22:19:38 +0000 -Subject: [PATCH 038/134] rendervulkan: Lower pScreenshotImages to 2 - -No need for as many now pipewire is not using this. ---- - src/rendervulkan.hpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index 651a1ed1b..d452c5bbc 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -495,7 +495,7 @@ struct VulkanOutput_t - VkFormat outputFormat = VK_FORMAT_UNDEFINED; - VkFormat outputFormatOverlay = VK_FORMAT_UNDEFINED; - -- std::array, 9> pScreenshotImages; -+ std::array, 2> pScreenshotImages; - - // NIS and FSR - std::shared_ptr tmpOutput; - -From 5caf3e139b587f2361d85f34ca881b9ce9dd5109 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 12 Dec 2023 22:25:02 +0000 -Subject: [PATCH 039/134] pipewire: Make push_pipewire_buffer in_buffer - exchange non-fatal - ---- - src/pipewire.cpp | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/src/pipewire.cpp b/src/pipewire.cpp -index 9461dc6ca..55505693b 100644 ---- a/src/pipewire.cpp -+++ b/src/pipewire.cpp -@@ -730,7 +730,10 @@ struct pipewire_buffer *dequeue_pipewire_buffer(void) - void push_pipewire_buffer(struct pipewire_buffer *buffer) - { - struct pipewire_buffer *old = in_buffer.exchange(buffer); -- assert(old == nullptr); -+ if ( old != nullptr ) -+ { -+ pwr_log.errorf_errno("push_pipewire_buffer: Already had a buffer?!"); -+ } - nudge_pipewire(); - } - - -From cbb1646359034b04bc36ce7788fded40429ccdbd Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 12 Dec 2023 22:33:29 +0000 -Subject: [PATCH 040/134] shaders: Fix rgb to nv12 being off by half a texel - ---- - src/shaders/cs_rgb_to_nv12.comp | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/src/shaders/cs_rgb_to_nv12.comp b/src/shaders/cs_rgb_to_nv12.comp -index 622965cb9..a15a3d4df 100644 ---- a/src/shaders/cs_rgb_to_nv12.comp -+++ b/src/shaders/cs_rgb_to_nv12.comp -@@ -49,12 +49,12 @@ void main() { - - // todo: fix - if (all(lessThan(thread_id.xy, ivec2(u_halfExtent.x, u_halfExtent.y)))) { -- ivec2 offset_table[4] = { -- ivec2(0, 0), ivec2(1, 0), ivec2(0, 1), ivec2(1, 1), -+ vec2 offset_table[4] = { -+ vec2(0, 0), vec2(1, 0), vec2(0, 1), vec2(1, 1), - }; - - ivec2 chroma_uv = thread_id.xy; -- ivec2 luma_uv = thread_id.xy * 2; -+ vec2 luma_uv = vec2(thread_id.xy * 2) + vec2(0.5f, 0.5f); - - vec3 color[4] = { - sampleLayer(0, vec2(luma_uv.x + offset_table[0].x, luma_uv.y + offset_table[0].y)).rgb, -@@ -69,7 +69,7 @@ void main() { - - for (int i = 0; i < 4; i++) { - float y = applyColorMatrix(color[i], u_outputCTM).x; -- imageStore(dst_luma, luma_uv + offset_table[i], vec4(y, 0.0f, 0.0f, 1.0f)); -+ imageStore(dst_luma, ivec2(luma_uv + offset_table[i]), vec4(y, 0.0f, 0.0f, 1.0f)); - } - } - } - -From d2a396ec235cd4e86437d1b3a205b84f93fc62b9 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 12 Dec 2023 23:13:46 +0000 -Subject: [PATCH 041/134] steamcompmgr: Handle pipewire stream after drm_commit - -Avoids it getting in the way of us doing the actual preparation on-device and missing vblank ---- - src/rendervulkan.cpp | 40 ++++------------ - src/rendervulkan.hpp | 5 +- - src/steamcompmgr.cpp | 108 +++++++++++++++++++++++-------------------- - 3 files changed, 69 insertions(+), 84 deletions(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 77c173033..8940b7704 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -3571,7 +3571,7 @@ void bind_all_layers(CVulkanCmdBuffer* cmdBuffer, const struct FrameInfo_t *fram - } - } - --bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ) -+std::optional vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ) - { - auto cmdBuffer = g_device.commandBuffer(); - -@@ -3588,28 +3588,14 @@ bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptrdispatch(div_roundup(currentOutputWidth, pixelsPerGroup), div_roundup(currentOutputHeight, pixelsPerGroup)); - - uint64_t sequence = g_device.submit(std::move(cmdBuffer)); -- g_device.wait(sequence); -- -- return true; -+ return sequence; - } - - extern std::string g_reshade_effect; - extern uint32_t g_reshade_technique_idx; - --std::unique_ptr defer_wait_thread; --uint64_t defer_sequence = 0; -- --bool vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture, bool partial, bool defer, std::shared_ptr pOutputOverride, bool increment ) -+std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture, bool partial, std::shared_ptr pOutputOverride, bool increment ) - { -- if ( defer_wait_thread ) -- { -- defer_wait_thread->join(); -- defer_wait_thread = nullptr; -- -- g_device.resetCmdBuffers(defer_sequence); -- defer_sequence = 0; -- } -- - EOTF outputTF = g_ColorMgmt.current.outputEncodingEOTF; - if (!frameInfo->applyOutputColorMgmt) - outputTF = EOTF_Count; //Disable blending stuff. -@@ -3830,25 +3816,17 @@ bool vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr([sequence] -- { -- g_device.wait(sequence, false); -- }); -- defer_sequence = sequence; -- } -- else -- { -- g_device.wait(sequence); -- } -- - if ( !BIsSDLSession() && pOutputOverride == nullptr && increment ) - { - g_output.nOutImage = ( g_output.nOutImage + 1 ) % 3; - } - -- return true; -+ return sequence; -+} -+ -+void vulkan_wait( uint64_t ulSeqNo, bool bReset ) -+{ -+ return g_device.wait( ulSeqNo, bReset ); - } - - std::shared_ptr vulkan_get_last_output_image( bool partial, bool defer ) -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index d452c5bbc..e9984695f 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -373,7 +373,8 @@ std::shared_ptr vulkan_create_texture_from_dmabuf( struct wlr_dm - std::shared_ptr 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 ); - std::shared_ptr vulkan_create_texture_from_wlr_buffer( struct wlr_buffer *buf ); - --bool vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture, bool partial, bool deferred, std::shared_ptr pOutputOverride = nullptr, bool increment = true ); -+std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture, bool partial, std::shared_ptr pOutputOverride = nullptr, bool increment = true ); -+void vulkan_wait( uint64_t ulSeqNo, bool bReset ); - std::shared_ptr vulkan_get_last_output_image( bool partial, bool defer ); - std::shared_ptr vulkan_acquire_screenshot_texture(uint32_t width, uint32_t height, bool exportable, uint32_t drmFormat, EStreamColorspace colorspace = k_EStreamColorspace_Unknown); - -@@ -396,7 +397,7 @@ void vulkan_update_luts(const std::shared_ptr& lut1d, const std: - - std::shared_ptr vulkan_get_hacky_blank_texture(); - --bool vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ); -+std::optional vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ); - - struct wlr_renderer *vulkan_renderer_create( void ); - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 6add8f427..b02fa331c 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -2640,11 +2640,6 @@ paint_all(bool async) - bool propertyRequestedScreenshot = g_bPropertyRequestedScreenshot; - g_bPropertyRequestedScreenshot = false; - -- struct pipewire_buffer *pw_buffer = nullptr; --#if HAVE_PIPEWIRE -- pw_buffer = dequeue_pipewire_buffer(); --#endif -- - update_app_target_refresh_cycle(); - - int nDynamicRefresh = g_nDynamicRefreshRate[drm_get_screen_type( &g_DRM )]; -@@ -2665,7 +2660,7 @@ paint_all(bool async) - - bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; - -- bool bDoMuraCompensation = is_mura_correction_enabled() && frameInfo.layerCount && !pw_buffer; -+ bool bDoMuraCompensation = is_mura_correction_enabled() && frameInfo.layerCount; - if ( bDoMuraCompensation ) - { - auto& MuraCorrectionImage = s_MuraCorrectionImage[drm_get_screen_type( &g_DRM )]; -@@ -2693,7 +2688,6 @@ paint_all(bool async) - - bool bNeedsFullComposite = BIsNested(); - bNeedsFullComposite |= alwaysComposite; -- bNeedsFullComposite |= pw_buffer != nullptr; - bNeedsFullComposite |= bWasFirstFrame; - bNeedsFullComposite |= frameInfo.useFSRLayer0; - bNeedsFullComposite |= frameInfo.useNISLayer0; -@@ -2749,14 +2743,6 @@ paint_all(bool async) - if ( kDisablePartialComposition ) - bNeedsFullComposite = true; - -- std::shared_ptr pPipewireTexture = nullptr; --#if HAVE_PIPEWIRE -- if ( pw_buffer != nullptr ) -- { -- pPipewireTexture = pw_buffer->texture; -- } --#endif -- - struct FrameInfo_t compositeFrameInfo = frameInfo; - - if ( compositeFrameInfo.layerCount == 1 ) -@@ -2819,38 +2805,20 @@ paint_all(bool async) - if ( bDefer && !!( g_uCompositeDebug & CompositeDebugFlag::Markers ) ) - g_uCompositeDebug |= CompositeDebugFlag::Markers_Partial; - -- bool bResult; -- // If using a pipewire stream, apply screenshot color management. -- if ( pPipewireTexture ) -- { -- for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) -- { -- compositeFrameInfo.lut3D[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut3d; -- compositeFrameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; -- } -- vulkan_composite( &compositeFrameInfo, pPipewireTexture, !bNeedsFullComposite, bDefer, nullptr, false ); -- for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) -- { -- if (g_ColorMgmtLuts[nInputEOTF].HasLuts()) -- { -- compositeFrameInfo.shaperLut[nInputEOTF] = g_ColorMgmtLuts[nInputEOTF].vk_lut1d; -- compositeFrameInfo.lut3D[nInputEOTF] = g_ColorMgmtLuts[nInputEOTF].vk_lut3d; -- } -- } -- } -- -- bResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite, bDefer ); -+ std::optional oCompositeResult = vulkan_composite( &compositeFrameInfo, nullptr, !bNeedsFullComposite ); - - g_bWasCompositing = true; - - g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; - -- if ( bResult != true ) -+ if ( !oCompositeResult ) - { - xwm_log.errorf("vulkan_composite failed"); - return; - } - -+ vulkan_wait( *oCompositeResult, true ); -+ - if ( BIsNested() == true ) - { - #if HAVE_OPENVR -@@ -2994,15 +2962,6 @@ paint_all(bool async) - - drm_commit( &g_DRM, &compositeFrameInfo ); - } -- --#if HAVE_PIPEWIRE -- if ( pw_buffer != nullptr ) -- { -- push_pipewire_buffer(pw_buffer); -- // TODO: make sure the pw_buffer isn't lost in one of the failure -- // code-paths above -- } --#endif - } - else - { -@@ -3011,6 +2970,51 @@ paint_all(bool async) - drm_commit( &g_DRM, &frameInfo ); - } - -+#if HAVE_PIPEWIRE -+ struct pipewire_buffer *pw_buffer = dequeue_pipewire_buffer(); -+ if ( pw_buffer ) -+ { -+ if ( pw_buffer->texture ) -+ { -+ struct FrameInfo_t pipewireFrameInfo = frameInfo; -+ -+ // If using a pipewire stream, apply screenshot color management. -+ for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ ) -+ { -+ pipewireFrameInfo.lut3D[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut3d; -+ pipewireFrameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; -+ } -+ -+ if ( is_mura_correction_enabled() ) -+ { -+ // Remove the last layer which is for mura... -+ for (int i = 0; i < pipewireFrameInfo.layerCount; i++) -+ { -+ if (pipewireFrameInfo.layers[i].zpos >= (int)g_zposMuraCorrection) -+ { -+ pipewireFrameInfo.layerCount = i; -+ break; -+ } -+ } -+ -+ // Re-enable output color management (blending) if it was disabled by mura. -+ pipewireFrameInfo.applyOutputColorMgmt = true; -+ } -+ -+ std::optional oPipewireSequence = vulkan_composite( &pipewireFrameInfo, pw_buffer->texture, false, nullptr, false ); -+ -+ if ( oPipewireSequence ) -+ { -+ vulkan_wait( *oPipewireSequence, true ); -+ -+ push_pipewire_buffer( pw_buffer ); -+ // TODO: make sure the pw_buffer isn't lost in one of the failure -+ // code-paths above -+ } -+ } -+ } -+#endif -+ - if ( takeScreenshot ) - { - uint32_t drmCaptureFormat = bHackForceNV12DumpScreenshot -@@ -3067,20 +3071,22 @@ paint_all(bool async) - - frameInfo.applyOutputColorMgmt = true; - -- bool bResult; -+ std::optional oScreenshotSeq; - if ( drmCaptureFormat == DRM_FORMAT_NV12 ) -- bResult = vulkan_composite( &frameInfo, pScreenshotTexture, false, false, nullptr ); -+ oScreenshotSeq = vulkan_composite( &frameInfo, pScreenshotTexture, false, nullptr ); - else if ( takeScreenshot == TAKE_SCREENSHOT_FULL_COMPOSITION || takeScreenshot == TAKE_SCREENSHOT_SCREEN_BUFFER ) -- bResult = vulkan_composite( &frameInfo, nullptr, false, false, pScreenshotTexture ); -+ oScreenshotSeq = vulkan_composite( &frameInfo, nullptr, false, pScreenshotTexture ); - else -- bResult = vulkan_screenshot( &frameInfo, pScreenshotTexture ); -+ oScreenshotSeq = vulkan_screenshot( &frameInfo, pScreenshotTexture ); - -- if ( bResult != true ) -+ if ( !oScreenshotSeq ) - { - xwm_log.errorf("vulkan_screenshot failed"); - return; - } - -+ vulkan_wait( *oScreenshotSeq, false ); -+ - std::thread screenshotThread = std::thread([=] { - pthread_setname_np( pthread_self(), "gamescope-scrsh" ); - - -From 52624de3616e8f64111334bfbf2ca69acbbf9ed9 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 12 Dec 2023 23:32:21 +0000 -Subject: [PATCH 042/134] pipewire: Nudge on stream_handle_remove_buffer if - copying - ---- - src/pipewire.cpp | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/src/pipewire.cpp b/src/pipewire.cpp -index 55505693b..bde1d87cb 100644 ---- a/src/pipewire.cpp -+++ b/src/pipewire.cpp -@@ -571,6 +571,8 @@ static void stream_handle_remove_buffer(void *data, struct pw_buffer *pw_buffer) - - if (!buffer->copying) { - destroy_buffer(buffer); -+ } else { -+ nudge_pipewire(); - } - } - - -From ed6cd455beca4c60dd6a1a53ce3c84643b5da6e0 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 13 Dec 2023 00:12:03 +0000 -Subject: [PATCH 043/134] pipewire: Make state change an infof, not a debugf - ---- - src/pipewire.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/pipewire.cpp b/src/pipewire.cpp -index bde1d87cb..6eab648ce 100644 ---- a/src/pipewire.cpp -+++ b/src/pipewire.cpp -@@ -296,7 +296,7 @@ static void stream_handle_state_changed(void *data, enum pw_stream_state old_str - { - struct pipewire_state *state = (struct pipewire_state *) data; - -- pwr_log.debugf("stream state changed: %s", pw_stream_state_as_string(stream_state)); -+ pwr_log.infof("stream state changed: %s", pw_stream_state_as_string(stream_state)); - - switch (stream_state) { - case PW_STREAM_STATE_PAUSED: - -From 4e0a42f2e09628ee1f664c23dd876b556a63b105 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 13 Dec 2023 01:38:50 +0000 -Subject: [PATCH 044/134] layer: Fix newline in GAMESCOPE_WSI_BYPASS_DEBUG log - ---- - layer/VkLayer_FROG_gamescope_wsi.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp -index 77636cbc9..c08caa2e7 100644 ---- a/layer/VkLayer_FROG_gamescope_wsi.cpp -+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp -@@ -187,7 +187,7 @@ namespace GamescopeWSILayer { - iabs(int32_t(toplevelRect->extent.width) - int32_t(rect->extent.width)) > 1 || - iabs(int32_t(toplevelRect->extent.height) - int32_t(rect->extent.height)) > 1) { - #if GAMESCOPE_WSI_BYPASS_DEBUG -- fprintf(stderr, "[Gamescope WSI] Not within 1px margin of error. Offset: %d %d Extent: %u %u vs %u %u", -+ fprintf(stderr, "[Gamescope WSI] Not within 1px margin of error. Offset: %d %d Extent: %u %u vs %u %u\n", - rect->offset.x, rect->offset.y, - toplevelRect->extent.width, toplevelRect->extent.height, - rect->extent.width, rect->extent.height); - -From a2f8db9fdb20975582535828533619603705b257 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 13 Dec 2023 01:47:53 +0000 -Subject: [PATCH 045/134] wlserver: Fix destroying content overrides for active - windows - ---- - src/wlserver.cpp | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 44f2b6724..3fbc4ff58 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -542,6 +542,9 @@ void gamescope_xwayland_server_t::destroy_content_override( struct wlserver_x11_ - if (iter == content_overrides.end()) - return; - -+ if ( x11_surface->override_surface == surf ) -+ x11_surface->override_surface = nullptr; -+ - struct wlserver_content_override *co = iter->second; - if (co->surface == surf) - destroy_content_override(iter->second); - -From 3e14ef9c37266b19ba77fbef467d1b8a77d827f2 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 13 Dec 2023 02:14:32 +0000 -Subject: [PATCH 046/134] layer: Add GAMESCOPE_WSI_FORCE_BYPASS - ---- - layer/VkLayer_FROG_gamescope_wsi.cpp | 11 +++++++++++ - src/layer_defines.h | 1 + - 2 files changed, 12 insertions(+) - -diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp -index c08caa2e7..946f53802 100644 ---- a/layer/VkLayer_FROG_gamescope_wsi.cpp -+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp -@@ -92,6 +92,10 @@ namespace GamescopeWSILayer { - static GamescopeLayerClient::Flags defaultLayerClientFlags(uint32_t appid) { - GamescopeLayerClient::Flags flags = 0; - -+ const char *bypassEnv = getenv("GAMESCOPE_WSI_FORCE_BYPASS"); -+ if (bypassEnv && *bypassEnv && atoi(bypassEnv) != 0) -+ flags |= GamescopeLayerClient::Flag::ForceBypass; -+ - // My Little Pony: A Maretime Bay Adventure picks a HDR colorspace if available, - // but does not render as HDR at all. - if (appid == 1600780) -@@ -169,6 +173,13 @@ namespace GamescopeWSILayer { - return false; - } - -+ // Some games do things like have a 1280x800 top-level window and -+ // a 1280x720 child window for "fullscreen". -+ // To avoid Glamor work on the XWayland side of things, have a -+ // flag to force bypassing this. -+ if (!!(flags & GamescopeLayerClient::Flag::ForceBypass)) -+ return true; -+ - // If we have any child windows obscuring us bigger than 1x1, - // then we cannot flip. - // (There can be dummy composite redirect windows and whatever.) -diff --git a/src/layer_defines.h b/src/layer_defines.h -index fb34465a9..ef9a10792 100644 ---- a/src/layer_defines.h -+++ b/src/layer_defines.h -@@ -8,6 +8,7 @@ namespace GamescopeLayerClient - // GAMESCOPE_LAYER_CLIENT_FLAGS - namespace Flag { - static constexpr uint32_t DisableHDR = 1u << 0; -+ static constexpr uint32_t ForceBypass = 1u << 1; - } - using Flags = uint32_t; - } -\ No newline at end of file - -From 243582c0c7625577a2535c3363ae345de713f323 Mon Sep 17 00:00:00 2001 -From: Lionel Landwerlin -Date: Thu, 14 Dec 2023 16:13:22 +0200 -Subject: [PATCH 047/134] renderervulkan: only consider modifiers support all - the image properties - -If you have a modifier with image compression that is only support for -some image formats like : - - B8G8R8A8_UNORM : compression supported - - B8G8R8A8_SRB : compression not supported - -Then the render will not use compression but the modifier indicates it -is present, so the compositor will try to make use of the side -compressed data and this will lead to corruptions. - -This fixes issues on Intel HW of Gfx9 generations. ---- - src/rendervulkan.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++ - 1 file changed, 65 insertions(+) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 8940b7704..936c1d6a4 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -2449,6 +2449,49 @@ int CVulkanTexture::memoryFence() - return fence; - } - -+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; -+} - - bool vulkan_init_format(VkFormat format, uint32_t drmFormat) - { -@@ -2461,6 +2504,25 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) - .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; -+ } -+ -+ - VkImageFormatProperties2 imageFormatProps = { - .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, - }; -@@ -2518,6 +2580,9 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) - - 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; - -From 9888a50cbdf9f4538652ab616580f1e3dd3beb64 Mon Sep 17 00:00:00 2001 -From: Tatsuyuki Ishi -Date: Fri, 8 Dec 2023 14:02:41 +0900 -Subject: [PATCH 048/134] steamcompmgr: Fix calculated refresh cycle for - present timing - -The "target FPS" feature divides vblank to achieve a target refresh -rate. Previously, the target frame time was divided by the divisor -again, making it way too small and nonsensical. Correctly calculate -this by multiplying instead of dividing. - -Since the real refresh rate may be odd, apply the multiplier after -dividing to avoid rounding error. ---- - src/steamcompmgr.cpp | 5 ++--- - 1 file changed, 2 insertions(+), 3 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index b02fa331c..2042b9125 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -7916,11 +7916,10 @@ steamcompmgr_main(int argc, char **argv) - int nRealRefresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh; - int nTargetFPS = g_nSteamCompMgrTargetFPS ? g_nSteamCompMgrTargetFPS : nRealRefresh; - nTargetFPS = std::min( nTargetFPS, nRealRefresh ); -- int nMultiplier = nRealRefresh / nTargetFPS; -+ int nVblankDivisor = nRealRefresh / nTargetFPS; - -- int nAppRefresh = nRealRefresh * nMultiplier; - g_SteamCompMgrAppRefreshCycle = 1'000'000'000ul / nRealRefresh; -- g_SteamCompMgrLimitedAppRefreshCycle = 1'000'000'000ul / nAppRefresh; -+ g_SteamCompMgrLimitedAppRefreshCycle = 1'000'000'000ul / nRealRefresh * nVblankDivisor; - } - - // Handle presentation-time stuff - -From 618a154391032188bad2a36fb541b18c0abd1109 Mon Sep 17 00:00:00 2001 -From: Tatsuyuki Ishi -Date: Mon, 18 Dec 2023 20:30:56 +0900 -Subject: [PATCH 049/134] drm: Initialize the owned field of blob wrappers - properly - -This was leading to use-after-free of HDR metadata blobs which can show up -as modeset failures. ---- - src/drm.hpp | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/src/drm.hpp b/src/drm.hpp -index 681079700..a0893464a 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -67,7 +67,7 @@ struct wlserver_hdr_metadata - } - - wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) -- : blob(blob) -+ : blob(blob), owned(owned) - { - if (_metadata) - this->metadata = *_metadata; -@@ -94,6 +94,7 @@ struct wlserver_ctm - wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) - : matrix(ctm) - , blob(blob) -+ , owned(owned) - { - } - - -From 9a19aeeb55d809cdf34fe1f8dee7c12112409d12 Mon Sep 17 00:00:00 2001 -From: Tatsuyuki Ishi -Date: Mon, 18 Dec 2023 19:31:13 +0900 -Subject: [PATCH 050/134] drm: Introduce a drm_blob abstraction - -This becomes the base class of the existing wlserver blob structs, and -will be used for more DRM state tracking in future. - -The copy and move constructors are deleted to ensure we don't mishandle -lifetimes. - -One potential evolution direction is to make this take the blob data -struct as a generic parameter and make it handle creation as well, but -keep it simple for now. ---- - src/drm.hpp | 55 +++++++++++++++++++++++++++++++---------------------- - 1 file changed, 32 insertions(+), 23 deletions(-) - -diff --git a/src/drm.hpp b/src/drm.hpp -index a0893464a..608a4dc59 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -59,54 +59,63 @@ extern "C" - extern struct drm_t g_DRM; - void drm_destroy_blob(struct drm_t *drm, uint32_t blob); - --struct wlserver_hdr_metadata -+class drm_blob - { -- wlserver_hdr_metadata() -+public: -+ drm_blob() : blob( 0 ), owned( false ) - { -- - } - -- wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) -- : blob(blob), owned(owned) -+ drm_blob(uint32_t blob, bool owned = true) -+ : blob( blob ), owned( owned ) - { -- if (_metadata) -- this->metadata = *_metadata; - } - -- ~wlserver_hdr_metadata() -+ ~drm_blob() - { -- if ( blob && owned ) -+ if (blob && owned) - drm_destroy_blob( &g_DRM, blob ); - } - -- hdr_output_metadata metadata = {}; -- uint32_t blob = 0; -- bool owned = true; -+ // No copy constructor, because we can't duplicate the blob handle. -+ drm_blob(const drm_blob&) = delete; -+ drm_blob& operator=(const drm_blob&) = delete; -+ // No move constructor, because we use shared_ptr anyway, but can be added if necessary. -+ drm_blob(drm_blob&&) = delete; -+ drm_blob& operator=(drm_blob&&) = delete; -+ -+ uint32_t blob; -+ bool owned; - }; - --struct wlserver_ctm -+struct wlserver_hdr_metadata : drm_blob - { -- wlserver_ctm() -+ wlserver_hdr_metadata() - { -+ } - -+ wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) -+ : drm_blob( blob, owned ) -+ { -+ if (_metadata) -+ this->metadata = *_metadata; - } - -- wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) -- : matrix(ctm) -- , blob(blob) -- , owned(owned) -+ hdr_output_metadata metadata = {}; -+}; -+ -+struct wlserver_ctm : drm_blob -+{ -+ wlserver_ctm() - { - } - -- ~wlserver_ctm() -+ wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) -+ : drm_blob( blob, owned ), matrix( ctm ) - { -- if ( blob && owned ) -- drm_destroy_blob( &g_DRM, blob ); - } - - glm::mat3x4 matrix{}; -- uint32_t blob = 0; -- bool owned = true; - }; - - #include - -From 128951fcd0ec2d05445ffe2005681a40b9b784d8 Mon Sep 17 00:00:00 2001 -From: Tatsuyuki Ishi -Date: Mon, 18 Dec 2023 20:29:04 +0900 -Subject: [PATCH 051/134] drm: Port mode and lut tracking to drm_blob - -This also fixes an issue where blobs were leaked due to current = pending -assignment happening earlier than when it was supposed to be. ---- - src/drm.cpp | 25 ++++++++----------------- - src/drm.hpp | 6 +++--- - 2 files changed, 11 insertions(+), 20 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index c2694f00f..400109ac1 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -1733,18 +1733,9 @@ int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) - - drm->current = drm->pending; - -- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) -+ for (auto & crtc : drm->crtcs) - { -- if ( drm->pending.mode_id != drm->current.mode_id ) -- drmModeDestroyPropertyBlob(drm->fd, drm->current.mode_id); -- for ( uint32_t i = 0; i < EOTF_Count; i++ ) -- { -- if ( drm->pending.lut3d_id[i] != drm->current.lut3d_id[i] ) -- drmModeDestroyPropertyBlob(drm->fd, drm->current.lut3d_id[i]); -- if ( drm->pending.shaperlut_id[i] != drm->current.shaperlut_id[i] ) -- drmModeDestroyPropertyBlob(drm->fd, drm->current.shaperlut_id[i]); -- } -- drm->crtcs[i].current = drm->crtcs[i].pending; -+ crtc.current = crtc.pending; - } - - for (auto &kv : drm->connectors) { -@@ -2427,9 +2418,9 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - - if ( !g_bDisableShaperAnd3DLUT ) - { -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ] ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", shaper_tf ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ] ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); - // 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 -@@ -2708,7 +2699,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - return ret; - } - -- ret = add_crtc_property(drm->req, drm->crtc, "MODE_ID", drm->pending.mode_id); -+ ret = add_crtc_property(drm->req, drm->crtc, "MODE_ID", drm->pending.mode_id->blob); - if (ret < 0) - return ret; - -@@ -2952,14 +2943,14 @@ bool drm_update_color_mgmt(struct drm_t *drm) - drm_log.errorf_errno("Unable to create SHAPERLUT property blob"); - return false; - } -- drm->pending.shaperlut_id[ i ] = shaper_blob_id; -+ drm->pending.shaperlut_id[ i ] = std::make_shared( shaper_blob_id ); - - uint32_t lut3d_blob_id = 0; - if (drmModeCreatePropertyBlob(drm->fd, g_ColorMgmtLuts[i].lut3d, sizeof(g_ColorMgmtLuts[i].lut3d), &lut3d_blob_id) != 0) { - drm_log.errorf_errno("Unable to create LUT3D property blob"); - return false; - } -- drm->pending.lut3d_id[ i ] = lut3d_blob_id; -+ drm->pending.lut3d_id[ i ] = std::make_shared( lut3d_blob_id ); - } - - return true; -@@ -3015,7 +3006,7 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) - - drm_log.infof("selecting mode %dx%d@%uHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); - -- drm->pending.mode_id = mode_id; -+ drm->pending.mode_id = std::make_shared(mode_id); - drm->needs_modeset = true; - - g_nOutputRefresh = mode->vrefresh; -diff --git a/src/drm.hpp b/src/drm.hpp -index 608a4dc59..4840e2b8d 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -271,10 +271,10 @@ struct drm_t { - std::shared_ptr sdr_static_metadata; - - struct { -- uint32_t mode_id; -+ std::shared_ptr mode_id; - uint32_t color_mgmt_serial; -- uint32_t lut3d_id[ EOTF_Count ]; -- uint32_t shaperlut_id[ EOTF_Count ]; -+ std::shared_ptr lut3d_id[ EOTF_Count ]; -+ std::shared_ptr shaperlut_id[ EOTF_Count ]; - enum drm_screen_type screen_type = DRM_SCREEN_TYPE_INTERNAL; - bool vrr_enabled = false; - drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; - -From 37563f9f5139e9edb5e8ac8bdbae0707342e2bba Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 18 Dec 2023 03:04:46 +0000 -Subject: [PATCH 052/134] layer: Log more about minImageCount - ---- - layer/VkLayer_FROG_gamescope_wsi.cpp | 44 +++++++++++++++++++++------- - 1 file changed, 33 insertions(+), 11 deletions(-) - -diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp -index 946f53802..a2a93d1c4 100644 ---- a/layer/VkLayer_FROG_gamescope_wsi.cpp -+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp -@@ -7,6 +7,7 @@ - #include "../src/color_helpers.h" - #include "../src/layer_defines.h" - -+#include - #include - #include - #include -@@ -699,20 +700,41 @@ namespace GamescopeWSILayer { - return s_isRunningUnderGamescope; - } - -+ template -+ static std::optional parseEnv(const char *envName) { -+ const char *str = std::getenv(envName); -+ if (!str || !*str) -+ return std::nullopt; -+ -+ T value; -+ auto result = std::from_chars(str, str + strlen(str), value); -+ if (result.ec != std::errc{}) -+ return std::nullopt; -+ -+ return value; -+ } -+ - static uint32_t getMinImageCount() { -- { -- const char *overrideStr = std::getenv("GAMESCOPE_WSI_MIN_IMAGE_COUNT"); -- if (overrideStr && *overrideStr) -- return uint32_t(std::atoi(overrideStr)); -- } -+ static uint32_t s_minImageCount = []() -> uint32_t { -+ if (auto minCount = parseEnv("GAMESCOPE_WSI_MIN_IMAGE_COUNT")) { -+ fprintf(stderr, "[Gamescope WSI] minImageCount overridden by GAMESCOPE_WSI_MIN_IMAGE_COUNT: %u\n", *minCount); -+ return *minCount; -+ } - -- { -- const char *overrideStr = std::getenv("vk_x11_override_min_image_count"); -- if (overrideStr && *overrideStr) -- return uint32_t(std::atoi(overrideStr)); -- } -+ if (auto minCount = parseEnv("vk_wsi_override_min_image_count")) { -+ fprintf(stderr, "[Gamescope WSI] minImageCount overridden by vk_wsi_override_min_image_count: %u\n", *minCount); -+ return *minCount; -+ } -+ -+ if (auto minCount = parseEnv("vk_x11_override_min_image_count")) { -+ fprintf(stderr, "[Gamescope WSI] minImageCount overridden by vk_x11_override_min_image_count: %u\n", *minCount); -+ return *minCount; -+ } -+ -+ return 3u; -+ }(); - -- return 3; -+ return s_minImageCount; - } - - static constexpr wl_registry_listener s_registryListener = { - -From b1c4c9685bfe14d2f8c964270578a95a86beacf8 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 19 Dec 2023 01:43:43 +0000 -Subject: [PATCH 053/134] layer: Print more info about VkHdrMetadata - ---- - layer/VkLayer_FROG_gamescope_wsi.cpp | 11 ++++++++--- - 1 file changed, 8 insertions(+), 3 deletions(-) - -diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp -index a2a93d1c4..0bc339514 100644 ---- a/layer/VkLayer_FROG_gamescope_wsi.cpp -+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp -@@ -1013,9 +1013,14 @@ namespace GamescopeWSILayer { - nits_to_u16(metadata.maxContentLightLevel), - nits_to_u16(metadata.maxFrameAverageLightLevel)); - -- fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: mastering luminance min %f nits, max %f nits\n", metadata.minLuminance, metadata.maxLuminance); -- fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: maxContentLightLevel %f nits\n", metadata.maxContentLightLevel); -- fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: maxFrameAverageLightLevel %f nits\n", metadata.maxFrameAverageLightLevel); -+ fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: display primaries:\n", metadata.minLuminance, metadata.maxLuminance); -+ fprintf(stderr, " r: %.4g %.4g\n", metadata.displayPrimaryRed.x, metadata.displayPrimaryRed.y); -+ fprintf(stderr, " g: %.4g %.4g\n", metadata.displayPrimaryGreen.x, metadata.displayPrimaryGreen.y); -+ fprintf(stderr, " b: %.4g %.4g\n", metadata.displayPrimaryBlue.x, metadata.displayPrimaryBlue.y); -+ fprintf(stderr, " w: %.4g %.4g\n", metadata.whitePoint.x, metadata.whitePoint.y); -+ fprintf(stderr, " mastering luminance: min %g nits, max %g nits\n", metadata.minLuminance, metadata.maxLuminance); -+ fprintf(stderr, " maxContentLightLevel: %g nits\n", metadata.maxContentLightLevel); -+ fprintf(stderr, " maxFrameAverageLightLevel: %g nits\n", metadata.maxFrameAverageLightLevel); - } - } - - -From 0868a4ab5e763b7e23ad97a84792a4fe60b0a74d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Cl=C3=A9ment=20Gu=C3=A9rin?= -Date: Tue, 19 Dec 2023 16:34:17 -0800 -Subject: [PATCH 054/134] drm: fix NPE while in headless mode - -caused by e810317 ---- - src/drm.cpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 400109ac1..2a7208f72 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -3232,6 +3232,7 @@ void drm_get_native_colorimetry( struct drm_t *drm, - *displayEOTF = EOTF_Gamma22; - *outputEncodingColorimetry = displaycolorimetry_709; - *outputEncodingEOTF = EOTF_Gamma22; -+ return; - } - - *displayColorimetry = drm->connector->metadata.colorimetry; - -From 38e21856103ae872e75e9c4c05b978e2a5ae743b Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 20 Dec 2023 09:05:47 +0000 -Subject: [PATCH 055/134] waitable: Factor out common ITimerWaitable - ---- - src/steamcompmgr.cpp | 4 +-- - src/vblankmanager.cpp | 40 ++++++------------------ - src/vblankmanager.hpp | 5 ++- - src/waitable.h | 72 +++++++++++++++++++++++++++++++++++++++++++ - 4 files changed, 85 insertions(+), 36 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 2042b9125..40f812531 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -7703,7 +7703,7 @@ steamcompmgr_main(int argc, char **argv) - - bool vblank = false; - g_SteamCompMgrWaiter.AddWaitable( &g_VBlankTimer ); -- g_VBlankTimer.RearmTimer( true ); -+ g_VBlankTimer.ArmNextVBlank( true ); - - { - gamescope_xwayland_server_t *pServer = NULL; -@@ -8092,7 +8092,7 @@ steamcompmgr_main(int argc, char **argv) - // - // Juuust in case pageflip handler doesn't happen - // so we don't stop vblanking forever. -- g_VBlankTimer.RearmTimer( true ); -+ g_VBlankTimer.ArmNextVBlank( true ); - } - - update_vrr_atoms(root_ctx, false, &flush_root); -diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp -index df77bdf33..2a730501a 100644 ---- a/src/vblankmanager.cpp -+++ b/src/vblankmanager.cpp -@@ -11,7 +11,6 @@ - #include - #include - #include --#include - - #include "gpuvis_trace_utils.h" - -@@ -43,12 +42,6 @@ namespace gamescope - if ( bShouldUseTimerFD ) - { - g_VBlankLog.infof( "Using timerfd." ); -- m_nTimerFD = timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC ); -- if ( m_nTimerFD < 0 ) -- { -- g_VBlankLog.errorf_errno( "Failed to create VBlankTimer timerfd." ); -- abort(); -- } - } - else - { -@@ -84,12 +77,6 @@ namespace gamescope - m_bArmed = true; - m_bArmed.notify_all(); - -- if ( m_nTimerFD >= 0 ) -- { -- close( m_nTimerFD ); -- m_nTimerFD = 0; -- } -- - for ( int i = 0; i < 2; i++ ) - { - if ( m_nNudgePipe[ i ] >= 0 ) -@@ -226,7 +213,7 @@ namespace gamescope - if ( bReArmTimer ) - { - // Force timer re-arm with the new vblank timings. -- RearmTimer( false ); -+ ArmNextVBlank( false ); - } - } - -@@ -251,7 +238,7 @@ namespace gamescope - m_bArmed.wait( false ); - } - -- void CVBlankTimer::RearmTimer( bool bPreemptive ) -+ void CVBlankTimer::ArmNextVBlank( bool bPreemptive ) - { - std::unique_lock lock( m_ScheduleMutex ); - -@@ -267,24 +254,18 @@ namespace gamescope - { - m_TimerFDSchedule = CalcNextWakeupTime( bPreemptive ); - -- itimerspec timerspec = -- { -- .it_interval = timespec{}, -- .it_value = nanos_to_timespec( m_TimerFDSchedule.ulScheduledWakeupPoint ), -- }; -- if ( timerfd_settime( m_nTimerFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) -- g_VBlankLog.errorf_errno( "timerfd_settime failed!" ); -+ ITimerWaitable::ArmTimer( m_TimerFDSchedule.ulScheduledWakeupPoint ); - } - } - - bool CVBlankTimer::UsingTimerFD() const - { -- return m_nTimerFD >= 0; -+ return m_nNudgePipe[ 0 ] < 0; - } - - int CVBlankTimer::GetFD() - { -- return UsingTimerFD() ? m_nTimerFD : m_nNudgePipe[ 0 ]; -+ return UsingTimerFD() ? ITimerWaitable::GetFD() : m_nNudgePipe[ 0 ]; - } - - void CVBlankTimer::OnPollIn() -@@ -317,10 +298,7 @@ namespace gamescope - - gpuvis_trace_printf( "vblank timerfd wakeup" ); - -- // Disarm timer. -- itimerspec timerspec{}; -- if ( timerfd_settime( m_nTimerFD, TFD_TIMER_ABSTIME, &timerspec, NULL ) < 0 ) -- g_VBlankLog.errorf_errno( "timerfd_settime failed!" ); -+ ITimerWaitable::DisarmTimer(); - } - else - { -@@ -335,13 +313,13 @@ namespace gamescope - continue; - - g_VBlankLog.errorf_errno( "Failed to read nudge pipe. Pre-emptively re-arming." ); -- RearmTimer( true ); -+ ArmNextVBlank( true ); - return; - } - else if ( ret != sizeof( VBlankTime ) ) - { - g_VBlankLog.errorf( "Nudge pipe had less data than sizeof( VBlankTime ). Pre-emptively re-arming." ); -- RearmTimer( true ); -+ ArmNextVBlank( true ); - return; - } - else -@@ -354,7 +332,7 @@ namespace gamescope - if ( ulDiff > 1'000'000ul ) - { - gpuvis_trace_printf( "Ignoring stale vblank... Pre-emptively re-arming." ); -- RearmTimer( true ); -+ ArmNextVBlank( true ); - return; - } - -diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp -index 30a8c4abc..0a7f1c428 100644 ---- a/src/vblankmanager.hpp -+++ b/src/vblankmanager.hpp -@@ -24,7 +24,7 @@ namespace gamescope - uint64_t ulWakeupTime = 0; - }; - -- class CVBlankTimer : public gamescope::IWaitable -+ class CVBlankTimer : public ITimerWaitable - { - public: - static constexpr uint64_t kSecInNanoSecs = 1'000'000'000ul; -@@ -57,7 +57,7 @@ namespace gamescope - void UpdateLastDrawTime( uint64_t ulNanos ); - - void WaitToBeArmed(); -- void RearmTimer( bool bPreemptive ); -+ void ArmNextVBlank( bool bPreemptive ); - - bool UsingTimerFD() const; - int GetFD() final; -@@ -80,7 +80,6 @@ namespace gamescope - // Does not cover m_ulLastVBlank, this is just atomic. - std::mutex m_ScheduleMutex; - VBlankScheduleTime m_TimerFDSchedule{}; -- int m_nTimerFD = -1; - - std::thread m_NudgeThread; - int m_nNudgePipe[2] = { -1, -1 }; -diff --git a/src/waitable.h b/src/waitable.h -index a8f1f212a..1882e6e4d 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -4,6 +4,7 @@ - #include - #include - #include -+#include - - #include - -@@ -11,6 +12,8 @@ - - extern LogScope g_WaitableLog; - -+timespec nanos_to_timespec( uint64_t ulNanos ); -+ - namespace gamescope - { - class IWaitable -@@ -128,6 +131,75 @@ namespace gamescope - std::function m_fnPollFunc; - }; - -+ class ITimerWaitable : public IWaitable -+ { -+ public: -+ ITimerWaitable() -+ { -+ 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(); -+ } -+ -+ void Shutdown() -+ { -+ if ( m_nFD >= 0 ) -+ { -+ close( m_nFD ); -+ m_nFD = -1; -+ } -+ } -+ -+ 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!" ); -+ } -+ -+ void DisarmTimer() -+ { -+ ArmTimer( 0ul, false ); -+ } -+ -+ int GetFD() -+ { -+ return m_nFD; -+ } -+ private: -+ int m_nFD = -1; -+ }; -+ -+ class CTimerFunction final : public ITimerWaitable -+ { -+ public: -+ CTimerFunction( std::function fnPollFunc ) -+ : m_fnPollFunc{ fnPollFunc } -+ { -+ } -+ -+ void OnPollIn() final -+ { -+ m_fnPollFunc(); -+ } -+ private: -+ std::function m_fnPollFunc; -+ }; -+ - template - class CWaiter - { - -From 3a97ba3e5cd459f1ab9513aab3b649b51f3dddc7 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 20 Dec 2023 09:06:44 +0000 -Subject: [PATCH 056/134] waitable: Fix some FD close settings - ---- - src/vblankmanager.cpp | 2 +- - src/waitable.h | 3 +++ - 2 files changed, 4 insertions(+), 1 deletion(-) - -diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp -index 2a730501a..b62fc9023 100644 ---- a/src/vblankmanager.cpp -+++ b/src/vblankmanager.cpp -@@ -82,7 +82,7 @@ namespace gamescope - if ( m_nNudgePipe[ i ] >= 0 ) - { - close ( m_nNudgePipe[ i ] ); -- m_nNudgePipe[ i ] = 0; -+ m_nNudgePipe[ i ] = -1; - } - } - } -diff --git a/src/waitable.h b/src/waitable.h -index 1882e6e4d..cd806f698 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -78,7 +78,10 @@ namespace gamescope - for ( int i = 0; i < 2; i++ ) - { - if ( m_nFDs[i] >= 0 ) -+ { - close( m_nFDs[i] ); -+ m_nFDs[i] = -1; -+ } - } - } - - -From b1879a3728d617fd3cc8eb4d3b07d833a7942822 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 20 Dec 2023 09:12:24 +0000 -Subject: [PATCH 057/134] layer: Fix warning on printf - ---- - layer/VkLayer_FROG_gamescope_wsi.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp -index 0bc339514..5844c2a63 100644 ---- a/layer/VkLayer_FROG_gamescope_wsi.cpp -+++ b/layer/VkLayer_FROG_gamescope_wsi.cpp -@@ -1013,7 +1013,7 @@ namespace GamescopeWSILayer { - nits_to_u16(metadata.maxContentLightLevel), - nits_to_u16(metadata.maxFrameAverageLightLevel)); - -- fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: display primaries:\n", metadata.minLuminance, metadata.maxLuminance); -+ fprintf(stderr, "[Gamescope WSI] VkHdrMetadataEXT: display primaries:\n"); - fprintf(stderr, " r: %.4g %.4g\n", metadata.displayPrimaryRed.x, metadata.displayPrimaryRed.y); - fprintf(stderr, " g: %.4g %.4g\n", metadata.displayPrimaryGreen.x, metadata.displayPrimaryGreen.y); - fprintf(stderr, " b: %.4g %.4g\n", metadata.displayPrimaryBlue.x, metadata.displayPrimaryBlue.y); - -From b0ce622a8ef095796daeb98f7882e19ba7d4f994 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Cl=C3=A9ment=20Gu=C3=A9rin?= -Date: Wed, 20 Dec 2023 17:18:32 -0800 -Subject: [PATCH 058/134] drm: fix other headless NPE - -fixes a980d912 ---- - src/drm.cpp | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 2a7208f72..352f8b11c 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -2519,10 +2519,10 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - assert( drm->req == nullptr ); - drm->req = drmModeAtomicAlloc(); - -- bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; -- bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; -- - if (drm->connector != nullptr) { -+ bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; -+ bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; -+ - if (drm->connector->has_colorspace) { - drm->connector->pending.colorspace = ( bConnectorHDR ) ? DRM_MODE_COLORIMETRY_BT2020_RGB : DRM_MODE_COLORIMETRY_DEFAULT; - } - -From 8dffcfa51049ebe235149a4a7f8ed295d27cde6e Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sat, 23 Dec 2023 11:27:30 +0000 -Subject: [PATCH 059/134] steamcompmgr: Support saving AVIF screenshots - ---- - src/drm.cpp | 15 +++- - src/drm.hpp | 4 +- - src/meson.build | 3 +- - src/rendervulkan.cpp | 8 ++- - src/rendervulkan.hpp | 1 + - src/steamcompmgr.cpp | 165 +++++++++++++++++++++++++++++++++++++++++-- - 6 files changed, 183 insertions(+), 13 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 352f8b11c..29682a356 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -486,6 +486,9 @@ drm_hdr_parse_edid(drm_t *drm, struct connector *connector, const struct di_edid - infoframe->max_cll = nits_to_u16(default_max_fall); - infoframe->max_fall = nits_to_u16(default_max_fall); - } -+ -+ metadata->maxCLL = (uint16_t)hdr_static_metadata->desired_content_max_luminance; -+ metadata->maxFALL = (uint16_t)hdr_static_metadata->desired_content_max_frame_avg_luminance; - } - - metadata->supportsST2084 = -@@ -3124,10 +3127,16 @@ bool drm_get_vrr_capable(struct drm_t *drm) - return false; - } - --bool drm_supports_st2084(struct drm_t *drm) -+bool drm_supports_st2084(struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL) - { -- if ( drm->connector ) -- return drm->connector->metadata.supportsST2084; -+ if ( drm->connector && drm->connector->metadata.supportsST2084 ) -+ { -+ if ( maxCLL ) -+ *maxCLL = drm->connector->metadata.maxCLL; -+ if ( maxFALL ) -+ *maxFALL = drm->connector->metadata.maxFALL; -+ return true; -+ } - - return false; - } -diff --git a/src/drm.hpp b/src/drm.hpp -index 4840e2b8d..409c87c92 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -174,6 +174,8 @@ struct connector_metadata_t { - - displaycolorimetry_t colorimetry = displaycolorimetry_709; - EOTF eotf = EOTF_Gamma22; -+ uint16_t maxCLL = 0; -+ uint16_t maxFALL = 0; - }; - - struct connector { -@@ -363,7 +365,7 @@ drm_screen_type drm_get_screen_type(struct drm_t *drm); - char *find_drm_node_by_devid(dev_t devid); - int drm_get_default_refresh(struct drm_t *drm); - bool drm_get_vrr_capable(struct drm_t *drm); --bool drm_supports_st2084(struct drm_t *drm); -+bool drm_supports_st2084(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); - void drm_set_vrr_enabled(struct drm_t *drm, bool enabled); - bool drm_get_vrr_in_use(struct drm_t *drm); - bool drm_supports_color_mgmt(struct drm_t *drm); -diff --git a/src/meson.build b/src/meson.build -index 5385dfb8b..1df4f027b 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -20,6 +20,7 @@ epoll_dep = dependency('epoll-shim', required: false) - glm_dep = dependency('glm') - sdl_dep = dependency('SDL2') - stb_dep = dependency('stb') -+avif_dep = dependency('libavif') - - wlroots_dep = dependency( - 'wlroots', -@@ -135,7 +136,7 @@ endif - dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, - xkbcommon, thread_dep, sdl_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, -+ stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, - ], - install: true, - ) -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 936c1d6a4..b202352a4 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -3638,12 +3638,16 @@ void bind_all_layers(CVulkanCmdBuffer* cmdBuffer, const struct FrameInfo_t *fram - - std::optional vulkan_screenshot( const struct FrameInfo_t *frameInfo, std::shared_ptr pScreenshotTexture ) - { -+ 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(), EOTF_Gamma22 )); -+ 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); -@@ -3661,7 +3665,7 @@ extern uint32_t g_reshade_technique_idx; - - std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::shared_ptr pPipewireTexture, bool partial, std::shared_ptr pOutputOverride, bool increment ) - { -- EOTF outputTF = g_ColorMgmt.current.outputEncodingEOTF; -+ EOTF outputTF = frameInfo->outputEncodingEOTF; - if (!frameInfo->applyOutputColorMgmt) - outputTF = EOTF_Count; //Disable blending stuff. - -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index e9984695f..e10295208 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -270,6 +270,7 @@ struct FrameInfo_t - std::shared_ptr lut3D[EOTF_Count]; - - bool applyOutputColorMgmt; // drm only -+ EOTF outputEncodingEOTF; - - int layerCount; - struct Layer_t -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 40f812531..bf4a9871c 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -90,6 +90,8 @@ - #include "win32_styles.h" - #include "mwm_hints.h" - -+#include "avif/avif.h" -+ - static const int g_nBaseCursorScale = 36; - - #if HAVE_PIPEWIRE -@@ -115,6 +117,8 @@ LogScope g_WaitableLog("waitable"); - - bool g_bWasPartialComposite = false; - -+bool g_bUseAVIFScreenshots = false; -+ - /// - // Color Mgmt - // -@@ -128,6 +132,7 @@ static lut3d_t g_ColorMgmtLooks[ EOTF_Count ]; - gamescope_color_mgmt_luts g_ColorMgmtLuts[ EOTF_Count ]; - - gamescope_color_mgmt_luts g_ScreenshotColorMgmtLuts[ EOTF_Count ]; -+gamescope_color_mgmt_luts g_ScreenshotColorMgmtLutsHDR[ EOTF_Count ]; - - static lut1d_t g_tmpLut1d; - static lut3d_t g_tmpLut3d; -@@ -179,6 +184,15 @@ static const gamescope_color_mgmt_t k_ScreenshotColorMgmt = - .outputEncodingEOTF = EOTF_Gamma22, - }; - -+static const gamescope_color_mgmt_t k_ScreenshotColorMgmtHDR = -+{ -+ .enabled = true, -+ .displayColorimetry = displaycolorimetry_2020, -+ .displayEOTF = EOTF_PQ, -+ .outputEncodingColorimetry = displaycolorimetry_2020, -+ .outputEncodingEOTF = EOTF_PQ, -+}; -+ - //#define COLOR_MGMT_MICROBENCH - // sudo cpupower frequency-set --governor performance - -@@ -399,6 +413,7 @@ static void - update_screenshot_color_mgmt() - { - create_color_mgmt_luts(k_ScreenshotColorMgmt, g_ScreenshotColorMgmtLuts); -+ create_color_mgmt_luts(k_ScreenshotColorMgmtHDR, g_ScreenshotColorMgmtLutsHDR); - } - - bool set_color_sdr_gamut_wideness( float flVal ) -@@ -2431,6 +2446,7 @@ paint_all(bool async) - - struct FrameInfo_t frameInfo = {}; - frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; -+ frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; - - // 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 -@@ -3017,21 +3033,29 @@ paint_all(bool async) - - if ( takeScreenshot ) - { -- uint32_t drmCaptureFormat = bHackForceNV12DumpScreenshot -- ? DRM_FORMAT_NV12 -- : DRM_FORMAT_XRGB8888; -+ uint32_t drmCaptureFormat = DRM_FORMAT_XRGB8888; -+ -+ if ( bHackForceNV12DumpScreenshot ) -+ drmCaptureFormat = DRM_FORMAT_NV12; -+ else if ( g_bUseAVIFScreenshots ) -+ drmCaptureFormat = DRM_FORMAT_XRGB2101010; - - std::shared_ptr pScreenshotTexture = vulkan_acquire_screenshot_texture(g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat); - - if ( pScreenshotTexture ) - { -+ bool bHDRScreenshot = frameInfo.layerCount > 0 && -+ ColorspaceIsHDR( frameInfo.layers[0].colorspace ) && -+ takeScreenshot != TAKE_SCREENSHOT_SCREEN_BUFFER; -+ - if ( drmCaptureFormat == DRM_FORMAT_NV12 || takeScreenshot != TAKE_SCREENSHOT_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++ ) - { -- frameInfo.lut3D[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut3d; -- frameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; -+ auto& luts = bHDRScreenshot ? g_ScreenshotColorMgmtLutsHDR : g_ScreenshotColorMgmtLuts; -+ frameInfo.lut3D[nInputEOTF] = luts[nInputEOTF].vk_lut3d; -+ frameInfo.shaperLut[nInputEOTF] = luts[nInputEOTF].vk_lut1d; - } - - if ( takeScreenshot == TAKE_SCREENSHOT_BASEPLANE_ONLY ) -@@ -3071,6 +3095,11 @@ paint_all(bool async) - - frameInfo.applyOutputColorMgmt = true; - -+ frameInfo.outputEncodingEOTF = bHDRScreenshot ? EOTF_PQ : EOTF_Gamma22; -+ -+ uint32_t uCompositeDebugBackup = g_uCompositeDebug; -+ g_uCompositeDebug = 0; -+ - std::optional oScreenshotSeq; - if ( drmCaptureFormat == DRM_FORMAT_NV12 ) - oScreenshotSeq = vulkan_composite( &frameInfo, pScreenshotTexture, false, nullptr ); -@@ -3079,6 +3108,8 @@ paint_all(bool async) - else - oScreenshotSeq = vulkan_screenshot( &frameInfo, pScreenshotTexture ); - -+ g_uCompositeDebug = uCompositeDebugBackup; -+ - if ( !oScreenshotSeq ) - { - xwm_log.errorf("vulkan_screenshot failed"); -@@ -3087,12 +3118,134 @@ paint_all(bool async) - - 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 ) -+ { -+ drm_supports_st2084( &g_DRM, &maxCLLNits, &maxFALLNits ); -+ } -+ -+ 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(); - -- if (pScreenshotTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) -+ 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; -+ } -+ } -+ -+ 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 ( takeScreenshot == TAKE_SCREENSHOT_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 ); -+ -+ avifImageRGBToYUV( pAvifImage, &rgbAvifImage ); // Not really! See Matrix Coefficients IDENTITY above. -+ -+ 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; -+ } -+ -+ char pFileName[1024] = "/tmp/gamescope.avif"; -+ -+ if ( !propertyRequestedScreenshot ) -+ { -+ time_t currentTime = time(0); -+ struct tm *localTime = localtime( ¤tTime ); -+ strftime( pFileName, sizeof( pFileName ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.avif", localTime ); -+ } -+ -+ FILE *pScreenshotFile = nullptr; -+ if ( ( pScreenshotFile = fopen( pFileName, "wb" ) ) == nullptr ) -+ { -+ xwm_log.errorf( "Failed to fopen file: %s", pFileName ); -+ return; -+ } -+ -+ fwrite( avifOutput.data, 1, avifOutput.size, pScreenshotFile ); -+ fclose( pScreenshotFile ); -+ -+ xwm_log.infof( "Screenshot saved to %s", pFileName ); -+ } -+ 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); - -From a82befbde1f3c7756f257cf20bbadff01706d2f2 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sat, 23 Dec 2023 12:32:17 +0000 -Subject: [PATCH 060/134] protocols, steamcompmgr: Add take_screenshot - interface, refactor screenshot taking - ---- - protocol/gamescope-control.xml | 24 +++++- - src/main.cpp | 2 +- - src/sdlwindow.cpp | 2 +- - src/steamcompmgr.cpp | 152 +++++++++++++++------------------ - src/steamcompmgr.hpp | 9 -- - src/steamcompmgr_shared.hpp | 49 +++++++++++ - src/xwayland_ctx.hpp | 1 - - 7 files changed, 143 insertions(+), 96 deletions(-) - -diff --git a/protocol/gamescope-control.xml b/protocol/gamescope-control.xml -index 376de490a..4359eb148 100644 ---- a/protocol/gamescope-control.xml -+++ b/protocol/gamescope-control.xml -@@ -29,7 +29,7 @@ - it. - - -- -+ - - - -@@ -76,5 +76,27 @@ - - - -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ - - -diff --git a/src/main.cpp b/src/main.cpp -index 76721d63d..5be060bf0 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -412,7 +412,7 @@ static void handle_signal( int sig ) - { - switch ( sig ) { - case SIGUSR2: -- take_screenshot( TAKE_SCREENSHOT_BASEPLANE_ONLY ); -+ gamescope::CScreenshotManager::Get().TakeScreenshot( true ); - break; - case SIGHUP: - case SIGQUIT: -diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp -index 2eb8c3c74..78912e15a 100644 ---- a/src/sdlwindow.cpp -+++ b/src/sdlwindow.cpp -@@ -286,7 +286,7 @@ void inputSDLThreadRun( void ) - g_upscaleFilterSharpness = std::max(0, g_upscaleFilterSharpness - 1); - break; - case KEY_S: -- take_screenshot(); -+ gamescope::CScreenshotManager::Get().TakeScreenshot( true ); - break; - case KEY_G: - g_bGrabbed = !g_bGrabbed; -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index bf4a9871c..b42eecf61 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -48,6 +48,7 @@ - #include - #include - #include -+#include - #include - #include - -@@ -117,8 +118,6 @@ LogScope g_WaitableLog("waitable"); - - bool g_bWasPartialComposite = false; - --bool g_bUseAVIFScreenshots = false; -- - /// - // Color Mgmt - // -@@ -1092,8 +1091,15 @@ struct wlr_buffer_map_entry { - static std::mutex wlr_buffer_map_lock; - static std::unordered_map wlr_buffer_map; - --static std::atomic< int > g_nTakeScreenshot{ 0 }; --static bool g_bPropertyRequestedScreenshot; -+namespace gamescope -+{ -+ CScreenshotManager &CScreenshotManager::Get() -+ { -+ static CScreenshotManager s_Instance; -+ return s_Instance; -+ } -+} -+ - - static std::atomic g_bForceRepaint{false}; - -@@ -2651,11 +2657,6 @@ paint_all(bool async) - - bool bDoComposite = true; - -- // Handoff from whatever thread to this one since we check ours twice -- int takeScreenshot = g_nTakeScreenshot.exchange( 0 ); -- bool propertyRequestedScreenshot = g_bPropertyRequestedScreenshot; -- g_bPropertyRequestedScreenshot = false; -- - update_app_target_refresh_cycle(); - - int nDynamicRefresh = g_nDynamicRefreshRate[drm_get_screen_type( &g_DRM )]; -@@ -2714,8 +2715,6 @@ paint_all(bool async) - bNeedsFullComposite |= fadingOut; - bNeedsFullComposite |= !g_reshade_effect.empty(); - -- constexpr bool bHackForceNV12DumpScreenshot = false; -- - for (uint32_t i = 0; i < EOTF_Count; i++) - { - if (g_ColorMgmtLuts[i].HasLuts()) -@@ -3031,24 +3030,33 @@ paint_all(bool async) - } - #endif - -- if ( takeScreenshot ) -+ std::optional oScreenshotInfo = -+ gamescope::CScreenshotManager::Get().ProcessPendingScreenshot(); -+ -+ if ( oScreenshotInfo ) - { -- uint32_t drmCaptureFormat = DRM_FORMAT_XRGB8888; -+ std::filesystem::path path = std::filesystem::path{ oScreenshotInfo->szScreenshotPath }; - -- if ( bHackForceNV12DumpScreenshot ) -- drmCaptureFormat = DRM_FORMAT_NV12; -- else if ( g_bUseAVIFScreenshots ) -+ 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; - -- std::shared_ptr pScreenshotTexture = vulkan_acquire_screenshot_texture(g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat); -+ std::shared_ptr pScreenshotTexture; -+ if ( drmCaptureFormat != DRM_FORMAT_INVALID ) -+ pScreenshotTexture = vulkan_acquire_screenshot_texture( g_nOutputWidth, g_nOutputHeight, false, drmCaptureFormat ); - - if ( pScreenshotTexture ) - { - bool bHDRScreenshot = frameInfo.layerCount > 0 && - ColorspaceIsHDR( frameInfo.layers[0].colorspace ) && -- takeScreenshot != TAKE_SCREENSHOT_SCREEN_BUFFER; -+ oScreenshotInfo->eScreenshotType != GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER; - -- if ( drmCaptureFormat == DRM_FORMAT_NV12 || takeScreenshot != TAKE_SCREENSHOT_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++ ) -@@ -3058,7 +3066,7 @@ paint_all(bool async) - frameInfo.shaperLut[nInputEOTF] = luts[nInputEOTF].vk_lut1d; - } - -- if ( takeScreenshot == TAKE_SCREENSHOT_BASEPLANE_ONLY ) -+ 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++) -@@ -3103,7 +3111,8 @@ paint_all(bool async) - std::optional oScreenshotSeq; - if ( drmCaptureFormat == DRM_FORMAT_NV12 ) - oScreenshotSeq = vulkan_composite( &frameInfo, pScreenshotTexture, false, nullptr ); -- else if ( takeScreenshot == TAKE_SCREENSHOT_FULL_COMPOSITION || takeScreenshot == TAKE_SCREENSHOT_SCREEN_BUFFER ) -+ 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 ); -@@ -3149,6 +3158,8 @@ paint_all(bool async) - - 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. -@@ -3179,7 +3190,7 @@ paint_all(bool async) - // This does not compress as well, but is always lossless! - pAvifImage->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; - -- if ( takeScreenshot == TAKE_SCREENSHOT_SCREEN_BUFFER ) -+ 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 -@@ -3224,26 +3235,18 @@ paint_all(bool async) - return; - } - -- char pFileName[1024] = "/tmp/gamescope.avif"; -- -- if ( !propertyRequestedScreenshot ) -- { -- time_t currentTime = time(0); -- struct tm *localTime = localtime( ¤tTime ); -- strftime( pFileName, sizeof( pFileName ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.avif", localTime ); -- } -- - FILE *pScreenshotFile = nullptr; -- if ( ( pScreenshotFile = fopen( pFileName, "wb" ) ) == nullptr ) -+ if ( ( pScreenshotFile = fopen( oScreenshotInfo->szScreenshotPath.c_str(), "wb" ) ) == nullptr ) - { -- xwm_log.errorf( "Failed to fopen file: %s", pFileName ); -+ 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", pFileName ); -+ xwm_log.infof( "Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str() ); -+ bScreenshotSuccess = true; - } - else if (pScreenshotTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) - { -@@ -3262,73 +3265,65 @@ paint_all(bool async) - imageData[y * pitch + x * comp + 3] = 255; - } - } -- -- char pTimeBuffer[1024] = "/tmp/gamescope.png"; -- -- if ( !propertyRequestedScreenshot ) -+ if ( stbi_write_png( oScreenshotInfo->szScreenshotPath.c_str(), currentOutputWidth, currentOutputHeight, 4, imageData.data(), pitch ) ) - { -- time_t currentTime = time(0); -- struct tm *localTime = localtime( ¤tTime ); -- strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.png", localTime ); -- } -- -- if ( stbi_write_png(pTimeBuffer, currentOutputWidth, currentOutputHeight, 4, imageData.data(), pitch) ) -- { -- xwm_log.infof("Screenshot saved to %s", pTimeBuffer); -+ xwm_log.infof( "Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str() ); -+ bScreenshotSuccess = true; - } - else - { -- xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); -+ xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - } - } - else if (pScreenshotTexture->format() == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) - { -- char pTimeBuffer[1024] = "/tmp/gamescope.raw"; -- -- if ( !propertyRequestedScreenshot ) -- { -- time_t currentTime = time(0); -- struct tm *localTime = localtime( ¤tTime ); -- strftime( pTimeBuffer, sizeof( pTimeBuffer ), "/tmp/gamescope_%Y-%m-%d_%H-%M-%S.raw", localTime ); -- } -- -- FILE *file = fopen(pTimeBuffer, "wb"); -+ FILE *file = fopen( oScreenshotInfo->szScreenshotPath.c_str(), "wb" ); - if (file) - { - fwrite(mappedData, 1, pScreenshotTexture->totalSize(), file ); - fclose(file); - - char cmd[4096]; -- sprintf(cmd, "ffmpeg -f rawvideo -pixel_format nv12 -video_size %dx%d -i %s %s_encoded.png", pScreenshotTexture->width(), pScreenshotTexture->height(), pTimeBuffer, pTimeBuffer); -+ 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) { -+ if (ret) { - xwm_log.infof("Ffmpeg call return status %i", ret); -- xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); -+ xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - } else { -- xwm_log.infof("Screenshot saved to %s", pTimeBuffer); -+ xwm_log.infof("Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str()); -+ bScreenshotSuccess = true; - } - } - else - { -- xwm_log.errorf( "Failed to save screenshot to %s", pTimeBuffer ); -+ xwm_log.errorf( "Failed to save screenshot to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - } - } - -- XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); -+ if ( oScreenshotInfo->bX11PropertyRequested ) -+ XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); -+ -+ if ( bScreenshotSuccess ) -+ { -+ wlserver_lock(); -+ for ( const auto &control : wlserver.gamescope_controls ) -+ { -+ gamescope_control_send_screenshot_taken( control, oScreenshotInfo->szScreenshotPath.c_str() ); -+ } -+ wlserver_unlock(); -+ } - }); - - screenshotThread.detach(); -- -- takeScreenshot = 0; - } - else - { - xwm_log.errorf( "Oh no, we ran out of screenshot images. Not actually writing a screenshot." ); -- XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); -- takeScreenshot = 0; -+ if ( oScreenshotInfo->bX11PropertyRequested ) -+ XDeleteProperty( root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeScreenShotAtom ); - } - } - -@@ -5602,15 +5597,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - { - if ( ev->state == PropertyNewValue ) - { -- g_nTakeScreenshot = (int)get_prop( ctx, ctx->root, ctx->atoms.gamescopeScreenShotAtom, None ); -- g_bPropertyRequestedScreenshot = true; -- } -- } -- if ( ev->atom == ctx->atoms.gamescopeDebugScreenShotAtom ) -- { -- if ( ev->state == PropertyNewValue ) -- { -- g_nTakeScreenshot = (int)get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugScreenShotAtom, None ); -+ 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.gameAtom) -@@ -6736,12 +6729,6 @@ void nudge_steamcompmgr( void ) - g_SteamCompMgrWaiter.Nudge(); - } - --void take_screenshot( int flags ) --{ -- g_nTakeScreenshot = flags; -- nudge_steamcompmgr(); --} -- - void force_repaint( void ) - { - g_bForceRepaint = true; -@@ -7387,7 +7374,6 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - 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); -diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp -index e73a70767..59ac3b283 100644 ---- a/src/steamcompmgr.hpp -+++ b/src/steamcompmgr.hpp -@@ -49,14 +49,6 @@ extern EStreamColorspace g_ForcedNV12ColorSpace; - // use the proper libliftoff composite plane system. - static constexpr bool kDisablePartialComposition = true; - --enum TakeScreenshotMode_t --{ -- TAKE_SCREENSHOT_BASEPLANE_ONLY = 1, // Just the game/base plane -- TAKE_SCREENSHOT_ALL_REAL_LAYERS = 2, // Just the game + steam overlay/perf overlays -- TAKE_SCREENSHOT_FULL_COMPOSITION = 3, // Everything, but no display color management + no mura -- TAKE_SCREENSHOT_SCREEN_BUFFER = 4, // Yes, mura comp, color management! Exactly what we put on the screen. --}; -- - class MouseCursor - { - public: -@@ -146,7 +138,6 @@ extern uint32_t inputCounter; - extern uint64_t g_lastWinSeq; - - void nudge_steamcompmgr( void ); --void take_screenshot( int flags = TAKE_SCREENSHOT_BASEPLANE_ONLY ); - void force_repaint( void ); - - extern void mangoapp_update( uint64_t visible_frametime, uint64_t app_frametime_ns, uint64_t latency_ns ); -diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp -index 3bcdb16e8..7de514583 100644 ---- a/src/steamcompmgr_shared.hpp -+++ b/src/steamcompmgr_shared.hpp -@@ -2,6 +2,8 @@ - - #include "xwayland_ctx.hpp" - #include -+#include -+#include "gamescope-control-protocol.h" - - struct commit_t; - struct wlserver_vk_swapchain_feedback; -@@ -179,3 +181,50 @@ struct steamcompmgr_win_t { - return nullptr; - } - }; -+ -+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; -+ }; -+ -+ class CScreenshotManager -+ { -+ public: -+ void TakeScreenshot( GamescopeScreenshotInfo info = GamescopeScreenshotInfo{} ) -+ { -+ std::unique_lock lock{ m_ScreenshotInfoMutex }; -+ m_ScreenshotInfo = std::move( info ); -+ } -+ -+ 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; -+} -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index 5764c4b7b..7a4030f5d 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -133,7 +133,6 @@ struct xwayland_ctx_t final : public gamescope::IWaitable - Atom gamescopeCtrlWindowAtom; - Atom gamescopeInputCounterAtom; - Atom gamescopeScreenShotAtom; -- Atom gamescopeDebugScreenShotAtom; - - Atom gamescopeFocusDisplay; - Atom gamescopeMouseFocusDisplay; - -From 20e9bc1429b0648bf2da6f049f2988e9067a2e64 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sat, 23 Dec 2023 12:36:24 +0000 -Subject: [PATCH 061/134] ci: Add deps for AV1 - ---- - .github/workflows/main.yml | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml -index 9ab3b0d87..5c2967142 100644 ---- a/.github/workflows/main.yml -+++ b/.github/workflows/main.yml -@@ -17,7 +17,8 @@ jobs: - pacman -S --noconfirm git meson clang glslang libcap wlroots \ - sdl2 vulkan-headers libx11 libxmu libxcomposite libxrender libxres \ - libxtst libxkbcommon libdrm libinput wayland-protocols benchmark \ -- xorg-xwayland pipewire cmake -+ xorg-xwayland pipewire cmake \ -+ libavif libheif aom rav1e - - uses: actions/checkout@v2 - with: - submodules: recursive - -From d434d86c0d8a569af518c6bdd9ff2616f5a784c9 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sat, 23 Dec 2023 12:38:43 +0000 -Subject: [PATCH 062/134] steamcompmgr: Only save HDR screenshots if outputting - as AVIF - ---- - src/steamcompmgr.cpp | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index b42eecf61..86b57796f 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -3052,7 +3052,8 @@ paint_all(bool async) - - if ( pScreenshotTexture ) - { -- bool bHDRScreenshot = frameInfo.layerCount > 0 && -+ bool bHDRScreenshot = path.extension() == ".avif" && -+ frameInfo.layerCount > 0 && - ColorspaceIsHDR( frameInfo.layers[0].colorspace ) && - oScreenshotInfo->eScreenshotType != GAMESCOPE_CONTROL_SCREENSHOT_TYPE_SCREEN_BUFFER; - - -From d7758a8d7eae69384a33263eee267d60fdcc98f9 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 26 Dec 2023 15:07:43 +0000 -Subject: [PATCH 063/134] steamcompmgr: Use XInput2 RawMotion events as a hint - to update cursor pos - -Taking a page out of XEyes' book to get the cursor position (even when the cursor is grabbed, etc) by only doing so when we get a RawMotion event -- and only register for those when we know there's a cursor image that we would want to render. - -This is a kinda crap workaround for the fact that we cannot directly just recieve information about the cursor from the XServer, even as the X11 WM due to cursor grabbing, input masking and other such nonsense. - -An alternative would be using something like libei and having XTest funnel through that -- however that needs work to be functional on nested compositors. ---- - src/meson.build | 3 +- - src/steamcompmgr.cpp | 169 ++++++++++++++++++++++++++----------------- - src/steamcompmgr.hpp | 30 ++++++-- - src/xwayland_ctx.hpp | 1 + - 4 files changed, 129 insertions(+), 74 deletions(-) - -diff --git a/src/meson.build b/src/meson.build -index 1df4f027b..4cb546696 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -8,6 +8,7 @@ dep_xxf86vm = dependency('xxf86vm') - dep_xtst = dependency('xtst') - dep_xres = dependency('xres') - dep_xmu = dependency('xmu') -+dep_xi = dependency('xi') - - drm_dep = dependency('libdrm', version: '>= 2.4.113') - -@@ -136,7 +137,7 @@ endif - dep_xxf86vm, dep_xres, glm_dep, drm_dep, wayland_server, - xkbcommon, thread_dep, sdl_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, -+ stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi - ], - install: true, - ) -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 86b57796f..c24ba58ba 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -34,6 +34,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -1579,35 +1580,9 @@ MouseCursor::MouseCursor(xwayland_ctx_t *ctx) - updateCursorFeedback( true ); - } - --void MouseCursor::queryPositions(int &rootX, int &rootY, int &winX, int &winY) --{ -- Window window, child; -- unsigned int mask; -- -- XQueryPointer(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), &window, &child, -- &rootX, &rootY, &winX, &winY, &mask); -- --} -- --void MouseCursor::queryGlobalPosition(int &x, int &y) --{ -- int winX, winY; -- queryPositions(x, y, winX, winY); --} -- --void MouseCursor::queryButtonMask(unsigned int &mask) --{ -- Window window, child; -- int rootX, rootY, winX, winY; -- -- XQueryPointer(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), &window, &child, -- &rootX, &rootY, &winX, &winY, &mask); --} -- - void MouseCursor::checkSuspension() - { -- unsigned int buttonMask; -- queryButtonMask(buttonMask); -+ unsigned int buttonMask = 0; - - bool bWasHidden = m_hideForMovement; - -@@ -1666,11 +1641,6 @@ void MouseCursor::warp(int x, int y) - XWarpPointer(m_ctx->dpy, None, x11_win(m_ctx->focus.inputFocusWindow), 0, 0, 0, 0, x, y); - } - --void MouseCursor::resetPosition() --{ -- warp(m_x, m_y); --} -- - void MouseCursor::setDirty() - { - // We can't prove it's empty until checking again -@@ -1767,23 +1737,28 @@ bool MouseCursor::setCursorImageByName(const char *name) - - void MouseCursor::constrainPosition() - { -- int i; - steamcompmgr_win_t *window = m_ctx->focus.inputFocusWindow; - steamcompmgr_win_t *override = m_ctx->focus.overrideWindow; - if (window == override) - window = m_ctx->focus.focusWindow; - -- // If we had barriers before, get rid of them. -- for (i = 0; i < 4; i++) { -- if (m_scaledFocusBarriers[i] != None) { -- XFixesDestroyPointerBarrier(m_ctx->dpy, m_scaledFocusBarriers[i]); -- m_scaledFocusBarriers[i] = None; -+ if (!window) -+ return; -+ -+ auto barricade = [this](CursorBarrier& barrier, const CursorBarrierInfo& info) { -+ if (barrier.info.x1 == info.x1 && barrier.info.x2 == info.x2 && -+ barrier.info.y1 == info.y1 && barrier.info.y2 == info.y2) -+ return; -+ -+ if (barrier.obj != None) -+ { -+ XFixesDestroyPointerBarrier(m_ctx->dpy, barrier.obj); -+ barrier.obj = None; - } -- } - -- auto barricade = [this](int x1, int y1, int x2, int y2) { -- return XFixesCreatePointerBarrier(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), -- x1, y1, x2, y2, 0, 0, NULL); -+ barrier.obj = XFixesCreatePointerBarrier(m_ctx->dpy, DefaultRootWindow(m_ctx->dpy), -+ info.x1, info.y1, info.x2, info.y2, 0, 0, NULL); -+ barrier.info = info; - }; - - int x1 = window->xwayland().a.x; -@@ -1802,14 +1777,13 @@ void MouseCursor::constrainPosition() - } - - // Constrain it to the window; careful, the corners will leak due to a known X server bug. -- m_scaledFocusBarriers[0] = barricade(0, y1, m_ctx->root_width, y1); -- m_scaledFocusBarriers[1] = barricade(x2, 0, x2, m_ctx->root_height); -- m_scaledFocusBarriers[2] = barricade(m_ctx->root_width, y2, 0, y2); -- m_scaledFocusBarriers[3] = barricade(x1, m_ctx->root_height, x1, 0); -+ barricade(m_barriers[0], CursorBarrierInfo{ 0, y1, m_ctx->root_width, y1 }); -+ barricade(m_barriers[1], CursorBarrierInfo{ x2, 0, x2, m_ctx->root_height }); -+ barricade(m_barriers[2], CursorBarrierInfo{ m_ctx->root_width, y2, 0, y2 }); -+ barricade(m_barriers[3], CursorBarrierInfo{ x1, m_ctx->root_height, x1, 0 }); - - // Make sure the cursor is somewhere in our jail -- int rootX, rootY; -- queryGlobalPosition(rootX, rootY); -+ int rootX = m_x, rootY = m_y; - - if ( rootX >= x2 || rootY >= y2 || rootX < x1 || rootY < y1 ) { - if ( window_wants_no_focus_when_mouse_hidden( window ) && m_hideForMovement ) -@@ -1865,14 +1839,6 @@ void MouseCursor::move(int x, int y) - updateCursorFeedback(); - } - --void MouseCursor::updatePosition() --{ -- int x,y; -- queryGlobalPosition(x, y); -- move(x, y); -- checkSuspension(); --} -- - int MouseCursor::x() const - { - return m_x; -@@ -1987,12 +1953,15 @@ bool MouseCursor::getTexture() - - m_dirty = false; - updateCursorFeedback(); -+ UpdateXInputMotionMasks(); - - if (m_imageEmpty) { - - return false; - } - -+ UpdatePosition(); -+ - CVulkanTexture::createFlags texCreateFlags; - if ( BIsNested() == false ) - { -@@ -2022,15 +1991,46 @@ void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) - nHeight = nSize; - } - -+void MouseCursor::UpdateXInputMotionMasks() -+{ -+ bool bShouldMotionMask = !imageEmpty(); -+ -+ if ( m_bMotionMaskEnabled != bShouldMotionMask ) -+ { -+ XIEventMask xi_eventmask; -+ unsigned char xi_mask[ ( XI_LASTEVENT + 7 ) / 8 ]{}; -+ xi_eventmask.deviceid = XIAllDevices; -+ xi_eventmask.mask_len = sizeof( xi_mask ); -+ xi_eventmask.mask = xi_mask; -+ if ( bShouldMotionMask ) -+ XISetMask( xi_mask, XI_RawMotion ); -+ XISelectEvents( m_ctx->dpy, m_ctx->root, &xi_eventmask, 1 ); -+ -+ m_bMotionMaskEnabled = bShouldMotionMask; -+ } -+} -+ -+void MouseCursor::UpdatePosition() -+{ -+ Window root_return, child_return; -+ int root_x_return, root_y_return; -+ int win_x_return, win_y_return; -+ unsigned int mask_return; -+ XQueryPointer(m_ctx->dpy, m_ctx->root, &root_return, &child_return, -+ &root_x_return, &root_y_return, -+ &win_x_return, &win_y_return, -+ &mask_return); -+ -+ move(root_x_return, root_y_return); -+} -+ - void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, struct FrameInfo_t *frameInfo) - { - if ( m_hideForMovement || m_imageEmpty ) { - return; - } - -- int rootX, rootY, winX, winY; -- queryPositions(rootX, rootY, winX, winY); -- move(rootX, rootY); -+ int winX = m_x, winY = m_y; - - // Also need new texture - if (!getTexture()) { -@@ -6996,6 +6996,7 @@ void xwayland_ctx_t::Dispatch() - MouseCursor *cursor = ctx->cursor.get(); - bool bShouldResetCursor = false; - bool bSetFocus = false; -+ bool bShouldUpdateCursor = false; - - while (XPending(ctx->dpy)) - { -@@ -7135,6 +7136,16 @@ void xwayland_ctx_t::Dispatch() - case SelectionRequest: - handle_selection_request(ctx, &ev.xselectionrequest); - break; -+ case GenericEvent: -+ if (ev.xcookie.extension == ctx->xinput_opcode) -+ { -+ if (ev.xcookie.evtype == XI_RawMotion) -+ { -+ bShouldUpdateCursor = true; -+ } -+ } -+ break; -+ - default: - if (ev.type == ctx->damage_event + XDamageNotify) - { -@@ -7153,12 +7164,24 @@ void xwayland_ctx_t::Dispatch() - XFlush(ctx->dpy); - } - -- if ( bShouldResetCursor ) -+ if ( bShouldUpdateCursor ) - { -- // This shouldn't happen due to our pointer barriers, -- // but there is a known X server bug; warp to last good -- // position. -- cursor->resetPosition(); -+ cursor->UpdatePosition(); -+ -+ if ( bShouldResetCursor ) -+ { -+ // This shouldn't happen due to our pointer barriers, -+ // but there is a known X server bug; warp to last good -+ // position. -+ steamcompmgr_win_t *pInputWindow = ctx->focus.inputFocusWindow; -+ int nX = std::clamp( cursor->x(), pInputWindow->xwayland().a.x, pInputWindow->xwayland().a.x + pInputWindow->xwayland().a.width ); -+ int nY = std::clamp( cursor->y(), pInputWindow->xwayland().a.y, pInputWindow->xwayland().a.y + pInputWindow->xwayland().a.height ); -+ -+ if ( cursor->x() != nX || cursor->y() != nY ) -+ { -+ cursor->forcePosition( nX, nY ); -+ } -+ } - } - - if ( bSetFocus ) -@@ -7319,6 +7342,18 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - 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)) - { -@@ -7543,6 +7578,9 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - } - } - -+ ctx->cursor->undirty(); -+ ctx->cursor->UpdateXInputMotionMasks(); -+ - XFlush(ctx->dpy); - } - -@@ -8239,7 +8277,8 @@ steamcompmgr_main(int argc, char **argv) - - if (global_focus.cursor) - { -- global_focus.cursor->updatePosition(); -+ global_focus.cursor->constrainPosition(); -+ global_focus.cursor->checkSuspension(); - - if (global_focus.cursor->needs_server_flush()) - { -diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp -index 59ac3b283..16ef13273 100644 ---- a/src/steamcompmgr.hpp -+++ b/src/steamcompmgr.hpp -@@ -49,6 +49,20 @@ extern EStreamColorspace g_ForcedNV12ColorSpace; - // use the proper libliftoff composite plane system. - static constexpr bool kDisablePartialComposition = true; - -+struct CursorBarrierInfo -+{ -+ int x1 = 0; -+ int y1 = 0; -+ int x2 = 0; -+ int y2 = 0; -+}; -+ -+struct CursorBarrier -+{ -+ PointerBarrier obj = None; -+ CursorBarrierInfo info = {}; -+}; -+ - class MouseCursor - { - public: -@@ -58,9 +72,7 @@ class MouseCursor - int y() const; - - void move(int x, int y); -- void updatePosition(); - void constrainPosition(); -- void resetPosition(); - - void paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, FrameInfo_t *frameInfo); - void setDirty(); -@@ -72,6 +84,7 @@ class MouseCursor - void hide() { m_lastMovedTime = 0; checkSuspension(); } - - bool isHidden() { return m_hideForMovement || m_imageEmpty; } -+ bool imageEmpty() const { return m_imageEmpty; } - - void forcePosition(int x, int y) - { -@@ -89,13 +102,12 @@ class MouseCursor - - void GetDesiredSize( int& nWidth, int &nHeight ); - -+ void UpdateXInputMotionMasks(); -+ void UpdatePosition(); -+ -+ void checkSuspension(); - private: - void warp(int x, int y); -- void checkSuspension(); -- -- void queryGlobalPosition(int &x, int &y); -- void queryPositions(int &rootX, int &rootY, int &winX, int &winY); -- void queryButtonMask(unsigned int &mask); - - bool getTexture(); - -@@ -111,7 +123,9 @@ class MouseCursor - unsigned int m_lastMovedTime = 0; - bool m_hideForMovement; - -- PointerBarrier m_scaledFocusBarriers[4] = { None }; -+ bool m_bMotionMaskEnabled = false; -+ -+ CursorBarrier m_barriers[4] = {}; - - xwayland_ctx_t *m_ctx; - -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index 7a4030f5d..11f9335de 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -70,6 +70,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable - 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; - -From 962f0abf023cc83cf44f6c1f4acdce8d63668c2b Mon Sep 17 00:00:00 2001 -From: sharkautarch <128002472+sharkautarch@users.noreply.github.com> -Date: Wed, 27 Dec 2023 18:31:54 -0500 -Subject: [PATCH 064/134] move calls to bInit() outside of assert, so that - bInit() still runs when gamescope is built with NDEBUG set - ---- - src/rendervulkan.cpp | 12 ++++++++---- - 1 file changed, 8 insertions(+), 4 deletions(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index b202352a4..3dbbfe069 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -2765,7 +2765,8 @@ std::shared_ptr vulkan_create_1d_lut(uint32_t size) - - auto texture = std::make_shared(); - auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM ); -- assert( texture->BInit( size, 1u, 1u, drmFormat, flags ) ); -+ bool bRes = texture->BInit( size, 1u, 1u, drmFormat, flags ); -+ assert( bRes ); - - return texture; - } -@@ -2779,7 +2780,8 @@ std::shared_ptr vulkan_create_3d_lut(uint32_t width, uint32_t he - - auto texture = std::make_shared(); - auto drmFormat = VulkanFormatToDRM( VK_FORMAT_R16G16B16A16_UNORM ); -- assert( texture->BInit( width, height, depth, drmFormat, flags ) ); -+ bool bRes = texture->BInit( width, height, depth, drmFormat, flags ); -+ assert( bRes ); - - return texture; - } -@@ -2820,7 +2822,8 @@ std::shared_ptr vulkan_create_debug_blank_texture() - int height = std::min( g_nOutputHeight, 1080 ); - - auto texture = std::make_shared(); -- assert( texture->BInit( width, height, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ) ); -+ bool bRes = texture->BInit( width, height, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ); -+ assert( bRes ); - - void* dst = g_device.uploadBufferData( width * height * 4 ); - memset( dst, 0x0, width * height * 4 ); -@@ -2843,7 +2846,8 @@ std::shared_ptr vulkan_create_debug_white_texture() - flags.bLinear = true; - - auto texture = std::make_shared(); -- assert( texture->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags ) ); -+ bool bRes = texture->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags); -+ assert( bRes ); - - memset( texture->mappedData(), 0xFF, texture->width() * texture->height() * 4 ); - - -From 8f3f5c5b445a42409ed387442ad80fec8c5153d9 Mon Sep 17 00:00:00 2001 -From: Sandelinos <26635624+Sandelinos@users.noreply.github.com> -Date: Mon, 1 Jan 2024 21:05:53 +0200 -Subject: [PATCH 065/134] build: Define minimum libavif version - -Co-authored-by: Sandelinos ---- - src/meson.build | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/meson.build b/src/meson.build -index 4cb546696..4f88d6ecb 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -21,7 +21,7 @@ epoll_dep = dependency('epoll-shim', required: false) - glm_dep = dependency('glm') - sdl_dep = dependency('SDL2') - stb_dep = dependency('stb') --avif_dep = dependency('libavif') -+avif_dep = dependency('libavif', version: '>=1.0.0') - - wlroots_dep = dependency( - 'wlroots', - -From aad7eed33bd2186f10ffc39213d6091a9764e65a Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 2 Jan 2024 18:10:12 +0000 -Subject: [PATCH 066/134] drm: Collection of DRM refactors - ---- - src/drm.cpp | 2136 +++++++++++++++++++--------------------- - src/drm.hpp | 377 +++++-- - src/gamescope_shared.h | 27 + - src/main.cpp | 21 +- - src/modegen.cpp | 17 +- - src/modegen.hpp | 3 +- - src/steamcompmgr.cpp | 85 +- - src/steamcompmgr.hpp | 2 +- - src/vblankmanager.cpp | 4 +- - src/wlserver.cpp | 10 +- - src/xwayland_ctx.hpp | 8 +- - 11 files changed, 1398 insertions(+), 1292 deletions(-) - create mode 100644 src/gamescope_shared.h - -diff --git a/src/drm.cpp b/src/drm.cpp -index 29682a356..717a5299f 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -19,6 +19,7 @@ extern "C" { - } - - #include "drm.hpp" -+#include "defer.hpp" - #include "main.hpp" - #include "modegen.hpp" - #include "vblankmanager.hpp" -@@ -40,12 +41,13 @@ extern "C" { - - #include "gamescope-control-protocol.h" - -+using namespace std::literals; -+ - struct drm_t g_DRM = {}; - - 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; --bool g_bUseLayers = true; - bool g_bDebugLayers = false; - const char *g_sOutputName = nullptr; - -@@ -63,29 +65,28 @@ struct drm_color_ctm2 { - - bool g_bSupportsAsyncFlips = false; - --enum drm_mode_generation g_drmModeGeneration = DRM_MODE_GENERATE_CVT; -+gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; - enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; --std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; -+std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; - - bool g_bForceDisableColorMgmt = false; - - static LogScope drm_log("drm"); - static LogScope drm_verbose_log("drm", LOG_SILENT); - --static std::map< std::string, std::string > pnps = {}; -+static std::unordered_map< std::string, std::string > pnps = {}; - --drm_screen_type drm_get_connector_type(drmModeConnector *connector); - static void drm_unset_mode( struct drm_t *drm ); - static void drm_unset_connector( struct drm_t *drm ); - --static uint32_t steam_deck_display_rates[] = -+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, - }; - --static uint32_t galileo_display_rates[] = -+static constexpr uint32_t s_kSteamDeckOLEDRates[] = - { - 45,47,48,49, - 50,51,53,55,56,59, -@@ -95,17 +96,17 @@ static uint32_t galileo_display_rates[] = - 90, - }; - --static uint32_t get_conn_display_info_flags(struct drm_t *drm, struct connector *connector) -+static uint32_t get_conn_display_info_flags( struct drm_t *drm, gamescope::CDRMConnector *pConnector ) - { -- if (!connector) -+ if ( !pConnector ) - return 0; - - uint32_t flags = 0; -- if ( drm_get_connector_type(connector->connector) == DRM_SCREEN_TYPE_INTERNAL ) -+ if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; -- if ( connector->vrr_capable ) -+ if ( pConnector->IsVRRCapable() ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; -- if ( connector->metadata.supportsST2084 ) -+ if ( pConnector->GetHDRInfo().bExposeHDRSupport ) - flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; - - return flags; -@@ -115,22 +116,22 @@ void drm_send_gamescope_control(wl_resource *control, struct drm_t *drm) - { - // assumes wlserver_lock HELD! - -- if ( !drm->connector ) -+ if ( !drm->pConnector ) - return; - -- auto& conn = drm->connector; -+ auto& conn = drm->pConnector; - -- uint32_t flags = get_conn_display_info_flags( drm, drm->connector ); -+ uint32_t flags = get_conn_display_info_flags( drm, drm->pConnector ); - - struct wl_array display_rates; - wl_array_init(&display_rates); -- if ( conn->valid_display_rates.size() ) -+ if ( conn->GetValidDynamicRefreshRates().size() ) - { -- size_t size = conn->valid_display_rates.size() * sizeof(uint32_t); -+ size_t size = conn->GetValidDynamicRefreshRates().size() * sizeof(uint32_t); - uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); -- memcpy( ptr, conn->valid_display_rates.data(), size ); -+ memcpy( ptr, conn->GetValidDynamicRefreshRates().data(), size ); - } -- gamescope_control_send_active_display_info( control, drm->connector->name, drm->connector->make, drm->connector->model, flags, &display_rates ); -+ gamescope_control_send_active_display_info( control, drm->pConnector->GetName(), drm->pConnector->GetMake(), drm->pConnector->GetModel(), flags, &display_rates ); - wl_array_release(&display_rates); - } - -@@ -175,60 +176,55 @@ static struct fb& get_fb( struct drm_t& drm, uint32_t id ) - return drm.fb_map[ id ]; - } - --static struct crtc *find_crtc_for_connector(struct drm_t *drm, const struct connector *connector) { -- for (size_t i = 0; i < drm->crtcs.size(); i++) { -- uint32_t crtc_mask = 1 << i; -- if (connector->possible_crtcs & crtc_mask) -- return &drm->crtcs[i]; -+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(); - } - - return nullptr; - } - --static bool get_plane_formats(struct drm_t *drm, struct plane *plane, struct wlr_drm_format_set *formats) { -- for (uint32_t k = 0; k < plane->plane->count_formats; k++) { -- uint32_t fmt = plane->plane->formats[k]; -- wlr_drm_format_set_add(formats, fmt, DRM_FORMAT_MOD_INVALID); -+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 (plane->props.count("IN_FORMATS") > 0) { -- uint64_t blob_id = plane->initial_prop_values["IN_FORMATS"]; -+ if ( pPlane->GetProperties().IN_FORMATS ) -+ { -+ const uint64_t ulBlobId = pPlane->GetProperties().IN_FORMATS->GetCurrentValue(); - -- drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(drm->fd, blob_id); -- if (!blob) { -+ drmModePropertyBlobRes *pBlob = drmModeGetPropertyBlob( drm->fd, ulBlobId ); -+ if ( !pBlob ) -+ { - drm_log.errorf_errno("drmModeGetPropertyBlob(IN_FORMATS) failed"); - return false; - } -+ defer( drmModeFreePropertyBlob( pBlob ) ); - -- struct drm_format_modifier_blob *data = -- (struct drm_format_modifier_blob *)blob->data; -- uint32_t *fmts = (uint32_t *)((char *)data + data->formats_offset); -- struct drm_format_modifier *mods = (struct drm_format_modifier *) -- ((char *)data + data->modifiers_offset); -- for (uint32_t i = 0; i < data->count_modifiers; ++i) { -- for (int j = 0; j < 64; ++j) { -- if (mods[i].formats & ((uint64_t)1 << j)) { -- wlr_drm_format_set_add(formats, -- fmts[j + mods[i].offset], mods[i].modifier); -- } -+ 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 ); - } - } -- -- drmModeFreePropertyBlob(blob); - } - - return true; - } - --static const char *get_enum_name(const drmModePropertyRes *prop, uint64_t value) --{ -- for (int i = 0; i < prop->count_enums; i++) { -- if (prop->enums[i].value == value) -- return prop->enums[i].name; -- } -- return nullptr; --} -- - 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; -@@ -245,27 +241,21 @@ static uint32_t pick_plane_format( const struct wlr_drm_format_set *formats, uin - } - - /* Pick a primary plane that can be connected to the chosen CRTC. */ --static struct plane *find_primary_plane(struct drm_t *drm) -+static gamescope::CDRMPlane *find_primary_plane(struct drm_t *drm) - { -- struct plane *primary = nullptr; -- -- for (size_t i = 0; i < drm->planes.size(); i++) { -- struct plane *plane = &drm->planes[i]; -- -- if (!(plane->plane->possible_crtcs & (1 << drm->crtc_index))) -- continue; -+ if ( !drm->pCRTC ) -+ return nullptr; - -- uint64_t plane_type = drm->planes[i].initial_prop_values["type"]; -- if (plane_type == DRM_PLANE_TYPE_PRIMARY) { -- primary = plane; -- break; -+ 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(); - } - } - -- if (primary == nullptr) -- return nullptr; -- -- return primary; -+ return nullptr; - } - - static void drm_unlock_fb_internal( struct drm_t *drm, struct fb *fb ); -@@ -278,7 +268,10 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi - uint64_t flipcount = (uint64_t)data; - g_nCompletedPageFlipCount = flipcount; - -- if ( g_DRM.crtc->id != crtc_id ) -+ if ( !g_DRM.pCRTC ) -+ return; -+ -+ if ( g_DRM.pCRTC->GetObjectId() != crtc_id ) - return; - - // This is the last vblank time -@@ -363,209 +356,6 @@ void flip_handler_thread_run(void) - } - } - --static const drmModePropertyRes *get_prop(struct drm_t *drm, uint32_t prop_id) --{ -- if (drm->props.count(prop_id) > 0) { -- return drm->props[prop_id]; -- } -- -- drmModePropertyRes *prop = drmModeGetProperty(drm->fd, prop_id); -- if (!prop) { -- drm_log.errorf_errno("drmModeGetProperty failed"); -- return nullptr; -- } -- -- drm->props[prop_id] = prop; -- return prop; --} -- --static bool get_object_properties(struct drm_t *drm, uint32_t obj_id, uint32_t obj_type, std::map &map, std::map &values) --{ -- drmModeObjectProperties *props = drmModeObjectGetProperties(drm->fd, obj_id, obj_type); -- if (!props) { -- drm_log.errorf_errno("drmModeObjectGetProperties failed"); -- return false; -- } -- -- map = {}; -- values = {}; -- -- for (uint32_t i = 0; i < props->count_props; i++) { -- const drmModePropertyRes *prop = get_prop(drm, props->props[i]); -- if (!prop) { -- return false; -- } -- map[prop->name] = prop; -- values[prop->name] = props->prop_values[i]; -- } -- -- drmModeFreeObjectProperties(props); -- return true; --} -- --static bool compare_modes( drmModeModeInfo mode1, drmModeModeInfo mode2 ) --{ -- bool goodRefresh1 = mode1.vrefresh >= 60; -- bool goodRefresh2 = mode2.vrefresh >= 60; -- if (goodRefresh1 != goodRefresh2) -- return goodRefresh1; -- -- bool preferred1 = mode1.type & DRM_MODE_TYPE_PREFERRED; -- bool preferred2 = mode2.type & DRM_MODE_TYPE_PREFERRED; -- if (preferred1 != preferred2) -- return preferred1; -- -- int area1 = mode1.hdisplay * mode1.vdisplay; -- int area2 = mode2.hdisplay * mode2.vdisplay; -- if (area1 != area2) -- return area1 > area2; -- -- return mode1.vrefresh > mode2.vrefresh; --} -- --static void --drm_hdr_parse_edid(drm_t *drm, struct connector *connector, const struct di_edid *edid) --{ -- struct connector_metadata_t *metadata = &connector->metadata; -- -- const struct di_edid_chromaticity_coords* chroma = di_edid_get_chromaticity_coords(edid); -- const struct di_cta_hdr_static_metadata_block* hdr_static_metadata = NULL; -- const struct di_cta_colorimetry_block* colorimetry = NULL; -- -- const struct di_edid_cta* cta = NULL; -- const struct di_edid_ext* const* exts = di_edid_get_extensions(edid); -- for (; *exts != NULL; exts++) { -- if ((cta = di_edid_ext_get_cta(*exts))) -- break; -- } -- -- if (cta) { -- const struct di_cta_data_block* const* blocks = di_edid_cta_get_data_blocks(cta); -- for (; *blocks != NULL; blocks++) { -- if (!hdr_static_metadata && (hdr_static_metadata = di_cta_data_block_get_hdr_static_metadata(*blocks))) -- continue; -- if (!colorimetry && (colorimetry = di_cta_data_block_get_colorimetry(*blocks))) -- continue; -- } -- } -- -- struct hdr_metadata_infoframe *infoframe = &metadata->defaultHdrMetadata.hdmi_metadata_type1; -- -- if (chroma) { -- infoframe->display_primaries[0].x = color_xy_to_u16(chroma->red_x); -- infoframe->display_primaries[0].y = color_xy_to_u16(chroma->red_y); -- infoframe->display_primaries[1].x = color_xy_to_u16(chroma->green_x); -- infoframe->display_primaries[1].y = color_xy_to_u16(chroma->green_y); -- infoframe->display_primaries[2].x = color_xy_to_u16(chroma->blue_x); -- infoframe->display_primaries[2].y = color_xy_to_u16(chroma->blue_y); -- infoframe->white_point.x = color_xy_to_u16(chroma->white_x); -- infoframe->white_point.y = color_xy_to_u16(chroma->white_y); -- } -- -- /* Some sane defaults for SDR for displays with missing data... */ -- infoframe->max_display_mastering_luminance = nits_to_u16(1000.0f); -- infoframe->min_display_mastering_luminance = nits_to_u16_dark(0.0f); -- infoframe->max_cll = nits_to_u16(400.0f); -- infoframe->max_fall = nits_to_u16(400.0f); -- -- if (hdr_static_metadata) { -- if (hdr_static_metadata->desired_content_max_luminance) -- infoframe->max_display_mastering_luminance = nits_to_u16(hdr_static_metadata->desired_content_max_luminance); -- if (hdr_static_metadata->desired_content_min_luminance) -- infoframe->min_display_mastering_luminance = nits_to_u16_dark(hdr_static_metadata->desired_content_min_luminance); -- /* 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. -- */ -- float default_max_fall = hdr_static_metadata->desired_content_max_frame_avg_luminance -- ? hdr_static_metadata->desired_content_max_frame_avg_luminance -- : hdr_static_metadata->desired_content_max_luminance; -- -- if (default_max_fall) { -- infoframe->max_cll = nits_to_u16(default_max_fall); -- infoframe->max_fall = nits_to_u16(default_max_fall); -- } -- -- metadata->maxCLL = (uint16_t)hdr_static_metadata->desired_content_max_luminance; -- metadata->maxFALL = (uint16_t)hdr_static_metadata->desired_content_max_frame_avg_luminance; -- } -- -- metadata->supportsST2084 = -- chroma && -- colorimetry && colorimetry->bt2020_rgb && -- hdr_static_metadata && hdr_static_metadata->eotfs && hdr_static_metadata->eotfs->pq; -- -- if (metadata->supportsST2084) { -- metadata->defaultHdrMetadata.metadata_type = 0; -- infoframe->metadata_type = 0; -- infoframe->eotf = HDMI_EOTF_ST2084; -- -- metadata->hdr10_metadata_blob = drm_create_hdr_metadata_blob(drm, &metadata->defaultHdrMetadata); -- -- if (metadata->hdr10_metadata_blob == nullptr) { -- fprintf(stderr, "Failed to create blob for HDR_OUTPUT_METADATA. Falling back to null blob.\n"); -- } -- } -- -- const char *coloroverride = getenv( "GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE" ); -- if (coloroverride && drm_get_connector_type(connector->connector) == DRM_SCREEN_TYPE_INTERNAL) -- { -- if (sscanf( coloroverride, "%f %f %f %f %f %f %f %f", -- &metadata->colorimetry.primaries.r.x, &metadata->colorimetry.primaries.r.y, -- &metadata->colorimetry.primaries.g.x, &metadata->colorimetry.primaries.g.y, -- &metadata->colorimetry.primaries.b.x, &metadata->colorimetry.primaries.b.y, -- &metadata->colorimetry.white.x, &metadata->colorimetry.white.y ) == 8 ) -- { -- drm_log.infof("[colorimetry]: GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE detected"); -- } -- else -- { -- drm_log.errorf("[colorimetry]: GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE specified, but could not parse \"rx ry gx gy bx by wx wy\""); -- } -- } -- else if (connector->is_steam_deck_display && !connector->is_galileo_display) -- { -- drm_log.infof("[colorimetry]: Steam Deck (internal display) detected."); -- -- // Hardcode Steam Deck display info to support -- // BIOSes with missing info for this in EDID. -- drm_log.infof("[colorimetry]: using default steamdeck colorimetry"); -- metadata->colorimetry = displaycolorimetry_steamdeck_measured; -- metadata->eotf = EOTF_Gamma22; -- } -- else if (chroma && chroma->red_x != 0.0f) -- { -- drm_log.infof("[colorimetry]: EDID with colorimetry detected. Using it"); -- metadata->colorimetry.primaries = { { chroma->red_x, chroma->red_y }, { chroma->green_x, chroma->green_y }, { chroma->blue_x, chroma->blue_y } }; -- metadata->colorimetry.white = { chroma->white_x, chroma->white_y }; -- metadata->eotf = infoframe->eotf == HDMI_EOTF_ST2084 ? EOTF_PQ : EOTF_Gamma22; -- } -- else -- { -- // No valid chroma data in the EDID, fill it in ourselves. -- if (infoframe->eotf == HDMI_EOTF_ST2084) -- { -- drm_log.infof("[colorimetry]: EDID does not define colorimetry. Assuming rec2020 based on HDMI_EOTF_ST2084 support"); -- // Fallback to 2020 primaries for HDR -- metadata->colorimetry = displaycolorimetry_2020; -- metadata->eotf = EOTF_PQ; -- } -- else -- { -- // Fallback to 709 primaries for SDR -- drm_log.infof("[colorimetry]: EDID does not define colorimetry. Assuming rec709 / gamma 2.2"); -- metadata->colorimetry = displaycolorimetry_709; -- metadata->eotf = EOTF_Gamma22; -- } -- } -- -- drm_log.infof("[colorimetry]: r %f %f", metadata->colorimetry.primaries.r.x, metadata->colorimetry.primaries.r.y); -- drm_log.infof("[colorimetry]: g %f %f", metadata->colorimetry.primaries.g.x, metadata->colorimetry.primaries.g.y); -- drm_log.infof("[colorimetry]: b %f %f", metadata->colorimetry.primaries.b.x, metadata->colorimetry.primaries.b.y); -- drm_log.infof("[colorimetry]: w %f %f", metadata->colorimetry.white.x, metadata->colorimetry.white.y); --} -- - 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; -@@ -639,7 +429,7 @@ static uint8_t encode_max_luminance(float nits) - return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); - } - --static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm_t *drm, struct connector *conn ) -+static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm_t *drm, gamescope::CDRMConnector *conn ) - { - // A zero length indicates that the edid parsing failed. - if (orig_size == 0) { -@@ -675,7 +465,7 @@ static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm - // 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 ( g_bForceHDRSupportDebug && !conn->metadata.supportsST2084 ) -+ if ( conn->GetHDRInfo().ShouldPatchEDID() ) - { - // TODO: Allow for override of min luminance - float flMaxPeakLuminance = g_ColorMgmt.pending.hdrTonemapDisplayMetadata.BIsValid() ? -@@ -824,302 +614,119 @@ static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm - - void drm_update_patched_edid( drm_t *drm ) - { -- if (!drm || !drm->connector) -- return; -- -- create_patched_edid(drm->connector->edid_data.data(), drm->connector->edid_data.size(), drm, drm->connector); --} -- --#define GALILEO_SDC_PID 0x3003 --#define GALILEO_BOE_PID 0x3004 -- --static void parse_edid( drm_t *drm, struct connector *conn) --{ -- memset(conn->make_pnp, 0, sizeof(conn->make_pnp)); -- free(conn->make); -- conn->make = NULL; -- free(conn->model); -- conn->model = NULL; -- -- if (conn->props.count("EDID") == 0) { -+ if (!drm || !drm->pConnector) - return; -- } -- -- uint64_t blob_id = conn->initial_prop_values["EDID"]; -- if (blob_id == 0) { -- return; -- } -- -- drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(drm->fd, blob_id); -- if (!blob) { -- drm_log.errorf_errno("drmModeGetPropertyBlob(EDID) failed"); -- return; -- } -- -- struct di_info *info = di_info_parse_edid(blob->data, blob->length); -- if (!info) { -- drmModeFreePropertyBlob(blob); -- drm_log.errorf("Failed to parse edid"); -- return; -- } -- -- conn->edid_data = std::vector((const uint8_t*)blob->data, ((const uint8_t*)(blob->data)) + blob->length); -- -- drmModeFreePropertyBlob(blob); -- -- const struct di_edid *edid = di_info_get_edid(info); -- -- const struct di_edid_vendor_product *vendor_product = di_edid_get_vendor_product(edid); -- char pnp_id[] = { -- vendor_product->manufacturer[0], -- vendor_product->manufacturer[1], -- vendor_product->manufacturer[2], -- '\0', -- }; -- memcpy(conn->make_pnp, pnp_id, sizeof(pnp_id)); -- if (pnps.count(pnp_id) > 0) { -- conn->make = strdup(pnps[pnp_id].c_str()); -- } -- else { -- // Some vendors like AOC, don't have a PNP id listed, -- // but their name is literally just "AOC", so just -- // use the PNP name directly. -- conn->make = strdup(pnp_id); -- } -- -- const struct di_edid_display_descriptor *const *descriptors = di_edid_get_display_descriptors(edid); -- for (size_t i = 0; descriptors[i] != NULL; i++) { -- const struct di_edid_display_descriptor *desc = descriptors[i]; -- if (di_edid_display_descriptor_get_tag(desc) == DI_EDID_DISPLAY_DESCRIPTOR_PRODUCT_NAME) { -- conn->model = strdup(di_edid_display_descriptor_get_string(desc)); -- } -- } -- -- drm_log.infof("Connector make %s model %s", conn->make_pnp, conn->model ); -- -- conn->is_steam_deck_display = -- (strcmp(conn->make_pnp, "WLC") == 0 && strcmp(conn->model, "ANX7530 U") == 0) || -- (strcmp(conn->make_pnp, "ANX") == 0 && strcmp(conn->model, "ANX7530 U") == 0) || -- (strcmp(conn->make_pnp, "VLV") == 0 && strcmp(conn->model, "ANX7530 U") == 0) || -- (strcmp(conn->make_pnp, "VLV") == 0 && strcmp(conn->model, "Jupiter") == 0); -- -- if ((vendor_product->product == GALILEO_SDC_PID) || (vendor_product->product == GALILEO_BOE_PID)) { -- conn->is_galileo_display = vendor_product->product; -- conn->valid_display_rates = std::span(galileo_display_rates); -- } else { -- conn->is_galileo_display = 0; -- if ( conn->is_steam_deck_display ) -- conn->valid_display_rates = std::span(steam_deck_display_rates); -- } -- -- drm_hdr_parse_edid(drm, conn, edid); - -- di_info_destroy(info); -+ create_patched_edid(drm->pConnector->GetRawEDID().data(), drm->pConnector->GetRawEDID().size(), drm, drm->pConnector); - } - - static bool refresh_state( drm_t *drm ) - { -- drmModeRes *resources = drmModeGetResources(drm->fd); -- if (resources == nullptr) { -- drm_log.errorf_errno("drmModeGetResources failed"); -+ 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 < resources->count_connectors; i++) { -- uint32_t conn_id = resources->connectors[i]; -+ for ( int i = 0; i < pResources->count_connectors; i++ ) -+ { -+ uint32_t uConnectorId = pResources->connectors[i]; - -- if (drm->connectors.count(conn_id) == 0) { -- struct connector conn = { .id = conn_id }; -- drm->connectors[conn_id] = conn; -+ drmModeConnector *pConnector = drmModeGetConnector( drm->fd, uConnectorId ); -+ if ( !pConnector ) -+ continue; -+ -+ if ( !drm->connectors.contains( uConnectorId ) ) -+ { -+ drm->connectors.emplace( -+ std::piecewise_construct, -+ std::forward_as_tuple( uConnectorId ), -+ std::forward_as_tuple( pConnector ) ); - } - } - - // Remove connectors which disappeared -- auto it = drm->connectors.begin(); -- while (it != drm->connectors.end()) { -- struct connector *conn = &it->second; -- -- bool found = false; -- for (int j = 0; j < resources->count_connectors; j++) { -- if (resources->connectors[j] == conn->id) { -- found = true; -- break; -- } -- } -+ 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 (!found) { -- drm_log.debugf("connector '%s' disappeared", conn->name); -+ if ( !bFound ) -+ { -+ drm_log.debugf( "Connector '%s' disappeared.", pConnector->GetName() ); - -- if (drm->connector == conn) { -- drm_log.infof("current connector '%s' disappeared", conn->name); -- drm->connector = nullptr; -+ if ( drm->pConnector == pConnector ) -+ { -+ drm_log.infof( "Current connector '%s' disappeared.", pConnector->GetName() ); -+ drm->pConnector = nullptr; - } - -- free(conn->name); -- conn->name = nullptr; -- conn->metadata.hdr10_metadata_blob = nullptr; -- drmModeFreeConnector(conn->connector); -- it = drm->connectors.erase(it); -- } else { -- it++; -+ iter = drm->connectors.erase( iter ); - } -+ else -+ iter++; - } - -- drmModeFreeResources(resources); -- -- // Re-probe connectors props and status -- for (auto &kv : drm->connectors) { -- struct connector *conn = &kv.second; -- if (conn->connector != nullptr) { -- conn->metadata.hdr10_metadata_blob = nullptr; -- drmModeFreeConnector(conn->connector); -- } -- -- conn->connector = drmModeGetConnector(drm->fd, conn->id); -- if (conn->connector == nullptr) { -- drm_log.errorf_errno("drmModeGetConnector failed"); -- return false; -- } -- -- if (!get_object_properties(drm, conn->id, DRM_MODE_OBJECT_CONNECTOR, conn->props, conn->initial_prop_values)) { -- return false; -- } -- -- /* sort modes by preference: preferred flag, then highest area, then -- * highest refresh rate */ -- std::stable_sort(conn->connector->modes, conn->connector->modes + conn->connector->count_modes, compare_modes); -- -- parse_edid(drm, conn); -- -- if ( conn->name != nullptr ) -- { -- free(conn->name); -- conn->name = nullptr; -- } -- -- const char *type_str = drmModeGetConnectorTypeName(conn->connector->connector_type); -- if (!type_str) -- type_str = "Unknown"; -- -- char name[128] = {}; -- snprintf(name, sizeof(name), "%s-%d", type_str, conn->connector->connector_type_id); -- conn->name = strdup(name); -- -- conn->possible_crtcs = drmModeConnectorGetPossibleCrtcs(drm->fd, conn->connector); -- if (!conn->possible_crtcs) -- drm_log.errorf_errno("drmModeConnectorGetPossibleCrtcs failed"); -- -- conn->has_colorspace = conn->props.contains( "Colorspace" ); -- conn->has_hdr_output_metadata = conn->props.contains( "HDR_OUTPUT_METADATA" ); -- conn->has_content_type = conn->props.contains( "content type" ); -- -- conn->current.crtc_id = conn->initial_prop_values["CRTC_ID"]; -- if (conn->has_colorspace) -- conn->current.colorspace = conn->initial_prop_values["Colorspace"]; -- if (conn->has_hdr_output_metadata) -- conn->current.hdr_output_metadata = std::make_shared(nullptr, conn->initial_prop_values["HDR_OUTPUT_METADATA"], false); -- if (conn->has_content_type) -- conn->current.content_type = conn->initial_prop_values["content type"]; -- -- conn->target_refresh = 0; -- -- conn->vrr_capable = !!conn->initial_prop_values["vrr_capable"]; -- -- drm_log.debugf("found new connector '%s'", conn->name); -+ // Re-probe connectors props and status) -+ for ( auto &iter : drm->connectors ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter.second; -+ pConnector->RefreshState(); - } - -- for (size_t i = 0; i < drm->crtcs.size(); i++) { -- struct crtc *crtc = &drm->crtcs[i]; -- if (!get_object_properties(drm, crtc->id, DRM_MODE_OBJECT_CRTC, crtc->props, crtc->initial_prop_values)) { -- return false; -- } -+ for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) -+ pCRTC->RefreshState(); - -- crtc->has_gamma_lut = crtc->props.contains( "GAMMA_LUT" ); -- if (!crtc->has_gamma_lut) -- drm_log.infof("CRTC %" PRIu32 " has no gamma LUT support", crtc->id); -- crtc->has_degamma_lut = crtc->props.contains( "DEGAMMA_LUT" ); -- if (!crtc->has_degamma_lut) -- drm_log.infof("CRTC %" PRIu32 " has no degamma LUT support", crtc->id); -- crtc->has_ctm = crtc->props.contains( "CTM" ); -- if (!crtc->has_ctm) -- drm_log.infof("CRTC %" PRIu32 " has no CTM support", crtc->id); -- crtc->has_vrr_enabled = crtc->props.contains( "VRR_ENABLED" ); -- if (!crtc->has_vrr_enabled) -- drm_log.infof("CRTC %" PRIu32 " has no VRR_ENABLED support", crtc->id); -- crtc->has_valve1_regamma_tf = crtc->props.contains( "VALVE1_CRTC_REGAMMA_TF" ); -- if (!crtc->has_valve1_regamma_tf) -- drm_log.infof("CRTC %" PRIu32 " has no VALVE1_CRTC_REGAMMA_TF support", crtc->id); -- -- crtc->current.active = crtc->initial_prop_values["ACTIVE"]; -- if (crtc->has_vrr_enabled) -- drm->current.vrr_enabled = crtc->initial_prop_values["VRR_ENABLED"]; -- if (crtc->has_valve1_regamma_tf) -- drm->current.output_tf = (drm_valve1_transfer_function) crtc->initial_prop_values["VALVE1_CRTC_REGAMMA_TF"]; -- } -- -- for (size_t i = 0; i < drm->planes.size(); i++) { -- struct plane *plane = &drm->planes[i]; -- if (!get_object_properties(drm, plane->id, DRM_MODE_OBJECT_PLANE, plane->props, plane->initial_prop_values)) { -- return false; -- } -- plane->has_color_mgmt = plane->props.contains( "VALVE1_PLANE_BLEND_TF" ); -- } -+ for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) -+ pPlane->RefreshState(); - - return true; - } - - static bool get_resources(struct drm_t *drm) - { -- drmModeRes *resources = drmModeGetResources(drm->fd); -- if (resources == nullptr) { -- drm_log.errorf_errno("drmModeGetResources failed"); -- return false; -- } -- -- for (int i = 0; i < resources->count_crtcs; i++) { -- struct crtc crtc = { .id = resources->crtcs[i] }; -- -- crtc.crtc = drmModeGetCrtc(drm->fd, crtc.id); -- if (crtc.crtc == nullptr) { -- drm_log.errorf_errno("drmModeGetCrtc failed"); -+ { -+ drmModeRes *pResources = drmModeGetResources( drm->fd ); -+ if ( !pResources ) -+ { -+ drm_log.errorf_errno( "drmModeGetResources failed" ); - return false; - } -+ defer( drmModeFreeResources( pResources ) ); - -- drm->crtcs.push_back(crtc); -- } -- -- drmModeFreeResources(resources); -- -- drmModePlaneRes *plane_resources = drmModeGetPlaneResources(drm->fd); -- if (!plane_resources) { -- drm_log.errorf_errno("drmModeGetPlaneResources failed"); -- return false; -+ 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 ) ); -+ } - } - -- for (uint32_t i = 0; i < plane_resources->count_planes; i++) { -- struct plane plane = { .id = plane_resources->planes[i] }; -- -- plane.plane = drmModeGetPlane(drm->fd, plane.id); -- if (plane.plane == nullptr) { -- drm_log.errorf_errno("drmModeGetPlane failed"); -+ { -+ drmModePlaneRes *pPlaneResources = drmModeGetPlaneResources( drm->fd ); -+ if ( !pPlaneResources ) -+ { -+ drm_log.errorf_errno( "drmModeGetPlaneResources failed" ); - return false; - } -+ defer( drmModeFreePlaneResources( pPlaneResources ) ); - -- drm->planes.push_back(plane); -- } -- -- drmModeFreePlaneResources(plane_resources); -- -- if (!refresh_state(drm)) -- return false; -- -- for (size_t i = 0; i < drm->crtcs.size(); i++) { -- struct crtc *crtc = &drm->crtcs[i]; -- crtc->pending = crtc->current; -+ 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 true; -+ return refresh_state( drm ); - } - - struct mode_blocklist_entry -@@ -1220,31 +827,33 @@ static bool get_saved_mode(const char *description, saved_mode &mode_info) - - static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - { -- if (drm->connector && drm->connector->connector->connection != DRM_MODE_CONNECTED) { -- drm_log.infof("current connector '%s' disconnected", drm->connector->name); -- drm->connector = nullptr; -+ if (drm->pConnector && drm->pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED) { -+ drm_log.infof("current connector '%s' disconnected", drm->pConnector->GetName()); -+ drm->pConnector = nullptr; - } - -- struct connector *best = nullptr; -- int best_priority = INT_MAX; -- for (auto &kv : drm->connectors) { -- struct connector *conn = &kv.second; -+ gamescope::CDRMConnector *best = nullptr; -+ int nBestPriority = INT_MAX; -+ for ( auto &iter : drm->connectors ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter.second; - -- if (conn->connector->connection != DRM_MODE_CONNECTED) -+ if ( pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED ) - continue; - -- if (drm->force_internal && drm_get_connector_type(conn->connector) == DRM_SCREEN_TYPE_EXTERNAL) -+ if ( drm->force_internal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) - continue; - -- int priority = get_connector_priority(drm, conn->name); -- if (priority < best_priority) { -- best = conn; -- best_priority = priority; -+ int nPriority = get_connector_priority( drm, pConnector->GetName() ); -+ if ( nPriority < nBestPriority ) -+ { -+ best = pConnector; -+ nBestPriority = nPriority; - } - } - - if (!force) { -- if ((!best && drm->connector) || (best && best == drm->connector)) { -+ if ((!best && drm->pConnector) || (best && best == drm->pConnector)) { - // Let's keep our current connector - return true; - } -@@ -1268,12 +877,12 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - } - - char description[256]; -- if (drm_get_connector_type(best->connector) == DRM_SCREEN_TYPE_INTERNAL) { -+ if (best->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL) { - snprintf(description, sizeof(description), "Internal screen"); -- } else if (best->make && best->model) { -- snprintf(description, sizeof(description), "%s %s", best->make, best->model); -- } else if (best->model) { -- snprintf(description, sizeof(description), "%s", best->model); -+ } 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"); - } -@@ -1281,17 +890,17 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - const drmModeModeInfo *mode = nullptr; - if ( drm->preferred_width != 0 || drm->preferred_height != 0 || drm->preferred_refresh != 0 ) - { -- mode = find_mode(best->connector, drm->preferred_width, drm->preferred_height, drm->preferred_refresh); -+ mode = find_mode(best->GetModeConnector(), drm->preferred_width, drm->preferred_height, drm->preferred_refresh); - } - -- if (!mode && drm_get_connector_type(best->connector) == DRM_SCREEN_TYPE_EXTERNAL) { -+ if (!mode && best->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL) { - saved_mode mode_info; - if (get_saved_mode(description, mode_info)) -- mode = find_mode(best->connector, mode_info.width, mode_info.height, mode_info.refresh); -+ mode = find_mode(best->GetModeConnector(), mode_info.width, mode_info.height, mode_info.refresh); - } - - if (!mode) { -- mode = find_mode(best->connector, 0, 0, 0); -+ mode = find_mode(best->GetModeConnector(), 0, 0, 0); - } - - if (!mode) { -@@ -1299,7 +908,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - return false; - } - -- best->target_refresh = mode->vrefresh; -+ best->SetBaseRefresh( mode->vrefresh ); - - if (!drm_set_mode(drm, mode)) { - return false; -@@ -1307,15 +916,15 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - - const struct wlserver_output_info wlserver_output_info = { - .description = description, -- .phys_width = (int) best->connector->mmWidth, -- .phys_height = (int) best->connector->mmHeight, -+ .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) -- create_patched_edid(best->edid_data.data(), best->edid_data.size(), drm, best); -+ create_patched_edid(best->GetRawEDID().data(), best->GetRawEDID().size(), drm, best); - - update_connector_display_info_wl( drm ); - -@@ -1366,6 +975,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ - { - load_pnps(); - -+ drm->bUseLiftoff = true; -+ - drm->wants_vrr_enabled = wants_adaptive_sync; - drm->preferred_width = width; - drm->preferred_height = height; -@@ -1435,14 +1046,15 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ - return false; - - drm_log.infof("Connectors:"); -- for (const auto &kv : drm->connectors) { -- const struct connector *conn = &kv.second; -+ for ( auto &iter : drm->connectors ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter.second; - - const char *status_str = "disconnected"; -- if ( conn->connector->connection == DRM_MODE_CONNECTED ) -+ if ( pConnector->GetModeConnector()->connection == DRM_MODE_CONNECTED ) - status_str = "connected"; - -- drm_log.infof(" %s (%s)", conn->name, status_str); -+ drm_log.infof(" %s (%s)", pConnector->GetName(), status_str); - } - - drm->connector_priorities = parse_connector_priorities( g_sOutputName ); -@@ -1452,23 +1064,24 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ - } - - // Fetch formats which can be scanned out -- for (size_t i = 0; i < drm->planes.size(); i++) { -- struct plane *plane = &drm->planes[i]; -- if (!get_plane_formats(drm, plane, &drm->formats)) -+ 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 -- struct plane *primary_plane = drm->primary; -- if (primary_plane == nullptr) { -- primary_plane = find_primary_plane(drm); -- } -- if (primary_plane == nullptr) { -+ 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, primary_plane, &drm->primary_formats)) { -+ if ( !get_plane_formats( drm, drm->pPrimaryPlane, &drm->primary_formats ) ) -+ { - return false; - } - -@@ -1511,9 +1124,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ - std::thread flip_handler_thread( flip_handler_thread_run ); - flip_handler_thread.detach(); - -- if (g_bUseLayers) { -+ if ( drm->bUseLiftoff ) - liftoff_log_set_priority(g_bDebugLayers ? LIFTOFF_DEBUG : LIFTOFF_ERROR); -- } - - hdr_output_metadata sdr_metadata; - memset(&sdr_metadata, 0, sizeof(sdr_metadata)); -@@ -1525,48 +1137,6 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ - return true; - } - --static int add_property(drmModeAtomicReq *req, uint32_t obj_id, std::map &props, const char *name, uint64_t value) --{ -- if ( props.count( name ) == 0 ) -- { -- drm_log.errorf("no property %s on object %u", name, obj_id); -- return -ENOENT; -- } -- -- const drmModePropertyRes *prop = props[ name ]; -- -- int ret = drmModeAtomicAddProperty(req, obj_id, prop->prop_id, value); -- if ( ret < 0 ) -- { -- drm_log.errorf_errno( "drmModeAtomicAddProperty failed" ); -- } -- return ret; --} -- --static int add_connector_property(drmModeAtomicReq *req, struct connector *conn, const char *name, uint64_t value) --{ -- return add_property(req, conn->id, conn->props, name, value); --} -- --static int add_crtc_property(drmModeAtomicReq *req, struct crtc *crtc, const char *name, uint64_t value) --{ -- return add_property(req, crtc->id, crtc->props, name, value); --} -- --static int add_plane_property(drmModeAtomicReq *req, struct plane *plane, const char *name, uint64_t value) --{ -- return add_property(req, plane->id, plane->props, name, value); --} -- --static std::shared_ptr get_default_hdr_metadata(struct drm_t *drm, struct connector *connector) --{ -- if ( !connector->has_hdr_output_metadata ) -- return nullptr; -- if ( !connector->metadata.supportsST2084 ) -- return nullptr; -- return drm->sdr_static_metadata; --} -- - void finish_drm(struct drm_t *drm) - { - // Disable all connectors, CRTCs and planes. This is necessary to leave a -@@ -1575,88 +1145,122 @@ void finish_drm(struct drm_t *drm) - // together. - - drmModeAtomicReq *req = drmModeAtomicAlloc(); -- for ( auto &kv : drm->connectors ) { -- struct connector *conn = &kv.second; -- add_connector_property(req, conn, "CRTC_ID", 0); -- if (conn->has_colorspace) -- add_connector_property(req, conn, "Colorspace", 0); -- // HACK HACK: Setting to 0 doesn't disable HDR properly. -- // Set an SDR metadata blob. -- if (conn->has_hdr_output_metadata) -- { -- auto metadata = get_default_hdr_metadata( drm, conn ); -- add_connector_property(req, conn, "HDR_OUTPUT_METADATA", metadata ? metadata->blob : 0); -- } -- if (conn->has_content_type) -- add_connector_property(req, conn, "content type", 0); -- } -- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) { -- add_crtc_property(req, &drm->crtcs[i], "MODE_ID", 0); -- if ( drm->crtcs[i].has_gamma_lut ) -- add_crtc_property(req, &drm->crtcs[i], "GAMMA_LUT", 0); -- if ( drm->crtcs[i].has_degamma_lut ) -- add_crtc_property(req, &drm->crtcs[i], "DEGAMMA_LUT", 0); -- if ( drm->crtcs[i].has_ctm ) -- add_crtc_property(req, &drm->crtcs[i], "CTM", 0); -- if ( drm->crtcs[i].has_vrr_enabled ) -- add_crtc_property(req, &drm->crtcs[i], "VRR_ENABLED", 0); -- if ( drm->crtcs[i].has_valve1_regamma_tf ) -- add_crtc_property(req, &drm->crtcs[i], "VALVE1_CRTC_REGAMMA_TF", 0); -- add_crtc_property(req, &drm->crtcs[i], "ACTIVE", 0); -- } -- for ( size_t i = 0; i < drm->planes.size(); i++ ) { -- struct plane *plane = &drm->planes[i]; -- add_plane_property(req, plane, "FB_ID", 0); -- add_plane_property(req, plane, "CRTC_ID", 0); -- add_plane_property(req, plane, "SRC_X", 0); -- add_plane_property(req, plane, "SRC_Y", 0); -- add_plane_property(req, plane, "SRC_W", 0); -- add_plane_property(req, plane, "SRC_H", 0); -- add_plane_property(req, plane, "CRTC_X", 0); -- add_plane_property(req, plane, "CRTC_Y", 0); -- add_plane_property(req, plane, "CRTC_W", 0); -- add_plane_property(req, plane, "CRTC_H", 0); -- if (plane->props.count("rotation") > 0) -- add_plane_property(req, plane, "rotation", DRM_MODE_ROTATE_0); -- if (plane->props.count("alpha") > 0) -- add_plane_property(req, plane, "alpha", 0xFFFF); -- if (plane->props.count("VALVE1_PLANE_DEGAMMA_TF") > 0) -- add_plane_property(req, plane, "VALVE1_PLANE_DEGAMMA_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -- if (plane->props.count("VALVE1_PLANE_HDR_MULT") > 0) -- add_plane_property(req, plane, "VALVE1_PLANE_HDR_MULT", 0x100000000ULL); -- if (plane->props.count("VALVE1_PLANE_SHAPER_TF") > 0) -- add_plane_property(req, plane, "VALVE1_PLANE_SHAPER_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -- if (plane->props.count("VALVE1_PLANE_SHAPER_LUT") > 0) -- add_plane_property(req, plane, "VALVE1_PLANE_SHAPER_LUT", 0 ); -- if (plane->props.count("VALVE1_PLANE_LUT3D") > 0) -- add_plane_property(req, plane, "VALVE1_PLANE_LUT3D", 0 ); -- if (plane->props.count("VALVE1_PLANE_BLEND_TF") > 0) -- add_plane_property(req, plane, "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -- if (plane->props.count("VALVE1_PLANE_BLEND_LUT") > 0) -- add_plane_property(req, plane, "VALVE1_PLANE_BLEND_LUT", 0 ); -- if (plane->props.count("VALVE1_PLANE_CTM") > 0) -- add_plane_property(req, plane, "VALVE1_PLANE_CTM", 0 ); -- } -- // 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); -+ for ( auto &iter : drm->connectors ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter.second; - -- // We can't close the DRM FD here, it might still be in use by the -- // page-flip handler thread. --} -+ pConnector->GetProperties().CRTC_ID->SetPendingValue( req, 0, true ); - --int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) --{ -- int ret; -+ if ( pConnector->GetProperties().Colorspace ) -+ pConnector->GetProperties().Colorspace->SetPendingValue( req, 0, true ); - -- assert( drm->req != nullptr ); -+ 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->blob, 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< gamescope::CDRMCRTC > &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().VALVE1_CRTC_REGAMMA_TF ) -+ pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->SetPendingValue( req, 0, true ); -+ } -+ -+ for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) -+ { -+ pPlane->GetProperties().FB_ID->SetPendingValue( req, 0, 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().VALVE1_PLANE_DEGAMMA_TF ) -+ pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); -+ -+ if ( pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_LUT ) -+ pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_LUT->SetPendingValue( req, 0, true ); -+ -+ if ( pPlane->GetProperties().VALVE1_PLANE_CTM ) -+ pPlane->GetProperties().VALVE1_PLANE_CTM->SetPendingValue( req, 0, true ); -+ -+ if ( pPlane->GetProperties().VALVE1_PLANE_HDR_MULT ) -+ pPlane->GetProperties().VALVE1_PLANE_HDR_MULT->SetPendingValue( req, 0x100000000ULL, true ); -+ -+ if ( pPlane->GetProperties().VALVE1_PLANE_SHAPER_TF ) -+ pPlane->GetProperties().VALVE1_PLANE_SHAPER_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); -+ -+ if ( pPlane->GetProperties().VALVE1_PLANE_SHAPER_LUT ) -+ pPlane->GetProperties().VALVE1_PLANE_SHAPER_LUT->SetPendingValue( req, 0, true ); -+ -+ if ( pPlane->GetProperties().VALVE1_PLANE_LUT3D ) -+ pPlane->GetProperties().VALVE1_PLANE_LUT3D->SetPendingValue( req, 0, true ); -+ -+ if ( pPlane->GetProperties().VALVE1_PLANE_BLEND_TF ) -+ pPlane->GetProperties().VALVE1_PLANE_BLEND_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); -+ -+ if ( pPlane->GetProperties().VALVE1_PLANE_BLEND_LUT ) -+ pPlane->GetProperties().VALVE1_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); -+ -+ // We can't close the DRM FD here, it might still be in use by the -+ // page-flip handler thread. -+} -+ -+int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) -+{ -+ int ret; -+ -+ assert( drm->req != nullptr ); - - // if (drm->kms_in_fence_fd != -1) { - // add_plane_property(req, plane_id, "IN_FENCE_FD", drm->kms_in_fence_fd); -@@ -1707,14 +1311,32 @@ int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) - - drm->pending = drm->current; - -- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) -+ 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 ) - { -- drm->crtcs[i].pending = drm->crtcs[i].current; -+ for ( std::optional &oProperty : pPlane->GetProperties() ) -+ { -+ if ( oProperty ) -+ oProperty->Rollback(); -+ } - } - -- for (auto &kv : drm->connectors) { -- struct connector *conn = &kv.second; -- conn->pending = conn->current; -+ for ( auto &iter : drm->connectors ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter.second; -+ for ( std::optional &oProperty : pConnector->GetProperties() ) -+ { -+ if ( oProperty ) -+ oProperty->Rollback(); -+ } - } - - // Undo refcount if the commit didn't actually work -@@ -1736,14 +1358,32 @@ int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) - - drm->current = drm->pending; - -- for (auto & crtc : drm->crtcs) -+ for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) - { -- crtc.current = crtc.pending; -+ for ( std::optional &oProperty : pCRTC->GetProperties() ) -+ { -+ if ( oProperty ) -+ oProperty->OnCommit(); -+ } - } - -- for (auto &kv : drm->connectors) { -- struct connector *conn = &kv.second; -- conn->current = conn->pending; -+ 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(); -+ } - } - } - -@@ -1928,190 +1568,87 @@ void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ) - drm_unlock_fb_internal( drm, &fb ); - } - --static uint64_t determine_drm_orientation(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) -+static uint64_t determine_drm_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) - { -- drm_screen_type screenType = drm_get_connector_type(conn->connector); -- -- if (conn && conn->props.count("panel orientation") > 0) -+ if ( pConnector && pConnector->GetProperties().panel_orientation ) - { -- const char *orientation = get_enum_name(conn->props["panel orientation"], conn->initial_prop_values["panel orientation"]); -- -- if (strcmp(orientation, "Normal") == 0) -+ switch ( pConnector->GetProperties().panel_orientation->GetCurrentValue() ) - { -- return DRM_MODE_ROTATE_0; -- } -- else if (strcmp(orientation, "Left Side Up") == 0) -- { -- return DRM_MODE_ROTATE_90; -- } -- else if (strcmp(orientation, "Upside Down") == 0) -- { -- return DRM_MODE_ROTATE_180; -- } -- else if (strcmp(orientation, "Right Side Up") == 0) -- { -- return DRM_MODE_ROTATE_270; -+ case DRM_MODE_PANEL_ORIENTATION_NORMAL: -+ return DRM_MODE_ROTATE_0; -+ case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: -+ return DRM_MODE_ROTATE_180; -+ case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: -+ return DRM_MODE_ROTATE_90; -+ case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: -+ return DRM_MODE_ROTATE_270; - } - } -- else -+ -+ if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && mode ) - { -- if (screenType == DRM_SCREEN_TYPE_INTERNAL && mode) -- { -- // Auto-detect portait mode for internal displays -- return mode->hdisplay < mode->vdisplay ? DRM_MODE_ROTATE_270 : DRM_MODE_ROTATE_0; -- } -- else -- { -- return DRM_MODE_ROTATE_0; -- } -+ // Auto-detect portait mode for internal displays -+ return mode->hdisplay < mode->vdisplay ? DRM_MODE_ROTATE_270 : DRM_MODE_ROTATE_0; - } - - return DRM_MODE_ROTATE_0; - } - - /* Handle the orientation of the display */ --static void update_drm_effective_orientation(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) -+static void update_drm_effective_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) - { -- drm_screen_type screenType = drm_get_connector_type(conn->connector); -+ gamescope::GamescopeScreenType eScreenType = pConnector->GetScreenType(); - -- if (screenType == DRM_SCREEN_TYPE_INTERNAL) -+ if ( eScreenType == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - { - switch ( g_drmModeOrientation ) - { - case PANEL_ORIENTATION_0: -- g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_0; -+ g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_0; - break; - case PANEL_ORIENTATION_90: -- g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_90; -+ g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_90; - break; - case PANEL_ORIENTATION_180: -- g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_180; -+ g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_180; - break; - case PANEL_ORIENTATION_270: -- g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_270; -+ g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_270; - break; - case PANEL_ORIENTATION_AUTO: -- g_drmEffectiveOrientation[screenType] = determine_drm_orientation(drm, conn, mode); -+ g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); - break; - } - } - else - { -- g_drmEffectiveOrientation[screenType] = determine_drm_orientation(drm, conn, mode); -+ g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); - } - } - --static void update_drm_effective_orientations(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) -+static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) - { -- drm_screen_type screenType = drm_get_connector_type(conn->connector); -- if (screenType == DRM_SCREEN_TYPE_INTERNAL) -- { -- update_drm_effective_orientation(drm, conn, mode); -- return; -- } -- else if (screenType == DRM_SCREEN_TYPE_EXTERNAL) -- { -- update_drm_effective_orientation(drm, conn, mode); -+ gamescope::CDRMConnector *pInternalConnector = nullptr; -+ if ( drm->pConnector && drm->pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) -+ pInternalConnector = drm->pConnector; - -- struct connector *internal_conn = nullptr; -- for ( auto &kv : drm->connectors ) { -- struct connector *kv_con = &kv.second; -- if (kv_con->connector) -+ if ( !pInternalConnector ) -+ { -+ for ( auto &iter : drm->connectors ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter.second; -+ if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - { -- drm_screen_type kv_screentype = drm_get_connector_type(kv_con->connector); -- if (kv_screentype == DRM_SCREEN_TYPE_INTERNAL) -- { -- internal_conn = kv_con; -- break; -- } -+ pInternalConnector = pConnector; -+ // Find mode for internal connector instead. -+ pMode = find_mode(pInternalConnector->GetModeConnector(), 0, 0, 0); -+ break; - } - } -- -- if (internal_conn) -- { -- const drmModeModeInfo *default_internal_mode = find_mode(internal_conn->connector, 0, 0, 0); -- update_drm_effective_orientation(drm, internal_conn, default_internal_mode); -- } -- } --} -- --/* Prepares an atomic commit without using libliftoff */ --static int --drm_prepare_basic( struct drm_t *drm, const struct FrameInfo_t *frameInfo ) --{ -- // Discard cases where our non-liftoff path is known to fail -- -- drm_screen_type screenType = drm_get_screen_type(drm); -- -- // It only supports one layer -- if ( frameInfo->layerCount > 1 ) -- { -- drm_verbose_log.errorf("drm_prepare_basic: cannot handle %d layers", frameInfo->layerCount); -- return -EINVAL; -- } -- -- if ( frameInfo->layers[ 0 ].fbid == 0 ) -- { -- drm_verbose_log.errorf("drm_prepare_basic: layer has no FB"); -- return -EINVAL; -- } -- -- drmModeAtomicReq *req = drm->req; -- uint32_t fb_id = frameInfo->layers[ 0 ].fbid; -- -- drm->fbids_in_req.push_back( fb_id ); -- -- add_plane_property(req, drm->primary, "rotation", g_drmEffectiveOrientation[screenType] ); -- -- add_plane_property(req, drm->primary, "FB_ID", fb_id); -- add_plane_property(req, drm->primary, "CRTC_ID", drm->crtc->id); -- add_plane_property(req, drm->primary, "SRC_X", 0); -- add_plane_property(req, drm->primary, "SRC_Y", 0); -- -- const uint16_t srcWidth = frameInfo->layers[ 0 ].tex->width(); -- const uint16_t srcHeight = frameInfo->layers[ 0 ].tex->height(); -- -- add_plane_property(req, drm->primary, "SRC_W", srcWidth << 16); -- add_plane_property(req, drm->primary, "SRC_H", srcHeight << 16); -- -- gpuvis_trace_printf ( "legacy flip fb_id %u src %ix%i", fb_id, -- srcWidth, srcHeight ); -- -- int64_t crtcX = frameInfo->layers[ 0 ].offset.x * -1; -- int64_t crtcY = frameInfo->layers[ 0 ].offset.y * -1; -- int64_t crtcW = srcWidth / frameInfo->layers[ 0 ].scale.x; -- int64_t crtcH = srcHeight / frameInfo->layers[ 0 ].scale.y; -- -- if ( g_bRotated ) -- { -- int64_t imageH = frameInfo->layers[ 0 ].tex->contentHeight() / frameInfo->layers[ 0 ].scale.y; -- -- int64_t tmp = crtcX; -- crtcX = g_nOutputHeight - imageH - crtcY; -- crtcY = tmp; -- -- tmp = crtcW; -- crtcW = crtcH; -- crtcH = tmp; -- } -- -- add_plane_property(req, drm->primary, "CRTC_X", crtcX); -- add_plane_property(req, drm->primary, "CRTC_Y", crtcY); -- add_plane_property(req, drm->primary, "CRTC_W", crtcW); -- add_plane_property(req, drm->primary, "CRTC_H", crtcH); -- -- gpuvis_trace_printf ( "crtc %li,%li %lix%li", crtcX, crtcY, crtcW, crtcH ); -- -- // TODO: disable all planes except drm->primary -- -- unsigned test_flags = (drm->flags & DRM_MODE_ATOMIC_ALLOW_MODESET) | DRM_MODE_ATOMIC_TEST_ONLY; -- int ret = drmModeAtomicCommit( drm->fd, drm->req, test_flags, NULL ); -- -- if ( ret != 0 && ret != -EINVAL && ret != -ERANGE ) { -- drm_log.errorf_errno( "drmModeAtomicCommit failed" ); - } - -- return ret; -+ if ( pInternalConnector ) -+ update_drm_effective_orientation( drm, pInternalConnector, pMode ); - } - - // Only used for NV12 buffers -@@ -2333,10 +1870,498 @@ bool g_bDisableBlendTF = false; - - bool g_bSinglePlaneOptimizations = true; - -+namespace gamescope -+{ -+ //////////////////// -+ // 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.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.VALVE1_PLANE_DEGAMMA_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_DEGAMMA_TF", this, *rawProperties ); -+ m_Props.VALVE1_PLANE_DEGAMMA_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_DEGAMMA_LUT", this, *rawProperties ); -+ m_Props.VALVE1_PLANE_CTM = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_CTM", this, *rawProperties ); -+ m_Props.VALVE1_PLANE_HDR_MULT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_HDR_MULT", this, *rawProperties ); -+ m_Props.VALVE1_PLANE_SHAPER_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_SHAPER_LUT", this, *rawProperties ); -+ m_Props.VALVE1_PLANE_SHAPER_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_SHAPER_TF", this, *rawProperties ); -+ m_Props.VALVE1_PLANE_LUT3D = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_LUT3D", this, *rawProperties ); -+ m_Props.VALVE1_PLANE_BLEND_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_BLEND_TF", this, *rawProperties ); -+ m_Props.VALVE1_PLANE_BLEND_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_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.VALVE1_CRTC_REGAMMA_TF = CDRMAtomicProperty::Instantiate( "VALVE1_CRTC_REGAMMA_TF", this, *rawProperties ); -+ } -+ } -+ -+ ///////////////////////// -+ // CDRMConnector -+ ///////////////////////// -+ CDRMConnector::CDRMConnector( drmModeConnector *pConnector ) -+ : CDRMAtomicTypedObject( pConnector->connector_id ) -+ , 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; -+ } ); -+ -+ // 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'; -+ -+ 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 ); -+ } -+ -+ ParseEDID(); -+ } -+ -+ 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]; -+ if ( di_edid_display_descriptor_get_tag( pDesc ) == 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 ) ); -+ } -+ } -+ -+ drm_log.infof("Connector %s -> %s - %s", m_Mutable.szName, m_Mutable.szMakePNP, m_Mutable.szModel ); -+ -+ const bool bSteamDeckDisplay = -+ ( m_Mutable.szMakePNP == "WLC"sv && m_Mutable.szModel == "ANX7530 U"sv ) || -+ ( m_Mutable.szMakePNP == "ANX"sv && m_Mutable.szModel == "ANX7530 U"sv ) || -+ ( m_Mutable.szMakePNP == "VLV"sv && m_Mutable.szModel == "ANX7530 U"sv ) || -+ ( m_Mutable.szMakePNP == "VLV"sv && m_Mutable.szModel == "Jupiter"sv ) || -+ ( m_Mutable.szMakePNP == "VLV"sv && m_Mutable.szModel == "Galileo"sv ); -+ -+ if ( bSteamDeckDisplay ) -+ { -+ static constexpr uint32_t kPIDGalileoSDC = 0x3003; -+ static constexpr uint32_t kPIDGalileoBOE = 0x3004; -+ -+ if ( pProduct->product == kPIDGalileoSDC ) -+ { -+ m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC; -+ m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckOLEDRates ); -+ } -+ else if ( pProduct->product == kPIDGalileoBOE ) -+ { -+ m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE; -+ m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckOLEDRates ); -+ } -+ else -+ { -+ m_Mutable.eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD; -+ m_Mutable.ValidDynamicRefreshRates = std::span( s_kSteamDeckLCDRates ); -+ } -+ } -+ -+ // Colorimetry -+ const char *pszColorOverride = getenv( "GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE" ); -+ if ( pszColorOverride && *pszColorOverride && GetScreenType() == GAMESCOPE_SCREEN_TYPE_INTERNAL ) -+ { -+ if ( sscanf( pszColorOverride, "%f %f %f %f %f %f %f %f", -+ &m_Mutable.DisplayColorimetry.primaries.r.x, &m_Mutable.DisplayColorimetry.primaries.r.y, -+ &m_Mutable.DisplayColorimetry.primaries.g.x, &m_Mutable.DisplayColorimetry.primaries.g.y, -+ &m_Mutable.DisplayColorimetry.primaries.b.x, &m_Mutable.DisplayColorimetry.primaries.b.y, -+ &m_Mutable.DisplayColorimetry.white.x, &m_Mutable.DisplayColorimetry.white.y ) == 8 ) -+ { -+ drm_log.infof( "[colorimetry]: GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE detected" ); -+ } -+ else -+ { -+ drm_log.errorf( "[colorimetry]: GAMESCOPE_INTERNAL_COLORIMETRY_OVERRIDE specified, but could not parse \"rx ry gx gy bx by wx wy\"" ); -+ } -+ } -+ else if ( m_Mutable.eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) -+ { -+ drm_log.infof( "[colorimetry]: Steam Deck LCD detected. Using known colorimetry" ); -+ m_Mutable.DisplayColorimetry = displaycolorimetry_steamdeck_measured; -+ } -+ else -+ { -+ // 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 }, -+ }; -+ } -+ } -+ -+ 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. -+ ///////////////////// -+ std::optional oKnownHDRInfo = GetKnownDisplayHDRInfo( m_Mutable.eKnownDisplay ); -+ if ( oKnownHDRInfo ) -+ { -+ m_Mutable.HDR = *oKnownHDRInfo; -+ } -+ else -+ { -+ 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 ); -+ -+ // Generate a default HDR10 infoframe. -+ 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 = drm_create_hdr_metadata_blob( &g_DRM, &defaultHDRMetadata ); -+ } -+ else -+ { -+ m_Mutable.HDR.bExposeHDRSupport = false; -+ } -+ } -+ } -+ -+ /*static*/ std::optional CDRMConnector::GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ) -+ { -+ if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE || eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC ) -+ { -+ // The stuff in the EDID for the HDR metadata does not fully -+ // reflect what we can achieve on the display by poking at more -+ // things out-of-band. -+ return HDRInfo -+ { -+ .bExposeHDRSupport = true, -+ .eOutputEncodingEOTF = EOTF_Gamma22, -+ .uMaxContentLightLevel = nits_to_u16( 1000.0f ), -+ .uMaxFrameAverageLuminance = nits_to_u16( 800.0f ), // Full-frame sustained. -+ .uMinContentLightLevel = nits_to_u16_dark( 0 ), -+ }; -+ } -+ else if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) -+ { -+ // Set up some HDR fallbacks for undocking -+ return HDRInfo -+ { -+ .bExposeHDRSupport = false, -+ .eOutputEncodingEOTF = EOTF_Gamma22, -+ .uMaxContentLightLevel = nits_to_u16( 500.0f ), -+ .uMaxFrameAverageLuminance = nits_to_u16( 500.0f ), -+ .uMinContentLightLevel = nits_to_u16_dark( 0.5f ), -+ }; -+ } -+ -+ return std::nullopt; -+ } -+} -+ - static int - drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, bool needs_modeset ) - { -- drm_screen_type screenType = drm_get_screen_type(drm); -+ gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); - auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); - - // If we are modesetting, reset the state cache, we might -@@ -2510,8 +2535,6 @@ bool g_bForceAsyncFlips = false; - * 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 ) - { -- drm->pending.screen_type = drm_get_screen_type(drm); -- - drm_update_vrr_state(drm); - drm_update_color_mgmt(drm); - -@@ -2522,43 +2545,17 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - assert( drm->req == nullptr ); - drm->req = drmModeAtomicAlloc(); - -- if (drm->connector != nullptr) { -- bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; -- bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; -- -- if (drm->connector->has_colorspace) { -- drm->connector->pending.colorspace = ( bConnectorHDR ) ? DRM_MODE_COLORIMETRY_BT2020_RGB : DRM_MODE_COLORIMETRY_DEFAULT; -- } -- -- if (drm->connector->has_content_type) { -- drm->connector->pending.content_type = DRM_MODE_CONTENT_TYPE_GAME; -- } -- -- if ( bConnectorHDR ) -+ wlserver_hdr_metadata *pHDRMetadata = nullptr; -+ if ( drm->pConnector && drm->pConnector->GetHDRInfo().IsHDR10() ) -+ { -+ if ( g_bOutputHDREnabled ) - { -- if (drm->connector->has_hdr_output_metadata) { -- auto hdr_output_metadata = get_default_hdr_metadata( drm, drm->connector ); -- -- if ( drm->connector->metadata.hdr10_metadata_blob ) -- hdr_output_metadata = drm->connector->metadata.hdr10_metadata_blob; -- -- auto feedback = steamcompmgr_get_base_layer_swapchain_feedback(); -- if (feedback && feedback->hdr_metadata_blob) -- hdr_output_metadata = feedback->hdr_metadata_blob; -- -- drm->connector->pending.hdr_output_metadata = hdr_output_metadata; -- } -+ wlserver_vk_swapchain_feedback* pFeedback = steamcompmgr_get_base_layer_swapchain_feedback(); -+ pHDRMetadata = pFeedback ? pFeedback->hdr_metadata_blob.get() : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); - } - else - { -- if (drm->connector->has_hdr_output_metadata && bConnectorSupportsHDR) -- { -- drm->connector->pending.hdr_output_metadata = drm->sdr_static_metadata; -- } -- else -- { -- drm->connector->pending.hdr_output_metadata = nullptr; -- } -+ pHDRMetadata = drm->sdr_static_metadata.get(); - } - } - -@@ -2585,196 +2582,116 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; - - // We do internal refcounting with these events -- if ( drm->crtc != nullptr ) -+ if ( drm->pCRTC != nullptr ) - flags |= DRM_MODE_PAGE_FLIP_EVENT; - - if ( async || g_bForceAsyncFlips ) - flags |= DRM_MODE_PAGE_FLIP_ASYNC; - -- if ( needs_modeset ) { -+ bool bForceInRequest = needs_modeset; -+ -+ if ( needs_modeset ) -+ { - flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - - // Disable all connectors and CRTCs - -- for ( auto &kv : drm->connectors ) { -- struct connector *conn = &kv.second; -- -- if ( conn->current.crtc_id == 0 ) -+ for ( auto &iter : drm->connectors ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter.second; -+ if ( pConnector->GetProperties().CRTC_ID->GetCurrentValue() == 0 ) - continue; - -- conn->pending.crtc_id = 0; -- int ret = add_connector_property( drm->req, conn, "CRTC_ID", 0 ); -- if (ret < 0) -- return ret; -+ pConnector->GetProperties().CRTC_ID->SetPendingValue( drm->req, 0, bForceInRequest ); - -- if (conn->has_colorspace) { -- ret = add_connector_property( drm->req, conn, "Colorspace", 0 ); -- if (ret < 0) -- return ret; -- } -+ if ( pConnector->GetProperties().Colorspace ) -+ pConnector->GetProperties().Colorspace->SetPendingValue( drm->req, 0, bForceInRequest ); - -- if (conn->has_hdr_output_metadata) { -- ret = add_connector_property( drm->req, conn, "HDR_OUTPUT_METADATA", 0 ); -- if (ret < 0) -- return ret; -- } -+ if ( pConnector->GetProperties().HDR_OUTPUT_METADATA ) -+ pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, 0, bForceInRequest ); - -- if (conn->has_content_type) { -- ret = add_connector_property( drm->req, conn, "content type", 0 ); -- if (ret < 0) -- return ret; -- } -+ if ( pConnector->GetProperties().content_type ) -+ pConnector->GetProperties().content_type->SetPendingValue( drm->req, 0, bForceInRequest ); - } -- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) { -- struct crtc *crtc = &drm->crtcs[i]; - -+ 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 (crtc->current.active == 0) -+ if ( pCRTC->GetProperties().ACTIVE->GetCurrentValue() == 0 ) - continue; - -- int ret = add_crtc_property(drm->req, crtc, "MODE_ID", 0); -- if (ret < 0) -- return ret; -- if (crtc->has_gamma_lut) -- { -- int ret = add_crtc_property(drm->req, crtc, "GAMMA_LUT", 0); -- if (ret < 0) -- return ret; -- } -- if (crtc->has_degamma_lut) -- { -- int ret = add_crtc_property(drm->req, crtc, "DEGAMMA_LUT", 0); -- if (ret < 0) -- return ret; -- } -- if (crtc->has_ctm) -- { -- int ret = add_crtc_property(drm->req, crtc, "CTM", 0); -- if (ret < 0) -- return ret; -- } -- if (crtc->has_vrr_enabled) -- { -- int ret = add_crtc_property(drm->req, crtc, "VRR_ENABLED", 0); -- if (ret < 0) -- return ret; -- } -- if (crtc->has_valve1_regamma_tf) -- { -- int ret = add_crtc_property(drm->req, crtc, "VALVE1_CRTC_REGAMMA_TF", 0); -- if (ret < 0) -- return ret; -- } -+ pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 0, bForceInRequest ); -+ pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, 0, bForceInRequest ); - -- ret = add_crtc_property(drm->req, crtc, "ACTIVE", 0); -- if (ret < 0) -- return ret; -- crtc->pending.active = 0; -- } -+ if ( pCRTC->GetProperties().GAMMA_LUT ) -+ pCRTC->GetProperties().GAMMA_LUT->SetPendingValue( drm->req, 0, bForceInRequest ); - -- // Then enable the one we've picked -- int ret = 0; -- if (drm->connector != nullptr) { -- // Always set our CRTC_ID for the modeset, especially -- // as we zero-ed it above. -- drm->connector->pending.crtc_id = drm->crtc->id; -- ret = add_connector_property(drm->req, drm->connector, "CRTC_ID", drm->crtc->id); -- if (ret < 0) -- return ret; -- -- if (drm->connector->has_colorspace) { -- ret = add_connector_property(drm->req, drm->connector, "Colorspace", drm->connector->pending.colorspace); -- if (ret < 0) -- return ret; -- } -+ if ( pCRTC->GetProperties().DEGAMMA_LUT ) -+ pCRTC->GetProperties().DEGAMMA_LUT->SetPendingValue( drm->req, 0, bForceInRequest ); - -- if (drm->connector->has_hdr_output_metadata) { -- uint32_t value = drm->connector->pending.hdr_output_metadata ? drm->connector->pending.hdr_output_metadata->blob : 0; -- ret = add_connector_property(drm->req, drm->connector, "HDR_OUTPUT_METADATA", value); -- if (ret < 0) -- return ret; -- } -+ if ( pCRTC->GetProperties().CTM ) -+ pCRTC->GetProperties().CTM->SetPendingValue( drm->req, 0, bForceInRequest ); - -- if (drm->connector->has_content_type) { -- ret = add_connector_property(drm->req, drm->connector, "content type", drm->connector->pending.content_type); -- if (ret < 0) -- return ret; -- } -+ if ( pCRTC->GetProperties().VRR_ENABLED ) -+ pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, 0, bForceInRequest ); - -- ret = add_crtc_property(drm->req, drm->crtc, "MODE_ID", drm->pending.mode_id->blob); -- if (ret < 0) -- return ret; -+ if ( pCRTC->GetProperties().OUT_FENCE_PTR ) -+ pCRTC->GetProperties().OUT_FENCE_PTR->SetPendingValue( drm->req, 0, bForceInRequest ); - -- if (drm->crtc->has_vrr_enabled) -- { -- ret = add_crtc_property(drm->req, drm->crtc, "VRR_ENABLED", drm->pending.vrr_enabled); -- if (ret < 0) -- return ret; -- } -+ if ( pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) -+ pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->SetPendingValue( drm->req, 0, bForceInRequest ); -+ } -+ -+ if ( drm->pConnector ) -+ { -+ // 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->crtc->has_valve1_regamma_tf) -+ if ( drm->pConnector->GetProperties().Colorspace ) - { -- ret = add_crtc_property(drm->req, drm->crtc, "VALVE1_CRTC_REGAMMA_TF", drm->pending.output_tf); -- if (ret < 0) -- return ret; -+ uint32_t uColorimetry = g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() -+ ? DRM_MODE_COLORIMETRY_BT2020_RGB -+ : DRM_MODE_COLORIMETRY_DEFAULT; -+ drm->pConnector->GetProperties().Colorspace->SetPendingValue( drm->req, uColorimetry, bForceInRequest ); - } -- -- ret = add_crtc_property(drm->req, drm->crtc, "ACTIVE", 1); -- if (ret < 0) -- return ret; -- drm->crtc->pending.active = 1; - } -- } -- else -- { -- if (drm->connector != nullptr) { -- if (drm->connector->has_colorspace && drm->connector->pending.colorspace != drm->connector->current.colorspace) { -- int ret = add_connector_property(drm->req, drm->connector, "Colorspace", drm->connector->pending.colorspace); -- if (ret < 0) -- return ret; -- } - -- if (drm->connector->has_hdr_output_metadata && drm->connector->pending.hdr_output_metadata != drm->connector->current.hdr_output_metadata) { -- uint32_t value = drm->connector->pending.hdr_output_metadata ? drm->connector->pending.hdr_output_metadata->blob : 0; -- int ret = add_connector_property(drm->req, drm->connector, "HDR_OUTPUT_METADATA", value); -- if (ret < 0) -- return ret; -- } -+ if ( drm->pCRTC ) -+ { -+ 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->blob : 0lu, true ); - -- if (drm->connector->has_content_type && drm->connector->pending.content_type != drm->connector->current.content_type) { -- int ret = add_connector_property(drm->req, drm->connector, "content type", drm->connector->pending.content_type); -- if (ret < 0) -- return ret; -- } -+ if ( drm->pCRTC->GetProperties().VRR_ENABLED ) -+ drm->pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, drm->pending.vrr_enabled, true ); - } -+ } - -- if (drm->crtc != nullptr) { -- if ( drm->crtc->has_vrr_enabled && drm->pending.vrr_enabled != drm->current.vrr_enabled ) -- { -- int ret = add_crtc_property(drm->req, drm->crtc, "VRR_ENABLED", drm->pending.vrr_enabled ); -- if (ret < 0) -- return ret; -- } -+ if ( drm->pConnector ) -+ { -+ if ( drm->pConnector->GetProperties().HDR_OUTPUT_METADATA ) -+ drm->pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, pHDRMetadata ? pHDRMetadata->blob : 0lu, bForceInRequest ); - -- if ( drm->crtc->has_valve1_regamma_tf && drm->pending.output_tf != drm->current.output_tf ) -- { -- int ret = add_crtc_property(drm->req, drm->crtc, "VALVE1_CRTC_REGAMMA_TF", drm->pending.output_tf ); -- if (ret < 0) -- return ret; -- } -- } -+ if ( drm->pConnector->GetProperties().content_type ) -+ drm->pConnector->GetProperties().content_type->SetPendingValue( drm->req, DRM_MODE_CONTENT_TYPE_GAME, bForceInRequest ); -+ } -+ -+ if ( drm->pCRTC ) -+ { -+ if ( drm->pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) -+ drm->pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->SetPendingValue( drm->req, drm->pending.output_tf, bForceInRequest ); - } - - drm->flags = flags; - - int ret; -- if ( drm->crtc == nullptr ) { -+ if ( drm->pCRTC == nullptr ) { - ret = 0; -- } else if ( g_bUseLayers == true ) { -+ } else if ( drm->bUseLiftoff ) { - ret = drm_prepare_liftoff( drm, frameInfo, needs_modeset ); - } else { -- ret = drm_prepare_basic( drm, frameInfo ); -+ ret = 0; - } - - if ( ret != 0 ) { -@@ -2790,21 +2707,6 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - return ret; - } - --void drm_rollback( struct drm_t *drm ) --{ -- drm->pending = drm->current; -- -- for ( size_t i = 0; i < drm->crtcs.size(); i++ ) -- { -- drm->crtcs[i].pending = drm->crtcs[i].current; -- } -- -- for (auto &kv : drm->connectors) { -- struct connector *conn = &kv.second; -- conn->pending = conn->current; -- } --} -- - bool drm_poll_state( struct drm_t *drm ) - { - int out_of_date = drm->out_of_date.exchange(false); -@@ -2818,25 +2720,18 @@ bool drm_poll_state( struct drm_t *drm ) - return true; - } - --static bool drm_set_crtc( struct drm_t *drm, struct crtc *crtc ) -+static bool drm_set_crtc( struct drm_t *drm, gamescope::CDRMCRTC *pCRTC ) - { -- drm->crtc = crtc; -+ drm->pCRTC = pCRTC; - drm->needs_modeset = true; - -- for (size_t i = 0; i < drm->crtcs.size(); i++) { -- if (drm->crtcs[i].id == drm->crtc->id) { -- drm->crtc_index = i; -- break; -- } -- } -- -- drm->primary = find_primary_plane( drm ); -- if ( drm->primary == nullptr ) { -+ 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, crtc->id ); -+ struct liftoff_output *lo_output = liftoff_output_create( drm->lo_device, pCRTC->GetObjectId() ); - if ( lo_output == nullptr ) - return false; - -@@ -2854,21 +2749,22 @@ static bool drm_set_crtc( struct drm_t *drm, struct crtc *crtc ) - return true; - } - --bool drm_set_connector( struct drm_t *drm, struct connector *conn ) -+bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ) - { -- drm_log.infof("selecting connector %s", conn->name); -+ drm_log.infof("selecting connector %s", conn->GetName()); - -- struct crtc *crtc = find_crtc_for_connector(drm, conn); -- if (crtc == nullptr) { -+ 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, crtc)) { -+ if (!drm_set_crtc(drm, pCRTC)) { - return false; - } - -- drm->connector = conn; -+ drm->pConnector = conn; - drm->needs_modeset = true; - - return true; -@@ -2876,8 +2772,8 @@ bool drm_set_connector( struct drm_t *drm, struct connector *conn ) - - static void drm_unset_connector( struct drm_t *drm ) - { -- drm->crtc = nullptr; -- drm->primary = nullptr; -+ drm->pCRTC = nullptr; -+ drm->pPrimaryPlane = nullptr; - - for ( int i = 0; i < k_nMaxLayers; i++ ) - { -@@ -2888,7 +2784,7 @@ static void drm_unset_connector( struct drm_t *drm ) - liftoff_output_destroy(drm->lo_output); - drm->lo_output = nullptr; - -- drm->connector = nullptr; -+ drm->pConnector = nullptr; - drm->needs_modeset = true; - } - -@@ -2902,22 +2798,12 @@ bool drm_get_vrr_in_use(struct drm_t *drm) - return drm->current.vrr_enabled; - } - --drm_screen_type drm_get_connector_type(drmModeConnector *connector) -+gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm) - { -- if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || -- connector->connector_type == DRM_MODE_CONNECTOR_LVDS || -- connector->connector_type == DRM_MODE_CONNECTOR_DSI) -- return DRM_SCREEN_TYPE_INTERNAL; -+ if ( !drm->pConnector ) -+ return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; - -- return DRM_SCREEN_TYPE_EXTERNAL; --} -- --drm_screen_type drm_get_screen_type(struct drm_t *drm) --{ -- if (!drm->connector || !drm->connector->connector) -- return DRM_SCREEN_TYPE_INTERNAL; -- -- return drm_get_connector_type(drm->connector->connector); -+ return drm->pConnector->GetScreenType(); - } - - bool drm_update_color_mgmt(struct drm_t *drm) -@@ -2963,9 +2849,9 @@ bool drm_update_vrr_state(struct drm_t *drm) - { - drm->pending.vrr_enabled = false; - -- if ( drm->connector && drm->crtc && drm->crtc->has_vrr_enabled ) -+ if ( drm->pConnector && drm->pCRTC && drm->pCRTC->GetProperties().VRR_ENABLED ) - { -- if ( drm->wants_vrr_enabled && drm->connector->vrr_capable ) -+ if ( drm->wants_vrr_enabled && drm->pConnector->IsVRRCapable() ) - drm->pending.vrr_enabled = true; - } - -@@ -2991,21 +2877,21 @@ static void drm_unset_mode( struct drm_t *drm ) - if (g_nOutputRefresh == 0) - g_nOutputRefresh = 60; - -- g_drmEffectiveOrientation[DRM_SCREEN_TYPE_INTERNAL] = DRM_MODE_ROTATE_0; -- g_drmEffectiveOrientation[DRM_SCREEN_TYPE_EXTERNAL] = DRM_MODE_ROTATE_0; -+ g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = DRM_MODE_ROTATE_0; -+ g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = DRM_MODE_ROTATE_0; - g_bRotated = false; - } - - bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) - { -- if (!drm->connector || !drm->connector->connector) -+ if (!drm->pConnector || !drm->pConnector->GetModeConnector()) - return false; - - uint32_t mode_id = 0; - if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode), &mode_id) != 0) - return false; - -- drm_screen_type screenType = drm_get_screen_type(drm); -+ gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); - - drm_log.infof("selecting mode %dx%d@%uHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); - -@@ -3014,7 +2900,7 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) - - g_nOutputRefresh = mode->vrefresh; - -- update_drm_effective_orientations(drm, drm->connector, mode); -+ update_drm_effective_orientations(drm, mode); - - switch ( g_drmEffectiveOrientation[screenType] ) - { -@@ -3045,10 +2931,10 @@ bool drm_set_refresh( struct drm_t *drm, int refresh ) - width = height; - height = tmp; - } -- if (!drm->connector || !drm->connector->connector) -+ if (!drm->pConnector || !drm->pConnector->GetModeConnector()) - return false; - -- drmModeConnector *connector = drm->connector->connector; -+ drmModeConnector *connector = drm->pConnector->GetModeConnector(); - const drmModeModeInfo *existing_mode = find_mode(connector, width, height, refresh); - drmModeModeInfo mode = {0}; - if ( existing_mode ) -@@ -3058,15 +2944,15 @@ bool drm_set_refresh( struct drm_t *drm, int refresh ) - else - { - /* TODO: check refresh is within the EDID limits */ -- switch ( g_drmModeGeneration ) -+ switch ( g_eGamescopeModeGeneration ) - { -- case DRM_MODE_GENERATE_CVT: -+ case gamescope::GAMESCOPE_MODE_GENERATE_CVT: - generate_cvt_mode( &mode, width, height, refresh, true, false ); - break; -- case DRM_MODE_GENERATE_FIXED: -+ case gamescope::GAMESCOPE_MODE_GENERATE_FIXED: - { - const drmModeModeInfo *preferred_mode = find_mode(connector, 0, 0, 0); -- generate_fixed_mode( &mode, preferred_mode, refresh, drm->connector->is_steam_deck_display, drm->connector->is_galileo_display ); -+ generate_fixed_mode( &mode, preferred_mode, refresh, drm->pConnector->GetKnownDisplayType() ); - break; - } - } -@@ -3079,10 +2965,10 @@ bool drm_set_refresh( struct drm_t *drm, int refresh ) - - bool drm_set_resolution( struct drm_t *drm, int width, int height ) - { -- if (!drm->connector || !drm->connector->connector) -+ if (!drm->pConnector || !drm->pConnector->GetModeConnector()) - return false; - -- drmModeConnector *connector = drm->connector->connector; -+ drmModeConnector *connector = drm->pConnector->GetModeConnector(); - const drmModeModeInfo *mode = find_mode(connector, width, height, 0); - if ( !mode ) - { -@@ -3097,10 +2983,10 @@ int drm_get_default_refresh(struct drm_t *drm) - if ( drm->preferred_refresh ) - return drm->preferred_refresh; - -- if ( drm->connector && drm->connector->target_refresh ) -- return drm->connector->target_refresh; -+ if ( drm->pConnector && drm->pConnector->GetBaseRefresh() ) -+ return drm->pConnector->GetBaseRefresh(); - -- if ( drm->connector && drm->connector->connector ) -+ if ( drm->pConnector && drm->pConnector->GetModeConnector() ) - { - int width = g_nOutputWidth; - int height = g_nOutputHeight; -@@ -3110,7 +2996,7 @@ int drm_get_default_refresh(struct drm_t *drm) - height = tmp; - } - -- drmModeConnector *connector = drm->connector->connector; -+ drmModeConnector *connector = drm->pConnector->GetModeConnector(); - const drmModeModeInfo *mode = find_mode( connector, width, height, 0); - if ( mode ) - return mode->vrefresh; -@@ -3121,20 +3007,20 @@ int drm_get_default_refresh(struct drm_t *drm) - - bool drm_get_vrr_capable(struct drm_t *drm) - { -- if ( drm->connector ) -- return drm->connector->vrr_capable; -+ if ( drm->pConnector ) -+ return drm->pConnector->IsVRRCapable(); - - return false; - } - --bool drm_supports_st2084(struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL) -+bool drm_supports_hdr( struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL ) - { -- if ( drm->connector && drm->connector->metadata.supportsST2084 ) -+ if ( drm->pConnector && drm->pConnector->GetHDRInfo().SupportsHDR() ) - { - if ( maxCLL ) -- *maxCLL = drm->connector->metadata.maxCLL; -+ *maxCLL = drm->pConnector->GetHDRInfo().uMaxContentLightLevel; - if ( maxFALL ) -- *maxFALL = drm->connector->metadata.maxFALL; -+ *maxFALL = drm->pConnector->GetHDRInfo().uMaxFrameAverageLuminance; - return true; - } - -@@ -3150,10 +3036,10 @@ void drm_set_hdr_state(struct drm_t *drm, bool enabled) { - - const char *drm_get_connector_name(struct drm_t *drm) - { -- if ( !drm->connector ) -+ if ( !drm->pConnector ) - return nullptr; - -- return drm->connector->name; -+ return drm->pConnector->GetName(); - } - - const char *drm_get_device_name(struct drm_t *drm) -@@ -3163,10 +3049,10 @@ const char *drm_get_device_name(struct drm_t *drm) - - std::pair drm_get_connector_identifier(struct drm_t *drm) - { -- if ( !drm->connector ) -+ if ( !drm->pConnector ) - return { 0u, 0u }; - -- return std::make_pair(drm->connector->connector->connector_type, drm->connector->connector->connector_type_id); -+ return std::make_pair(drm->pConnector->GetModeConnector()->connector_type, drm->pConnector->GetModeConnector()->connector_type_id); - } - - std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata) -@@ -3222,20 +3108,20 @@ std::shared_ptr drm_create_ctm(struct drm_t *drm, glm::mat3x4 ctm) - - bool drm_supports_color_mgmt(struct drm_t *drm) - { -- if (g_bForceDisableColorMgmt) -+ if ( g_bForceDisableColorMgmt ) - return false; - -- if (!drm->primary) -+ if ( !drm->pPrimaryPlane ) - return false; - -- return drm->primary->has_color_mgmt; -+ return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); - } - - void drm_get_native_colorimetry( struct drm_t *drm, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) - { -- if ( !drm || !drm->connector ) -+ if ( !drm || !drm->pConnector ) - { - *displayColorimetry = displaycolorimetry_709; - *displayEOTF = EOTF_Gamma22; -@@ -3244,33 +3130,27 @@ void drm_get_native_colorimetry( struct drm_t *drm, - return; - } - -- *displayColorimetry = drm->connector->metadata.colorimetry; -- *displayEOTF = drm->connector->metadata.eotf; -+ *displayColorimetry = drm->pConnector->GetDisplayColorimetry(); -+ *displayEOTF = EOTF_Gamma22; - -- // For HDR output, expected content colorspace != native colorspace. -- if (drm->connector->metadata.supportsST2084 && g_bOutputHDREnabled) -+ // For HDR10 output, expected content colorspace != native colorspace. -+ if ( g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() ) - { - *outputEncodingColorimetry = displaycolorimetry_2020; -- *outputEncodingEOTF = EOTF_PQ; -+ *outputEncodingEOTF = drm->pConnector->GetHDRInfo().eOutputEncodingEOTF; - } - else - { -- *outputEncodingColorimetry = drm->connector->metadata.colorimetry; -- *outputEncodingEOTF = drm->connector->metadata.eotf; -- } -- -- if (!g_bOutputHDREnabled) -- { -- *displayEOTF = EOTF_Gamma22; -+ *outputEncodingColorimetry = drm->pConnector->GetDisplayColorimetry(); - *outputEncodingEOTF = EOTF_Gamma22; - } - } - - --std::span drm_get_valid_refresh_rates( struct drm_t *drm ) -+std::span drm_get_valid_refresh_rates( struct drm_t *drm ) - { -- if (drm && drm->connector) -- return drm->connector->valid_display_rates; -+ if ( drm && drm->pConnector ) -+ return drm->pConnector->GetValidDynamicRefreshRates(); - -- return std::span{}; -+ return std::span{}; - } -diff --git a/src/drm.hpp b/src/drm.hpp -index 409c87c92..1ab4fc387 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -11,6 +11,7 @@ - #include - - #include "color_helpers.h" -+#include "gamescope_shared.h" - - // Josh: Okay whatever, this header isn't - // available for whatever stupid reason. :v -@@ -28,14 +29,6 @@ enum drm_color_range { - DRM_COLOR_RANGE_MAX, - }; - --enum drm_screen_type { -- DRM_SCREEN_TYPE_INTERNAL = 0, -- DRM_SCREEN_TYPE_EXTERNAL = 1, -- -- DRM_SCREEN_TYPE_COUNT --}; -- -- - enum GamescopeAppTextureColorspace { - GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR = 0, - GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB, -@@ -137,79 +130,275 @@ extern "C" { - #include - #include - --struct saved_mode { -- int width; -- int height; -- int refresh; --}; -- --struct plane { -- uint32_t id; -- drmModePlane *plane; -- std::map props; -- std::map initial_prop_values; -- bool has_color_mgmt; --}; -- --struct crtc { -- uint32_t id; -- drmModeCrtc *crtc; -- std::map props; -- std::map initial_prop_values; -- bool has_gamma_lut; -- bool has_degamma_lut; -- bool has_ctm; -- bool has_vrr_enabled; -- bool has_valve1_regamma_tf; -- -- struct { -- bool active; -- } current, pending; --}; -+namespace gamescope -+{ -+ template -+ using CAutoDeletePtr = std::unique_ptr; - --struct connector_metadata_t { -- struct hdr_output_metadata defaultHdrMetadata = {}; -- std::shared_ptr hdr10_metadata_blob; -- bool supportsST2084 = false; -+ //////////////////////////////////////// -+ // DRM Object Wrappers + State Trackers -+ //////////////////////////////////////// -+ struct DRMObjectRawProperty -+ { -+ uint32_t uPropertyId = 0ul; -+ uint64_t ulValue = 0ul; -+ }; -+ using DRMObjectRawProperties = std::unordered_map; - -- displaycolorimetry_t colorimetry = displaycolorimetry_709; -- EOTF eotf = EOTF_Gamma22; -- uint16_t maxCLL = 0; -- uint16_t maxFALL = 0; --}; -+ 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(); -+ }; - --struct connector { -- uint32_t id; -- char *name; -- drmModeConnector *connector; -- uint32_t possible_crtcs; -- std::map props; -- std::map initial_prop_values; -+ class CDRMAtomicProperty -+ { -+ public: -+ CDRMAtomicProperty( CDRMAtomicObject *pObject, DRMObjectRawProperty rawProperty ); - -- char make_pnp[4]; -- char *make; -- char *model; -- bool is_steam_deck_display; -- std::span valid_display_rates{}; -- uint16_t is_galileo_display; -+ static std::optional Instantiate( const char *pszName, CDRMAtomicObject *pObject, const DRMObjectRawProperties& rawProperties ); - -- int target_refresh; -- bool vrr_capable; -+ uint64_t GetPendingValue() const { return m_ulPendingValue; } -+ uint64_t GetCurrentValue() const { return m_ulCurrentValue; } -+ int SetPendingValue( drmModeAtomicReq *pRequest, uint64_t ulValue, bool bForce ); - -- connector_metadata_t metadata; -+ void OnCommit(); -+ void Rollback(); -+ private: -+ CDRMAtomicObject *m_pObject = nullptr; -+ uint32_t m_uPropertyId = 0u; - -- struct { -- uint32_t crtc_id; -- uint32_t colorspace; -- uint32_t content_type; -- std::shared_ptr hdr_output_metadata; -- } current, pending; -+ uint64_t m_ulPendingValue = 0ul; -+ uint64_t m_ulCurrentValue = 0ul; -+ uint64_t m_ulInitialValue = 0ul; -+ }; - -- std::vector edid_data; -+ 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 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 VALVE1_PLANE_DEGAMMA_TF; -+ std::optional VALVE1_PLANE_DEGAMMA_LUT; -+ std::optional VALVE1_PLANE_CTM; -+ std::optional VALVE1_PLANE_HDR_MULT; -+ std::optional VALVE1_PLANE_SHAPER_LUT; -+ std::optional VALVE1_PLANE_SHAPER_TF; -+ std::optional VALVE1_PLANE_LUT3D; -+ std::optional VALVE1_PLANE_BLEND_TF; -+ std::optional VALVE1_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 VALVE1_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 CDRMAtomicTypedObject -+ { -+ public: -+ CDRMConnector( 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 DUMMY_END; -+ }; -+ ConnectorProperties &GetProperties() { return m_Props; } -+ const ConnectorProperties &GetProperties() const { return m_Props; } -+ -+ struct HDRInfo -+ { -+ // 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. -+ bool bExposeHDRSupport = 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. -+ EOTF eOutputEncodingEOTF = EOTF_Gamma22; -+ -+ uint16_t uMaxContentLightLevel = 500; // Nits -+ uint16_t uMaxFrameAverageLuminance = 500; // Nits -+ uint16_t uMinContentLightLevel = 0; // Nits / 10000 -+ std::shared_ptr pDefaultMetadataBlob; -+ -+ bool ShouldPatchEDID() const -+ { -+ return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_Gamma22; -+ } -+ -+ bool SupportsHDR() const -+ { -+ // Note: Different to IsHDR10, as we can expose HDR10 on G2.2 displays -+ // using LUTs and CTMs. -+ return bExposeHDRSupport; -+ } -+ -+ bool IsHDR10() const -+ { -+ // PQ output encoding is always HDR10 (PQ + 2020) for us. -+ // If that assumption changes, update me. -+ return eOutputEncodingEOTF == EOTF_PQ; -+ } -+ }; -+ -+ drmModeConnector *GetModeConnector() { return m_pConnector.get(); } -+ const char *GetName() const { return m_Mutable.szName; } -+ const char *GetMake() const { return m_Mutable.pszMake; } -+ const char *GetModel() const { return m_Mutable.szModel; } -+ uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } -+ const HDRInfo &GetHDRInfo() const { return m_Mutable.HDR; } -+ std::span GetValidDynamicRefreshRates() const { return m_Mutable.ValidDynamicRefreshRates; } -+ GamescopeKnownDisplays GetKnownDisplayType() const { return m_Mutable.eKnownDisplay; } -+ GamescopeScreenType GetScreenType() const -+ { -+ 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; -+ } -+ bool IsVRRCapable() const -+ { -+ return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); -+ } -+ const displaycolorimetry_t& GetDisplayColorimetry() const { return m_Mutable.DisplayColorimetry; } -+ -+ const std::vector &GetRawEDID() const { return m_Mutable.EdidData; } -+ -+ // TODO: Remove -+ void SetBaseRefresh( int nRefresh ) { m_nBaseRefresh = nRefresh; } -+ int GetBaseRefresh() const { return m_nBaseRefresh; } -+ private: -+ void ParseEDID(); -+ -+ static std::optional GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ); -+ -+ CAutoDeletePtr m_pConnector; -+ -+ struct MutableConnectorState -+ { -+ int nDefaultRefresh = 0; -+ -+ uint32_t uPossibleCRTCMask = 0u; -+ char szName[32]{}; -+ char szMakePNP[4]{}; -+ char szModel[16]{}; -+ const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. -+ GamescopeKnownDisplays eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_UNKNOWN; -+ std::span ValidDynamicRefreshRates{}; -+ std::vector EdidData; // Raw, unmodified. -+ -+ displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; -+ HDRInfo HDR; -+ } m_Mutable; -+ -+ // TODO: Remove -+ int m_nBaseRefresh = 0; -+ -+ ConnectorProperties m_Props; -+ }; -+} - -- bool has_colorspace; -- bool has_content_type; -- bool has_hdr_output_metadata; -+struct saved_mode { -+ int width; -+ int height; -+ int refresh; - }; - - struct fb { -@@ -240,6 +429,8 @@ enum drm_valve1_transfer_function { - }; - - struct drm_t { -+ bool bUseLiftoff; -+ - int fd; - - int preferred_width, preferred_height, preferred_refresh; -@@ -248,16 +439,15 @@ struct drm_t { - bool allow_modifiers; - struct wlr_drm_format_set formats; - -- std::vector< struct plane > planes; -- std::vector< struct crtc > crtcs; -- std::unordered_map< uint32_t, struct connector > connectors; -+ 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::map< uint32_t, drmModePropertyRes * > props; - -- struct plane *primary; -- struct crtc *crtc; -- struct connector *connector; -- int crtc_index; -+ gamescope::CDRMPlane *pPrimaryPlane; -+ gamescope::CDRMCRTC *pCRTC; -+ gamescope::CDRMConnector *pConnector; - int kms_in_fence_fd; - int kms_out_fence_fd; - -@@ -277,7 +467,7 @@ struct drm_t { - uint32_t color_mgmt_serial; - std::shared_ptr lut3d_id[ EOTF_Count ]; - std::shared_ptr shaperlut_id[ EOTF_Count ]; -- enum drm_screen_type screen_type = DRM_SCREEN_TYPE_INTERNAL; -+ // TODO: Remove me, this should be some generic setting. - bool vrr_enabled = false; - drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; - } current, pending; -@@ -318,17 +508,11 @@ extern struct drm_t g_DRM; - extern uint32_t g_nDRMFormat; - extern uint32_t g_nDRMFormatOverlay; - --extern bool g_bUseLayers; - extern bool g_bRotated; - extern bool g_bFlipped; - extern bool g_bDebugLayers; - extern const char *g_sOutputName; - --enum drm_mode_generation { -- DRM_MODE_GENERATE_CVT, -- DRM_MODE_GENERATE_FIXED, --}; -- - enum g_panel_orientation { - PANEL_ORIENTATION_0, /* NORMAL */ - PANEL_ORIENTATION_270, /* RIGHT */ -@@ -337,10 +521,18 @@ enum g_panel_orientation { - PANEL_ORIENTATION_AUTO, - }; - --extern enum drm_mode_generation g_drmModeGeneration; -+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, -+}; -+ -+extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; - extern enum g_panel_orientation g_drmModeOrientation; - --extern std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* -+extern std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* - - extern bool g_bForceDisableColorMgmt; - -@@ -348,24 +540,23 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_ - void finish_drm(struct drm_t *drm); - int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); - int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ); --void drm_rollback( struct drm_t *drm ); - bool drm_poll_state(struct drm_t *drm); - uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ); - void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); - void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); - void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); --bool drm_set_connector( struct drm_t *drm, struct connector *conn ); -+bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ); - bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); - bool drm_set_refresh( struct drm_t *drm, int refresh ); - bool drm_set_resolution( struct drm_t *drm, int width, int height ); - bool drm_update_color_mgmt(struct drm_t *drm); - bool drm_update_vrr_state(struct drm_t *drm); --drm_screen_type drm_get_screen_type(struct drm_t *drm); -+gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm); - - char *find_drm_node_by_devid(dev_t devid); - int drm_get_default_refresh(struct drm_t *drm); - bool drm_get_vrr_capable(struct drm_t *drm); --bool drm_supports_st2084(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); -+bool drm_supports_hdr(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); - void drm_set_vrr_enabled(struct drm_t *drm, bool enabled); - bool drm_get_vrr_in_use(struct drm_t *drm); - bool drm_supports_color_mgmt(struct drm_t *drm); -@@ -383,7 +574,7 @@ void drm_get_native_colorimetry( struct drm_t *drm, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, - displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ); - --std::span drm_get_valid_refresh_rates( struct drm_t *drm ); -+std::span drm_get_valid_refresh_rates( struct drm_t *drm ); - - extern bool g_bSupportsAsyncFlips; - -diff --git a/src/gamescope_shared.h b/src/gamescope_shared.h -new file mode 100644 -index 000000000..523f58ed9 ---- /dev/null -+++ b/src/gamescope_shared.h -@@ -0,0 +1,27 @@ -+#pragma once -+ -+namespace gamescope -+{ -+ enum GamescopeKnownDisplays -+ { -+ GAMESCOPE_KNOWN_DISPLAY_UNKNOWN, -+ GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD, // Jupiter -+ GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC, // Galileo SDC -+ GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE, // Galileo BOE -+ }; -+ -+ enum GamescopeModeGeneration -+ { -+ GAMESCOPE_MODE_GENERATE_CVT, -+ GAMESCOPE_MODE_GENERATE_FIXED, -+ }; -+ -+ enum GamescopeScreenType -+ { -+ GAMESCOPE_SCREEN_TYPE_INTERNAL, -+ GAMESCOPE_SCREEN_TYPE_EXTERNAL, -+ -+ GAMESCOPE_SCREEN_TYPE_COUNT -+ }; -+} -+ -diff --git a/src/main.cpp b/src/main.cpp -index 5be060bf0..5bbb01bde 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -341,13 +341,18 @@ static std::string build_optstring(const struct option *options) - return optstring; - } - --static enum drm_mode_generation parse_drm_mode_generation(const char *str) -+static gamescope::GamescopeModeGeneration parse_gamescope_mode_generation( const char *str ) - { -- if (strcmp(str, "cvt") == 0) { -- return DRM_MODE_GENERATE_CVT; -- } else if (strcmp(str, "fixed") == 0) { -- return DRM_MODE_GENERATE_FIXED; -- } else { -+ 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); - } -@@ -594,8 +599,6 @@ int main(int argc, char **argv) - if (strcmp(opt_name, "help") == 0) { - fprintf(stderr, "%s", usage); - return 0; -- } else if (strcmp(opt_name, "disable-layers") == 0) { -- g_bUseLayers = false; - } else if (strcmp(opt_name, "debug-layers") == 0) { - g_bDebugLayers = true; - } else if (strcmp(opt_name, "disable-color-management") == 0) { -@@ -611,7 +614,7 @@ int main(int argc, char **argv) - g_nDefaultTouchClickMode = (enum wlserver_touch_click_mode) atoi( optarg ); - g_nTouchClickMode = g_nDefaultTouchClickMode; - } else if (strcmp(opt_name, "generate-drm-mode") == 0) { -- g_drmModeGeneration = parse_drm_mode_generation( optarg ); -+ g_eGamescopeModeGeneration = parse_gamescope_mode_generation( optarg ); - } else if (strcmp(opt_name, "force-orientation") == 0) { - g_drmModeOrientation = force_orientation( optarg ); - } else if (strcmp(opt_name, "sharpness") == 0 || -diff --git a/src/modegen.cpp b/src/modegen.cpp -index 197641c21..d174c2d21 100644 ---- a/src/modegen.cpp -+++ b/src/modegen.cpp -@@ -312,15 +312,16 @@ unsigned int get_galileo_vfp( int vrefresh, unsigned int * vfp_array, unsigned i - return 0; - } - --void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int vrefresh, -- bool use_tuned_clocks, unsigned int use_vfp ) -+void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int vrefresh, gamescope::GamescopeKnownDisplays eKnownDisplay ) - { - *mode = *base; - if (!vrefresh) - vrefresh = 60; -- if ( use_vfp ) { -- unsigned int vfp, vsync, vbp = 0; -- if (GALILEO_SDC_PID == use_vfp) { -+ if ( eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC || -+ eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE ) { -+ unsigned int vfp = 0, vsync = 0, vbp = 0; -+ if ( eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC ) -+ { - vfp = get_galileo_vfp( vrefresh, galileo_sdc_vfp, ARRAY_SIZE(galileo_sdc_vfp) ); - // if we did not find a matching rate then we default to 60 Hz - if ( !vfp ) { -@@ -329,7 +330,7 @@ void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int - } - vsync = GALILEO_SDC_VSYNC; - vbp = GALILEO_SDC_VBP; -- } else { // BOE Panel -+ } else if ( eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE ) { // BOE Panel - vfp = get_galileo_vfp( vrefresh, galileo_boe_vfp, ARRAY_SIZE(galileo_boe_vfp) ); - // if we did not find a matching rate then we default to 60 Hz - if ( !vfp ) { -@@ -338,12 +339,12 @@ void generate_fixed_mode(drmModeModeInfo *mode, const drmModeModeInfo *base, int - } - vsync = GALILEO_BOE_VSYNC; - vbp = GALILEO_BOE_VBP; -- } -+ } - mode->vsync_start = mode->vdisplay + vfp; - mode->vsync_end = mode->vsync_start + vsync; - mode->vtotal = mode->vsync_end + vbp; - } else { -- if ( use_tuned_clocks ) -+ if ( eKnownDisplay == gamescope::GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) - { - mode->hdisplay = 800; - mode->hsync_start = 840; -diff --git a/src/modegen.hpp b/src/modegen.hpp -index 2513d34e9..010d9cab3 100644 ---- a/src/modegen.hpp -+++ b/src/modegen.hpp -@@ -5,8 +5,9 @@ - #include - #include - #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, bool use_tuned_clocks, unsigned int use_vfp); -+ int vrefresh, gamescope::GamescopeKnownDisplays eKnownDisplay); -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index c24ba58ba..f63222f9d 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -328,7 +328,7 @@ int g_nAsyncFlipsEnabled = 0; - int g_nSteamMaxHeight = 0; - bool g_bVRRCapable_CachedValue = false; - bool g_bVRRInUse_CachedValue = false; --bool g_bSupportsST2084_CachedValue = false; -+bool g_bSupportsHDR_CachedValue = false; - bool g_bForceHDR10OutputDebug = false; - bool g_bHDREnabled = false; - bool g_bHDRItmEnable = false; -@@ -505,8 +505,8 @@ bool set_color_mgmt_enabled( bool bEnabled ) - return true; - } - --static std::shared_ptr s_MuraCorrectionImage[DRM_SCREEN_TYPE_COUNT]; --static std::shared_ptr s_MuraCTMBlob[DRM_SCREEN_TYPE_COUNT]; -+static std::shared_ptr 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; - -@@ -517,8 +517,8 @@ bool is_mura_correction_enabled() - - void update_mura_ctm() - { -- s_MuraCTMBlob[DRM_SCREEN_TYPE_INTERNAL] = nullptr; -- if (s_MuraCorrectionImage[DRM_SCREEN_TYPE_INTERNAL] == nullptr) -+ s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = nullptr; -+ if (s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] == nullptr) - return; - - static constexpr float kMuraMapScale = 0.0625f; -@@ -533,7 +533,7 @@ void update_mura_ctm() - 0, flScale, 0, kMuraOffset * flScale, - 0, 0, 0, 0, // No mura comp for blue channel. - }; -- s_MuraCTMBlob[DRM_SCREEN_TYPE_INTERNAL] = drm_create_ctm(&g_DRM, mura_scale_offset); -+ s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = drm_create_ctm(&g_DRM, mura_scale_offset); - } - - bool g_bMuraDebugFullColor = false; -@@ -541,7 +541,7 @@ 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[DRM_SCREEN_TYPE_INTERNAL] = nullptr; -+ s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = nullptr; - update_mura_ctm(); - - std::string red_path = std::string(path) + "_red.png"; -@@ -577,7 +577,7 @@ bool set_mura_overlay( const char *path ) - CVulkanTexture::createFlags texCreateFlags; - texCreateFlags.bFlippable = !BIsNested(); - texCreateFlags.bSampled = true; -- s_MuraCorrectionImage[DRM_SCREEN_TYPE_INTERNAL] = vulkan_create_texture_from_bits(w, h, w, h, DRM_FORMAT_ABGR8888, texCreateFlags, (void*)data); -+ 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!"); -@@ -912,26 +912,26 @@ bool g_bCurrentBasePlaneIsFifo = false; - - static int g_nSteamCompMgrTargetFPS = 0; - static uint64_t g_uDynamicRefreshEqualityTime = 0; --static int g_nDynamicRefreshRate[DRM_SCREEN_TYPE_COUNT] = { 0, 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[DRM_SCREEN_TYPE_COUNT] = { 0, 0 }; -+static int g_nCombinedAppRefreshCycleOverride[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT] = { 0, 0 }; - - static void _update_app_target_refresh_cycle() - { - if ( BIsNested() ) - { -- g_nDynamicRefreshRate[ DRM_SCREEN_TYPE_INTERNAL ] = 0; -- g_nSteamCompMgrTargetFPS = g_nCombinedAppRefreshCycleOverride[ DRM_SCREEN_TYPE_INTERNAL ]; -+ g_nDynamicRefreshRate[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = 0; -+ g_nSteamCompMgrTargetFPS = g_nCombinedAppRefreshCycleOverride[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ]; - return; - } - -- static drm_screen_type last_type; -+ static gamescope::GamescopeScreenType last_type; - static int last_target_fps; - static bool first = true; - -- drm_screen_type type = drm_get_screen_type( &g_DRM ); -+ gamescope::GamescopeScreenType type = drm_get_screen_type( &g_DRM ); - int target_fps = g_nCombinedAppRefreshCycleOverride[type]; - - if ( !first && type == last_type && last_target_fps == target_fps ) -@@ -975,7 +975,7 @@ static void update_app_target_refresh_cycle() - update_runtime_info(); - } - --void steamcompmgr_set_app_refresh_cycle_override( drm_screen_type type, int override_fps ) -+void steamcompmgr_set_app_refresh_cycle_override( gamescope::GamescopeScreenType type, int override_fps ) - { - g_nCombinedAppRefreshCycleOverride[ type ] = override_fps; - update_app_target_refresh_cycle(); -@@ -2950,8 +2950,6 @@ paint_all(bool async) - - xwm_log.errorf("Failed to prepare 1-layer flip (%s), trying again with previous mode if modeset needed", strerror( -ret )); - -- drm_rollback( &g_DRM ); -- - // Try once again to in case we need to fall back to another mode. - ret = drm_prepare( &g_DRM, async, &compositeFrameInfo ); - -@@ -3144,7 +3142,7 @@ paint_all(bool async) - - if ( !maxCLLNits && !maxFALLNits ) - { -- drm_supports_st2084( &g_DRM, &maxCLLNits, &maxFALLNits ); -+ drm_supports_hdr( &g_DRM, &maxCLLNits, &maxFALLNits ); - } - - if ( !maxCLLNits && !maxFALLNits ) -@@ -5832,7 +5830,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - g_nSteamCompMgrTargetFPS = get_prop( ctx, ctx->root, ctx->atoms.gamescopeFPSLimit, 0 ); - update_runtime_info(); - } -- for (int i = 0; i < DRM_SCREEN_TYPE_COUNT; i++) -+ for (int i = 0; i < gamescope::GAMESCOPE_SCREEN_TYPE_COUNT; i++) - { - if ( ev->atom == ctx->atoms.gamescopeDynamicRefresh[i] ) - { -@@ -6131,24 +6129,24 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - g_ColorMgmt.pending.chromaticAdaptationMode = ( EChromaticAdaptationMethod ) val; - } - // TODO: Hook up gamescopeColorMuraCorrectionImage for external. -- if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionImage[DRM_SCREEN_TYPE_INTERNAL] ) -+ if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) - { -- std::string path = get_string_prop( ctx, ctx->root, ctx->atoms.gamescopeColorMuraCorrectionImage[DRM_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[DRM_SCREEN_TYPE_INTERNAL] ) -+ if ( ev->atom == ctx->atoms.gamescopeColorMuraScale[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) - { -- uint32_t val = get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorMuraScale[DRM_SCREEN_TYPE_INTERNAL], 0); -+ 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[DRM_SCREEN_TYPE_INTERNAL] ) -+ if ( ev->atom == ctx->atoms.gamescopeColorMuraCorrectionDisabled[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) - { -- bool disabled = !!get_prop(ctx, ctx->root, ctx->atoms.gamescopeColorMuraCorrectionDisabled[DRM_SCREEN_TYPE_INTERNAL], 0); -+ 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; -@@ -7425,8 +7423,8 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - - ctx->atoms.gamescopeXWaylandModeControl = XInternAtom( ctx->dpy, "GAMESCOPE_XWAYLAND_MODE_CONTROL", false ); - ctx->atoms.gamescopeFPSLimit = XInternAtom( ctx->dpy, "GAMESCOPE_FPS_LIMIT", false ); -- ctx->atoms.gamescopeDynamicRefresh[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH", false ); -- ctx->atoms.gamescopeDynamicRefresh[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH_EXTERNAL", 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 ); -@@ -7490,12 +7488,12 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - 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[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_IMAGE", false ); -- ctx->atoms.gamescopeColorMuraCorrectionImage[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_IMAGE_EXTERNAL", false ); -- ctx->atoms.gamescopeColorMuraScale[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_SCALE", false ); -- ctx->atoms.gamescopeColorMuraScale[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_SCALE_EXTERNAL", false ); -- ctx->atoms.gamescopeColorMuraCorrectionDisabled[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_DISABLED", false ); -- ctx->atoms.gamescopeColorMuraCorrectionDisabled[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_COLOR_MURA_CORRECTION_DISABLED_EXTERNAL", 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 ); -@@ -7597,13 +7595,13 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = - *needs_flush = true; - } - -- bool st2084 = BIsNested() ? vulkan_supports_hdr10() : drm_supports_st2084( &g_DRM ); -- if ( st2084 != g_bSupportsST2084_CachedValue || force ) -+ bool HDR = BIsNested() ? vulkan_supports_hdr10() : drm_supports_hdr( &g_DRM ); -+ if ( HDR != g_bSupportsHDR_CachedValue || force ) - { -- uint32_t hdr_value = st2084 ? 1 : 0; -+ 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_bSupportsST2084_CachedValue = st2084; -+ g_bSupportsHDR_CachedValue = HDR; - if (needs_flush) - *needs_flush = true; - } -@@ -7646,7 +7644,7 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) - if (needs_flush) - *needs_flush = true; - -- if ( drm_get_screen_type(&g_DRM) == DRM_SCREEN_TYPE_INTERNAL ) -+ if ( drm_get_screen_type(&g_DRM) == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - { - XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayModeListExternal); - -@@ -7656,12 +7654,17 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) - return; - } - -+ if ( !g_DRM.pConnector || !g_DRM.pConnector->GetModeConnector() ) -+ { -+ return; -+ } -+ - char modes[4096] = ""; - int remaining_size = sizeof(modes) - 1; - int len = 0; -- for (int i = 0; remaining_size > 0 && i < g_DRM.connector->connector->count_modes; i++) -+ for (int i = 0; remaining_size > 0 && i < g_DRM.pConnector->GetModeConnector()->count_modes; i++) - { -- const auto& mode = g_DRM.connector->connector->modes[i]; -+ const auto& mode = g_DRM.pConnector->GetModeConnector()->modes[i]; - int mode_len = snprintf(&modes[len], remaining_size, "%s%dx%d@%d", - i == 0 ? "" : " ", - int(mode.hdisplay), int(mode.vdisplay), int(mode.vrefresh)); -@@ -7974,7 +7977,7 @@ steamcompmgr_main(int argc, char **argv) - } - } - -- g_bOutputHDREnabled = (g_bSupportsST2084_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; -+ g_bOutputHDREnabled = (g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; - - // 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 -diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp -index 16ef13273..d1a209d48 100644 ---- a/src/steamcompmgr.hpp -+++ b/src/steamcompmgr.hpp -@@ -172,4 +172,4 @@ MouseCursor *steamcompmgr_get_server_cursor(uint32_t serverId); - - extern int g_nAsyncFlipsEnabled; - --extern void steamcompmgr_set_app_refresh_cycle_override( drm_screen_type type, int override_fps ); -+extern void steamcompmgr_set_app_refresh_cycle_override( gamescope::GamescopeScreenType type, int override_fps ); -diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp -index b62fc9023..4b617584e 100644 ---- a/src/vblankmanager.cpp -+++ b/src/vblankmanager.cpp -@@ -112,7 +112,7 @@ namespace gamescope - - VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) - { -- const drm_screen_type eScreenType = drm_get_screen_type( &g_DRM ); -+ const GamescopeScreenType eScreenType = drm_get_screen_type( &g_DRM ); - - const int nRefreshRate = GetRefresh(); - const uint64_t ulRefreshInterval = kSecInNanoSecs / nRefreshRate; -@@ -125,7 +125,7 @@ namespace gamescope - // 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 == DRM_SCREEN_TYPE_INTERNAL -+ const uint64_t ulRedZone = eScreenType == GAMESCOPE_SCREEN_TYPE_INTERNAL - ? m_ulVBlankDrawBufferRedZone - : ( m_ulVBlankDrawBufferRedZone * 60 * kSecInNanoSecs ) / ( nRefreshRate * kSecInNanoSecs ); - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 3fbc4ff58..2eb0f9c43 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -875,9 +875,9 @@ static void create_gamescope_pipewire( void ) - - 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 = DRM_SCREEN_TYPE_EXTERNAL; -+ auto display_type = gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL; - if ( flags & GAMESCOPE_CONTROL_TARGET_REFRESH_CYCLE_FLAG_INTERNAL_DISPLAY ) -- display_type = DRM_SCREEN_TYPE_INTERNAL; -+ display_type = gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; - - steamcompmgr_set_app_refresh_cycle_override( display_type, fps ); - } -@@ -1956,7 +1956,7 @@ static void apply_touchscreen_orientation(double *x, double *y ) - double ty = 0; - - // Use internal screen always for orientation purposes. -- switch ( g_drmEffectiveOrientation[DRM_SCREEN_TYPE_INTERNAL] ) -+ switch ( g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) - { - default: - case DRM_MODE_ROTATE_0: -@@ -1987,8 +1987,8 @@ int get_effective_touch_mode() - { - if (!BIsNested() && g_bTrackpadTouchExternalDisplay) - { -- drm_screen_type screenType = drm_get_screen_type(&g_DRM); -- if ( screenType == DRM_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) -+ gamescope::GamescopeScreenType screenType = drm_get_screen_type(&g_DRM); -+ if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) - return WLSERVER_TOUCH_CLICK_TRACKPAD; - } - -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index 11f9335de..c558387e6 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -149,7 +149,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable - Atom gamescopeXWaylandModeControl; - - Atom gamescopeFPSLimit; -- Atom gamescopeDynamicRefresh[DRM_SCREEN_TYPE_COUNT]; -+ Atom gamescopeDynamicRefresh[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; - Atom gamescopeLowLatency; - - Atom gamescopeFSRFeedback; -@@ -216,9 +216,9 @@ struct xwayland_ctx_t final : public gamescope::IWaitable - Atom gamescopeColorAppHDRMetadataFeedback; - Atom gamescopeColorSliderInUse; - Atom gamescopeColorChromaticAdaptationMode; -- Atom gamescopeColorMuraCorrectionImage[DRM_SCREEN_TYPE_COUNT]; -- Atom gamescopeColorMuraScale[DRM_SCREEN_TYPE_COUNT]; -- Atom gamescopeColorMuraCorrectionDisabled[DRM_SCREEN_TYPE_COUNT]; -+ 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; - -From 7b126cb383c53118ff1f37b8f64874dc172a3623 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 7 Jan 2024 01:17:57 +0000 -Subject: [PATCH 067/134] rendervulkan: Add GAMESCOPE_FORCE_GENERAL_QUEUE - ---- - src/rendervulkan.cpp | 12 ++++++++++-- - 1 file changed, 10 insertions(+), 2 deletions(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 3dbbfe069..96e0ea53f 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -269,6 +269,8 @@ bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface) - return true; - } - -+extern bool env_to_bool(const char *env); -+ - bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) - { - uint32_t deviceCount = 0; -@@ -339,6 +341,9 @@ bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) - m_queueFamily = computeOnlyIndex == ~0u ? generalIndex : computeOnlyIndex; - m_generalQueueFamily = generalIndex; - m_physDev = cphysDev; -+ -+ if ( env_to_bool( "GAMESCOPE_FORCE_GENERAL_QUEUE" ) ) -+ m_queueFamily = generalIndex; - } - } - } -@@ -552,7 +557,7 @@ bool CVulkanDevice::createDevice() - VkDeviceCreateInfo deviceCreateInfo = { - .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pNext = &features2, -- .queueCreateInfoCount = std::size(queueCreateInfos), -+ .queueCreateInfoCount = m_queueFamily == m_generalQueueFamily ? 1u : 2u, - .pQueueCreateInfos = queueCreateInfos, - .enabledExtensionCount = (uint32_t)enabledExtensions.size(), - .ppEnabledExtensionNames = enabledExtensions.data(), -@@ -606,7 +611,10 @@ bool CVulkanDevice::createDevice() - #undef VK_FUNC - - vk.GetDeviceQueue(device(), m_queueFamily, 0, &m_queue); -- vk.GetDeviceQueue(device(), m_generalQueueFamily, 0, &m_generalQueue); -+ if ( m_queueFamily == m_generalQueueFamily ) -+ m_queue = m_generalQueue; -+ else -+ vk.GetDeviceQueue(device(), m_generalQueueFamily, 0, &m_generalQueue); - - return true; - } - -From db714154452c43af24fdb544bbdaf80a6c34cf79 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 7 Jan 2024 01:22:59 +0000 -Subject: [PATCH 068/134] rendervulkan: Fix general queue assignment - ---- - src/rendervulkan.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 96e0ea53f..a2e054f46 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -612,7 +612,7 @@ bool CVulkanDevice::createDevice() - - vk.GetDeviceQueue(device(), m_queueFamily, 0, &m_queue); - if ( m_queueFamily == m_generalQueueFamily ) -- m_queue = m_generalQueue; -+ m_generalQueue = m_queue; - else - vk.GetDeviceQueue(device(), m_generalQueueFamily, 0, &m_generalQueue); - - -From 8bc99b78b2b386c162603d4f179882a697784e11 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 7 Jan 2024 01:23:26 +0000 -Subject: [PATCH 069/134] rendervulkan: Fix getenv call for - GAMESCOPE_FORCE_GENERAL_QUEUE - ---- - src/rendervulkan.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index a2e054f46..f31d8a931 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -342,7 +342,7 @@ bool CVulkanDevice::selectPhysDev(VkSurfaceKHR surface) - m_generalQueueFamily = generalIndex; - m_physDev = cphysDev; - -- if ( env_to_bool( "GAMESCOPE_FORCE_GENERAL_QUEUE" ) ) -+ if ( env_to_bool( getenv( "GAMESCOPE_FORCE_GENERAL_QUEUE" ) ) ) - m_queueFamily = generalIndex; - } - } - -From 1a008fafbd6cb5291eda160b9dcbfa1c13785921 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 7 Jan 2024 01:33:21 +0000 -Subject: [PATCH 070/134] steamcompmgr: Don't scale cursor if no set cursor - scale height - -Fixes: #1054 ---- - src/steamcompmgr.cpp | 8 ++++++-- - 1 file changed, 6 insertions(+), 2 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index f63222f9d..0091f5dd3 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -1864,8 +1864,12 @@ bool MouseCursor::getTexture() - m_hotspotX = image->xhot; - m_hotspotY = image->yhot; - -- int nDesiredWidth, nDesiredHeight; -- GetDesiredSize( nDesiredWidth, nDesiredHeight ); -+ int nDesiredWidth = image->width; -+ int nDesiredHeight = image->height; -+ if ( g_nCursorScaleHeight > 0 ) -+ { -+ GetDesiredSize( nDesiredWidth, nDesiredHeight ); -+ } - - uint32_t surfaceWidth; - uint32_t surfaceHeight; - -From 58509f03ba00f7adaca77b27515e585412e440cc Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 7 Jan 2024 01:38:49 +0000 -Subject: [PATCH 071/134] main: Only set XCURSOR_SIZE if cursor scale height is - set - ---- - src/main.cpp | 10 +++++++++- - src/steamcompmgr.cpp | 4 +--- - 2 files changed, 10 insertions(+), 4 deletions(-) - -diff --git a/src/main.cpp b/src/main.cpp -index 5bbb01bde..220ee1583 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -45,6 +45,8 @@ const char *gamescope_optstring = nullptr; - const char *g_pOriginalDisplay = nullptr; - const char *g_pOriginalWaylandDisplay = nullptr; - -+int g_nCursorScaleHeight = -1; -+ - const struct option *gamescope_options = (struct option[]){ - { "help", no_argument, nullptr, 0 }, - { "nested-width", required_argument, nullptr, 'w' }, -@@ -639,6 +641,8 @@ int main(int argc, char **argv) - } else if (strcmp(opt_name, "headless") == 0) { - g_bHeadless = true; - g_bIsNested = true; -+ } else if (strcmp(opt_name, "cursor-scale-height") == 0) { -+ g_nCursorScaleHeight = atoi(optarg); - } - #if HAVE_OPENVR - else if (strcmp(opt_name, "openvr") == 0) { -@@ -700,7 +704,11 @@ int main(int argc, char **argv) - #endif - - setenv( "XWAYLAND_FORCE_ENABLE_EXTRA_MODES", "1", 1 ); -- setenv( "XCURSOR_SIZE", "256", 1 ); -+ -+ if ( g_nCursorScaleHeight > 0 ) -+ { -+ setenv( "XCURSOR_SIZE", "256", 1 ); -+ } - - raise_fd_limit(); - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 0091f5dd3..177530c70 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -1104,7 +1104,7 @@ namespace gamescope - - static std::atomic g_bForceRepaint{false}; - --static int g_nCursorScaleHeight = -1; -+extern int g_nCursorScaleHeight; - - // poor man's semaphore - class sem -@@ -7797,8 +7797,6 @@ steamcompmgr_main(int argc, char **argv) - g_FadeOutDuration = atoi(optarg); - } else if (strcmp(opt_name, "force-windows-fullscreen") == 0) { - bForceWindowsFullscreen = true; -- } else if (strcmp(opt_name, "cursor-scale-height") == 0) { -- g_nCursorScaleHeight = atoi(optarg); - } else if (strcmp(opt_name, "hdr-enabled") == 0) { - g_bHDREnabled = true; - } else if (strcmp(opt_name, "hdr-debug-force-support") == 0) { - -From 20cac0d3356c8a83afa8a5001e771c26fb9a5424 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 10 Jan 2024 01:06:32 +0000 -Subject: [PATCH 072/134] steamcompmgr: Add back - GAMESCOPECTRL_DEBUG_REQUEST_SCREENSHOT - -Short term debugging ---- - src/steamcompmgr.cpp | 20 ++++++++++++++++++++ - src/xwayland_ctx.hpp | 1 + - 2 files changed, 21 insertions(+) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 177530c70..7c8d2eeaf 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -3307,7 +3307,10 @@ paint_all(bool async) - } - - 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 ) - { -@@ -3326,7 +3329,10 @@ paint_all(bool async) - { - 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 ); -+ } - } - } - -@@ -5609,6 +5615,19 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - } ); - } - } -+ 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); -@@ -7412,6 +7431,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - 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); -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index c558387e6..229a0884a 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -134,6 +134,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable - Atom gamescopeCtrlWindowAtom; - Atom gamescopeInputCounterAtom; - Atom gamescopeScreenShotAtom; -+ Atom gamescopeDebugScreenShotAtom; - - Atom gamescopeFocusDisplay; - Atom gamescopeMouseFocusDisplay; - -From 7fcfbf00f4bde7cefc6ab03966dea4371ee0b44c Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Fri, 12 Jan 2024 16:33:34 +0000 -Subject: [PATCH 073/134] rendervulkan: Better logging/handling of device - loss/fatal errors - -Log vk result and such so we can track what happened. ---- - src/rendervulkan.cpp | 35 ++++++++++++++++++----------------- - 1 file changed, 18 insertions(+), 17 deletions(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index f31d8a931..8194ca2bc 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -116,6 +116,18 @@ static void vk_errorf(VkResult result, const char *fmt, ...) { - 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 ) -+ - template - Target *pNextFind(const Base *base, VkStructureType sType) - { -@@ -1217,12 +1229,7 @@ uint64_t CVulkanDevice::submitInternal( CVulkanCmdBuffer* cmdBuffer ) - .pSignalSemaphores = &m_scratchTimelineSemaphore, - }; - -- VkResult res = vk.QueueSubmit( cmdBuffer->queue(), 1, &submitInfo, VK_NULL_HANDLE ); -- -- if ( res != VK_SUCCESS ) -- { -- assert( 0 ); -- } -+ vk_check( vk.QueueSubmit( cmdBuffer->queue(), 1, &submitInfo, VK_NULL_HANDLE ) ); - - return nextSeqNo; - } -@@ -1237,8 +1244,7 @@ uint64_t CVulkanDevice::submit( std::unique_ptr cmdBuffer) - void CVulkanDevice::garbageCollect( void ) - { - uint64_t currentSeqNo; -- VkResult res = vk.GetSemaphoreCounterValue(device(), m_scratchTimelineSemaphore, ¤tSeqNo); -- assert( res == VK_SUCCESS ); -+ vk_check( vk.GetSemaphoreCounterValue(device(), m_scratchTimelineSemaphore, ¤tSeqNo) ); - - resetCmdBuffers(currentSeqNo); - } -@@ -1255,9 +1261,7 @@ void CVulkanDevice::wait(uint64_t sequence, bool reset) - .pValues = &sequence, - } ; - -- VkResult res = vk.WaitSemaphores(device(), &waitInfo, ~0ull); -- if (res != VK_SUCCESS) -- assert( 0 ); -+ vk_check( vk.WaitSemaphores( device(), &waitInfo, ~0ull ) ); - - if (reset) - resetCmdBuffers(sequence); -@@ -1297,8 +1301,7 @@ CVulkanCmdBuffer::~CVulkanCmdBuffer() - - void CVulkanCmdBuffer::reset() - { -- VkResult res = m_device->vk.ResetCommandBuffer(m_cmdBuffer, 0); -- assert(res == VK_SUCCESS); -+ vk_check( m_device->vk.ResetCommandBuffer(m_cmdBuffer, 0) ); - m_textureRefs.clear(); - m_textureState.clear(); - } -@@ -1310,8 +1313,7 @@ void CVulkanCmdBuffer::begin() - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT - }; - -- VkResult res = m_device->vk.BeginCommandBuffer(m_cmdBuffer, &commandBufferBeginInfo); -- assert(res == VK_SUCCESS); -+ vk_check( m_device->vk.BeginCommandBuffer(m_cmdBuffer, &commandBufferBeginInfo) ); - - clearState(); - } -@@ -1319,8 +1321,7 @@ void CVulkanCmdBuffer::begin() - void CVulkanCmdBuffer::end() - { - insertBarrier(true); -- VkResult res = m_device->vk.EndCommandBuffer(m_cmdBuffer); -- assert(res == VK_SUCCESS); -+ vk_check( m_device->vk.EndCommandBuffer(m_cmdBuffer) ); - } - - void CVulkanCmdBuffer::bindTexture(uint32_t slot, std::shared_ptr texture) - -From 52c8cd7e852ff207dd6b6acf875dc53a4677e9c7 Mon Sep 17 00:00:00 2001 -From: Jeremy Selan -Date: Wed, 10 Jan 2024 14:46:08 -0800 -Subject: [PATCH 074/134] [color_helpers]: do not do color remapping for PQ - source content - -Fixes a bug where HDR colors were not being rendered faithfully at the edges of the color gamut. - -Thanks to JDSP for reporting. ---- - src/color_helpers.cpp | 24 ++++++------------------ - 1 file changed, 6 insertions(+), 18 deletions(-) - -diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp -index 7a6e046f3..4ec148a65 100644 ---- a/src/color_helpers.cpp -+++ b/src/color_helpers.cpp -@@ -866,24 +866,12 @@ void buildPQColorimetry( displaycolorimetry_t * pColorimetry, colormapping_t *pM - { - *pColorimetry = displaycolorimetry_2020; - -- if ( BIsWideGamut( nativeDisplayOutput) ) -- { -- colormapping_t smoothRemap; -- smoothRemap.blendEnableMinSat = 0.7f; -- smoothRemap.blendEnableMaxSat = 1.0f; -- smoothRemap.blendAmountMin = 0.0f; -- smoothRemap.blendAmountMax = 1.f; -- *pMapping = smoothRemap; -- } -- else -- { -- colormapping_t noRemap; -- noRemap.blendEnableMinSat = 0.7f; -- noRemap.blendEnableMaxSat = 1.0f; -- noRemap.blendAmountMin = 0.0f; -- noRemap.blendAmountMax = 0.0f; -- *pMapping = noRemap; -- } -+ colormapping_t noRemap; -+ noRemap.blendEnableMinSat = 0.0f; -+ noRemap.blendEnableMaxSat = 1.0f; -+ noRemap.blendAmountMin = 0.0f; -+ noRemap.blendAmountMax = 0.0f; -+ *pMapping = noRemap; - } - - bool approxEqual( const glm::vec3 & a, const glm::vec3 & b, float flTolerance = 1e-5f ) - -From 308a0638e5ac39d92aa01a5f31c7a1c3cca9d3c0 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Fri, 19 Jan 2024 23:55:11 +0000 -Subject: [PATCH 075/134] steamcompmgr: Fix rare crash in - handle_presented_for_window - ---- - src/steamcompmgr.cpp | 12 +++++------- - 1 file changed, 5 insertions(+), 7 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 7c8d2eeaf..94880c82c 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -6670,6 +6670,8 @@ void handle_done_commits_xdg() - - 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 ) -@@ -6681,8 +6683,6 @@ void handle_presented_for_window( steamcompmgr_win_t* w ) - { - if (!lastCommit->presentation_feedbacks.empty() || lastCommit->present_id) - { -- wlserver_lock(); -- - if (!lastCommit->presentation_feedbacks.empty()) - { - wlserver_presentation_feedback_presented( -@@ -6703,17 +6703,14 @@ void handle_presented_for_window( steamcompmgr_win_t* w ) - lastCommit->present_margin); - lastCommit->present_id = std::nullopt; - } -- -- wlserver_unlock(); - } - } - - if (struct wlr_surface *surface = w->current_surface()) - { - auto info = get_wl_surface_info(surface); -- if (info->gamescope_swapchain != nullptr && info->last_refresh_cycle != refresh_cycle) -+ if (info != nullptr && info->gamescope_swapchain != nullptr && info->last_refresh_cycle != refresh_cycle) - { -- wlserver_lock(); - if (info->gamescope_swapchain != nullptr) - { - // Could have got the override set in this bubble.s -@@ -6725,7 +6722,6 @@ void handle_presented_for_window( steamcompmgr_win_t* w ) - wlserver_refresh_cycle(surface, refresh_cycle); - } - } -- wlserver_unlock(); - } - } - } -@@ -8147,9 +8143,11 @@ steamcompmgr_main(int argc, char **argv) - - 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(); - } - - // - -From 868206c1e89bcc1069e1129755f834d62e987732 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sat, 20 Jan 2024 00:36:52 +0000 -Subject: [PATCH 076/134] rendervulkan: Add VkExternalMemoryImageCreateInfo for - any flippable surface - -Fixes some corruption on NVIDIA ---- - src/rendervulkan.cpp | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 8194ca2bc..4715970db 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -1965,6 +1965,12 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - .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; - } - - -From 8a6fc5c5f539439e141da00eccdda885439b8127 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sat, 20 Jan 2024 00:50:38 +0000 -Subject: [PATCH 077/134] steamcompmgr: Limit desired size by drm cursor size - ---- - src/steamcompmgr.cpp | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 94880c82c..1bbc1dce8 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -1991,6 +1991,12 @@ void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) - nSize = std::clamp( nSize, g_nBaseCursorScale, 256 ); - } - -+ if ( BIsNested() == false && alwaysComposite == false ) -+ { -+ nSize = std::min( nSize, g_DRM.cursor_width ); -+ nSize = std::min( nSize, g_DRM.cursor_height ); -+ } -+ - nWidth = nSize; - nHeight = nSize; - } - -From d61dba14be618384a9f824c5d91f81dcc614725d Mon Sep 17 00:00:00 2001 -From: Sefa Eyeoglu -Date: Tue, 19 Dec 2023 11:43:23 +0100 -Subject: [PATCH 078/134] vr_session: update to OpenVR 2 - -Signed-off-by: Sefa Eyeoglu ---- - src/vr_session.cpp | 3 --- - subprojects/openvr | 2 +- - 2 files changed, 1 insertion(+), 4 deletions(-) - -diff --git a/src/vr_session.cpp b/src/vr_session.cpp -index 439a54f07..8696b62d2 100644 ---- a/src/vr_session.cpp -+++ b/src/vr_session.cpp -@@ -116,9 +116,6 @@ bool vr_init(int argc, char **argv) - // Not in public headers yet. - namespace vr - { -- const VROverlayFlags VROverlayFlags_EnableControlBar = (VROverlayFlags)(1 << 23); -- const VROverlayFlags VROverlayFlags_EnableControlBarKeyboard = (VROverlayFlags)(1 << 24); -- const VROverlayFlags VROverlayFlags_EnableControlBarClose = (VROverlayFlags)(1 << 25); - const VROverlayFlags VROverlayFlags_EnableControlBarSteamUI = (VROverlayFlags)(1 << 26); - - const EVRButtonId k_EButton_Steam = (EVRButtonId)(50); -diff --git a/subprojects/openvr b/subprojects/openvr -index 1a0ea2664..15f0838a0 160000 ---- a/subprojects/openvr -+++ b/subprojects/openvr -@@ -1 +1 @@ --Subproject commit 1a0ea26642e517824b66871e6a12280a426cfec3 -+Subproject commit 15f0838a0487feb7da60acd39aab8099b994234c - -From b050e3d0d1e1f395ba47c241fde7cc0c06f85e81 Mon Sep 17 00:00:00 2001 -From: tcoyvwac <53616399+tcoyvwac@users.noreply.github.com> -Date: Tue, 3 Oct 2023 14:48:30 +0200 -Subject: [PATCH 079/134] reshade: Break function into smaller chunks & common - params - -* Lambdas use common variable array name ---- - src/reshade_effect_manager.cpp | 74 +++++++++++++++++++++------------- - 1 file changed, 45 insertions(+), 29 deletions(-) - -diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp -index 3597ca12d..697974997 100644 ---- a/src/reshade_effect_manager.cpp -+++ b/src/reshade_effect_manager.cpp -@@ -199,71 +199,87 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) - - uint32_t zero_data[16] = {}; - -- switch (m_info.type.base) -- { -- case reshadefx::type::t_bool: -+ const auto inner_bool = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ - if (thing) - { -- VkBool32 VkBool32_stuff[16]; -+ decltype(temp_type) array_stuff[16]; - for (uint32_t i = 0; i < m_info.type.components(); i++) -- VkBool32_stuff[i] = !!thing[i]; -- copy(mappedBuffer, VkBool32_stuff, sizeof(VkBool32) * m_info.type.components()); -+ array_stuff[i] = pred(thing[i]); -+ copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); - } - else - { - if (m_info.has_initializer_value) -- copy(mappedBuffer, (const void*)&m_info.initializer_value.as_uint[0], sizeof(VkBool32) * m_info.type.components() ); -+ copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); - else -- copy(mappedBuffer, (const void*)zero_data, sizeof(VkBool32) * m_info.type.components() ); -+ copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); - } -- break; -- case reshadefx::type::t_int: -+ }; -+ const auto inner_int = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ - if (thing) - { -- int32_t int32_t_stuff[16]; -+ decltype(temp_type) array_stuff[16]; - for (uint32_t i = 0; i < m_info.type.components(); i++) -- int32_t_stuff[i] = thing[i]; -- copy(mappedBuffer, int32_t_stuff, sizeof(int32_t) * m_info.type.components()); -+ array_stuff[i] = pred(thing[i]); -+ copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); - } - else - { - if (m_info.has_initializer_value) -- copy(mappedBuffer, (const void*)&m_info.initializer_value.as_int[0], sizeof(int32_t) * m_info.type.components() ); -+ copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); - else -- copy(mappedBuffer, (const void*)zero_data, sizeof(int32_t) * m_info.type.components() ); -+ copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); - } -- break; -- case reshadefx::type::t_uint: -+ }; -+ const auto inner_uint = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ - if (thing) - { -- uint32_t uint32_t_stuff[16]; -+ decltype(temp_type) array_stuff[16]; - for (uint32_t i = 0; i < m_info.type.components(); i++) -- uint32_t_stuff[i] = thing[i]; -- copy(mappedBuffer, uint32_t_stuff, sizeof(uint32_t) * m_info.type.components()); -+ array_stuff[i] = pred(thing[i]); -+ copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); - } - else - { - if (m_info.has_initializer_value) -- copy(mappedBuffer, (const void*)&m_info.initializer_value.as_uint[0], sizeof(uint32_t) * m_info.type.components() ); -+ copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); - else -- copy(mappedBuffer, (const void*)zero_data, sizeof(uint32_t) * m_info.type.components() ); -+ copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); - } -- break; -- case reshadefx::type::t_float: -+ }; -+ const auto inner_float = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ - if (thing) - { -- float float_stuff[16]; -+ decltype(temp_type) array_stuff[16]; - for (uint32_t i = 0; i < m_info.type.components(); i++) -- float_stuff[i] = thing[i]; -- copy(mappedBuffer, float_stuff, sizeof(float) * m_info.type.components()); -+ array_stuff[i] = pred(thing[i]); -+ copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); - } - else - { - if (m_info.has_initializer_value) -- copy(mappedBuffer, (const void*)&m_info.initializer_value.as_float[0], sizeof(float) * m_info.type.components() ); -+ copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); - else -- copy(mappedBuffer, (const void*)zero_data, sizeof(float) * m_info.type.components() ); -+ copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); - } -+ }; -+ -+ const auto double_negate = [](const T item){return !!(item);}; -+ const auto id = [](const T item){return item;}; // C++20 = std::identity -+ -+ switch (m_info.type.base) -+ { -+ case reshadefx::type::t_bool: -+ inner_bool(VkBool32{}, &reshadefx::constant::as_uint, double_negate, mappedBuffer, thing); -+ break; -+ case reshadefx::type::t_int: -+ inner_int(int32_t{}, &reshadefx::constant::as_int, id, mappedBuffer, thing); -+ break; -+ case reshadefx::type::t_uint: -+ inner_uint(uint32_t{}, &reshadefx::constant::as_uint, id, mappedBuffer, thing); -+ break; -+ case reshadefx::type::t_float: -+ inner_float(float{}, &reshadefx::constant::as_float, id, mappedBuffer, thing); - break; - default: - reshade_log.errorf("Unknown uniform type!"); - -From 0c02396633e19b90ace76c29f62bfe54cacfe20f Mon Sep 17 00:00:00 2001 -From: tcoyvwac <53616399+tcoyvwac@users.noreply.github.com> -Date: Tue, 3 Oct 2023 14:52:29 +0200 -Subject: [PATCH 080/134] reshade: Condense to one common lambda function - ---- - src/reshade_effect_manager.cpp | 59 +++------------------------------- - 1 file changed, 5 insertions(+), 54 deletions(-) - -diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp -index 697974997..33d65f538 100644 ---- a/src/reshade_effect_manager.cpp -+++ b/src/reshade_effect_manager.cpp -@@ -198,56 +198,7 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) - assert(m_info.type.array_length == 0 || m_info.type.array_length == 1); - - uint32_t zero_data[16] = {}; -- -- const auto inner_bool = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ -- if (thing) -- { -- decltype(temp_type) array_stuff[16]; -- for (uint32_t i = 0; i < m_info.type.components(); i++) -- array_stuff[i] = pred(thing[i]); -- copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); -- } -- else -- { -- if (m_info.has_initializer_value) -- copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); -- else -- copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); -- } -- }; -- const auto inner_int = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ -- if (thing) -- { -- decltype(temp_type) array_stuff[16]; -- for (uint32_t i = 0; i < m_info.type.components(); i++) -- array_stuff[i] = pred(thing[i]); -- copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); -- } -- else -- { -- if (m_info.has_initializer_value) -- copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); -- else -- copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); -- } -- }; -- const auto inner_uint = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ -- if (thing) -- { -- decltype(temp_type) array_stuff[16]; -- for (uint32_t i = 0; i < m_info.type.components(); i++) -- array_stuff[i] = pred(thing[i]); -- copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); -- } -- else -- { -- if (m_info.has_initializer_value) -- copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); -- else -- copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); -- } -- }; -- const auto inner_float = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ -+ const auto inner_common = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ - if (thing) - { - decltype(temp_type) array_stuff[16]; -@@ -270,16 +221,16 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) - switch (m_info.type.base) - { - case reshadefx::type::t_bool: -- inner_bool(VkBool32{}, &reshadefx::constant::as_uint, double_negate, mappedBuffer, thing); -+ inner_common(VkBool32{}, &reshadefx::constant::as_uint, double_negate, mappedBuffer, thing); - break; - case reshadefx::type::t_int: -- inner_int(int32_t{}, &reshadefx::constant::as_int, id, mappedBuffer, thing); -+ inner_common(int32_t{}, &reshadefx::constant::as_int, id, mappedBuffer, thing); - break; - case reshadefx::type::t_uint: -- inner_uint(uint32_t{}, &reshadefx::constant::as_uint, id, mappedBuffer, thing); -+ inner_common(uint32_t{}, &reshadefx::constant::as_uint, id, mappedBuffer, thing); - break; - case reshadefx::type::t_float: -- inner_float(float{}, &reshadefx::constant::as_float, id, mappedBuffer, thing); -+ inner_common(float{}, &reshadefx::constant::as_float, id, mappedBuffer, thing); - break; - default: - reshade_log.errorf("Unknown uniform type!"); - -From 658dd989f5facd2ee854fa53fd76286955a3940b Mon Sep 17 00:00:00 2001 -From: tcoyvwac <53616399+tcoyvwac@users.noreply.github.com> -Date: Tue, 3 Oct 2023 15:20:30 +0200 -Subject: [PATCH 081/134] reshade: Removed explicit datatype parameter for - inferred typename T (of function) - ---- - src/reshade_effect_manager.cpp | 33 +++++++++++++++------------------ - 1 file changed, 15 insertions(+), 18 deletions(-) - -diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp -index 33d65f538..0495f0434 100644 ---- a/src/reshade_effect_manager.cpp -+++ b/src/reshade_effect_manager.cpp -@@ -198,21 +198,18 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) - assert(m_info.type.array_length == 0 || m_info.type.array_length == 1); - - uint32_t zero_data[16] = {}; -- const auto inner_common = [&](auto temp_type, const auto accessor, auto pred, void* mappedBuffer, const T* thing){ -- if (thing) -- { -- decltype(temp_type) array_stuff[16]; -+ const auto inner_common = [&](const auto accessor, const auto pred){ -+ T array_stuff[16] = {}; -+ const auto path_thing = [&](){ - for (uint32_t i = 0; i < m_info.type.components(); i++) - array_stuff[i] = pred(thing[i]); -- copy(mappedBuffer, array_stuff, sizeof(decltype(temp_type)) * m_info.type.components()); -- } -- else -- { -- if (m_info.has_initializer_value) -- copy(mappedBuffer, (const void*)&((m_info.initializer_value).*accessor)[0], sizeof(decltype(temp_type)) * m_info.type.components() ); -- else -- copy(mappedBuffer, (const void*)zero_data, sizeof(decltype(temp_type)) * m_info.type.components() ); -- } -+ return array_stuff; -+ }; -+ const auto path_zero = (m_info.has_initializer_value) -+ ? static_cast(&((m_info.initializer_value).*accessor)[0]) -+ : zero_data; -+ const auto sourceBuffer = (thing) ? path_thing() : path_zero; -+ copy(mappedBuffer, sourceBuffer, sizeof(T) * m_info.type.components()); - }; - - const auto double_negate = [](const T item){return !!(item);}; -@@ -220,17 +217,17 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) - - switch (m_info.type.base) - { -- case reshadefx::type::t_bool: -- inner_common(VkBool32{}, &reshadefx::constant::as_uint, double_negate, mappedBuffer, thing); -+ case reshadefx::type::t_bool: // VkBool32 = uint32_t; -+ inner_common(&reshadefx::constant::as_uint, double_negate); - break; - case reshadefx::type::t_int: -- inner_common(int32_t{}, &reshadefx::constant::as_int, id, mappedBuffer, thing); -+ inner_common(&reshadefx::constant::as_int, id); - break; - case reshadefx::type::t_uint: -- inner_common(uint32_t{}, &reshadefx::constant::as_uint, id, mappedBuffer, thing); -+ inner_common(&reshadefx::constant::as_uint, id); - break; - case reshadefx::type::t_float: -- inner_common(float{}, &reshadefx::constant::as_float, id, mappedBuffer, thing); -+ inner_common(&reshadefx::constant::as_float, id); - break; - default: - reshade_log.errorf("Unknown uniform type!"); - -From 425eb07fc530afa8df45c6910f172c42a10e7401 Mon Sep 17 00:00:00 2001 -From: tcoyvwac <53616399+tcoyvwac@users.noreply.github.com> -Date: Sun, 8 Oct 2023 21:13:44 +0200 -Subject: [PATCH 082/134] reshade: Make changeset as small using inlining - -* Inlined multi-lined statements into single-lined expressions -* Use understore to denote x_detail lambda function ---- - src/reshade_effect_manager.cpp | 24 +++++++++--------------- - 1 file changed, 9 insertions(+), 15 deletions(-) - -diff --git a/src/reshade_effect_manager.cpp b/src/reshade_effect_manager.cpp -index 0495f0434..382a016bf 100644 ---- a/src/reshade_effect_manager.cpp -+++ b/src/reshade_effect_manager.cpp -@@ -198,36 +198,30 @@ void ReshadeUniform::copy(void* mappedBuffer, const T* thing) - assert(m_info.type.array_length == 0 || m_info.type.array_length == 1); - - uint32_t zero_data[16] = {}; -- const auto inner_common = [&](const auto accessor, const auto pred){ -+ const auto copy_ = [&](const auto constantDatatype){ - T array_stuff[16] = {}; -- const auto path_thing = [&](){ -+ const auto thingBuffer = [&](){ - for (uint32_t i = 0; i < m_info.type.components(); i++) -- array_stuff[i] = pred(thing[i]); -+ array_stuff[i] = (m_info.type.base == reshadefx::type::t_bool) ? !!(thing[i]) : thing[i]; - return array_stuff; - }; -- const auto path_zero = (m_info.has_initializer_value) -- ? static_cast(&((m_info.initializer_value).*accessor)[0]) -- : zero_data; -- const auto sourceBuffer = (thing) ? path_thing() : path_zero; -- copy(mappedBuffer, sourceBuffer, sizeof(T) * m_info.type.components()); -+ const auto defaultOrZeroBuffer = (m_info.has_initializer_value) ? static_cast(std::begin((m_info.initializer_value).*constantDatatype)) : zero_data; -+ copy(mappedBuffer, (thing) ? thingBuffer() : defaultOrZeroBuffer, sizeof(T) * m_info.type.components()); - }; - -- const auto double_negate = [](const T item){return !!(item);}; -- const auto id = [](const T item){return item;}; // C++20 = std::identity -- - switch (m_info.type.base) - { - case reshadefx::type::t_bool: // VkBool32 = uint32_t; -- inner_common(&reshadefx::constant::as_uint, double_negate); -+ copy_(&reshadefx::constant::as_uint); - break; - case reshadefx::type::t_int: -- inner_common(&reshadefx::constant::as_int, id); -+ copy_(&reshadefx::constant::as_int); - break; - case reshadefx::type::t_uint: -- inner_common(&reshadefx::constant::as_uint, id); -+ copy_(&reshadefx::constant::as_uint); - break; - case reshadefx::type::t_float: -- inner_common(&reshadefx::constant::as_float, id); -+ copy_(&reshadefx::constant::as_float); - break; - default: - reshade_log.errorf("Unknown uniform type!"); - -From 87834648e61964889de7971a370b084b00de83fb Mon Sep 17 00:00:00 2001 -From: Dexter Gaon-Shatford -Date: Mon, 24 Jul 2023 22:09:17 -0400 -Subject: [PATCH 083/134] Add configurable mouse sensitivity - -The `--mouse-sensitivity`/`-s` command line option allows the user to -set the value `g_mouseSensitivity` which is used to scale the mouse -movement in `wlserver_mousemotion`. ---- - src/main.cpp | 7 +++++++ - src/main.hpp | 2 ++ - src/wlserver.cpp | 13 ++++--------- - 3 files changed, 13 insertions(+), 9 deletions(-) - -diff --git a/src/main.cpp b/src/main.cpp -index 220ee1583..451c02705 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -62,6 +62,7 @@ const struct option *gamescope_options = (struct option[]){ - { "rt", no_argument, nullptr, 0 }, - { "prefer-vk-device", required_argument, 0 }, - { "expose-wayland", no_argument, 0 }, -+ { "mouse-sensitivity", required_argument, nullptr, 's' }, - - { "headless", no_argument, 0 }, - -@@ -159,6 +160,7 @@ const char usage[] = - " 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" - " --headless use headless backend (no window, no DRM output)\n" - " --cursor path to default cursor image\n" - " -R, --ready-fd notify FD when ready\n" -@@ -267,6 +269,8 @@ bool g_bHeadless = false; - - bool g_bGrabbed = false; - -+float g_mouseSensitivity = 1.0; -+ - GamescopeUpscaleFilter g_upscaleFilter = GamescopeUpscaleFilter::LINEAR; - GamescopeUpscaleScaler g_upscaleScaler = GamescopeUpscaleScaler::AUTO; - -@@ -596,6 +600,9 @@ int main(int argc, char **argv) - case 'g': - g_bGrabbed = true; - break; -+ case 's': -+ g_mouseSensitivity = atof( optarg ); -+ break; - case 0: // long options without a short option - opt_name = gamescope_options[opt_index].name; - if (strcmp(opt_name, "help") == 0) { -diff --git a/src/main.hpp b/src/main.hpp -index 7d8e9f1be..7cf1c4088 100644 ---- a/src/main.hpp -+++ b/src/main.hpp -@@ -23,6 +23,8 @@ extern bool g_bFullscreen; - - extern bool g_bGrabbed; - -+extern float g_mouseSensitivity; -+ - enum class GamescopeUpscaleFilter : uint32_t - { - LINEAR = 0, -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 2eb0f9c43..4644f667f 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -270,6 +270,9 @@ static void wlserver_perform_rel_pointer_motion(double unaccel_dx, double unacce - auto server = steamcompmgr_get_focused_server(); - if ( server != NULL ) - { -+ unaccel_dx *= g_mouseSensitivity; -+ unaccel_dy *= g_mouseSensitivity; -+ - server->ctx->accum_x += unaccel_dx; - server->ctx->accum_y += unaccel_dy; - -@@ -1861,15 +1864,7 @@ void wlserver_mousefocus( struct wlr_surface *wlrsurface, int x /* = 0 */, int y - - void wlserver_mousemotion( int x, int y, uint32_t time ) - { -- assert( wlserver_is_lock_held() ); -- -- // TODO: Pick the xwayland_server with active focus -- auto server = steamcompmgr_get_focused_server(); -- if ( server != NULL ) -- { -- XTestFakeRelativeMotionEvent( server->get_xdisplay(), x, y, CurrentTime ); -- XFlush( server->get_xdisplay() ); -- } -+ wlserver_perform_rel_pointer_motion( x, y ); - } - - void wlserver_mousewarp( int x, int y, uint32_t time ) - -From bca7990e61a1eb8198e54d86a4a9a44d41d9b07e Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sat, 20 Jan 2024 01:09:32 +0000 -Subject: [PATCH 084/134] main: Pick correct Vulkan device for DRM - -Closes: #1080 ---- - src/main.cpp | 59 +++++++++++++++++++++++++++----------------- - src/rendervulkan.cpp | 7 +++--- - src/rendervulkan.hpp | 2 +- - 3 files changed, 40 insertions(+), 28 deletions(-) - -diff --git a/src/main.cpp b/src/main.cpp -index 451c02705..1ec1f48e3 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -765,9 +765,6 @@ int main(int argc, char **argv) - } - } - -- VkInstance instance = vulkan_create_instance(); -- VkSurfaceKHR surface = VK_NULL_HANDLE; -- - if ( !BIsNested() ) - { - g_bForceRelativeMouse = false; -@@ -790,30 +787,15 @@ int main(int argc, char **argv) - return 1; - } - -- if ( BIsSDLSession() ) -- { -- if ( !SDL_Vulkan_CreateSurface( g_SDLWindow, instance, &surface ) ) -- { -- fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); -- return 1; -- } -- } -- - g_ForcedNV12ColorSpace = parse_colorspace_string( getenv( "GAMESCOPE_NV12_COLORSPACE" ) ); - -- if ( !vulkan_init(instance, surface) ) -- { -- fprintf( stderr, "Failed to initialize Vulkan\n" ); -- return 1; -- } -- - if ( !vulkan_init_formats() ) - { - fprintf( stderr, "vulkan_init_formats failed\n" ); - return 1; - } - -- if ( !vulkan_make_output(surface) ) -+ if ( !vulkan_make_output() ) - { - fprintf( stderr, "vulkan_make_output failed\n" ); - return 1; -@@ -925,6 +907,8 @@ static void steamCompMgrThreadRun(int argc, char **argv) - - static bool initOutput( int preferredWidth, int preferredHeight, int preferredRefresh ) - { -+ VkInstance instance = vulkan_create_instance(); -+ - if ( BIsNested() ) - { - g_nOutputWidth = preferredWidth; -@@ -936,7 +920,7 @@ static bool initOutput( int preferredWidth, int preferredHeight, int preferredRe - if ( g_nOutputWidth != 0 ) - { - fprintf( stderr, "Cannot specify -W without -H\n" ); -- return 1; -+ return false; - } - g_nOutputHeight = 720; - } -@@ -948,16 +932,45 @@ static bool initOutput( int preferredWidth, int preferredHeight, int preferredRe - if ( BIsVRSession() ) - { - #if HAVE_OPENVR -- return vrsession_init(); -+ if ( !vrsession_init() ) -+ return false; - #else - return false; - #endif - } - else if ( BIsSDLSession() ) - { -- return sdlwindow_init(); -+ if ( !sdlwindow_init() ) -+ return false; -+ } -+ -+ VkSurfaceKHR surface = VK_NULL_HANDLE; -+ -+ if ( BIsSDLSession() ) -+ { -+ if ( !SDL_Vulkan_CreateSurface( g_SDLWindow, instance, &surface ) ) -+ { -+ fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); -+ return false; -+ } -+ } -+ -+ if ( !vulkan_init( instance, surface ) ) -+ { -+ fprintf( stderr, "Failed to initialize Vulkan\n" ); -+ return false; - } -+ - return true; - } -- return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh, s_bInitialWantsVRREnabled ); -+ else -+ { -+ if ( !vulkan_init( instance, VK_NULL_HANDLE ) ) -+ { -+ fprintf( stderr, "Failed to initialize Vulkan\n" ); -+ return false; -+ } -+ -+ return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh, s_bInitialWantsVRREnabled ); -+ } - } -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 4715970db..357018607 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -253,6 +253,8 @@ bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface) - assert(instance); - assert(!m_bInitialized); - -+ g_output.surface = surface; -+ - m_instance = instance; - #define VK_FUNC(x) vk.x = (PFN_vk##x) vkGetInstanceProcAddr(instance, "vk"#x); - VULKAN_INSTANCE_FUNCTIONS -@@ -3129,7 +3131,7 @@ bool vulkan_remake_output_images() - return bRet; - } - --bool vulkan_make_output( VkSurfaceKHR surface ) -+bool vulkan_make_output() - { - VulkanOutput_t *pOutput = &g_output; - -@@ -3142,9 +3144,6 @@ bool vulkan_make_output( VkSurfaceKHR surface ) - } - else if ( BIsSDLSession() ) - { -- assert(surface); -- pOutput->surface = surface; -- - result = g_device.vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( g_device.physDev(), pOutput->surface, &pOutput->surfaceCaps ); - if ( result != VK_SUCCESS ) - { -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index e10295208..98b3936f1 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -368,7 +368,7 @@ namespace CompositeDebugFlag - VkInstance vulkan_create_instance(void); - bool vulkan_init(VkInstance instance, VkSurfaceKHR surface); - bool vulkan_init_formats(void); --bool vulkan_make_output(VkSurfaceKHR surface); -+bool vulkan_make_output(); - - std::shared_ptr vulkan_create_texture_from_dmabuf( struct wlr_dmabuf_attributes *pDMA ); - std::shared_ptr 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 ); - -From dc81258cec01ad137056191b8db990adcbba6341 Mon Sep 17 00:00:00 2001 -From: LuK1337 -Date: Sat, 20 Jan 2024 10:21:47 +0100 -Subject: [PATCH 085/134] readme: Remove dead shortcut - -This shortcut was removed in 371fcf0. ---- - README.md | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/README.md b/README.md -index 128089123..a56075a91 100644 ---- a/README.md -+++ b/README.md -@@ -40,7 +40,6 @@ meson install -C build/ --skip-subprojects - * **Super + O** : Decrease FSR sharpness by 1 - * **Super + S** : Take screenshot (currently goes to `/tmp/gamescope_$DATE.png`) - * **Super + G** : Toggle keyboard grab --* **Super + C** : Update clipboard - - ## Examples - - -From 6b89dc2f615ce1bc39470e9c330c3f66a4d980e2 Mon Sep 17 00:00:00 2001 -From: Simon Ser -Date: Sun, 28 Jan 2024 15:11:05 +0100 -Subject: [PATCH 086/134] Centralize wlroots include guards - ---- - src/drm.cpp | 4 ++-- - src/drm.hpp | 12 +++--------- - src/ime.cpp | 6 ++---- - src/rendervulkan.hpp | 6 ++---- - src/steamcompmgr.hpp | 7 +++---- - src/wlr_begin.hpp | 7 +++++++ - src/wlr_end.hpp | 5 +++++ - src/wlserver.cpp | 10 ++-------- - 8 files changed, 26 insertions(+), 31 deletions(-) - create mode 100644 src/wlr_begin.hpp - create mode 100644 src/wlr_end.hpp - -diff --git a/src/drm.cpp b/src/drm.cpp -index 717a5299f..6272238f5 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -14,9 +14,9 @@ - #include - #include - --extern "C" { -+#include "wlr_begin.hpp" - #include --} -+#include "wlr_end.hpp" - - #include "drm.hpp" - #include "defer.hpp" -diff --git a/src/drm.hpp b/src/drm.hpp -index 1ab4fc387..048b84a8f 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -7,6 +7,7 @@ - #include - #include - #include -+#include - - #include - -@@ -44,11 +45,6 @@ inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) - colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; - } - --extern "C" --{ -- struct wl_resource; --} -- - extern struct drm_t g_DRM; - void drm_destroy_blob(struct drm_t *drm, uint32_t blob); - -@@ -111,13 +107,11 @@ struct wlserver_ctm : drm_blob - glm::mat3x4 matrix{}; - }; - --#include -- --extern "C" { -+#include "wlr_begin.hpp" - #include - #include - #include --} -+#include "wlr_end.hpp" - - #include "rendervulkan.hpp" - -diff --git a/src/ime.cpp b/src/ime.cpp -index c72e8ae7a..0c8f082aa 100644 ---- a/src/ime.cpp -+++ b/src/ime.cpp -@@ -12,13 +12,11 @@ - - #include - --extern "C" { --#define delete delete_ -+#include "wlr_begin.hpp" - #include - #include - #include --#undef delete --} -+#include "wlr_end.hpp" - - #include "gamescope-input-method-protocol.h" - -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index 98b3936f1..992438d53 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -64,12 +64,10 @@ enum EStreamColorspace : int - #include - #include - --extern "C" { --#define static -+#include "wlr_begin.hpp" - #include - #include --#undef static --} -+#include "wlr_end.hpp" - - #define VK_NO_PROTOTYPES - #include -diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp -index d1a209d48..2429ee066 100644 ---- a/src/steamcompmgr.hpp -+++ b/src/steamcompmgr.hpp -@@ -1,9 +1,10 @@ - #include - --extern "C" { -+#include "wlr_begin.hpp" - #include - #include --} -+#include -+#include "wlr_end.hpp" - - extern uint32_t currentOutputWidth; - extern uint32_t currentOutputHeight; -@@ -23,8 +24,6 @@ void steamcompmgr_main(int argc, char **argv); - #include - #include - --#include -- - #include - - struct _XDisplay; -diff --git a/src/wlr_begin.hpp b/src/wlr_begin.hpp -new file mode 100644 -index 000000000..c0936fc9b ---- /dev/null -+++ b/src/wlr_begin.hpp -@@ -0,0 +1,7 @@ -+#include -+ -+extern "C" { -+#define static -+#define class class_ -+#define namespace _namespace -+#define delete delete_ -diff --git a/src/wlr_end.hpp b/src/wlr_end.hpp -new file mode 100644 -index 000000000..b1afdff37 ---- /dev/null -+++ b/src/wlr_end.hpp -@@ -0,0 +1,5 @@ -+#undef static -+#undef class -+#undef namespace -+#undef delete -+} -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 4644f667f..cc5e68be0 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -14,11 +14,7 @@ - #include - #include - --#include -- --extern "C" { --#define static --#define class class_ -+#include "wlr_begin.hpp" - #include - #include - #include -@@ -33,9 +29,7 @@ extern "C" { - #include - #include - #include --#undef static --#undef class --} -+#include "wlr_end.hpp" - - #include "gamescope-xwayland-protocol.h" - #include "gamescope-pipewire-protocol.h" - -From 1bd20cebd2980e629161669caec5c5306c63da70 Mon Sep 17 00:00:00 2001 -From: Etaash Mathamsetty -Date: Sat, 27 Jan 2024 22:02:35 -0500 -Subject: [PATCH 087/134] Support to put nested window on a certain display - ---- - src/main.cpp | 5 +++++ - src/main.hpp | 1 + - src/sdlwindow.cpp | 4 ++-- - 3 files changed, 8 insertions(+), 2 deletions(-) - -diff --git a/src/main.cpp b/src/main.cpp -index 1ec1f48e3..4bf7c3336 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -72,6 +72,7 @@ const struct option *gamescope_options = (struct option[]){ - { "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 }, -@@ -190,6 +191,7 @@ const char usage[] = - " -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\n" -@@ -255,6 +257,7 @@ int g_nNestedWidth = 0; - int g_nNestedHeight = 0; - int g_nNestedRefresh = 0; - int g_nNestedUnfocusedRefresh = 0; -+int g_nNestedDisplayIndex = 0; - - uint32_t g_nOutputWidth = 0; - uint32_t g_nOutputHeight = 0; -@@ -641,6 +644,8 @@ int main(int argc, char **argv) - g_nAsyncFlipsEnabled = 1; - } else if (strcmp(opt_name, "force-grab-cursor") == 0) { - g_bForceRelativeMouse = true; -+ } else if (strcmp(opt_name, "display-index") == 0) { -+ g_nNestedDisplayIndex = atoi( optarg ); - } else if (strcmp(opt_name, "adaptive-sync") == 0) { - s_bInitialWantsVRREnabled = true; - } else if (strcmp(opt_name, "expose-wayland") == 0) { -diff --git a/src/main.hpp b/src/main.hpp -index 7cf1c4088..b3de6b847 100644 ---- a/src/main.hpp -+++ b/src/main.hpp -@@ -13,6 +13,7 @@ extern int g_nNestedWidth; - extern int g_nNestedHeight; - extern int g_nNestedRefresh; // Hz - extern int g_nNestedUnfocusedRefresh; // Hz -+extern int g_nNestedDisplayIndex; - - extern uint32_t g_nOutputWidth; - extern uint32_t g_nOutputHeight; -diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp -index 78912e15a..1e51eab6c 100644 ---- a/src/sdlwindow.cpp -+++ b/src/sdlwindow.cpp -@@ -136,8 +136,8 @@ void inputSDLThreadRun( void ) - } - - g_SDLWindow = SDL_CreateWindow( DEFAULT_TITLE, -- SDL_WINDOWPOS_UNDEFINED, -- SDL_WINDOWPOS_UNDEFINED, -+ SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), -+ SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), - g_nOutputWidth, - g_nOutputHeight, - nSDLWindowFlags ); - -From 6309cddde5c2d7339e49546f0e32203fedd51697 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 28 Jan 2024 22:39:36 +0000 -Subject: [PATCH 088/134] drm: Some more refactoring - ---- - src/color_helpers.h | 2 + - src/drm.hpp | 126 ++++++----------------------------------- - src/drm_include.h | 73 ++++++++++++++++++++++++ - src/gamescope_shared.h | 16 ++++++ - src/rendervulkan.hpp | 7 ++- - 5 files changed, 113 insertions(+), 111 deletions(-) - create mode 100644 src/drm_include.h - -diff --git a/src/color_helpers.h b/src/color_helpers.h -index 51aaedc74..591119015 100644 ---- a/src/color_helpers.h -+++ b/src/color_helpers.h -@@ -1,5 +1,7 @@ - #pragma once - -+#define GLM_ENABLE_EXPERIMENTAL 1 -+ - #include - #include - #include -diff --git a/src/drm.hpp b/src/drm.hpp -index 048b84a8f..1c64f3c6e 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -1,49 +1,26 @@ --// DRM output stuff -- - #pragma once - --#include --#include --#include --#include --#include --#include -- --#include -- -+#include "drm_include.h" - #include "color_helpers.h" - #include "gamescope_shared.h" -+#include "rendervulkan.hpp" - --// 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, --}; -- --enum drm_color_range { -- DRM_COLOR_YCBCR_LIMITED_RANGE, -- DRM_COLOR_YCBCR_FULL_RANGE, -- DRM_COLOR_RANGE_MAX, --}; -- --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, --}; --const uint32_t GamescopeAppTextureColorspace_Bits = 3; -+#include "wlr_begin.hpp" -+#include -+#include -+#include -+#include "wlr_end.hpp" - --inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) --{ -- return colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_SCRGB || -- colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; --} -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include - - extern struct drm_t g_DRM; - void drm_destroy_blob(struct drm_t *drm, uint32_t blob); -@@ -106,24 +83,6 @@ struct wlserver_ctm : drm_blob - - glm::mat3x4 matrix{}; - }; -- --#include "wlr_begin.hpp" --#include --#include --#include --#include "wlr_end.hpp" -- --#include "rendervulkan.hpp" -- --#include --#include --#include --#include --#include --#include --#include --#include -- - namespace gamescope - { - template -@@ -407,21 +366,6 @@ struct fb { - std::atomic< uint32_t > n_refs; - }; - --enum drm_valve1_transfer_function { -- DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, -- -- DRM_VALVE1_TRANSFER_FUNCTION_SRGB, -- DRM_VALVE1_TRANSFER_FUNCTION_BT709, -- DRM_VALVE1_TRANSFER_FUNCTION_PQ, -- DRM_VALVE1_TRANSFER_FUNCTION_LINEAR, -- DRM_VALVE1_TRANSFER_FUNCTION_UNITY, -- DRM_VALVE1_TRANSFER_FUNCTION_HLG, -- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA22, -- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA24, -- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA26, -- DRM_VALVE1_TRANSFER_FUNCTION_MAX, --}; -- - struct drm_t { - bool bUseLiftoff; - -@@ -572,42 +516,6 @@ std::span drm_get_valid_refresh_rates( struct drm_t *drm ); - - extern bool g_bSupportsAsyncFlips; - --/* from CTA-861-G */ --#define HDMI_EOTF_SDR 0 --#define HDMI_EOTF_TRADITIONAL_HDR 1 --#define HDMI_EOTF_ST2084 2 --#define HDMI_EOTF_HLG 3 -- --/* For Default case, driver will set the colorspace */ --#define DRM_MODE_COLORIMETRY_DEFAULT 0 --/* CEA 861 Normal Colorimetry options */ --#define DRM_MODE_COLORIMETRY_NO_DATA 0 --#define DRM_MODE_COLORIMETRY_SMPTE_170M_YCC 1 --#define DRM_MODE_COLORIMETRY_BT709_YCC 2 --/* CEA 861 Extended Colorimetry Options */ --#define DRM_MODE_COLORIMETRY_XVYCC_601 3 --#define DRM_MODE_COLORIMETRY_XVYCC_709 4 --#define DRM_MODE_COLORIMETRY_SYCC_601 5 --#define DRM_MODE_COLORIMETRY_OPYCC_601 6 --#define DRM_MODE_COLORIMETRY_OPRGB 7 --#define DRM_MODE_COLORIMETRY_BT2020_CYCC 8 --#define DRM_MODE_COLORIMETRY_BT2020_RGB 9 --#define DRM_MODE_COLORIMETRY_BT2020_YCC 10 --/* Additional Colorimetry extension added as part of CTA 861.G */ --#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 11 --#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER 12 --/* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format */ --#define DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED 13 --#define DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT 14 --#define DRM_MODE_COLORIMETRY_BT601_YCC 15 -- --/* 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 -- - const char* drm_get_patched_edid_path(); - void drm_update_patched_edid(drm_t *drm); - -diff --git a/src/drm_include.h b/src/drm_include.h -new file mode 100644 -index 000000000..cf4a7cb5c ---- /dev/null -+++ b/src/drm_include.h -@@ -0,0 +1,73 @@ -+#pragma once -+ -+#include -+#include -+#include -+#include -+ -+// 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, -+}; -+ -+enum drm_color_range { -+ DRM_COLOR_YCBCR_LIMITED_RANGE, -+ DRM_COLOR_YCBCR_FULL_RANGE, -+ DRM_COLOR_RANGE_MAX, -+}; -+ -+enum drm_valve1_transfer_function { -+ DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, -+ -+ DRM_VALVE1_TRANSFER_FUNCTION_SRGB, -+ DRM_VALVE1_TRANSFER_FUNCTION_BT709, -+ DRM_VALVE1_TRANSFER_FUNCTION_PQ, -+ DRM_VALVE1_TRANSFER_FUNCTION_LINEAR, -+ DRM_VALVE1_TRANSFER_FUNCTION_UNITY, -+ DRM_VALVE1_TRANSFER_FUNCTION_HLG, -+ DRM_VALVE1_TRANSFER_FUNCTION_GAMMA22, -+ DRM_VALVE1_TRANSFER_FUNCTION_GAMMA24, -+ DRM_VALVE1_TRANSFER_FUNCTION_GAMMA26, -+ DRM_VALVE1_TRANSFER_FUNCTION_MAX, -+}; -+ -+/* from CTA-861-G */ -+#define HDMI_EOTF_SDR 0 -+#define HDMI_EOTF_TRADITIONAL_HDR 1 -+#define HDMI_EOTF_ST2084 2 -+#define HDMI_EOTF_HLG 3 -+ -+/* For Default case, driver will set the colorspace */ -+#define DRM_MODE_COLORIMETRY_DEFAULT 0 -+/* CEA 861 Normal Colorimetry options */ -+#define DRM_MODE_COLORIMETRY_NO_DATA 0 -+#define DRM_MODE_COLORIMETRY_SMPTE_170M_YCC 1 -+#define DRM_MODE_COLORIMETRY_BT709_YCC 2 -+/* CEA 861 Extended Colorimetry Options */ -+#define DRM_MODE_COLORIMETRY_XVYCC_601 3 -+#define DRM_MODE_COLORIMETRY_XVYCC_709 4 -+#define DRM_MODE_COLORIMETRY_SYCC_601 5 -+#define DRM_MODE_COLORIMETRY_OPYCC_601 6 -+#define DRM_MODE_COLORIMETRY_OPRGB 7 -+#define DRM_MODE_COLORIMETRY_BT2020_CYCC 8 -+#define DRM_MODE_COLORIMETRY_BT2020_RGB 9 -+#define DRM_MODE_COLORIMETRY_BT2020_YCC 10 -+/* Additional Colorimetry extension added as part of CTA 861.G */ -+#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 11 -+#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER 12 -+/* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format */ -+#define DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED 13 -+#define DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT 14 -+#define DRM_MODE_COLORIMETRY_BT601_YCC 15 -+ -+/* 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 -diff --git a/src/gamescope_shared.h b/src/gamescope_shared.h -index 523f58ed9..fdbcfa481 100644 ---- a/src/gamescope_shared.h -+++ b/src/gamescope_shared.h -@@ -25,3 +25,19 @@ 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, -+}; -+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; -+} -+ -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index 992438d53..59e986d3f 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -13,11 +13,16 @@ - #include - - #include "main.hpp" -+#include "color_helpers.h" -+#include "gamescope_shared.h" - - #include "shaders/descriptor_set_constants.h" - - class CVulkanCmdBuffer; - -+struct wlserver_ctm; -+struct wlserver_hdr_metadata; -+ - // 1: Fade Plane (Fade outs between switching focus) - // 2: Video Underlay (The actual video) - // 3: Video Streaming UI (Game, App) -@@ -56,8 +61,6 @@ enum EStreamColorspace : int - k_EStreamColorspace_BT709_Full = 4 - }; - --#include "drm.hpp" -- - #include - #include - #include - -From e8f3b355875dbd1eb5e9cff164428d6f733023b7 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 29 Jan 2024 02:15:40 +0000 -Subject: [PATCH 089/134] drm: Refactor out VRR state from DRM - ---- - src/drm.cpp | 41 +++++++++++++++-------------------------- - src/drm.hpp | 6 +----- - src/main.cpp | 6 +++--- - src/rendervulkan.hpp | 1 + - src/steamcompmgr.cpp | 7 +++++-- - 5 files changed, 25 insertions(+), 36 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 6272238f5..000633fbc 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -971,13 +971,12 @@ bool env_to_bool(const char *env) - return !!atoi(env); - } - --bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_adaptive_sync) -+bool init_drm(struct drm_t *drm, int width, int height, int refresh) - { - load_pnps(); - - drm->bUseLiftoff = true; - -- drm->wants_vrr_enabled = wants_adaptive_sync; - drm->preferred_width = width; - drm->preferred_height = height; - drm->preferred_refresh = refresh; -@@ -2535,9 +2534,17 @@ bool g_bForceAsyncFlips = false; - * 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 ) - { -- drm_update_vrr_state(drm); - drm_update_color_mgmt(drm); - -+ const bool bVRRCapable = drm->pConnector && drm->pConnector->GetProperties().vrr_capable && -+ drm->pCRTC && drm->pCRTC->GetProperties().VRR_ENABLED; -+ const bool bVRREnabled = bVRRCapable && frameInfo->allowVRR; -+ if ( bVRRCapable ) -+ { -+ if ( bVRREnabled != !!drm->pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue() ) -+ drm->needs_modeset = true; -+ } -+ - drm->fbids_in_req.clear(); - - bool needs_modeset = drm->needs_modeset.exchange(false); -@@ -2664,7 +2671,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - drm->pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, drm->pending.mode_id ? drm->pending.mode_id->blob : 0lu, true ); - - if ( drm->pCRTC->GetProperties().VRR_ENABLED ) -- drm->pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, drm->pending.vrr_enabled, true ); -+ drm->pCRTC->GetProperties().VRR_ENABLED->SetPendingValue( drm->req, bVRREnabled, true ); - } - } - -@@ -2788,14 +2795,12 @@ static void drm_unset_connector( struct drm_t *drm ) - drm->needs_modeset = true; - } - --void drm_set_vrr_enabled(struct drm_t *drm, bool enabled) --{ -- drm->wants_vrr_enabled = enabled; --} -- - bool drm_get_vrr_in_use(struct drm_t *drm) - { -- return drm->current.vrr_enabled; -+ if ( !drm->pCRTC || !drm->pCRTC->GetProperties().VRR_ENABLED ) -+ return false; -+ -+ return !!drm->pCRTC->GetProperties().VRR_ENABLED->GetCurrentValue(); - } - - gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm) -@@ -2845,22 +2850,6 @@ bool drm_update_color_mgmt(struct drm_t *drm) - return true; - } - --bool drm_update_vrr_state(struct drm_t *drm) --{ -- drm->pending.vrr_enabled = false; -- -- if ( drm->pConnector && drm->pCRTC && drm->pCRTC->GetProperties().VRR_ENABLED ) -- { -- if ( drm->wants_vrr_enabled && drm->pConnector->IsVRRCapable() ) -- drm->pending.vrr_enabled = true; -- } -- -- if (drm->pending.vrr_enabled != drm->current.vrr_enabled) -- drm->needs_modeset = true; -- -- return true; --} -- - static void drm_unset_mode( struct drm_t *drm ) - { - drm->pending.mode_id = 0; -diff --git a/src/drm.hpp b/src/drm.hpp -index 1c64f3c6e..3271c088d 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -405,11 +405,8 @@ struct drm_t { - uint32_t color_mgmt_serial; - std::shared_ptr lut3d_id[ EOTF_Count ]; - std::shared_ptr shaperlut_id[ EOTF_Count ]; -- // TODO: Remove me, this should be some generic setting. -- bool vrr_enabled = false; - drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; - } current, pending; -- bool wants_vrr_enabled = false; - - /* FBs in the atomic request, but not yet submitted to KMS */ - std::vector < uint32_t > fbids_in_req; -@@ -474,7 +471,7 @@ extern std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCRE - - extern bool g_bForceDisableColorMgmt; - --bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_adaptive_sync); -+bool init_drm(struct drm_t *drm, int width, int height, int refresh); - void finish_drm(struct drm_t *drm); - int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); - int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ); -@@ -495,7 +492,6 @@ char *find_drm_node_by_devid(dev_t devid); - int drm_get_default_refresh(struct drm_t *drm); - bool drm_get_vrr_capable(struct drm_t *drm); - bool drm_supports_hdr(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); --void drm_set_vrr_enabled(struct drm_t *drm, bool enabled); - bool drm_get_vrr_in_use(struct drm_t *drm); - bool drm_supports_color_mgmt(struct drm_t *drm); - std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata); -diff --git a/src/main.cpp b/src/main.cpp -index 4bf7c3336..02cc5ce3a 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -39,7 +39,7 @@ - using namespace std::literals; - - EStreamColorspace g_ForcedNV12ColorSpace = k_EStreamColorspace_Unknown; --static bool s_bInitialWantsVRREnabled = false; -+extern bool g_bAllowVRR; - - const char *gamescope_optstring = nullptr; - const char *g_pOriginalDisplay = nullptr; -@@ -647,7 +647,7 @@ int main(int argc, char **argv) - } else if (strcmp(opt_name, "display-index") == 0) { - g_nNestedDisplayIndex = atoi( optarg ); - } else if (strcmp(opt_name, "adaptive-sync") == 0) { -- s_bInitialWantsVRREnabled = true; -+ g_bAllowVRR = true; - } else if (strcmp(opt_name, "expose-wayland") == 0) { - g_bExposeWayland = true; - } else if (strcmp(opt_name, "headless") == 0) { -@@ -976,6 +976,6 @@ static bool initOutput( int preferredWidth, int preferredHeight, int preferredRe - return false; - } - -- return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh, s_bInitialWantsVRREnabled ); -+ return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh ); - } - } -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index 59e986d3f..f090344ae 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -270,6 +270,7 @@ struct FrameInfo_t - std::shared_ptr shaperLut[EOTF_Count]; - std::shared_ptr lut3D[EOTF_Count]; - -+ bool allowVRR; - bool applyOutputColorMgmt; // drm only - EOTF outputEncodingEOTF; - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 1bbc1dce8..d3f0ef42c 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -172,6 +172,8 @@ timespec nanos_to_timespec( uint64_t ulNanos ) - static void - update_runtime_info(); - -+bool g_bAllowVRR = false; -+ - static uint64_t g_SteamCompMgrLimitedAppRefreshCycle = 16'666'666; - static uint64_t g_SteamCompMgrAppRefreshCycle = 16'666'666; - -@@ -2463,6 +2465,7 @@ paint_all(bool async) - struct FrameInfo_t frameInfo = {}; - frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; - frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; -+ frameInfo.allowVRR = g_bAllowVRR; - - // 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 -@@ -5917,7 +5920,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - if ( ev->atom == ctx->atoms.gamescopeVRREnabled ) - { - bool enabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeVRREnabled, 0 ); -- drm_set_vrr_enabled( &g_DRM, enabled ); -+ g_bAllowVRR = enabled; - } - if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal ) - { -@@ -7656,7 +7659,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = - // Keep this as a preference, starting with off. - if ( force ) - { -- bool wants_vrr = g_DRM.wants_vrr_enabled; -+ bool wants_vrr = g_bAllowVRR; - 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 ); - -From 9e46c89ffc56c0bc6359ef4269274804f0c9d382 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 29 Jan 2024 02:35:16 +0000 -Subject: [PATCH 090/134] drm: Refactor HDR10 decisionmaking - ---- - src/drm.cpp | 42 +++++++++++++++++++++--------------------- - src/drm.hpp | 30 +++++++++++++++++++----------- - src/steamcompmgr.cpp | 8 -------- - 3 files changed, 40 insertions(+), 40 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 000633fbc..dc232620f 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -55,6 +55,10 @@ const char *g_sOutputName = nullptr; - #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 - #endif - -+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 ); -+ - struct drm_color_ctm2 { - /* - * Conversion matrix in S31.32 sign-magnitude -@@ -2545,27 +2549,35 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - drm->needs_modeset = true; - } - -- drm->fbids_in_req.clear(); -- -- bool needs_modeset = drm->needs_modeset.exchange(false); -- -- assert( drm->req == nullptr ); -- drm->req = drmModeAtomicAlloc(); -+ uint32_t uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; - -+ const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; - wlserver_hdr_metadata *pHDRMetadata = nullptr; -- if ( drm->pConnector && drm->pConnector->GetHDRInfo().IsHDR10() ) -+ if ( drm->pConnector && drm->pConnector->SupportsHDR10() ) - { -- if ( g_bOutputHDREnabled ) -+ if ( bWantsHDR10 ) - { - wlserver_vk_swapchain_feedback* pFeedback = steamcompmgr_get_base_layer_swapchain_feedback(); - pHDRMetadata = pFeedback ? pFeedback->hdr_metadata_blob.get() : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.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->fbids_in_req.clear(); -+ -+ bool needs_modeset = drm->needs_modeset.exchange(false); -+ -+ assert( drm->req == nullptr ); -+ drm->req = drmModeAtomicAlloc(); -+ - bool bSinglePlane = frameInfo->layerCount < 2 && g_bSinglePlaneOptimizations; - - if ( drm_supports_color_mgmt( &g_DRM ) && frameInfo->applyOutputColorMgmt ) -@@ -2657,12 +2669,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - drm->pConnector->GetProperties().CRTC_ID->SetPendingValue( drm->req, drm->pCRTC->GetObjectId(), bForceInRequest ); - - if ( drm->pConnector->GetProperties().Colorspace ) -- { -- uint32_t uColorimetry = g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() -- ? DRM_MODE_COLORIMETRY_BT2020_RGB -- : DRM_MODE_COLORIMETRY_DEFAULT; - drm->pConnector->GetProperties().Colorspace->SetPendingValue( drm->req, uColorimetry, bForceInRequest ); -- } - } - - if ( drm->pCRTC ) -@@ -3004,7 +3011,7 @@ bool drm_get_vrr_capable(struct drm_t *drm) - - bool drm_supports_hdr( struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL ) - { -- if ( drm->pConnector && drm->pConnector->GetHDRInfo().SupportsHDR() ) -+ if ( drm->pConnector && drm->pConnector->SupportsHDR() ) - { - if ( maxCLL ) - *maxCLL = drm->pConnector->GetHDRInfo().uMaxContentLightLevel; -@@ -3016,13 +3023,6 @@ bool drm_supports_hdr( struct drm_t *drm, uint16_t *maxCLL, uint16_t *maxFALL ) - return false; - } - --void drm_set_hdr_state(struct drm_t *drm, bool enabled) { -- if (drm->enable_hdr != enabled) { -- drm->needs_modeset = true; -- drm->enable_hdr = enabled; -- } --} -- - const char *drm_get_connector_name(struct drm_t *drm) - { - if ( !drm->pConnector ) -diff --git a/src/drm.hpp b/src/drm.hpp -index 3271c088d..79c88050a 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -269,23 +269,21 @@ namespace gamescope - uint16_t uMinContentLightLevel = 0; // Nits / 10000 - std::shared_ptr pDefaultMetadataBlob; - -- bool ShouldPatchEDID() const -+ bool IsHDRG22() const - { - return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_Gamma22; - } - -- bool SupportsHDR() const -+ bool ShouldPatchEDID() const - { -- // Note: Different to IsHDR10, as we can expose HDR10 on G2.2 displays -- // using LUTs and CTMs. -- return bExposeHDRSupport; -+ return IsHDRG22(); - } - - bool IsHDR10() const - { - // PQ output encoding is always HDR10 (PQ + 2020) for us. - // If that assumption changes, update me. -- return eOutputEncodingEOTF == EOTF_PQ; -+ return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_PQ; - } - }; - -@@ -317,6 +315,21 @@ namespace gamescope - // TODO: Remove - void SetBaseRefresh( int nRefresh ) { m_nBaseRefresh = nRefresh; } - int GetBaseRefresh() const { return m_nBaseRefresh; } -+ -+ bool SupportsHDR10() const -+ { -+ return !!GetProperties().Colorspace && !!GetProperties().HDR_OUTPUT_METADATA && GetHDRInfo().IsHDR10(); -+ } -+ -+ bool SupportsHDRG22() const -+ { -+ return GetHDRInfo().IsHDRG22(); -+ } -+ -+ bool SupportsHDR() const -+ { -+ return SupportsHDR10() || SupportsHDRG22(); -+ } - private: - void ParseEDID(); - -@@ -433,7 +446,6 @@ struct drm_t { - std::unordered_map< std::string, int > connector_priorities; - - bool force_internal = false; -- bool enable_hdr = false; - - char *device_name = nullptr; - }; -@@ -480,12 +492,9 @@ uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct - void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); - void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); - void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); --bool drm_set_connector( struct drm_t *drm, gamescope::CDRMConnector *conn ); - bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); - bool drm_set_refresh( struct drm_t *drm, int refresh ); - bool drm_set_resolution( struct drm_t *drm, int width, int height ); --bool drm_update_color_mgmt(struct drm_t *drm); --bool drm_update_vrr_state(struct drm_t *drm); - gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm); - - char *find_drm_node_by_devid(dev_t devid); -@@ -502,7 +511,6 @@ const char *drm_get_connector_name(struct drm_t *drm); - const char *drm_get_device_name(struct drm_t *drm); - - std::pair drm_get_connector_identifier(struct drm_t *drm); --void drm_set_hdr_state(struct drm_t *drm, bool enabled); - - void drm_get_native_colorimetry( struct drm_t *drm, - displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index d3f0ef42c..49aa6fbd3 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -8032,14 +8032,6 @@ steamcompmgr_main(int argc, char **argv) - } - else - { -- if ( !BIsNested() ) -- { -- if (g_bOutputHDREnabled != currentHDROutput) -- { -- drm_set_hdr_state(&g_DRM, g_bOutputHDREnabled); -- } -- } -- - vulkan_remake_output_images(); - } - - -From 88eb1b477d8b1efbe6d7087dcde74052dad84049 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 09:08:35 +0000 -Subject: [PATCH 091/134] all: Refactor and add a generic backend interface - -Part 1 of a major refactor to make this more generic. ---- - src/backend.cpp | 60 ++ - src/backend.h | 284 +++++ - src/backends.h | 20 + - src/color_bench.cpp | 12 +- - src/color_helpers.h | 3 +- - src/color_tests.cpp | 12 +- - src/drm.cpp | 2040 +++++++++++++++++++++-------------- - src/drm.hpp | 526 --------- - src/drm_include.h | 19 +- - src/edid.cpp | 296 +++++ - src/edid.h | 16 + - src/gamescope_shared.h | 24 + - src/hdmi.h | 7 + - src/headless.cpp | 248 +++++ - src/main.cpp | 245 ++--- - src/main.hpp | 7 +- - src/meson.build | 3 + - src/rendervulkan.cpp | 230 ++-- - src/rendervulkan.hpp | 25 +- - src/sdlscancodetable.hpp | 24 +- - src/sdlwindow.cpp | 1274 ++++++++++++++-------- - src/sdlwindow.hpp | 23 - - src/steamcompmgr.cpp | 802 ++++---------- - src/steamcompmgr.hpp | 7 +- - src/steamcompmgr_shared.hpp | 4 +- - src/vblankmanager.cpp | 80 +- - src/vblankmanager.hpp | 6 +- - src/vr_session.cpp | 1037 +++++++++++------- - src/vr_session.hpp | 31 - - src/waitable.h | 1 + - src/wlserver.cpp | 96 +- - src/wlserver.hpp | 8 +- - src/xwayland_ctx.hpp | 3 +- - 33 files changed, 4223 insertions(+), 3250 deletions(-) - create mode 100644 src/backend.cpp - create mode 100644 src/backend.h - create mode 100644 src/backends.h - delete mode 100644 src/drm.hpp - create mode 100644 src/edid.cpp - create mode 100644 src/edid.h - create mode 100644 src/hdmi.h - create mode 100644 src/headless.cpp - delete mode 100644 src/sdlwindow.hpp - delete mode 100644 src/vr_session.hpp - -diff --git a/src/backend.cpp b/src/backend.cpp -new file mode 100644 -index 000000000..b8eeaa9a9 ---- /dev/null -+++ b/src/backend.cpp -@@ -0,0 +1,60 @@ -+#include "backend.h" -+#include "vblankmanager.hpp" -+ -+extern void sleep_until_nanos(uint64_t nanos); -+extern bool env_to_bool(const char *env); -+ -+namespace gamescope -+{ -+ ///////////// -+ // IBackend -+ ///////////// -+ -+ static IBackend *s_pBackend = nullptr; -+ -+ IBackend *IBackend::Get() -+ { -+ return s_pBackend; -+ } -+ -+ bool IBackend::Set( IBackend *pBackend ) -+ { -+ if ( s_pBackend ) -+ { -+ delete s_pBackend; -+ s_pBackend = nullptr; -+ } -+ -+ s_pBackend = pBackend; -+ if ( !s_pBackend->Init() ) -+ { -+ delete s_pBackend; -+ s_pBackend = nullptr; -+ return false; -+ } -+ -+ return true; -+ } -+ -+ ///////////////// -+ // CBaseBackend -+ ///////////////// -+ -+ bool CBaseBackend::NeedsFrameSync() const -+ { -+ const bool bForceTimerFd = env_to_bool( getenv( "GAMESCOPE_DISABLE_TIMERFD" ) ); -+ return bForceTimerFd; -+ } -+ -+ INestedHints *CBaseBackend::GetNestedHints() -+ { -+ return nullptr; -+ } -+ -+ VBlankScheduleTime CBaseBackend::FrameSync() -+ { -+ VBlankScheduleTime schedule = GetVBlankTimer().CalcNextWakeupTime( false ); -+ sleep_until_nanos( schedule.ulScheduledWakeupPoint ); -+ return schedule; -+ } -+} -diff --git a/src/backend.h b/src/backend.h -new file mode 100644 -index 000000000..37e93345b ---- /dev/null -+++ b/src/backend.h -@@ -0,0 +1,284 @@ -+#pragma once -+ -+#include "color_helpers.h" -+#include "gamescope_shared.h" -+ -+#include "vulkan/vulkan_core.h" -+#include -+#include -+#include -+#include -+#include -+#include -+ -+struct wlr_buffer; -+struct wlr_dmabuf_attributes; -+ -+struct FrameInfo_t; -+ -+namespace gamescope -+{ -+ struct VBlankScheduleTime; -+ class BackendBlob; -+ -+ 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. -+ bool bExposeHDRSupport = 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. -+ EOTF eOutputEncodingEOTF = EOTF_Gamma22; -+ -+ 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 ShouldPatchEDID() const -+ { -+ return IsHDRG22(); -+ } -+ -+ bool IsHDR10() const -+ { -+ // PQ output encoding is always HDR10 (PQ + 2020) for us. -+ // If that assumption changes, update me. -+ return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_PQ; -+ } -+ }; -+ -+ struct BackendMode -+ { -+ uint32_t uWidth; -+ uint32_t uHeight; -+ uint32_t uRefresh; // Hz -+ }; -+ -+ class IBackendConnector -+ { -+ public: -+ virtual ~IBackendConnector() {} -+ -+ 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 std::span GetModes() const = 0; -+ -+ virtual bool SupportsVRR() 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; -+ -+ virtual const char *GetName() const = 0; -+ virtual const char *GetMake() const = 0; -+ virtual const char *GetModel() const = 0; -+ }; -+ -+ class INestedHints -+ { -+ public: -+ virtual ~INestedHints() {} -+ -+ struct CursorInfo -+ { -+ std::vector pPixels; -+ 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 std::optional GetHostCursor() = 0; -+ }; -+ -+ struct BackendPresentFeedback -+ { -+ public: -+ 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(); } -+ -+ std::atomic m_uQueuedPresents = { 0u }; -+ std::atomic m_uCompletedPresents = { 0u }; -+ }; -+ -+ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const = 0; -+ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const = 0; -+ -+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) = 0; -+ virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) = 0; -+ virtual bool PollState() = 0; -+ -+ virtual std::shared_ptr CreateBackendBlob( 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( std::span( pBegin, pEnd ) ); -+ } -+ -+ // For DRM, this is -+ // dmabuf -> fb_id. -+ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) = 0; -+ virtual void LockBackendFb( uint32_t uFbId ) = 0; -+ virtual void UnlockBackendFb( uint32_t uFbId ) = 0; -+ virtual void DropBackendFb( uint32_t uFbId ) = 0; -+ -+ virtual bool UsesModifiers() const = 0; -+ virtual std::span GetSupportedModifiers( uint32_t uDrmFormat ) const = 0; -+ -+ virtual IBackendConnector *GetCurrentConnector() = 0; -+ virtual IBackendConnector *GetConnector( GamescopeScreenType eScreenType ) = 0; -+ -+ // Might want to move this to connector someday, but it lives in CRTC. -+ virtual bool IsVRRActive() const = 0; -+ -+ virtual bool SupportsPlaneHardwareCursor() const = 0; -+ virtual bool SupportsTearing() const = 0; -+ -+ virtual bool UsesVulkanSwapchain() const = 0; -+ virtual bool IsSessionBased() const = 0; -+ -+ // Dumb helper we should remove to support multi display someday. -+ gamescope::GamescopeScreenType GetScreenType() -+ { -+ if ( GetCurrentConnector() ) -+ return GetCurrentConnector()->GetScreenType(); -+ -+ return gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL; -+ } -+ -+ virtual bool IsVisible() const = 0; -+ virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const = 0; -+ -+ virtual INestedHints *GetNestedHints() = 0; -+ -+ // This will move to the connector and be deprecated soon. -+ virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) = 0; -+ virtual void HackUpdatePatchedEdid() = 0; -+ -+ virtual bool NeedsFrameSync() const = 0; -+ virtual VBlankScheduleTime FrameSync() = 0; -+ -+ // TODO: Make me const someday. -+ virtual BackendPresentFeedback& PresentationFeedback() = 0; -+ -+ static IBackend *Get(); -+ template -+ static bool Set(); -+ protected: -+ friend BackendBlob; -+ -+ virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) = 0; -+ private: -+ static bool Set( IBackend *pBackend ); -+ }; -+ -+ -+ class CBaseBackend : public IBackend -+ { -+ public: -+ virtual INestedHints *GetNestedHints() override; -+ -+ virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override { return false; } -+ virtual void HackUpdatePatchedEdid() override {} -+ -+ virtual bool NeedsFrameSync() const override; -+ virtual VBlankScheduleTime FrameSync() override; -+ -+ virtual BackendPresentFeedback& PresentationFeedback() override { return m_PresentFeedback; } -+ protected: -+ BackendPresentFeedback m_PresentFeedback{}; -+ }; -+ -+ // This is a blob of data that may be associated with -+ // a backend if it needs to be. -+ // Currently on non-DRM backends this is basically a -+ // no-op. -+ class BackendBlob -+ { -+ public: -+ BackendBlob() -+ { -+ } -+ -+ 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() -+ { -+ if ( m_bOwned ) -+ IBackend::Get()->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 -+ { -+ assert( sizeof( T ) == m_Data.size() ); -+ return *reinterpret_cast( m_Data.data() ); -+ } -+ uint32_t GetBlobValue() const { return m_uBlob; } -+ -+ private: -+ std::vector m_Data; -+ uint32_t m_uBlob = 0; -+ bool m_bOwned = false; -+ }; -+} -+ -+inline gamescope::IBackend *GetBackend() -+{ -+ return gamescope::IBackend::Get(); -+} -+ -diff --git a/src/backends.h b/src/backends.h -new file mode 100644 -index 000000000..11a54bd04 ---- /dev/null -+++ b/src/backends.h -@@ -0,0 +1,20 @@ -+#pragma once -+ -+namespace gamescope -+{ -+ // Backend enum. -+ enum GamescopeBackend -+ { -+ Auto, -+ DRM, -+ SDL, -+ OpenVR, -+ Headless, -+ }; -+ -+ // Backend forward declarations. -+ class CSDLBackend; -+ class CDRMBackend; -+ class COpenVRBackend; -+ class CHeadlessBackend; -+} -diff --git a/src/color_bench.cpp b/src/color_bench.cpp -index bde6dd19c..33dff78c0 100644 ---- a/src/color_bench.cpp -+++ b/src/color_bench.cpp -@@ -40,16 +40,16 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) - colorMapping, nightmode, tonemapping, nullptr, flGain ); - for ( size_t i=0, end = lut1d_float.dataR.size(); i // glm::vec2 - #include // glm::vec3 - #include // glm::mat3 -+#include - - // Color utils - inline int quantize( float fVal, float fMaxVal ) -@@ -17,7 +18,7 @@ inline int quantize( float fVal, float fMaxVal ) - return std::max( 0.f, std::min( fMaxVal, rintf( fVal * fMaxVal ) ) ); - } - --inline uint16_t drm_quantize_lut_value( float flValue ) -+inline uint16_t quantize_lut_value_16bit( float flValue ) - { - return (uint16_t)quantize( flValue, (float)UINT16_MAX ); - } -diff --git a/src/color_tests.cpp b/src/color_tests.cpp -index 61da1b74b..66aae90d7 100644 ---- a/src/color_tests.cpp -+++ b/src/color_tests.cpp -@@ -41,16 +41,16 @@ static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) - colorMapping, nightmode, tonemapping, nullptr, flGain ); - for ( size_t i=0, end = lut1d_float.dataR.size(); i --#include --#include --#include --#include -- - #include - #include - #include -@@ -14,33 +8,430 @@ - #include - #include - --#include "wlr_begin.hpp" --#include --#include "wlr_end.hpp" -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include - --#include "drm.hpp" -+#include "backend.h" -+#include "color_helpers.h" - #include "defer.hpp" -+#include "drm_include.h" -+#include "edid.h" -+#include "gamescope_shared.h" -+#include "gpuvis_trace_utils.h" -+#include "log.hpp" - #include "main.hpp" - #include "modegen.hpp" -+#include "rendervulkan.hpp" -+#include "steamcompmgr.hpp" - #include "vblankmanager.hpp" - #include "wlserver.hpp" --#include "log.hpp" -- --#include "gpuvis_trace_utils.h" --#include "steamcompmgr.hpp" - --#include --#include --#include -- --extern "C" { -+#include "wlr_begin.hpp" -+#include -+#include - #include "libdisplay-info/info.h" - #include "libdisplay-info/edid.h" - #include "libdisplay-info/cta.h" --} -+#include "wlr_end.hpp" - - #include "gamescope-control-protocol.h" - -+ -+namespace gamescope -+{ -+ 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 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 VALVE1_PLANE_DEGAMMA_TF; -+ std::optional VALVE1_PLANE_DEGAMMA_LUT; -+ std::optional VALVE1_PLANE_CTM; -+ std::optional VALVE1_PLANE_HDR_MULT; -+ std::optional VALVE1_PLANE_SHAPER_LUT; -+ std::optional VALVE1_PLANE_SHAPER_TF; -+ std::optional VALVE1_PLANE_LUT3D; -+ std::optional VALVE1_PLANE_BLEND_TF; -+ std::optional VALVE1_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 VALVE1_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 IBackendConnector, public CDRMAtomicTypedObject -+ { -+ public: -+ CDRMConnector( 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 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; } -+ uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } -+ std::span GetValidDynamicRefreshRates() const override { return m_Mutable.ValidDynamicRefreshRates; } -+ GamescopeKnownDisplays GetKnownDisplayType() const { return m_Mutable.eKnownDisplay; } -+ 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 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 std::span GetModes() const override { return m_Mutable.BackendModes; } -+ -+ bool SupportsVRR() const override -+ { -+ return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); -+ } -+ -+ 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; -+ } -+ } -+ -+ void UpdateEffectiveOrientation( const drmModeModeInfo *pMode ); -+ -+ private: -+ void ParseEDID(); -+ -+ static std::optional GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ); -+ -+ CAutoDeletePtr m_pConnector; -+ -+ struct MutableConnectorState -+ { -+ int nDefaultRefresh = 0; -+ -+ uint32_t uPossibleCRTCMask = 0u; -+ char szName[32]{}; -+ char szMakePNP[4]{}; -+ char szModel[16]{}; -+ const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. -+ GamescopeKnownDisplays eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_UNKNOWN; -+ std::span ValidDynamicRefreshRates{}; -+ std::vector EdidData; // Raw, unmodified. -+ std::vector BackendModes; -+ -+ displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; -+ BackendConnectorHDRInfo HDR; -+ } m_Mutable; -+ -+ GamescopePanelOrientation m_ChosenOrientation = GAMESCOPE_PANEL_ORIENTATION_AUTO; -+ -+ ConnectorProperties m_Props; -+ }; -+} -+ -+struct saved_mode { -+ int width; -+ int height; -+ int refresh; -+}; -+ -+struct fb { -+ uint32_t id; -+ /* Client buffer, if any */ -+ struct wlr_buffer *buf; -+ /* A FB is held if it's being used by steamcompmgr -+ * doesn't need to be atomic as it's only ever -+ * modified/read from the steamcompmgr thread */ -+ int held_refs; -+ /* Number of page-flips using the FB */ -+ std::atomic< uint32_t > n_refs; -+}; -+ -+struct drm_t { -+ bool bUseLiftoff; -+ -+ int fd; -+ -+ int preferred_width, preferred_height, preferred_refresh; -+ -+ 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::map< uint32_t, drmModePropertyRes * > props; -+ -+ gamescope::CDRMPlane *pPrimaryPlane; -+ gamescope::CDRMCRTC *pCRTC; -+ gamescope::CDRMConnector *pConnector; -+ int kms_in_fence_fd; -+ int kms_out_fence_fd; -+ -+ struct wlr_drm_format_set primary_formats; -+ -+ drmModeAtomicReq *req; -+ uint32_t flags; -+ -+ struct liftoff_device *lo_device; -+ struct liftoff_output *lo_output; -+ struct liftoff_layer *lo_layers[ k_nMaxLayers ]; -+ -+ std::shared_ptr sdr_static_metadata; -+ -+ struct { -+ std::shared_ptr mode_id; -+ uint32_t color_mgmt_serial; -+ std::shared_ptr lut3d_id[ EOTF_Count ]; -+ std::shared_ptr shaperlut_id[ EOTF_Count ]; -+ drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; -+ } current, pending; -+ -+ /* FBs in the atomic request, but not yet submitted to KMS */ -+ std::vector < uint32_t > fbids_in_req; -+ /* FBs submitted to KMS, but not yet displayed on screen */ -+ std::vector < uint32_t > fbids_queued; -+ /* FBs currently on screen */ -+ std::vector < uint32_t > fbids_on_screen; -+ -+ std::unordered_map< uint32_t, struct fb > fb_map; -+ std::mutex fb_map_mutex; -+ -+ std::mutex free_queue_lock; -+ std::vector< uint32_t > fbid_unlock_queue; -+ std::vector< uint32_t > fbid_free_queue; -+ -+ std::mutex flip_lock; -+ -+ std::atomic < bool > paused; -+ std::atomic < int > out_of_date; -+ std::atomic < bool > needs_modeset; -+ -+ std::unordered_map< std::string, int > connector_priorities; -+ -+ 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 = {}; -@@ -48,8 +439,17 @@ struct drm_t g_DRM = {}; - 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; --bool g_bDebugLayers = false; --const char *g_sOutputName = nullptr; -+extern bool g_bDebugLayers; -+ -+struct DRMPresentCtx -+{ -+ uint64_t ulPendingFlipCount = 0; -+}; -+ -+extern bool alwaysComposite; -+extern bool g_bColorSliderInUse; -+extern bool fadingOut; -+extern std::string g_reshade_effect; - - #ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP - #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 -@@ -69,11 +469,10 @@ struct drm_color_ctm2 { - - bool g_bSupportsAsyncFlips = false; - --gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; --enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; --std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; -+extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; -+extern GamescopePanelOrientation g_DesiredInternalOrientation; - --bool g_bForceDisableColorMgmt = false; -+extern bool g_bForceDisableColorMgmt; - - static LogScope drm_log("drm"); - static LogScope drm_verbose_log("drm", LOG_SILENT); -@@ -100,51 +499,12 @@ static constexpr uint32_t s_kSteamDeckOLEDRates[] = - 90, - }; - --static uint32_t get_conn_display_info_flags( struct drm_t *drm, gamescope::CDRMConnector *pConnector ) --{ -- if ( !pConnector ) -- return 0; -- -- uint32_t flags = 0; -- if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) -- flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_INTERNAL_DISPLAY; -- if ( pConnector->IsVRRCapable() ) -- flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_VRR; -- if ( pConnector->GetHDRInfo().bExposeHDRSupport ) -- flags |= GAMESCOPE_CONTROL_DISPLAY_FLAG_SUPPORTS_HDR; -- -- return flags; --} -- --void drm_send_gamescope_control(wl_resource *control, struct drm_t *drm) --{ -- // assumes wlserver_lock HELD! -- -- if ( !drm->pConnector ) -- return; -- -- auto& conn = drm->pConnector; -- -- uint32_t flags = get_conn_display_info_flags( drm, drm->pConnector ); -- -- struct wl_array display_rates; -- wl_array_init(&display_rates); -- if ( conn->GetValidDynamicRefreshRates().size() ) -- { -- size_t size = conn->GetValidDynamicRefreshRates().size() * sizeof(uint32_t); -- uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); -- memcpy( ptr, conn->GetValidDynamicRefreshRates().data(), size ); -- } -- gamescope_control_send_active_display_info( control, drm->pConnector->GetName(), drm->pConnector->GetMake(), drm->pConnector->GetModel(), flags, &display_rates ); -- wl_array_release(&display_rates); --} -- - static void update_connector_display_info_wl(struct drm_t *drm) - { - wlserver_lock(); - for ( const auto &control : wlserver.gamescope_controls ) - { -- drm_send_gamescope_control(control, drm); -+ wlserver_send_gamescope_control( control ); - } - wlserver_unlock(); - } -@@ -264,13 +624,13 @@ static gamescope::CDRMPlane *find_primary_plane(struct drm_t *drm) - - static void drm_unlock_fb_internal( struct drm_t *drm, struct fb *fb ); - --std::atomic g_nCompletedPageFlipCount = { 0u }; -- - 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) - { -- uint64_t flipcount = (uint64_t)data; -- g_nCompletedPageFlipCount = flipcount; -+ DRMPresentCtx *pCtx = reinterpret_cast( data ); -+ -+ // Make this const when we move into CDRMBackend. -+ GetBackend()->PresentationFeedback().m_uCompletedPresents = pCtx->ulPendingFlipCount; - - if ( !g_DRM.pCRTC ) - return; -@@ -280,12 +640,12 @@ static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsi - - // This is the last vblank time - uint64_t vblanktime = sec * 1'000'000'000lu + usec * 1'000lu; -- g_VBlankTimer.MarkVBlank( vblanktime, true ); -+ GetVBlankTimer().MarkVBlank( vblanktime, true ); - - // TODO: get the fbids_queued instance from data if we ever have more than one in flight - -- drm_verbose_log.debugf("page_flip_handler %" PRIu64, flipcount); -- gpuvis_trace_printf("page_flip_handler %" PRIu64, flipcount); -+ drm_verbose_log.debugf("page_flip_handler %" PRIu64, pCtx->ulPendingFlipCount); -+ gpuvis_trace_printf("page_flip_handler %" PRIu64, pCtx->ulPendingFlipCount); - - for ( uint32_t i = 0; i < g_DRM.fbids_on_screen.size(); i++ ) - { -@@ -360,362 +720,98 @@ void flip_handler_thread_run(void) - } - } - --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_CVT_TIMING_CODES_COUNT = 4; -- --static inline uint8_t get_bit_range(uint8_t val, size_t high, size_t low) --{ -- size_t n; -- uint8_t bitmask; -- -- assert(high <= 7 && high >= low); -- -- 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 bool refresh_state( drm_t *drm ) - { -- size_t n; -- uint8_t bitmask; -- -- assert(high <= 7 && high >= low); -+ drmModeRes *pResources = drmModeGetResources( drm->fd ); -+ if ( pResources == nullptr ) -+ { -+ drm_log.errorf_errno( "drmModeGetResources failed" ); -+ return false; -+ } -+ defer( drmModeFreeResources( pResources ) ); - -- n = high - low + 1; -- bitmask = (uint8_t) ((1 << n) - 1); -- assert((bits & ~bitmask) == 0); -+ // Add connectors which appeared -+ for ( int i = 0; i < pResources->count_connectors; i++ ) -+ { -+ uint32_t uConnectorId = pResources->connectors[i]; - -- *val |= (uint8_t)(bits << low); --} -+ drmModeConnector *pConnector = drmModeGetConnector( drm->fd, uConnectorId ); -+ if ( !pConnector ) -+ continue; - -+ if ( !drm->connectors.contains( uConnectorId ) ) -+ { -+ drm->connectors.emplace( -+ std::piecewise_construct, -+ std::forward_as_tuple( uConnectorId ), -+ std::forward_as_tuple( pConnector ) ); -+ } -+ } - --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]; -+ // Remove connectors which disappeared -+ for ( auto iter = drm->connectors.begin(); iter != drm->connectors.end(); ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter->second; - -- uint8_t checksum = uint32_t(256) - uint32_t(sum); -+ const bool bFound = std::any_of( -+ pResources->connectors, -+ pResources->connectors + pResources->count_connectors, -+ std::bind_front( std::equal_to{}, pConnector->GetObjectId() ) ); - -- block[127] = checksum; --} -+ if ( !bFound ) -+ { -+ drm_log.debugf( "Connector '%s' disappeared.", pConnector->GetName() ); - --static bool validate_block_checksum(const uint8_t* data) --{ -- uint8_t sum = 0; -- size_t i; -+ if ( drm->pConnector == pConnector ) -+ { -+ drm_log.infof( "Current connector '%s' disappeared.", pConnector->GetName() ); -+ drm->pConnector = nullptr; -+ } - -- for (i = 0; i < EDID_BLOCK_SIZE; i++) { -- sum += data[i]; -+ iter = drm->connectors.erase( iter ); -+ } -+ else -+ iter++; - } - -- return sum == 0; --} -+ // Re-probe connectors props and status) -+ for ( auto &iter : drm->connectors ) -+ { -+ gamescope::CDRMConnector *pConnector = &iter.second; -+ pConnector->RefreshState(); -+ } - --const char *drm_get_patched_edid_path() --{ -- return getenv("GAMESCOPE_PATCHED_EDID_FILE"); --} -+ for ( std::unique_ptr< gamescope::CDRMCRTC > &pCRTC : drm->crtcs ) -+ pCRTC->RefreshState(); - --static uint8_t encode_max_luminance(float nits) --{ -- if (nits == 0.0f) -- return 0; -+ for ( std::unique_ptr< gamescope::CDRMPlane > &pPlane : drm->planes ) -+ pPlane->RefreshState(); - -- return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); -+ return true; - } - --static void create_patched_edid( const uint8_t *orig_data, size_t orig_size, drm_t *drm, gamescope::CDRMConnector *conn ) -+static bool get_resources(struct drm_t *drm) - { -- // A zero length indicates that the edid parsing failed. -- if (orig_size == 0) { -- return; -- } -- -- std::vector edid(orig_data, orig_data + orig_size); -- -- if ( g_bRotated ) - { -- // Patch width, height. -- drm_log.infof("[patched edid] 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++) -+ drmModeRes *pResources = drmModeGetResources( drm->fd ); -+ if ( !pResources ) - { -- 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]; -- drm_log.infof("[patched edid] 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; -- } -+ drm_log.errorf_errno( "drmModeGetResources failed" ); -+ return false; - } -+ defer( drmModeFreeResources( pResources ) ); - -- patch_edid_checksum(&edid[0]); -+ 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 ) ); -+ } - } - -- // 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 ( conn->GetHDRInfo().ShouldPatchEDID() ) - { -- // TODO: Allow for override of min luminance -- float flMaxPeakLuminance = g_ColorMgmt.pending.hdrTonemapDisplayMetadata.BIsValid() ? -- g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits : -- g_ColorMgmt.pending.flInternalDisplayBrightness; -- drm_log.infof("[edid] Patching HDR static metadata. max peak luminance/max frame avg luminance = %f nits", flMaxPeakLuminance ); -- 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(flMaxPeakLuminance), /* desired content max peak luminance */ -- encode_max_luminance(flMaxPeakLuminance * 0.8f), /* 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; -- bool has_hdr_metadata_block = false; -- -- 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) -- { -- has_cta_block = true; -- uint8_t dtd_start = ext_data[2]; -- uint8_t flags = ext_data[3]; -- if (dtd_start == 0) -- { -- drm_log.infof("[josh edid] Hmmmm.... dtd start is 0. Interesting... Not going further! :-("); -- continue; -- } -- if (flags != 0) -- { -- drm_log.infof("[josh edid] 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) -- { -- 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) -- { -- drm_log.infof("[josh edid] Hmmmm.... CTA malformatted. Interesting... Not going further! :-("); -- break; -- } -- -- 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; -- -- if (extended_tag == 6) // hdr static -- { -- if (extended_block_size >= sizeof(new_hdr_static_metadata_block)) -- { -- drm_log.infof("[josh edid] 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; -- } -- } -- } -- -- j += 1 + data_block_size; // account for header size. -- } -- -- 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 -- drm_log.infof("[josh edid] No HDR metadata block to patch... Trying to insert one."); -- -- // Assert that the end of the data blocks == dtd_start -- if (dtd_start != j) -- { -- drm_log.infof("[josh edid] 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); -- 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) -- -- // 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)); -- } -- -- patch_edid_checksum(ext_data); -- bool sum_valid = validate_block_checksum(ext_data); -- drm_log.infof("[josh edid] CTA Checksum valid? %s", sum_valid ? "Y" : "N"); -- } -- } -- -- if (!has_cta_block) -- { -- drm_log.infof("[josh edid] Couldn't patch for HDR metadata as we had no CTA block! Womp womp =c"); -- } -- } -- -- bool sum_valid = validate_block_checksum(&edid[0]); -- drm_log.infof("[josh edid] BASE Checksum valid? %s", sum_valid ? "Y" : "N"); -- -- // Write it out then flip it over atomically. -- -- const char *filename = drm_get_patched_edid_path(); -- if (!filename) -- { -- drm_log.errorf("[josh edid] Couldn't write patched edid. No Path."); -- return; -- } -- -- char filename_tmp[PATH_MAX]; -- snprintf(filename_tmp, sizeof(filename_tmp), "%s.tmp", filename); -- -- FILE *file = fopen(filename_tmp, "wb"); -- if (!file) -- { -- drm_log.errorf("[josh edid] Couldn't open file: %s", filename_tmp); -- return; -- } -- -- fwrite(edid.data(), 1, edid.size(), file); -- fflush(file); -- fclose(file); -- -- rename(filename_tmp, filename); -- drm_log.infof("[josh edid] Wrote new edid to: %s", filename); --} -- --void drm_update_patched_edid( drm_t *drm ) --{ -- if (!drm || !drm->pConnector) -- return; -- -- create_patched_edid(drm->pConnector->GetRawEDID().data(), drm->pConnector->GetRawEDID().size(), drm, drm->pConnector); --} -- --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 ) ); -- -- // 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 ( !drm->connectors.contains( uConnectorId ) ) -- { -- drm->connectors.emplace( -- std::piecewise_construct, -- std::forward_as_tuple( uConnectorId ), -- std::forward_as_tuple( 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; --} -- --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 ) -+ drmModePlaneRes *pPlaneResources = drmModeGetPlaneResources( drm->fd ); -+ if ( !pPlaneResources ) - { - drm_log.errorf_errno( "drmModeGetPlaneResources failed" ); - return false; -@@ -845,7 +941,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - if ( pConnector->GetModeConnector()->connection != DRM_MODE_CONNECTED ) - continue; - -- if ( drm->force_internal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) -+ if ( g_bForceInternal && pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ) - continue; - - int nPriority = get_connector_priority( drm, pConnector->GetName() ); -@@ -912,8 +1008,6 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - return false; - } - -- best->SetBaseRefresh( mode->vrefresh ); -- - if (!drm_set_mode(drm, mode)) { - return false; - } -@@ -928,7 +1022,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - wlserver_unlock(); - - if (!initial) -- create_patched_edid(best->GetRawEDID().data(), best->GetRawEDID().size(), drm, best); -+ WritePatchedEdid( best->GetRawEDID(), best->GetHDRInfo() ); - - update_connector_display_info_wl( drm ); - -@@ -1132,9 +1226,8 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh) - - hdr_output_metadata sdr_metadata; - memset(&sdr_metadata, 0, sizeof(sdr_metadata)); -- drm->sdr_static_metadata = drm_create_hdr_metadata_blob(drm, &sdr_metadata); -+ drm->sdr_static_metadata = GetBackend()->CreateBackendBlob( sdr_metadata ); - -- drm->flipcount = 0; - drm->needs_modeset = true; - - return true; -@@ -1161,7 +1254,7 @@ void finish_drm(struct drm_t *drm) - 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->blob, true ); -+ pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, drm->sdr_static_metadata->GetBlobValue(), true ); - else - pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( req, 0, true ); - } -@@ -1259,164 +1352,6 @@ void finish_drm(struct drm_t *drm) - // page-flip handler thread. - } - --int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ) --{ -- int ret; -- -- assert( drm->req != nullptr ); -- --// if (drm->kms_in_fence_fd != -1) { --// add_plane_property(req, plane_id, "IN_FENCE_FD", drm->kms_in_fence_fd); --// } -- --// drm->kms_out_fence_fd = -1; -- --// add_crtc_property(req, drm->crtc_id, "OUT_FENCE_PTR", --// (uint64_t)(unsigned long)&drm->kms_out_fence_fd); -- -- -- assert( drm->fbids_queued.size() == 0 ); -- -- bool isPageFlip = drm->flags & DRM_MODE_PAGE_FLIP_EVENT; -- -- if ( isPageFlip ) { -- drm->flip_lock.lock(); -- -- // Do it before the commit, as otherwise the pageflip handler could -- // potentially beat us to the refcount checks. -- for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) -- { -- struct fb &fb = get_fb( g_DRM, drm->fbids_in_req[ i ] ); -- assert( fb.held_refs ); -- fb.n_refs++; -- } -- -- drm->fbids_queued = drm->fbids_in_req; -- } -- -- g_DRM.flipcount++; -- -- drm_verbose_log.debugf("flip commit %" PRIu64, (uint64_t)g_DRM.flipcount); -- gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)g_DRM.flipcount ); -- -- ret = drmModeAtomicCommit(drm->fd, drm->req, drm->flags, (void*)(uint64_t)g_DRM.flipcount ); -- if ( ret != 0 ) -- { -- drm_log.errorf_errno( "flip error" ); -- -- if ( ret != -EBUSY && ret != -EACCES ) -- { -- drm_log.errorf( "fatal flip error, aborting" ); -- if ( isPageFlip ) -- drm->flip_lock.unlock(); -- abort(); -- } -- -- 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(); -- } -- } -- -- // Undo refcount if the commit didn't actually work -- for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) -- { -- get_fb( g_DRM, drm->fbids_in_req[ i ] ).n_refs--; -- } -- -- drm->fbids_queued.clear(); -- -- g_DRM.flipcount--; -- -- if ( isPageFlip ) -- drm->flip_lock.unlock(); -- -- goto out; -- } else { -- drm->fbids_in_req.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. -- g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); -- -- if ( isPageFlip ) { -- // Wait for flip handler to unlock -- drm->flip_lock.lock(); -- drm->flip_lock.unlock(); -- } -- --// if (drm->kms_in_fence_fd != -1) { --// close(drm->kms_in_fence_fd); --// drm->kms_in_fence_fd = -1; --// } --// --// drm->kms_in_fence_fd = drm->kms_out_fence_fd; -- --out: -- drmModeAtomicFree( drm->req ); -- drm->req = nullptr; -- -- return ret; --} -- - uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ) - { - uint32_t fb_id = 0; -@@ -1571,93 +1506,35 @@ void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ) - drm_unlock_fb_internal( drm, &fb ); - } - --static uint64_t determine_drm_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) -+static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) - { -- if ( pConnector && pConnector->GetProperties().panel_orientation ) -+ gamescope::IBackendConnector *pInternalConnector = GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ); -+ if ( pInternalConnector ) - { -- switch ( pConnector->GetProperties().panel_orientation->GetCurrentValue() ) -- { -- case DRM_MODE_PANEL_ORIENTATION_NORMAL: -- return DRM_MODE_ROTATE_0; -- case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: -- return DRM_MODE_ROTATE_180; -- case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: -- return DRM_MODE_ROTATE_90; -- case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: -- return DRM_MODE_ROTATE_270; -- } -+ 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 ); - } - -- if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL && mode ) -+ gamescope::IBackendConnector *pExternalConnector = GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL ); -+ if ( pExternalConnector ) - { -- // Auto-detect portait mode for internal displays -- return mode->hdisplay < mode->vdisplay ? DRM_MODE_ROTATE_270 : DRM_MODE_ROTATE_0; -- } -+ gamescope::CDRMConnector *pDRMExternalConnector = static_cast( pExternalConnector ); -+ const drmModeModeInfo *pExternalMode = pMode; -+ if ( pDRMExternalConnector != drm->pConnector ) -+ pExternalMode = find_mode( pDRMExternalConnector->GetModeConnector(), 0, 0, 0 ); - -- return DRM_MODE_ROTATE_0; -+ pDRMExternalConnector->UpdateEffectiveOrientation( pExternalMode ); -+ } - } - --/* Handle the orientation of the display */ --static void update_drm_effective_orientation(struct drm_t *drm, gamescope::CDRMConnector *pConnector, const drmModeModeInfo *mode) -+// Only used for NV12 buffers -+static drm_color_encoding drm_get_color_encoding(EStreamColorspace colorspace) - { -- gamescope::GamescopeScreenType eScreenType = pConnector->GetScreenType(); -- -- if ( eScreenType == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) -- { -- switch ( g_drmModeOrientation ) -- { -- case PANEL_ORIENTATION_0: -- g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_0; -- break; -- case PANEL_ORIENTATION_90: -- g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_90; -- break; -- case PANEL_ORIENTATION_180: -- g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_180; -- break; -- case PANEL_ORIENTATION_270: -- g_drmEffectiveOrientation[eScreenType] = DRM_MODE_ROTATE_270; -- break; -- case PANEL_ORIENTATION_AUTO: -- g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); -- break; -- } -- } -- else -- { -- g_drmEffectiveOrientation[eScreenType] = determine_drm_orientation( drm, pConnector, mode ); -- } --} -- --static void update_drm_effective_orientations( struct drm_t *drm, const drmModeModeInfo *pMode ) --{ -- gamescope::CDRMConnector *pInternalConnector = nullptr; -- if ( drm->pConnector && drm->pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) -- pInternalConnector = drm->pConnector; -- -- if ( !pInternalConnector ) -- { -- for ( auto &iter : drm->connectors ) -- { -- gamescope::CDRMConnector *pConnector = &iter.second; -- if ( pConnector->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) -- { -- pInternalConnector = pConnector; -- // Find mode for internal connector instead. -- pMode = find_mode(pInternalConnector->GetModeConnector(), 0, 0, 0); -- break; -- } -- } -- } -- -- if ( pInternalConnector ) -- update_drm_effective_orientation( drm, pInternalConnector, pMode ); --} -- --// Only used for NV12 buffers --static drm_color_encoding drm_get_color_encoding(EStreamColorspace colorspace) --{ -- switch (colorspace) -+ switch (colorspace) - { - default: - case k_EStreamColorspace_Unknown: -@@ -2095,6 +1972,17 @@ namespace gamescope - 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 ) - { -@@ -2110,6 +1998,49 @@ namespace gamescope - ParseEDID(); - } - -+ 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 ) -@@ -2240,7 +2171,7 @@ namespace gamescope - ///////////////////// - // Parse HDR stuff. - ///////////////////// -- std::optional oKnownHDRInfo = GetKnownDisplayHDRInfo( m_Mutable.eKnownDisplay ); -+ std::optional oKnownHDRInfo = GetKnownDisplayHDRInfo( m_Mutable.eKnownDisplay ); - if ( oKnownHDRInfo ) - { - m_Mutable.HDR = *oKnownHDRInfo; -@@ -2319,7 +2250,7 @@ namespace gamescope - pInfoframe->max_fall = uDefaultInfoframeLuminances; - pInfoframe->eotf = HDMI_EOTF_ST2084; - -- m_Mutable.HDR.pDefaultMetadataBlob = drm_create_hdr_metadata_blob( &g_DRM, &defaultHDRMetadata ); -+ m_Mutable.HDR.pDefaultMetadataBlob = GetBackend()->CreateBackendBlob( defaultHDRMetadata ); - } - else - { -@@ -2328,14 +2259,14 @@ namespace gamescope - } - } - -- /*static*/ std::optional CDRMConnector::GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ) -+ /*static*/ std::optional CDRMConnector::GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ) - { - if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_BOE || eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_OLED_SDC ) - { - // The stuff in the EDID for the HDR metadata does not fully - // reflect what we can achieve on the display by poking at more - // things out-of-band. -- return HDRInfo -+ return BackendConnectorHDRInfo - { - .bExposeHDRSupport = true, - .eOutputEncodingEOTF = EOTF_Gamma22, -@@ -2347,7 +2278,7 @@ namespace gamescope - else if ( eKnownDisplay == GAMESCOPE_KNOWN_DISPLAY_STEAM_DECK_LCD ) - { - // Set up some HDR fallbacks for undocking -- return HDRInfo -+ return BackendConnectorHDRInfo - { - .bExposeHDRSupport = false, - .eOutputEncodingEOTF = EOTF_Gamma22, -@@ -2364,7 +2295,6 @@ namespace gamescope - static int - drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, bool needs_modeset ) - { -- gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); - auto entry = FrameInfoToLiftoffStateCacheEntry( drm, frameInfo ); - - // If we are modesetting, reset the state cache, we might -@@ -2402,7 +2332,24 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - 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 ); - -- liftoff_layer_set_property( drm->lo_layers[ i ], "rotation", g_drmEffectiveOrientation[screenType] ); -+ 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); -@@ -2449,9 +2396,9 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - - if ( !g_bDisableShaperAnd3DLUT ) - { -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", shaper_tf ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->blob ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_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 -@@ -2482,7 +2429,7 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); - - if (frameInfo->layers[i].ctm != nullptr) -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", frameInfo->layers[i].ctm->blob ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", frameInfo->layers[i].ctm->GetBlobValue() ); - else - liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); - } -@@ -2552,13 +2499,15 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - uint32_t uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; - - const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; -- wlserver_hdr_metadata *pHDRMetadata = nullptr; -+ gamescope::BackendBlob *pHDRMetadata = nullptr; - if ( drm->pConnector && drm->pConnector->SupportsHDR10() ) - { - if ( bWantsHDR10 ) - { - wlserver_vk_swapchain_feedback* pFeedback = steamcompmgr_get_base_layer_swapchain_feedback(); -- pHDRMetadata = pFeedback ? pFeedback->hdr_metadata_blob.get() : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); -+ pHDRMetadata = pFeedback -+ ? pFeedback->hdr_metadata_blob.get() -+ : drm->pConnector->GetHDRInfo().pDefaultMetadataBlob.get(); - uColorimetry = DRM_MODE_COLORIMETRY_BT2020_RGB; - } - else -@@ -2675,7 +2624,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - if ( drm->pCRTC ) - { - drm->pCRTC->GetProperties().ACTIVE->SetPendingValue( drm->req, 1u, true ); -- drm->pCRTC->GetProperties().MODE_ID->SetPendingValue( drm->req, drm->pending.mode_id ? drm->pending.mode_id->blob : 0lu, 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 ); -@@ -2685,7 +2634,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - if ( drm->pConnector ) - { - if ( drm->pConnector->GetProperties().HDR_OUTPUT_METADATA ) -- drm->pConnector->GetProperties().HDR_OUTPUT_METADATA->SetPendingValue( drm->req, pHDRMetadata ? pHDRMetadata->blob : 0lu, bForceInRequest ); -+ 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 ); -@@ -2839,19 +2788,8 @@ bool drm_update_color_mgmt(struct drm_t *drm) - if ( !g_ColorMgmtLuts[i].HasLuts() ) - continue; - -- uint32_t shaper_blob_id = 0; -- if (drmModeCreatePropertyBlob(drm->fd, g_ColorMgmtLuts[i].lut1d, sizeof(g_ColorMgmtLuts[i].lut1d), &shaper_blob_id) != 0) { -- drm_log.errorf_errno("Unable to create SHAPERLUT property blob"); -- return false; -- } -- drm->pending.shaperlut_id[ i ] = std::make_shared( shaper_blob_id ); -- -- uint32_t lut3d_blob_id = 0; -- if (drmModeCreatePropertyBlob(drm->fd, g_ColorMgmtLuts[i].lut3d, sizeof(g_ColorMgmtLuts[i].lut3d), &lut3d_blob_id) != 0) { -- drm_log.errorf_errno("Unable to create LUT3D property blob"); -- return false; -- } -- drm->pending.lut3d_id[ i ] = std::make_shared( lut3d_blob_id ); -+ drm->pending.shaperlut_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut1d ); -+ drm->pending.lut3d_id[ i ] = GetBackend()->CreateBackendBlob( g_ColorMgmtLuts[i].lut3d ); - } - - return true; -@@ -2873,8 +2811,6 @@ static void drm_unset_mode( struct drm_t *drm ) - if (g_nOutputRefresh == 0) - g_nOutputRefresh = 60; - -- g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = DRM_MODE_ROTATE_0; -- g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL] = DRM_MODE_ROTATE_0; - g_bRotated = false; - } - -@@ -2883,31 +2819,26 @@ bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ) - if (!drm->pConnector || !drm->pConnector->GetModeConnector()) - return false; - -- uint32_t mode_id = 0; -- if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode), &mode_id) != 0) -- return false; -- -- gamescope::GamescopeScreenType screenType = drm_get_screen_type(drm); -- - drm_log.infof("selecting mode %dx%d@%uHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); - -- drm->pending.mode_id = std::make_shared(mode_id); -+ drm->pending.mode_id = GetBackend()->CreateBackendBlob( *mode ); - drm->needs_modeset = true; - - g_nOutputRefresh = mode->vrefresh; - - update_drm_effective_orientations(drm, mode); - -- switch ( g_drmEffectiveOrientation[screenType] ) -+ switch ( drm->pConnector->GetCurrentOrientation() ) - { -- case DRM_MODE_ROTATE_0: -- case DRM_MODE_ROTATE_180: -+ default: -+ case GAMESCOPE_PANEL_ORIENTATION_0: -+ case GAMESCOPE_PANEL_ORIENTATION_180: - g_bRotated = false; - g_nOutputWidth = mode->hdisplay; - g_nOutputHeight = mode->vdisplay; - break; -- case DRM_MODE_ROTATE_90: -- case DRM_MODE_ROTATE_270: -+ case GAMESCOPE_PANEL_ORIENTATION_90: -+ case GAMESCOPE_PANEL_ORIENTATION_270: - g_bRotated = true; - g_nOutputWidth = mode->vdisplay; - g_nOutputHeight = mode->hdisplay; -@@ -2974,37 +2905,10 @@ bool drm_set_resolution( struct drm_t *drm, int width, int height ) - return drm_set_mode(drm, mode); - } - --int drm_get_default_refresh(struct drm_t *drm) --{ -- if ( drm->preferred_refresh ) -- return drm->preferred_refresh; -- -- if ( drm->pConnector && drm->pConnector->GetBaseRefresh() ) -- return drm->pConnector->GetBaseRefresh(); -- -- if ( drm->pConnector && drm->pConnector->GetModeConnector() ) -- { -- int width = g_nOutputWidth; -- int height = g_nOutputHeight; -- if ( g_bRotated ) { -- int tmp = width; -- width = height; -- height = tmp; -- } -- -- drmModeConnector *connector = drm->pConnector->GetModeConnector(); -- const drmModeModeInfo *mode = find_mode( connector, width, height, 0); -- if ( mode ) -- return mode->vrefresh; -- } -- -- return 60; --} -- - bool drm_get_vrr_capable(struct drm_t *drm) - { - if ( drm->pConnector ) -- return drm->pConnector->IsVRRCapable(); -+ return drm->pConnector->SupportsVRR(); - - return false; - } -@@ -3044,102 +2948,662 @@ std::pair drm_get_connector_identifier(struct drm_t *drm) - return std::make_pair(drm->pConnector->GetModeConnector()->connector_type, drm->pConnector->GetModeConnector()->connector_type_id); - } - --std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata) -+bool drm_supports_color_mgmt(struct drm_t *drm) - { -- uint32_t blob = 0; -- if (!BIsNested()) -- { -- int ret = drmModeCreatePropertyBlob(drm->fd, metadata, sizeof(*metadata), &blob); -- -- if (ret != 0) { -- drm_log.errorf("Failed to create blob for HDR_OUTPUT_METADATA. (%s) Falling back to null blob.", strerror(-ret)); -- blob = 0; -- } -- -+ if ( g_bForceDisableColorMgmt ) -+ return false; - -- if (!blob) -- return nullptr; -- } -+ if ( !drm->pPrimaryPlane ) -+ return false; - -- return std::make_shared(metadata, blob); -+ return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); - } --void drm_destroy_blob(struct drm_t *drm, uint32_t blob) -+ -+std::span drm_get_valid_refresh_rates( struct drm_t *drm ) - { -- drmModeDestroyPropertyBlob(drm->fd, blob); -+ if ( drm && drm->pConnector ) -+ return drm->pConnector->GetValidDynamicRefreshRates(); -+ -+ return std::span{}; - } - --std::shared_ptr drm_create_ctm(struct drm_t *drm, glm::mat3x4 ctm) -+namespace gamescope - { -- uint32_t blob = 0; -- if (!BIsNested()) -+ class CDRMBackend; -+ -+ class CDRMBackend final : public CBaseBackend - { -- drm_color_ctm2 ctm2; -- for (uint32_t i = 0; i < 12; i++) -+ public: -+ CDRMBackend() -+ { -+ } -+ -+ virtual ~CDRMBackend() -+ { -+ } -+ -+ virtual bool Init() override - { -- float *data = (float*)&ctm; -- ctm2.matrix[i] = drm_calc_s31_32(data[i]); -+ 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, 0, 0, 0 ); - } - -- int ret = drmModeCreatePropertyBlob(drm->fd, &ctm2, sizeof(ctm2), &blob); -+ virtual bool PostInit() override -+ { -+ if ( g_DRM.pConnector ) -+ WritePatchedEdid( g_DRM.pConnector->GetRawEDID(), g_DRM.pConnector->GetHDRInfo() ); -+ return true; -+ } - -- if (ret != 0) { -- drm_log.errorf("Failed to create blob for CTM. (%s) Falling back to null blob.", strerror(-ret)); -- blob = 0; -+ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override -+ { -+ *pPrimaryPlaneFormat = DRMFormatToVulkan( g_nDRMFormat, false ); -+ *pOverlayPlaneFormat = DRMFormatToVulkan( g_nDRMFormatOverlay, false ); -+ } -+ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override -+ { -+ return true; -+ } -+ -+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override -+ { -+ 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 |= alwaysComposite; -+ bNeedsFullComposite |= bWasFirstFrame; -+ bNeedsFullComposite |= pFrameInfo->useFSRLayer0; -+ bNeedsFullComposite |= pFrameInfo->useNISLayer0; -+ bNeedsFullComposite |= pFrameInfo->blurLayer0; -+ bNeedsFullComposite |= bNeedsCompositeFromFilter; -+ bNeedsFullComposite |= 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 ); -+ } -+ -+ 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_bWasPartialCompsiting = 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_bWasPartialCompsiting ); -+ -+ // 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 = {}; -+ -+ 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->fbid = baseLayer->tex->fbid(); -+ baseLayer->applyColorMgmt = false; -+ -+ baseLayer->filter = GamescopeUpscaleFilter::NEAREST; -+ baseLayer->ctm = nullptr; -+ baseLayer->colorspace = g_bOutputHDREnabled ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; -+ -+ m_bWasPartialCompsiting = false; -+ } -+ else -+ { -+ if ( m_bWasPartialCompsiting || !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->fbid = overlayLayer->tex->fbid(); -+ 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_bWasPartialCompsiting = 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)); -+ abort(); -+ } -+ -+ 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( std::span data ) override -+ { -+ uint32_t uBlob = 0; -+ if ( drmModeCreatePropertyBlob( g_DRM.fd, data.data(), data.size(), &uBlob ) != 0 ) -+ return nullptr; -+ -+ return std::make_shared( data, uBlob, true ); -+ } -+ -+ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override -+ { -+ return drm_fbid_from_dmabuf( &g_DRM, pBuffer, pDmaBuf ); -+ } -+ -+ virtual void LockBackendFb( uint32_t uFbId ) override -+ { -+ drm_lock_fbid( &g_DRM, uFbId ); -+ } -+ virtual void UnlockBackendFb( uint32_t uFbId ) override -+ { -+ drm_unlock_fbid( &g_DRM, uFbId ); -+ } -+ virtual void DropBackendFb( uint32_t uFbId ) override -+ { -+ drm_drop_fbid( &g_DRM, uFbId ); -+ } -+ -+ virtual bool UsesModifiers() const override -+ { -+ return true; -+ } -+ 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; -+ } -+ } - -- if (!blob) - return nullptr; -- } -+ } - -- return std::make_shared(ctm, blob); --} -+ 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(); -+ } - --bool drm_supports_color_mgmt(struct drm_t *drm) --{ -- if ( g_bForceDisableColorMgmt ) -- return false; -+ virtual bool SupportsPlaneHardwareCursor() const override -+ { -+ return true; -+ } - -- if ( !drm->pPrimaryPlane ) -- return false; -+ virtual bool SupportsTearing() const override -+ { -+ return g_bSupportsAsyncFlips; -+ } - -- return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); --} -+ virtual bool UsesVulkanSwapchain() const override -+ { -+ return false; -+ } - --void drm_get_native_colorimetry( struct drm_t *drm, -- displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, -- displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ) --{ -- if ( !drm || !drm->pConnector ) -- { -- *displayColorimetry = displaycolorimetry_709; -- *displayEOTF = EOTF_Gamma22; -- *outputEncodingColorimetry = displaycolorimetry_709; -- *outputEncodingEOTF = EOTF_Gamma22; -- return; -- } -+ virtual bool IsSessionBased() const override -+ { -+ return true; -+ } - -- *displayColorimetry = drm->pConnector->GetDisplayColorimetry(); -- *displayEOTF = EOTF_Gamma22; -+ virtual bool IsVisible() const override -+ { -+ return !g_DRM.paused; -+ } - -- // For HDR10 output, expected content colorspace != native colorspace. -- if ( g_bOutputHDREnabled && drm->pConnector->GetHDRInfo().IsHDR10() ) -- { -- *outputEncodingColorimetry = displaycolorimetry_2020; -- *outputEncodingEOTF = drm->pConnector->GetHDRInfo().eOutputEncodingEOTF; -- } -- else -- { -- *outputEncodingColorimetry = drm->pConnector->GetDisplayColorimetry(); -- *outputEncodingEOTF = EOTF_Gamma22; -- } --} -+ virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override -+ { -+ return glm::uvec2{ g_DRM.cursor_width, g_DRM.cursor_height }; -+ } - -+ virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override -+ { -+ return drm_set_refresh( &g_DRM, nRefresh ); -+ } - --std::span drm_get_valid_refresh_rates( struct drm_t *drm ) --{ -- if ( drm && drm->pConnector ) -- return drm->pConnector->GetValidDynamicRefreshRates(); -+ virtual void HackUpdatePatchedEdid() override -+ { -+ if ( !GetCurrentConnector() ) -+ return; - -- return std::span{}; -+ WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo() ); -+ } -+ -+ protected: -+ -+ virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override -+ { -+ if ( pBlob->GetBlobValue() ) -+ drmModeDestroyPropertyBlob( g_DRM.fd, pBlob->GetBlobValue() ); -+ } -+ -+ private: -+ bool m_bWasCompositing = false; -+ bool m_bWasPartialCompsiting = 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 ); -+ assert( drm->fbids_queued.size() == 0 ); -+ -+ defer( if ( drm->req != nullptr ) { drmModeAtomicFree( drm->req ); drm->req = nullptr; } ); -+ -+ bool isPageFlip = drm->flags & DRM_MODE_PAGE_FLIP_EVENT; -+ -+ if ( isPageFlip ) -+ { -+ drm->flip_lock.lock(); -+ -+ // Do it before the commit, as otherwise the pageflip handler could -+ // potentially beat us to the refcount checks. -+ for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) -+ { -+ struct fb &fb = get_fb( g_DRM, drm->fbids_in_req[ i ] ); -+ assert( fb.held_refs ); -+ fb.n_refs++; -+ } -+ -+ drm->fbids_queued = drm->fbids_in_req; -+ } -+ -+ m_PresentFeedback.m_uQueuedPresents++; -+ -+ uint32_t uCurrentPresentCtx = m_uNextPresentCtx; -+ m_uNextPresentCtx = ( m_uNextPresentCtx + 1 ) % 3; -+ m_PresentCtxs[uCurrentPresentCtx].ulPendingFlipCount = m_PresentFeedback.m_uQueuedPresents; -+ -+ drm_verbose_log.debugf("flip commit %" PRIu64, (uint64_t)m_PresentFeedback.m_uQueuedPresents); -+ gpuvis_trace_printf( "flip commit %" PRIu64, (uint64_t)m_PresentFeedback.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->flip_lock.unlock(); -+ abort(); -+ } -+ -+ 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(); -+ } -+ } -+ -+ // Undo refcount if the commit didn't actually work -+ for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) -+ { -+ get_fb( g_DRM, drm->fbids_in_req[ i ] ).n_refs--; -+ } -+ -+ drm->fbids_queued.clear(); -+ -+ m_PresentFeedback.m_uQueuedPresents--; -+ -+ if ( isPageFlip ) -+ drm->flip_lock.unlock(); -+ -+ return ret; -+ } else { -+ drm->fbids_in_req.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 flip handler to unlock -+ drm->flip_lock.lock(); -+ drm->flip_lock.unlock(); -+ } -+ -+ return ret; -+ } -+ -+ }; -+ -+ ///////////////////////// -+ // Backend Instantiator -+ ///////////////////////// -+ -+ template <> -+ bool IBackend::Set() -+ { -+ return Set( new CDRMBackend{} ); -+ } - } -diff --git a/src/drm.hpp b/src/drm.hpp -deleted file mode 100644 -index 79c88050a..000000000 ---- a/src/drm.hpp -+++ /dev/null -@@ -1,526 +0,0 @@ --#pragma once -- --#include "drm_include.h" --#include "color_helpers.h" --#include "gamescope_shared.h" --#include "rendervulkan.hpp" -- --#include "wlr_begin.hpp" --#include --#include --#include --#include "wlr_end.hpp" -- --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include -- --extern struct drm_t g_DRM; --void drm_destroy_blob(struct drm_t *drm, uint32_t blob); -- --class drm_blob --{ --public: -- drm_blob() : blob( 0 ), owned( false ) -- { -- } -- -- drm_blob(uint32_t blob, bool owned = true) -- : blob( blob ), owned( owned ) -- { -- } -- -- ~drm_blob() -- { -- if (blob && owned) -- drm_destroy_blob( &g_DRM, blob ); -- } -- -- // No copy constructor, because we can't duplicate the blob handle. -- drm_blob(const drm_blob&) = delete; -- drm_blob& operator=(const drm_blob&) = delete; -- // No move constructor, because we use shared_ptr anyway, but can be added if necessary. -- drm_blob(drm_blob&&) = delete; -- drm_blob& operator=(drm_blob&&) = delete; -- -- uint32_t blob; -- bool owned; --}; -- --struct wlserver_hdr_metadata : drm_blob --{ -- wlserver_hdr_metadata() -- { -- } -- -- wlserver_hdr_metadata(hdr_output_metadata* _metadata, uint32_t blob, bool owned = true) -- : drm_blob( blob, owned ) -- { -- if (_metadata) -- this->metadata = *_metadata; -- } -- -- hdr_output_metadata metadata = {}; --}; -- --struct wlserver_ctm : drm_blob --{ -- wlserver_ctm() -- { -- } -- -- wlserver_ctm(glm::mat3x4 ctm, uint32_t blob, bool owned = true) -- : drm_blob( blob, owned ), matrix( ctm ) -- { -- } -- -- glm::mat3x4 matrix{}; --}; --namespace gamescope --{ -- 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; } -- 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 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 VALVE1_PLANE_DEGAMMA_TF; -- std::optional VALVE1_PLANE_DEGAMMA_LUT; -- std::optional VALVE1_PLANE_CTM; -- std::optional VALVE1_PLANE_HDR_MULT; -- std::optional VALVE1_PLANE_SHAPER_LUT; -- std::optional VALVE1_PLANE_SHAPER_TF; -- std::optional VALVE1_PLANE_LUT3D; -- std::optional VALVE1_PLANE_BLEND_TF; -- std::optional VALVE1_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 VALVE1_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 CDRMAtomicTypedObject -- { -- public: -- CDRMConnector( 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 DUMMY_END; -- }; -- ConnectorProperties &GetProperties() { return m_Props; } -- const ConnectorProperties &GetProperties() const { return m_Props; } -- -- struct HDRInfo -- { -- // 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. -- bool bExposeHDRSupport = 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. -- EOTF eOutputEncodingEOTF = EOTF_Gamma22; -- -- 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 ShouldPatchEDID() const -- { -- return IsHDRG22(); -- } -- -- bool IsHDR10() const -- { -- // PQ output encoding is always HDR10 (PQ + 2020) for us. -- // If that assumption changes, update me. -- return bExposeHDRSupport && eOutputEncodingEOTF == EOTF_PQ; -- } -- }; -- -- drmModeConnector *GetModeConnector() { return m_pConnector.get(); } -- const char *GetName() const { return m_Mutable.szName; } -- const char *GetMake() const { return m_Mutable.pszMake; } -- const char *GetModel() const { return m_Mutable.szModel; } -- uint32_t GetPossibleCRTCMask() const { return m_Mutable.uPossibleCRTCMask; } -- const HDRInfo &GetHDRInfo() const { return m_Mutable.HDR; } -- std::span GetValidDynamicRefreshRates() const { return m_Mutable.ValidDynamicRefreshRates; } -- GamescopeKnownDisplays GetKnownDisplayType() const { return m_Mutable.eKnownDisplay; } -- GamescopeScreenType GetScreenType() const -- { -- 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; -- } -- bool IsVRRCapable() const -- { -- return this->GetProperties().vrr_capable && !!this->GetProperties().vrr_capable->GetCurrentValue(); -- } -- const displaycolorimetry_t& GetDisplayColorimetry() const { return m_Mutable.DisplayColorimetry; } -- -- const std::vector &GetRawEDID() const { return m_Mutable.EdidData; } -- -- // TODO: Remove -- void SetBaseRefresh( int nRefresh ) { m_nBaseRefresh = nRefresh; } -- int GetBaseRefresh() const { return m_nBaseRefresh; } -- -- bool SupportsHDR10() const -- { -- return !!GetProperties().Colorspace && !!GetProperties().HDR_OUTPUT_METADATA && GetHDRInfo().IsHDR10(); -- } -- -- bool SupportsHDRG22() const -- { -- return GetHDRInfo().IsHDRG22(); -- } -- -- bool SupportsHDR() const -- { -- return SupportsHDR10() || SupportsHDRG22(); -- } -- private: -- void ParseEDID(); -- -- static std::optional GetKnownDisplayHDRInfo( GamescopeKnownDisplays eKnownDisplay ); -- -- CAutoDeletePtr m_pConnector; -- -- struct MutableConnectorState -- { -- int nDefaultRefresh = 0; -- -- uint32_t uPossibleCRTCMask = 0u; -- char szName[32]{}; -- char szMakePNP[4]{}; -- char szModel[16]{}; -- const char *pszMake = ""; // Not owned, no free. This is a pointer to pnp db or szMakePNP. -- GamescopeKnownDisplays eKnownDisplay = GAMESCOPE_KNOWN_DISPLAY_UNKNOWN; -- std::span ValidDynamicRefreshRates{}; -- std::vector EdidData; // Raw, unmodified. -- -- displaycolorimetry_t DisplayColorimetry = displaycolorimetry_709; -- HDRInfo HDR; -- } m_Mutable; -- -- // TODO: Remove -- int m_nBaseRefresh = 0; -- -- ConnectorProperties m_Props; -- }; --} -- --struct saved_mode { -- int width; -- int height; -- int refresh; --}; -- --struct fb { -- uint32_t id; -- /* Client buffer, if any */ -- struct wlr_buffer *buf; -- /* A FB is held if it's being used by steamcompmgr -- * doesn't need to be atomic as it's only ever -- * modified/read from the steamcompmgr thread */ -- int held_refs; -- /* Number of page-flips using the FB */ -- std::atomic< uint32_t > n_refs; --}; -- --struct drm_t { -- bool bUseLiftoff; -- -- int fd; -- -- int preferred_width, preferred_height, preferred_refresh; -- -- 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::map< uint32_t, drmModePropertyRes * > props; -- -- gamescope::CDRMPlane *pPrimaryPlane; -- gamescope::CDRMCRTC *pCRTC; -- gamescope::CDRMConnector *pConnector; -- int kms_in_fence_fd; -- int kms_out_fence_fd; -- -- struct wlr_drm_format_set primary_formats; -- -- drmModeAtomicReq *req; -- uint32_t flags; -- -- struct liftoff_device *lo_device; -- struct liftoff_output *lo_output; -- struct liftoff_layer *lo_layers[ k_nMaxLayers ]; -- -- std::shared_ptr sdr_static_metadata; -- -- struct { -- std::shared_ptr mode_id; -- uint32_t color_mgmt_serial; -- std::shared_ptr lut3d_id[ EOTF_Count ]; -- std::shared_ptr shaperlut_id[ EOTF_Count ]; -- drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; -- } current, pending; -- -- /* FBs in the atomic request, but not yet submitted to KMS */ -- std::vector < uint32_t > fbids_in_req; -- /* FBs submitted to KMS, but not yet displayed on screen */ -- std::vector < uint32_t > fbids_queued; -- /* FBs currently on screen */ -- std::vector < uint32_t > fbids_on_screen; -- -- std::unordered_map< uint32_t, struct fb > fb_map; -- std::mutex fb_map_mutex; -- -- std::mutex free_queue_lock; -- std::vector< uint32_t > fbid_unlock_queue; -- std::vector< uint32_t > fbid_free_queue; -- -- std::mutex flip_lock; -- -- std::atomic < uint64_t > flipcount; -- -- std::atomic < bool > paused; -- std::atomic < int > out_of_date; -- std::atomic < bool > needs_modeset; -- -- std::unordered_map< std::string, int > connector_priorities; -- -- bool force_internal = false; -- -- char *device_name = nullptr; --}; -- --extern struct drm_t g_DRM; -- --extern uint32_t g_nDRMFormat; --extern uint32_t g_nDRMFormatOverlay; -- --extern bool g_bRotated; --extern bool g_bFlipped; --extern bool g_bDebugLayers; --extern const char *g_sOutputName; -- --enum g_panel_orientation { -- PANEL_ORIENTATION_0, /* NORMAL */ -- PANEL_ORIENTATION_270, /* RIGHT */ -- PANEL_ORIENTATION_90, /* LEFT */ -- PANEL_ORIENTATION_180, /* UPSIDE DOWN */ -- PANEL_ORIENTATION_AUTO, --}; -- --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, --}; -- --extern gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration; --extern enum g_panel_orientation g_drmModeOrientation; -- --extern std::atomic g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* -- --extern bool g_bForceDisableColorMgmt; -- --bool init_drm(struct drm_t *drm, int width, int height, int refresh); --void finish_drm(struct drm_t *drm); --int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); --int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ); --bool drm_poll_state(struct drm_t *drm); --uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ); --void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); --void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); --void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); --bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); --bool drm_set_refresh( struct drm_t *drm, int refresh ); --bool drm_set_resolution( struct drm_t *drm, int width, int height ); --gamescope::GamescopeScreenType drm_get_screen_type(struct drm_t *drm); -- --char *find_drm_node_by_devid(dev_t devid); --int drm_get_default_refresh(struct drm_t *drm); --bool drm_get_vrr_capable(struct drm_t *drm); --bool drm_supports_hdr(struct drm_t *drm, uint16_t *maxCLL = nullptr, uint16_t *maxFALL = nullptr); --bool drm_get_vrr_in_use(struct drm_t *drm); --bool drm_supports_color_mgmt(struct drm_t *drm); --std::shared_ptr drm_create_hdr_metadata_blob(struct drm_t *drm, hdr_output_metadata *metadata); --std::shared_ptr drm_create_ctm(struct drm_t *drm, glm::mat3x4 ctm); --void drm_destroy_blob(struct drm_t *drm, uint32_t blob); -- --const char *drm_get_connector_name(struct drm_t *drm); --const char *drm_get_device_name(struct drm_t *drm); -- --std::pair drm_get_connector_identifier(struct drm_t *drm); -- --void drm_get_native_colorimetry( struct drm_t *drm, -- displaycolorimetry_t *displayColorimetry, EOTF *displayEOTF, -- displaycolorimetry_t *outputEncodingColorimetry, EOTF *outputEncodingEOTF ); -- --std::span drm_get_valid_refresh_rates( struct drm_t *drm ); -- --extern bool g_bSupportsAsyncFlips; -- --const char* drm_get_patched_edid_path(); --void drm_update_patched_edid(drm_t *drm); -- --void drm_send_gamescope_control(wl_resource *control, struct drm_t *drm); -diff --git a/src/drm_include.h b/src/drm_include.h -index cf4a7cb5c..500c3040a 100644 ---- a/src/drm_include.h -+++ b/src/drm_include.h -@@ -5,6 +5,13 @@ - #include - #include - -+#include "wlr_begin.hpp" -+#include -+#include -+#include "wlr_end.hpp" -+ -+#include "hdmi.h" -+ - // Josh: Okay whatever, this header isn't - // available for whatever stupid reason. :v - //#include -@@ -36,11 +43,13 @@ enum drm_valve1_transfer_function { - DRM_VALVE1_TRANSFER_FUNCTION_MAX, - }; - --/* from CTA-861-G */ --#define HDMI_EOTF_SDR 0 --#define HDMI_EOTF_TRADITIONAL_HDR 1 --#define HDMI_EOTF_ST2084 2 --#define HDMI_EOTF_HLG 3 -+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, -+}; - - /* For Default case, driver will set the colorspace */ - #define DRM_MODE_COLORIMETRY_DEFAULT 0 -diff --git a/src/edid.cpp b/src/edid.cpp -new file mode 100644 -index 000000000..3f499fc98 ---- /dev/null -+++ b/src/edid.cpp -@@ -0,0 +1,296 @@ -+#include "edid.h" -+ -+#include "backend.h" -+#include "log.hpp" -+#include "hdmi.h" -+ -+#include -+#include -+#include -+ -+extern "C" -+{ -+#include "libdisplay-info/info.h" -+#include "libdisplay-info/edid.h" -+#include "libdisplay-info/cta.h" -+} -+ -+extern bool g_bRotated; -+ -+static LogScope edid_log("josh 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_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_CVT_TIMING_CODES_COUNT = 4; -+ -+ static inline uint8_t get_bit_range(uint8_t val, size_t high, size_t low) -+ { -+ size_t n; -+ uint8_t bitmask; -+ -+ assert(high <= 7 && high >= low); -+ -+ 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) -+ { -+ size_t n; -+ uint8_t bitmask; -+ -+ assert(high <= 7 && high >= low); -+ -+ n = high - low + 1; -+ bitmask = (uint8_t) ((1 << n) - 1); -+ assert((bits & ~bitmask) == 0); -+ -+ *val |= (uint8_t)(bits << low); -+ } -+ -+ -+ 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]; -+ -+ uint8_t checksum = uint32_t(256) - uint32_t(sum); -+ -+ block[127] = checksum; -+ } -+ -+ static bool validate_block_checksum(const uint8_t* data) -+ { -+ uint8_t sum = 0; -+ size_t i; -+ -+ for (i = 0; i < EDID_BLOCK_SIZE; i++) { -+ sum += data[i]; -+ } -+ -+ return sum == 0; -+ } -+ -+ static uint8_t encode_max_luminance(float nits) -+ { -+ if (nits == 0.0f) -+ return 0; -+ -+ return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); -+ } -+ -+ std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) -+ { -+ // A zero length indicates that the edid parsing failed. -+ if ( pEdid.empty() ) -+ return std::nullopt; -+ -+ std::vector edid( pEdid.begin(), pEdid.end() ); -+ -+ if ( g_bRotated ) -+ { -+ // 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++) -+ { -+ 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]); -+ break; -+ } -+ } -+ -+ 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() ) -+ return std::nullopt; -+ -+ // TODO: Allow for override of min luminance -+#if 0 -+ float flMaxPeakLuminance = g_ColorMgmt.pending.hdrTonemapDisplayMetadata.BIsValid() ? -+ g_ColorMgmt.pending.hdrTonemapDisplayMetadata.flWhitePointNits : -+ g_ColorMgmt.pending.flInternalDisplayBrightness; -+#endif -+ // TODO(JoshA): Need to resolve flInternalDisplayBrightness vs new connector hdrinfo mechanism. -+ -+ 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" */ -+ }; -+ -+ 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++) -+ { -+ 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) -+ { -+ edid_log.infof("Hmmmm.... dtd start is 0. Interesting... Not going further! :-("); -+ continue; -+ } -+ if (flags != 0) -+ { -+ 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) -+ { -+ 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) -+ { -+ 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 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_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)); -+ has_hdr_metadata_block = true; -+ } -+ } -+ } -+ -+ j += 1 + data_block_size; // account for header size. -+ } -+ -+ 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."); -+ -+ // Assert that the end of the data blocks == dtd_start -+ if (dtd_start != j) -+ { -+ 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); -+ 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) -+ -+ // 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)); -+ } -+ -+ 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) -+ { -+ 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"); -+ -+ return edid; -+ } -+ -+ const char *GetPatchedEdidPath() -+ { -+ const char *pszPatchedEdidPath = getenv( "GAMESCOPE_PATCHED_EDID_FILE" ); -+ if ( !pszPatchedEdidPath || !*pszPatchedEdidPath ) -+ return nullptr; -+ -+ return pszPatchedEdidPath; -+ } -+ -+ void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) -+ { -+ const char *pszPatchedEdidPath = GetPatchedEdidPath(); -+ if ( !pszPatchedEdidPath ) -+ return; -+ -+ std::span pEdidToWrite = pEdid; -+ -+ auto oPatchedEdid = PatchEdid( pEdid, hdrInfo ); -+ if ( oPatchedEdid ) -+ pEdidToWrite = std::span{ oPatchedEdid->begin(), oPatchedEdid->end() }; -+ -+ char szTmpFilename[PATH_MAX]; -+ snprintf( szTmpFilename, sizeof( szTmpFilename ), "%s.tmp", pszPatchedEdidPath ); -+ -+ FILE *pFile = fopen( szTmpFilename, "wb" ); -+ if ( !pFile ) -+ { -+ edid_log.errorf( "Couldn't open file: %s", szTmpFilename ); -+ return; -+ } -+ -+ fwrite( pEdidToWrite.data(), 1, pEdidToWrite.size(), pFile ); -+ fflush( pFile ); -+ fclose( pFile ); -+ -+ // Flip it over. -+ rename( szTmpFilename, pszPatchedEdidPath ); -+ edid_log.infof( "Wrote new edid to: %s", pszPatchedEdidPath ); -+ } -+} -diff --git a/src/edid.h b/src/edid.h -new file mode 100644 -index 000000000..e6ab74688 ---- /dev/null -+++ b/src/edid.h -@@ -0,0 +1,16 @@ -+#pragma once -+ -+#include -+#include -+#include -+#include -+ -+namespace gamescope -+{ -+ struct BackendConnectorHDRInfo; -+ -+ const char *GetPatchedEdidPath(); -+ void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); -+ -+ std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); -+} -\ No newline at end of file -diff --git a/src/gamescope_shared.h b/src/gamescope_shared.h -index fdbcfa481..f34174e59 100644 ---- a/src/gamescope_shared.h -+++ b/src/gamescope_shared.h -@@ -2,6 +2,8 @@ - - namespace gamescope - { -+ class BackendBlob; -+ - enum GamescopeKnownDisplays - { - GAMESCOPE_KNOWN_DISPLAY_UNKNOWN, -@@ -41,3 +43,25 @@ inline bool ColorspaceIsHDR( GamescopeAppTextureColorspace colorspace ) - colorspace == GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ; - } - -+enum GamescopeSelection -+{ -+ GAMESCOPE_SELECTION_CLIPBOARD, -+ GAMESCOPE_SELECTION_PRIMARY, -+ -+ 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_AUTO, -+}; -+ -+// Disable partial composition for now until we get -+// composite priorities working in libliftoff + also -+// use the proper libliftoff composite plane system. -+static constexpr bool kDisablePartialComposition = true; -diff --git a/src/hdmi.h b/src/hdmi.h -new file mode 100644 -index 000000000..57d5257e2 ---- /dev/null -+++ b/src/hdmi.h -@@ -0,0 +1,7 @@ -+#pragma once -+ -+/* from CTA-861-G */ -+#define HDMI_EOTF_SDR 0 -+#define HDMI_EOTF_TRADITIONAL_HDR 1 -+#define HDMI_EOTF_ST2084 2 -+#define HDMI_EOTF_HLG 3 -diff --git a/src/headless.cpp b/src/headless.cpp -new file mode 100644 -index 000000000..493aea13e ---- /dev/null -+++ b/src/headless.cpp -@@ -0,0 +1,248 @@ -+#include "backend.h" -+ -+namespace gamescope -+{ -+ class CHeadlessConnector final : public IBackendConnector -+ { -+ public: -+ 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 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 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; -+ } -+ -+ 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"; -+ } -+ -+ private: -+ BackendConnectorHDRInfo m_HDRInfo{}; -+ }; -+ -+ class CHeadlessBackend final : public CBaseBackend -+ { -+ public: -+ CHeadlessBackend() -+ { -+ } -+ -+ virtual ~CHeadlessBackend() -+ { -+ } -+ -+ virtual bool Init() override -+ { -+ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override -+ { -+ *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; -+ *pOverlayPlaneFormat = VK_FORMAT_B8G8R8A8_UNORM; -+ } -+ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override -+ { -+ return true; -+ } -+ -+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override -+ { -+ return 0; -+ } -+ -+ virtual void DirtyState( bool bForce, bool bForceModeset ) override -+ { -+ } -+ -+ virtual bool PollState() override -+ { -+ return false; -+ } -+ -+ virtual std::shared_ptr CreateBackendBlob( std::span data ) override -+ { -+ return std::make_shared( data ); -+ } -+ -+ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override -+ { -+ return 0; -+ } -+ -+ virtual void LockBackendFb( uint32_t uFbId ) override -+ { -+ abort(); -+ } -+ virtual void UnlockBackendFb( uint32_t uFbId ) override -+ { -+ abort(); -+ } -+ virtual void DropBackendFb( uint32_t uFbId ) override -+ { -+ abort(); -+ } -+ -+ 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 IsVRRActive() const override -+ { -+ return false; -+ } -+ -+ 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 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 -+ ///////////////////////// -+ -+ template <> -+ bool IBackend::Set() -+ { -+ return Set( new CHeadlessBackend{} ); -+ } -+ -+} -\ No newline at end of file -diff --git a/src/main.cpp b/src/main.cpp -index 02cc5ce3a..42110675d 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -20,15 +20,11 @@ - - #include "main.hpp" - #include "steamcompmgr.hpp" --#include "drm.hpp" - #include "rendervulkan.hpp" --#include "sdlwindow.hpp" - #include "wlserver.hpp" - #include "gpuvis_trace_utils.h" - --#if HAVE_OPENVR --#include "vr_session.hpp" --#endif -+#include "backends.h" - - #if HAVE_PIPEWIRE - #include "pipewire.hpp" -@@ -267,9 +263,6 @@ bool g_bOutputHDREnabled = false; - bool g_bFullscreen = false; - bool g_bForceRelativeMouse = false; - --bool g_bIsNested = false; --bool g_bHeadless = false; -- - bool g_bGrabbed = false; - - float g_mouseSensitivity = 1.0; -@@ -281,6 +274,8 @@ GamescopeUpscaleFilter g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR; - GamescopeUpscaleScaler g_wantedUpscaleScaler = GamescopeUpscaleScaler::AUTO; - int g_upscaleFilterSharpness = 2; - -+gamescope::GamescopeModeGeneration g_eGamescopeModeGeneration = gamescope::GAMESCOPE_MODE_GENERATE_CVT; -+ - bool g_bBorderlessOutputWindow = false; - - int g_nXWaylandCount = 1; -@@ -300,36 +295,6 @@ uint32_t g_preferDeviceID = 0; - - pthread_t g_mainThread; - --bool BIsNested() --{ -- return g_bIsNested; --} -- --bool BIsHeadless() --{ -- return g_bHeadless; --} -- --#if HAVE_OPENVR --bool g_bUseOpenVR = false; --bool BIsVRSession( void ) --{ -- return g_bUseOpenVR; --} --#else --bool BIsVRSession( void ) --{ -- return false; --} --#endif -- --bool BIsSDLSession( void ) --{ -- return g_bIsNested && !g_bHeadless && !BIsVRSession(); --} -- -- --static bool initOutput(int preferredWidth, int preferredHeight, int preferredRefresh); - static void steamCompMgrThreadRun(int argc, char **argv); - - static std::string build_optstring(const struct option *options) -@@ -367,16 +332,17 @@ static gamescope::GamescopeModeGeneration parse_gamescope_mode_generation( const - } - } - --static enum g_panel_orientation 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 PANEL_ORIENTATION_0; -+ return GAMESCOPE_PANEL_ORIENTATION_0; - } else if (strcmp(str, "right") == 0) { -- return PANEL_ORIENTATION_270; -+ return GAMESCOPE_PANEL_ORIENTATION_270; - } else if (strcmp(str, "left") == 0) { -- return PANEL_ORIENTATION_90; -+ return GAMESCOPE_PANEL_ORIENTATION_90; - } else if (strcmp(str, "upsidedown") == 0) { -- return PANEL_ORIENTATION_180; -+ return GAMESCOPE_PANEL_ORIENTATION_180; - } else { - fprintf( stderr, "gamescope: invalid value for --force-orientation\n" ); - exit(1); -@@ -545,19 +511,62 @@ static bool CheckWaylandPresentationTime() - return g_bSupportsWaylandPresentationTime; - } - -+#if 0 -+static bool IsInDebugSession() -+{ -+ static FILE *fp; -+ if ( !fp ) -+ { -+ fp = fopen( "/proc/self/status", "r" ); -+ } -+ -+ char rgchLine[256]; rgchLine[0] = '\0'; -+ int nTracePid = 0; -+ if ( fp ) -+ { -+ const char *pszSearchString = "TracerPid:"; -+ const uint cchSearchString = strlen( pszSearchString ); -+ rewind( fp ); -+ fflush( fp ); -+ while ( fgets( rgchLine, sizeof(rgchLine), fp ) ) -+ { -+ if ( !strncasecmp( pszSearchString, rgchLine, cchSearchString ) ) -+ { -+ char *pszVal = rgchLine+cchSearchString+1; -+ nTracePid = atoi( pszVal ); -+ break; -+ } -+ } -+ } -+ return nTracePid != 0; -+} -+#endif - - 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; -+ -+// This will go away when we remove the getopt stuff from vr session. -+// For now... -+int g_argc; -+char **g_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; -+ - int o; - int opt_index = -1; - while ((o = getopt_long(argc, argv, gamescope_optstring, gamescope_options, &opt_index)) != -1) -@@ -628,7 +637,7 @@ int main(int argc, char **argv) - } 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_drmModeOrientation = force_orientation( optarg ); -+ g_DesiredInternalOrientation = force_orientation( optarg ); - } else if (strcmp(opt_name, "sharpness") == 0 || - strcmp(opt_name, "fsr-sharpness") == 0) { - g_upscaleFilterSharpness = atoi( optarg ); -@@ -651,15 +660,13 @@ int main(int argc, char **argv) - } else if (strcmp(opt_name, "expose-wayland") == 0) { - g_bExposeWayland = true; - } else if (strcmp(opt_name, "headless") == 0) { -- g_bHeadless = true; -- g_bIsNested = true; -+ eCurrentBackend = gamescope::GamescopeBackend::Headless; - } else if (strcmp(opt_name, "cursor-scale-height") == 0) { - g_nCursorScaleHeight = atoi(optarg); - } - #if HAVE_OPENVR - else if (strcmp(opt_name, "openvr") == 0) { -- g_bUseOpenVR = true; -- g_bIsNested = true; -+ eCurrentBackend = gamescope::GamescopeBackend::OpenVR; - } - #endif - break; -@@ -722,6 +729,13 @@ int main(int argc, char **argv) - setenv( "XCURSOR_SIZE", "256", 1 ); - } - -+#if 0 -+ while( !IsInDebugSession() ) -+ { -+ usleep( 100 ); -+ } -+#endif -+ - raise_fd_limit(); - - if ( gpuvis_trace_init() != -1 ) -@@ -734,9 +748,16 @@ int main(int argc, char **argv) - - g_pOriginalDisplay = getenv("DISPLAY"); - g_pOriginalWaylandDisplay = getenv("WAYLAND_DISPLAY"); -- g_bIsNested = g_pOriginalDisplay != NULL || g_pOriginalWaylandDisplay != NULL; - -- if ( BIsSDLSession() && g_pOriginalWaylandDisplay != NULL ) -+ if ( eCurrentBackend == gamescope::GamescopeBackend::Auto ) -+ { -+ if ( g_pOriginalDisplay != NULL || g_pOriginalWaylandDisplay != NULL ) -+ eCurrentBackend = gamescope::GamescopeBackend::SDL; -+ else -+ eCurrentBackend = gamescope::GamescopeBackend::DRM; -+ } -+ -+ if ( g_pOriginalWaylandDisplay != NULL ) - { - if (CheckWaylandPresentationTime()) - { -@@ -755,40 +776,29 @@ int main(int argc, char **argv) - } - } - -- if ( !wlsession_init() ) -- { -- fprintf( stderr, "Failed to initialize Wayland session\n" ); -- return 1; -- } -- -- if ( BIsSDLSession() ) -+ switch ( eCurrentBackend ) - { -- if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS ) != 0 ) -- { -- fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); -- return 1; -- } -- } -- -- if ( !BIsNested() ) -- { -- g_bForceRelativeMouse = false; -- } -- -+ case gamescope::GamescopeBackend::DRM: -+ gamescope::IBackend::Set(); -+ break; -+ case gamescope::GamescopeBackend::SDL: -+ gamescope::IBackend::Set(); -+ break; - #if HAVE_OPENVR -- if ( BIsVRSession() ) -- { -- if ( !vr_init( argc, argv ) ) -- { -- fprintf( stderr, "Failed to initialize OpenVR runtime\n" ); -- return 1; -- } -- } -+ case gamescope::GamescopeBackend::OpenVR: -+ gamescope::IBackend::Set(); -+ break; - #endif -+ case gamescope::GamescopeBackend::Headless: -+ gamescope::IBackend::Set(); -+ break; -+ default: -+ abort(); -+ } - -- if ( !initOutput( g_nPreferredOutputWidth, g_nPreferredOutputHeight, g_nNestedRefresh ) ) -+ if ( !GetBackend() ) - { -- fprintf( stderr, "Failed to initialize output\n" ); -+ fprintf( stderr, "Failed to create backend.\n" ); - return 1; - } - -@@ -846,13 +856,6 @@ int main(int argc, char **argv) - return 1; - } - --#if HAVE_OPENVR -- if ( BIsVRSession() ) -- { -- vrsession_ime_init(); -- } --#endif -- - gamescope_xwayland_server_t *base_server = wlserver_get_xwayland_server(0); - - setenv("DISPLAY", base_server->get_nested_display_name(), 1); -@@ -909,73 +912,3 @@ static void steamCompMgrThreadRun(int argc, char **argv) - - pthread_kill( g_mainThread, SIGINT ); - } -- --static bool initOutput( int preferredWidth, int preferredHeight, int preferredRefresh ) --{ -- VkInstance instance = vulkan_create_instance(); -- -- if ( BIsNested() ) -- { -- g_nOutputWidth = preferredWidth; -- g_nOutputHeight = preferredHeight; -- g_nOutputRefresh = preferredRefresh; -- -- 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 = 60; -- -- if ( BIsVRSession() ) -- { --#if HAVE_OPENVR -- if ( !vrsession_init() ) -- return false; --#else -- return false; --#endif -- } -- else if ( BIsSDLSession() ) -- { -- if ( !sdlwindow_init() ) -- return false; -- } -- -- VkSurfaceKHR surface = VK_NULL_HANDLE; -- -- if ( BIsSDLSession() ) -- { -- if ( !SDL_Vulkan_CreateSurface( g_SDLWindow, instance, &surface ) ) -- { -- fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s", SDL_GetError() ); -- return false; -- } -- } -- -- if ( !vulkan_init( instance, surface ) ) -- { -- fprintf( stderr, "Failed to initialize Vulkan\n" ); -- return false; -- } -- -- return true; -- } -- else -- { -- if ( !vulkan_init( instance, VK_NULL_HANDLE ) ) -- { -- fprintf( stderr, "Failed to initialize Vulkan\n" ); -- return false; -- } -- -- return init_drm( &g_DRM, preferredWidth, preferredHeight, preferredRefresh ); -- } --} -diff --git a/src/main.hpp b/src/main.hpp -index b3de6b847..a87030b0f 100644 ---- a/src/main.hpp -+++ b/src/main.hpp -@@ -17,14 +17,17 @@ extern int g_nNestedDisplayIndex; - - extern uint32_t g_nOutputWidth; - extern uint32_t g_nOutputHeight; -+extern bool g_bForceRelativeMouse; - extern int g_nOutputRefresh; // Hz - extern bool g_bOutputHDREnabled; -+extern bool g_bForceInternal; - - extern bool g_bFullscreen; - - extern bool g_bGrabbed; - - extern float g_mouseSensitivity; -+extern const char *g_sOutputName; - - enum class GamescopeUpscaleFilter : uint32_t - { -@@ -70,7 +73,3 @@ extern uint32_t g_preferVendorID; - extern uint32_t g_preferDeviceID; - - void restore_fd_limit( void ); --bool BIsNested( void ); --bool BIsHeadless( void ); --bool BIsSDLSession( void ); --bool BIsVRSession( void ); -diff --git a/src/meson.build b/src/meson.build -index 4f88d6ecb..a9e64990c 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -105,6 +105,8 @@ src = [ - 'steamcompmgr.cpp', - 'color_helpers.cpp', - 'main.cpp', -+ 'edid.cpp', -+ 'headless.cpp', - 'wlserver.cpp', - 'drm.cpp', - 'modegen.cpp', -@@ -115,6 +117,7 @@ src = [ - 'ime.cpp', - 'mangoapp.cpp', - 'reshade_effect_manager.cpp', -+ 'backend.cpp', - ] - - src += spirv_shaders -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 357018607..51a5d5c7f 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -21,14 +21,12 @@ - // 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 "drm_include.h" -+ - #include "rendervulkan.hpp" - #include "main.hpp" - #include "steamcompmgr.hpp" --#include "sdlwindow.hpp" - #include "log.hpp" --#if HAVE_OPENVR --#include "vr_session.hpp" --#endif - - #include "cs_composite_blit.h" - #include "cs_composite_blur.h" -@@ -47,6 +45,9 @@ - - #include "reshade_effect_manager.hpp" - -+#include "SDL.h" -+#include "SDL_vulkan.h" -+ - extern bool g_bWasPartialComposite; - - static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601_limited = {{ -@@ -100,6 +101,12 @@ VulkanOutput_t g_output; - - uint32_t g_uCompositeDebug = 0u; - -+template -+static bool Contains( const std::span x, T value ) -+{ -+ return std::ranges::any_of( x, std::bind_front(std::equal_to{}, value) ); -+} -+ - static std::map< VkFormat, std::map< uint64_t, VkDrmFormatModifierPropertiesEXT > > DRMModifierProps = {}; - static std::vector< uint32_t > sampledShmFormats{}; - static struct wlr_drm_format_set sampledDRMFormats = {}; -@@ -409,6 +416,11 @@ bool CVulkanDevice::createDevice() - - vk_log.infof( "physical device %s DRM format modifiers", m_bSupportsModifiers ? "supports" : "does not support" ); - -+ if ( !GetBackend()->ValidPhysicalDevice( physDev() ) ) -+ return false; -+ -+ // XXX(JoshA): Move this to ValidPhysicalDevice. -+ // We need to refactor some Vulkan stuff to do that though. - if ( hasDrmProps ) { - VkPhysicalDeviceDrmPropertiesEXT drmProps = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, -@@ -419,7 +431,7 @@ bool CVulkanDevice::createDevice() - }; - vk.GetPhysicalDeviceProperties2( physDev(), &props2 ); - -- if ( !BIsNested() && !drmProps.hasPrimary ) { -+ if ( !GetBackend()->UsesVulkanSwapchain() && !drmProps.hasPrimary ) { - vk_log.errorf( "physical device has no primary node" ); - return false; - } -@@ -500,7 +512,7 @@ bool CVulkanDevice::createDevice() - - std::vector< const char * > enabledExtensions; - -- if ( BIsNested() == true ) -+ if ( GetBackend()->UsesVulkanSwapchain() ) - { - enabledExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME ); - enabledExtensions.push_back( VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME ); -@@ -526,12 +538,8 @@ bool CVulkanDevice::createDevice() - if ( supportsHDRMetadata ) - enabledExtensions.push_back( VK_EXT_HDR_METADATA_EXTENSION_NAME ); - -- if ( BIsVRSession() ) -- { --#if HAVE_OPENVR -- vrsession_append_device_exts( physDev(), enabledExtensions ); --#endif -- } -+ for ( auto& extension : GetBackend()->GetDeviceExtensions( physDev() ) ) -+ enabledExtensions.push_back( extension ); - - #if 0 - VkPhysicalDeviceMaintenance5FeaturesKHR maintenance5 = { -@@ -1609,7 +1617,7 @@ void CVulkanCmdBuffer::prepareSrcImage(CVulkanTexture *image) - if (!result.second) - return; - // using the swapchain image as a source without writing to it doesn't make any sense -- assert(image->swapchainImage() == false); -+ assert(image->outputImage() == false); - result.first->second.needsImport = image->externalImage(); - result.first->second.needsExport = image->externalImage(); - } -@@ -1622,7 +1630,7 @@ void CVulkanCmdBuffer::prepareDestImage(CVulkanTexture *image) - return; - result.first->second.discarded = true; - result.first->second.needsExport = image->externalImage(); -- result.first->second.needsPresentLayout = image->swapchainImage(); -+ result.first->second.needsPresentLayout = image->outputImage(); - } - - void CVulkanCmdBuffer::discardImage(CVulkanTexture *image) -@@ -1663,8 +1671,6 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) - bool isExport = flush && state.needsExport; - bool isPresent = flush && state.needsPresentLayout; - -- VkImageLayout presentLayout = BIsVRSession() ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; -- - if (!state.discarded && !state.dirty && !state.needsImport && !isExport && !isPresent) - continue; - -@@ -1680,7 +1686,7 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) - .srcAccessMask = state.dirty ? write_bits : 0u, - .dstAccessMask = flush ? 0u : read_bits | write_bits, - .oldLayout = (state.discarded || state.needsImport) ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL, -- .newLayout = isPresent ? presentLayout : 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(), -@@ -1699,7 +1705,7 @@ void CVulkanCmdBuffer::insertBarrier(bool flush) - 0, 0, nullptr, 0, nullptr, barriers.size(), barriers.data()); - } - --static CVulkanDevice g_device; -+CVulkanDevice g_device; - - static bool allDMABUFsEqual( wlr_dmabuf_attributes *pDMA ) - { -@@ -1827,9 +1833,9 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - } - -- if ( flags.bSwapchain == true ) -+ if ( flags.bOutputImage == true ) - { -- m_bSwapchain = true; -+ m_bOutputImage = true; - } - - m_bExternal = pDMA || flags.bExportable == true; -@@ -1916,7 +1922,8 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - } - - std::vector modifiers = {}; -- if ( flags.bFlippable == true && g_device.supportsModifiers() && !pDMA ) -+ // 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 ); - -@@ -1931,10 +1938,10 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - } - else - { -- const struct wlr_drm_format *drmFormatDesc = wlr_drm_format_set_get( &g_DRM.primary_formats, drmFormat ); -- assert( drmFormatDesc != nullptr ); -- possibleModifiers = drmFormatDesc->modifiers; -- numPossibleModifiers = drmFormatDesc->len; -+ std::span modifiers = GetBackend()->GetSupportedModifiers( drmFormat ); -+ assert( !modifiers.empty() ); -+ possibleModifiers = modifiers.data(); -+ numPossibleModifiers = modifiers.size(); - } - - for ( size_t i = 0; i < numPossibleModifiers; i++ ) -@@ -1976,7 +1983,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - imageInfo.tiling = tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; - } - -- if ( flags.bFlippable == true && tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) -+ if ( GetBackend()->UsesModifiers() && flags.bFlippable == true && tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT ) - { - // We want to scan-out the image - wsiImageCreateInfo = { -@@ -2240,11 +2247,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - - if ( flags.bFlippable == true ) - { -- m_FBID = drm_fbid_from_dmabuf( &g_DRM, nullptr, &m_dmabuf ); -- if ( m_FBID == 0 ) { -- vk_log.errorf( "drm_fbid_from_dmabuf failed" ); -- return false; -- } -+ m_FBID = GetBackend()->ImportDmabufToBackend( nullptr, &m_dmabuf ); - } - - bool bHasAlpha = pDMA ? DRMFormatHasAlpha( pDMA->format ) : true; -@@ -2357,7 +2360,7 @@ bool CVulkanTexture::BInitFromSwapchain( VkImage image, uint32_t width, uint32_t - m_format = format; - m_contentWidth = width; - m_contentHeight = height; -- m_bSwapchain = true; -+ m_bOutputImage = true; - - VkImageViewCreateInfo createInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, -@@ -2430,7 +2433,7 @@ CVulkanTexture::~CVulkanTexture( void ) - - if ( m_FBID != 0 ) - { -- drm_drop_fbid( &g_DRM, m_FBID ); -+ GetBackend()->DropBackendFb( m_FBID ); - m_FBID = 0; - } - -@@ -2560,10 +2563,12 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) - - if ( !g_device.supportsModifiers() ) - { -- if ( BIsNested() == false && !wlr_drm_format_set_has( &g_DRM.formats, drmFormat, DRM_FORMAT_MOD_INVALID ) ) -+ if ( GetBackend()->UsesModifiers() ) - { -- return false; -+ if ( !Contains( GetBackend()->GetSupportedModifiers( drmFormat ), DRM_FORMAT_MOD_INVALID ) ) -+ return false; - } -+ - wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, DRM_FORMAT_MOD_INVALID ); - return false; - } -@@ -2604,18 +2609,13 @@ bool vulkan_init_format(VkFormat format, uint32_t drmFormat) - { - continue; - } -- if ( BIsNested() == false && !wlr_drm_format_set_has( &g_DRM.formats, drmFormat, modifier ) ) -- { -- continue; -- } -- if ( BIsNested() == false && drmFormat == DRM_FORMAT_NV12 && modifier == DRM_FORMAT_MOD_LINEAR && g_bRotated ) -+ -+ if ( GetBackend()->UsesModifiers() ) - { -- // If embedded and rotated, blacklist NV12 LINEAR because -- // amdgpu won't support direct scan-out. Since only pure -- // Wayland clients can submit NV12 buffers, this should only -- // affect streaming_client. -- continue; -+ if ( !Contains( GetBackend()->GetSupportedModifiers( drmFormat ), modifier ) ) -+ continue; - } -+ - wlr_drm_format_set_add( &sampledDRMFormats, drmFormat, modifier ); - } - -@@ -2684,7 +2684,7 @@ static void present_wait_thread_func( void ) - { - g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu ); - uint64_t vblanktime = get_time_in_nanos(); -- g_VBlankTimer.MarkVBlank( vblanktime, true ); -+ GetVBlankTimer().MarkVBlank( vblanktime, true ); - mangoapp_output_update( vblanktime ); - } - } -@@ -2707,7 +2707,7 @@ void vulkan_update_swapchain_hdr_metadata( VulkanOutput_t *pOutput ) - return; - } - -- hdr_metadata_infoframe &infoframe = g_output.swapchainHDRMetadata->metadata.hdmi_metadata_type1; -+ const hdr_metadata_infoframe &infoframe = g_output.swapchainHDRMetadata->View().hdmi_metadata_type1; - VkHdrMetadataEXT metadata = - { - .sType = VK_STRUCTURE_TYPE_HDR_METADATA_EXT, -@@ -2830,7 +2830,7 @@ std::shared_ptr vulkan_get_hacky_blank_texture() - std::shared_ptr vulkan_create_debug_blank_texture() - { - CVulkanTexture::createFlags flags; -- flags.bFlippable = !BIsNested(); -+ flags.bFlippable = true; - flags.bSampled = true; - flags.bTransferDst = true; - -@@ -2853,46 +2853,6 @@ std::shared_ptr vulkan_create_debug_blank_texture() - return texture; - } - --#if HAVE_OPENVR --std::shared_ptr vulkan_create_debug_white_texture() --{ -- CVulkanTexture::createFlags flags; -- flags.bMappable = true; -- flags.bSampled = true; -- flags.bTransferSrc = true; -- flags.bLinear = true; -- -- auto texture = std::make_shared(); -- bool bRes = texture->BInit( g_nOutputWidth, g_nOutputHeight, 1u, VulkanFormatToDRM( VK_FORMAT_B8G8R8A8_UNORM ), flags); -- assert( bRes ); -- -- memset( texture->mappedData(), 0xFF, texture->width() * texture->height() * 4 ); -- -- return texture; --} -- --void vulkan_present_to_openvr( void ) --{ -- //static auto texture = vulkan_create_debug_white_texture(); -- auto texture = vulkan_get_last_output_image( false, false ); -- -- vr::VRVulkanTextureData_t data = -- { -- .m_nImage = (uint64_t)(uintptr_t)texture->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 = texture->width(), -- .m_nHeight = texture->height(), -- .m_nFormat = texture->format(), -- .m_nSampleCount = 1, -- }; -- vrsession_present(&data); --} --#endif -- - bool vulkan_supports_hdr10() - { - for ( auto& format : g_output.surfaceFormats ) -@@ -3038,11 +2998,11 @@ bool vulkan_remake_swapchain( void ) - static bool vulkan_make_output_images( VulkanOutput_t *pOutput ) - { - CVulkanTexture::createFlags outputImageflags; -- outputImageflags.bFlippable = !BIsNested(); -+ outputImageflags.bFlippable = true; - outputImageflags.bStorage = true; - outputImageflags.bTransferSrc = true; // for screenshots - outputImageflags.bSampled = true; // for pipewire blits -- outputImageflags.bSwapchain = BIsVRSession(); -+ outputImageflags.bOutputImage = true; - - pOutput->outputImages.resize(3); // extra image for partial composition. - pOutput->outputImagesPartialOverlay.resize(3); -@@ -3137,12 +3097,7 @@ bool vulkan_make_output() - - VkResult result; - -- if ( BIsVRSession() || BIsHeadless() ) -- { -- pOutput->outputFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; -- vulkan_make_output_images( pOutput ); -- } -- else if ( BIsSDLSession() ) -+ if ( GetBackend()->UsesVulkanSwapchain() ) - { - result = g_device.vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( g_device.physDev(), pOutput->surface, &pOutput->surfaceCaps ); - if ( result != VK_SUCCESS ) -@@ -3195,9 +3150,8 @@ bool vulkan_make_output() - } - else - { -- pOutput->outputFormat = DRMFormatToVulkan( g_nDRMFormat, false ); -- pOutput->outputFormatOverlay = DRMFormatToVulkan( g_nDRMFormatOverlay, false ); -- -+ GetBackend()->GetPreferredOutputFormat( &pOutput->outputFormat, &pOutput->outputFormatOverlay ); -+ - if ( pOutput->outputFormat == VK_FORMAT_UNDEFINED ) - { - vk_log.errorf( "failed to find Vulkan format suitable for KMS" ); -@@ -3261,55 +3215,41 @@ static bool init_nis_data() - return true; - } - --VkInstance vulkan_create_instance( void ) -+VkInstance vulkan_get_instance( void ) - { -- VkResult result = VK_ERROR_INITIALIZATION_FAILED; -- -- std::vector< const char * > sdlExtensions; -- if ( BIsVRSession() ) -- { --#if HAVE_OPENVR -- vrsession_append_instance_exts( sdlExtensions ); --#endif -- } -- else if ( BIsSDLSession() ) -+ static VkInstance s_pVkInstance = []() -> VkInstance - { -- if ( SDL_Vulkan_LoadLibrary( nullptr ) != 0 ) -- { -- fprintf(stderr, "SDL_Vulkan_LoadLibrary failed: %s\n", SDL_GetError()); -- return nullptr; -- } -+ VkResult result = VK_ERROR_INITIALIZATION_FAILED; - -- unsigned int extCount = 0; -- SDL_Vulkan_GetInstanceExtensions( nullptr, &extCount, nullptr ); -- sdlExtensions.resize( extCount ); -- SDL_Vulkan_GetInstanceExtensions( nullptr, &extCount, sdlExtensions.data() ); -- } -+ 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 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)sdlExtensions.size(), -- .ppEnabledExtensionNames = sdlExtensions.data(), -- }; -+ const VkInstanceCreateInfo createInfo = { -+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, -+ .pApplicationInfo = &appInfo, -+ .enabledExtensionCount = (uint32_t)instanceExtensions.size(), -+ .ppEnabledExtensionNames = instanceExtensions.data(), -+ }; - -- VkInstance instance = nullptr; -- result = vkCreateInstance(&createInfo, 0, &instance); -- if ( result != VK_SUCCESS ) -- { -- vk_errorf( result, "vkCreateInstance failed" ); -- } -+ VkInstance instance = nullptr; -+ result = vkCreateInstance(&createInfo, 0, &instance); -+ if ( result != VK_SUCCESS ) -+ { -+ vk_errorf( result, "vkCreateInstance failed" ); -+ } -+ -+ return instance; -+ }(); - -- return instance; -+ return s_pVkInstance; - } - - bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ) -@@ -3320,7 +3260,7 @@ bool vulkan_init( VkInstance instance, VkSurfaceKHR surface ) - if (!init_nis_data()) - return false; - -- if (BIsNested() && !BIsVRSession()) -+ if ( GetBackend()->UsesVulkanSwapchain() ) - { - std::thread present_wait_thread( present_wait_thread_func ); - present_wait_thread.detach(); -@@ -3451,7 +3391,7 @@ struct BlitPushData_t - - if (layer->ctm) - { -- ctm[i] = layer->ctm->matrix; -+ ctm[i] = layer->ctm->View(); - } - else - { -@@ -3586,7 +3526,7 @@ struct RcasPushData_t - - if (layer->ctm) - { -- ctm[i] = layer->ctm->matrix; -+ ctm[i] = layer->ctm->View(); - } - else - { -@@ -3903,7 +3843,7 @@ std::optional vulkan_composite( struct FrameInfo_t *frameInfo, std::sh - - uint64_t sequence = g_device.submit(std::move(cmdBuffer)); - -- if ( !BIsSDLSession() && pOutputOverride == nullptr && increment ) -+ if ( !GetBackend()->UsesVulkanSwapchain() && pOutputOverride == nullptr && increment ) - { - g_output.nOutImage = ( g_output.nOutImage + 1 ) % 3; - } -diff --git a/src/rendervulkan.hpp b/src/rendervulkan.hpp -index f090344ae..e07da598c 100644 ---- a/src/rendervulkan.hpp -+++ b/src/rendervulkan.hpp -@@ -20,9 +20,6 @@ - - class CVulkanCmdBuffer; - --struct wlserver_ctm; --struct wlserver_hdr_metadata; -- - // 1: Fade Plane (Fade outs between switching focus) - // 2: Video Underlay (The actual video) - // 3: Video Streaming UI (Game, App) -@@ -141,7 +138,7 @@ class CVulkanTexture - bTransferDst = false; - bLinear = false; - bExportable = false; -- bSwapchain = false; -+ bOutputImage = false; - bColorAttachment = false; - imageType = VK_IMAGE_TYPE_2D; - } -@@ -154,7 +151,7 @@ class CVulkanTexture - bool bTransferDst : 1; - bool bLinear : 1; - bool bExportable : 1; -- bool bSwapchain : 1; -+ bool bOutputImage : 1; - bool bColorAttachment : 1; - VkImageType imageType; - }; -@@ -178,7 +175,7 @@ class CVulkanTexture - 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 swapchainImage() { return m_bSwapchain; } -+ 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; } -@@ -206,7 +203,7 @@ class CVulkanTexture - private: - bool m_bInitialized = false; - bool m_bExternal = false; -- bool m_bSwapchain = false; -+ bool m_bOutputImage = false; - - uint32_t m_drmFormat = DRM_FORMAT_INVALID; - -@@ -264,6 +261,7 @@ struct FrameInfo_t - { - bool useFSRLayer0; - bool useNISLayer0; -+ bool bFadingOut; - BlurMode blurLayer0; - int blurRadius; - -@@ -291,7 +289,7 @@ struct FrameInfo_t - bool blackBorder; - bool applyColorMgmt; // drm only - -- std::shared_ptr ctm; -+ std::shared_ptr ctm; - - GamescopeAppTextureColorspace colorspace; - -@@ -367,7 +365,7 @@ namespace CompositeDebugFlag - static constexpr uint32_t Tonemap_Reinhard = 1u << 7; - }; - --VkInstance vulkan_create_instance(void); -+VkInstance vulkan_get_instance(void); - bool vulkan_init(VkInstance instance, VkSurfaceKHR surface); - bool vulkan_init_formats(void); - bool vulkan_make_output(); -@@ -382,9 +380,6 @@ std::shared_ptr vulkan_get_last_output_image( bool partial, bool - std::shared_ptr 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 ); --#if HAVE_OPENVR --void vulkan_present_to_openvr( void ); --#endif - - void vulkan_garbage_collect( void ); - bool vulkan_remake_swapchain( void ); -@@ -438,7 +433,7 @@ struct gamescope_color_mgmt_t - glm::vec2 outputVirtualWhite = { 0.f, 0.f }; - EChromaticAdaptationMethod chromaticAdaptationMode = k_EChromaticAdapatationMethod_Bradford; - -- std::shared_ptr appHDRMetadata = nullptr; -+ std::shared_ptr appHDRMetadata; - - bool operator == (const gamescope_color_mgmt_t&) const = default; - bool operator != (const gamescope_color_mgmt_t&) const = default; -@@ -487,7 +482,7 @@ struct VulkanOutput_t - std::vector< VkPresentModeKHR > presentModes; - - -- std::shared_ptr swapchainHDRMetadata; -+ std::shared_ptr swapchainHDRMetadata; - VkSwapchainKHR swapChain; - VkFence acquireFence; - -@@ -918,3 +913,5 @@ uint32_t DRMFormatGetBPP( uint32_t nDRMFormat ); - bool vulkan_supports_hdr10(); - - void vulkan_wait_idle(); -+ -+extern CVulkanDevice g_device; -diff --git a/src/sdlscancodetable.hpp b/src/sdlscancodetable.hpp -index 17aa90256..9ae0a38ad 100644 ---- a/src/sdlscancodetable.hpp -+++ b/src/sdlscancodetable.hpp -@@ -1,5 +1,5 @@ - --static uint32_t s_ScancodeTable[] = -+static const uint32_t s_ScancodeTable[] = - { - KEY_RESERVED, /* SDL_SCANCODE_UNKNOWN 0 */ - KEY_RESERVED, /* SDL_SCANCODE_UNKNOWN 1 */ -@@ -287,3 +287,25 @@ static uint32_t s_ScancodeTable[] = - KEY_PROG1, /* SDL_SCANCODE_APP1 283 */ - KEY_RESERVED, /* SDL_SCANCODE_APP2 284 */ - }; -+ -+inline uint32_t SDLScancodeToLinuxKey( uint32_t nScancode ) -+{ -+ if ( nScancode < sizeof( s_ScancodeTable ) / sizeof( s_ScancodeTable[0] ) ) -+ { -+ return s_ScancodeTable[ nScancode ]; -+ } -+ return KEY_RESERVED; -+} -+ -+inline int SDLButtonToLinuxButton( int SDLButton ) -+{ -+ switch ( SDLButton ) -+ { -+ case SDL_BUTTON_LEFT: return BTN_LEFT; -+ case SDL_BUTTON_MIDDLE: return BTN_MIDDLE; -+ case SDL_BUTTON_RIGHT: return BTN_RIGHT; -+ case SDL_BUTTON_X1: return BTN_SIDE; -+ case SDL_BUTTON_X2: return BTN_EXTRA; -+ default: return 0; -+ } -+} -diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp -index 1e51eab6c..1974bc229 100644 ---- a/src/sdlwindow.cpp -+++ b/src/sdlwindow.cpp -@@ -13,587 +13,963 @@ - #include "SDL_events.h" - #include "main.hpp" - #include "wlserver.hpp" --#include "sdlwindow.hpp" -+#include -+#include - #include "rendervulkan.hpp" - #include "steamcompmgr.hpp" -+#include "defer.hpp" - - #include "sdlscancodetable.hpp" - --#define DEFAULT_TITLE "gamescope" -- --static bool g_bSDLInitOK = false; --static std::mutex g_SDLInitLock; -- --static bool g_bWindowShown = false; -- - static int g_nOldNestedRefresh = 0; - static bool g_bWindowFocused = true; - - static int g_nOutputWidthPts = 0; - static int g_nOutputHeightPts = 0; - -- -+extern const char *g_pOriginalDisplay; -+extern bool g_bForceHDR10OutputDebug; - extern bool steamMode; - extern bool g_bFirstFrame; -+extern int g_nPreferredOutputWidth; -+extern int g_nPreferredOutputHeight; - --SDL_Window *g_SDLWindow; -- --enum UserEvents -+namespace gamescope - { -- USER_EVENT_TITLE, -- USER_EVENT_VISIBLE, -- USER_EVENT_GRAB, -- USER_EVENT_CURSOR, -+ enum class SDLInitState -+ { -+ SDLInit_Waiting, -+ SDLInit_Success, -+ SDLInit_Failure, -+ }; - -- USER_EVENT_COUNT --}; -+ enum SDLCustomEvents -+ { -+ GAMESCOPE_SDL_EVENT_TITLE, -+ GAMESCOPE_SDL_EVENT_ICON, -+ GAMESCOPE_SDL_EVENT_VISIBLE, -+ GAMESCOPE_SDL_EVENT_GRAB, -+ GAMESCOPE_SDL_EVENT_CURSOR, - --static uint32_t g_unSDLUserEventID; -+ GAMESCOPE_SDL_EVENT_COUNT, -+ }; - --static std::mutex g_SDLWindowTitleLock; --static std::shared_ptr g_SDLWindowTitle; --static std::shared_ptr> g_SDLWindowIcon; --static bool g_bUpdateSDLWindowTitle = false; --static bool g_bUpdateSDLWindowIcon = false; -+ class CSDLConnector final : public IBackendConnector -+ { -+ public: -+ CSDLConnector(); -+ virtual bool Init(); - --struct SDLPendingCursor --{ -- uint32_t width, height, xhot, yhot; -- std::shared_ptr> data; --}; --static std::mutex g_SDLCursorLock; --static SDLPendingCursor g_SDLPendingCursorData; --static bool g_bUpdateSDLCursor = false; -- --static void set_gamescope_selections(); -- --//----------------------------------------------------------------------------- --// Purpose: Convert from the remote scancode to a Linux event keycode --//----------------------------------------------------------------------------- --static inline uint32_t SDLScancodeToLinuxKey( uint32_t nScancode ) --{ -- if ( nScancode < sizeof( s_ScancodeTable ) / sizeof( s_ScancodeTable[0] ) ) -+ 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 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; -+ -+ 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"; -+ } -+ -+ //-- -+ -+ SDL_Window *GetSDLWindow() const { return m_pWindow; } -+ VkSurfaceKHR GetVulkanSurface() const { return m_pVkSurface; } -+ private: -+ SDL_Window *m_pWindow = nullptr; -+ VkSurfaceKHR m_pVkSurface = VK_NULL_HANDLE; -+ BackendConnectorHDRInfo m_HDRInfo{}; -+ }; -+ -+ class CSDLBackend : public CBaseBackend, public INestedHints -+ { -+ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override; -+ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override; -+ -+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override; -+ virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override; -+ virtual bool PollState() override; -+ -+ virtual std::shared_ptr CreateBackendBlob( std::span data ) override; -+ -+ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override; -+ virtual void LockBackendFb( uint32_t uFbId ) override; -+ virtual void UnlockBackendFb( uint32_t uFbId ) override; -+ virtual void DropBackendFb( uint32_t uFbId ) 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 IsVRRActive() const override; -+ virtual bool SupportsPlaneHardwareCursor() const override; -+ -+ virtual bool SupportsTearing() const override; -+ virtual bool UsesVulkanSwapchain() const override; -+ -+ virtual bool IsSessionBased() const override; -+ -+ virtual bool IsVisible() const override; -+ -+ virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override; -+ -+ virtual INestedHints *GetNestedHints() override; -+ -+ /////////////////// -+ // INestedHints -+ /////////////////// -+ -+ 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 std::optional GetHostCursor() override; -+ 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() - { -- return s_ScancodeTable[ nScancode ]; - } -- return KEY_RESERVED; --} - --static inline int SDLButtonToLinuxButton( int SDLButton ) --{ -- switch ( SDLButton ) -+ CSDLConnector::~CSDLConnector() - { -- case SDL_BUTTON_LEFT: return BTN_LEFT; -- case SDL_BUTTON_MIDDLE: return BTN_MIDDLE; -- case SDL_BUTTON_RIGHT: return BTN_RIGHT; -- case SDL_BUTTON_X1: return BTN_SIDE; -- case SDL_BUTTON_X2: return BTN_EXTRA; -- default: return 0; -+ if ( m_pWindow ) -+ SDL_DestroyWindow( m_pWindow ); - } --} - --void updateOutputRefresh( void ) --{ -- int display_index = 0; -- SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 }; -+ 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 = 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; -+ } - -- display_index = SDL_GetWindowDisplayIndex( g_SDLWindow ); -- if ( SDL_GetDesktopDisplayMode( display_index, &mode ) == 0 ) -+ return true; -+ } -+ -+ GamescopeScreenType CSDLConnector::GetScreenType() const - { -- g_nOutputRefresh = mode.refresh_rate; -+ 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; -+ } -+ std::span CSDLConnector::GetModes() const -+ { -+ return std::span{}; - } --} - --extern bool g_bForceRelativeMouse; -+ bool CSDLConnector::SupportsVRR() const -+ { -+ return false; -+ } - --static std::string gamescope_str = DEFAULT_TITLE; -+ std::span CSDLConnector::GetRawEDID() const -+ { -+ return std::span{}; -+ } -+ std::span CSDLConnector::GetValidDynamicRefreshRates() const -+ { -+ return std::span{}; -+ } - --void inputSDLThreadRun( void ) --{ -- pthread_setname_np( pthread_self(), "gamescope-sdl" ); -+ 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; -+ } -+ } - -- SDL_Event event; -- uint32_t key; -- bool bRelativeMouse = false; -+ //////////////// -+ // CSDLBackend -+ //////////////// - -- g_unSDLUserEventID = SDL_RegisterEvents( USER_EVENT_COUNT ); -+ CSDLBackend::CSDLBackend() -+ : m_SDLThread{ [this](){ this->SDLThreadFunc(); } } -+ { -+ } - -- uint32_t nSDLWindowFlags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI; -+ bool CSDLBackend::Init() -+ { -+ m_eSDLInit.wait( SDLInitState::SDLInit_Waiting ); - -- if ( g_bBorderlessOutputWindow == true ) -+ return m_eSDLInit == SDLInitState::SDLInit_Success; -+ } -+ -+ bool CSDLBackend::PostInit() - { -- nSDLWindowFlags |= SDL_WINDOW_BORDERLESS; -+ return true; - } - -- if ( g_bFullscreen == true ) -+ std::span CSDLBackend::GetInstanceExtensions() const - { -- nSDLWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; -+ return std::span{ m_pszInstanceExtensions.begin(), m_pszInstanceExtensions.end() }; - } - -- if ( g_bGrabbed == true ) -+ std::span CSDLBackend::GetDeviceExtensions( VkPhysicalDevice pVkPhysicalDevice ) const - { -- nSDLWindowFlags |= SDL_WINDOW_KEYBOARD_GRABBED; -+ return std::span{}; - } - -- g_SDLWindow = SDL_CreateWindow( DEFAULT_TITLE, -- SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), -- SDL_WINDOWPOS_UNDEFINED_DISPLAY( g_nNestedDisplayIndex ), -- g_nOutputWidth, -- g_nOutputHeight, -- nSDLWindowFlags ); -+ VkImageLayout CSDLBackend::GetPresentLayout() const -+ { -+ return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; -+ } - -- if ( g_SDLWindow == nullptr ) -+ void CSDLBackend::GetPreferredOutputFormat( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const - { -- fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); -- g_SDLInitLock.unlock(); -- return; -+ *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; -+ *pOverlayPlaneFormat = VK_FORMAT_B8G8R8A8_UNORM; - } - -- // Update g_nOutputWidthPts. -+ bool CSDLBackend::ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const - { -- int width, height; -- SDL_GetWindowSize( g_SDLWindow, &width, &height ); -- g_nOutputWidthPts = width; -- g_nOutputHeightPts = height; -+ return true; -+ } -+ -+ int CSDLBackend::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(); - -- #if SDL_VERSION_ATLEAST(2, 26, 0) -- SDL_GetWindowSizeInPixels( g_SDLWindow, &width, &height ); -- #endif -- g_nOutputWidth = width; -- g_nOutputHeight = height; -+ // 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 CSDLBackend::DirtyState( bool bForce, bool bForceModeset ) -+ { -+ } -+ bool CSDLBackend::PollState() -+ { -+ return false; -+ } -+ -+ std::shared_ptr CSDLBackend::CreateBackendBlob( std::span data ) -+ { -+ return std::make_shared( data ); -+ } -+ -+ uint32_t CSDLBackend::ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) -+ { -+ return 0; -+ } -+ void CSDLBackend::LockBackendFb( uint32_t uFbId ) -+ { -+ abort(); -+ } -+ void CSDLBackend::UnlockBackendFb( uint32_t uFbId ) -+ { -+ abort(); -+ } -+ void CSDLBackend::DropBackendFb( uint32_t uFbId ) -+ { -+ abort(); -+ } -+ -+ 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::IsVRRActive() const -+ { -+ return false; -+ } -+ -+ 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::IsVisible() const -+ { -+ return true; -+ } -+ -+ glm::uvec2 CSDLBackend::CursorSurfaceSize( glm::uvec2 uvecSize ) const -+ { -+ return uvecSize; - } - -- if ( g_bForceRelativeMouse ) -+ INestedHints *CSDLBackend::GetNestedHints() - { -- SDL_SetRelativeMouseMode( SDL_TRUE ); -- bRelativeMouse = true; -+ return this; - } - -- SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); -+ /////////////////// -+ // INestedHints -+ /////////////////// - -- g_nOldNestedRefresh = g_nNestedRefresh; -+ 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 ); -+ } -+ -+ std::optional CSDLBackend::GetHostCursor() -+ { -+ if ( !g_pOriginalDisplay ) -+ return std::nullopt; -+ -+ Display *display = XOpenDisplay( g_pOriginalDisplay ); -+ if ( !display ) -+ return std::nullopt; -+ 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 std::nullopt; -+ } -+ -+ XFixesCursorImage *image = XFixesGetCursorImage( display ); -+ if ( !image ) -+ return std::nullopt; -+ 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] ); -+ } -+ } - -- g_bSDLInitOK = true; -- g_SDLInitLock.unlock(); -+ return CursorInfo -+ { -+ .pPixels = std::move( cursorData ), -+ .uWidth = image->width, -+ .uHeight = image->height, -+ .uXHotspot = image->xhot, -+ .uYHotspot = image->yhot, -+ }; -+ } - -- static uint32_t fake_timestamp = 0; -- SDL_Surface *cursor_surface = nullptr; -- SDL_Surface *icon_surface = nullptr; -- SDL_Cursor *cursor = nullptr; -+ void CSDLBackend::OnBackendBlobDestroyed( BackendBlob *pBlob ) -+ { -+ // Do nothing. -+ } - -- while( SDL_WaitEvent( &event ) ) -+ void CSDLBackend::SDLThreadFunc() - { -- fake_timestamp++; -+ pthread_setname_np( pthread_self(), "gamescope-sdl" ); -+ -+ m_uUserEventIdBase = SDL_RegisterEvents( GAMESCOPE_SDL_EVENT_COUNT ); -+ -+ 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; -+ } -+ -+ bool bRelativeMouse = false; -+ if ( g_bForceRelativeMouse ) -+ { -+ SDL_SetRelativeMouseMode( SDL_TRUE ); -+ bRelativeMouse = 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; - -- switch( event.type ) -+ SDL_Event event; -+ while( SDL_WaitEvent( &event ) ) - { -- case SDL_CLIPBOARDUPDATE: -- set_gamescope_selections(); -+ 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 ( bRelativeMouse ) -+ -+ case SDL_MOUSEMOTION: - { -- if ( g_bWindowFocused ) -+ if ( bRelativeMouse ) -+ { -+ if ( g_bWindowFocused ) -+ { -+ wlserver_lock(); -+ wlserver_mousemotion( event.motion.xrel, event.motion.yrel, fake_timestamp ); -+ wlserver_unlock(); -+ } -+ } -+ else - { - wlserver_lock(); -- wlserver_mousemotion( event.motion.xrel, event.motion.yrel, fake_timestamp ); -+ wlserver_touchmotion( -+ event.motion.x / float(g_nOutputWidthPts), -+ event.motion.y / float(g_nOutputHeightPts), -+ 0, -+ fake_timestamp ); - wlserver_unlock(); - } - } -- else -+ break; -+ -+ case SDL_MOUSEBUTTONDOWN: -+ case SDL_MOUSEBUTTONUP: - { - wlserver_lock(); -- wlserver_touchmotion( -- event.motion.x / float(g_nOutputWidthPts), -- event.motion.y / float(g_nOutputHeightPts), -- 0, -- fake_timestamp ); -+ wlserver_mousebutton( SDLButtonToLinuxButton( event.button.button ), -+ event.button.state == SDL_PRESSED, -+ 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(); -+ -+ case SDL_MOUSEWHEEL: -+ { -+ wlserver_lock(); -+ wlserver_mousewheel( -event.wheel.x, -event.wheel.y, 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(); -+ -+ case SDL_FINGERMOTION: -+ { -+ wlserver_lock(); -+ wlserver_touchmotion( 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(); -+ -+ case SDL_FINGERDOWN: -+ { -+ wlserver_lock(); -+ wlserver_touchdown( event.tfinger.x, event.tfinger.y, 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 ) -+ -+ case SDL_FINGERUP: - { -- 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; -- } -+ wlserver_lock(); -+ wlserver_touchup( event.tfinger.fingerId, fake_timestamp ); -+ wlserver_unlock(); - } -- [[fallthrough]]; -- case SDL_KEYUP: -- key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); -+ break; - -- if ( event.type == SDL_KEYUP && ( event.key.keysym.mod & KMOD_LGUI ) ) -+ case SDL_KEYDOWN: - { -- bool handled = true; -- switch ( key ) -+ // 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 ) - { -- case KEY_F: -- g_bFullscreen = !g_bFullscreen; -- SDL_SetWindowFullscreen( g_SDLWindow, 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( g_SDLWindow, g_bGrabbed ? SDL_TRUE : SDL_FALSE ); -- g_bUpdateSDLWindowTitle = true; -- -- SDL_Event event; -- event.type = g_unSDLUserEventID + USER_EVENT_TITLE; -- SDL_PushEvent( &event ); -+ 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; -- default: -- handled = false; -+ } - } -- if ( handled ) -+ } -+ [[fallthrough]]; -+ case SDL_KEYUP: -+ { -+ uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode ); -+ -+ if ( event.type == SDL_KEYUP && ( event.key.keysym.mod & KMOD_LGUI ) ) - { -- break; -+ 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; -+ // On Wayland, clients handle key repetition -+ if ( event.key.repeat ) -+ break; - -- wlserver_lock(); -- wlserver_key( key, event.type == SDL_KEYDOWN, fake_timestamp ); -- wlserver_unlock(); -+ wlserver_lock(); -+ wlserver_key( key, event.type == SDL_KEYDOWN, fake_timestamp ); -+ wlserver_unlock(); -+ } - break; -- case SDL_WINDOWEVENT: -- switch( event.window.event ) -+ -+ case SDL_WINDOWEVENT: - { -- case SDL_WINDOWEVENT_CLOSE: -- raise( SIGTERM ); -- break; -- default: -- break; -- case SDL_WINDOWEVENT_MOVED: -- case SDL_WINDOWEVENT_SHOWN: -- updateOutputRefresh(); -- break; -- case SDL_WINDOWEVENT_SIZE_CHANGED: -- int width, height; -- SDL_GetWindowSize( g_SDLWindow, &width, &height ); -- g_nOutputWidthPts = width; -- g_nOutputHeightPts = height; -+ 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( g_SDLWindow, &width, &height ); -+ SDL_GetWindowSizeInPixels( m_Connector.GetSDLWindow(), &width, &height ); - #endif -- g_nOutputWidth = width; -- g_nOutputHeight = height; -- -- updateOutputRefresh(); -- -- 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; -+ 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 = 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 == g_unSDLUserEventID + USER_EVENT_TITLE ) -+ -+ default: - { -- g_SDLWindowTitleLock.lock(); -- if ( g_bUpdateSDLWindowTitle ) -+ if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_VISIBLE ) ) - { -- std::string tmp_title; -+ bool bVisible = m_bApplicationVisible; - -- const std::string *window_title = g_SDLWindowTitle.get(); -- if (!window_title) -- window_title = &gamescope_str; -+ // 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; - -- g_bUpdateSDLWindowTitle = false; -- if ( g_bGrabbed ) -+ if ( m_bShown != bVisible ) - { -- tmp_title = *window_title; -- tmp_title += " (grabbed)"; -- -- window_title = &tmp_title; -+ m_bShown = bVisible; -+ -+ if ( m_bShown ) -+ { -+ SDL_ShowWindow( m_Connector.GetSDLWindow() ); -+ } -+ else -+ { -+ SDL_HideWindow( m_Connector.GetSDLWindow() ); -+ } - } -- SDL_SetWindowTitle( g_SDLWindow, window_title->c_str() ); - } -- -- if ( g_bUpdateSDLWindowIcon ) -+ else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE ) ) - { -- if ( icon_surface ) -+ 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() ); -+ } -+ else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_ICON ) ) -+ { -+ std::shared_ptr> pIcon = m_pApplicationIcon; -+ -+ if ( m_pIconSurface ) - { -- SDL_FreeSurface( icon_surface ); -- icon_surface = nullptr; -+ SDL_FreeSurface( m_pIconSurface ); -+ m_pIconSurface = nullptr; - } - -- if ( g_SDLWindowIcon && g_SDLWindowIcon->size() >= 3 ) -+ if ( pIcon && pIcon->size() >= 3 ) - { -- const uint32_t width = (*g_SDLWindowIcon)[0]; -- const uint32_t height = (*g_SDLWindowIcon)[1]; -+ const uint32_t uWidth = (*pIcon)[0]; -+ const uint32_t uHeight = (*pIcon)[1]; - -- icon_surface = SDL_CreateRGBSurfaceFrom( -- &(*g_SDLWindowIcon)[2], -- width, height, -- 32, width * sizeof(uint32_t), -+ m_pIconSurface = SDL_CreateRGBSurfaceFrom( -+ &(*pIcon)[2], -+ uWidth, uHeight, -+ 32, uWidth * sizeof(uint32_t), - 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); - } - -- SDL_SetWindowIcon( g_SDLWindow, icon_surface ); -+ SDL_SetWindowIcon( m_Connector.GetSDLWindow(), m_pIconSurface ); - } -- g_SDLWindowTitleLock.unlock(); -- } -- if ( event.type == g_unSDLUserEventID + USER_EVENT_VISIBLE ) -- { -- bool should_show = !!event.user.code; -- -- // 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 ) -- should_show |= !g_bFirstFrame; -- -- if ( g_bWindowShown != should_show ) -+ else if ( event.type == GetUserEventIndex( GAMESCOPE_SDL_EVENT_GRAB ) ) - { -- g_bWindowShown = should_show; -+ 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 ( g_bWindowShown ) -+ if ( m_pCursor ) - { -- SDL_ShowWindow( g_SDLWindow ); -+ SDL_FreeCursor( m_pCursor ); -+ m_pCursor = nullptr; - } -- else -+ -+ if ( pCursorInfo ) - { -- SDL_HideWindow( g_SDLWindow ); -+ 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 ); - } -- } -- } -- if ( event.type == g_unSDLUserEventID + USER_EVENT_GRAB ) -- { -- bool grab = !!event.user.code; -- if ( grab != bRelativeMouse ) -- { -- SDL_SetRelativeMouseMode( grab ? SDL_TRUE : SDL_FALSE ); -- bRelativeMouse = grab; -- } -- } -- if ( event.type == g_unSDLUserEventID + USER_EVENT_CURSOR ) -- { -- std::unique_lock lock(g_SDLCursorLock); -- if ( g_bUpdateSDLCursor ) -- { -- if (cursor_surface) -- SDL_FreeSurface(cursor_surface); -- -- cursor_surface = SDL_CreateRGBSurfaceFrom( -- g_SDLPendingCursorData.data->data(), -- g_SDLPendingCursorData.width, -- g_SDLPendingCursorData.height, -- 32, -- g_SDLPendingCursorData.width * sizeof(uint32_t), -- 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); -- -- if (cursor) -- SDL_FreeCursor(cursor); -- -- cursor = SDL_CreateColorCursor( cursor_surface, g_SDLPendingCursorData.xhot, g_SDLPendingCursorData.yhot ); -- SDL_SetCursor( cursor ); -- g_bUpdateSDLCursor = false; -+ -+ SDL_SetCursor( m_pCursor ); - } - } - break; -+ } - } - } --} -- --std::optional sdlwindow_thread; -- --bool sdlwindow_init( void ) --{ -- g_SDLInitLock.lock(); -- -- std::thread inputSDLThread( inputSDLThreadRun ); -- sdlwindow_thread = inputSDLThread.native_handle(); -- inputSDLThread.detach(); -- -- // When this returns SDL_Init should be over -- g_SDLInitLock.lock(); -- -- return g_bSDLInitOK; --} - --void sdlwindow_shutdown( void ) --{ -- if ( sdlwindow_thread ) -+ uint32_t CSDLBackend::GetUserEventIndex( SDLCustomEvents eEvent ) const - { -- pthread_cancel(*sdlwindow_thread); -- sdlwindow_thread = std::nullopt; -+ return m_uUserEventIdBase + uint32_t( eEvent ); - } --} -- --void sdlwindow_title( std::shared_ptr title, std::shared_ptr> icon ) --{ -- if ( !BIsSDLSession() ) -- return; - -+ void CSDLBackend::PushUserEvent( SDLCustomEvents eEvent ) - { -- std::unique_lock lock(g_SDLWindowTitleLock); -- -- if ( g_SDLWindowTitle != title ) -+ SDL_Event event = - { -- g_SDLWindowTitle = title; -- g_bUpdateSDLWindowTitle = true; -- } -- -- if ( g_SDLWindowIcon != icon ) -- { -- g_SDLWindowIcon = icon; -- g_bUpdateSDLWindowIcon = true; -- } -- -- if ( g_bUpdateSDLWindowTitle || g_bUpdateSDLWindowIcon ) -- { -- SDL_Event event; -- event.type = g_unSDLUserEventID + USER_EVENT_TITLE; -- SDL_PushEvent( &event ); -- } -- } --} -- --void sdlwindow_set_selection(std::string contents, int selection) --{ -- if (selection == CLIPBOARD) -- { -- SDL_SetClipboardText(contents.c_str()); -+ .user = -+ { -+ .type = GetUserEventIndex( eEvent ), -+ }, -+ }; -+ SDL_PushEvent( &event ); - } -- else if (selection == PRIMARYSELECTION) -- { -- SDL_SetPrimarySelectionText(contents.c_str()); -- } --} - --static void set_gamescope_selections() --{ -- char *_clipboard = SDL_GetClipboardText(); -- -- char *_primarySelection = SDL_GetPrimarySelectionText(); -- -- gamescope_set_selection(_clipboard, CLIPBOARD); -- gamescope_set_selection(_primarySelection, PRIMARYSELECTION); -- -- SDL_free(_clipboard); -- SDL_free(_primarySelection); --} -- --void sdlwindow_visible( bool bVisible ) --{ -- if ( !BIsSDLSession() ) -- return; -- -- SDL_Event event; -- event.type = g_unSDLUserEventID + USER_EVENT_VISIBLE; -- event.user.code = bVisible ? 1 : 0; -- SDL_PushEvent( &event ); --} -- --void sdlwindow_grab( bool bGrab ) --{ -- if ( !BIsSDLSession() ) -- return; -- -- if ( g_bForceRelativeMouse ) -- return; -- -- static bool s_bWasGrabbed = false; -- -- if ( s_bWasGrabbed == bGrab ) -- return; -- -- s_bWasGrabbed = bGrab; -- -- SDL_Event event; -- event.type = g_unSDLUserEventID + USER_EVENT_GRAB; -- event.user.code = bGrab ? 1 : 0; -- SDL_PushEvent( &event ); --} -- --void sdlwindow_cursor(std::shared_ptr> pixels, uint32_t width, uint32_t height, uint32_t xhot, uint32_t yhot) --{ -- if ( !BIsSDLSession() ) -- return; -- -- if ( g_bForceRelativeMouse ) -- return; -+ ///////////////////////// -+ // Backend Instantiator -+ ///////////////////////// - -+ template <> -+ bool IBackend::Set() - { -- std::unique_lock lock( g_SDLCursorLock ); -- g_SDLPendingCursorData.width = width; -- g_SDLPendingCursorData.height = height; -- g_SDLPendingCursorData.xhot = xhot; -- g_SDLPendingCursorData.yhot = yhot; -- g_SDLPendingCursorData.data = pixels; -- g_bUpdateSDLCursor = true; -+ return Set( new CSDLBackend{} ); - } -- -- SDL_Event event; -- event.type = g_unSDLUserEventID + USER_EVENT_CURSOR; -- SDL_PushEvent( &event ); - } -diff --git a/src/sdlwindow.hpp b/src/sdlwindow.hpp -deleted file mode 100644 -index bb88fd30a..000000000 ---- a/src/sdlwindow.hpp -+++ /dev/null -@@ -1,23 +0,0 @@ --// For the nested case, manages SDL window for input/output -- --#pragma once -- --#include --#include -- --#define CLIPBOARD 0 --#define PRIMARYSELECTION 1 -- --bool sdlwindow_init( void ); --void sdlwindow_shutdown( void ); -- --void sdlwindow_update( void ); --void sdlwindow_title( std::shared_ptr title, std::shared_ptr> icon ); --void sdlwindow_set_selection(std::string, int selection); -- --// called from other threads with interesting things have happened with clients that might warrant updating the nested window --void sdlwindow_visible( bool bVisible ); --void sdlwindow_grab( bool bGrab ); --void sdlwindow_cursor(std::shared_ptr> pixels, uint32_t width, uint32_t height, uint32_t xhot, uint32_t yhot); -- --extern SDL_Window *g_SDLWindow; -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 49aa6fbd3..e3dae6154 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -78,19 +78,16 @@ - #include - #include "waitable.h" - --#include "steamcompmgr_shared.hpp" -- - #include "main.hpp" - #include "wlserver.hpp" --#include "drm.hpp" - #include "rendervulkan.hpp" - #include "steamcompmgr.hpp" - #include "vblankmanager.hpp" --#include "sdlwindow.hpp" - #include "log.hpp" - #include "defer.hpp" - #include "win32_styles.h" - #include "mwm_hints.h" -+#include "edid.h" - - #include "avif/avif.h" - -@@ -100,10 +97,6 @@ static const int g_nBaseCursorScale = 36; - #include "pipewire.hpp" - #endif - --#if HAVE_OPENVR --#include "vr_session.hpp" --#endif -- - #define STB_IMAGE_IMPLEMENTATION - #define STB_IMAGE_WRITE_IMPLEMENTATION - #include -@@ -144,7 +137,7 @@ extern float g_flHDRItmTargetNits; - - uint64_t g_lastWinSeq = 0; - --static std::shared_ptr s_scRGB709To2020Matrix; -+static std::shared_ptr s_scRGB709To2020Matrix; - - std::string clipboard; - std::string primarySelection; -@@ -153,6 +146,7 @@ std::string g_reshade_effect{}; - uint32_t g_reshade_technique_idx = 0; - - bool g_bSteamIsActiveWindow = false; -+bool g_bForceInternal = false; - - uint64_t timespec_to_nanos(struct timespec& spec) - { -@@ -304,17 +298,17 @@ create_color_mgmt_luts(const gamescope_color_mgmt_t& newColorMgmt, gamescope_col - // Create quantized output luts - for ( size_t i=0, end = g_tmpLut1d.dataR.size(); iGetCurrentConnector() ) -+ return; -+ -+ GetBackend()->GetCurrentConnector()->GetNativeColorimetry( -+ g_bHDREnabled, -+ &g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF, -+ &g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF ); - - #ifdef COLOR_MGMT_MICROBENCH - struct timespec t0, t1; -@@ -508,13 +488,16 @@ bool set_color_mgmt_enabled( bool bEnabled ) - } - - static std::shared_ptr s_MuraCorrectionImage[gamescope::GAMESCOPE_SCREEN_TYPE_COUNT]; --static std::shared_ptr s_MuraCTMBlob[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() - { -- return s_MuraCorrectionImage[drm_get_screen_type( &g_DRM )] != nullptr && !g_bMuraCompensationDisabled; -+ if ( !GetBackend()->GetCurrentConnector() ) -+ return false; -+ -+ return s_MuraCorrectionImage[GetBackend()->GetCurrentConnector()->GetScreenType()] != nullptr && !g_bMuraCompensationDisabled; - } - - void update_mura_ctm() -@@ -535,7 +518,7 @@ void update_mura_ctm() - 0, flScale, 0, kMuraOffset * flScale, - 0, 0, 0, 0, // No mura comp for blue channel. - }; -- s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = drm_create_ctm(&g_DRM, mura_scale_offset); -+ s_MuraCTMBlob[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] = GetBackend()->CreateBackendBlob( mura_scale_offset ); - } - - bool g_bMuraDebugFullColor = false; -@@ -577,7 +560,7 @@ bool set_mura_overlay( const char *path ) - free(green_data); - - CVulkanTexture::createFlags texCreateFlags; -- texCreateFlags.bFlippable = !BIsNested(); -+ 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); -@@ -703,7 +686,7 @@ struct commit_t : public gamescope::IWaitable - - if ( fb_id != 0 ) - { -- drm_unlock_fbid( &g_DRM, fb_id ); -+ GetBackend()->UnlockBackendFb( fb_id ); - fb_id = 0; - } - -@@ -922,18 +905,14 @@ static int g_nCombinedAppRefreshCycleOverride[gamescope::GAMESCOPE_SCREEN_TYPE_C - - static void _update_app_target_refresh_cycle() - { -- if ( BIsNested() ) -- { -- g_nDynamicRefreshRate[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ] = 0; -- g_nSteamCompMgrTargetFPS = g_nCombinedAppRefreshCycleOverride[ gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ]; -+ if ( !GetBackend()->GetCurrentConnector() ) - return; -- } - - static gamescope::GamescopeScreenType last_type; - static int last_target_fps; - static bool first = true; - -- gamescope::GamescopeScreenType type = drm_get_screen_type( &g_DRM ); -+ gamescope::GamescopeScreenType type = GetBackend()->GetCurrentConnector()->GetScreenType(); - int target_fps = g_nCombinedAppRefreshCycleOverride[type]; - - if ( !first && type == last_type && last_target_fps == target_fps ) -@@ -952,7 +931,7 @@ static void _update_app_target_refresh_cycle() - return; - } - -- auto rates = drm_get_valid_refresh_rates( &g_DRM ); -+ auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates(); - - g_nDynamicRefreshRate[ type ] = 0; - g_nSteamCompMgrTargetFPS = target_fps; -@@ -1081,7 +1060,7 @@ static bool debugFocus = false; - static bool drawDebugInfo = false; - static bool debugEvents = false; - bool steamMode = false; --static bool alwaysComposite = false; -+bool alwaysComposite = false; - static bool useXRes = true; - - struct wlr_buffer_map_entry { -@@ -1355,7 +1334,7 @@ destroy_buffer( struct wl_listener *listener, void * ) - - if ( entry->fb_id != 0 ) - { -- drm_drop_fbid( &g_DRM, entry->fb_id ); -+ GetBackend()->DropBackendFb( entry->fb_id ); - } - - wl_list_remove( &entry->listener.link ); -@@ -1400,7 +1379,7 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff - - if (commit->fb_id) - { -- drm_lock_fbid( &g_DRM, commit->fb_id ); -+ GetBackend()->LockBackendFb( commit->fb_id ); - } - - return commit; -@@ -1425,20 +1404,17 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff - commit->vulkanTex = vulkan_create_texture_from_wlr_buffer( buf ); - assert( commit->vulkanTex ); - -+ commit->fb_id = 0; - struct wlr_dmabuf_attributes dmabuf = {0}; -- if ( BIsNested() == false && wlr_buffer_get_dmabuf( buf, &dmabuf ) ) -+ if ( wlr_buffer_get_dmabuf( buf, &dmabuf ) ) - { -- commit->fb_id = drm_fbid_from_dmabuf( &g_DRM, buf, &dmabuf ); -+ commit->fb_id = GetBackend()->ImportDmabufToBackend( buf, &dmabuf ); - - if ( commit->fb_id ) - { -- drm_lock_fbid( &g_DRM, commit->fb_id ); -+ GetBackend()->LockBackendFb( commit->fb_id ); - } - } -- else -- { -- commit->fb_id = 0; -- } - - entry.listener.notify = destroy_buffer; - entry.buf = buf; -@@ -1875,23 +1851,16 @@ bool MouseCursor::getTexture() - - uint32_t surfaceWidth; - uint32_t surfaceHeight; -- if ( BIsNested() == false && alwaysComposite == false ) -- { -- surfaceWidth = g_DRM.cursor_width; -- surfaceHeight = g_DRM.cursor_height; -- } -- else -- { -- surfaceWidth = nDesiredWidth; -- surfaceHeight = nDesiredHeight; -- } -+ 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::shared_ptr> cursorBuffer = nullptr; -+ std::vector cursorBuffer; - - int nContentWidth = image->width; - int nContentHeight = image->height; -@@ -1913,14 +1882,12 @@ bool MouseCursor::getTexture() - (unsigned char *)resizeBuffer.data(), nDesiredWidth, nDesiredHeight, 0, - 4, 3, STBIR_FLAG_ALPHA_PREMULTIPLIED ); - -- cursorBuffer = std::make_shared>(surfaceWidth * surfaceHeight); -- for (int i = 0; i < nDesiredHeight; i++) { -- for (int j = 0; j < nDesiredWidth; j++) { -- (*cursorBuffer)[i * surfaceWidth + j] = resizeBuffer[i * nDesiredWidth + j]; -- -- if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { -- bNoCursor = false; -- } -+ 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]; - } - } - -@@ -1932,29 +1899,39 @@ bool MouseCursor::getTexture() - } - else - { -- cursorBuffer = std::make_shared>(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]; -- -- if ( (*cursorBuffer)[i * surfaceWidth + j] & 0xff000000 ) { -- bNoCursor = false; -- } -+ 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 (int i = 0; i < image->height; i++) -+ { -+ for (int j = 0; j < image->width; j++) -+ { -+ if ( cursorBuffer[i * surfaceWidth + j] & 0xff000000 ) -+ { -+ bNoCursor = false; -+ break; -+ } -+ } -+ } - - if (bNoCursor) -- cursorBuffer = nullptr; -+ cursorBuffer.clear(); - - m_imageEmpty = bNoCursor; - -- if ( !g_bForceRelativeMouse ) -+ if ( !GetBackend()->GetNestedHints() || !g_bForceRelativeMouse ) - { -- sdlwindow_grab( m_imageEmpty ); -- bSteamCompMgrGrab = BIsNested() && m_imageEmpty; -+ if ( GetBackend()->GetNestedHints() ) -+ GetBackend()->GetNestedHints()->SetRelativeMouseMode( m_imageEmpty ); -+ bSteamCompMgrGrab = GetBackend()->GetNestedHints() && m_imageEmpty; - } - - m_dirty = false; -@@ -1969,15 +1946,28 @@ bool MouseCursor::getTexture() - UpdatePosition(); - - CVulkanTexture::createFlags texCreateFlags; -- if ( BIsNested() == false ) -+ texCreateFlags.bFlippable = true; -+ if ( GetBackend()->SupportsPlaneHardwareCursor() ) - { -- texCreateFlags.bFlippable = true; - 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()); -- sdlwindow_cursor(std::move(cursorBuffer), nDesiredWidth, nDesiredHeight, image->xhot, image->yhot); -+ m_texture = vulkan_create_texture_from_bits(surfaceWidth, surfaceHeight, nContentWidth, nContentHeight, DRM_FORMAT_ARGB8888, texCreateFlags, cursorBuffer.data()); -+ if ( GetBackend()->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()->GetNestedHints()->SetCursorImage( std::move( info ) ); -+ } -+ - assert(m_texture); - XFree(image); - -@@ -1993,11 +1983,7 @@ void MouseCursor::GetDesiredSize( int& nWidth, int &nHeight ) - nSize = std::clamp( nSize, g_nBaseCursorScale, 256 ); - } - -- if ( BIsNested() == false && alwaysComposite == false ) -- { -- nSize = std::min( nSize, g_DRM.cursor_width ); -- nSize = std::min( nSize, g_DRM.cursor_height ); -- } -+ nSize = std::min( nSize, glm::compMin( GetBackend()->CursorSurfaceSize( glm::uvec2{ (uint32_t)nSize, (uint32_t)nSize } ) ) ); - - nWidth = nSize; - nHeight = nSize; -@@ -2108,7 +2094,7 @@ void MouseCursor::paint(steamcompmgr_win_t *window, steamcompmgr_win_t *fit, str - layer->applyColorMgmt = false; - - layer->tex = m_texture; -- layer->fbid = BIsNested() ? 0 : m_texture->fbid(); -+ layer->fbid = m_texture->fbid(); - - layer->filter = cursor_scale != 1.0f ? GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NEAREST; - layer->blackBorder = false; -@@ -2466,6 +2452,7 @@ paint_all(bool async) - frameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; - frameInfo.outputEncodingEOTF = g_ColorMgmt.pending.outputEncodingEOTF; - frameInfo.allowVRR = g_bAllowVRR; -+ 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 -@@ -2578,7 +2565,7 @@ paint_all(bool async) - else - { - auto tex = vulkan_get_hacky_blank_texture(); -- if ( !BIsNested() && tex != nullptr ) -+ if ( !GetBackend()->UsesVulkanSwapchain() && tex != nullptr ) - { - // HACK! HACK HACK HACK - // To avoid stutter when toggling the overlay on -@@ -2620,21 +2607,16 @@ paint_all(bool async) - global_focus.cursor->undirty(); - } - -- bool bForceHideCursor = BIsSDLSession() && !bSteamCompMgrGrab; -- -- bool bDrewCursor = false; -+ bool bForceHideCursor = GetBackend()->GetNestedHints() && !bSteamCompMgrGrab; - - // Draw cursor if we need to - if (input && !bForceHideCursor) { -- int nLayerCountBefore = frameInfo.layerCount; - global_focus.cursor->paint( - input, w == input ? override : nullptr, - &frameInfo); -- int nLayerCountAfter = frameInfo.layerCount; -- bDrewCursor = nLayerCountAfter > nLayerCountBefore; - } - -- if ( !bValidContents || ( BIsNested() == false && g_DRM.paused == true ) ) -+ if ( !bValidContents || !GetBackend()->IsVisible() ) - { - return; - } -@@ -2665,35 +2647,34 @@ paint_all(bool async) - - g_bFSRActive = frameInfo.useFSRLayer0; - -- bool bWasFirstFrame = g_bFirstFrame; - g_bFirstFrame = false; - -- bool bDoComposite = true; -- - update_app_target_refresh_cycle(); - -- int nDynamicRefresh = g_nDynamicRefreshRate[drm_get_screen_type( &g_DRM )]; -- -- int nTargetRefresh = nDynamicRefresh && steamcompmgr_window_should_refresh_switch( global_focus.focusWindow )// && !global_focus.overlayWindow -- ? nDynamicRefresh -- : drm_get_default_refresh( &g_DRM ); -+ const bool bSupportsDynamicRefresh = GetBackend()->GetCurrentConnector() && !GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates().empty(); -+ if ( bSupportsDynamicRefresh ) -+ { -+ auto rates = GetBackend()->GetCurrentConnector()->GetValidDynamicRefreshRates(); - -- uint64_t now = get_time_in_nanos(); -+ int nDynamicRefresh = g_nDynamicRefreshRate[GetBackend()->GetScreenType()]; - -- if ( g_nOutputRefresh == nTargetRefresh ) -- g_uDynamicRefreshEqualityTime = now; -+ int nTargetRefresh = nDynamicRefresh && steamcompmgr_window_should_refresh_switch( global_focus.focusWindow )// && !global_focus.overlayWindow -+ ? nDynamicRefresh -+ : int( rates[ rates.size() - 1 ] ); - -- if ( !BIsNested() && g_nOutputRefresh != nTargetRefresh && g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now ) -- drm_set_refresh( &g_DRM, nTargetRefresh ); -+ uint64_t now = get_time_in_nanos(); - -- bool bLayer0ScreenSize = close_enough(frameInfo.layers[0].scale.x, 1.0f) && close_enough(frameInfo.layers[0].scale.y, 1.0f); -+ if ( g_nOutputRefresh == nTargetRefresh ) -+ g_uDynamicRefreshEqualityTime = now; - -- bool bNeedsCompositeFromFilter = (g_upscaleFilter == GamescopeUpscaleFilter::NEAREST || g_upscaleFilter == GamescopeUpscaleFilter::PIXEL) && !bLayer0ScreenSize; -+ if ( g_nOutputRefresh != nTargetRefresh && g_uDynamicRefreshEqualityTime + g_uDynamicRefreshDelay < now ) -+ GetBackend()->HackTemporarySetDynamicRefresh( nTargetRefresh ); -+ } - - bool bDoMuraCompensation = is_mura_correction_enabled() && frameInfo.layerCount; - if ( bDoMuraCompensation ) - { -- auto& MuraCorrectionImage = s_MuraCorrectionImage[drm_get_screen_type( &g_DRM )]; -+ auto& MuraCorrectionImage = s_MuraCorrectionImage[GetBackend()->GetScreenType()]; - int curLayer = frameInfo.layerCount++; - - FrameInfo_t::Layer_t *layer = &frameInfo.layers[ curLayer ]; -@@ -2708,292 +2689,24 @@ paint_all(bool async) - layer->zpos = g_zposMuraCorrection; - layer->filter = GamescopeUpscaleFilter::NEAREST; - layer->tex = MuraCorrectionImage; -- layer->ctm = s_MuraCTMBlob[drm_get_screen_type( &g_DRM )]; -+ layer->ctm = s_MuraCTMBlob[GetBackend()->GetScreenType()]; - - // Blending needs to be done in Gamma 2.2 space for mura correction to work. - frameInfo.applyOutputColorMgmt = false; - } - -- bool bWantsPartialComposite = frameInfo.layerCount >= 3 && !kDisablePartialComposition; -- -- bool bNeedsFullComposite = BIsNested(); -- bNeedsFullComposite |= alwaysComposite; -- bNeedsFullComposite |= bWasFirstFrame; -- bNeedsFullComposite |= frameInfo.useFSRLayer0; -- bNeedsFullComposite |= frameInfo.useNISLayer0; -- bNeedsFullComposite |= frameInfo.blurLayer0; -- bNeedsFullComposite |= bNeedsCompositeFromFilter; -- bNeedsFullComposite |= bDrewCursor; -- bNeedsFullComposite |= g_bColorSliderInUse; -- bNeedsFullComposite |= fadingOut; -- bNeedsFullComposite |= !g_reshade_effect.empty(); -- - for (uint32_t i = 0; i < EOTF_Count; i++) - { -- if (g_ColorMgmtLuts[i].HasLuts()) -+ if ( g_ColorMgmtLuts[i].HasLuts() ) - { - frameInfo.shaperLut[i] = g_ColorMgmtLuts[i].vk_lut1d; - frameInfo.lut3D[i] = g_ColorMgmtLuts[i].vk_lut3d; - } - } - -- if ( !BIsNested() && g_bOutputHDREnabled ) -+ if ( GetBackend()->Present( &frameInfo, async ) != 0 ) - { -- bNeedsFullComposite |= g_bHDRItmEnable; -- if ( !drm_supports_color_mgmt(&g_DRM) ) -- bNeedsFullComposite |= ( frameInfo.layerCount > 1 || frameInfo.layers[0].colorspace != GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ ); -- } -- bNeedsFullComposite |= !!(g_uCompositeDebug & CompositeDebugFlag::Heatmap); -- -- static int g_nLastSingleOverlayZPos = 0; -- static bool g_bWasCompositing = false; -- -- if ( !bNeedsFullComposite && !bWantsPartialComposite ) -- { -- int ret = drm_prepare( &g_DRM, async, &frameInfo ); -- if ( ret == 0 ) -- { -- bDoComposite = false; -- g_bWasPartialComposite = false; -- g_bWasCompositing = false; -- if ( frameInfo.layerCount == 2 ) -- g_nLastSingleOverlayZPos = frameInfo.layers[1].zpos; -- } -- else if ( ret == -EACCES ) -- return; -- } -- -- // Update to let the vblank manager know we are currently compositing. -- g_VBlankTimer.UpdateWasCompositing( bDoComposite ); -- -- if ( bDoComposite == true ) -- { -- if ( kDisablePartialComposition ) -- bNeedsFullComposite = true; -- -- struct FrameInfo_t compositeFrameInfo = frameInfo; -- -- 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 ( frameInfo.layers[i - 1].colorspace != frameInfo.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 && ( !g_bWasCompositing || g_bWasPartialComposite ); -- -- // 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 ); -- -- g_bWasCompositing = true; -- -- g_uCompositeDebug &= ~CompositeDebugFlag::Markers_Partial; -- -- if ( !oCompositeResult ) -- { -- xwm_log.errorf("vulkan_composite failed"); -- return; -- } -- -- vulkan_wait( *oCompositeResult, true ); -- -- if ( BIsNested() == true ) -- { --#if HAVE_OPENVR -- if ( BIsVRSession() ) -- { -- vulkan_present_to_openvr(); -- } -- else if ( BIsSDLSession() ) --#endif -- { -- vulkan_present_to_window(); -- } -- -- // Update the time it took us to commit -- g_VBlankTimer.UpdateLastDrawTime( get_time_in_nanos() - g_SteamCompMgrVBlankTime.ulWakeupTime ); -- } -- else -- { -- struct FrameInfo_t presentCompFrameInfo = {}; -- -- 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->fbid = baseLayer->tex->fbid(); -- baseLayer->applyColorMgmt = false; -- -- baseLayer->filter = GamescopeUpscaleFilter::NEAREST; -- baseLayer->ctm = nullptr; -- baseLayer->colorspace = g_bOutputHDREnabled ? GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ : GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; -- -- g_bWasPartialComposite = false; -- } -- else -- { -- if ( g_bWasPartialComposite || !bDefer ) -- { -- presentCompFrameInfo.applyOutputColorMgmt = g_ColorMgmt.pending.enabled; -- presentCompFrameInfo.layerCount = 2; -- -- presentCompFrameInfo.layers[ 0 ] = frameInfo.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->fbid = overlayLayer->tex->fbid(); -- 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 ] = frameInfo.layers[ 0 ]; -- presentCompFrameInfo.layers[ 0 ].zpos = g_zposBase; -- -- FrameInfo_t::Layer_t *lastPresentedOverlayLayer = nullptr; -- for (int i = 0; i < frameInfo.layerCount; i++) -- { -- if (frameInfo.layers[i].zpos == g_nLastSingleOverlayZPos) -- { -- lastPresentedOverlayLayer = &frameInfo.layers[i]; -- break; -- } -- } -- -- if (lastPresentedOverlayLayer) -- { -- FrameInfo_t::Layer_t *overlayLayer = &presentCompFrameInfo.layers[ 1 ]; -- *overlayLayer = *lastPresentedOverlayLayer; -- overlayLayer->zpos = g_zposOverlay; -- -- presentCompFrameInfo.layerCount = 2; -- } -- } -- -- g_bWasPartialComposite = true; -- } -- -- int ret = drm_prepare( &g_DRM, async, &presentCompFrameInfo ); -- -- // Happens when we're VT-switched away -- if ( ret == -EACCES ) -- return; -- -- 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)); -- abort(); -- } -- -- 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, async, &compositeFrameInfo ); -- -- // Happens when we're VT-switched away -- if ( ret == -EACCES ) -- return; -- -- 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. -- g_DRM.out_of_date = 2; -- drm_poll_state( &g_DRM ); -- return; -- } -- } -- -- drm_commit( &g_DRM, &compositeFrameInfo ); -- } -- } -- else -- { -- assert( BIsNested() == false ); -- -- drm_commit( &g_DRM, &frameInfo ); -+ return; - } - - #if HAVE_PIPEWIRE -@@ -3155,7 +2868,11 @@ paint_all(bool async) - - if ( !maxCLLNits && !maxFALLNits ) - { -- drm_supports_hdr( &g_DRM, &maxCLLNits, &maxFALLNits ); -+ if ( GetBackend()->GetCurrentConnector() ) -+ { -+ maxCLLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxContentLightLevel; -+ maxFALLNits = GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxFrameAverageLuminance; -+ } - } - - if ( !maxCLLNits && !maxFALLNits ) -@@ -3347,7 +3064,7 @@ paint_all(bool async) - - - gpuvis_trace_end_ctx_printf( paintID, "paint_all" ); -- gpuvis_trace_printf( "paint_all %i layers, composite %i", (int)frameInfo.layerCount, bDoComposite ); -+ gpuvis_trace_printf( "paint_all %i layers", (int)frameInfo.layerCount ); - } - - /* Get prop from window -@@ -4308,35 +4025,17 @@ determine_and_apply_focus() - } - - // Set SDL window title -- if ( global_focus.focusWindow ) -+ if ( GetBackend()->GetNestedHints() ) - { --#if HAVE_OPENVR -- if ( BIsVRSession() ) -- { -- const char *title = global_focus.focusWindow->title -- ? global_focus.focusWindow->title->c_str() -- : nullptr; -- vrsession_title( title, global_focus.focusWindow->icon ); -- } --#endif -- -- if ( BIsSDLSession() ) -+ if ( global_focus.focusWindow ) - { -- sdlwindow_title( global_focus.focusWindow->title, global_focus.focusWindow->icon ); -+ GetBackend()->GetNestedHints()->SetVisible( true ); -+ GetBackend()->GetNestedHints()->SetTitle( global_focus.focusWindow->title ); -+ GetBackend()->GetNestedHints()->SetIcon( global_focus.focusWindow->icon ); - } -- } -- --#if HAVE_OPENVR -- if ( BIsVRSession() ) -- { -- vrsession_set_dashboard_visible( global_focus.focusWindow != nullptr ); -- } -- else --#endif -- { -- if ( BIsSDLSession() ) -+ else - { -- sdlwindow_visible( global_focus.focusWindow != nullptr ); -+ GetBackend()->GetNestedHints()->SetVisible( false ); - } - } - -@@ -5263,14 +4962,14 @@ handle_client_message(xwayland_ctx_t *ctx, XClientMessageEvent *ev) - } - } - --static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, int selectionTarget) -+static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, GamescopeSelection eSelectionTarget) - { - Atom target; -- if (selectionTarget == CLIPBOARD) -+ if (eSelectionTarget == GAMESCOPE_SELECTION_CLIPBOARD) - { - target = ctx->atoms.clipboard; - } -- else if (selectionTarget == PRIMARYSELECTION) -+ else if (eSelectionTarget == GAMESCOPE_SELECTION_PRIMARY) - { - target = ctx->atoms.primarySelection; - } -@@ -5282,13 +4981,13 @@ static void x11_set_selection_owner(xwayland_ctx_t *ctx, std::string contents, i - XSetSelectionOwner(ctx->dpy, target, ctx->ourWindow, CurrentTime); - } - --void gamescope_set_selection(std::string contents, int selection) -+void gamescope_set_selection(std::string contents, GamescopeSelection eSelection) - { -- if (selection == CLIPBOARD) -+ if (eSelection == GAMESCOPE_SELECTION_CLIPBOARD) - { - clipboard = contents; - } -- else if (selection == PRIMARYSELECTION) -+ else if (eSelection == GAMESCOPE_SELECTION_PRIMARY) - { - primarySelection = contents; - } -@@ -5296,7 +4995,7 @@ void gamescope_set_selection(std::string contents, int selection) - gamescope_xwayland_server_t *server = NULL; - for (int i = 0; (server = wlserver_get_xwayland_server(i)); i++) - { -- x11_set_selection_owner(server->ctx.get(), contents, selection); -+ x11_set_selection_owner(server->ctx.get(), contents, eSelection); - } - } - -@@ -5356,8 +5055,6 @@ handle_selection_request(xwayland_ctx_t *ctx, XSelectionRequestEvent *ev) - static void - handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) - { -- int selection; -- - Atom actual_type; - int actual_format; - unsigned long nitems; -@@ -5375,37 +5072,34 @@ handle_selection_notify(xwayland_ctx_t *ctx, XSelectionEvent *ev) - &actual_type, &actual_format, &nitems, &bytes_after, &data); - if (data) { - const char *contents = (const char *) data; -+ defer( XFree( data ); ); - - if (ev->selection == ctx->atoms.clipboard) - { -- selection = CLIPBOARD; -+ if ( GetBackend()->GetNestedHints() ) -+ { -+ //GetBackend()->GetNestedHints()->SetSelection() -+ } -+ else -+ { -+ gamescope_set_selection( contents, GAMESCOPE_SELECTION_CLIPBOARD ); -+ } - } - else if (ev->selection == ctx->atoms.primarySelection) - { -- selection = PRIMARYSELECTION; -+ if ( GetBackend()->GetNestedHints() ) -+ { -+ //GetBackend()->GetNestedHints()->SetSelection() -+ } -+ else -+ { -+ gamescope_set_selection( contents, GAMESCOPE_SELECTION_PRIMARY ); -+ } - } - else - { - xwm_log.errorf( "Selection '%s' not supported. Ignoring", XGetAtomName(ctx->dpy, ev->selection) ); -- goto done; -- } -- -- if (BIsNested()) -- { -- /* -- * gamescope_set_selection() doesn't need to be called here. -- * sdlwindow_set_selection triggers a clipboard update, which -- * then indirectly ccalls gamescope_set_selection() -- */ -- sdlwindow_set_selection(contents, selection); -- } -- else -- { -- gamescope_set_selection(contents, selection); - } -- --done: -- XFree(data); - } - } - } -@@ -5578,10 +5272,6 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - if (ev->atom == ctx->atoms.steamTouchClickModeAtom ) - { - g_nTouchClickMode = (enum wlserver_touch_click_mode) get_prop(ctx, ctx->root, ctx->atoms.steamTouchClickModeAtom, g_nDefaultTouchClickMode ); --#if HAVE_OPENVR -- if (BIsVRSession()) -- vrsession_update_touch_mode(); --#endif - } - if (ev->atom == ctx->atoms.steamStreamingClientAtom) - { -@@ -5750,7 +5440,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - - if (ev->window == x11_win(global_focus.focusWindow)) - { -- sdlwindow_title( w->title, w->icon ); -+ if ( GetBackend()->GetNestedHints() ) -+ GetBackend()->GetNestedHints()->SetTitle( w->title ); - } - } - } -@@ -5764,7 +5455,8 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - - if (ev->window == x11_win(global_focus.focusWindow)) - { -- sdlwindow_title( w->title, w->icon ); -+ if ( GetBackend()->GetNestedHints() ) -+ GetBackend()->GetNestedHints()->SetIcon( w->icon ); - } - } - } -@@ -5924,19 +5616,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - } - if ( ev->atom == ctx->atoms.gamescopeDisplayForceInternal ) - { -- if ( !BIsNested() ) -- { -- g_DRM.force_internal = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayForceInternal, 0 ); -- g_DRM.out_of_date = 1; -- } -+ g_bForceInternal = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayForceInternal, 0 ); -+ GetBackend()->DirtyState(); - } - if ( ev->atom == ctx->atoms.gamescopeDisplayModeNudge ) - { -- if ( !BIsNested() ) -- { -- g_DRM.out_of_date = 2; -- XDeleteProperty( ctx->dpy, ctx->root, ctx->atoms.gamescopeDisplayModeNudge ); -- } -+ GetBackend()->DirtyState( true ); -+ XDeleteProperty( ctx->dpy, ctx->root, ctx->atoms.gamescopeDisplayModeNudge ); - } - if ( ev->atom == ctx->atoms.gamescopeNewScalingFilter ) - { -@@ -5969,7 +5655,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - if ( ev->atom == ctx->atoms.gamescopeDebugForceHDRSupport ) - { - g_bForceHDRSupportDebug = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDebugForceHDRSupport, 0 ); -- drm_update_patched_edid(&g_DRM); -+ GetBackend()->HackUpdatePatchedEdid(); - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeDebugHDRHeatmap ) -@@ -6079,15 +5765,6 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - } - hasRepaint = true; - } -- if ( ev->atom == ctx->atoms.gamescopeInternalDisplayBrightness ) -- { -- uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeInternalDisplayBrightness, 0 ); -- if ( set_internal_display_brightness( bit_cast(val) ) ) -- { -- drm_update_patched_edid(&g_DRM); -- hasRepaint = true; -- } -- } - if ( ev->atom == ctx->atoms.gamescopeHDRInputGain ) - { - uint32_t val = get_prop( ctx, ctx->root, ctx->atoms.gamescopeHDRInputGain, 0 ); -@@ -6398,14 +6075,12 @@ steamcompmgr_exit(void) - statsThreadSem.signal(); - } - -- sdlwindow_shutdown(); -+ //sdlwindow_shutdown(); - - wlserver_lock(); - wlserver_force_shutdown(); - wlserver_unlock(false); - -- finish_drm( &g_DRM ); -- - pthread_exit(NULL); - } - -@@ -7246,46 +6921,6 @@ load_mouse_cursor( MouseCursor *cursor, const char *path, int hx, int hy ) - return cursor->setCursorImage((char *)data, w, h, hx, hy); - } - --static bool --load_host_cursor( MouseCursor *cursor ) --{ -- extern const char *g_pOriginalDisplay; -- -- if ( !g_pOriginalDisplay ) -- return false; -- -- Display *display = XOpenDisplay( g_pOriginalDisplay ); -- if ( !display ) -- return false; -- 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 false; -- } -- -- XFixesCursorImage *image = XFixesGetCursorImage( display ); -- if ( !image ) -- return false; -- defer( XFree( image ) ); -- -- // image->pixels is `unsigned long*` :/ -- // Thanks X11. -- std::vector cursorData; -- for (uint32_t y = 0; y < image->height; y++) -- { -- for (uint32_t x = 0; x < image->width; x++) -- { -- cursorData.push_back((uint32_t)image->pixels[image->height * y + x]); -- } -- } -- -- cursor->setCursorImage((char *)cursorData.data(), image->width, image->height, image->xhot, image->yhot); -- return true; --} -- - const char* g_customCursorPath = nullptr; - int g_customCursorHotspotX = 0; - int g_customCursorHotspotY = 0; -@@ -7493,7 +7128,6 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - 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.gamescopeInternalDisplayBrightness = XInternAtom( ctx->dpy, "GAMESCOPE_INTERNAL_DISPLAY_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 ); -@@ -7588,20 +7222,21 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - } - else - { -- if ( BIsNested() ) -+ std::optional oHostCursor = std::nullopt; -+ if ( GetBackend()->GetNestedHints() && ( oHostCursor = GetBackend()->GetNestedHints()->GetHostCursor() ) ) - { -- if ( !load_host_cursor( ctx->cursor.get() ) ) -- { -- xwm_log.errorf("Failed to load host cursor. Falling back to left_ptr."); -- if (!ctx->cursor->setCursorImageByName("left_ptr")) -- xwm_log.errorf("Failed to load mouse cursor: left_ptr"); -- } -+ ctx->cursor->setCursorImage( -+ reinterpret_cast( oHostCursor->pPixels.data() ), -+ oHostCursor->uWidth, -+ oHostCursor->uHeight, -+ oHostCursor->uXHotspot, -+ oHostCursor->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"); -+ 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" ); - } - } - -@@ -7613,7 +7248,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - - void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = nullptr) - { -- bool capable = drm_get_vrr_capable( &g_DRM ); -+ bool capable = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->SupportsVRR(); - if ( capable != g_bVRRCapable_CachedValue || force ) - { - uint32_t capable_value = capable ? 1 : 0; -@@ -7624,7 +7259,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = - *needs_flush = true; - } - -- bool HDR = BIsNested() ? vulkan_supports_hdr10() : drm_supports_hdr( &g_DRM ); -+ bool HDR = GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->SupportsHDR(); - if ( HDR != g_bSupportsHDR_CachedValue || force ) - { - uint32_t hdr_value = HDR ? 1 : 0; -@@ -7635,7 +7270,7 @@ void update_vrr_atoms(xwayland_ctx_t *root_ctx, bool force, bool* needs_flush = - *needs_flush = true; - } - -- bool in_use = drm_get_vrr_in_use( &g_DRM ); -+ bool in_use = GetBackend()->IsVRRActive(); - if ( in_use != g_bVRRInUse_CachedValue || force ) - { - uint32_t in_use_value = in_use ? 1 : 0; -@@ -7673,7 +7308,7 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) - if (needs_flush) - *needs_flush = true; - -- if ( drm_get_screen_type(&g_DRM) == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) -+ if ( GetBackend()->GetCurrentConnector() && GetBackend()->GetCurrentConnector()->GetScreenType() == gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL ) - { - XDeleteProperty(root_ctx->dpy, root_ctx->root, root_ctx->atoms.gamescopeDisplayModeListExternal); - -@@ -7683,20 +7318,20 @@ void update_mode_atoms(xwayland_ctx_t *root_ctx, bool* needs_flush = nullptr) - return; - } - -- if ( !g_DRM.pConnector || !g_DRM.pConnector->GetModeConnector() ) -- { -+ 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 < g_DRM.pConnector->GetModeConnector()->count_modes; i++) -+ for (int i = 0; remaining_size > 0 && i < (int)connectorModes.size(); i++) - { -- const auto& mode = g_DRM.pConnector->GetModeConnector()->modes[i]; -+ const auto& mode = connectorModes[i]; - int mode_len = snprintf(&modes[len], remaining_size, "%s%dx%d@%d", - i == 0 ? "" : " ", -- int(mode.hdisplay), int(mode.vdisplay), int(mode.vrefresh)); -+ int(mode.uWidth), int(mode.uHeight), int(mode.uRefresh)); - len += mode_len; - remaining_size -= mode_len; - } -@@ -7713,8 +7348,6 @@ extern int g_nPreferredOutputHeight; - - static bool g_bWasFSRActive = false; - --extern std::atomic g_nCompletedPageFlipCount; -- - void steamcompmgr_check_xdg(bool vblank) - { - if (wlserver_xdg_dirty()) -@@ -7741,25 +7374,22 @@ void steamcompmgr_check_xdg(bool vblank) - - void update_edid_prop() - { -- if ( !BIsNested() ) -- { -- const char *filename = drm_get_patched_edid_path(); -- if (!filename) -- return; -+ 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++) -+ gamescope_xwayland_server_t *server = NULL; -+ for (size_t i = 0; (server = wlserver_get_xwayland_server(i)); i++) -+ { -+ XTextProperty text_property = - { -- XTextProperty text_property = -- { -- .value = (unsigned char *)filename, -- .encoding = server->ctx->atoms.utf8StringAtom, -- .format = 8, -- .nitems = strlen(filename), -- }; -+ .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 ); -- } -+ XSetTextProperty( server->ctx->dpy, server->ctx->root, &text_property, server->ctx->atoms.gamescopeDisplayEdidPath ); - } - } - -@@ -7771,7 +7401,7 @@ steamcompmgr_main(int argc, char **argv) - // Reset getopt() state - optind = 1; - -- bSteamCompMgrGrab = BIsNested() && g_bForceRelativeMouse; -+ bSteamCompMgrGrab = GetBackend()->GetNestedHints() && g_bForceRelativeMouse; - - int o; - int opt_index = -1; -@@ -7872,10 +7502,6 @@ steamcompmgr_main(int argc, char **argv) - currentOutputHeight = g_nPreferredOutputHeight; - - init_runtime_info(); --#if HAVE_OPENVR -- if ( BIsVRSession() ) -- vrsession_steam_mode( steamMode ); --#endif - - std::unique_lock xwayland_server_guard(g_SteamCompMgrXWaylandServerMutex); - -@@ -7910,8 +7536,8 @@ steamcompmgr_main(int argc, char **argv) - } - - bool vblank = false; -- g_SteamCompMgrWaiter.AddWaitable( &g_VBlankTimer ); -- g_VBlankTimer.ArmNextVBlank( true ); -+ g_SteamCompMgrWaiter.AddWaitable( &GetVBlankTimer() ); -+ GetVBlankTimer().ArmNextVBlank( true ); - - { - gamescope_xwayland_server_t *pServer = NULL; -@@ -7928,18 +7554,16 @@ steamcompmgr_main(int argc, char **argv) - update_mode_atoms(root_ctx); - XFlush(root_ctx->dpy); - -- if ( !BIsNested() ) -- { -- drm_update_patched_edid(&g_DRM); -- update_edid_prop(); -- } -+ GetBackend()->PostInit(); -+ -+ update_edid_prop(); - - update_screenshot_color_mgmt(); - - // 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 = drm_create_ctm(&g_DRM, glm::mat3x4(glm::transpose(k_2020_from_709))); -+ s_scRGB709To2020Matrix = GetBackend()->CreateBackendBlob( glm::mat3x4( glm::transpose( k_2020_from_709 ) ) ); - - for (;;) - { -@@ -7957,7 +7581,7 @@ steamcompmgr_main(int argc, char **argv) - - g_SteamCompMgrWaiter.PollEvents(); - -- if ( std::optional pendingVBlank = g_VBlankTimer.ProcessVBlank() ) -+ if ( std::optional pendingVBlank = GetVBlankTimer().ProcessVBlank() ) - { - g_SteamCompMgrVBlankTime = *pendingVBlank; - vblank = true; -@@ -7994,14 +7618,11 @@ steamcompmgr_main(int argc, char **argv) - - // If our DRM state is out-of-date, refresh it. This might update - // the output size. -- if ( BIsNested() == false ) -+ if ( GetBackend()->PollState() ) - { -- if ( drm_poll_state( &g_DRM ) ) -- { -- hasRepaint = true; -+ hasRepaint = true; - -- update_mode_atoms(root_ctx, &flush_root); -- } -+ update_mode_atoms(root_ctx, &flush_root); - } - - g_bOutputHDREnabled = (g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; -@@ -8023,7 +7644,8 @@ steamcompmgr_main(int argc, char **argv) - wlserver_unlock(); - } - -- if ( BIsSDLSession() ) -+ // XXX(JoshA): Remake this. It sucks. -+ if ( GetBackend()->UsesVulkanSwapchain() ) - { - vulkan_remake_swapchain(); - -@@ -8162,7 +7784,7 @@ steamcompmgr_main(int argc, char **argv) - - { - GamescopeAppTextureColorspace current_app_colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB; -- std::shared_ptr app_hdr_metadata = nullptr; -+ std::shared_ptr app_hdr_metadata = nullptr; - if ( g_HeldCommits[HELD_COMMIT_BASE] ) - { - current_app_colorspace = g_HeldCommits[HELD_COMMIT_BASE]->colorspace(); -@@ -8192,7 +7814,7 @@ steamcompmgr_main(int argc, char **argv) - 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->metadata, sizeof(hdr_metadata_infoframe)); -+ memcpy(app_hdr_metadata_blob.data(), &app_hdr_metadata->View(), 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() ); -@@ -8235,7 +7857,7 @@ steamcompmgr_main(int argc, char **argv) - - static int nIgnoredOverlayRepaints = 0; - -- const bool bVRR = drm_get_vrr_in_use( &g_DRM ); -+ const bool bVRR = GetBackend()->IsVRRActive(); - - // HACK: Disable tearing if we have an overlay to avoid stutters right now - // TODO: Fix properly. -@@ -8252,13 +7874,13 @@ steamcompmgr_main(int argc, char **argv) - // 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 bNeedsSyncFlip = bForceSyncFlip || g_VBlankTimer.WasCompositing() || nIgnoredOverlayRepaints; -- const bool bDoAsyncFlip = ( ((g_nAsyncFlipsEnabled >= 1) && g_bSupportsAsyncFlips && bSurfaceWantsAsync && !bHasOverlay) || bVRR ) && !bSteamOverlayOpen && !bNeedsSyncFlip; -+ const bool bNeedsSyncFlip = bForceSyncFlip || GetVBlankTimer().WasCompositing() || nIgnoredOverlayRepaints; -+ const bool bDoAsyncFlip = ( ((g_nAsyncFlipsEnabled >= 1) && GetBackend()->SupportsTearing() && bSurfaceWantsAsync && !bHasOverlay) || bVRR ) && !bSteamOverlayOpen && !bNeedsSyncFlip; - - bool bShouldPaint = false; - if ( bDoAsyncFlip ) - { -- if ( hasRepaint && !g_VBlankTimer.WasCompositing() ) -+ if ( hasRepaint && !GetVBlankTimer().WasCompositing() ) - bShouldPaint = true; - } - else -@@ -8267,16 +7889,14 @@ steamcompmgr_main(int argc, char **argv) - } - - // If we have a pending page flip and doing VRR, lets not do another... -- if ( bVRR && g_nCompletedPageFlipCount != g_DRM.flipcount ) -+ if ( bVRR && GetBackend()->PresentationFeedback().CurrentPresentsInFlight() != 0 ) - bShouldPaint = false; - - if ( !bShouldPaint && hasRepaintNonBasePlane && vblank ) - nIgnoredOverlayRepaints++; - --#if HAVE_OPENVR -- if ( BIsVRSession() && !vrsession_visible() ) -+ if ( !GetBackend()->IsVisible() ) - bShouldPaint = false; --#endif - - if ( bShouldPaint ) - { -@@ -8294,7 +7914,7 @@ steamcompmgr_main(int argc, char **argv) - // - // Juuust in case pageflip handler doesn't happen - // so we don't stop vblanking forever. -- g_VBlankTimer.ArmNextVBlank( true ); -+ GetVBlankTimer().ArmNextVBlank( true ); - } - - update_vrr_atoms(root_ctx, false, &flush_root); -diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp -index 2429ee066..69ed2cb4c 100644 ---- a/src/steamcompmgr.hpp -+++ b/src/steamcompmgr.hpp -@@ -43,11 +43,6 @@ extern bool g_bForceHDRSupportDebug; - - extern EStreamColorspace g_ForcedNV12ColorSpace; - --// Disable partial composition for now until we get --// composite priorities working in libliftoff + also --// use the proper libliftoff composite plane system. --static constexpr bool kDisablePartialComposition = true; -- - struct CursorBarrierInfo - { - int x1 = 0; -@@ -164,7 +159,7 @@ extern gamescope::VBlankTime g_SteamCompMgrVBlankTime; - extern pid_t focusWindow_pid; - - void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server); --void gamescope_set_selection(std::string contents, int selection); -+void gamescope_set_selection(std::string contents, GamescopeSelection eSelection); - - MouseCursor *steamcompmgr_get_current_cursor(); - MouseCursor *steamcompmgr_get_server_cursor(uint32_t serverId); -diff --git a/src/steamcompmgr_shared.hpp b/src/steamcompmgr_shared.hpp -index 7de514583..7f7e6ea93 100644 ---- a/src/steamcompmgr_shared.hpp -+++ b/src/steamcompmgr_shared.hpp -@@ -1,8 +1,10 @@ - #pragma once - --#include "xwayland_ctx.hpp" - #include - #include -+#include -+ -+#include "xwayland_ctx.hpp" - #include "gamescope-control-protocol.h" - - struct commit_t; -diff --git a/src/vblankmanager.cpp b/src/vblankmanager.cpp -index 4b617584e..74cfe3dca 100644 ---- a/src/vblankmanager.cpp -+++ b/src/vblankmanager.cpp -@@ -16,20 +16,12 @@ - - #include "vblankmanager.hpp" - #include "steamcompmgr.hpp" --#include "wlserver.hpp" - #include "main.hpp" --#include "drm.hpp" -- --#if HAVE_OPENVR --#include "vr_session.hpp" --#endif - - LogScope g_VBlankLog("vblank"); - - // #define VBLANK_DEBUG - --extern bool env_to_bool(const char *env); -- - namespace gamescope - { - CVBlankTimer::CVBlankTimer() -@@ -37,10 +29,10 @@ namespace gamescope - m_ulTargetVBlank = get_time_in_nanos(); - m_ulLastVBlank = m_ulTargetVBlank; - -- const bool bShouldUseTimerFD = !BIsVRSession() || env_to_bool( "GAMESCOPE_DISABLE_TIMERFD" ); -- -- if ( bShouldUseTimerFD ) -+ if ( !GetBackend()->NeedsFrameSync() ) - { -+ // Majority of backends fall down this optimal -+ // timerfd path, vs nudge thread. - g_VBlankLog.infof( "Using timerfd." ); - } - else -@@ -53,18 +45,8 @@ namespace gamescope - abort(); - } - --#if HAVE_OPENVR -- if ( BIsVRSession() ) -- { -- std::thread vblankThread( [this]() { this->VRNudgeThread(); } ); -- vblankThread.detach(); -- } -- else --#endif -- { -- std::thread vblankThread( [this]() { this->NudgeThread(); } ); -- vblankThread.detach(); -- } -+ std::thread vblankThread( [this]() { this->NudgeThread(); } ); -+ vblankThread.detach(); - } - } - -@@ -112,7 +94,7 @@ namespace gamescope - - VBlankScheduleTime CVBlankTimer::CalcNextWakeupTime( bool bPreemptive ) - { -- const GamescopeScreenType eScreenType = drm_get_screen_type( &g_DRM ); -+ const GamescopeScreenType eScreenType = GetBackend()->GetScreenType(); - - const int nRefreshRate = GetRefresh(); - const uint64_t ulRefreshInterval = kSecInNanoSecs / nRefreshRate; -@@ -129,7 +111,7 @@ namespace gamescope - ? m_ulVBlankDrawBufferRedZone - : ( m_ulVBlankDrawBufferRedZone * 60 * kSecInNanoSecs ) / ( nRefreshRate * kSecInNanoSecs ); - -- bool bVRR = drm_get_vrr_in_use( &g_DRM ); -+ bool bVRR = GetBackend()->IsVRRActive(); - uint64_t ulOffset = 0; - if ( !bVRR ) - { -@@ -368,43 +350,6 @@ namespace gamescope - #endif - } - --#if HAVE_OPENVR -- void CVBlankTimer::VRNudgeThread() -- { -- pthread_setname_np( pthread_self(), "gamescope-vblkvr" ); -- -- for ( ;; ) -- { -- vrsession_wait_until_visible(); -- -- // Includes redzone. -- vrsession_framesync( ~0u ); -- -- uint64_t ulWakeupTime = get_time_in_nanos(); -- -- VBlankTime timeInfo = -- { -- .schedule = -- { -- .ulTargetVBlank = ulWakeupTime + 3'000'000, // Not right. just a stop-gap for now. -- .ulScheduledWakeupPoint = ulWakeupTime, -- }, -- .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)" ); -- } -- } -- } --#endif -- - void CVBlankTimer::NudgeThread() - { - pthread_setname_np( pthread_self(), "gamescope-vblk" ); -@@ -416,10 +361,9 @@ namespace gamescope - if ( !m_bRunning ) - return; - -- VBlankScheduleTime schedule = CalcNextWakeupTime( false ); -- sleep_until_nanos( schedule.ulScheduledWakeupPoint ); -- const uint64_t ulWakeupTime = get_time_in_nanos(); -+ VBlankScheduleTime schedule = GetBackend()->FrameSync(); - -+ const uint64_t ulWakeupTime = get_time_in_nanos(); - { - std::unique_lock lock( m_ScheduleMutex ); - -@@ -446,5 +390,9 @@ namespace gamescope - } - } - --gamescope::CVBlankTimer g_VBlankTimer{}; -+gamescope::CVBlankTimer &GetVBlankTimer() -+{ -+ static gamescope::CVBlankTimer s_VBlankTimer; -+ return s_VBlankTimer; -+} - -diff --git a/src/vblankmanager.hpp b/src/vblankmanager.hpp -index 0a7f1c428..a08af1700 100644 ---- a/src/vblankmanager.hpp -+++ b/src/vblankmanager.hpp -@@ -128,11 +128,9 @@ namespace gamescope - // 93% by default. (kDefaultVBlankRateOfDecayPercentage) - uint64_t m_ulVBlankRateOfDecayPercentage = kDefaultVBlankRateOfDecayPercentage; - --#if HAVE_OPENVR -- void VRNudgeThread(); --#endif - void NudgeThread(); - }; - } - --extern gamescope::CVBlankTimer g_VBlankTimer; -+gamescope::CVBlankTimer &GetVBlankTimer(); -+ -diff --git a/src/vr_session.cpp b/src/vr_session.cpp -index 8696b62d2..9bf8085cd 100644 ---- a/src/vr_session.cpp -+++ b/src/vr_session.cpp -@@ -1,7 +1,16 @@ --#include "vr_session.hpp" -+#include -+#include -+#define VK_NO_PROTOTYPES -+#include -+ -+#pragma GCC diagnostic push -+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -+#include -+#pragma GCC diagnostic pop -+ -+#include "backend.h" - #include "main.hpp" - #include "openvr.h" --#include "rendervulkan.hpp" - #include "steamcompmgr.hpp" - #include "wlserver.hpp" - #include "log.hpp" -@@ -11,107 +20,18 @@ - #include - #include - #include --#include -+ -+struct wlserver_input_method; -+ -+extern bool steamMode; -+extern int g_argc; -+extern char **g_argv; - - static LogScope openvr_log("openvr"); - - static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList ); - static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, std::vector< std::string > &outDeviceExtensionList ); --static void vrsession_input_thread(); -- --struct OpenVRSession --{ -- const char *pchOverlayKey = nullptr; -- const char *pchOverlayName = nullptr; -- const char *pchOverlayIcon = nullptr; -- bool bExplicitOverlayName = false; -- bool bNudgeToVisible = false; -- bool bEnableControlBar = false; -- bool bEnableControlBarKeyboard = false; -- bool bEnableControlBarClose = false; -- bool bModal = false; -- float flPhysicalWidth = 2.0f; -- float flPhysicalCurvature = 0.0f; -- float flPhysicalPreCurvePitch = 0.0f; -- float flScrollSpeed = 8.0f; -- float flScrollAccum[2] = { 0.0f, 0.0f }; -- vr::VROverlayHandle_t hOverlay = vr::k_ulOverlayHandleInvalid; -- vr::VROverlayHandle_t hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; -- struct wlserver_input_method *pIME = nullptr; --}; -- --OpenVRSession &GetVR() --{ -- static OpenVRSession s_Global; -- return s_Global; --} -- --bool vr_init(int argc, char **argv) --{ -- vr::EVRInitError error = vr::VRInitError_None; -- VR_Init(&error, vr::VRApplication_Background); -- -- if ( error != vr::VRInitError_None ) -- { -- openvr_log.errorf("Unable to init VR runtime: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription( error )); -- return false; -- } -- -- // Reset getopt() state -- optind = 1; -- -- 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 0: // long options without a short option -- opt_name = gamescope_options[opt_index].name; -- if (strcmp(opt_name, "vr-overlay-key") == 0) { -- GetVR().pchOverlayKey = optarg; -- } else if (strcmp(opt_name, "vr-overlay-explicit-name") == 0) { -- GetVR().pchOverlayName = optarg; -- GetVR().bExplicitOverlayName = true; -- } else if (strcmp(opt_name, "vr-overlay-default-name") == 0) { -- GetVR().pchOverlayName = optarg; -- } else if (strcmp(opt_name, "vr-overlay-icon") == 0) { -- GetVR().pchOverlayIcon = optarg; -- } else if (strcmp(opt_name, "vr-overlay-show-immediately") == 0) { -- GetVR().bNudgeToVisible = true; -- } else if (strcmp(opt_name, "vr-overlay-enable-control-bar") == 0) { -- GetVR().bEnableControlBar = true; -- } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-keyboard") == 0) { -- GetVR().bEnableControlBarKeyboard = true; -- } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-close") == 0) { -- GetVR().bEnableControlBarClose = true; -- } else if (strcmp(opt_name, "vr-overlay-modal") == 0) { -- GetVR().bModal = true; -- } else if (strcmp(opt_name, "vr-overlay-physical-width") == 0) { -- GetVR().flPhysicalWidth = atof( optarg ); -- if ( GetVR().flPhysicalWidth <= 0.0f ) -- GetVR().flPhysicalWidth = 2.0f; -- } else if (strcmp(opt_name, "vr-overlay-physical-curvature") == 0) { -- GetVR().flPhysicalCurvature = atof( optarg ); -- } else if (strcmp(opt_name, "vr-overlay-physical-pre-curve-pitch") == 0) { -- GetVR().flPhysicalPreCurvePitch = atof( optarg ); -- } else if (strcmp(opt_name, "vr-scroll-speed") == 0) { -- GetVR().flScrollSpeed = atof( optarg ); -- } -- break; -- case '?': -- assert(false); // unreachable -- } -- } -- -- if (!GetVR().pchOverlayKey) -- GetVR().pchOverlayKey = wlserver_get_wl_display_name(); -- -- if (!GetVR().pchOverlayName) -- GetVR().pchOverlayName = "Gamescope"; - -- return true; --} - - // Not in public headers yet. - namespace vr -@@ -122,302 +42,6 @@ namespace vr - const EVRButtonId k_EButton_QAM = (EVRButtonId)(51); - } - --bool vrsession_init() --{ -- // Setup the overlay. -- -- if ( !vr::VROverlay() ) -- { -- openvr_log.errorf("SteamVR runtime version mismatch!\n"); -- return false; -- } -- -- vr::VROverlay()->CreateDashboardOverlay( -- GetVR().pchOverlayKey, -- GetVR().pchOverlayName, -- &GetVR().hOverlay, &GetVR().hOverlayThumbnail ); -- -- vr::VROverlay()->SetOverlayInputMethod( GetVR().hOverlay, vr::VROverlayInputMethod_Mouse ); -- -- vr::HmdVector2_t vMouseScale = { { (float)g_nOutputWidth, (float)g_nOutputHeight } }; -- vr::VROverlay()->SetOverlayMouseScale( GetVR().hOverlay, &vMouseScale ); -- -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, true ); -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBar, GetVR().bEnableControlBar ); -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarKeyboard, GetVR().bEnableControlBarKeyboard ); -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarClose, GetVR().bEnableControlBarClose ); -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_WantsModalBehavior, GetVR().bModal ); -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); -- vrsession_update_touch_mode(); -- -- vr::VROverlay()->SetOverlayWidthInMeters( GetVR().hOverlay, GetVR().flPhysicalWidth ); -- vr::VROverlay()->SetOverlayCurvature ( GetVR().hOverlay, GetVR().flPhysicalCurvature ); -- vr::VROverlay()->SetOverlayPreCurvePitch( GetVR().hOverlay, GetVR().flPhysicalPreCurvePitch ); -- -- if ( GetVR().pchOverlayIcon ) -- { -- vr::EVROverlayError err = vr::VROverlay()->SetOverlayFromFile( GetVR().hOverlayThumbnail, GetVR().pchOverlayIcon ); -- if( err != vr::VROverlayError_None ) -- { -- openvr_log.errorf( "Unable to set thumbnail to %s: %s\n", GetVR().pchOverlayIcon, vr::VROverlay()->GetOverlayErrorNameFromEnum( err ) ); -- } -- } -- -- // Setup misc. stuff -- -- g_nOutputRefresh = (int) vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ); -- -- std::thread input_thread_vrinput( vrsession_input_thread ); -- input_thread_vrinput.detach(); -- -- return true; --} -- --std::mutex g_OverlayVisibleMutex; --std::condition_variable g_OverlayVisibleCV; --std::atomic g_bOverlayVisible = { false }; -- --bool vrsession_visible() --{ -- return g_bOverlayVisible.load(); --} -- --void vrsession_set_dashboard_visible( bool bVisible ) --{ -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_VisibleInDashboard, bVisible ); --} -- --void vrsession_wait_until_visible() --{ -- if (vrsession_visible()) -- return; -- -- std::unique_lock lock(g_OverlayVisibleMutex); -- g_OverlayVisibleCV.wait( lock, []{ return g_bOverlayVisible.load(); } ); --} -- --void vrsession_present( vr::VRVulkanTextureData_t *pTextureData ) --{ -- vr::Texture_t texture = { pTextureData, vr::TextureType_Vulkan, vr::ColorSpace_Gamma }; -- vr::VROverlay()->SetOverlayTexture( GetVR().hOverlay, &texture ); -- if ( GetVR().bNudgeToVisible ) -- { -- vr::VROverlay()->ShowDashboard( GetVR().pchOverlayKey ); -- GetVR().bNudgeToVisible = false; -- } --} -- --static void vector_append_unique_str( std::vector& exts, const char *str ) --{ -- for ( auto &c_str : exts ) -- { -- if ( !strcmp( c_str, str ) ) -- return; -- } -- -- exts.push_back( str ); --} -- --void vrsession_append_instance_exts( std::vector& exts ) --{ -- static std::vector s_exts; -- GetVulkanInstanceExtensionsRequired( s_exts ); -- -- for (const auto &str : s_exts) -- vector_append_unique_str( exts, str.c_str() ); --} -- --void vrsession_append_device_exts( VkPhysicalDevice physDev, std::vector& exts ) --{ -- static std::vector s_exts; -- GetVulkanDeviceExtensionsRequired( physDev, s_exts ); -- -- for (const auto &str : s_exts) -- vector_append_unique_str( exts, str.c_str() ); --} -- --bool vrsession_framesync( uint32_t timeoutMS ) --{ -- return vr::VROverlay()->WaitFrameSync( timeoutMS ) != vr::VROverlayError_None; --} -- --/* --static int VRButtonToWLButton( vr::EVRMouseButton mb ) --{ -- switch( mb ) -- { -- default: -- case vr::VRMouseButton_Left: -- return BTN_LEFT; -- case vr::VRMouseButton_Right: -- return BTN_RIGHT; -- case vr::VRMouseButton_Middle: -- return BTN_MIDDLE; -- } --} --*/ -- --bool vrsession_ime_init() --{ -- GetVR().pIME = create_local_ime(); -- return true; --} -- --static void vrsession_input_thread() --{ -- pthread_setname_np( pthread_self(), "gamescope-vrinp" ); -- -- // Josh: PollNextOverlayEvent sucks. -- // I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep. -- while (true) -- { -- vr::VREvent_t vrEvent; -- while( vr::VROverlay()->PollNextOverlayEvent( GetVR().hOverlay, &vrEvent, sizeof( vrEvent ) ) ) -- { -- uint32_t timestamp = vrEvent.eventAgeSeconds * 1'000'000; -- -- switch( vrEvent.eventType ) -- { -- case vr::VREvent_OverlayClosed: -- case vr::VREvent_Quit: -- raise( SIGTERM ); -- break; -- -- case vr::VREvent_KeyboardCharInput: -- { -- if (GetVR().pIME) -- { -- type_text(GetVR().pIME, vrEvent.data.keyboard.cNewInput); -- } -- break; -- } -- -- case vr::VREvent_MouseMove: -- { -- float x = vrEvent.data.mouse.x; -- float y = g_nOutputHeight - vrEvent.data.mouse.y; -- -- x /= (float)g_nOutputWidth; -- y /= (float)g_nOutputHeight; -- -- wlserver_lock(); -- wlserver_touchmotion( x, y, 0, timestamp ); -- wlserver_unlock(); -- break; -- } -- case vr::VREvent_MouseButtonUp: -- case vr::VREvent_MouseButtonDown: -- { -- float x = vrEvent.data.mouse.x; -- float y = g_nOutputHeight - vrEvent.data.mouse.y; -- -- x /= (float)g_nOutputWidth; -- y /= (float)g_nOutputHeight; -- -- wlserver_lock(); -- if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) -- wlserver_touchdown( x, y, 0, timestamp ); -- else -- wlserver_touchup( 0, timestamp ); -- wlserver_unlock(); -- break; -- } -- -- case vr::VREvent_ScrollSmooth: -- { -- wlserver_lock(); -- -- GetVR().flScrollAccum[0] += -vrEvent.data.scroll.xdelta * GetVR().flScrollSpeed; -- GetVR().flScrollAccum[1] += -vrEvent.data.scroll.ydelta * GetVR().flScrollSpeed; -- -- float dx, dy; -- GetVR().flScrollAccum[0] = modf( GetVR().flScrollAccum[0], &dx ); -- GetVR().flScrollAccum[1] = modf( GetVR().flScrollAccum[1], &dy ); -- -- wlserver_mousewheel( dx, dy, timestamp ); -- wlserver_unlock(); -- break; -- } -- -- case vr::VREvent_ButtonPress: -- { -- vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button; -- -- if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM) -- break; -- -- if (button == vr::k_EButton_Steam) -- openvr_log.infof("STEAM button pressed."); -- else -- openvr_log.infof("QAM button pressed."); -- -- wlserver_open_steam_menu( button == vr::k_EButton_QAM ); -- break; -- } -- -- case vr::VREvent_OverlayShown: -- case vr::VREvent_OverlayHidden: -- { -- { -- std::unique_lock lock(g_OverlayVisibleMutex); -- g_bOverlayVisible = vrEvent.eventType == vr::VREvent_OverlayShown; -- } -- g_OverlayVisibleCV.notify_all(); -- } -- } -- } -- sleep_for_nanos(2'000'000); -- } --} -- --void vrsession_update_touch_mode() --{ -- const bool bHideLaserIntersection = g_nTouchClickMode != WLSERVER_TOUCH_CLICK_PASSTHROUGH; -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_HideLaserIntersection, bHideLaserIntersection ); --} -- --struct rgba_t --{ -- uint8_t r,g,b,a; --}; -- --void vrsession_title( const char *title, std::shared_ptr> icon ) --{ -- if ( !GetVR().bExplicitOverlayName ) -- { -- vr::VROverlay()->SetOverlayName( GetVR().hOverlay, (title && *title) ? title : GetVR().pchOverlayName ); -- } -- -- if ( icon && icon->size() >= 3 ) -- { -- const uint32_t width = (*icon)[0]; -- const uint32_t height = (*icon)[1]; -- -- for (uint32_t& val : *icon) -- { -- rgba_t rgb = *((rgba_t*)&val); -- std::swap(rgb.r, rgb.b); -- val = *((uint32_t*)&rgb); -- } -- -- vr::VROverlay()->SetOverlayRaw( GetVR().hOverlayThumbnail, &(*icon)[2], width, height, sizeof(uint32_t) ); -- } -- else if ( GetVR().pchOverlayName ) -- { -- vr::VROverlay()->SetOverlayFromFile( GetVR().hOverlayThumbnail, GetVR().pchOverlayIcon ); -- } -- else -- { -- vr::VROverlay()->ClearOverlayTexture( GetVR().hOverlayThumbnail ); -- } --} -- --void vrsession_steam_mode( bool steamMode ) --{ -- vr::VROverlay()->SetOverlayFlag( GetVR().hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode ); --} -- - /////////////////////////////////////////////// - // Josh: - // GetVulkanInstanceExtensionsRequired and GetVulkanDeviceExtensionsRequired return *space separated* exts :( -@@ -508,3 +132,630 @@ static bool GetVulkanDeviceExtensionsRequired( VkPhysicalDevice pPhysicalDevice, - - return true; - } -+ -+namespace gamescope -+{ -+ class CVROverlayConnector final : public IBackendConnector -+ { -+ public: -+ -+ ////////////////////// -+ // IBackendConnector -+ ////////////////////// -+ -+ CVROverlayConnector() -+ { -+ } -+ virtual ~CVROverlayConnector() -+ { -+ } -+ -+ virtual 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 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 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; -+ } -+ -+ virtual const char *GetName() const override -+ { -+ return "OpenVR"; -+ } -+ virtual const char *GetMake() const override -+ { -+ return "Gamescope"; -+ } -+ virtual const char *GetModel() const override -+ { -+ return "Virtual Display"; -+ } -+ -+ private: -+ BackendConnectorHDRInfo m_HDRInfo{}; -+ }; -+ -+ class COpenVRBackend final : public CBaseBackend, public INestedHints -+ { -+ public: -+ COpenVRBackend() -+ { -+ } -+ -+ virtual ~COpenVRBackend() -+ { -+ } -+ -+ ///////////// -+ // IBackend -+ ///////////// -+ -+ virtual bool Init() override -+ { -+ vr::EVRInitError error = vr::VRInitError_None; -+ VR_Init(&error, vr::VRApplication_Background); -+ -+ if ( error != vr::VRInitError_None ) -+ { -+ openvr_log.errorf("Unable to init VR runtime: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription( error )); -+ return false; -+ } -+ -+ // 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) -+ { -+ const char *opt_name; -+ 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_pchOverlayKey = 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) { -+ m_pchOverlayName = optarg; -+ } else if (strcmp(opt_name, "vr-overlay-icon") == 0) { -+ m_pchOverlayIcon = optarg; -+ } else if (strcmp(opt_name, "vr-overlay-show-immediately") == 0) { -+ m_bNudgeToVisible = true; -+ } 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) { -+ m_bEnableControlBarKeyboard = true; -+ } else if (strcmp(opt_name, "vr-overlay-enable-control-bar-close") == 0) { -+ m_bEnableControlBarClose = true; -+ } else if (strcmp(opt_name, "vr-overlay-modal") == 0) { -+ m_bModal = true; -+ } 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) { -+ m_flPhysicalCurvature = atof( optarg ); -+ } 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) { -+ m_flScrollSpeed = atof( optarg ); -+ } -+ break; -+ case '?': -+ assert(false); // unreachable -+ } -+ } -+ -+ if ( m_pchOverlayKey ) -+ m_pchOverlayKey = wlserver_get_wl_display_name(); -+ -+ if ( m_pchOverlayName ) -+ m_pchOverlayName = "Gamescope"; -+ -+ if ( !vr::VROverlay() ) -+ { -+ openvr_log.errorf( "SteamVR runtime version mismatch!\n" ); -+ return false; -+ } -+ -+ vr::VROverlay()->CreateDashboardOverlay( -+ m_pchOverlayKey, -+ m_pchOverlayName, -+ &m_hOverlay, &m_hOverlayThumbnail ); -+ -+ vr::VROverlay()->SetOverlayInputMethod( m_hOverlay, vr::VROverlayInputMethod_Mouse ); -+ -+ vr::HmdVector2_t vMouseScale = { { (float)g_nOutputWidth, (float)g_nOutputHeight } }; -+ vr::VROverlay()->SetOverlayMouseScale( m_hOverlay, &vMouseScale ); -+ -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_IgnoreTextureAlpha, true ); -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBar, m_bEnableControlBar ); -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarKeyboard, m_bEnableControlBarKeyboard ); -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarClose, m_bEnableControlBarClose ); -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_WantsModalBehavior, m_bModal ); -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, false ); -+ -+ vr::VROverlay()->SetOverlayWidthInMeters( m_hOverlay, m_flPhysicalWidth ); -+ vr::VROverlay()->SetOverlayCurvature ( m_hOverlay, m_flPhysicalCurvature ); -+ vr::VROverlay()->SetOverlayPreCurvePitch( m_hOverlay, m_flPhysicalPreCurvePitch ); -+ -+ if ( m_pchOverlayIcon ) -+ { -+ vr::EVROverlayError err = vr::VROverlay()->SetOverlayFromFile( m_hOverlayThumbnail, m_pchOverlayIcon ); -+ if( err != vr::VROverlayError_None ) -+ { -+ openvr_log.errorf( "Unable to set thumbnail to %s: %s\n", m_pchOverlayIcon, vr::VROverlay()->GetOverlayErrorNameFromEnum( err ) ); -+ } -+ } -+ -+ // Setup misc. stuff -+ g_nOutputRefresh = (int) vr::VRSystem()->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float ); -+ -+ std::thread input_thread_vrinput( [this](){ this->VRInputThread(); } ); -+ input_thread_vrinput.detach(); -+ -+ return true; -+ } -+ -+ virtual bool PostInit() override -+ { -+ m_pIME = create_local_ime(); -+ if ( !m_pIME ) -+ return false; -+ -+ return true; -+ } -+ -+ 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 -+ { -+ 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( VkFormat *pPrimaryPlaneFormat, VkFormat *pOverlayPlaneFormat ) const override -+ { -+ *pPrimaryPlaneFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; -+ *pOverlayPlaneFormat = VK_FORMAT_B8G8R8A8_UNORM; -+ } -+ virtual bool ValidPhysicalDevice( VkPhysicalDevice pVkPhysicalDevice ) const override -+ { -+ return true; -+ } -+ -+ virtual int Present( const FrameInfo_t *pFrameInfo, bool bAsync ) override -+ { -+ // TODO: Resolve const crap -+ std::optional oCompositeResult = vulkan_composite( (FrameInfo_t *)pFrameInfo, nullptr, false ); -+ if ( !oCompositeResult ) -+ return -EINVAL; -+ -+ UpdateTouchMode(); -+ -+ auto outputImage = vulkan_get_last_output_image( false, false ); -+ -+ vr::VRVulkanTextureData_t data = -+ { -+ .m_nImage = (uint64_t)(uintptr_t)outputImage->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 = outputImage->width(), -+ .m_nHeight = outputImage->height(), -+ .m_nFormat = outputImage->format(), -+ .m_nSampleCount = 1, -+ }; -+ -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_EnableControlBarSteamUI, steamMode ); -+ -+ // Wait for the composite result on our side *after* we -+ // commit the buffer to the compositor to avoid a bubble. -+ vulkan_wait( *oCompositeResult, true ); -+ -+ vr::Texture_t texture = { &data, vr::TextureType_Vulkan, vr::ColorSpace_Gamma }; -+ vr::VROverlay()->SetOverlayTexture( m_hOverlay, &texture ); -+ if ( m_bNudgeToVisible ) -+ { -+ vr::VROverlay()->ShowDashboard( m_pchOverlayKey ); -+ m_bNudgeToVisible = false; -+ } -+ -+ return 0; -+ } -+ -+ virtual void DirtyState( bool bForce, bool bForceModeset ) override -+ { -+ } -+ -+ virtual bool PollState() override -+ { -+ return false; -+ } -+ -+ virtual std::shared_ptr CreateBackendBlob( std::span data ) override -+ { -+ return std::make_shared( data ); -+ } -+ -+ virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override -+ { -+ return 0; -+ } -+ -+ virtual void LockBackendFb( uint32_t uFbId ) override -+ { -+ abort(); -+ } -+ virtual void UnlockBackendFb( uint32_t uFbId ) override -+ { -+ abort(); -+ } -+ virtual void DropBackendFb( uint32_t uFbId ) override -+ { -+ abort(); -+ } -+ -+ 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 IsVRRActive() const override -+ { -+ return false; -+ } -+ -+ 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 IsVisible() const override -+ { -+ return m_bOverlayVisible.load(); -+ } -+ -+ virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override -+ { -+ return uvecSize; -+ } -+ -+ virtual bool HackTemporarySetDynamicRefresh( int nRefresh ) override -+ { -+ return false; -+ } -+ -+ virtual void HackUpdatePatchedEdid() override -+ { -+ } -+ -+ virtual bool NeedsFrameSync() const override -+ { -+ return true; -+ } -+ virtual VBlankScheduleTime FrameSync() override -+ { -+ WaitUntilVisible(); -+ -+ if ( vr::VROverlay()->WaitFrameSync( ~0u ) != vr::VROverlayError_None ) -+ openvr_log.errorf( "WaitFrameSync failed!" ); -+ -+ uint64_t ulNow = get_time_in_nanos(); -+ return VBlankScheduleTime -+ { -+ .ulTargetVBlank = ulNow + 3'000'000, // Not right. just a stop-gap for now. -+ .ulScheduledWakeupPoint = ulNow, -+ }; -+ } -+ -+ virtual INestedHints *GetNestedHints() override -+ { -+ return this; -+ } -+ -+ /////////////////// -+ // INestedHints -+ /////////////////// -+ -+ virtual void SetCursorImage( std::shared_ptr info ) override -+ { -+ } -+ virtual void SetRelativeMouseMode( bool bRelative ) override -+ { -+ } -+ virtual void SetVisible( bool bVisible ) override -+ { -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_VisibleInDashboard, bVisible ); -+ } -+ virtual void SetTitle( std::shared_ptr szTitle ) override -+ { -+ if ( !m_bExplicitOverlayName ) -+ vr::VROverlay()->SetOverlayName( m_hOverlay, szTitle ? szTitle->c_str() : m_pchOverlayName ); -+ -+ } -+ virtual void SetIcon( std::shared_ptr> uIconPixels ) override -+ { -+ if ( uIconPixels && uIconPixels->size() >= 3 ) -+ { -+ const uint32_t uWidth = (*uIconPixels)[0]; -+ const uint32_t uHeight = (*uIconPixels)[1]; -+ -+ struct rgba_t -+ { -+ uint8_t r,g,b,a; -+ }; -+ -+ for ( uint32_t& val : *uIconPixels ) -+ { -+ rgba_t rgb = *((rgba_t*)&val); -+ std::swap(rgb.r, rgb.b); -+ val = *((uint32_t*)&rgb); -+ } -+ -+ vr::VROverlay()->SetOverlayRaw( m_hOverlayThumbnail, &(*uIconPixels)[2], uWidth, uHeight, sizeof(uint32_t) ); -+ } -+ else if ( m_pchOverlayName ) -+ { -+ vr::VROverlay()->SetOverlayFromFile( m_hOverlayThumbnail, m_pchOverlayIcon ); -+ } -+ else -+ { -+ vr::VROverlay()->ClearOverlayTexture( m_hOverlayThumbnail ); -+ } -+ } -+ virtual std::optional GetHostCursor() override -+ { -+ return std::nullopt; -+ } -+ -+ protected: -+ -+ virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override -+ { -+ } -+ -+ private: -+ -+ void UpdateTouchMode() -+ { -+ const bool bHideLaserIntersection = g_nTouchClickMode != WLSERVER_TOUCH_CLICK_PASSTHROUGH; -+ vr::VROverlay()->SetOverlayFlag( m_hOverlay, vr::VROverlayFlags_HideLaserIntersection, bHideLaserIntersection ); -+ } -+ -+ void WaitUntilVisible() -+ { -+ m_bOverlayVisible.wait( false ); -+ } -+ -+ void VRInputThread() -+ { -+ pthread_setname_np( pthread_self(), "gamescope-vrinp" ); -+ -+ // Josh: PollNextOverlayEvent sucks. -+ // I want WaitNextOverlayEvent (like SDL_WaitEvent) so this doesn't have to spin and sleep. -+ while (true) -+ { -+ vr::VREvent_t vrEvent; -+ while( vr::VROverlay()->PollNextOverlayEvent( m_hOverlay, &vrEvent, sizeof( vrEvent ) ) ) -+ { -+ uint32_t timestamp = vrEvent.eventAgeSeconds * 1'000'000; -+ -+ switch( vrEvent.eventType ) -+ { -+ case vr::VREvent_OverlayClosed: -+ case vr::VREvent_Quit: -+ raise( SIGTERM ); -+ break; -+ -+ case vr::VREvent_KeyboardCharInput: -+ { -+ if (m_pIME) -+ { -+ type_text(m_pIME, vrEvent.data.keyboard.cNewInput); -+ } -+ break; -+ } -+ -+ case vr::VREvent_MouseMove: -+ { -+ float x = vrEvent.data.mouse.x; -+ float y = g_nOutputHeight - vrEvent.data.mouse.y; -+ -+ x /= (float)g_nOutputWidth; -+ y /= (float)g_nOutputHeight; -+ -+ wlserver_lock(); -+ wlserver_touchmotion( x, y, 0, timestamp ); -+ wlserver_unlock(); -+ break; -+ } -+ case vr::VREvent_MouseButtonUp: -+ case vr::VREvent_MouseButtonDown: -+ { -+ float x = vrEvent.data.mouse.x; -+ float y = g_nOutputHeight - vrEvent.data.mouse.y; -+ -+ x /= (float)g_nOutputWidth; -+ y /= (float)g_nOutputHeight; -+ -+ wlserver_lock(); -+ if ( vrEvent.eventType == vr::VREvent_MouseButtonDown ) -+ wlserver_touchdown( x, y, 0, timestamp ); -+ else -+ wlserver_touchup( 0, timestamp ); -+ wlserver_unlock(); -+ break; -+ } -+ -+ case vr::VREvent_ScrollSmooth: -+ { -+ wlserver_lock(); -+ -+ m_flScrollAccum[0] += -vrEvent.data.scroll.xdelta * m_flScrollSpeed; -+ m_flScrollAccum[1] += -vrEvent.data.scroll.ydelta * m_flScrollSpeed; -+ -+ float dx, dy; -+ m_flScrollAccum[0] = modf( m_flScrollAccum[0], &dx ); -+ m_flScrollAccum[1] = modf( m_flScrollAccum[1], &dy ); -+ -+ wlserver_mousewheel( dx, dy, timestamp ); -+ wlserver_unlock(); -+ break; -+ } -+ -+ case vr::VREvent_ButtonPress: -+ { -+ vr::EVRButtonId button = (vr::EVRButtonId)vrEvent.data.controller.button; -+ -+ if (button != vr::k_EButton_Steam && button != vr::k_EButton_QAM) -+ break; -+ -+ if (button == vr::k_EButton_Steam) -+ openvr_log.infof("STEAM button pressed."); -+ else -+ openvr_log.infof("QAM button pressed."); -+ -+ wlserver_open_steam_menu( button == vr::k_EButton_QAM ); -+ break; -+ } -+ -+ case vr::VREvent_OverlayShown: -+ case vr::VREvent_OverlayHidden: -+ { -+ m_bOverlayVisible = vrEvent.eventType == vr::VREvent_OverlayShown; -+ m_bOverlayVisible.notify_all(); -+ break; -+ } -+ -+ default: -+ break; -+ } -+ } -+ sleep_for_nanos( 2'000'000ul ); -+ } -+ } -+ -+ CVROverlayConnector m_Connector; -+ const char *m_pchOverlayKey = nullptr; -+ 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_bModal = false; -+ float m_flPhysicalWidth = 2.0f; -+ float m_flPhysicalCurvature = 0.0f; -+ float m_flPhysicalPreCurvePitch = 0.0f; -+ float m_flScrollSpeed = 8.0f; -+ float m_flScrollAccum[2] = { 0.0f, 0.0f }; -+ vr::VROverlayHandle_t m_hOverlay = vr::k_ulOverlayHandleInvalid; -+ vr::VROverlayHandle_t m_hOverlayThumbnail = vr::k_ulOverlayHandleInvalid; -+ wlserver_input_method *m_pIME = nullptr; -+ std::atomic m_bOverlayVisible = { false }; -+ }; -+ -+ ///////////////////////// -+ // Backend Instantiator -+ ///////////////////////// -+ -+ template <> -+ bool IBackend::Set() -+ { -+ return Set( new COpenVRBackend{} ); -+ } -+} -diff --git a/src/vr_session.hpp b/src/vr_session.hpp -deleted file mode 100644 -index f8007ae6d..000000000 ---- a/src/vr_session.hpp -+++ /dev/null -@@ -1,31 +0,0 @@ --#pragma once -- --#include --#include --#define VK_NO_PROTOTYPES --#include -- --#pragma GCC diagnostic push --#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" --#include --#pragma GCC diagnostic pop -- --bool vr_init(int argc, char **argv); -- --bool vrsession_init(); --bool vrsession_visible(); --void vrsession_wait_until_visible(); --void vrsession_present( vr::VRVulkanTextureData_t *pTextureData ); -- --void vrsession_append_instance_exts( std::vector& exts ); --void vrsession_append_device_exts( VkPhysicalDevice physDev, std::vector& exts ); -- --bool vrsession_framesync( uint32_t timeoutMS ); --void vrsession_update_touch_mode(); -- --void vrsession_title( const char *title, std::shared_ptr> icon ); --bool vrsession_ime_init(); -- --void vrsession_steam_mode( bool bSteamMode ); -- --void vrsession_set_dashboard_visible( bool bVisible ); -diff --git a/src/waitable.h b/src/waitable.h -index cd806f698..fa7022fbe 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -7,6 +7,7 @@ - #include - - #include -+#include - - #include "log.hpp" - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index cc5e68be0..3a3a8a05e 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -40,13 +40,12 @@ - #include "presentation-time-protocol.h" - - #include "wlserver.hpp" --#include "drm.hpp" -+#include "drm_include.h" - #include "main.hpp" - #include "steamcompmgr.hpp" - #include "log.hpp" - #include "ime.hpp" - #include "xwayland_ctx.hpp" --#include "sdlwindow.hpp" - - #if HAVE_PIPEWIRE - #include "pipewire.hpp" -@@ -766,8 +765,7 @@ static void gamescope_swapchain_set_hdr_metadata( struct wl_client *client, stru - infoframe.max_cll = max_cll; - infoframe.max_fall = max_fall; - -- wl_info->swapchain_feedback->hdr_metadata_blob = -- drm_create_hdr_metadata_blob( &g_DRM, &metadata ); -+ wl_info->swapchain_feedback->hdr_metadata_blob = GetBackend()->CreateBackendBlob( metadata ); - } - } - -@@ -889,6 +887,46 @@ static const struct gamescope_control_interface gamescope_control_impl = { - .set_app_target_refresh_cycle = gamescope_control_set_app_target_refresh_cycle, - }; - -+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() ) -+ { -+ size_t size = pConn->GetValidDynamicRefreshRates().size() * sizeof(uint32_t); -+ uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); -+ memcpy( ptr, pConn->GetValidDynamicRefreshRates().data(), size ); -+ } -+ 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 ); -@@ -904,10 +942,7 @@ static void gamescope_control_bind( struct wl_client *client, void *data, uint32 - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_PIXEL_FILTER, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DONE, 0, 0 ); - -- if ( !BIsNested() ) -- { -- drm_send_gamescope_control( resource, &g_DRM ); -- } -+ wlserver_send_gamescope_control( resource ); - - wlserver.gamescope_controls.push_back(resource); - } -@@ -1249,14 +1284,16 @@ void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle - - /////////////////////// - -+bool wlsession_active() -+{ -+ return wlserver.wlr.session->active; -+} -+ - static void handle_session_active( struct wl_listener *listener, void *data ) - { -- if (wlserver.wlr.session->active) { -- g_DRM.out_of_date = 1; -- g_DRM.needs_modeset = 1; -- } -- g_DRM.paused = !wlserver.wlr.session->active; -- wl_log.infof( "Session %s", g_DRM.paused ? "paused" : "resumed" ); -+ if (wlserver.wlr.session->active) -+ GetBackend()->DirtyState( true, true ); -+ wl_log.infof( "Session %s", wlserver.wlr.session->active ? "resumed" : "paused" ); - } - - static void handle_wlr_log(enum wlr_log_importance importance, const char *fmt, va_list args) -@@ -1336,7 +1373,7 @@ bool wlsession_init( void ) { - }; - wlserver_set_output_info( &output_info ); - -- if ( BIsNested() ) -+ if ( !GetBackend()->IsSessionBased() ) - return true; - - wlserver.wlr.session = wlr_session_create( wlserver.display ); -@@ -1354,7 +1391,7 @@ bool wlsession_init( void ) { - - static void kms_device_handle_change( struct wl_listener *listener, void *data ) - { -- g_DRM.out_of_date = 1; -+ GetBackend()->DirtyState(); - wl_log.infof( "Got change event for KMS device" ); - - nudge_steamcompmgr(); -@@ -1570,8 +1607,6 @@ void xdg_surface_new(struct wl_listener *listener, void *data) - bool wlserver_init( void ) { - assert( wlserver.display != nullptr ); - -- bool bIsDRM = !BIsNested(); -- - wl_list_init(&pending_surfaces); - - wlserver.event_loop = wl_display_get_event_loop(wlserver.display); -@@ -1583,7 +1618,7 @@ bool wlserver_init( void ) { - - wl_signal_add( &wlserver.wlr.multi_backend->events.new_input, &new_input_listener ); - -- if ( bIsDRM == True ) -+ if ( GetBackend()->IsSessionBased() ) - { - wlserver.wlr.libinput_backend = wlr_libinput_backend_create( wlserver.display, wlserver.wlr.session ); - if ( wlserver.wlr.libinput_backend == NULL) -@@ -1945,22 +1980,23 @@ static void apply_touchscreen_orientation(double *x, double *y ) - double ty = 0; - - // Use internal screen always for orientation purposes. -- switch ( g_drmEffectiveOrientation[gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL] ) -+ switch ( GetBackend()->GetConnector( gamescope::GAMESCOPE_SCREEN_TYPE_INTERNAL )->GetCurrentOrientation() ) - { - default: -- case DRM_MODE_ROTATE_0: -+ case GAMESCOPE_PANEL_ORIENTATION_AUTO: -+ case GAMESCOPE_PANEL_ORIENTATION_0: - tx = *x; - ty = *y; - break; -- case DRM_MODE_ROTATE_90: -+ case GAMESCOPE_PANEL_ORIENTATION_90: - tx = 1.0 - *y; - ty = *x; - break; -- case DRM_MODE_ROTATE_180: -+ case GAMESCOPE_PANEL_ORIENTATION_180: - tx = 1.0 - *x; - ty = 1.0 - *y; - break; -- case DRM_MODE_ROTATE_270: -+ case GAMESCOPE_PANEL_ORIENTATION_270: - tx = *y; - ty = 1.0 - *x; - break; -@@ -1974,12 +2010,12 @@ bool g_bTrackpadTouchExternalDisplay = false; - - int get_effective_touch_mode() - { -- if (!BIsNested() && g_bTrackpadTouchExternalDisplay) -- { -- gamescope::GamescopeScreenType screenType = drm_get_screen_type(&g_DRM); -- if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) -- return WLSERVER_TOUCH_CLICK_TRACKPAD; -- } -+ if ( !GetBackend() || !GetBackend()->GetCurrentConnector() ) -+ return g_nTouchClickMode; -+ -+ gamescope::GamescopeScreenType screenType = GetBackend()->GetCurrentConnector()->GetScreenType(); -+ if ( screenType == gamescope::GAMESCOPE_SCREEN_TYPE_EXTERNAL && g_nTouchClickMode == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) -+ return WLSERVER_TOUCH_CLICK_TRACKPAD; - - return g_nTouchClickMode; - } -diff --git a/src/wlserver.hpp b/src/wlserver.hpp -index 852a5a15d..c884a3ab9 100644 ---- a/src/wlserver.hpp -+++ b/src/wlserver.hpp -@@ -13,7 +13,6 @@ - #include - #include - --#include "drm.hpp" - #include "steamcompmgr_shared.hpp" - - #define WLSERVER_BUTTON_COUNT 7 -@@ -31,7 +30,7 @@ struct wlserver_vk_swapchain_feedback - VkPresentModeKHR vk_present_mode; - VkBool32 vk_clipped; - -- std::shared_ptr hdr_metadata_blob; -+ std::shared_ptr hdr_metadata_blob; - }; - - struct ResListEntry_t { -@@ -270,3 +269,8 @@ void wlserver_past_present_timing( struct wlr_surface *surface, uint32_t present - void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle ); - - void wlserver_force_shutdown(); -+ -+void wlserver_send_gamescope_control( wl_resource *control ); -+ -+bool wlsession_active(); -+ -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index 229a0884a..d77a1844b 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -1,6 +1,6 @@ - #pragma once - --#include "drm.hpp" -+#include "backend.h" - #include "waitable.h" - - #include -@@ -192,7 +192,6 @@ struct xwayland_ctx_t final : public gamescope::IWaitable - Atom gamescopeDebugHDRHeatmap_MSWCG; - Atom gamescopeHDROutputFeedback; - Atom gamescopeSDROnHDRContentBrightness; -- Atom gamescopeInternalDisplayBrightness; - Atom gamescopeHDRInputGain; - Atom gamescopeSDRInputGain; - Atom gamescopeHDRItmEnable; - -From 1c31291a5c0325df1ade35110f1b0f0387c0e291 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 10:02:37 +0000 -Subject: [PATCH 092/134] rendervulkan: Remove SDL includes - -No longer needed ---- - src/rendervulkan.cpp | 3 --- - 1 file changed, 3 deletions(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 51a5d5c7f..8afe07788 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -45,9 +45,6 @@ - - #include "reshade_effect_manager.hpp" - --#include "SDL.h" --#include "SDL_vulkan.h" -- - extern bool g_bWasPartialComposite; - - static constexpr mat3x4 g_rgb2yuv_srgb_to_bt601_limited = {{ - -From 5f6fab9f46e705635ad8d94cff99a9ad470b9c0f Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 10:14:20 +0000 -Subject: [PATCH 093/134] wlserver: Remove dependency on drm_include.h - ---- - src/drm.cpp | 7 +++++++ - src/wlserver.cpp | 24 +++++++++++------------- - src/wlserver.hpp | 3 +++ - 3 files changed, 21 insertions(+), 13 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 88b93b685..135a7efaa 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -1103,6 +1103,13 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh) - 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; -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 3a3a8a05e..d15dd7c19 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -40,7 +40,7 @@ - #include "presentation-time-protocol.h" - - #include "wlserver.hpp" --#include "drm_include.h" -+#include "hdmi.h" - #include "main.hpp" - #include "steamcompmgr.hpp" - #include "log.hpp" -@@ -1398,22 +1398,15 @@ static void kms_device_handle_change( struct wl_listener *listener, void *data ) - } - - int wlsession_open_kms( const char *device_name ) { -- struct wlr_device *device = nullptr; - if ( device_name != nullptr ) - { -- device = wlr_session_open_file( wlserver.wlr.session, device_name ); -- if ( device == nullptr ) -+ wlserver.wlr.device = wlr_session_open_file( wlserver.wlr.session, device_name ); -+ if ( wlserver.wlr.device == nullptr ) - return -1; -- if ( !drmIsKMS( device->fd ) ) -- { -- wl_log.errorf( "'%s' is not a KMS device", device_name ); -- wlr_session_close_file( wlserver.wlr.session, device ); -- return -1; -- } - } - else - { -- ssize_t n = wlr_session_find_gpus( wlserver.wlr.session, 1, &device ); -+ ssize_t n = wlr_session_find_gpus( wlserver.wlr.session, 1, &wlserver.wlr.device ); - if ( n < 0 ) - { - wl_log.errorf( "Failed to list GPUs" ); -@@ -1428,9 +1421,14 @@ int wlsession_open_kms( const char *device_name ) { - - struct wl_listener *listener = new wl_listener(); - listener->notify = kms_device_handle_change; -- wl_signal_add( &device->events.change, listener ); -+ wl_signal_add( &wlserver.wlr.device->events.change, listener ); - -- return device->fd; -+ return wlserver.wlr.device->fd; -+} -+ -+void wlsession_close_kms() -+{ -+ wlr_session_close_file( wlserver.wlr.session, wlserver.wlr.device ); - } - - gamescope_xwayland_server_t::gamescope_xwayland_server_t(wl_display *display) -diff --git a/src/wlserver.hpp b/src/wlserver.hpp -index c884a3ab9..6c41843ee 100644 ---- a/src/wlserver.hpp -+++ b/src/wlserver.hpp -@@ -110,6 +110,8 @@ struct wlserver_t { - // Used to simulate key events and set the keymap - struct wlr_keyboard *virtual_keyboard_device; - -+ struct wlr_device *device; -+ - std::vector> xwayland_servers; - } wlr; - -@@ -185,6 +187,7 @@ 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(); - - bool wlserver_init( void ); - - -From 5e6177f34c4a05f8a8047265bbc1cd678fd71cdf Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 11:52:14 +0000 -Subject: [PATCH 094/134] build: Make hwdata optional - ---- - meson.build | 12 ++++++++++-- - src/drm.cpp | 2 ++ - 2 files changed, 12 insertions(+), 2 deletions(-) - -diff --git a/meson.build b/meson.build -index 7af6884a0..04bd47208 100644 ---- a/meson.build -+++ b/meson.build -@@ -38,7 +38,7 @@ add_project_arguments(cppc.get_supported_arguments([ - - pipewire_dep = dependency('libpipewire-0.3', required: get_option('pipewire')) - librt_dep = cppc.find_library('rt', required : get_option('pipewire')) --hwdata_dep = dependency('hwdata') -+hwdata_dep = dependency('hwdata', required : false) - - dep_x11 = dependency('x11') - dep_wayland = dependency('wayland-client') -@@ -62,10 +62,18 @@ endif - add_project_arguments( - '-DHAVE_PIPEWIRE=@0@'.format(pipewire_dep.found().to_int()), - '-DHAVE_OPENVR=@0@'.format(openvr_dep.found().to_int()), -- '-DHWDATA_PNP_IDS="@0@"'.format(hwdata_dep.get_variable('pkgdatadir') / 'pnp.ids'), - language: 'cpp', - ) - -+if hwdata_dep.found() -+ 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.') -+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) -diff --git a/src/drm.cpp b/src/drm.cpp -index 135a7efaa..98883e426 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -1031,6 +1031,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - - void load_pnps(void) - { -+#ifdef HWDATA_PNP_IDS - const char *filename = HWDATA_PNP_IDS; - FILE *f = fopen(filename, "r"); - if (!f) { -@@ -1059,6 +1060,7 @@ void load_pnps(void) - - free(line); - fclose(f); -+#endif - } - - bool env_to_bool(const char *env) - -From 67984a89c5092c4164baef3c1d27fb5d5f1e9fb3 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 11:58:20 +0000 -Subject: [PATCH 095/134] build: Make drm backend optional - ---- - meson_options.txt | 1 + - src/main.cpp | 2 ++ - src/meson.build | 11 +++++++++-- - 3 files changed, 12 insertions(+), 2 deletions(-) - -diff --git a/meson_options.txt b/meson_options.txt -index 1d403fec4..c2c90b612 100644 ---- a/meson_options.txt -+++ b/meson_options.txt -@@ -1,4 +1,5 @@ - option('pipewire', type: 'feature', description: 'Screen capture via PipeWire') -+option('drm_backend', type: 'feature', description: 'DRM Atomic Backend') - 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') -diff --git a/src/main.cpp b/src/main.cpp -index 42110675d..a2665d112 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -778,9 +778,11 @@ int main(int argc, char **argv) - - switch ( eCurrentBackend ) - { -+#if HAVE_DRM - case gamescope::GamescopeBackend::DRM: - gamescope::IBackend::Set(); - break; -+#endif - case gamescope::GamescopeBackend::SDL: - gamescope::IBackend::Set(); - break; -diff --git a/src/meson.build b/src/meson.build -index a9e64990c..d23176781 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -10,7 +10,7 @@ dep_xres = dependency('xres') - dep_xmu = dependency('xmu') - dep_xi = dependency('xi') - --drm_dep = dependency('libdrm', version: '>= 2.4.113') -+drm_dep = dependency('libdrm', version: '>= 2.4.113', required: get_option('drm_backend')) - - wayland_server = dependency('wayland-server', version: '>=1.21') - wayland_protos = dependency('wayland-protocols', version: '>=1.17') -@@ -101,6 +101,13 @@ reshade_include = include_directories([ - '../thirdparty/SPIRV-Headers/include/spirv/unified1' - ]) - -+gamescope_cpp_args = [] -+if drm_dep.found() -+ src += 'drm.cpp' -+ gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) -+endif -+ -+ - src = [ - 'steamcompmgr.cpp', - 'color_helpers.cpp', -@@ -108,7 +115,6 @@ src = [ - 'edid.cpp', - 'headless.cpp', - 'wlserver.cpp', -- 'drm.cpp', - 'modegen.cpp', - 'sdlwindow.cpp', - 'vblankmanager.cpp', -@@ -143,6 +149,7 @@ endif - stb_dep, displayinfo_dep, openvr_dep, dep_xcursor, avif_dep, dep_xi - ], - install: true, -+ cpp_args: gamescope_cpp_args, - ) - - - -From e2828babbed0b58e462f06713687565d8b881170 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 12:05:04 +0000 -Subject: [PATCH 096/134] build: Make libavif and SDL2 optional - ---- - meson_options.txt | 2 ++ - src/main.cpp | 2 ++ - src/meson.build | 13 ++++++++++--- - src/steamcompmgr.cpp | 5 +++++ - 4 files changed, 19 insertions(+), 3 deletions(-) - -diff --git a/meson_options.txt b/meson_options.txt -index c2c90b612..1e7754e8e 100644 ---- a/meson_options.txt -+++ b/meson_options.txt -@@ -1,5 +1,7 @@ - option('pipewire', type: 'feature', description: 'Screen capture via PipeWire') - 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('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') -diff --git a/src/main.cpp b/src/main.cpp -index a2665d112..a37899df9 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -783,9 +783,11 @@ int main(int argc, char **argv) - gamescope::IBackend::Set(); - break; - #endif -+#if HAVE_SDL2 - case gamescope::GamescopeBackend::SDL: - gamescope::IBackend::Set(); - break; -+#endif - #if HAVE_OPENVR - case gamescope::GamescopeBackend::OpenVR: - gamescope::IBackend::Set(); -diff --git a/src/meson.build b/src/meson.build -index d23176781..4b2a5bcb1 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -19,9 +19,9 @@ thread_dep = dependency('threads') - cap_dep = dependency('libcap', required: false) - epoll_dep = dependency('epoll-shim', required: false) - glm_dep = dependency('glm') --sdl_dep = dependency('SDL2') -+sdl_dep = dependency('SDL2', required: get_option('sdl2_backend')) - stb_dep = dependency('stb') --avif_dep = dependency('libavif', version: '>=1.0.0') -+avif_dep = dependency('libavif', version: '>=1.0.0', required: get_option('avif_screenshots')) - - wlroots_dep = dependency( - 'wlroots', -@@ -107,6 +107,14 @@ if drm_dep.found() - gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) - endif - -+if sdl_dep.found() -+ src += 'sdlwindow.cpp' -+ gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) -+endif -+ -+if avif_dep.found() -+ gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) -+endif - - src = [ - 'steamcompmgr.cpp', -@@ -116,7 +124,6 @@ src = [ - 'headless.cpp', - 'wlserver.cpp', - 'modegen.cpp', -- 'sdlwindow.cpp', - 'vblankmanager.cpp', - 'rendervulkan.cpp', - 'log.cpp', -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index e3dae6154..a01e91c60 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -89,7 +89,9 @@ - #include "mwm_hints.h" - #include "edid.h" - -+#if HAVE_AVIF - #include "avif/avif.h" -+#endif - - static const int g_nBaseCursorScale = 36; - -@@ -2908,6 +2910,8 @@ paint_all(bool async) - } - } - -+ assert( HAVE_AVIF ); -+#if HAVE_AVIF - avifResult avifResult = AVIF_RESULT_OK; - - avifImage *pAvifImage = avifImageCreate( g_nOutputWidth, g_nOutputHeight, 10, AVIF_PIXEL_FORMAT_YUV444 ); -@@ -2976,6 +2980,7 @@ paint_all(bool async) - - xwm_log.infof( "Screenshot saved to %s", oScreenshotInfo->szScreenshotPath.c_str() ); - bScreenshotSuccess = true; -+#endif - } - else if (pScreenshotTexture->format() == VK_FORMAT_B8G8R8A8_UNORM) - { - -From fd1e9c6b958cb4497557ae644d3cb06ca0c07142 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 12:21:20 +0000 -Subject: [PATCH 097/134] build: Fix build - -Derp. ---- - src/meson.build | 30 +++++++++++++++--------------- - 1 file changed, 15 insertions(+), 15 deletions(-) - -diff --git a/src/meson.build b/src/meson.build -index 4b2a5bcb1..1df9c47fc 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -101,21 +101,6 @@ reshade_include = include_directories([ - '../thirdparty/SPIRV-Headers/include/spirv/unified1' - ]) - --gamescope_cpp_args = [] --if drm_dep.found() -- src += 'drm.cpp' -- gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) --endif -- --if sdl_dep.found() -- src += 'sdlwindow.cpp' -- gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) --endif -- --if avif_dep.found() -- gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) --endif -- - src = [ - 'steamcompmgr.cpp', - 'color_helpers.cpp', -@@ -133,6 +118,21 @@ src = [ - 'backend.cpp', - ] - -+gamescope_cpp_args = [] -+if drm_dep.found() -+ src += 'drm.cpp' -+ gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) -+endif -+ -+if sdl_dep.found() -+ src += 'sdlwindow.cpp' -+ gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) -+endif -+ -+if avif_dep.found() -+ gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) -+endif -+ - src += spirv_shaders - src += protocols_server_src - - -From f53f825e7e7322cf7e363429f5fc4661b345be97 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:00:04 +0000 -Subject: [PATCH 098/134] build: Don't require libinput backend if building - without DRM - ---- - src/meson.build | 16 +++++++++------- - 1 file changed, 9 insertions(+), 7 deletions(-) - -diff --git a/src/meson.build b/src/meson.build -index 1df9c47fc..6762640f2 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -37,13 +37,6 @@ displayinfo_dep = dependency( - default_options: ['default_library=static'], - ) - --required_wlroots_features = ['xwayland', 'libinput_backend'] --foreach feat : required_wlroots_features -- if wlroots_dep.get_variable('have_' + feat) != 'true' -- error('Cannot use wlroots built without ' + feat + ' support') -- endif --endforeach -- - glsl_compiler = find_program('glslang', 'glslangValidator', native: true) - - # Use --depfile to rebuild shaders when included files have changed. Sadly debian based -@@ -101,6 +94,8 @@ reshade_include = include_directories([ - '../thirdparty/SPIRV-Headers/include/spirv/unified1' - ]) - -+required_wlroots_features = ['xwayland'] -+ - src = [ - 'steamcompmgr.cpp', - 'color_helpers.cpp', -@@ -121,6 +116,7 @@ src = [ - gamescope_cpp_args = [] - if drm_dep.found() - src += 'drm.cpp' -+ required_wlroots_features += 'libinput_backend' - gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) - endif - -@@ -144,6 +140,12 @@ if openvr_dep.found() - src += 'vr_session.cpp' - endif - -+foreach feat : required_wlroots_features -+ if wlroots_dep.get_variable('have_' + feat) != 'true' -+ error('Cannot use wlroots built without ' + feat + ' support') -+ endif -+endforeach -+ - executable( - 'gamescope', - src, reshade_src, - -From da4f94c23cf005f309803a0a07e3bbe597ab94dc Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:00:39 +0000 -Subject: [PATCH 099/134] build: Don't require libliftoff if building without - DRM - ---- - src/meson.build | 15 ++++++++------- - 1 file changed, 8 insertions(+), 7 deletions(-) - -diff --git a/src/meson.build b/src/meson.build -index 6762640f2..0e47df375 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -71,13 +71,6 @@ shader_src = [ - - spirv_shaders = glsl_generator.process(shader_src) - --liftoff_dep = dependency( -- 'libliftoff', -- version: ['>= 0.4.0', '< 0.5.0'], -- fallback: ['libliftoff', 'liftoff'], -- default_options: ['default_library=static'], --) -- - reshade_src = [ - 'reshade/source/effect_codegen_spirv.cpp', - 'reshade/source/effect_expression.cpp', -@@ -118,6 +111,14 @@ if drm_dep.found() - src += 'drm.cpp' - required_wlroots_features += 'libinput_backend' - gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) -+ liftoff_dep = dependency( -+ 'libliftoff', -+ version: ['>= 0.4.0', '< 0.5.0'], -+ fallback: ['libliftoff', 'liftoff'], -+ default_options: ['default_library=static'], -+ ) -+else -+ liftoff_dep = dependency('', required: false) - endif - - if sdl_dep.found() - -From 9f9c666aadf12ea4e6174f5eadcffcff04161c18 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 17:55:02 +0000 -Subject: [PATCH 100/134] waitable: Add unistd.h include - -Gets closer building on some platforms ---- - src/waitable.h | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/waitable.h b/src/waitable.h -index fa7022fbe..f956a2c98 100644 ---- a/src/waitable.h -+++ b/src/waitable.h -@@ -3,6 +3,7 @@ - #include - #include - #include -+#include - #include - #include - - -From 350b0c3eb5798050d00d6ea95c37f982cfbcaba4 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 17:55:40 +0000 -Subject: [PATCH 101/134] wlserver: Get libinput stuff behind HAVE_DRM - ---- - src/wlserver.cpp | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index d15dd7c19..27ce45a70 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -17,7 +17,9 @@ - #include "wlr_begin.hpp" - #include - #include -+#if HAVE_DRM - #include -+#endif - #include - #include - #include -@@ -1618,12 +1620,14 @@ bool wlserver_init( void ) { - - if ( GetBackend()->IsSessionBased() ) - { -+#if HAVE_DRM - wlserver.wlr.libinput_backend = wlr_libinput_backend_create( wlserver.display, 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 - -From 2096b6882c1b2551a59114e07d5dc57e6e4f3a7c Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:01:12 +0000 -Subject: [PATCH 102/134] steamcompmgr: Remove drm_mode.h include - ---- - src/steamcompmgr.cpp | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index a01e91c60..304d0d91b 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -36,7 +36,6 @@ - #include - #include - #include --#include - #include - #include - #include - -From bb3e4d0c9bfc141aff8fc4b82e25467c5bf3bf4e Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:01:31 +0000 -Subject: [PATCH 103/134] build: Only build modegen.cpp with DRM backend - enabled - ---- - src/meson.build | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/meson.build b/src/meson.build -index 0e47df375..9f067640a 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -96,7 +96,6 @@ src = [ - 'edid.cpp', - 'headless.cpp', - 'wlserver.cpp', -- 'modegen.cpp', - 'vblankmanager.cpp', - 'rendervulkan.cpp', - 'log.cpp', -@@ -109,6 +108,7 @@ src = [ - gamescope_cpp_args = [] - if drm_dep.found() - src += 'drm.cpp' -+ src += 'modegen.cpp' - required_wlroots_features += 'libinput_backend' - gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) - liftoff_dep = dependency( - -From fbb8dd20e0c7de06eac3a463375928b175b4b354 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:02:36 +0000 -Subject: [PATCH 104/134] rendervulkan: Only include drm_fourcc.h, not all of - drm_include.h - ---- - src/rendervulkan.cpp | 17 +++++++++++++++-- - 1 file changed, 15 insertions(+), 2 deletions(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 8afe07788..175e9abc3 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -21,7 +21,13 @@ - // 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 -+#if HAVE_DRM - #include "drm_include.h" -+#endif -+#include "wlr_begin.hpp" -+#include -+#include "wlr_end.hpp" - - #include "rendervulkan.hpp" - #include "main.hpp" -@@ -416,9 +422,11 @@ bool CVulkanDevice::createDevice() - if ( !GetBackend()->ValidPhysicalDevice( physDev() ) ) - return false; - -+#if HAVE_DRM - // XXX(JoshA): Move this to ValidPhysicalDevice. - // We need to refactor some Vulkan stuff to do that though. -- if ( hasDrmProps ) { -+ if ( hasDrmProps ) -+ { - VkPhysicalDeviceDrmPropertiesEXT drmProps = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT, - }; -@@ -457,7 +465,10 @@ bool CVulkanDevice::createDevice() - m_bHasDrmPrimaryDevId = true; - m_drmPrimaryDevId = makedev( drmProps.primaryMajor, drmProps.primaryMinor ); - } -- } else { -+ } -+ else -+#endif -+ { - vk_log.errorf( "physical device doesn't support VK_EXT_physical_device_drm" ); - return false; - } -@@ -2640,9 +2651,11 @@ bool vulkan_init_formats() - 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); -+#endif - } - - return true; - -From ab16b8ce6b2fd3b2323c8db8ba35218c4ea8b56f Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:23:22 +0000 -Subject: [PATCH 105/134] build: Add HAVE_LIBCAP definition - ---- - meson_options.txt | 1 + - src/meson.build | 6 +++++- - 2 files changed, 6 insertions(+), 1 deletion(-) - -diff --git a/meson_options.txt b/meson_options.txt -index 1e7754e8e..3160b58c5 100644 ---- a/meson_options.txt -+++ b/meson_options.txt -@@ -1,4 +1,5 @@ - 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') -diff --git a/src/meson.build b/src/meson.build -index 9f067640a..2afcd3aef 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -16,7 +16,7 @@ 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: false) -+cap_dep = dependency('libcap', required: get_option('rt_cap')) - epoll_dep = dependency('epoll-shim', required: false) - glm_dep = dependency('glm') - sdl_dep = dependency('SDL2', required: get_option('sdl2_backend')) -@@ -130,6 +130,10 @@ if avif_dep.found() - gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) - endif - -+if cap_dep.found() -+ gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(avif_dep.found().to_int()) -+endif -+ - src += spirv_shaders - src += protocols_server_src - - -From 8b16bdbcc630a4ad577e4bb4d707e08fec57bcfc Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:23:51 +0000 -Subject: [PATCH 106/134] build: Simplify HAVE_* definition setting - ---- - src/meson.build | 13 ++++--------- - 1 file changed, 4 insertions(+), 9 deletions(-) - -diff --git a/src/meson.build b/src/meson.build -index 2afcd3aef..559054ddb 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -110,7 +110,6 @@ if drm_dep.found() - src += 'drm.cpp' - src += 'modegen.cpp' - required_wlroots_features += 'libinput_backend' -- gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) - liftoff_dep = dependency( - 'libliftoff', - version: ['>= 0.4.0', '< 0.5.0'], -@@ -123,16 +122,12 @@ endif - - if sdl_dep.found() - src += 'sdlwindow.cpp' -- gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) - endif - --if avif_dep.found() -- gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) --endif -- --if cap_dep.found() -- gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(avif_dep.found().to_int()) --endif -+gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) -+gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) -+gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) -+gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(avif_dep.found().to_int()) - - src += spirv_shaders - src += protocols_server_src - -From 085f95fa30af544aebc2312f88c04fb4912fee32 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:24:17 +0000 -Subject: [PATCH 107/134] main: Check for libcap in the cap code - ---- - src/main.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/main.cpp b/src/main.cpp -index a37899df9..bbc3649de 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -676,7 +676,7 @@ int main(int argc, char **argv) - } - } - --#if defined(__linux__) -+#if defined(__linux__) && HAVE_LIBCAP - cap_t caps = cap_get_proc(); - if ( caps != nullptr ) - { - -From 4ea2f847f06560f3bb947ef3d4975247c356dd41 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:26:23 +0000 -Subject: [PATCH 108/134] wlserver: Ifdef some session stuff behind HAVE_DRM - ---- - src/wlserver.cpp | 10 ++++++++++ - src/wlserver.hpp | 4 ++++ - 2 files changed, 14 insertions(+) - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 27ce45a70..b1a6ff41d 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -229,11 +229,13 @@ static void wlserver_handle_key(struct wl_listener *listener, void *data) - xkb_keycode_t keycode = event->keycode + 8; - xkb_keysym_t keysym = xkb_state_key_get_one_sym(keyboard->wlr->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; - } -+#endif - - bool forbidden_key = - keysym == XKB_KEY_XF86AudioLowerVolume || -@@ -1286,6 +1288,7 @@ void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle - - /////////////////////// - -+#if HAVE_SESSION - bool wlsession_active() - { - return wlserver.wlr.session->active; -@@ -1297,6 +1300,7 @@ static void handle_session_active( struct wl_listener *listener, void *data ) - GetBackend()->DirtyState( true, true ); - 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) - { -@@ -1375,6 +1379,7 @@ bool wlsession_init( void ) { - }; - wlserver_set_output_info( &output_info ); - -+#if HAVE_SESSION - if ( !GetBackend()->IsSessionBased() ) - return true; - -@@ -1387,10 +1392,13 @@ bool wlsession_init( void ) { - - wlserver.session_active.notify = handle_session_active; - wl_signal_add( &wlserver.wlr.session->events.active, &wlserver.session_active ); -+#endif - - return true; - } - -+#if HAVE_SESSION -+ - static void kms_device_handle_change( struct wl_listener *listener, void *data ) - { - GetBackend()->DirtyState(); -@@ -1433,6 +1441,8 @@ 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) - { - struct wlr_xwayland_server_options xwayland_options = { -diff --git a/src/wlserver.hpp b/src/wlserver.hpp -index 6c41843ee..c148847fb 100644 ---- a/src/wlserver.hpp -+++ b/src/wlserver.hpp -@@ -15,6 +15,10 @@ - - #include "steamcompmgr_shared.hpp" - -+#if HAVE_DRM -+#define HAVE_SESSION 1 -+#endif -+ - #define WLSERVER_BUTTON_COUNT 7 - - struct _XDisplay; - -From ec6470e835f809b12fe4421c44443bbbfc042d0b Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:59:41 +0000 -Subject: [PATCH 109/134] rendervulkan: Add some hdr_metadata_infoframe stubs - for when we are not using DRM - ---- - src/hdmi.h | 93 ++++++++++++++++++++++++++++++++++++++++++++ - src/rendervulkan.cpp | 1 + - 2 files changed, 94 insertions(+) - -diff --git a/src/hdmi.h b/src/hdmi.h -index 57d5257e2..92d149519 100644 ---- a/src/hdmi.h -+++ b/src/hdmi.h -@@ -1,7 +1,100 @@ - #pragma once - -+#include -+ - /* from CTA-861-G */ - #define HDMI_EOTF_SDR 0 - #define HDMI_EOTF_TRADITIONAL_HDR 1 - #define HDMI_EOTF_ST2084 2 - #define HDMI_EOTF_HLG 3 -+ -+#if HAVE_DRM -+#include -+#else -+/** -+ * struct hdr_metadata_infoframe - HDR Metadata Infoframe Data. -+ * -+ * HDR Metadata Infoframe as per CTA 861.G spec. This is expected -+ * to match exactly with the spec. -+ * -+ * 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_output_metadata - HDR output metadata -+ * -+ * 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; -+ }; -+}; -+ -+#endif -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 175e9abc3..7cb3c1df3 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -22,6 +22,7 @@ - #include "shaders/NVIDIAImageScaling/NIS/NIS_Config.h" - - #include -+#include "hdmi.h" - #if HAVE_DRM - #include "drm_include.h" - #endif - -From 55e80c580f7ed17e9e3708162587ba62eb1c9862 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 18:59:52 +0000 -Subject: [PATCH 110/134] drm: Move env_to_bool out of here. - ---- - src/drm.cpp | 8 +------- - src/steamcompmgr.cpp | 9 +++++++++ - 2 files changed, 10 insertions(+), 7 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 98883e426..95a0853ee 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -1063,13 +1063,7 @@ void load_pnps(void) - #endif - } - --bool env_to_bool(const char *env) --{ -- if (!env || !*env) -- return false; -- -- return !!atoi(env); --} -+extern bool env_to_bool(const char *env); - - bool init_drm(struct drm_t *drm, int width, int height, int refresh) - { -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 304d0d91b..370b45f7f 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -87,6 +87,7 @@ - #include "win32_styles.h" - #include "mwm_hints.h" - #include "edid.h" -+#include "hdmi.h" - - #if HAVE_AVIF - #include "avif/avif.h" -@@ -149,6 +150,14 @@ uint32_t g_reshade_technique_idx = 0; - bool g_bSteamIsActiveWindow = false; - bool g_bForceInternal = false; - -+bool env_to_bool(const char *env) -+{ -+ if (!env || !*env) -+ return false; -+ -+ return !!atoi(env); -+} -+ - uint64_t timespec_to_nanos(struct timespec& spec) - { - return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec; - -From 434074a2484d40308446be2ce2f4bdd8869dd4a5 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 19:02:00 +0000 -Subject: [PATCH 111/134] edid: Avoid using g_bRotated - ---- - src/drm.cpp | 6 +++--- - src/edid.cpp | 10 ++++------ - src/edid.h | 4 ++-- - 3 files changed, 9 insertions(+), 11 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 95a0853ee..5ff3cafcc 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -1022,7 +1022,7 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - wlserver_unlock(); - - if (!initial) -- WritePatchedEdid( best->GetRawEDID(), best->GetHDRInfo() ); -+ WritePatchedEdid( best->GetRawEDID(), best->GetHDRInfo(), g_bRotated ); - - update_connector_display_info_wl( drm ); - -@@ -3005,7 +3005,7 @@ namespace gamescope - virtual bool PostInit() override - { - if ( g_DRM.pConnector ) -- WritePatchedEdid( g_DRM.pConnector->GetRawEDID(), g_DRM.pConnector->GetHDRInfo() ); -+ WritePatchedEdid( g_DRM.pConnector->GetRawEDID(), g_DRM.pConnector->GetHDRInfo(), g_bRotated ); - return true; - } - -@@ -3429,7 +3429,7 @@ namespace gamescope - if ( !GetCurrentConnector() ) - return; - -- WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo() ); -+ WritePatchedEdid( GetCurrentConnector()->GetRawEDID(), GetCurrentConnector()->GetHDRInfo(), g_bRotated ); - } - - protected: -diff --git a/src/edid.cpp b/src/edid.cpp -index 3f499fc98..eb855096a 100644 ---- a/src/edid.cpp -+++ b/src/edid.cpp -@@ -15,8 +15,6 @@ extern "C" - #include "libdisplay-info/cta.h" - } - --extern bool g_bRotated; -- - static LogScope edid_log("josh edid"); - - namespace gamescope -@@ -89,7 +87,7 @@ namespace gamescope - return ceilf((logf(nits / 50.0f) / logf(2.0f)) * 32.0f); - } - -- std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) -+ std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ) - { - // A zero length indicates that the edid parsing failed. - if ( pEdid.empty() ) -@@ -97,7 +95,7 @@ namespace gamescope - - std::vector edid( pEdid.begin(), pEdid.end() ); - -- if ( g_bRotated ) -+ if ( bRotate ) - { - // Patch width, height. - edid_log.infof("Patching dims %ux%u -> %ux%u", edid[0x15], edid[0x16], edid[0x16], edid[0x15]); -@@ -263,7 +261,7 @@ namespace gamescope - return pszPatchedEdidPath; - } - -- void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ) -+ void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ) - { - const char *pszPatchedEdidPath = GetPatchedEdidPath(); - if ( !pszPatchedEdidPath ) -@@ -271,7 +269,7 @@ namespace gamescope - - std::span pEdidToWrite = pEdid; - -- auto oPatchedEdid = PatchEdid( pEdid, hdrInfo ); -+ auto oPatchedEdid = PatchEdid( pEdid, hdrInfo, bRotate ); - if ( oPatchedEdid ) - pEdidToWrite = std::span{ oPatchedEdid->begin(), oPatchedEdid->end() }; - -diff --git a/src/edid.h b/src/edid.h -index e6ab74688..2f821548d 100644 ---- a/src/edid.h -+++ b/src/edid.h -@@ -10,7 +10,7 @@ namespace gamescope - struct BackendConnectorHDRInfo; - - const char *GetPatchedEdidPath(); -- void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); -+ void WritePatchedEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ); - -- std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo ); -+ std::optional> PatchEdid( std::span pEdid, const BackendConnectorHDRInfo &hdrInfo, bool bRotate ); - } -\ No newline at end of file - -From 332f348a1f595d92528a86f9c8feaed45217b82d Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 14:03:54 +0000 -Subject: [PATCH 112/134] steamcompmgr: Fix GetNativeColorimetry when HDR is - enabled but not available - ---- - src/steamcompmgr.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 370b45f7f..cd5877e77 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -347,7 +347,7 @@ update_color_mgmt() - return; - - GetBackend()->GetCurrentConnector()->GetNativeColorimetry( -- g_bHDREnabled, -+ g_bOutputHDREnabled, - &g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF, - &g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF ); - - -From d70530cb94bbb1be957f04aad3bd400f53f1a90c Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 14:10:25 +0000 -Subject: [PATCH 113/134] convar: Add small convar system - -Co-authored-by: Alpyne ---- - src/convar.cpp | 10 ++++ - src/convar.h | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ - src/meson.build | 1 + - 3 files changed, 156 insertions(+) - create mode 100644 src/convar.cpp - create mode 100644 src/convar.h - -diff --git a/src/convar.cpp b/src/convar.cpp -new file mode 100644 -index 000000000..22a4b68ff ---- /dev/null -+++ b/src/convar.cpp -@@ -0,0 +1,10 @@ -+#include "convar.h" -+ -+namespace gamescope -+{ -+ Dict& ConCommand::GetCommands() -+ { -+ static Dict s_Commands; -+ return s_Commands; -+ } -+} -\ No newline at end of file -diff --git a/src/convar.h b/src/convar.h -new file mode 100644 -index 000000000..4e2372cf1 ---- /dev/null -+++ b/src/convar.h -@@ -0,0 +1,145 @@ -+#pragma once -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+namespace gamescope -+{ -+ class ConCommand; -+ -+ 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; -+ else -+ return std::nullopt; -+ } -+ -+ template <> -+ inline std::optional Parse( std::string_view chars ) -+ { -+ std::optional oNumber = Parse( chars ); -+ if ( oNumber ) -+ return !!*oNumber; -+ -+ if ( chars == "true" ) -+ return true; -+ else -+ return false; -+ } -+ -+ 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 ); } -+ }; -+ -+ template -+ using Dict = std::unordered_map>; -+ -+ class ConCommand -+ { -+ using ConCommandFunc = std::function )>; -+ -+ public: -+ ConCommand( std::string_view pszName, std::string_view pszDescription, ConCommandFunc func ) -+ : m_pszName{ pszName } -+ , m_pszDescription{ pszDescription } -+ , m_Func{ func } -+ { -+ assert( !GetCommands().contains( pszName ) ); -+ GetCommands()[ std::string( pszName ) ] = this; -+ } -+ -+ ~ConCommand() -+ { -+ GetCommands().erase( GetCommands().find( m_pszName ) ); -+ } -+ -+ void Invoke( std::span args ) -+ { -+ if ( m_Func ) -+ m_Func( args ); -+ } -+ -+ static Dict& GetCommands(); -+ protected: -+ std::string_view m_pszName; -+ std::string_view m_pszDescription; -+ ConCommandFunc m_Func; -+ }; -+ -+ template -+ class ConVar : public ConCommand -+ { -+ using ConVarCallbackFunc = std::function; -+ public: -+ ConVar( std::string_view pszName, T defaultValue = T{}, std::string_view pszDescription = "", ConVarCallbackFunc func = nullptr ) -+ : ConCommand( pszName, pszDescription, [this]( std::span pArgs ){ this->InvokeFunc( pArgs ); } ) -+ , m_Value{ defaultValue } -+ , m_Callback{ func } -+ { -+ } -+ -+ const T& Get() const -+ { -+ return m_Value; -+ } -+ -+ template -+ void SetValue( const J &newValue ) -+ { -+ m_Value = T{ newValue }; -+ -+ if ( !m_bInCallback && m_Callback ) -+ { -+ m_bInCallback = true; -+ m_Callback(); -+ m_bInCallback = false; -+ } -+ } -+ -+ template -+ ConVar& operator =( const J &newValue ) { SetValue( newValue ); return *this; } -+ -+ operator T() 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 bool operator <=>( const J &other ) const { return m_Value <=> other; } -+ -+ void InvokeFunc( std::span pArgs ) -+ { -+ if ( pArgs.size() != 2 ) -+ return; -+ -+ if constexpr ( std::is_integral::value ) -+ { -+ std::optional oResult = Parse( pArgs[1] ); -+ SetValue( oResult ? *oResult : T{} ); -+ } -+ else -+ { -+ SetValue( pArgs[1] ); -+ } -+ } -+ private: -+ T m_Value{}; -+ ConVarCallbackFunc m_Callback; -+ bool m_bInCallback; -+ }; -+} -diff --git a/src/meson.build b/src/meson.build -index 559054ddb..9e2ee20e8 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -91,6 +91,7 @@ required_wlroots_features = ['xwayland'] - - src = [ - 'steamcompmgr.cpp', -+ 'convar.cpp', - 'color_helpers.cpp', - 'main.cpp', - 'edid.cpp', - -From b14d851137f00a1dade22279ed6aeaed72e21337 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 5 Feb 2024 14:10:38 +0000 -Subject: [PATCH 114/134] steamcompmgr: Use convar for hdr_enabled - -Just an example for now ---- - src/steamcompmgr.cpp | 9 +++++---- - 1 file changed, 5 insertions(+), 4 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index cd5877e77..80094e997 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -88,6 +88,7 @@ - #include "mwm_hints.h" - #include "edid.h" - #include "hdmi.h" -+#include "convar.h" - - #if HAVE_AVIF - #include "avif/avif.h" -@@ -336,7 +337,7 @@ bool g_bVRRCapable_CachedValue = false; - bool g_bVRRInUse_CachedValue = false; - bool g_bSupportsHDR_CachedValue = false; - bool g_bForceHDR10OutputDebug = false; --bool g_bHDREnabled = 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; - -@@ -5657,7 +5658,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - } - if ( ev->atom == ctx->atoms.gamescopeDisplayHDREnabled ) - { -- g_bHDREnabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayHDREnabled, 0 ); -+ cv_hdr_enabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeDisplayHDREnabled, 0 ); - hasRepaint = true; - } - if ( ev->atom == ctx->atoms.gamescopeDebugForceHDR10Output ) -@@ -7466,7 +7467,7 @@ steamcompmgr_main(int argc, char **argv) - } else if (strcmp(opt_name, "force-windows-fullscreen") == 0) { - bForceWindowsFullscreen = true; - } else if (strcmp(opt_name, "hdr-enabled") == 0) { -- g_bHDREnabled = true; -+ 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) { -@@ -7638,7 +7639,7 @@ steamcompmgr_main(int argc, char **argv) - update_mode_atoms(root_ctx, &flush_root); - } - -- g_bOutputHDREnabled = (g_bSupportsHDR_CachedValue || g_bForceHDR10OutputDebug) && g_bHDREnabled; -+ 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 - -From 5618b6ce04f56c3065510424893e462d05799ee4 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 6 Feb 2024 08:41:04 +0000 -Subject: [PATCH 115/134] rendervulkan: Use getprocaddr for vkCreateImageView - ---- - src/backend.h | 2 +- - src/rendervulkan.cpp | 6 +++--- - src/vulkan_include.h | 4 ++++ - src/wlserver.hpp | 3 ++- - 4 files changed, 10 insertions(+), 5 deletions(-) - create mode 100644 src/vulkan_include.h - -diff --git a/src/backend.h b/src/backend.h -index 37e93345b..f19456a75 100644 ---- a/src/backend.h -+++ b/src/backend.h -@@ -2,8 +2,8 @@ - - #include "color_helpers.h" - #include "gamescope_shared.h" -+#include "vulkan_include.h" - --#include "vulkan/vulkan_core.h" - #include - #include - #include -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 7cb3c1df3..d476d443b 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -10,7 +10,7 @@ - #include - #include - #include --#include -+#include "vulkan_include.h" - - #if defined(__linux__) - #include -@@ -2315,7 +2315,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - createInfo.format = VK_FORMAT_R8_UNORM; - - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT; -- res = vkCreateImageView(g_device.device(), &createInfo, nullptr, &m_lumaView); -+ res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_lumaView); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkCreateImageView failed" ); - return false; -@@ -2324,7 +2324,7 @@ bool CVulkanTexture::BInit( uint32_t width, uint32_t height, uint32_t depth, uin - createInfo.pNext = NULL; - createInfo.format = VK_FORMAT_R8G8_UNORM; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; -- res = vkCreateImageView(g_device.device(), &createInfo, nullptr, &m_chromaView); -+ res = g_device.vk.CreateImageView(g_device.device(), &createInfo, nullptr, &m_chromaView); - if ( res != VK_SUCCESS ) { - vk_errorf( res, "vkCreateImageView failed" ); - return false; -diff --git a/src/vulkan_include.h b/src/vulkan_include.h -new file mode 100644 -index 000000000..1f78e5232 ---- /dev/null -+++ b/src/vulkan_include.h -@@ -0,0 +1,4 @@ -+#pragma once -+ -+#define VK_NO_PROTOTYPES -+#include -diff --git a/src/wlserver.hpp b/src/wlserver.hpp -index c148847fb..f0b8e1e1c 100644 ---- a/src/wlserver.hpp -+++ b/src/wlserver.hpp -@@ -11,7 +11,8 @@ - #include - #include - #include --#include -+ -+#include "vulkan_include.h" - - #include "steamcompmgr_shared.hpp" - - -From 8dcc1f363dfd1601778a9042b339f972c8fe6f98 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 6 Feb 2024 08:46:06 +0000 -Subject: [PATCH 116/134] rendervulkan: dlopen Vulkan ourselves - ---- - src/rendervulkan.cpp | 43 ++++++++++++++++++++++++++++++++----------- - 1 file changed, 32 insertions(+), 11 deletions(-) - -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index d476d443b..09a92d7c0 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -10,6 +10,7 @@ - #include - #include - #include -+#include - #include "vulkan_include.h" - - #if defined(__linux__) -@@ -88,18 +89,32 @@ static const mat3x4& colorspace_to_conversion_from_srgb_matrix(EStreamColorspace - } - } - --extern "C" -+PFN_vkGetInstanceProcAddr g_pfn_vkGetInstanceProcAddr; -+PFN_vkCreateInstance g_pfn_vkCreateInstance; -+ -+static VkResult vulkan_load_module() - { --VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance( -- const VkInstanceCreateInfo* pCreateInfo, -- const VkAllocationCallbacks* pAllocator, -- VkInstance* pInstance); -+ 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; - --VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr( -- VkInstance instance, -- const char* pName); --} -+ 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; - -@@ -267,7 +282,7 @@ bool CVulkanDevice::BInit(VkInstance instance, VkSurfaceKHR surface) - g_output.surface = surface; - - m_instance = instance; -- #define VK_FUNC(x) vk.x = (PFN_vk##x) vkGetInstanceProcAddr(instance, "vk"#x); -+ #define VK_FUNC(x) vk.x = (PFN_vk##x) g_pfn_vkGetInstanceProcAddr(instance, "vk"#x); - VULKAN_INSTANCE_FUNCTIONS - #undef VK_FUNC - -@@ -3232,6 +3247,12 @@ VkInstance vulkan_get_instance( void ) - { - 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 = { -@@ -3251,7 +3272,7 @@ VkInstance vulkan_get_instance( void ) - }; - - VkInstance instance = nullptr; -- result = vkCreateInstance(&createInfo, 0, &instance); -+ result = g_pfn_vkCreateInstance(&createInfo, 0, &instance); - if ( result != VK_SUCCESS ) - { - vk_errorf( result, "vkCreateInstance failed" ); - -From 532907af938f8a6bd8062ab71144186a610051b3 Mon Sep 17 00:00:00 2001 -From: Andres Rodriguez -Date: Thu, 8 Feb 2024 18:03:24 -0500 -Subject: [PATCH 117/134] wlserver: always broadcast a refresh rate in - gamescope_control - -If we know that the display only supports one refresh rate (the current -refresh rate), send that as part of active display info. - -This is useful so clients can determine valid framelimit values. ---- - src/wlserver.cpp | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index b1a6ff41d..0f972edf9 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -927,6 +927,11 @@ void wlserver_send_gamescope_control( wl_resource *control ) - uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, size ); - memcpy( ptr, pConn->GetValidDynamicRefreshRates().data(), size ); - } -+ else if ( g_nOutputRefresh > 0 ) -+ { -+ uint32_t *ptr = (uint32_t *)wl_array_add( &display_rates, sizeof(uint32_t) ); -+ *ptr = (uint32_t)g_nOutputRefresh; -+ } - gamescope_control_send_active_display_info( control, pConn->GetName(), pConn->GetMake(), pConn->GetModel(), flags, &display_rates ); - wl_array_release(&display_rates); - } - -From 7664011cbc1d65eeb494dd05aade914923a3f0c7 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Sun, 11 Feb 2024 21:38:58 +0000 -Subject: [PATCH 118/134] headless: Fix init - ---- - src/headless.cpp | 34 ++++++++++++++++++++++++++++++++++ - 1 file changed, 34 insertions(+) - -diff --git a/src/headless.cpp b/src/headless.cpp -index 493aea13e..5e6c7c04e 100644 ---- a/src/headless.cpp -+++ b/src/headless.cpp -@@ -1,4 +1,9 @@ - #include "backend.h" -+#include "rendervulkan.hpp" -+#include "wlserver.hpp" -+ -+extern int g_nPreferredOutputWidth; -+extern int g_nPreferredOutputHeight; - - namespace gamescope - { -@@ -92,6 +97,35 @@ namespace gamescope - - 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 = 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; - } - - -From 2428f274b8873f6454ad069a1068ea29d42fce2e Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Tue, 13 Feb 2024 01:53:45 +0000 -Subject: [PATCH 119/134] mangoapp: Flip app_frametime + visible_frametime - -We decided that we didn't want users to think things regressed by having the graph be more barcode-y. Flip the graphs around so visible frametimes are on the bottom on Level 4 instead. - -Gratuitiously ignoring the WARNING in mangoapp.cpp :-) ---- - src/mangoapp.cpp | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/mangoapp.cpp b/src/mangoapp.cpp -index 2d0c59f72..6726e0581 100644 ---- a/src/mangoapp.cpp -+++ b/src/mangoapp.cpp -@@ -18,10 +18,10 @@ struct mangoapp_msg_v1 { - struct mangoapp_msg_header hdr; - - uint32_t pid; -- uint64_t visible_frametime_ns; -+ uint64_t app_frametime_ns; - uint8_t fsrUpscale; - uint8_t fsrSharpness; -- uint64_t app_frametime_ns; -+ uint64_t visible_frametime_ns; - uint64_t latency_ns; - uint32_t outputWidth; - uint32_t outputHeight; - -From 9113c844726b7443561d31b2980da000b89f27fd Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 14 Feb 2024 20:03:04 +0000 -Subject: [PATCH 120/134] protocol: Add MURA_CORRECTION feature - ---- - protocol/gamescope-control.xml | 1 + - src/wlserver.cpp | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/protocol/gamescope-control.xml b/protocol/gamescope-control.xml -index 4359eb148..012c48c2b 100644 ---- a/protocol/gamescope-control.xml -+++ b/protocol/gamescope-control.xml -@@ -41,6 +41,7 @@ - - - -+ - - - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 0f972edf9..4ee931b15 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -949,6 +949,7 @@ static void gamescope_control_bind( struct wl_client *client, void *data, uint32 - 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_MURA_CORRECTION, 1, 0 ); - gamescope_control_send_feature_support( resource, GAMESCOPE_CONTROL_FEATURE_DONE, 0, 0 ); - - wlserver_send_gamescope_control( resource ); - -From 7d2a46c192573d1dc878cbbdc11b00d3051f8f50 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 14 Feb 2024 21:47:17 +0000 -Subject: [PATCH 121/134] vr_session: Setup vulkan + session stuff properly - ---- - src/vr_session.cpp | 29 +++++++++++++++++++++++++++++ - 1 file changed, 29 insertions(+) - -diff --git a/src/vr_session.cpp b/src/vr_session.cpp -index 9bf8085cd..654789aa8 100644 ---- a/src/vr_session.cpp -+++ b/src/vr_session.cpp -@@ -234,6 +234,35 @@ namespace gamescope - - 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; -+ -+ if ( !vulkan_init( vulkan_get_instance(), VK_NULL_HANDLE ) ) -+ { -+ return false; -+ } -+ -+ if ( !wlsession_init() ) -+ { -+ fprintf( stderr, "Failed to initialize Wayland session\n" ); -+ return false; -+ } -+ -+ // - vr::EVRInitError error = vr::VRInitError_None; - VR_Init(&error, vr::VRApplication_Background); - - -From 1bea9196edd0d0542272846f78f78ece63023056 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 14 Feb 2024 21:48:14 +0000 -Subject: [PATCH 122/134] build: Fix libcap define - ---- - src/meson.build | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/meson.build b/src/meson.build -index 9e2ee20e8..7f7e79305 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -128,7 +128,7 @@ endif - gamescope_cpp_args += '-DHAVE_DRM=@0@'.format(drm_dep.found().to_int()) - gamescope_cpp_args += '-DHAVE_SDL2=@0@'.format(sdl_dep.found().to_int()) - gamescope_cpp_args += '-DHAVE_AVIF=@0@'.format(avif_dep.found().to_int()) --gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(avif_dep.found().to_int()) -+gamescope_cpp_args += '-DHAVE_LIBCAP=@0@'.format(cap_dep.found().to_int()) - - src += spirv_shaders - src += protocols_server_src - -From 677dabfee377f29c0a6c2ea3ae2498da8d78cc4a Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 14 Feb 2024 22:00:35 +0000 -Subject: [PATCH 123/134] shaders: Don't require int8 stuff - ---- - src/main.hpp | 2 +- - src/rendervulkan.cpp | 22 ++++++++++---------- - src/shaders/blit_push_data.h | 4 ++-- - src/shaders/composite.h | 6 ++++-- - src/shaders/cs_composite_blit.comp | 1 - - src/shaders/cs_composite_blur.comp | 1 - - src/shaders/cs_composite_blur_cond.comp | 1 - - src/shaders/cs_composite_rcas.comp | 4 +--- - src/shaders/cs_easu.comp | 1 - - src/shaders/cs_easu_fp16.comp | 1 - - src/shaders/cs_gaussian_blur_horizontal.comp | 4 +--- - src/shaders/cs_nis.comp | 1 - - src/shaders/cs_nis_fp16.comp | 1 - - src/shaders/cs_rgb_to_nv12.comp | 3 +-- - src/shaders/shaderfilter.h | 10 +++++++++ - 15 files changed, 31 insertions(+), 31 deletions(-) - create mode 100644 src/shaders/shaderfilter.h - -diff --git a/src/main.hpp b/src/main.hpp -index a87030b0f..71e2c1227 100644 ---- a/src/main.hpp -+++ b/src/main.hpp -@@ -37,7 +37,7 @@ enum class GamescopeUpscaleFilter : uint32_t - NIS, - PIXEL, - -- FROM_VIEW = 255, // internal -+ FROM_VIEW = 0xF, // internal - }; - - enum class GamescopeUpscaleScaler : uint32_t -diff --git a/src/rendervulkan.cpp b/src/rendervulkan.cpp -index 09a92d7c0..036d5e2a1 100644 ---- a/src/rendervulkan.cpp -+++ b/src/rendervulkan.cpp -@@ -3401,8 +3401,7 @@ struct BlitPushData_t - uint32_t frameId; - uint32_t blurRadius; - -- uint8_t u_shaderFilter[k_nMaxLayers]; -- uint8_t u_padding[2]; -+ uint32_t u_shaderFilter; - - float u_linearToNits; // unset - float u_nitsToLinear; // unset -@@ -3411,15 +3410,17 @@ struct BlitPushData_t - - explicit BlitPushData_t(const struct FrameInfo_t *frameInfo) - { -+ u_shaderFilter = 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[i] = (uint32_t)GamescopeUpscaleFilter::FROM_VIEW; -+ u_shaderFilter |= ((uint32_t)GamescopeUpscaleFilter::FROM_VIEW) << (i * 4); - else -- u_shaderFilter[i] = (uint32_t)layer->filter; -+ u_shaderFilter |= ((uint32_t)layer->filter) << (i * 4); - - if (layer->ctm) - { -@@ -3450,7 +3451,7 @@ struct BlitPushData_t - scale[0] = { blit_scale, blit_scale }; - offset[0] = { 0.5f, 0.5f }; - opacity[0] = 1.0f; -- u_shaderFilter[0] = (uint32_t)GamescopeUpscaleFilter::LINEAR; -+ u_shaderFilter = (uint32_t)GamescopeUpscaleFilter::LINEAR; - ctm[0] = glm::mat3x4 - { - 1, 0, 0, 0, -@@ -3529,8 +3530,7 @@ struct RcasPushData_t - uint32_t u_frameId; - uint32_t u_c1; - -- uint8_t u_shaderFilter[k_nMaxLayers]; -- uint8_t u_padding[2]; -+ uint32_t u_shaderFilter; - - float u_linearToNits; // unset - float u_nitsToLinear; // unset -@@ -3546,15 +3546,16 @@ struct RcasPushData_t - u_borderMask = frameInfo->borderMask() >> 1u; - u_frameId = s_frameId++; - u_c1 = tmp.x; -+ u_shaderFilter = 0; - - for (int i = 0; i < frameInfo->layerCount; i++) - { - const FrameInfo_t::Layer_t *layer = &frameInfo->layers[i]; - -- if (layer->isScreenSize() || (layer->filter == GamescopeUpscaleFilter::LINEAR && layer->viewConvertsToLinearAutomatically())) -- u_shaderFilter[i] = (uint32_t)GamescopeUpscaleFilter::FROM_VIEW; -+ if (i == 0 || layer->isScreenSize() || (layer->filter == GamescopeUpscaleFilter::LINEAR && layer->viewConvertsToLinearAutomatically())) -+ u_shaderFilter |= ((uint32_t)GamescopeUpscaleFilter::FROM_VIEW) << (i * 4); - else -- u_shaderFilter[i] = (uint32_t)layer->filter; -+ u_shaderFilter |= ((uint32_t)layer->filter) << (i * 4); - - if (layer->ctm) - { -@@ -3572,7 +3573,6 @@ struct RcasPushData_t - - u_opacity[i] = frameInfo->layers[i].opacity; - } -- u_shaderFilter[0] = (uint32_t)GamescopeUpscaleFilter::FROM_VIEW; - - u_linearToNits = g_flInternalDisplayBrightnessNits; - u_nitsToLinear = 1.0f / g_flInternalDisplayBrightnessNits; -diff --git a/src/shaders/blit_push_data.h b/src/shaders/blit_push_data.h -index 3a4a32343..9395312ce 100644 ---- a/src/shaders/blit_push_data.h -+++ b/src/shaders/blit_push_data.h -@@ -8,8 +8,7 @@ uniform layers_t { - uint u_frameId; - uint u_blur_radius; - -- uint8_t u_shaderFilter[VKR_MAX_LAYERS]; -- uint8_t u_padding[2]; -+ uint u_shaderFilter; - - // hdr - float u_linearToNits; // sdr -> hdr -@@ -17,3 +16,4 @@ uniform layers_t { - float u_itmSdrNits; - float u_itmTargetNits; - }; -+ -diff --git a/src/shaders/composite.h b/src/shaders/composite.h -index 76c6cea65..884da244a 100644 ---- a/src/shaders/composite.h -+++ b/src/shaders/composite.h -@@ -1,5 +1,7 @@ - #include "colorimetry.h" - -+#include "shaderfilter.h" -+ - vec4 sampleRegular(sampler2D tex, vec2 coord, uint colorspace) { - vec4 color = textureLod(tex, coord, 0); - color.rgb = colorspace_plane_degamma_tf(color.rgb, colorspace); -@@ -162,12 +164,12 @@ vec4 sampleLayerEx(sampler2D layerSampler, uint offsetLayerIdx, uint colorspaceL - - uint colorspace = get_layer_colorspace(colorspaceLayerIdx); - vec4 color; -- if (u_shaderFilter[offsetLayerIdx] == filter_pixel) { -+ if (get_layer_shaderfilter(offsetLayerIdx) == filter_pixel) { - vec2 output_res = texSize / u_scale[offsetLayerIdx]; - vec2 extent = max((texSize / output_res), vec2(1.0 / 256.0)); - color = sampleBandLimited(layerSampler, coord, unnormalized ? vec2(1.0f) : texSize, unnormalized ? vec2(1.0f) : vec2(1.0f) / texSize, extent, colorspace, unnormalized); - } -- else if (u_shaderFilter[offsetLayerIdx] == filter_linear_emulated) { -+ else if (get_layer_shaderfilter(offsetLayerIdx) == filter_linear_emulated) { - color = sampleBilinear(layerSampler, coord, colorspace, unnormalized); - } - else { -diff --git a/src/shaders/cs_composite_blit.comp b/src/shaders/cs_composite_blit.comp -index 52bd9d517..51727fe86 100644 ---- a/src/shaders/cs_composite_blit.comp -+++ b/src/shaders/cs_composite_blit.comp -@@ -1,7 +1,6 @@ - #version 450 - - #extension GL_GOOGLE_include_directive : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #include "descriptor_set.h" -diff --git a/src/shaders/cs_composite_blur.comp b/src/shaders/cs_composite_blur.comp -index 52062bdca..a9326aa1a 100644 ---- a/src/shaders/cs_composite_blur.comp -+++ b/src/shaders/cs_composite_blur.comp -@@ -1,7 +1,6 @@ - #version 450 - - #extension GL_GOOGLE_include_directive : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #include "descriptor_set.h" -diff --git a/src/shaders/cs_composite_blur_cond.comp b/src/shaders/cs_composite_blur_cond.comp -index ffed4d5cf..7538cb06a 100644 ---- a/src/shaders/cs_composite_blur_cond.comp -+++ b/src/shaders/cs_composite_blur_cond.comp -@@ -1,7 +1,6 @@ - #version 450 - - #extension GL_GOOGLE_include_directive : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #include "descriptor_set.h" -diff --git a/src/shaders/cs_composite_rcas.comp b/src/shaders/cs_composite_rcas.comp -index 8c14920f7..cee2a45c2 100644 ---- a/src/shaders/cs_composite_rcas.comp -+++ b/src/shaders/cs_composite_rcas.comp -@@ -1,7 +1,6 @@ - #version 460 - - #extension GL_GOOGLE_include_directive : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #include "descriptor_set.h" -@@ -22,8 +21,7 @@ uniform layers_t { - uint u_frameId; - uint u_c1; - -- uint8_t u_shaderFilter[VKR_MAX_LAYERS]; -- uint8_t u_padding[2]; -+ uint u_shaderFilter; - - // hdr - float u_linearToNits; -diff --git a/src/shaders/cs_easu.comp b/src/shaders/cs_easu.comp -index 3eb883d8d..d13ee9577 100644 ---- a/src/shaders/cs_easu.comp -+++ b/src/shaders/cs_easu.comp -@@ -1,7 +1,6 @@ - #version 460 - - #extension GL_GOOGLE_include_directive : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #include "descriptor_set.h" -diff --git a/src/shaders/cs_easu_fp16.comp b/src/shaders/cs_easu_fp16.comp -index 344485404..403c2b28d 100644 ---- a/src/shaders/cs_easu_fp16.comp -+++ b/src/shaders/cs_easu_fp16.comp -@@ -2,7 +2,6 @@ - - #extension GL_GOOGLE_include_directive : require - #extension GL_EXT_shader_explicit_arithmetic_types_float16 : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #include "descriptor_set.h" -diff --git a/src/shaders/cs_gaussian_blur_horizontal.comp b/src/shaders/cs_gaussian_blur_horizontal.comp -index fac80d1e7..a4f6a92cb 100644 ---- a/src/shaders/cs_gaussian_blur_horizontal.comp -+++ b/src/shaders/cs_gaussian_blur_horizontal.comp -@@ -1,7 +1,6 @@ - #version 460 - - #extension GL_GOOGLE_include_directive : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #include "descriptor_set.h" -@@ -21,8 +20,7 @@ uniform layers_t { - uint u_frameId; - uint u_blur_radius; - -- uint8_t u_shaderFilter[VKR_MAX_LAYERS]; -- uint8_t u_padding[2]; -+ uint u_shaderFilter; - - // hdr - float u_linearToNits; -diff --git a/src/shaders/cs_nis.comp b/src/shaders/cs_nis.comp -index 78d0a85f5..9fe69e2f7 100644 ---- a/src/shaders/cs_nis.comp -+++ b/src/shaders/cs_nis.comp -@@ -1,7 +1,6 @@ - #version 450 - - #extension GL_GOOGLE_include_directive : enable --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #define NIS_GLSL 1 -diff --git a/src/shaders/cs_nis_fp16.comp b/src/shaders/cs_nis_fp16.comp -index 5827c83b3..f846d5d70 100644 ---- a/src/shaders/cs_nis_fp16.comp -+++ b/src/shaders/cs_nis_fp16.comp -@@ -2,7 +2,6 @@ - - #extension GL_GOOGLE_include_directive : enable - #extension GL_EXT_shader_explicit_arithmetic_types_float16 : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #define NIS_GLSL 1 -diff --git a/src/shaders/cs_rgb_to_nv12.comp b/src/shaders/cs_rgb_to_nv12.comp -index a15a3d4df..3702b8160 100644 ---- a/src/shaders/cs_rgb_to_nv12.comp -+++ b/src/shaders/cs_rgb_to_nv12.comp -@@ -1,7 +1,6 @@ - #version 450 - - #extension GL_GOOGLE_include_directive : require --#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require - #extension GL_EXT_scalar_block_layout : require - - #include "descriptor_set.h" -@@ -17,7 +16,7 @@ layout( - // UVUVUVUVUVUVUVU... - - const uint u_frameId = 0; --const uint8_t u_shaderFilter[VKR_MAX_LAYERS] = { uint8_t(filter_linear_emulated), uint8_t(0), uint8_t(0), uint8_t(0), uint8_t(0), uint8_t(0) }; -+const uint u_shaderFilter = filter_linear_emulated; - const float u_linearToNits = 400.0f; - const float u_nitsToLinear = 1.0f / 100.0f; - const float u_itmSdrNits = 100.f; -diff --git a/src/shaders/shaderfilter.h b/src/shaders/shaderfilter.h -new file mode 100644 -index 000000000..0d6fa4a98 ---- /dev/null -+++ b/src/shaders/shaderfilter.h -@@ -0,0 +1,10 @@ -+#ifndef SHADERFILTER_H -+#define SHADERFILTER_H -+ -+const int shader_filter_max_bits = 4; -+ -+uint get_layer_shaderfilter(uint layerIdx) { -+ return bitfieldExtract(u_shaderFilter, int(layerIdx) * shader_filter_max_bits, shader_filter_max_bits); -+} -+ -+#endif -\ No newline at end of file - -From 2a630934037dfebccf8307b947590646693206e6 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 14 Feb 2024 22:00:55 +0000 -Subject: [PATCH 124/134] vr: Fix build - ---- - src/vr_session.cpp | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/src/vr_session.cpp b/src/vr_session.cpp -index 654789aa8..c1a5444cd 100644 ---- a/src/vr_session.cpp -+++ b/src/vr_session.cpp -@@ -27,6 +27,9 @@ extern bool steamMode; - extern int g_argc; - extern char **g_argv; - -+extern int g_nPreferredOutputWidth; -+extern int g_nPreferredOutputHeight; -+ - static LogScope openvr_log("openvr"); - - static bool GetVulkanInstanceExtensionsRequired( std::vector< std::string > &outInstanceExtensionList ); - -From b5670a2e8501efbd2e593dfb4426c823990dc421 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 14 Feb 2024 23:50:28 +0000 -Subject: [PATCH 125/134] drm: Fix request rollback - ---- - src/drm.cpp | 65 +++++++++++++++++++++++++++++------------------------ - 1 file changed, 36 insertions(+), 29 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 5ff3cafcc..5c7ebf7d4 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -2484,6 +2484,39 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - - bool g_bForceAsyncFlips = false; - -+void drm_rollback( struct drm_t *drm ) -+{ -+ 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(); -+ } -+ } -+} -+ - /* 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 ) -@@ -2661,6 +2694,8 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - } - - if ( ret != 0 ) { -+ drm_rollback( drm ); -+ - drmModeAtomicFree( drm->req ); - drm->req = nullptr; - -@@ -3503,35 +3538,7 @@ namespace gamescope - abort(); - } - -- 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(); -- } -- } -+ drm_rollback( drm ); - - // Undo refcount if the commit didn't actually work - for ( uint32_t i = 0; i < drm->fbids_in_req.size(); i++ ) - -From f1cdbd1402717c8bf7e470c756620cae3b12301b Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Thu, 15 Feb 2024 00:10:39 +0000 -Subject: [PATCH 126/134] drm: Fix CTM matrix stuff, add typeinfo to backend - system - ---- - src/backend.h | 4 ++-- - src/drm.cpp | 22 +++++++++++++++++++--- - src/headless.cpp | 2 +- - src/sdlwindow.cpp | 4 ++-- - src/vr_session.cpp | 2 +- - 5 files changed, 25 insertions(+), 9 deletions(-) - -diff --git a/src/backend.h b/src/backend.h -index f19456a75..66b3ff964 100644 ---- a/src/backend.h -+++ b/src/backend.h -@@ -142,13 +142,13 @@ namespace gamescope - virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) = 0; - virtual bool PollState() = 0; - -- virtual std::shared_ptr CreateBackendBlob( std::span data ) = 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( std::span( pBegin, pEnd ) ); -+ return CreateBackendBlob( typeid( T ), std::span( pBegin, pEnd ) ); - } - - // For DRM, this is -diff --git a/src/drm.cpp b/src/drm.cpp -index 5c7ebf7d4..0fcf68efc 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -3353,11 +3353,27 @@ namespace gamescope - return drm_poll_state( &g_DRM ); - } - -- virtual std::shared_ptr CreateBackendBlob( std::span data ) override -+ virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override - { - uint32_t uBlob = 0; -- if ( drmModeCreatePropertyBlob( g_DRM.fd, data.data(), data.size(), &uBlob ) != 0 ) -- return nullptr; -+ 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] ); -+ -+ fprintf( stderr, " !!!! MAKING CTM BLOB!!!!!!\n "); -+ 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 ); - } -diff --git a/src/headless.cpp b/src/headless.cpp -index 5e6c7c04e..b6767cdd4 100644 ---- a/src/headless.cpp -+++ b/src/headless.cpp -@@ -170,7 +170,7 @@ namespace gamescope - return false; - } - -- virtual std::shared_ptr CreateBackendBlob( std::span data ) override -+ virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override - { - return std::make_shared( data ); - } -diff --git a/src/sdlwindow.cpp b/src/sdlwindow.cpp -index 1974bc229..50c3b95b3 100644 ---- a/src/sdlwindow.cpp -+++ b/src/sdlwindow.cpp -@@ -127,7 +127,7 @@ namespace gamescope - virtual void DirtyState( bool bForce = false, bool bForceModeset = false ) override; - virtual bool PollState() override; - -- virtual std::shared_ptr CreateBackendBlob( std::span data ) override; -+ virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override; - - virtual uint32_t ImportDmabufToBackend( wlr_buffer *pBuffer, wlr_dmabuf_attributes *pDmaBuf ) override; - virtual void LockBackendFb( uint32_t uFbId ) override; -@@ -390,7 +390,7 @@ namespace gamescope - return false; - } - -- std::shared_ptr CSDLBackend::CreateBackendBlob( std::span data ) -+ std::shared_ptr CSDLBackend::CreateBackendBlob( const std::type_info &type, std::span data ) - { - return std::make_shared( data ); - } -diff --git a/src/vr_session.cpp b/src/vr_session.cpp -index c1a5444cd..686d853fc 100644 ---- a/src/vr_session.cpp -+++ b/src/vr_session.cpp -@@ -466,7 +466,7 @@ namespace gamescope - return false; - } - -- virtual std::shared_ptr CreateBackendBlob( std::span data ) override -+ virtual std::shared_ptr CreateBackendBlob( const std::type_info &type, std::span data ) override - { - return std::make_shared( data ); - } - -From a6155bafaf38f5011f8bcd1eba90702f3498514c Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Thu, 15 Feb 2024 21:41:33 +0000 -Subject: [PATCH 127/134] steamcompmgr: Fix flInternalDisplayBrightness - -We should maybe remove this at some point...? ---- - src/steamcompmgr.cpp | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 80094e997..b2571ae6e 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -352,6 +352,9 @@ update_color_mgmt() - &g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF, - &g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF ); - -+ g_ColorMgmt.pending.flInternalDisplayBrightness = -+ GetBackend()->GetCurrentConnector()->GetHDRInfo().uMaxContentLightLevel; -+ - #ifdef COLOR_MGMT_MICROBENCH - struct timespec t0, t1; - #else - -From ba5f9e8e0c490ebadc1012ff1e6f5a38cb4a00f7 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Thu, 15 Feb 2024 21:43:42 +0000 -Subject: [PATCH 128/134] color_helpers: Add some extra color toys - ---- - src/color_helpers.cpp | 18 +++++++++++++++++- - 1 file changed, 17 insertions(+), 1 deletion(-) - -diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp -index 4ec148a65..18d3ee69d 100644 ---- a/src/color_helpers.cpp -+++ b/src/color_helpers.cpp -@@ -647,6 +647,8 @@ inline T calcLinearToEOTF( const T & input, EOTF eotf, const tonemapping_t & ton - // input is from 0->1 - // TODO: use tone-mapping for white, black, contrast ratio - -+bool g_bUseSourceEOTFForShaper = false; -+ - template - inline T applyShaper( const T & input, EOTF source, EOTF dest, const tonemapping_t & tonemapping, float flGain ) - { -@@ -658,9 +660,11 @@ inline T applyShaper( const T & input, EOTF source, EOTF dest, const tonemapping - T flLinear = flGain * calcEOTFToLinear( input, source, tonemapping ); - flLinear = tonemapping.apply( flLinear ); - -- return calcLinearToEOTF( flLinear, dest, tonemapping ); -+ return calcLinearToEOTF( flLinear, g_bUseSourceEOTFForShaper ? source : dest, tonemapping ); - } - -+bool g_bHuePreservationWhenClipping = false; -+ - void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, - lut3d_t * pLut3d, int nLutEdgeSize3d, - const displaycolorimetry_t & source, EOTF sourceEOTF, -@@ -778,6 +782,18 @@ void calcColorTransform( lut1d_t * pShaper, int nLutSize1d, - // Apply tonemapping - destColorLinear = tonemapping.apply( destColorLinear ); - -+ // 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. -+ if ( flMax > tonemapping.g22_luminance + 1.0f ) -+ { -+ destColorLinear /= flMax; -+ destColorLinear *= tonemapping.g22_luminance; -+ } -+ } -+ - // Apply dest EOTF - glm::vec3 destColorEOTFEncoded = calcLinearToEOTF( destColorLinear, destEOTF, tonemapping ); - - -From a3472f6f0518190215a3df3e41caee7e83338021 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 19 Feb 2024 17:46:46 -0800 -Subject: [PATCH 129/134] steamcompmgr: Fix cursor buffer empty check - ---- - src/steamcompmgr.cpp | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index b2571ae6e..5e3a5956b 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -1924,9 +1924,9 @@ bool MouseCursor::getTexture() - } - } - -- for (int i = 0; i < image->height; i++) -+ for (int i = 0; i < surfaceHeight; i++) - { -- for (int j = 0; j < image->width; j++) -+ for (int j = 0; j < surfaceWidth; j++) - { - if ( cursorBuffer[i * surfaceWidth + j] & 0xff000000 ) - { - -From bd225aa20662f5a8c9b862a8df197241df0e2649 Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 19 Feb 2024 17:54:11 -0800 -Subject: [PATCH 130/134] drm: Fix external HDR when compositing - ---- - src/drm.cpp | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 0fcf68efc..31bbe465e 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -3217,6 +3217,8 @@ namespace gamescope - vulkan_wait( *oCompositeResult, true ); - - FrameInfo_t presentCompFrameInfo = {}; -+ presentCompFrameInfo.allowVRR = pFrameInfo->allowVRR; -+ presentCompFrameInfo.outputEncodingEOTF = pFrameInfo->outputEncodingEOTF; - - if ( bNeedsFullComposite ) - { - -From e79d79a419402861952780ecd507ed6da56ea8ed Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Mon, 19 Feb 2024 17:57:05 -0800 -Subject: [PATCH 131/134] drm: Don't change cursor plane size if we are not - using the plane - ---- - src/drm.cpp | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 31bbe465e..9420ccbe4 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -50,6 +50,7 @@ - - #include "gamescope-control-protocol.h" - -+static constexpr bool k_bUseCursorPlane = false; - - namespace gamescope - { -@@ -3098,7 +3099,7 @@ namespace gamescope - bNeedsFullComposite |= pFrameInfo->useNISLayer0; - bNeedsFullComposite |= pFrameInfo->blurLayer0; - bNeedsFullComposite |= bNeedsCompositeFromFilter; -- bNeedsFullComposite |= bDrewCursor; -+ bNeedsFullComposite |= !k_bUseCursorPlane && bDrewCursor; - bNeedsFullComposite |= g_bColorSliderInUse; - bNeedsFullComposite |= pFrameInfo->bFadingOut; - bNeedsFullComposite |= !g_reshade_effect.empty(); -@@ -3469,6 +3470,9 @@ namespace gamescope - - 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 }; - } - - -From 6db5d2d06baaf4ed0e8dd0551ea109ebce78e56f Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 21 Feb 2024 22:11:20 +0000 -Subject: [PATCH 132/134] wlserver: Hook up take_screenshot and expose ver 3 - ---- - src/wlserver.cpp | 13 ++++++++++++- - 1 file changed, 12 insertions(+), 1 deletion(-) - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 4ee931b15..a0727924b 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -881,6 +881,16 @@ static void gamescope_control_set_app_target_refresh_cycle( struct wl_client *cl - steamcompmgr_set_app_refresh_cycle_override( display_type, fps ); - } - -+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, -+ } ); -+} -+ - static void gamescope_control_handle_destroy( struct wl_client *client, struct wl_resource *resource ) - { - wl_resource_destroy( resource ); -@@ -889,6 +899,7 @@ static void gamescope_control_handle_destroy( struct wl_client *client, struct w - 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, - }; - - static uint32_t get_conn_display_info_flags() -@@ -959,7 +970,7 @@ static void gamescope_control_bind( struct wl_client *client, void *data, uint32 - - static void create_gamescope_control( void ) - { -- uint32_t version = 2; -+ uint32_t version = 3; - wl_global_create( wlserver.display, &gamescope_control_interface, version, NULL, gamescope_control_bind ); - } - - -From d0d23c4c3010c81add1bd90cbe478ce4a386e28d Mon Sep 17 00:00:00 2001 -From: Joshua Ashton -Date: Wed, 21 Feb 2024 22:11:46 +0000 -Subject: [PATCH 133/134] steamcompmgr: Fix warning in cursor code - ---- - src/steamcompmgr.cpp | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 5e3a5956b..12136714d 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -1924,9 +1924,9 @@ bool MouseCursor::getTexture() - } - } - -- for (int i = 0; i < surfaceHeight; i++) -+ for (uint32_t i = 0; i < surfaceHeight; i++) - { -- for (int j = 0; j < surfaceWidth; j++) -+ for (uint32_t j = 0; j < surfaceWidth; j++) - { - if ( cursorBuffer[i * surfaceWidth + j] & 0xff000000 ) - { - -From 85432af61b779a02b636fdc29d98aba5e89fcff7 Mon Sep 17 00:00:00 2001 -From: Andrew O'Neil -Date: Sat, 24 Feb 2024 11:40:08 +1100 -Subject: [PATCH 134/134] Update AMD color management for Linux 6.8 - ---- - src/drm.cpp | 258 ++++++++++++++++++++++++++-------------------- - src/drm_include.h | 76 +++++++------- - 2 files changed, 188 insertions(+), 146 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 9420ccbe4..29b8d06f5 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -148,15 +148,15 @@ namespace gamescope - std::optional rotation; - std::optional COLOR_ENCODING; - std::optional COLOR_RANGE; -- std::optional VALVE1_PLANE_DEGAMMA_TF; -- std::optional VALVE1_PLANE_DEGAMMA_LUT; -- std::optional VALVE1_PLANE_CTM; -- std::optional VALVE1_PLANE_HDR_MULT; -- std::optional VALVE1_PLANE_SHAPER_LUT; -- std::optional VALVE1_PLANE_SHAPER_TF; -- std::optional VALVE1_PLANE_LUT3D; -- std::optional VALVE1_PLANE_BLEND_TF; -- std::optional VALVE1_PLANE_BLEND_LUT; -+ 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; } -@@ -187,7 +187,7 @@ namespace gamescope - std::optional CTM; - std::optional VRR_ENABLED; - std::optional OUT_FENCE_PTR; -- std::optional VALVE1_CRTC_REGAMMA_TF; -+ std::optional AMD_CRTC_REGAMMA_TF; - std::optional DUMMY_END; - }; - CRTCProperties &GetProperties() { return m_Props; } -@@ -401,7 +401,7 @@ struct drm_t { - uint32_t color_mgmt_serial; - std::shared_ptr lut3d_id[ EOTF_Count ]; - std::shared_ptr shaperlut_id[ EOTF_Count ]; -- drm_valve1_transfer_function output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; -+ amdgpu_transfer_function output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } current, pending; - - /* FBs in the atomic request, but not yet submitted to KMS */ -@@ -1287,8 +1287,8 @@ void finish_drm(struct drm_t *drm) - if ( pCRTC->GetProperties().OUT_FENCE_PTR ) - pCRTC->GetProperties().OUT_FENCE_PTR->SetPendingValue( req, 0, true ); - -- if ( pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) -- pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->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< gamescope::CDRMPlane > &pPlane : drm->planes ) -@@ -1313,32 +1313,32 @@ void finish_drm(struct drm_t *drm) - //if ( pPlane->GetProperties().zpos ) - // pPlane->GetProperties().zpos->SetPendingValue( req, , true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_TF ) -- pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); -+ if ( pPlane->GetProperties().AMD_PLANE_DEGAMMA_TF ) -+ pPlane->GetProperties().AMD_PLANE_DEGAMMA_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_LUT ) -- pPlane->GetProperties().VALVE1_PLANE_DEGAMMA_LUT->SetPendingValue( req, 0, true ); -+ if ( pPlane->GetProperties().AMD_PLANE_DEGAMMA_LUT ) -+ pPlane->GetProperties().AMD_PLANE_DEGAMMA_LUT->SetPendingValue( req, 0, true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_CTM ) -- pPlane->GetProperties().VALVE1_PLANE_CTM->SetPendingValue( req, 0, true ); -+ if ( pPlane->GetProperties().AMD_PLANE_CTM ) -+ pPlane->GetProperties().AMD_PLANE_CTM->SetPendingValue( req, 0, true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_HDR_MULT ) -- pPlane->GetProperties().VALVE1_PLANE_HDR_MULT->SetPendingValue( req, 0x100000000ULL, true ); -+ if ( pPlane->GetProperties().AMD_PLANE_HDR_MULT ) -+ pPlane->GetProperties().AMD_PLANE_HDR_MULT->SetPendingValue( req, 0x100000000ULL, true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_SHAPER_TF ) -- pPlane->GetProperties().VALVE1_PLANE_SHAPER_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); -+ if ( pPlane->GetProperties().AMD_PLANE_SHAPER_TF ) -+ pPlane->GetProperties().AMD_PLANE_SHAPER_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_SHAPER_LUT ) -- pPlane->GetProperties().VALVE1_PLANE_SHAPER_LUT->SetPendingValue( req, 0, true ); -+ if ( pPlane->GetProperties().AMD_PLANE_SHAPER_LUT ) -+ pPlane->GetProperties().AMD_PLANE_SHAPER_LUT->SetPendingValue( req, 0, true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_LUT3D ) -- pPlane->GetProperties().VALVE1_PLANE_LUT3D->SetPendingValue( req, 0, true ); -+ if ( pPlane->GetProperties().AMD_PLANE_LUT3D ) -+ pPlane->GetProperties().AMD_PLANE_LUT3D->SetPendingValue( req, 0, true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_BLEND_TF ) -- pPlane->GetProperties().VALVE1_PLANE_BLEND_TF->SetPendingValue( req, DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, true ); -+ if ( pPlane->GetProperties().AMD_PLANE_BLEND_TF ) -+ pPlane->GetProperties().AMD_PLANE_BLEND_TF->SetPendingValue( req, AMDGPU_TRANSFER_FUNCTION_DEFAULT, true ); - -- if ( pPlane->GetProperties().VALVE1_PLANE_BLEND_LUT ) -- pPlane->GetProperties().VALVE1_PLANE_BLEND_LUT->SetPendingValue( req, 0, 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 -@@ -1637,37 +1637,73 @@ struct LiftoffStateCacheEntryKasher - - std::unordered_set g_LiftoffStateCache; - --static inline drm_valve1_transfer_function colorspace_to_plane_degamma_tf(GamescopeAppTextureColorspace colorspace) -+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 DRM_VALVE1_TRANSFER_FUNCTION_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 DRM_VALVE1_TRANSFER_FUNCTION_LINEAR; -+ return AMDGPU_TRANSFER_FUNCTION_IDENTITY; - case GAMESCOPE_APP_TEXTURE_COLORSPACE_HDR10_PQ: -- return DRM_VALVE1_TRANSFER_FUNCTION_PQ; -+ return AMDGPU_TRANSFER_FUNCTION_PQ_EOTF; - } - } - --static inline drm_valve1_transfer_function colorspace_to_plane_shaper_tf(GamescopeAppTextureColorspace colorspace) -+static inline amdgpu_transfer_function colorspace_to_plane_shaper_tf(GamescopeAppTextureColorspace colorspace) - { - switch ( colorspace ) - { - default: - case GAMESCOPE_APP_TEXTURE_COLORSPACE_SRGB: -- return DRM_VALVE1_TRANSFER_FUNCTION_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 DRM_VALVE1_TRANSFER_FUNCTION_PQ; -+ return AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF; - case GAMESCOPE_APP_TEXTURE_COLORSPACE_PASSTHRU: -- return DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; -+ 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; - } - } - -@@ -1862,33 +1898,33 @@ namespace gamescope - 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.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.VALVE1_PLANE_DEGAMMA_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_DEGAMMA_TF", this, *rawProperties ); -- m_Props.VALVE1_PLANE_DEGAMMA_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_DEGAMMA_LUT", this, *rawProperties ); -- m_Props.VALVE1_PLANE_CTM = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_CTM", this, *rawProperties ); -- m_Props.VALVE1_PLANE_HDR_MULT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_HDR_MULT", this, *rawProperties ); -- m_Props.VALVE1_PLANE_SHAPER_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_SHAPER_LUT", this, *rawProperties ); -- m_Props.VALVE1_PLANE_SHAPER_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_SHAPER_TF", this, *rawProperties ); -- m_Props.VALVE1_PLANE_LUT3D = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_LUT3D", this, *rawProperties ); -- m_Props.VALVE1_PLANE_BLEND_TF = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_BLEND_TF", this, *rawProperties ); -- m_Props.VALVE1_PLANE_BLEND_LUT = CDRMAtomicProperty::Instantiate( "VALVE1_PLANE_BLEND_LUT", this, *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.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 ); - } - } - -@@ -1908,14 +1944,14 @@ namespace gamescope - 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.VALVE1_CRTC_REGAMMA_TF = CDRMAtomicProperty::Instantiate( "VALVE1_CRTC_REGAMMA_TF", this, *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 ); - } - } - -@@ -2376,8 +2412,8 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - - if ( drm_supports_color_mgmt( drm ) ) - { -- drm_valve1_transfer_function degamma_tf = colorspace_to_plane_degamma_tf( entry.layerState[i].colorspace ); -- drm_valve1_transfer_function shaper_tf = colorspace_to_plane_shaper_tf( entry.layerState[i].colorspace ); -+ 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 ( entry.layerState[i].ycbcr ) - { -@@ -2389,27 +2425,27 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - // - // Doing LINEAR/DEFAULT here introduces banding so... this is the best way. - // (sRGB DEGAMMA does NOT work on YUV planes!) -- degamma_tf = DRM_VALVE1_TRANSFER_FUNCTION_BT709; -- shaper_tf = DRM_VALVE1_TRANSFER_FUNCTION_BT709; -+ degamma_tf = AMDGPU_TRANSFER_FUNCTION_BT709_OETF; -+ shaper_tf = AMDGPU_TRANSFER_FUNCTION_BT709_INV_OETF; - } - - if (!g_bDisableDegamma) -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", degamma_tf ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", degamma_tf ); - else -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", 0 ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_DEGAMMA_TF", 0 ); - - if ( !g_bDisableShaperAnd3DLUT ) - { -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", drm->pending.shaperlut_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", shaper_tf ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", drm->pending.lut3d_id[ ColorSpaceToEOTFIndex( entry.layerState[i].colorspace ) ]->GetBlobValue() ); -+ 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 ], "VALVE1_PLANE_SHAPER_LUT", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); -+ 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 ); - } - } - } -@@ -2417,25 +2453,25 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - { - if ( drm_supports_color_mgmt( drm ) ) - { -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); -+ 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 (!g_bDisableBlendTF && !bSinglePlane) -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", drm->pending.output_tf ); -+ 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 ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_BLEND_TF", AMDGPU_TRANSFER_FUNCTION_DEFAULT ); - - if (frameInfo->layers[i].ctm != nullptr) -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", frameInfo->layers[i].ctm->GetBlobValue() ); -+ 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 ], "VALVE1_PLANE_CTM", 0 ); -+ liftoff_layer_set_property( drm->lo_layers[ i ], "AMD_PLANE_CTM", 0 ); - } - } - else -@@ -2447,12 +2483,12 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo - - if ( drm_supports_color_mgmt( drm ) ) - { -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_DEGAMMA_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_LUT", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_SHAPER_TF", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_LUT3D", 0 ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_BLEND_TF", DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT ); -- liftoff_layer_set_property( drm->lo_layers[ i ], "VALVE1_PLANE_CTM", 0 ); -+ 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 ); - } - } - } -@@ -2533,7 +2569,7 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - drm->needs_modeset = true; - } - -- uint32_t uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; -+ drm_colorspace uColorimetry = DRM_MODE_COLORIMETRY_DEFAULT; - - const bool bWantsHDR10 = g_bOutputHDREnabled && frameInfo->outputEncodingEOTF == EOTF_PQ; - gamescope::BackendBlob *pHDRMetadata = nullptr; -@@ -2571,17 +2607,17 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - if ( !g_bDisableRegamma && !bSinglePlane ) - { - drm->pending.output_tf = g_bOutputHDREnabled -- ? DRM_VALVE1_TRANSFER_FUNCTION_PQ -- : DRM_VALVE1_TRANSFER_FUNCTION_SRGB; -+ ? AMDGPU_TRANSFER_FUNCTION_PQ_EOTF -+ : AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF; - } - else - { -- drm->pending.output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; -+ drm->pending.output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } - } - else - { -- drm->pending.output_tf = DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT; -+ drm->pending.output_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; - } - - uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; -@@ -2644,8 +2680,8 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - if ( pCRTC->GetProperties().OUT_FENCE_PTR ) - pCRTC->GetProperties().OUT_FENCE_PTR->SetPendingValue( drm->req, 0, bForceInRequest ); - -- if ( pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) -- pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->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 ) -@@ -2679,8 +2715,8 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - - if ( drm->pCRTC ) - { -- if ( drm->pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF ) -- drm->pCRTC->GetProperties().VALVE1_CRTC_REGAMMA_TF->SetPendingValue( drm->req, drm->pending.output_tf, bForceInRequest ); -+ if ( drm->pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF ) -+ drm->pCRTC->GetProperties().AMD_CRTC_REGAMMA_TF->SetPendingValue( drm->req, inverse_tf(drm->pending.output_tf), bForceInRequest ); - } - - drm->flags = flags; -@@ -2995,7 +3031,7 @@ bool drm_supports_color_mgmt(struct drm_t *drm) - if ( !drm->pPrimaryPlane ) - return false; - -- return drm->pPrimaryPlane->GetProperties().VALVE1_PLANE_CTM.has_value(); -+ return drm->pPrimaryPlane->GetProperties().AMD_PLANE_CTM.has_value(); - } - - std::span drm_get_valid_refresh_rates( struct drm_t *drm ) -diff --git a/src/drm_include.h b/src/drm_include.h -index 500c3040a..d0ed3ce9e 100644 ---- a/src/drm_include.h -+++ b/src/drm_include.h -@@ -28,19 +28,22 @@ enum drm_color_range { - DRM_COLOR_RANGE_MAX, - }; - --enum drm_valve1_transfer_function { -- DRM_VALVE1_TRANSFER_FUNCTION_DEFAULT, -- -- DRM_VALVE1_TRANSFER_FUNCTION_SRGB, -- DRM_VALVE1_TRANSFER_FUNCTION_BT709, -- DRM_VALVE1_TRANSFER_FUNCTION_PQ, -- DRM_VALVE1_TRANSFER_FUNCTION_LINEAR, -- DRM_VALVE1_TRANSFER_FUNCTION_UNITY, -- DRM_VALVE1_TRANSFER_FUNCTION_HLG, -- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA22, -- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA24, -- DRM_VALVE1_TRANSFER_FUNCTION_GAMMA26, -- DRM_VALVE1_TRANSFER_FUNCTION_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 drm_panel_orientation { -@@ -51,28 +54,31 @@ enum drm_panel_orientation { - DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, - }; - --/* For Default case, driver will set the colorspace */ --#define DRM_MODE_COLORIMETRY_DEFAULT 0 --/* CEA 861 Normal Colorimetry options */ --#define DRM_MODE_COLORIMETRY_NO_DATA 0 --#define DRM_MODE_COLORIMETRY_SMPTE_170M_YCC 1 --#define DRM_MODE_COLORIMETRY_BT709_YCC 2 --/* CEA 861 Extended Colorimetry Options */ --#define DRM_MODE_COLORIMETRY_XVYCC_601 3 --#define DRM_MODE_COLORIMETRY_XVYCC_709 4 --#define DRM_MODE_COLORIMETRY_SYCC_601 5 --#define DRM_MODE_COLORIMETRY_OPYCC_601 6 --#define DRM_MODE_COLORIMETRY_OPRGB 7 --#define DRM_MODE_COLORIMETRY_BT2020_CYCC 8 --#define DRM_MODE_COLORIMETRY_BT2020_RGB 9 --#define DRM_MODE_COLORIMETRY_BT2020_YCC 10 --/* Additional Colorimetry extension added as part of CTA 861.G */ --#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 11 --#define DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER 12 --/* Additional Colorimetry Options added for DP 1.4a VSC Colorimetry Format */ --#define DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED 13 --#define DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT 14 --#define DRM_MODE_COLORIMETRY_BT601_YCC 15 -+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 diff --git a/spec_files/gamescope/40/add_720p_var.patch b/spec_files/gamescope/40/add_720p_var.patch deleted file mode 100644 index 05ff1c72a4..0000000000 --- a/spec_files/gamescope/40/add_720p_var.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 19c6635d5e20dd429cb23b4a7c728afa306fae0a Mon Sep 17 00:00:00 2001 -From: Sterophonick -Date: Sat, 10 Feb 2024 22:00:36 -0700 -Subject: [PATCH] steamcompmgr: add env var to enable/disable 720p restriction - ---- - src/steamcompmgr.cpp | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 00c00e9..795898c 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -137,6 +137,9 @@ extern float g_flInternalDisplayBrightnessNits; - extern float g_flHDRItmSdrNits; - extern float g_flHDRItmTargetNits; - -+// define env_to_bool to point to the function in drm: remove in later patches pl0x -+extern bool env_to_bool(const char *env); -+ - uint64_t g_lastWinSeq = 0; - - static std::shared_ptr s_scRGB709To2020Matrix; -@@ -5657,7 +5660,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - int width = xwayland_mode_ctl[ 1 ]; - int height = xwayland_mode_ctl[ 2 ]; - -- if ( g_nOutputWidth != 1280 && width == 1280 ) -+ if ( g_nOutputWidth != 1280 && width == 1280 && !env_to_bool(getenv("GAMESCOPE_ENABLE_720P_RESTRICT")) ) - { - width = g_nOutputWidth; - height = g_nOutputHeight; --- -2.43.0 - diff --git a/spec_files/gamescope/40/chimeraos.patch b/spec_files/gamescope/40/chimeraos.patch deleted file mode 100644 index a2018692aa..0000000000 --- a/spec_files/gamescope/40/chimeraos.patch +++ /dev/null @@ -1,974 +0,0 @@ -From 4cafbd696c342c1f45eea6242dcaadd26e8e4a3d Mon Sep 17 00:00:00 2001 -From: Matthew Anderson -Date: Fri, 6 Oct 2023 17:22:41 -0500 -Subject: [PATCH 1/9] Add initial rotation atom controls - ---- - src/drm.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ - src/drm.hpp | 11 +++++++++++ - src/steamcompmgr.cpp | 32 ++++++++++++++++++++++++++++++++ - src/xwayland_ctx.hpp | 1 + - 4 files changed, 86 insertions(+) - -diff --git a/src/drm.cpp b/src/drm.cpp -index c2694f0..de5e3ca 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -45,6 +45,7 @@ struct drm_t g_DRM = {}; - 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; -+bool g_rotate_ctl_enable = false; - bool g_bUseLayers = true; - bool g_bDebugLayers = false; - const char *g_sOutputName = nullptr; -@@ -65,6 +66,7 @@ bool g_bSupportsAsyncFlips = false; - - enum drm_mode_generation g_drmModeGeneration = DRM_MODE_GENERATE_CVT; - enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; -+enum g_rotate_ctl g_drmRotateCTL; - std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; - - bool g_bForceDisableColorMgmt = false; -@@ -2010,6 +2012,27 @@ static void update_drm_effective_orientation(struct drm_t *drm, struct connector - static void update_drm_effective_orientations(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) - { - drm_screen_type screenType = drm_get_connector_type(conn->connector); -+ -+ if (g_rotate_ctl_enable) -+ { -+ switch (g_drmRotateCTL) -+ { -+ default: -+ case NORMAL: -+ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_0; -+ break; -+ case LEFT_UP: -+ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_90; -+ break; -+ case UPSIDEDOWN: -+ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_180; -+ break; -+ case RIGHT_UP: -+ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_270; -+ break; -+ } -+ return; -+ } - if (screenType == DRM_SCREEN_TYPE_INTERNAL) - { - update_drm_effective_orientation(drm, conn, mode); -@@ -3083,6 +3106,25 @@ bool drm_set_refresh( struct drm_t *drm, int refresh ) - return drm_set_mode(drm, &mode); - } - -+void drm_set_orientation( struct drm_t *drm, bool isRotated) -+{ -+ int width = g_nOutputWidth; -+ int height = g_nOutputHeight; -+ g_bRotated = isRotated; -+ if ( g_bRotated ) { -+ int tmp = width; -+ width = height; -+ height = tmp; -+ } -+ -+ if (!drm->connector || !drm->connector->connector) -+ return; -+ -+ drmModeConnector *connector = drm->connector->connector; -+ const drmModeModeInfo *mode = find_mode(connector, width, height, 0); -+ update_drm_effective_orientations(drm, drm->connector, mode); -+} -+ - bool drm_set_resolution( struct drm_t *drm, int width, int height ) - { - if (!drm->connector || !drm->connector->connector) -diff --git a/src/drm.hpp b/src/drm.hpp -index 6810797..b2ab49f 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -325,13 +325,24 @@ enum g_panel_orientation { - PANEL_ORIENTATION_AUTO, - }; - -+enum g_rotate_ctl{ -+ NORMAL, -+ LEFT_UP, -+ UPSIDEDOWN, -+ RIGHT_UP, -+}; -+ - extern enum drm_mode_generation g_drmModeGeneration; - extern enum g_panel_orientation g_drmModeOrientation; -+extern enum g_rotate_ctl g_drmRotateCTL; -+extern bool g_rotate_ctl_enable; - - extern std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* - - extern bool g_bForceDisableColorMgmt; - -+void drm_set_orientation( struct drm_t *drm, bool isRotated ); -+ - bool init_drm(struct drm_t *drm, int width, int height, int refresh, bool wants_adaptive_sync); - void finish_drm(struct drm_t *drm); - int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index b02fa33..277a54c 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -5644,6 +5644,37 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - if ( g_upscaleFilter == GamescopeUpscaleFilter::FSR || g_upscaleFilter == GamescopeUpscaleFilter::NIS ) - hasRepaint = true; - } -+ if ( ev->atom == ctx->atoms.gamescopeRotateControl ) -+ { -+ std::vector< uint32_t > drm_rot_ctl; -+ bool rotate = get_prop( ctx, ctx->root, ctx->atoms.gamescopeRotateControl, drm_rot_ctl ); -+ bool rotated = false; -+ if ( rotate && drm_rot_ctl.size() == 1 ) -+ { -+ xwm_log.debugf("drm_rot_ctl %d", drm_rot_ctl[0]); -+ g_rotate_ctl_enable = true; -+ switch ( drm_rot_ctl[0] ) -+ { -+ case 0: -+ g_drmRotateCTL = NORMAL; -+ rotated = false; -+ break; -+ case 1: -+ g_drmRotateCTL = LEFT_UP; -+ rotated = true; -+ break; -+ case 2: -+ g_drmRotateCTL = UPSIDEDOWN; -+ rotated = false; -+ break; -+ case 3: -+ g_drmRotateCTL = RIGHT_UP; -+ rotated = true; -+ break; -+ } -+ drm_set_orientation(&g_DRM, rotated); -+ } -+ } - if ( ev->atom == ctx->atoms.gamescopeXWaylandModeControl ) - { - std::vector< uint32_t > xwayland_mode_ctl; -@@ -7248,6 +7279,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - ctx->atoms.gamescopeFSRSharpness = XInternAtom( ctx->dpy, "GAMESCOPE_FSR_SHARPNESS", false ); - ctx->atoms.gamescopeSharpness = XInternAtom( ctx->dpy, "GAMESCOPE_SHARPNESS", false ); - -+ ctx->atoms.gamescopeRotateControl = XInternAtom( ctx->dpy, "GAMESCOPE_ROTATE_CONTROL", 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[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH", false ); -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index 5764c4b..6231007 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -146,6 +146,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable - Atom gamescopeFSRSharpness; - Atom gamescopeSharpness; - -+ Atom gamescopeRotateControl; - Atom gamescopeXWaylandModeControl; - - Atom gamescopeFPSLimit; --- -2.42.0 - - -From 4d8f1c32f1be873bf009b3d14b1ff3756495da63 Mon Sep 17 00:00:00 2001 -From: Matthew Anderson -Date: Sat, 7 Oct 2023 10:38:09 -0500 -Subject: [PATCH 2/9] Flag drm_out_of_date to ensure rotation logic gets reset - ---- - src/steamcompmgr.cpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 277a54c..236bba4 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -5673,6 +5673,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - break; - } - drm_set_orientation(&g_DRM, rotated); -+ g_DRM.out_of_date = 2; - } - } - if ( ev->atom == ctx->atoms.gamescopeXWaylandModeControl ) --- -2.42.0 - - -From b145e5cde74d026ffceddee7f4096a23e60e6112 Mon Sep 17 00:00:00 2001 -From: Matthew Anderson -Date: Tue, 25 Apr 2023 06:45:01 -0500 -Subject: [PATCH 3/9] Add --force-panel-type and --force-external-orientation - arguments (#2) - -* Add --force-panel-type and --force-external-orientation arguments. - -* Rotate only the internal display when faked as "external" - -* Try to prevent the external display from being rotated when --force-panel-type external is used. - -* Fixed docking issue when --force-panel-type external is used and you dock/undock the handheld. ---- - src/drm.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++------- - src/drm.hpp | 15 +++++++++++++++ - src/main.cpp | 36 +++++++++++++++++++++++++++++++++++ - 3 files changed, 98 insertions(+), 7 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index de5e3ca..f4fe8fd 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -46,6 +46,7 @@ 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; - bool g_rotate_ctl_enable = false; -+bool g_bDisplayTypeInternal = false; - bool g_bUseLayers = true; - bool g_bDebugLayers = false; - const char *g_sOutputName = nullptr; -@@ -68,6 +69,8 @@ enum drm_mode_generation g_drmModeGeneration = DRM_MODE_GENERATE_CVT; - enum g_panel_orientation g_drmModeOrientation = PANEL_ORIENTATION_AUTO; - enum g_rotate_ctl g_drmRotateCTL; - std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]{ {DRM_MODE_ROTATE_0}, {DRM_MODE_ROTATE_0} }; -+enum g_panel_external_orientation g_drmModeExternalOrientation = PANEL_EXTERNAL_ORIENTATION_AUTO; -+enum g_panel_type g_drmPanelType = PANEL_TYPE_AUTO; - - bool g_bForceDisableColorMgmt = false; - -@@ -1981,8 +1984,29 @@ static uint64_t determine_drm_orientation(struct drm_t *drm, struct connector *c - static void update_drm_effective_orientation(struct drm_t *drm, struct connector *conn, const drmModeModeInfo *mode) - { - drm_screen_type screenType = drm_get_connector_type(conn->connector); -- -- if (screenType == DRM_SCREEN_TYPE_INTERNAL) -+ if ( screenType == DRM_SCREEN_TYPE_EXTERNAL && g_bDisplayTypeInternal == true ) -+ { -+ switch ( g_drmModeExternalOrientation ) -+ { -+ case PANEL_EXTERNAL_ORIENTATION_0: -+ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_0; -+ break; -+ case PANEL_EXTERNAL_ORIENTATION_90: -+ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_90; -+ break; -+ case PANEL_EXTERNAL_ORIENTATION_180: -+ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_180; -+ break; -+ case PANEL_EXTERNAL_ORIENTATION_270: -+ g_drmEffectiveOrientation[screenType] = DRM_MODE_ROTATE_270; -+ break; -+ case PANEL_EXTERNAL_ORIENTATION_AUTO: -+ g_drmEffectiveOrientation[screenType] = determine_drm_orientation(drm, conn, mode); -+ break; -+ } -+ return; -+ } -+ else if ( screenType == DRM_SCREEN_TYPE_INTERNAL ) - { - switch ( g_drmModeOrientation ) - { -@@ -2933,11 +2957,27 @@ bool drm_get_vrr_in_use(struct drm_t *drm) - - drm_screen_type drm_get_connector_type(drmModeConnector *connector) - { -- if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || -- connector->connector_type == DRM_MODE_CONNECTOR_LVDS || -- connector->connector_type == DRM_MODE_CONNECTOR_DSI) -- return DRM_SCREEN_TYPE_INTERNAL; -- -+ // Set to the default state of false to make sure the external display isn't rotated when a system is docked -+ g_bDisplayTypeInternal = false; -+ switch ( g_drmPanelType ) -+ { -+ case PANEL_TYPE_INTERNAL: -+ return DRM_SCREEN_TYPE_INTERNAL; -+ break; -+ case PANEL_TYPE_EXTERNAL: -+ if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || -+ connector->connector_type == DRM_MODE_CONNECTOR_LVDS || -+ connector->connector_type == DRM_MODE_CONNECTOR_DSI) -+ g_bDisplayTypeInternal = true; -+ return DRM_SCREEN_TYPE_EXTERNAL; -+ break; -+ case PANEL_TYPE_AUTO: -+ if (connector->connector_type == DRM_MODE_CONNECTOR_eDP || -+ connector->connector_type == DRM_MODE_CONNECTOR_LVDS || -+ connector->connector_type == DRM_MODE_CONNECTOR_DSI) -+ return DRM_SCREEN_TYPE_INTERNAL; -+ break; -+ } - return DRM_SCREEN_TYPE_EXTERNAL; - } - -diff --git a/src/drm.hpp b/src/drm.hpp -index b2ab49f..53fc540 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -331,11 +331,26 @@ enum g_rotate_ctl{ - UPSIDEDOWN, - RIGHT_UP, - }; -+enum g_panel_external_orientation { -+ PANEL_EXTERNAL_ORIENTATION_0, /* NORMAL */ -+ PANEL_EXTERNAL_ORIENTATION_270, /* RIGHT */ -+ PANEL_EXTERNAL_ORIENTATION_90, /* LEFT */ -+ PANEL_EXTERNAL_ORIENTATION_180, /* UPSIDE DOWN */ -+ PANEL_EXTERNAL_ORIENTATION_AUTO, -+}; -+ -+enum g_panel_type { -+ PANEL_TYPE_INTERNAL, -+ PANEL_TYPE_EXTERNAL, -+ PANEL_TYPE_AUTO, -+}; - - extern enum drm_mode_generation g_drmModeGeneration; - extern enum g_panel_orientation g_drmModeOrientation; - extern enum g_rotate_ctl g_drmRotateCTL; - extern bool g_rotate_ctl_enable; -+extern enum g_panel_external_orientation g_drmModeExternalOrientation; -+extern enum g_panel_type g_drmPanelType; - - extern std::atomic g_drmEffectiveOrientation[DRM_SCREEN_TYPE_COUNT]; // DRM_MODE_ROTATE_* - -diff --git a/src/main.cpp b/src/main.cpp -index 76721d6..f6ba34f 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -118,6 +118,8 @@ const struct option *gamescope_options = (struct option[]){ - { "disable-xres", no_argument, nullptr, 'x' }, - { "fade-out-duration", required_argument, nullptr, 0 }, - { "force-orientation", required_argument, nullptr, 0 }, -+ { "force-external-orientation", required_argument, nullptr, 0 }, -+ { "force-panel-type", required_argument, nullptr, 0 }, - { "force-windows-fullscreen", no_argument, nullptr, 0 }, - - { "disable-color-management", no_argument, nullptr, 0 }, -@@ -167,6 +169,8 @@ const char usage[] = - " --xwayland-count create N xwayland servers\n" - " --prefer-vk-device prefer Vulkan device for compositing (ex: 1002:7300)\n" - " --force-orientation rotate the internal display (left, right, normal, upsidedown)\n" -+ " --force-external-orientation rotate the external display (left, right, normal, upsidedown)\n" -+ " --force-panel-type force gamescope to treat the display as either internal or external\n" - " --force-windows-fullscreen force windows inside of gamescope to be the size of the nested display (fullscreen)\n" - " --cursor-scale-height if specified, sets a base output height to linearly scale the cursor against.\n" - " --hdr-enabled enable HDR output (needs Gamescope WSI layer enabled for support from clients)\n" -@@ -353,6 +357,18 @@ static enum drm_mode_generation parse_drm_mode_generation(const char *str) - } - } - -+static enum g_panel_type force_panel_type(const char *str) -+{ -+ if (strcmp(str, "internal") == 0) { -+ return PANEL_TYPE_INTERNAL; -+ } else if (strcmp(str, "external") == 0) { -+ return PANEL_TYPE_EXTERNAL; -+ } else { -+ fprintf( stderr, "gamescope: invalid value for --force-panel-type\n" ); -+ exit(1); -+ } -+} -+ - static enum g_panel_orientation force_orientation(const char *str) - { - if (strcmp(str, "normal") == 0) { -@@ -408,6 +424,22 @@ static enum GamescopeUpscaleFilter parse_upscaler_filter(const char *str) - struct sigaction handle_signal_action = {}; - extern pid_t child_pid; - -+static enum g_panel_external_orientation force_external_orientation(const char *str) -+{ -+ if (strcmp(str, "normal") == 0) { -+ return PANEL_EXTERNAL_ORIENTATION_0; -+ } else if (strcmp(str, "right") == 0) { -+ return PANEL_EXTERNAL_ORIENTATION_270; -+ } else if (strcmp(str, "left") == 0) { -+ return PANEL_EXTERNAL_ORIENTATION_90; -+ } else if (strcmp(str, "upsidedown") == 0) { -+ return PANEL_EXTERNAL_ORIENTATION_180; -+ } else { -+ fprintf( stderr, "gamescope: invalid value for --force-external-orientation\n" ); -+ exit(1); -+ } -+} -+ - static void handle_signal( int sig ) - { - switch ( sig ) { -@@ -614,6 +646,10 @@ int main(int argc, char **argv) - g_drmModeGeneration = parse_drm_mode_generation( optarg ); - } else if (strcmp(opt_name, "force-orientation") == 0) { - g_drmModeOrientation = force_orientation( optarg ); -+ } else if (strcmp(opt_name, "force-external-orientation") == 0) { -+ g_drmModeExternalOrientation = force_external_orientation( optarg ); -+ } else if (strcmp(opt_name, "force-panel-type") == 0) { -+ g_drmPanelType = force_panel_type( optarg ); - } else if (strcmp(opt_name, "sharpness") == 0 || - strcmp(opt_name, "fsr-sharpness") == 0) { - g_upscaleFilterSharpness = atoi( optarg ); --- -2.42.0 - - -From 4d1f8e34b70fee42e4e30feac16eda7aa2aa63e7 Mon Sep 17 00:00:00 2001 -From: Matthew Anderson -Date: Tue, 25 Jul 2023 18:05:05 -0500 -Subject: [PATCH 4/9] Set default to native resolution of display if Steam - tries to force 720p/800p - -You can select 720p/800p still in game or via Steam's resolution setting -Steam > Settings > Display > Resolution - -This effectively reverts the changes Valve made a year ago forcing us to -720p. ---- - src/steamcompmgr.cpp | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 236bba4..60f9828 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -5685,6 +5685,13 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - size_t server_idx = size_t{ xwayland_mode_ctl[ 0 ] }; - int width = xwayland_mode_ctl[ 1 ]; - int height = xwayland_mode_ctl[ 2 ]; -+ -+ if ( g_nOutputWidth != 1280 && width == 1280 ) -+ { -+ width = g_nOutputWidth; -+ height = g_nOutputHeight; -+ } -+ - bool allowSuperRes = !!xwayland_mode_ctl[ 3 ]; - - if ( !allowSuperRes ) --- -2.42.0 - - -From 8959ef22543eb94d329ef9c117ec662061a3db6c Mon Sep 17 00:00:00 2001 -From: Matthew Anderson -Date: Wed, 26 Jul 2023 20:46:29 -0500 -Subject: [PATCH 5/9] Fix internal display touchscreen orientation when it's - forced - ---- - src/main.cpp | 6 ++++++ - src/main.hpp | 2 ++ - src/wlserver.cpp | 25 +++++++++++++++++++++++++ - 3 files changed, 33 insertions(+) - -diff --git a/src/main.cpp b/src/main.cpp -index f6ba34f..17409b5 100644 ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -269,6 +269,8 @@ bool g_bHeadless = false; - - bool g_bGrabbed = false; - -+bool g_bExternalForced = false; -+ - GamescopeUpscaleFilter g_upscaleFilter = GamescopeUpscaleFilter::LINEAR; - GamescopeUpscaleScaler g_upscaleScaler = GamescopeUpscaleScaler::AUTO; - -@@ -427,12 +429,16 @@ extern pid_t child_pid; - static enum g_panel_external_orientation force_external_orientation(const char *str) - { - if (strcmp(str, "normal") == 0) { -+ g_bExternalForced = true; - return PANEL_EXTERNAL_ORIENTATION_0; - } else if (strcmp(str, "right") == 0) { -+ g_bExternalForced = true; - return PANEL_EXTERNAL_ORIENTATION_270; - } else if (strcmp(str, "left") == 0) { -+ g_bExternalForced = true; - return PANEL_EXTERNAL_ORIENTATION_90; - } else if (strcmp(str, "upsidedown") == 0) { -+ g_bExternalForced = true; - return PANEL_EXTERNAL_ORIENTATION_180; - } else { - fprintf( stderr, "gamescope: invalid value for --force-external-orientation\n" ); -diff --git a/src/main.hpp b/src/main.hpp -index 7d8e9f1..97ec0a8 100644 ---- a/src/main.hpp -+++ b/src/main.hpp -@@ -23,6 +23,8 @@ extern bool g_bFullscreen; - - extern bool g_bGrabbed; - -+extern bool g_bExternalForced; -+ - enum class GamescopeUpscaleFilter : uint32_t - { - LINEAR = 0, -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index 3fbc4ff..dc37f97 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -1976,6 +1976,31 @@ static void apply_touchscreen_orientation(double *x, double *y ) - ty = 1.0 - *x; - break; - } -+ // Rotate screen if it's forced with --force-external-orientation -+ -+ if ( g_bExternalForced == true) -+ { -+ switch ( g_drmEffectiveOrientation[DRM_SCREEN_TYPE_EXTERNAL] ) -+ { -+ default: -+ case DRM_MODE_ROTATE_0: -+ tx = *x; -+ ty = *y; -+ break; -+ case DRM_MODE_ROTATE_90: -+ tx = 1.0 - *y; -+ ty = *x; -+ break; -+ case DRM_MODE_ROTATE_180: -+ tx = 1.0 - *x; -+ ty = 1.0 - *y; -+ break; -+ case DRM_MODE_ROTATE_270: -+ tx = *y; -+ ty = 1.0 - *x; -+ break; -+ } -+ } - - *x = tx; - *y = ty; --- -2.42.0 - - -From 17a8118d9ede790f27fa085a1d287f31e81abb64 Mon Sep 17 00:00:00 2001 -From: Matthew Anderson -Date: Fri, 6 Oct 2023 23:58:17 -0500 -Subject: [PATCH 6/9] Add initial display selection atom - ---- - src/drm.cpp | 20 +++++++++++++ - src/drm.hpp | 1 + - src/steamcompmgr.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++++ - src/xwayland_ctx.hpp | 1 + - 4 files changed, 91 insertions(+) - -diff --git a/src/drm.cpp b/src/drm.cpp -index f4fe8fd..d2f7677 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -50,6 +50,7 @@ bool g_bDisplayTypeInternal = false; - bool g_bUseLayers = true; - bool g_bDebugLayers = false; - const char *g_sOutputName = nullptr; -+char* targetConnector = (char*)"eDP-1"; - - #ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP - #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 -@@ -1245,6 +1246,19 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - } - } - -+ for (auto &kv : drm->connectors) { -+ struct connector *conn = &kv.second; -+ drm_log.debugf("force set adapter"); -+ drm_log.debugf("conn->name: %s", conn->name); -+ drm_log.debugf("targetConnector: %s", targetConnector); -+ if (strcmp(conn->name, targetConnector) == 0) -+ { -+ drm_log.debugf("target was found!!!"); -+ drm_log.infof(" %s (%s)", conn->name, targetConnector); -+ best = conn; -+ } -+ } -+ - if (!force) { - if ((!best && drm->connector) || (best && best == drm->connector)) { - // Let's keep our current connector -@@ -2907,6 +2921,12 @@ static bool drm_set_crtc( struct drm_t *drm, struct crtc *crtc ) - return true; - } - -+void drm_set_prefered_connector( struct drm_t *drm, char* name ) -+{ -+ drm_log.infof("selecting prefered connector %s", name); -+ targetConnector = name; -+} -+ - bool drm_set_connector( struct drm_t *drm, struct connector *conn ) - { - drm_log.infof("selecting connector %s", conn->name); -diff --git a/src/drm.hpp b/src/drm.hpp -index 53fc540..739f51b 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -368,6 +368,7 @@ uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct - void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); - void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); - void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); -+void drm_set_prefered_connector( struct drm_t *drm, char* name ); - bool drm_set_connector( struct drm_t *drm, struct connector *conn ); - bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); - bool drm_set_refresh( struct drm_t *drm, int refresh ); -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 60f9828..aeef706 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -5719,6 +5719,74 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - } - } - } -+ if ( ev->atom == ctx->atoms.gamescopeConnectorControl ) -+ { -+ std::vector< uint32_t > connector_ctl; -+ bool hasConnectorCtrl = get_prop( ctx, ctx->root, ctx->atoms.gamescopeConnectorControl, connector_ctl ); -+ char* adapter_type; -+ if ( hasConnectorCtrl && connector_ctl.size() == 1 ) -+ { -+ switch (connector_ctl[0]) -+ { -+ case 0: -+ adapter_type = (char*)"eDP-1"; -+ break; -+ case 1: -+ adapter_type = (char*)"eDP-2"; -+ break; -+ case 2: -+ adapter_type = (char*)"eDP-3"; -+ break; -+ case 3: -+ adapter_type = (char*)"DP-1"; -+ break; -+ case 4: -+ adapter_type = (char*)"DP-2"; -+ break; -+ case 5: -+ adapter_type = (char*)"DP-3"; -+ break; -+ case 6: -+ adapter_type = (char*)"HDMI-A-1"; -+ break; -+ case 7: -+ adapter_type = (char*)"HDMI-A-2"; -+ break; -+ case 8: -+ adapter_type = (char*)"HDMI-A-3"; -+ break; -+ case 9: -+ adapter_type = (char*)"HDMI-B-1"; -+ break; -+ case 10: -+ adapter_type = (char*)"HDMI-B-2"; -+ break; -+ case 11: -+ adapter_type = (char*)"HDMI-B-3"; -+ break; -+ case 12: -+ adapter_type = (char*)"HDMI-C-1"; -+ break; -+ case 13: -+ adapter_type = (char*)"HDMI-C-2"; -+ break; -+ case 14: -+ adapter_type = (char*)"HDMI-C-3"; -+ break; -+ case 15: -+ adapter_type = (char*)"DSI-1"; -+ break; -+ case 16: -+ adapter_type = (char*)"DSI-2"; -+ break; -+ case 17: -+ adapter_type = (char*)"DSI-3"; -+ break; -+ } -+ g_DRM.out_of_date = 2; -+ drm_set_prefered_connector(&g_DRM, adapter_type); -+ } -+ } - if ( ev->atom == ctx->atoms.gamescopeFPSLimit ) - { - g_nSteamCompMgrTargetFPS = get_prop( ctx, ctx->root, ctx->atoms.gamescopeFPSLimit, 0 ); -@@ -7289,6 +7357,7 @@ void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_ - - ctx->atoms.gamescopeRotateControl = XInternAtom( ctx->dpy, "GAMESCOPE_ROTATE_CONTROL", false ); - ctx->atoms.gamescopeXWaylandModeControl = XInternAtom( ctx->dpy, "GAMESCOPE_XWAYLAND_MODE_CONTROL", false ); -+ ctx->atoms.gamescopeConnectorControl = XInternAtom(ctx->dpy, "GAMESCOPE_CONNECTOR_CONTROL", false ); - ctx->atoms.gamescopeFPSLimit = XInternAtom( ctx->dpy, "GAMESCOPE_FPS_LIMIT", false ); - ctx->atoms.gamescopeDynamicRefresh[DRM_SCREEN_TYPE_INTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH", false ); - ctx->atoms.gamescopeDynamicRefresh[DRM_SCREEN_TYPE_EXTERNAL] = XInternAtom( ctx->dpy, "GAMESCOPE_DYNAMIC_REFRESH_EXTERNAL", false ); -diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp -index 6231007..9dbc544 100644 ---- a/src/xwayland_ctx.hpp -+++ b/src/xwayland_ctx.hpp -@@ -149,6 +149,7 @@ struct xwayland_ctx_t final : public gamescope::IWaitable - Atom gamescopeRotateControl; - Atom gamescopeXWaylandModeControl; - -+ Atom gamescopeConnectorControl; - Atom gamescopeFPSLimit; - Atom gamescopeDynamicRefresh[DRM_SCREEN_TYPE_COUNT]; - Atom gamescopeLowLatency; --- -2.42.0 - - -From 8b94b4297324bddf48f3578592cdb6f9fe20e5a4 Mon Sep 17 00:00:00 2001 -From: Matthew Anderson -Date: Mon, 9 Oct 2023 11:21:11 -0500 -Subject: [PATCH 7/9] Use sysfs connector_ids for target device selection. - ---- - src/drm.cpp | 14 +++-------- - src/drm.hpp | 2 +- - src/steamcompmgr.cpp | 60 +------------------------------------------- - 3 files changed, 6 insertions(+), 70 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index d2f7677..59516c7 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -50,7 +50,7 @@ bool g_bDisplayTypeInternal = false; - bool g_bUseLayers = true; - bool g_bDebugLayers = false; - const char *g_sOutputName = nullptr; --char* targetConnector = (char*)"eDP-1"; -+uint32_t targetConnector; - - #ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP - #define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 -@@ -1248,13 +1248,8 @@ static bool setup_best_connector(struct drm_t *drm, bool force, bool initial) - - for (auto &kv : drm->connectors) { - struct connector *conn = &kv.second; -- drm_log.debugf("force set adapter"); -- drm_log.debugf("conn->name: %s", conn->name); -- drm_log.debugf("targetConnector: %s", targetConnector); -- if (strcmp(conn->name, targetConnector) == 0) -+ if ( conn->id == targetConnector) - { -- drm_log.debugf("target was found!!!"); -- drm_log.infof(" %s (%s)", conn->name, targetConnector); - best = conn; - } - } -@@ -2921,10 +2916,9 @@ static bool drm_set_crtc( struct drm_t *drm, struct crtc *crtc ) - return true; - } - --void drm_set_prefered_connector( struct drm_t *drm, char* name ) -+void drm_set_prefered_connector( struct drm_t *drm, uint32_t connector_type_id ) - { -- drm_log.infof("selecting prefered connector %s", name); -- targetConnector = name; -+ targetConnector = connector_type_id; - } - - bool drm_set_connector( struct drm_t *drm, struct connector *conn ) -diff --git a/src/drm.hpp b/src/drm.hpp -index 739f51b..6320bf7 100644 ---- a/src/drm.hpp -+++ b/src/drm.hpp -@@ -368,7 +368,7 @@ uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct - void drm_lock_fbid( struct drm_t *drm, uint32_t fbid ); - void drm_unlock_fbid( struct drm_t *drm, uint32_t fbid ); - void drm_drop_fbid( struct drm_t *drm, uint32_t fbid ); --void drm_set_prefered_connector( struct drm_t *drm, char* name ); -+void drm_set_prefered_connector( struct drm_t *drm, uint32_t connector_type_id ); - bool drm_set_connector( struct drm_t *drm, struct connector *conn ); - bool drm_set_mode( struct drm_t *drm, const drmModeModeInfo *mode ); - bool drm_set_refresh( struct drm_t *drm, int refresh ); -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index aeef706..9a3f495 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -5723,68 +5723,10 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) - { - std::vector< uint32_t > connector_ctl; - bool hasConnectorCtrl = get_prop( ctx, ctx->root, ctx->atoms.gamescopeConnectorControl, connector_ctl ); -- char* adapter_type; - if ( hasConnectorCtrl && connector_ctl.size() == 1 ) - { -- switch (connector_ctl[0]) -- { -- case 0: -- adapter_type = (char*)"eDP-1"; -- break; -- case 1: -- adapter_type = (char*)"eDP-2"; -- break; -- case 2: -- adapter_type = (char*)"eDP-3"; -- break; -- case 3: -- adapter_type = (char*)"DP-1"; -- break; -- case 4: -- adapter_type = (char*)"DP-2"; -- break; -- case 5: -- adapter_type = (char*)"DP-3"; -- break; -- case 6: -- adapter_type = (char*)"HDMI-A-1"; -- break; -- case 7: -- adapter_type = (char*)"HDMI-A-2"; -- break; -- case 8: -- adapter_type = (char*)"HDMI-A-3"; -- break; -- case 9: -- adapter_type = (char*)"HDMI-B-1"; -- break; -- case 10: -- adapter_type = (char*)"HDMI-B-2"; -- break; -- case 11: -- adapter_type = (char*)"HDMI-B-3"; -- break; -- case 12: -- adapter_type = (char*)"HDMI-C-1"; -- break; -- case 13: -- adapter_type = (char*)"HDMI-C-2"; -- break; -- case 14: -- adapter_type = (char*)"HDMI-C-3"; -- break; -- case 15: -- adapter_type = (char*)"DSI-1"; -- break; -- case 16: -- adapter_type = (char*)"DSI-2"; -- break; -- case 17: -- adapter_type = (char*)"DSI-3"; -- break; -- } - g_DRM.out_of_date = 2; -- drm_set_prefered_connector(&g_DRM, adapter_type); -+ drm_set_prefered_connector(&g_DRM, connector_ctl[0]); - } - } - if ( ev->atom == ctx->atoms.gamescopeFPSLimit ) --- -2.42.0 - - -From 40cb952642118fb983ec4e3deedd7410dbf69a07 Mon Sep 17 00:00:00 2001 -From: Matthew Anderson -Date: Tue, 16 Jan 2024 13:57:50 -0600 -Subject: [PATCH 8/9] Add edge gesture support to open Home and QAM - ---- - src/wlserver.cpp | 28 +++++++++++++++++++++++++++- - 1 file changed, 27 insertions(+), 1 deletion(-) - -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index dc37f97..e7fb7c9 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -2048,8 +2048,34 @@ void wlserver_touchmotion( double x, double y, int touch_id, uint32_t time ) - - if ( get_effective_touch_mode() == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) - { -+ bool start_gesture = false; - wlr_seat_touch_notify_motion( wlserver.wlr.seat, time, touch_id, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); -- } -+ -+ // Round the x-coordinate to the nearest whole number -+ uint32_t roundedCursorX = static_cast(std::round(wlserver.mouse_surface_cursorx)); -+ // Grab 2% of the display to be used for the edge range -+ double edge_range = g_nOutputWidth * 0.02; -+ -+ // if the touch cursor x position is less or equal to the range then start the gesture for left to right -+ if (roundedCursorX <= edge_range) { -+ start_gesture = true; -+ } -+ // if the touch cursor x position is the output width minus the edge range value then we are doing right to left -+ if (roundedCursorX >= g_nOutputWidth - edge_range) { -+ start_gesture = true; -+ } -+ // when the gesture is started and we are moving to the end of the edge range open home -+ if (start_gesture && roundedCursorX >= 1 && roundedCursorX <= edge_range) { -+ wl_log.infof("Detected Home gesture"); -+ wlserver_open_steam_menu(0); -+ start_gesture = false; -+ } -+ // when the gesture is started and we are moving from the output width minus the edge range to the output width open QAM -+ if (start_gesture && roundedCursorX >= g_nOutputWidth - edge_range && roundedCursorX <= g_nOutputWidth ) { -+ wl_log.infof("Detected QAM gesture"); -+ wlserver_open_steam_menu(1); -+ start_gesture = false; -+ } } - else if ( get_effective_touch_mode() == WLSERVER_TOUCH_CLICK_DISABLED ) - { - return; --- -2.42.0 - - -From f975e7a804100bb031fab0526d6714530381ec45 Mon Sep 17 00:00:00 2001 -From: Bouke Sybren Haarsma -Date: Wed, 3 Jan 2024 17:03:04 +0100 -Subject: [PATCH 9/9] remove hacky texture - -This will use more hardware planes, causing some devices to composite yeilding lower framerates ---- - src/steamcompmgr.cpp | 29 ----------------------------- - 1 file changed, 29 deletions(-) - -diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp -index 9a3f495..9e7eee5 100644 ---- a/src/steamcompmgr.cpp -+++ b/src/steamcompmgr.cpp -@@ -2540,35 +2540,6 @@ paint_all(bool async) - if ( overlay == global_focus.inputFocusWindow ) - update_touch_scaling( &frameInfo ); - } -- else -- { -- auto tex = vulkan_get_hacky_blank_texture(); -- if ( !BIsNested() && 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->colorspace = GAMESCOPE_APP_TEXTURE_COLORSPACE_LINEAR; -- layer->ctm = nullptr; -- layer->tex = tex; -- layer->fbid = tex->fbid(); -- -- layer->filter = GamescopeUpscaleFilter::NEAREST; -- layer->blackBorder = true; -- } -- } - - if (notification) - { --- -2.42.0 - diff --git a/spec_files/gamescope/40/crashfix.patch b/spec_files/gamescope/40/crashfix.patch deleted file mode 100644 index f50180e0d6..0000000000 --- a/spec_files/gamescope/40/crashfix.patch +++ /dev/null @@ -1,57 +0,0 @@ -From adaa5e064a6149e1f8122cc55589f60b6f58f7ea Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Cl=C3=A9ment=20Gu=C3=A9rin?= -Date: Tue, 19 Dec 2023 16:34:17 -0800 -Subject: [PATCH 1/2] drm: fix NPE while in headless mode - -caused by e810317 ---- - src/drm.cpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 59516c7..8759321 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -3337,6 +3337,7 @@ void drm_get_native_colorimetry( struct drm_t *drm, - *displayEOTF = EOTF_Gamma22; - *outputEncodingColorimetry = displaycolorimetry_709; - *outputEncodingEOTF = EOTF_Gamma22; -+ return; - } - - *displayColorimetry = drm->connector->metadata.colorimetry; --- -2.42.0 - - -From 08c56c656539c88b23d243869b00cf3dd33bcb1d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Cl=C3=A9ment=20Gu=C3=A9rin?= -Date: Wed, 20 Dec 2023 17:18:32 -0800 -Subject: [PATCH 2/2] drm: fix other headless NPE - -fixes a980d912 ---- - src/drm.cpp | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/src/drm.cpp b/src/drm.cpp -index 8759321..d632128 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -2584,10 +2584,10 @@ int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameI - assert( drm->req == nullptr ); - drm->req = drmModeAtomicAlloc(); - -- bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; -- bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; -- - if (drm->connector != nullptr) { -+ bool bConnectorSupportsHDR = drm->connector->metadata.supportsST2084; -+ bool bConnectorHDR = g_bOutputHDREnabled && bConnectorSupportsHDR; -+ - if (drm->connector->has_colorspace) { - drm->connector->pending.colorspace = ( bConnectorHDR ) ? DRM_MODE_COLORIMETRY_BT2020_RGB : DRM_MODE_COLORIMETRY_DEFAULT; - } --- -2.42.0 - diff --git a/spec_files/gamescope/40/gamescope.spec b/spec_files/gamescope/40/gamescope.spec deleted file mode 100644 index 96a52a7192..0000000000 --- a/spec_files/gamescope/40/gamescope.spec +++ /dev/null @@ -1,112 +0,0 @@ -%global libliftoff_minver 0.4.1 - -Name: gamescope -Version: 3.13.19 -Release: 1%{?dist}.bazzite.{{{ git_dir_version }}} -Summary: Micro-compositor for video games on Wayland - -License: BSD -URL: https://github.com/ValveSoftware/gamescope - -# Create stb.pc to satisfy dependency('stb') -Source1: stb.pc - -# https://github.com/ChimeraOS/gamescope -Source2: chimeraos.patch -Source3: crashfix.patch -Source4: add_720p_var.patch -Source5: touch_gestures_env.patch -Source6: legion_go.patch - -# https://github.com/ValveSoftware/gamescope/pull/1149 -Source7: 1149.patch - -BuildRequires: meson >= 0.54.0 -BuildRequires: ninja-build -BuildRequires: cmake -BuildRequires: gcc -BuildRequires: gcc-c++ -BuildRequires: glm-devel -BuildRequires: google-benchmark-devel -BuildRequires: libXmu-devel -BuildRequires: pkgconfig(libdisplay-info) -BuildRequires: pkgconfig(x11) -BuildRequires: pkgconfig(xdamage) -BuildRequires: pkgconfig(xcomposite) -BuildRequires: pkgconfig(xrender) -BuildRequires: pkgconfig(xext) -BuildRequires: pkgconfig(xfixes) -BuildRequires: pkgconfig(xxf86vm) -BuildRequires: pkgconfig(xtst) -BuildRequires: pkgconfig(xres) -BuildRequires: pkgconfig(libdrm) -BuildRequires: pkgconfig(vulkan) -BuildRequires: pkgconfig(wayland-scanner) -BuildRequires: pkgconfig(wayland-server) -BuildRequires: pkgconfig(wayland-protocols) >= 1.17 -BuildRequires: pkgconfig(xkbcommon) -BuildRequires: pkgconfig(sdl2) -BuildRequires: pkgconfig(libpipewire-0.3) -BuildRequires: (pkgconfig(wlroots) >= 0.17.0 with pkgconfig(wlroots) < 0.18.0) -BuildRequires: (pkgconfig(libliftoff) >= 0.4.1 with pkgconfig(libliftoff) < 0.5) -BuildRequires: pkgconfig(libcap) -BuildRequires: pkgconfig(hwdata) -BuildRequires: pkgconfig(xwayland) -BuildRequires: pkgconfig(xcursor) -BuildRequires: vkroots-devel -BuildRequires: /usr/bin/glslangValidator -BuildRequires: git -BuildRequires: stb_image-devel -BuildRequires: stb_image_write-devel -BuildRequires: stb_image_resize-devel - -# libliftoff hasn't bumped soname, but API/ABI has changed for 0.2.0 release -Requires: libliftoff%{?_isa} >= %{libliftoff_minver} -Requires: xorg-x11-server-Xwayland -Requires: google-benchmark -Requires: gamescope-libs = %{version}-%{release} -Recommends: mesa-dri-drivers -Recommends: mesa-vulkan-drivers - -%description -%{name} is the micro-compositor optimized for running video games on Wayland. - -%package libs -Summary: libs for %{name} -%description libs -%summary - -%prep -git clone --single-branch --branch %{version} https://github.com/ValveSoftware/gamescope.git -cd gamescope -git submodule update --init --recursive -mkdir -p pkgconfig -cp %{SOURCE1} pkgconfig/stb.pc -patch -Np1 < %{SOURCE2} -patch -Np1 < %{SOURCE3} -patch -Np1 < %{SOURCE4} -patch -Np1 < %{SOURCE5} -patch -Np1 < %{SOURCE6} -patch -Np1 < %{SOURCE7} - -%build -cd gamescope -export PKG_CONFIG_PATH=pkgconfig -%meson -Dpipewire=enabled -Denable_gamescope=true -Denable_gamescope_wsi_layer=true -Denable_openvr_support=true -Dforce_fallback_for=[] -%meson_build - -%install -cd gamescope -%meson_install --skip-subprojects - -%files -%license gamescope/LICENSE -%doc gamescope/README.md -%attr(0755, root, root) %caps(cap_sys_nice=eip) %{_bindir}/gamescope - -%files libs -%{_libdir}/*.so -%{_datadir}/vulkan/implicit_layer.d/ - -%changelog -{{{ git_dir_changelog }}} diff --git a/spec_files/gamescope/40/legion_go.patch b/spec_files/gamescope/40/legion_go.patch deleted file mode 100644 index e2a3ac8d8d..0000000000 --- a/spec_files/gamescope/40/legion_go.patch +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/src/drm.cpp b/src/drm.cpp -index acff5e5..fdf58ee 100644 ---- a/src/drm.cpp -+++ b/src/drm.cpp -@@ -101,6 +101,12 @@ static uint32_t galileo_display_rates[] = - 90, - }; - -+static uint32_t legion_go_display_rates[] = -+{ -+ 60, -+ 144, -+}; -+ - static uint32_t get_conn_display_info_flags(struct drm_t *drm, struct connector *connector) - { - if (!connector) -@@ -911,8 +917,11 @@ static void parse_edid( drm_t *drm, struct connector *conn) - conn->valid_display_rates = std::span(galileo_display_rates); - } else { - conn->is_galileo_display = 0; -- if ( conn->is_steam_deck_display ) -+ if ( conn->is_steam_deck_display ) { - conn->valid_display_rates = std::span(steam_deck_display_rates); -+ } else if ( strcmp(conn->make_pnp, "LEN") == 0 && strcmp(conn->model, "Go Display") == 0 ) { -+ conn->valid_display_rates = std::span(legion_go_display_rates); -+ } - } - - drm_hdr_parse_edid(drm, conn, edid); diff --git a/spec_files/gamescope/40/stb.pc b/spec_files/gamescope/40/stb.pc deleted file mode 100644 index 02c304a9fa..0000000000 --- a/spec_files/gamescope/40/stb.pc +++ /dev/null @@ -1,7 +0,0 @@ -prefix=/usr -includedir=${prefix}/include/stb - -Name: stb -Description: Single-file public domain libraries for C/C++ -Version: 0.1.0 -Cflags: -I${includedir} diff --git a/spec_files/gamescope/40/touch_gestures_env.patch b/spec_files/gamescope/40/touch_gestures_env.patch deleted file mode 100644 index 45befc222c..0000000000 --- a/spec_files/gamescope/40/touch_gestures_env.patch +++ /dev/null @@ -1,77 +0,0 @@ -diff --git a/src/wlserver.cpp b/src/wlserver.cpp -index d569ee5..0512ab0 100644 ---- a/src/wlserver.cpp -+++ b/src/wlserver.cpp -@@ -66,6 +66,8 @@ extern "C" { - - static LogScope wl_log("wlserver"); - -+extern bool env_to_bool(const char *env); -+ - struct wlserver_t wlserver = { - .touch_down_ids = {} - }; -@@ -2043,34 +2045,38 @@ void wlserver_touchmotion( double x, double y, int touch_id, uint32_t time ) - - if ( get_effective_touch_mode() == WLSERVER_TOUCH_CLICK_PASSTHROUGH ) - { -- bool start_gesture = false; - wlr_seat_touch_notify_motion( wlserver.wlr.seat, time, touch_id, wlserver.mouse_surface_cursorx, wlserver.mouse_surface_cursory ); - -- // Round the x-coordinate to the nearest whole number -- uint32_t roundedCursorX = static_cast(std::round(wlserver.mouse_surface_cursorx)); -- // Grab 2% of the display to be used for the edge range -- double edge_range = g_nOutputWidth * 0.02; -- -- // if the touch cursor x position is less or equal to the range then start the gesture for left to right -- if (roundedCursorX <= edge_range) { -- start_gesture = true; -- } -- // if the touch cursor x position is the output width minus the edge range value then we are doing right to left -- if (roundedCursorX >= g_nOutputWidth - edge_range) { -- start_gesture = true; -- } -- // when the gesture is started and we are moving to the end of the edge range open home -- if (start_gesture && roundedCursorX >= 1 && roundedCursorX <= edge_range) { -- wl_log.infof("Detected Home gesture"); -- wlserver_open_steam_menu(0); -- start_gesture = false; -+ if (!env_to_bool(getenv("GAMESCOPE_DISABLE_TOUCH_GESTURES"))) { -+ bool start_gesture = false; -+ -+ // Round the x-coordinate to the nearest whole number -+ uint32_t roundedCursorX = static_cast(std::round(wlserver.mouse_surface_cursorx)); -+ // Grab 2% of the display to be used for the edge range -+ double edge_range = g_nOutputWidth * 0.02; -+ -+ // if the touch cursor x position is less or equal to the range then start the gesture for left to right -+ if (roundedCursorX <= edge_range) { -+ start_gesture = true; -+ } -+ // if the touch cursor x position is the output width minus the edge range value then we are doing right to left -+ if (roundedCursorX >= g_nOutputWidth - edge_range) { -+ start_gesture = true; -+ } -+ // when the gesture is started and we are moving to the end of the edge range open home -+ if (start_gesture && roundedCursorX >= 1 && roundedCursorX <= edge_range) { -+ wl_log.infof("Detected Home gesture"); -+ wlserver_open_steam_menu(0); -+ start_gesture = false; -+ } -+ // when the gesture is started and we are moving from the output width minus the edge range to the output width open QAM -+ if (start_gesture && roundedCursorX >= g_nOutputWidth - edge_range && roundedCursorX <= g_nOutputWidth ) { -+ wl_log.infof("Detected QAM gesture"); -+ wlserver_open_steam_menu(1); -+ start_gesture = false; -+ } - } -- // when the gesture is started and we are moving from the output width minus the edge range to the output width open QAM -- if (start_gesture && roundedCursorX >= g_nOutputWidth - edge_range && roundedCursorX <= g_nOutputWidth ) { -- wl_log.infof("Detected QAM gesture"); -- wlserver_open_steam_menu(1); -- start_gesture = false; -- } } -+ } - else if ( get_effective_touch_mode() == WLSERVER_TOUCH_CLICK_DISABLED ) - { - return; diff --git a/spec_files/gamescope/39/add_720p_var.patch b/spec_files/gamescope/add_720p_var.patch similarity index 100% rename from spec_files/gamescope/39/add_720p_var.patch rename to spec_files/gamescope/add_720p_var.patch diff --git a/spec_files/gamescope/39/chimeraos.patch b/spec_files/gamescope/chimeraos.patch similarity index 100% rename from spec_files/gamescope/39/chimeraos.patch rename to spec_files/gamescope/chimeraos.patch diff --git a/spec_files/gamescope/39/crashfix.patch b/spec_files/gamescope/crashfix.patch similarity index 100% rename from spec_files/gamescope/39/crashfix.patch rename to spec_files/gamescope/crashfix.patch diff --git a/spec_files/gamescope/39/gamescope.spec b/spec_files/gamescope/gamescope.spec similarity index 100% rename from spec_files/gamescope/39/gamescope.spec rename to spec_files/gamescope/gamescope.spec diff --git a/spec_files/gamescope/39/legion_go.patch b/spec_files/gamescope/legion_go.patch similarity index 100% rename from spec_files/gamescope/39/legion_go.patch rename to spec_files/gamescope/legion_go.patch diff --git a/spec_files/gamescope/39/stb.pc b/spec_files/gamescope/stb.pc similarity index 100% rename from spec_files/gamescope/39/stb.pc rename to spec_files/gamescope/stb.pc diff --git a/spec_files/gamescope/39/touch_gestures_env.patch b/spec_files/gamescope/touch_gestures_env.patch similarity index 100% rename from spec_files/gamescope/39/touch_gestures_env.patch rename to spec_files/gamescope/touch_gestures_env.patch From 21397351e7267fcc58731a698b7b19747034ed4c Mon Sep 17 00:00:00 2001 From: HikariKnight <2557889+HikariKnight@users.noreply.github.com> Date: Tue, 19 Mar 2024 05:43:36 +0100 Subject: [PATCH 07/12] feat(just): Enable virtualization for deck hardware (#895) * feat(just): Enable virtualization for deck hardware IOMMU stays unsupported * chore(just): add warning that virtualization is not really supported on the deck. --- .../share/ublue-os/just/84-bazzite-virt.just | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/system_files/desktop/shared/usr/share/ublue-os/just/84-bazzite-virt.just b/system_files/desktop/shared/usr/share/ublue-os/just/84-bazzite-virt.just index 54cf06e299..9848fcc5fa 100644 --- a/system_files/desktop/shared/usr/share/ublue-os/just/84-bazzite-virt.just +++ b/system_files/desktop/shared/usr/share/ublue-os/just/84-bazzite-virt.just @@ -6,16 +6,15 @@ setup-virtualization ACTION="": source /usr/lib/ujust/ujust.sh # Check if we are running on a Steam Deck if /usr/libexec/hardware/valve-hardware; then - echo "Virtualization is not supported on Steam Deck" - exit 0 - else - if [ "$(systemctl is-enabled libvirtd.service)" == "disabled" ]; then - echo "${b}libvirtd${n} service is ${red}disabled${n}!" - echo "${green}enabling${n} and starting libvirtd" - echo "If virt-manager says libvirtd.sock is not available after a big update, re-run this command." - sudo systemctl enable --now libvirtd 2> /dev/null - echo "Press ESC if you want to exit and do not need to do anything" - fi + echo "${red}${b}WARNING${n}: Virtualization is not properly supported on Steam Deck by Valve" + echo "Use at your own risk and performance may not be ideal." + fi + if [ "$(systemctl is-enabled libvirtd.service)" == "disabled" ]; then + echo "${b}libvirtd${n} service is ${red}disabled${n}!" + echo "${green}enabling${n} and starting libvirtd" + echo "If virt-manager says libvirtd.sock is not available after a big update, re-run this command." + sudo systemctl enable --now libvirtd 2> /dev/null + echo "Press ESC if you want to exit and do not need to do anything" fi OPTION={{ ACTION }} if [ "$OPTION" == "help" ]; then @@ -41,7 +40,7 @@ setup-virtualization ACTION="": "Autocreate Looking-Glass shm" \ ) fi - if [[ "${OPTION,,}" =~ ^enable[[:space:]]virt ]]; then + if [[ "${OPTION,,}" =~ ^enable(|[[:space:]]virtualization) ]]; then virt_test=$(rpm-ostree status | grep -A 4 "●" | grep "virt-manager") if [[ -z ${virt_test} ]]; then echo "Installing QEMU and virt-manager..." @@ -51,7 +50,7 @@ setup-virtualization ACTION="": --append-if-missing="kvm.report_ignored_msrs=0" echo 'Please re-run "ujust setup-virtualization" after the reboot to enable libvirtd service' fi - elif [[ "${OPTION,,}" =~ ^disable[[:space:]]virt ]]; then + elif [[ "${OPTION,,}" =~ ^disable(|[[:space:]]virtualization) ]]; then virt_test=$(rpm-ostree status | grep -A 4 "●" | grep "virt-manager") if [[ ${virt_test} ]]; then if [ "$(systemctl is-enabled libvirtd.service)" == "enabled" ]; then @@ -66,6 +65,11 @@ setup-virtualization ACTION="": echo 'Please re-run "ujust enable-virtualization" after the reboot to finish setup' fi elif [[ "${OPTION,,}" =~ (^enable[[:space:]]vfio|vfio-on) ]]; then + # Check if we are running on a Steam Deck + if /usr/libexec/hardware/valve-hardware; then + echo "IOMMU is not supported on Steam Deck" + exit 0 + fi echo "Enabling VFIO..." VIRT_TEST=$(rpm-ostree kargs) CPU_VENDOR=$(grep "vendor_id" "/proc/cpuinfo" | uniq | awk -F": " '{ print $2 }') @@ -95,6 +99,11 @@ setup-virtualization ACTION="": fi fi elif [[ "${OPTION,,}" =~ (^disable[[:space:]]vfio|vfio-off) ]]; then + # Check if we are running on a Steam Deck + if /usr/libexec/hardware/valve-hardware; then + echo "IOMMU is not supported on Steam Deck" + exit 0 + fi echo "" echo "Make sure you have ${b}disabled autostart of all VMs using VFIO${n} before continuing!" CONFIRM=$(Choose Cancel Continue) @@ -122,6 +131,11 @@ setup-virtualization ACTION="": $VFIO_IDS_KARG fi elif [[ "${OPTION,,}" =~ shm ]]; then + # Check if we are running on a Steam Deck + if /usr/libexec/hardware/valve-hardware; then + echo "IOMMU is not supported on Steam Deck" + exit 0 + fi echo "Creating tmpfile definition for shm file in /etc/tmpfiles.d/" sudo bash -c "tee << LOOKING_GLASS_TMP > /etc/tmpfiles.d/10-looking-glass.conf # Type Path Mode UID GID Age Argument From 0725bfd90b3e33db6942b6020a51b1c00b8bc385 Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Tue, 19 Mar 2024 00:15:33 -0700 Subject: [PATCH 08/12] chore: Add balanced-no-pstate tuned profile for CPUs without pstate support. Uses schedutil instead of powersave. --- .../lib/tuned/balanced-no-pstate/tuned.conf | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 system_files/desktop/shared/usr/lib/tuned/balanced-no-pstate/tuned.conf diff --git a/system_files/desktop/shared/usr/lib/tuned/balanced-no-pstate/tuned.conf b/system_files/desktop/shared/usr/lib/tuned/balanced-no-pstate/tuned.conf new file mode 100644 index 0000000000..3c78bf398c --- /dev/null +++ b/system_files/desktop/shared/usr/lib/tuned/balanced-no-pstate/tuned.conf @@ -0,0 +1,25 @@ +# +# tuned configuration +# +[main] + +[cpu] +priority = 10 +governor = schedutil +energy_perf_bias = normal +energy_performance_preference = balance_performance + +[acpi] +platform_profile = balanced + +[audio] +timeout = 10 + +[video] +radeon_powersave = dpm-balanced, auto + +[disk] + +[scsi_host] +alpm = medium_power + From d492edbf9ceded9d21eb0152436773bff7a5097c Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Tue, 19 Mar 2024 00:58:59 -0700 Subject: [PATCH 09/12] chore: Minor profile syntax cleanup, add summary --- .../lib/tuned/balanced-no-pstate/tuned.conf | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/system_files/desktop/shared/usr/lib/tuned/balanced-no-pstate/tuned.conf b/system_files/desktop/shared/usr/lib/tuned/balanced-no-pstate/tuned.conf index 3c78bf398c..4d2c3d8487 100644 --- a/system_files/desktop/shared/usr/lib/tuned/balanced-no-pstate/tuned.conf +++ b/system_files/desktop/shared/usr/lib/tuned/balanced-no-pstate/tuned.conf @@ -2,24 +2,27 @@ # tuned configuration # [main] +summary=General tuned profile for CPUs without p-state support [cpu] -priority = 10 -governor = schedutil -energy_perf_bias = normal -energy_performance_preference = balance_performance +priority=10 +governor=schedutil +energy_perf_bias=normal +energy_performance_preference=balance_performance [acpi] -platform_profile = balanced +platform_profile=balanced [audio] -timeout = 10 +timeout=10 [video] -radeon_powersave = dpm-balanced, auto +radeon_powersave=dpm-balanced,auto [disk] +# Comma separated list of devices, all devices if commented out. +# devices=sda [scsi_host] -alpm = medium_power +alpm=medium_power From 09222912952c46715cb46afc029c1345f6d33d5b Mon Sep 17 00:00:00 2001 From: leechgrrl <58349791+leechgrrl@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:14:34 +1000 Subject: [PATCH 10/12] Ensure the libvirt group exists in /etc/group so can actually work (#897) --- .../shared/usr/share/ublue-os/just/84-bazzite-virt.just | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system_files/desktop/shared/usr/share/ublue-os/just/84-bazzite-virt.just b/system_files/desktop/shared/usr/share/ublue-os/just/84-bazzite-virt.just index 9848fcc5fa..494a213a91 100644 --- a/system_files/desktop/shared/usr/share/ublue-os/just/84-bazzite-virt.just +++ b/system_files/desktop/shared/usr/share/ublue-os/just/84-bazzite-virt.just @@ -144,5 +144,8 @@ setup-virtualization ACTION="": echo "Adding SELinux context record for /dev/shm/looking-glass" sudo semanage fcontext -a -t svirt_tmpfs_t /dev/shm/looking-glass elif [[ "${OPTION,,}" =~ group ]]; then + if ! grep -q "^libvirt" /etc/group; then + grep '^libvirt' /usr/lib/group | sudo tee -a /etc/group > /dev/null + fi sudo usermod -aG libvirt $USER fi From bae2fae95b7adcaf2309f9c98637efac3f51c65d Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Tue, 19 Mar 2024 12:47:18 -0700 Subject: [PATCH 11/12] fix: Correct issues with newest version of distrobox --- .../OpenTabletDriver/opentabletdriver.service | 2 +- .../resilio_sync/fedora-resilio-sync.service | 2 +- .../usr/share/ublue-os/just/82-bazzite-apps.just | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/post_install_files/OpenTabletDriver/opentabletdriver.service b/post_install_files/OpenTabletDriver/opentabletdriver.service index d0c5b68873..6b89011c98 100644 --- a/post_install_files/OpenTabletDriver/opentabletdriver.service +++ b/post_install_files/OpenTabletDriver/opentabletdriver.service @@ -6,7 +6,7 @@ ConditionEnvironment=|WAYLAND_DISPLAY ConditionEnvironment=|DISPLAY [Service] -ExecStart=/usr/bin/distrobox-enter -n arch -- ' /usr/bin/otd-daemon' +ExecStart=/usr/bin/distrobox-enter -n arch -- /usr/bin/otd-daemon Restart=always RestartSec=3 diff --git a/post_install_files/resilio_sync/fedora-resilio-sync.service b/post_install_files/resilio_sync/fedora-resilio-sync.service index c09dd80af3..e307ff111d 100644 --- a/post_install_files/resilio_sync/fedora-resilio-sync.service +++ b/post_install_files/resilio_sync/fedora-resilio-sync.service @@ -4,7 +4,7 @@ After=network.target [Service] Type=simple -ExecStart=/usr/bin/distrobox-enter -n fedora -- ' /usr/bin/rslsync --nodaemon' +ExecStart=/usr/bin/distrobox-enter -n fedora -- /usr/bin/rslsync --nodaemon [Install] WantedBy=default.target diff --git a/system_files/desktop/shared/usr/share/ublue-os/just/82-bazzite-apps.just b/system_files/desktop/shared/usr/share/ublue-os/just/82-bazzite-apps.just index 1eb5103413..fa55300925 100644 --- a/system_files/desktop/shared/usr/share/ublue-os/just/82-bazzite-apps.just +++ b/system_files/desktop/shared/usr/share/ublue-os/just/82-bazzite-apps.just @@ -96,9 +96,9 @@ install-solaar: distrobox-check-fedora # Install Resilio Sync, a file synchronization utility powered by BitTorrent install-resilio-sync: distrobox-check-fedora - distrobox-enter -n fedora -- 'sudo rpm --import https://linux-packages.resilio.com/resilio-sync/key.asc' && \ - distrobox-enter -n fedora -- 'sudo wget https://raw.githubusercontent.com/ublue-os/bazzite/main/post_install_files/resilio_sync/resilio-sync.repo -O /etc/yum.repos.d/resilio-sync.repo' && \ - distrobox-enter -n fedora -- 'sudo dnf install -y resilio-sync' && \ + distrobox-enter -n fedora -- bash -c 'sudo rpm --import https://linux-packages.resilio.com/resilio-sync/key.asc' && \ + distrobox-enter -n fedora -- bash -c 'sudo wget https://raw.githubusercontent.com/ublue-os/bazzite/main/post_install_files/resilio_sync/resilio-sync.repo -O /etc/yum.repos.d/resilio-sync.repo' && \ + distrobox-enter -n fedora -- bash -c 'sudo dnf install -y resilio-sync' && \ mkdir -p ~/.config/systemd/user/ && \ rm -f ~/.config/systemd/user/fedora-resilio-sync.service && \ wget https://raw.githubusercontent.com/ublue-os/bazzite/main/post_install_files/resilio_sync/fedora-resilio-sync.service -O ~/.config/systemd/user/fedora-resilio-sync.service && \ @@ -118,13 +118,13 @@ install-opentabletdriver: if grep -qvz "arch" <<< $(distrobox list); then \ Assemble noconfirmcreate "" "arch"; \ fi && \ - distrobox enter -n arch -- ' paru -S opentabletdriver --noconfirm' && \ + distrobox enter -n arch -- bash -c 'paru -S opentabletdriver --noconfirm' && \ mkdir -p ~/.config/systemd/user/ && \ rm -f ~/.config/systemd/user/arch-opentabletdriver.service && \ wget https://raw.githubusercontent.com/ublue-os/bazzite/main/post_install_files/OpenTabletDriver/opentabletdriver.service -O ~/.config/systemd/user/arch-opentabletdriver.service && \ systemctl --user daemon-reload && \ systemctl enable --user --now arch-opentabletdriver.service && \ - distrobox enter -n arch -- 'distrobox-export --app otd-gui' + distrobox enter -n arch -- bash -c 'distrobox-export --app otd-gui' # Create fedora distrobox if it doesn't exist [private] From d2594b6857cc59605121f76089069cae83282956 Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Tue, 19 Mar 2024 13:20:25 -0700 Subject: [PATCH 12/12] chore: Add more hardware to SimpleDeckyTDP check --- .../usr/libexec/hardware/simpledeckytdp-supported-hardware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system_files/desktop/shared/usr/libexec/hardware/simpledeckytdp-supported-hardware b/system_files/desktop/shared/usr/libexec/hardware/simpledeckytdp-supported-hardware index 096ebbf7a7..ad300f4d73 100755 --- a/system_files/desktop/shared/usr/libexec/hardware/simpledeckytdp-supported-hardware +++ b/system_files/desktop/shared/usr/libexec/hardware/simpledeckytdp-supported-hardware @@ -1,7 +1,7 @@ #!/usr/bin/bash # Returns true for hardware that is supported by SimpleDeckyTDP SYS_ID="$(cat /sys/devices/virtual/dmi/id/product_name)" -if [[ ":ROG Ally RC71L_RC71L:ROG Ally RC71L:83E1:G1618-04:G1617-01:G1619-05:AIR Plus:AIR:" =~ ":$SYS_ID:" ]]; then +if [[ ":ROG Ally RC71L_RC71L:ROG Ally RC71L:83E1:G1618-04:G1619-04:G1617-01:G1619-05:AIR Plus:AIR:SLIDE:" =~ ":$SYS_ID:" ]]; then exit 0 else exit 1