gamescope/src/steamcompmgr.cpp

8213 lines
238 KiB
C++

/*
* Based on xcompmgr by Keith Packard et al.
* http://cgit.freedesktop.org/xorg/app/xcompmgr/
* Original xcompmgr legal notices follow:
*
* Copyright © 2003 Keith Packard
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of Keith Packard not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. Keith Packard makes no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* Modified by Matthew Hawn. I don't know what to say here so follow what it
* says above. Not that I can really do anything about it
*/
#include "xwayland_ctx.hpp"
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/xfixeswire.h>
#include <cstdint>
#include <drm_mode.h>
#include <memory>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <atomic>
#include <vector>
#include <algorithm>
#include <array>
#include <iostream>
#include <fstream>
#include <string>
#include <queue>
#include <variant>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#if defined(__linux__)
#include <sys/prctl.h>
#elif defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/procctl.h>
#endif
#include <sys/socket.h>
#include <sys/resource.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <spawn.h>
#include <signal.h>
#include <linux/input-event-codes.h>
#include <X11/Xmu/CurUtil.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"
#if HAVE_PIPEWIRE
#include "pipewire.hpp"
#endif
#if HAVE_OPENVR
#include "vr_session.hpp"
#endif
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image.h>
#include <stb_image_write.h>
#define GPUVIS_TRACE_IMPLEMENTATION
#include "gpuvis_trace_utils.h"
static LogScope xwm_log("xwm");
bool g_bWasPartialComposite = false;
///
// Color Mgmt
//
gamescope_color_mgmt_tracker_t g_ColorMgmt{};
static gamescope_color_mgmt_luts g_ColorMgmtLutsOverride[ EOTF_Count ];
static lut3d_t g_ColorMgmtLooks[ EOTF_Count ];
gamescope_color_mgmt_luts g_ColorMgmtLuts[ EOTF_Count ];
gamescope_color_mgmt_luts g_ScreenshotColorMgmtLuts[ EOTF_Count ];
static lut1d_t g_tmpLut1d;
static lut3d_t g_tmpLut3d;
bool g_bForceHDRSupportDebug = false;
extern float g_flInternalDisplayBrightnessNits;
extern float g_flHDRItmSdrNits;
extern float g_flHDRItmTargetNits;
extern std::atomic<uint64_t> g_lastVblank;
static std::shared_ptr<wlserver_ctm> s_scRGB709To2020Matrix;
std::string clipboard;
std::string primarySelection;
std::string g_reshade_effect{};
uint32_t g_reshade_technique_idx = 0;
bool g_bSteamIsActiveWindow = false;
uint64_t timespec_to_nanos(struct timespec& spec)
{
return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec;
}
static void
update_runtime_info();
static uint64_t g_SteamCompMgrLimitedAppRefreshCycle = 16'666'666;
static uint64_t g_SteamCompMgrAppRefreshCycle = 16'666'666;
static const gamescope_color_mgmt_t k_ScreenshotColorMgmt =
{
.enabled = true,
.displayColorimetry = displaycolorimetry_709,
.displayEOTF = EOTF_Gamma22,
.outputEncodingColorimetry = displaycolorimetry_709,
.outputEncodingEOTF = EOTF_Gamma22,
};
//#define COLOR_MGMT_MICROBENCH
// sudo cpupower frequency-set --governor performance
static void
create_color_mgmt_luts(const gamescope_color_mgmt_t& newColorMgmt, gamescope_color_mgmt_luts outColorMgmtLuts[ EOTF_Count ])
{
const displaycolorimetry_t& displayColorimetry = newColorMgmt.displayColorimetry;
const displaycolorimetry_t& outputEncodingColorimetry = newColorMgmt.outputEncodingColorimetry;
for ( uint32_t nInputEOTF = 0; nInputEOTF < EOTF_Count; nInputEOTF++ )
{
if (!outColorMgmtLuts[nInputEOTF].vk_lut1d)
outColorMgmtLuts[nInputEOTF].vk_lut1d = vulkan_create_1d_lut(s_nLutSize1d);
if (!outColorMgmtLuts[nInputEOTF].vk_lut3d)
outColorMgmtLuts[nInputEOTF].vk_lut3d = vulkan_create_3d_lut(s_nLutEdgeSize3d, s_nLutEdgeSize3d, s_nLutEdgeSize3d);
if ( g_ColorMgmtLutsOverride[nInputEOTF].HasLuts() )
{
memcpy(g_ColorMgmtLuts[nInputEOTF].lut1d, g_ColorMgmtLutsOverride[nInputEOTF].lut1d, sizeof(g_ColorMgmtLutsOverride[nInputEOTF].lut1d));
memcpy(g_ColorMgmtLuts[nInputEOTF].lut3d, g_ColorMgmtLutsOverride[nInputEOTF].lut3d, sizeof(g_ColorMgmtLutsOverride[nInputEOTF].lut3d));
}
else
{
displaycolorimetry_t inputColorimetry{};
colormapping_t colorMapping{};
tonemapping_t tonemapping{};
tonemapping.bUseShaper = true;
EOTF inputEOTF = static_cast<EOTF>( nInputEOTF );
float flGain = 1.f;
lut3d_t * pLook = g_ColorMgmtLooks[nInputEOTF].lutEdgeSize > 0 ? &g_ColorMgmtLooks[nInputEOTF] : nullptr;
if ( inputEOTF == EOTF_Gamma22 )
{
flGain = newColorMgmt.flSDRInputGain;
if ( newColorMgmt.outputEncodingEOTF == EOTF_Gamma22 )
{
// G22 -> G22. Does not matter what the g22 mult is
tonemapping.g22_luminance = 1.f;
// xwm_log.infof("G22 -> G22");
}
else if ( newColorMgmt.outputEncodingEOTF == EOTF_PQ )
{
// G22 -> PQ. SDR content going on an HDR output
tonemapping.g22_luminance = newColorMgmt.flSDROnHDRBrightness;
// xwm_log.infof("G22 -> PQ");
}
// The final display colorimetry is used to build the output mapping, as we want a gamut-aware handling
// for sdrGamutWideness indepdendent of the output encoding (for SDR data), and when mapping SDR -> PQ output
// we only want to utilize a portion of the gamut the actual display can reproduce
buildSDRColorimetry( &inputColorimetry, &colorMapping, newColorMgmt.sdrGamutWideness, displayColorimetry );
}
else if ( inputEOTF == EOTF_PQ )
{
flGain = newColorMgmt.flHDRInputGain;
if ( newColorMgmt.outputEncodingEOTF == EOTF_Gamma22 )
{
// PQ -> G22 Leverage the display's native brightness
tonemapping.g22_luminance = newColorMgmt.flInternalDisplayBrightness;
// Determine the tonemapping parameters
// Use the external atoms if provided
tonemap_info_t source = newColorMgmt.hdrTonemapSourceMetadata;
tonemap_info_t dest = newColorMgmt.hdrTonemapDisplayMetadata;
// Otherwise, rely on the Vulkan source info and the EDID
// TODO: If source is invalid, use the provided metadata.
// TODO: If hdrTonemapDisplayMetadata is invalid, use the one provided by the display
// Adjust the source brightness range by the requested HDR input gain
dest.flBlackPointNits /= flGain;
dest.flWhitePointNits /= flGain;
if ( source.BIsValid() && dest.BIsValid() )
{
tonemapping.eOperator = newColorMgmt.hdrTonemapOperator;
tonemapping.eetf2390.init( source, newColorMgmt.hdrTonemapDisplayMetadata );
}
else
{
tonemapping.eOperator = ETonemapOperator_None;
}
/*
xwm_log.infof("PQ -> 2.2 - g22_luminance %f gain %f", tonemapping.g22_luminance, flGain );
xwm_log.infof("source %f %f", source.flBlackPointNits, source.flWhitePointNits );
xwm_log.infof("dest %f %f", dest.flBlackPointNits, dest.flWhitePointNits );
xwm_log.infof("operator %d", (int) tonemapping.eOperator );*/
}
else if ( newColorMgmt.outputEncodingEOTF == EOTF_PQ )
{
// PQ -> PQ passthrough (though this does apply gain)
// TODO: should we manipulate the output static metadata to reflect the gain factor?
tonemapping.g22_luminance = 1.f;
// xwm_log.infof("PQ -> PQ");
}
buildPQColorimetry( &inputColorimetry, &colorMapping, displayColorimetry );
}
calcColorTransform( &g_tmpLut1d, s_nLutSize1d, &g_tmpLut3d, s_nLutEdgeSize3d, inputColorimetry, inputEOTF,
outputEncodingColorimetry, newColorMgmt.outputEncodingEOTF,
newColorMgmt.outputVirtualWhite, newColorMgmt.chromaticAdaptationMode,
colorMapping, newColorMgmt.nightmode, tonemapping, pLook, flGain );
// Create quantized output luts
for ( size_t i=0, end = g_tmpLut1d.dataR.size(); i<end; ++i )
{
outColorMgmtLuts[nInputEOTF].lut1d[4*i+0] = drm_quantize_lut_value( g_tmpLut1d.dataR[i] );
outColorMgmtLuts[nInputEOTF].lut1d[4*i+1] = drm_quantize_lut_value( g_tmpLut1d.dataG[i] );
outColorMgmtLuts[nInputEOTF].lut1d[4*i+2] = drm_quantize_lut_value( g_tmpLut1d.dataB[i] );
outColorMgmtLuts[nInputEOTF].lut1d[4*i+3] = 0;
}
for ( size_t i=0, end = g_tmpLut3d.data.size(); i<end; ++i )
{
outColorMgmtLuts[nInputEOTF].lut3d[4*i+0] = drm_quantize_lut_value( g_tmpLut3d.data[i].r );
outColorMgmtLuts[nInputEOTF].lut3d[4*i+1] = drm_quantize_lut_value( g_tmpLut3d.data[i].g );
outColorMgmtLuts[nInputEOTF].lut3d[4*i+2] = drm_quantize_lut_value( g_tmpLut3d.data[i].b );
outColorMgmtLuts[nInputEOTF].lut3d[4*i+3] = 0;
}
}
outColorMgmtLuts[nInputEOTF].bHasLut1D = true;
outColorMgmtLuts[nInputEOTF].bHasLut3D = true;
vulkan_update_luts(outColorMgmtLuts[nInputEOTF].vk_lut1d, outColorMgmtLuts[nInputEOTF].vk_lut3d, outColorMgmtLuts[nInputEOTF].lut1d, outColorMgmtLuts[nInputEOTF].lut3d);
}
}
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_bForceHDR10OutputDebug = false;
bool g_bHDREnabled = false;
bool g_bHDRItmEnable = false;
int g_nCurrentRefreshRate_CachedValue = 0;
static void
update_color_mgmt()
{
// update pending native display colorimetry
if ( !BIsNested() )
{
drm_get_native_colorimetry( &g_DRM,
&g_ColorMgmt.pending.displayColorimetry, &g_ColorMgmt.pending.displayEOTF,
&g_ColorMgmt.pending.outputEncodingColorimetry, &g_ColorMgmt.pending.outputEncodingEOTF );
}
else if (g_bForceHDR10OutputDebug)
{
g_ColorMgmt.pending.displayColorimetry = displaycolorimetry_2020;
g_ColorMgmt.pending.displayEOTF = EOTF_PQ;
g_ColorMgmt.pending.outputEncodingColorimetry = displaycolorimetry_2020;
g_ColorMgmt.pending.outputEncodingEOTF = EOTF_PQ;
}
else
{
g_ColorMgmt.pending.displayColorimetry = displaycolorimetry_709;
g_ColorMgmt.pending.displayEOTF = EOTF_Gamma22;
g_ColorMgmt.pending.outputEncodingColorimetry = displaycolorimetry_709;
g_ColorMgmt.pending.outputEncodingEOTF = EOTF_Gamma22;
}
#ifdef COLOR_MGMT_MICROBENCH
struct timespec t0, t1;
#else
// check if any part of our color mgmt stack is dirty
if ( g_ColorMgmt.pending == g_ColorMgmt.current && g_ColorMgmt.serial != 0 )
return;
#endif
#ifdef COLOR_MGMT_MICROBENCH
clock_gettime(CLOCK_MONOTONIC_RAW, &t0);
#endif
if (g_ColorMgmt.pending.enabled)
{
create_color_mgmt_luts(g_ColorMgmt.pending, g_ColorMgmtLuts);
}
else
{
for ( uint32_t i = 0; i < EOTF_Count; i++ )
g_ColorMgmtLuts[i].reset();
}
#ifdef COLOR_MGMT_MICROBENCH
clock_gettime(CLOCK_MONOTONIC_RAW, &t1);
#endif
#ifdef COLOR_MGMT_MICROBENCH
double delta = (timespec_to_nanos(t1) - timespec_to_nanos(t0)) / 1000000.0;
static uint32_t iter = 0;
static const uint32_t iter_count = 120;
static double accum = 0;
accum += delta;
if (iter++ == iter_count)
{
printf("update_color_mgmt: %.3fms\n", accum / iter_count);
iter = 0;
accum = 0;
}
#endif
static uint32_t s_NextColorMgmtSerial = 0;
g_ColorMgmt.serial = ++s_NextColorMgmtSerial;
g_ColorMgmt.current = g_ColorMgmt.pending;
}
static void
update_screenshot_color_mgmt()
{
create_color_mgmt_luts(k_ScreenshotColorMgmt, g_ScreenshotColorMgmtLuts);
}
bool set_color_sdr_gamut_wideness( float flVal )
{
if ( g_ColorMgmt.pending.sdrGamutWideness == flVal )
return false;
g_ColorMgmt.pending.sdrGamutWideness = flVal;
return g_ColorMgmt.pending.enabled;
}
bool set_internal_display_brightness( float flVal )
{
if ( flVal < 1.f )
{
flVal = 500.f;
}
if ( g_ColorMgmt.pending.flInternalDisplayBrightness == flVal )
return false;
g_ColorMgmt.pending.flInternalDisplayBrightness = flVal;
g_flInternalDisplayBrightnessNits = flVal;
return g_ColorMgmt.pending.enabled;
}
bool set_sdr_on_hdr_brightness( float flVal )
{
if ( flVal < 1.f )
{
flVal = 203.f;
}
if ( g_ColorMgmt.pending.flSDROnHDRBrightness == flVal )
return false;
g_ColorMgmt.pending.flSDROnHDRBrightness = flVal;
return g_ColorMgmt.pending.enabled;
}
bool set_hdr_input_gain( float flVal )
{
if ( flVal < 0.f )
{
flVal = 1.f;
}
if ( g_ColorMgmt.pending.flHDRInputGain == flVal )
return false;
g_ColorMgmt.pending.flHDRInputGain = flVal;
return g_ColorMgmt.pending.enabled;
}
bool set_sdr_input_gain( float flVal )
{
if ( flVal < 0.f )
{
flVal = 1.f;
}
if ( g_ColorMgmt.pending.flSDRInputGain == flVal )
return false;
g_ColorMgmt.pending.flSDRInputGain = flVal;
return g_ColorMgmt.pending.enabled;
}
bool set_color_nightmode( const nightmode_t &nightmode )
{
if ( g_ColorMgmt.pending.nightmode == nightmode )
return false;
g_ColorMgmt.pending.nightmode = nightmode;
return g_ColorMgmt.pending.enabled;
}
bool set_color_mgmt_enabled( bool bEnabled )
{
if ( g_ColorMgmt.pending.enabled == bEnabled )
return false;
g_ColorMgmt.pending.enabled = bEnabled;
return true;
}
static std::shared_ptr<CVulkanTexture> s_MuraCorrectionImage[DRM_SCREEN_TYPE_COUNT];
static std::shared_ptr<wlserver_ctm> s_MuraCTMBlob[DRM_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;
}
void update_mura_ctm()
{
s_MuraCTMBlob[DRM_SCREEN_TYPE_INTERNAL] = nullptr;
if (s_MuraCorrectionImage[DRM_SCREEN_TYPE_INTERNAL] == nullptr)
return;
static constexpr float kMuraMapScale = 0.0625f;
static constexpr float kMuraOffset = -127.0f / 255.0f;
// Mura's influence scales non-linearly with brightness, so we have an additional scale
// on top of the scale factor for the underlying mura map.
const float flScale = g_flMuraScale * kMuraMapScale;
glm::mat3x4 mura_scale_offset = glm::mat3x4
{
flScale, 0, 0, kMuraOffset * flScale,
0, flScale, 0, kMuraOffset * flScale,
0, 0, 0, 0, // No mura comp for blue channel.
};
s_MuraCTMBlob[DRM_SCREEN_TYPE_INTERNAL] = drm_create_ctm(&g_DRM, mura_scale_offset);
}
bool g_bMuraDebugFullColor = false;
bool set_mura_overlay( const char *path )
{
xwm_log.infof("[josh mura correction] Setting mura correction image to: %s", path);
s_MuraCorrectionImage[DRM_SCREEN_TYPE_INTERNAL] = nullptr;
update_mura_ctm();
std::string red_path = std::string(path) + "_red.png";
std::string green_path = std::string(path) + "_green.png";
int red_w, red_h, red_comp;
unsigned char *red_data = stbi_load(red_path.c_str(), &red_w, &red_h, &red_comp, 1);
int green_w, green_h, green_comp;
unsigned char *green_data = stbi_load(green_path.c_str(), &green_w, &green_h, &green_comp, 1);
if (!red_data || !green_data || red_w != green_w || red_h != green_h || red_comp != green_comp || red_comp != 1 || green_comp != 1)
{
xwm_log.infof("[josh mura correction] Couldn't load mura correction image, disabling mura correction.");
return true;
}
int w = red_w;
int h = red_h;
unsigned char *data = (unsigned char*)malloc(red_w * red_h * 4);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
data[(y * w * 4) + (x * 4) + 0] = g_bMuraDebugFullColor ? 255 : red_data[y * w + x];
data[(y * w * 4) + (x * 4) + 1] = g_bMuraDebugFullColor ? 255 : green_data[y * w + x];
data[(y * w * 4) + (x * 4) + 2] = 127; // offset of 0.
data[(y * w * 4) + (x * 4) + 3] = 0; // Make alpha = 0 so we act as addtive.
}
}
free(red_data);
free(green_data);
CVulkanTexture::createFlags texCreateFlags;
texCreateFlags.bFlippable = !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);
free(data);
xwm_log.infof("[josh mura correction] Loaded new mura correction image!");
update_mura_ctm();
return true;
}
bool set_mura_scale(float new_scale)
{
bool diff = g_flMuraScale != new_scale;
g_flMuraScale = new_scale;
update_mura_ctm();
return diff;
}
bool set_color_3dlut_override(const char *path)
{
int nLutIndex = EOTF_Gamma22;
g_ColorMgmt.pending.externalDirtyCtr++;
g_ColorMgmtLutsOverride[nLutIndex].bHasLut3D = false;
FILE *f = fopen(path, "rb");
if (!f) {
return true;
}
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
size_t elems = fsize / sizeof(uint16_t);
if (elems == 0) {
return true;
}
fread(g_ColorMgmtLutsOverride[nLutIndex].lut3d, elems, sizeof(uint16_t), f);
g_ColorMgmtLutsOverride[nLutIndex].bHasLut3D = true;
return true;
}