2022-12-02 18:18:36 +00:00
# define VK_USE_PLATFORM_WAYLAND_KHR
# define VK_USE_PLATFORM_XCB_KHR
# define VK_USE_PLATFORM_XLIB_KHR
# include "vkroots.h"
2023-01-02 04:13:16 +00:00
# include "xcb_helpers.hpp"
2023-09-24 14:43:21 +00:00
# include "gamescope-swapchain-client-protocol.h"
2022-12-18 09:05:14 +00:00
# include "../src/color_helpers.h"
2022-12-20 16:22:36 +00:00
# include "../src/layer_defines.h"
2022-12-02 18:18:36 +00:00
# include <cstdio>
# include <vector>
# include <algorithm>
# include <unordered_map>
# include <optional>
2023-09-28 20:54:02 +00:00
# include <poll.h>
2023-08-08 15:43:01 +00:00
// For limiter file.
2023-09-24 14:43:21 +00:00
# include <time.h>
2023-08-08 15:43:01 +00:00
# include <fcntl.h>
2023-08-16 04:41:09 +00:00
# include <unistd.h>
2023-08-08 15:43:01 +00:00
2023-09-29 14:36:01 +00:00
# include "../src/messagey.h"
2022-12-17 18:50:00 +00:00
using namespace std : : literals ;
2022-12-02 18:18:36 +00:00
namespace GamescopeWSILayer {
2023-09-29 21:36:37 +00:00
static const size_t MaxPastPresentationTimes = 16 ;
2023-09-24 14:43:21 +00:00
static uint64_t timespecToNanos ( struct timespec & spec ) {
return spec . tv_sec * 1'000'000'000ul + spec . tv_nsec ;
}
[[maybe_unused]] static uint64_t getTimeMonotonic ( ) {
timespec ts ;
clock_gettime ( CLOCK_MONOTONIC , & ts ) ;
return timespecToNanos ( ts ) ;
}
2022-12-17 18:50:00 +00:00
static bool contains ( const std : : vector < const char * > vec , std : : string_view lookupValue ) {
2022-12-02 18:18:36 +00:00
return std : : find_if ( vec . begin ( ) , vec . end ( ) ,
2022-12-17 18:50:00 +00:00
[ = ] ( const char * value ) { return value = = lookupValue ; } ) ! = vec . end ( ) ;
2022-12-02 18:18:36 +00:00
}
2023-09-28 20:54:02 +00:00
static int waylandPumpEvents ( wl_display * display ) {
int wlFd = wl_display_get_fd ( display ) ;
while ( true ) {
int ret = 0 ;
if ( ( ret = wl_display_dispatch_pending ( display ) ) < 0 )
return ret ;
if ( ( ret = wl_display_prepare_read ( display ) ) < 0 ) {
if ( errno = = EAGAIN )
continue ;
return - 1 ;
}
pollfd pollfd = {
. fd = wlFd ,
. events = POLLIN ,
} ;
timespec zeroTimeout = { } ;
ret = ppoll ( & pollfd , 1 , & zeroTimeout , NULL ) ;
if ( ret < = 0 ) {
wl_display_cancel_read ( display ) ;
if ( ret = = 0 )
wl_display_flush ( display ) ;
return ret ;
}
ret = wl_display_read_events ( display ) ;
if ( ret < 0 )
return ret ;
ret = wl_display_flush ( display ) ;
return ret ;
}
}
2023-09-30 19:24:28 +00:00
uint32_t clientAppId ( ) {
const char * appid = getenv ( " SteamAppId " ) ;
if ( ! appid | | ! * appid )
return 0 ;
return atoi ( appid ) ;
}
static GamescopeLayerClient : : Flags defaultLayerClientFlags ( uint32_t appid ) {
GamescopeLayerClient : : Flags flags = 0 ;
// My Little Pony: A Maretime Bay Adventure picks a HDR colorspace if available,
// but does not render as HDR at all.
if ( appid = = 1600780 )
flags | = GamescopeLayerClient : : Flag : : DisableHDR ;
return flags ;
}
2023-08-08 15:43:01 +00:00
// TODO: Maybe move to Wayland event or something.
// This just utilizes the same code as the Mesa path used
// for without the layer or GL though. Need to keep it around anyway.
static std : : mutex gamescopeSwapchainLimiterFDMutex ;
static uint32_t gamescopeFrameLimiterOverride ( ) {
const char * path = getenv ( " GAMESCOPE_LIMITER_FILE " ) ;
if ( ! path )
return 0 ;
int fd = - 1 ;
{
std : : unique_lock lock ( gamescopeSwapchainLimiterFDMutex ) ;
static int s_limiterFD = - 1 ;
if ( s_limiterFD < 0 )
s_limiterFD = open ( path , O_RDONLY ) ;
fd = s_limiterFD ;
}
if ( fd < 0 )
return 0 ;
uint32_t overrideValue = 0 ;
pread ( fd , & overrideValue , sizeof ( overrideValue ) , 0 ) ;
return overrideValue ;
}
2022-12-02 18:18:36 +00:00
struct GamescopeInstanceData {
wl_display * display ;
wl_compositor * compositor ;
2023-09-24 14:43:21 +00:00
gamescope_swapchain_factory * gamescopeSwapchainFactory ;
2023-09-30 19:24:28 +00:00
uint32_t appId = 0 ;
GamescopeLayerClient : : Flags flags = 0 ;
2022-12-02 18:18:36 +00:00
} ;
VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE ( GamescopeInstance , VkInstance ) ;
struct GamescopeSurfaceData {
2022-12-15 08:02:41 +00:00
VkInstance instance ;
2023-09-24 14:43:21 +00:00
wl_display * display ;
2023-01-02 05:57:44 +00:00
VkSurfaceKHR fallbackSurface ;
2022-12-02 18:18:36 +00:00
wl_surface * surface ;
2022-12-15 10:07:01 +00:00
2022-12-17 14:26:39 +00:00
xcb_connection_t * connection ;
xcb_window_t window ;
2022-12-20 16:22:36 +00:00
GamescopeLayerClient : : Flags flags ;
bool hdrOutput ;
bool shouldExposeHDR ( ) const {
const bool hdrAllowed = ! ( flags & GamescopeLayerClient : : Flag : : DisableHDR ) ;
return hdrOutput & & hdrAllowed ;
}
2023-01-02 05:57:44 +00:00
bool canBypassXWayland ( ) const {
auto rect = xcb : : getWindowRect ( connection , window ) ;
auto largestObscuringWindowSize = xcb : : getLargestObscuringChildWindowSize ( connection , window ) ;
auto toplevelWindow = xcb : : getToplevelWindow ( connection , window ) ;
if ( ! rect | | ! largestObscuringWindowSize | | ! toplevelWindow ) {
fprintf ( stderr , " [Gamescope WSI] canBypassXWayland: failed to get window info for window 0x%x. \n " , window ) ;
return false ;
}
auto toplevelRect = xcb : : getWindowRect ( connection , * toplevelWindow ) ;
if ( ! toplevelRect ) {
fprintf ( stderr , " [Gamescope WSI] canBypassXWayland: failed to get window info for window 0x%x. \n " , window ) ;
return false ;
}
// If we have any child windows obscuring us bigger than 1x1,
// then we cannot flip.
// (There can be dummy composite redirect windows and whatever.)
2023-09-28 20:15:07 +00:00
if ( largestObscuringWindowSize - > width > 1 | | largestObscuringWindowSize - > height > 1 ) {
# if GAMESCOPE_WSI_BYPASS_DEBUG
fprintf ( stderr , " [Gamescope WSI] Largest obscuring window size: %u %u \n " , largestObscuringWindowSize - > width , largestObscuringWindowSize - > height ) ;
# endif
2023-01-02 05:57:44 +00:00
return false ;
2023-09-28 20:15:07 +00:00
}
2023-01-02 05:57:44 +00:00
// If this window is not within 1px margin of error for the size of
// it's top level window, then it cannot be flipped.
2023-09-28 20:15:07 +00:00
if ( * toplevelWindow ! = window ) {
if ( iabs ( rect - > offset . x ) > 1 | |
iabs ( rect - > offset . y ) > 1 | |
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 " ,
rect - > offset . x , rect - > offset . y ,
toplevelRect - > extent . width , toplevelRect - > extent . height ,
rect - > extent . width , rect - > extent . height ) ;
# endif
return false ;
}
}
2023-01-02 05:57:44 +00:00
// I want to add more checks wrt. composite redirects and such here,
// but it seems what is exposed in xcb_composite is quite limited.
// So let's see how it goes for now. :-)
// Come back to this eventually.
return true ;
}
2022-12-02 18:18:36 +00:00
} ;
VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE ( GamescopeSurface , VkSurfaceKHR ) ;
2022-12-15 07:25:15 +00:00
struct GamescopeSwapchainData {
2023-09-24 14:43:21 +00:00
gamescope_swapchain * object ;
wl_display * display ;
2023-01-02 05:57:44 +00:00
VkSurfaceKHR surface ; // Always the Gamescope Surface surface -- so the Wayland one.
bool isBypassingXWayland ;
2023-08-08 15:43:01 +00:00
VkPresentModeKHR presentMode ;
VkPresentModeKHR originalPresentMode ;
2023-09-24 14:43:21 +00:00
std : : unique_ptr < std : : mutex > presentTimingMutex = std : : make_unique < std : : mutex > ( ) ;
std : : vector < VkPastPresentationTimingGOOGLE > pastPresentTimings ;
uint64_t refreshCycle = 16'666'666 ;
2022-12-15 07:25:15 +00:00
} ;
VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE ( GamescopeSwapchain , VkSwapchainKHR ) ;
2023-09-24 14:43:21 +00:00
static constexpr gamescope_swapchain_listener s_swapchainListener = {
. past_present_timing = [ ] (
void * data ,
gamescope_swapchain * object ,
uint32_t present_id ,
uint32_t desired_present_time_hi ,
uint32_t desired_present_time_lo ,
uint32_t actual_present_time_hi ,
uint32_t actual_present_time_lo ,
uint32_t earliest_present_time_hi ,
uint32_t earliest_present_time_lo ,
uint32_t present_margin_hi ,
uint32_t present_margin_lo ) {
GamescopeSwapchainData * swapchain = reinterpret_cast < GamescopeSwapchainData * > ( data ) ;
std : : unique_lock lock ( * swapchain - > presentTimingMutex ) ;
swapchain - > pastPresentTimings . emplace_back ( VkPastPresentationTimingGOOGLE {
. presentID = present_id ,
. desiredPresentTime = ( uint64_t ( desired_present_time_hi ) < < 32 ) | desired_present_time_lo ,
. actualPresentTime = ( uint64_t ( actual_present_time_hi ) < < 32 ) | actual_present_time_lo ,
. earliestPresentTime = ( uint64_t ( earliest_present_time_hi ) < < 32 ) | earliest_present_time_lo ,
. presentMargin = ( uint64_t ( present_margin_hi ) < < 32 ) | present_margin_lo
} ) ;
2023-09-29 21:36:37 +00:00
// Remove the first element if we are already at the max size.
if ( swapchain - > pastPresentTimings . size ( ) > = MaxPastPresentationTimes )
swapchain - > pastPresentTimings . erase ( swapchain - > pastPresentTimings . begin ( ) ) ;
2023-09-24 14:43:21 +00:00
} ,
. refresh_cycle = [ ] (
void * data ,
gamescope_swapchain * object ,
uint32_t refresh_cycle_hi ,
uint32_t refresh_cycle_lo ) {
GamescopeSwapchainData * swapchain = reinterpret_cast < GamescopeSwapchainData * > ( data ) ;
{
std : : unique_lock lock ( * swapchain - > presentTimingMutex ) ;
swapchain - > refreshCycle = ( uint64_t ( refresh_cycle_hi ) < < 32 ) | refresh_cycle_lo ;
}
fprintf ( stderr , " [Gamescope WSI] Swapchain recieved new refresh cycle: %.2fms \n " , swapchain - > refreshCycle / 1'000'000 .0 ) ;
}
} ;
2022-12-15 07:25:15 +00:00
2022-12-02 18:18:36 +00:00
class VkInstanceOverrides {
public :
static VkResult CreateInstance (
PFN_vkCreateInstance pfnCreateInstanceProc ,
const VkInstanceCreateInfo * pCreateInfo ,
const VkAllocationCallbacks * pAllocator ,
VkInstance * pInstance ) {
// If we are an app running under gamescope and we aren't gamescope itself,
// then setup our state for xwayland bypass.
if ( ! isRunningUnderGamescope ( ) | | isAppInfoGamescope ( pCreateInfo - > pApplicationInfo ) )
return pfnCreateInstanceProc ( pCreateInfo , pAllocator , pInstance ) ;
auto enabledExts = std : : vector < const char * > (
pCreateInfo - > ppEnabledExtensionNames ,
pCreateInfo - > ppEnabledExtensionNames + pCreateInfo - > enabledExtensionCount ) ;
if ( ! contains ( enabledExts , VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME ) )
enabledExts . push_back ( VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME ) ;
2023-01-02 06:41:16 +00:00
if ( ! contains ( enabledExts , VK_KHR_XCB_SURFACE_EXTENSION_NAME ) )
enabledExts . push_back ( VK_KHR_XCB_SURFACE_EXTENSION_NAME ) ;
2022-12-02 18:18:36 +00:00
VkInstanceCreateInfo createInfo = * pCreateInfo ;
createInfo . enabledExtensionCount = uint32_t ( enabledExts . size ( ) ) ;
createInfo . ppEnabledExtensionNames = enabledExts . data ( ) ;
2023-05-05 19:18:46 +00:00
setenv ( " vk_khr_present_wait " , " true " , 0 ) ;
2022-12-02 18:18:36 +00:00
VkResult result = pfnCreateInstanceProc ( & createInfo , pAllocator , pInstance ) ;
if ( result ! = VK_SUCCESS )
return result ;
wl_display * display = wl_display_connect ( gamescopeWaylandSocket ( ) ) ;
if ( ! display ) {
2023-01-07 12:09:12 +00:00
fprintf ( stderr , " [Gamescope WSI] Failed to connect to gamescope socket: %s. Bypass layer will be unavailable. \n " , gamescopeWaylandSocket ( ) ) ;
return result ;
2022-12-02 18:18:36 +00:00
}
wl_registry * registry = wl_display_get_registry ( display ) ;
{
2023-09-30 19:24:28 +00:00
uint32_t appId = clientAppId ( ) ;
2022-12-02 18:18:36 +00:00
auto state = GamescopeInstance : : create ( * pInstance , GamescopeInstanceData {
. display = display ,
2023-09-30 19:24:28 +00:00
. appId = appId ,
. flags = defaultLayerClientFlags ( appId ) ,
2022-12-02 18:18:36 +00:00
} ) ;
wl_registry_add_listener ( registry , & s_registryListener , reinterpret_cast < void * > ( state . get ( ) ) ) ;
2023-09-30 19:24:28 +00:00
// If we know at instance creation time we should disable HDR, force off
// DXVK_HDR now.
if ( state - > flags & GamescopeLayerClient : : Flag : : DisableHDR )
setenv ( " DXVK_HDR " , " 0 " , 1 ) ;
2022-12-02 18:18:36 +00:00
}
2023-09-24 14:43:21 +00:00
// Dispatch then roundtrip to get registry info.
2022-12-02 18:18:36 +00:00
wl_display_dispatch ( display ) ;
wl_display_roundtrip ( display ) ;
2023-09-24 14:43:21 +00:00
2022-12-02 18:18:36 +00:00
wl_registry_destroy ( registry ) ;
return result ;
}
static void DestroyInstance (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkInstance instance ,
const VkAllocationCallbacks * pAllocator ) {
if ( auto state = GamescopeInstance : : get ( instance ) ) {
2022-12-17 14:49:53 +00:00
wl_display_disconnect ( state - > display ) ;
2022-12-02 18:18:36 +00:00
}
GamescopeInstance : : remove ( instance ) ;
pDispatch - > DestroyInstance ( instance , pAllocator ) ;
}
static VkBool32 GetPhysicalDeviceXcbPresentationSupportKHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkPhysicalDevice physicalDevice ,
uint32_t queueFamilyIndex ,
xcb_connection_t * connection ,
xcb_visualid_t visual_id ) {
auto gamescopeInstance = GamescopeInstance : : get ( pDispatch - > Instance ) ;
if ( ! gamescopeInstance )
return pDispatch - > GetPhysicalDeviceXcbPresentationSupportKHR ( physicalDevice , queueFamilyIndex , connection , visual_id ) ;
2022-12-17 14:49:53 +00:00
return GetPhysicalDeviceGamescopePresentationSupport ( pDispatch , gamescopeInstance , physicalDevice , queueFamilyIndex ) ;
2022-12-02 18:18:36 +00:00
}
static VkBool32 GetPhysicalDeviceXlibPresentationSupportKHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkPhysicalDevice physicalDevice ,
uint32_t queueFamilyIndex ,
Display * dpy ,
VisualID visualID ) {
auto gamescopeInstance = GamescopeInstance : : get ( pDispatch - > Instance ) ;
if ( ! gamescopeInstance )
return pDispatch - > GetPhysicalDeviceXlibPresentationSupportKHR ( physicalDevice , queueFamilyIndex , dpy , visualID ) ;
2022-12-17 14:49:53 +00:00
return GetPhysicalDeviceGamescopePresentationSupport ( pDispatch , gamescopeInstance , physicalDevice , queueFamilyIndex ) ;
2022-12-02 18:18:36 +00:00
}
static VkResult CreateXcbSurfaceKHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkInstance instance ,
const VkXcbSurfaceCreateInfoKHR * pCreateInfo ,
const VkAllocationCallbacks * pAllocator ,
VkSurfaceKHR * pSurface ) {
auto gamescopeInstance = GamescopeInstance : : get ( instance ) ;
if ( ! gamescopeInstance )
return pDispatch - > CreateXcbSurfaceKHR ( instance , pCreateInfo , pAllocator , pSurface ) ;
2022-12-17 14:49:53 +00:00
return CreateGamescopeSurface ( pDispatch , gamescopeInstance , instance , pCreateInfo - > connection , pCreateInfo - > window , pAllocator , pSurface ) ;
2022-12-02 18:18:36 +00:00
}
static VkResult CreateXlibSurfaceKHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkInstance instance ,
const VkXlibSurfaceCreateInfoKHR * pCreateInfo ,
const VkAllocationCallbacks * pAllocator ,
VkSurfaceKHR * pSurface ) {
auto gamescopeInstance = GamescopeInstance : : get ( instance ) ;
if ( ! gamescopeInstance )
return pDispatch - > CreateXlibSurfaceKHR ( instance , pCreateInfo , pAllocator , pSurface ) ;
2022-12-17 14:49:53 +00:00
return CreateGamescopeSurface ( pDispatch , gamescopeInstance , instance , XGetXCBConnection ( pCreateInfo - > dpy ) , xcb_window_t ( pCreateInfo - > window ) , pAllocator , pSurface ) ;
2022-12-02 18:18:36 +00:00
}
2022-12-20 16:22:36 +00:00
static constexpr std : : array < VkSurfaceFormat2KHR , 3 > s_ExtraHDRSurfaceFormat2s = { {
2022-12-17 14:18:34 +00:00
{ . surfaceFormat = { VK_FORMAT_A2B10G10R10_UNORM_PACK32 , VK_COLOR_SPACE_HDR10_ST2084_EXT , } } ,
{ . surfaceFormat = { VK_FORMAT_A2R10G10B10_UNORM_PACK32 , VK_COLOR_SPACE_HDR10_ST2084_EXT , } } ,
2023-04-27 05:47:43 +00:00
{ . surfaceFormat = { VK_FORMAT_R16G16B16A16_SFLOAT , VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT , } } ,
2022-12-17 14:18:34 +00:00
} } ;
2022-12-20 16:22:36 +00:00
static constexpr auto s_ExtraHDRSurfaceFormats = [ ] ( ) {
std : : array < VkSurfaceFormatKHR , s_ExtraHDRSurfaceFormat2s . size ( ) > array ;
for ( size_t i = 0 ; i < s_ExtraHDRSurfaceFormat2s . size ( ) ; i + + )
array [ i ] = s_ExtraHDRSurfaceFormat2s [ i ] . surfaceFormat ;
2022-12-17 14:18:34 +00:00
return array ;
} ( ) ;
2022-12-15 07:25:15 +00:00
static VkResult GetPhysicalDeviceSurfaceFormatsKHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkPhysicalDevice physicalDevice ,
VkSurfaceKHR surface ,
uint32_t * pSurfaceFormatCount ,
VkSurfaceFormatKHR * pSurfaceFormats ) {
2022-12-20 16:22:36 +00:00
auto gamescopeSurface = GamescopeSurface : : get ( surface ) ;
2023-01-02 05:57:44 +00:00
if ( ! gamescopeSurface )
2022-12-20 16:22:36 +00:00
return pDispatch - > GetPhysicalDeviceSurfaceFormatsKHR ( physicalDevice , surface , pSurfaceFormatCount , pSurfaceFormats ) ;
2023-01-02 05:57:44 +00:00
const bool canBypass = gamescopeSurface - > canBypassXWayland ( ) ;
VkSurfaceKHR selectedSurface = canBypass ? surface : gamescopeSurface - > fallbackSurface ;
if ( ! canBypass | | ! gamescopeSurface - > shouldExposeHDR ( ) )
return pDispatch - > GetPhysicalDeviceSurfaceFormatsKHR ( physicalDevice , selectedSurface , pSurfaceFormatCount , pSurfaceFormats ) ;
2022-12-15 07:25:15 +00:00
return vkroots : : helpers : : append (
pDispatch - > GetPhysicalDeviceSurfaceFormatsKHR ,
2022-12-20 16:22:36 +00:00
s_ExtraHDRSurfaceFormats ,
2022-12-15 07:25:15 +00:00
pSurfaceFormatCount ,
pSurfaceFormats ,
physicalDevice ,
2023-01-02 05:57:44 +00:00
selectedSurface ) ;
2022-12-15 07:25:15 +00:00
}
static VkResult GetPhysicalDeviceSurfaceFormats2KHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkPhysicalDevice physicalDevice ,
const VkPhysicalDeviceSurfaceInfo2KHR * pSurfaceInfo ,
uint32_t * pSurfaceFormatCount ,
VkSurfaceFormat2KHR * pSurfaceFormats ) {
2022-12-20 16:22:36 +00:00
auto gamescopeSurface = GamescopeSurface : : get ( pSurfaceInfo - > surface ) ;
2023-01-02 05:57:44 +00:00
if ( ! gamescopeSurface )
2022-12-20 16:22:36 +00:00
return pDispatch - > GetPhysicalDeviceSurfaceFormats2KHR ( physicalDevice , pSurfaceInfo , pSurfaceFormatCount , pSurfaceFormats ) ;
2023-01-02 05:57:44 +00:00
VkPhysicalDeviceSurfaceInfo2KHR surfaceInfo = * pSurfaceInfo ;
const bool canBypass = gamescopeSurface - > canBypassXWayland ( ) ;
surfaceInfo . surface = canBypass ? surfaceInfo . surface : gamescopeSurface - > fallbackSurface ;
if ( ! canBypass | | ! gamescopeSurface - > shouldExposeHDR ( ) )
return pDispatch - > GetPhysicalDeviceSurfaceFormats2KHR ( physicalDevice , & surfaceInfo , pSurfaceFormatCount , pSurfaceFormats ) ;
2022-12-15 07:25:15 +00:00
return vkroots : : helpers : : append (
pDispatch - > GetPhysicalDeviceSurfaceFormats2KHR ,
2022-12-20 16:22:36 +00:00
s_ExtraHDRSurfaceFormat2s ,
2022-12-15 07:25:15 +00:00
pSurfaceFormatCount ,
pSurfaceFormats ,
physicalDevice ,
2023-01-02 05:57:44 +00:00
& surfaceInfo ) ;
2022-12-15 07:25:15 +00:00
}
2022-12-15 10:07:01 +00:00
static VkResult GetPhysicalDeviceSurfaceCapabilitiesKHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkPhysicalDevice physicalDevice ,
VkSurfaceKHR surface ,
VkSurfaceCapabilitiesKHR * pSurfaceCapabilities ) {
auto gamescopeSurface = GamescopeSurface : : get ( surface ) ;
if ( ! gamescopeSurface )
return pDispatch - > GetPhysicalDeviceSurfaceCapabilitiesKHR ( physicalDevice , surface , pSurfaceCapabilities ) ;
VkResult res = VK_SUCCESS ;
if ( ( res = pDispatch - > GetPhysicalDeviceSurfaceCapabilitiesKHR ( physicalDevice , surface , pSurfaceCapabilities ) ) ! = VK_SUCCESS )
return res ;
2023-01-02 06:08:32 +00:00
auto rect = xcb : : getWindowRect ( gamescopeSurface - > connection , gamescopeSurface - > window ) ;
if ( ! rect )
return VK_ERROR_SURFACE_LOST_KHR ;
2022-12-15 10:07:01 +00:00
2023-01-02 06:08:32 +00:00
pSurfaceCapabilities - > currentExtent = rect - > extent ;
2022-12-15 10:07:01 +00:00
pSurfaceCapabilities - > minImageCount = getMinImageCount ( ) ;
return VK_SUCCESS ;
}
2023-09-24 10:30:25 +00:00
static VkResult GetPhysicalDeviceSurfaceCapabilities2KHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkPhysicalDevice physicalDevice ,
const VkPhysicalDeviceSurfaceInfo2KHR * pSurfaceInfo ,
VkSurfaceCapabilities2KHR * pSurfaceCapabilities ) {
auto gamescopeSurface = GamescopeSurface : : get ( pSurfaceInfo - > surface ) ;
if ( ! gamescopeSurface )
return pDispatch - > GetPhysicalDeviceSurfaceCapabilities2KHR ( physicalDevice , pSurfaceInfo , pSurfaceCapabilities ) ;
VkResult res = VK_SUCCESS ;
if ( ( res = pDispatch - > GetPhysicalDeviceSurfaceCapabilities2KHR ( physicalDevice , pSurfaceInfo , pSurfaceCapabilities ) ) ! = VK_SUCCESS )
return res ;
auto rect = xcb : : getWindowRect ( gamescopeSurface - > connection , gamescopeSurface - > window ) ;
if ( ! rect )
return VK_ERROR_SURFACE_LOST_KHR ;
pSurfaceCapabilities - > surfaceCapabilities . currentExtent = rect - > extent ;
pSurfaceCapabilities - > surfaceCapabilities . minImageCount = getMinImageCount ( ) ;
return VK_SUCCESS ;
}
2022-12-02 18:18:36 +00:00
static void DestroySurfaceKHR (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkInstance instance ,
VkSurfaceKHR surface ,
const VkAllocationCallbacks * pAllocator ) {
if ( auto state = GamescopeSurface : : get ( surface ) ) {
2023-09-29 16:26:23 +00:00
pDispatch - > DestroySurfaceKHR ( instance , state - > fallbackSurface , pAllocator ) ;
2022-12-17 14:49:53 +00:00
wl_surface_destroy ( state - > surface ) ;
2022-12-02 18:18:36 +00:00
}
GamescopeSurface : : remove ( surface ) ;
pDispatch - > DestroySurfaceKHR ( instance , surface , pAllocator ) ;
}
2022-12-15 07:25:15 +00:00
static VkResult EnumerateDeviceExtensionProperties (
const vkroots : : VkInstanceDispatch * pDispatch ,
VkPhysicalDevice physicalDevice ,
const char * pLayerName ,
uint32_t * pPropertyCount ,
VkExtensionProperties * pProperties ) {
static constexpr std : : array < VkExtensionProperties , 1 > s_LayerExposedExts = { {
{ VK_EXT_HDR_METADATA_EXTENSION_NAME ,
VK_EXT_HDR_METADATA_SPEC_VERSION } ,
} } ;
if ( pLayerName ) {
2022-12-17 18:50:00 +00:00
if ( pLayerName = = " VK_LAYER_FROG_gamescope_wsi " sv ) {
2022-12-15 07:25:15 +00:00
return vkroots : : helpers : : array ( s_LayerExposedExts , pPropertyCount , pProperties ) ;
} else {
return pDispatch - > EnumerateDeviceExtensionProperties ( physicalDevice , pLayerName , pPropertyCount , pProperties ) ;
}
}
return vkroots : : helpers : : append (
pDispatch - > EnumerateDeviceExtensionProperties ,
s_LayerExposedExts ,
pPropertyCount ,
pProperties ,
physicalDevice ,
pLayerName ) ;
}
2022-12-02 18:18:36 +00:00
private :
static VkResult CreateGamescopeSurface (
const vkroots : : VkInstanceDispatch * pDispatch ,
GamescopeInstance & gamescopeInstance ,
VkInstance instance ,
2022-12-17 15:47:13 +00:00
xcb_connection_t * connection ,
2022-12-17 14:26:39 +00:00
xcb_window_t window ,
2022-12-02 18:18:36 +00:00
const VkAllocationCallbacks * pAllocator ,
VkSurfaceKHR * pSurface ) {
2022-12-17 14:26:39 +00:00
fprintf ( stderr , " [Gamescope WSI] Creating Gamescope surface: xid: 0x%x \n " , window ) ;
2022-12-02 18:18:36 +00:00
wl_surface * waylandSurface = wl_compositor_create_surface ( gamescopeInstance - > compositor ) ;
if ( ! waylandSurface ) {
2022-12-17 14:26:39 +00:00
fprintf ( stderr , " [Gamescope WSI] Failed to create wayland surface - xid: 0x%x \n " , window ) ;
2022-12-02 18:18:36 +00:00
return VK_ERROR_SURFACE_LOST_KHR ;
}
2023-09-30 19:24:28 +00:00
GamescopeLayerClient : : Flags flags = gamescopeInstance - > flags ;
2023-01-02 04:13:16 +00:00
if ( auto prop = xcb : : getPropertyValue < GamescopeLayerClient : : Flags > ( connection , " GAMESCOPE_LAYER_CLIENT_FLAGS " sv ) )
2022-12-20 16:22:36 +00:00
flags = * prop ;
bool hdrOutput = false ;
2023-01-02 04:13:16 +00:00
if ( auto prop = xcb : : getPropertyValue < uint32_t > ( connection , " GAMESCOPE_HDR_OUTPUT_FEEDBACK " sv ) )
2022-12-20 16:22:36 +00:00
hdrOutput = ! ! * prop ;
2022-12-02 18:18:36 +00:00
wl_display_flush ( gamescopeInstance - > display ) ;
VkWaylandSurfaceCreateInfoKHR waylandCreateInfo = {
2023-01-02 05:57:44 +00:00
. sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR ,
. pNext = nullptr ,
. flags = 0 ,
2022-12-02 18:18:36 +00:00
. display = gamescopeInstance - > display ,
. surface = waylandSurface ,
} ;
VkResult result = pDispatch - > CreateWaylandSurfaceKHR ( instance , & waylandCreateInfo , pAllocator , pSurface ) ;
if ( result ! = VK_SUCCESS ) {
2022-12-17 14:26:39 +00:00
fprintf ( stderr , " [Gamescope WSI] Failed to create Vulkan wayland surface - vr: %s xid: 0x%x \n " , vkroots : : helpers : : enumString ( result ) , window ) ;
2022-12-02 18:18:36 +00:00
return result ;
}
2023-01-02 05:57:44 +00:00
VkXcbSurfaceCreateInfoKHR xcbCreateInfo = {
. sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR ,
. pNext = nullptr ,
. flags = 0 ,
2022-12-17 15:47:13 +00:00
. connection = connection ,
2022-12-17 14:26:39 +00:00
. window = window ,
2023-01-02 05:57:44 +00:00
} ;
VkSurfaceKHR fallbackSurface = VK_NULL_HANDLE ;
result = pDispatch - > CreateXcbSurfaceKHR ( instance , & xcbCreateInfo , pAllocator , & fallbackSurface ) ;
if ( result ! = VK_SUCCESS ) {
fprintf ( stderr , " [Gamescope WSI] Failed to create Vulkan xcb (fallback) surface - vr: %s xid: 0x%x \n " , vkroots : : helpers : : enumString ( result ) , window ) ;
return result ;
}
fprintf ( stderr , " [Gamescope WSI] Made gamescope surface for xid: 0x%x \n " , window ) ;
auto gamescopeSurface = GamescopeSurface : : create ( * pSurface , GamescopeSurfaceData {
. instance = instance ,
2023-09-24 14:43:21 +00:00
. display = gamescopeInstance - > display ,
2023-01-02 05:57:44 +00:00
. fallbackSurface = fallbackSurface ,
. surface = waylandSurface ,
. connection = connection ,
. window = window ,
. flags = flags ,
. hdrOutput = hdrOutput ,
2022-12-02 18:18:36 +00:00
} ) ;
2023-09-30 19:24:28 +00:00
DumpGamescopeSurfaceState ( gamescopeInstance , gamescopeSurface ) ;
2022-12-24 13:42:01 +00:00
2022-12-02 18:18:36 +00:00
return result ;
}
2023-09-30 19:24:28 +00:00
static void DumpGamescopeSurfaceState ( GamescopeInstance & instance , GamescopeSurface & surface ) {
2022-12-24 13:42:01 +00:00
fprintf ( stderr , " [Gamescope WSI] Surface state: \n " ) ;
2023-09-30 19:24:28 +00:00
fprintf ( stderr , " steam app id: %u \n " , instance - > appId ) ;
2022-12-24 13:42:01 +00:00
fprintf ( stderr , " window xid: 0x%x \n " , surface - > window ) ;
fprintf ( stderr , " wayland surface res id: %u \n " , wl_proxy_get_id ( reinterpret_cast < struct wl_proxy * > ( surface - > surface ) ) ) ;
fprintf ( stderr , " layer client flags: 0x%x \n " , surface - > flags ) ;
fprintf ( stderr , " server hdr output enabled: %s \n " , surface - > hdrOutput ? " true " : " false " ) ;
fprintf ( stderr , " hdr formats exposed to client: %s \n " , surface - > shouldExposeHDR ( ) ? " true " : " false " ) ;
}
2022-12-02 18:18:36 +00:00
static VkBool32 GetPhysicalDeviceGamescopePresentationSupport (
const vkroots : : VkInstanceDispatch * pDispatch ,
GamescopeInstance & gamescopeInstance ,
VkPhysicalDevice physicalDevice ,
uint32_t queueFamilyIndex ) {
return pDispatch - > GetPhysicalDeviceWaylandPresentationSupportKHR ( physicalDevice , queueFamilyIndex , gamescopeInstance - > display ) ;
}
static const char * gamescopeWaylandSocket ( ) {
return std : : getenv ( " GAMESCOPE_WAYLAND_DISPLAY " ) ;
}
static bool isAppInfoGamescope ( const VkApplicationInfo * appInfo ) {
2022-12-15 12:41:17 +00:00
if ( ! appInfo | | ! appInfo - > pApplicationName )
2022-12-02 18:18:36 +00:00
return false ;
2022-12-17 18:50:00 +00:00
return appInfo - > pApplicationName = = " gamescope " sv ;
2022-12-02 18:18:36 +00:00
}
static bool isRunningUnderGamescope ( ) {
static bool s_isRunningUnderGamescope = [ ] ( ) - > bool {
const char * gamescopeSocketName = gamescopeWaylandSocket ( ) ;
if ( ! gamescopeSocketName | | ! * gamescopeSocketName )
return false ;
// Gamescope always unsets WAYLAND_SOCKET.
// So if that is set, we know we cannot be running under Gamescope
// and must be in a nested Wayland session inside of gamescope.
const char * waylandSocketName = std : : getenv ( " WAYLAND_DISPLAY " ) ;
if ( waylandSocketName & & * waylandSocketName )
return false ;
return true ;
} ( ) ;
return s_isRunningUnderGamescope ;
}
2022-12-15 10:07:01 +00:00
static uint32_t getMinImageCount ( ) {
2022-12-24 13:47:30 +00:00
{
const char * overrideStr = std : : getenv ( " GAMESCOPE_WSI_MIN_IMAGE_COUNT " ) ;
if ( overrideStr & & * overrideStr )
return uint32_t ( std : : atoi ( overrideStr ) ) ;
}
{
const char * overrideStr = std : : getenv ( " vk_x11_override_min_image_count " ) ;
if ( overrideStr & & * overrideStr )
return uint32_t ( std : : atoi ( overrideStr ) ) ;
}
2022-12-15 10:07:01 +00:00
return 3 ;
}
2022-12-02 18:18:36 +00:00
static constexpr wl_registry_listener s_registryListener = {
. global = [ ] ( void * data , wl_registry * registry , uint32_t name , const char * interface , uint32_t version ) {
auto instance = reinterpret_cast < GamescopeInstanceData * > ( data ) ;
2022-12-17 18:50:00 +00:00
if ( interface = = " wl_compositor " sv ) {
2022-12-02 18:18:36 +00:00
instance - > compositor = reinterpret_cast < wl_compositor * > (
wl_registry_bind ( registry , name , & wl_compositor_interface , version ) ) ;
2023-09-24 14:43:21 +00:00
} else if ( interface = = " gamescope_swapchain_factory " sv ) {
instance - > gamescopeSwapchainFactory = reinterpret_cast < gamescope_swapchain_factory * > (
wl_registry_bind ( registry , name , & gamescope_swapchain_factory_interface , version ) ) ;
2022-12-02 18:18:36 +00:00
}
} ,
. global_remove = [ ] ( void * data , wl_registry * registry , uint32_t name ) {
} ,
} ;
} ;
2022-12-15 07:25:15 +00:00
class VkDeviceOverrides {
public :
static void DestroySwapchainKHR (
const vkroots : : VkDeviceDispatch * pDispatch ,
VkDevice device ,
VkSwapchainKHR swapchain ,
const VkAllocationCallbacks * pAllocator ) {
2023-09-24 14:43:21 +00:00
if ( auto state = GamescopeSwapchain : : get ( swapchain ) ) {
gamescope_swapchain_destroy ( state - > object ) ;
}
2022-12-15 07:25:15 +00:00
GamescopeSwapchain : : remove ( swapchain ) ;
pDispatch - > DestroySwapchainKHR ( device , swapchain , pAllocator ) ;
}
static VkResult CreateSwapchainKHR (
const vkroots : : VkDeviceDispatch * pDispatch ,
VkDevice device ,
const VkSwapchainCreateInfoKHR * pCreateInfo ,
const VkAllocationCallbacks * pAllocator ,
VkSwapchainKHR * pSwapchain ) {
auto gamescopeSurface = GamescopeSurface : : get ( pCreateInfo - > surface ) ;
2023-09-29 14:36:01 +00:00
2023-09-29 14:22:37 +00:00
if ( ! gamescopeSurface ) {
2023-09-29 14:36:01 +00:00
static bool s_warned = false ;
if ( ! s_warned ) {
int messageId = - 1 ;
messagey : : ShowSimple (
" CreateSwapchainKHR: Creating swapchain for non-Gamescope swapchain. \n Hooking has failed somewhere! \n You may have a bad Vulkan layer interfering. \n Press OK to try to power through this error, or Cancel to stop. " ,
" Gamescope WSI Layer Error " ,
messagey : : MessageBoxFlag : : Warning | messagey : : MessageBoxFlag : : Simple_Cancel | messagey : : MessageBoxFlag : : Simple_OK ,
& messageId ) ;
if ( messageId = = 0 ) // Cancel
abort ( ) ;
s_warned = true ;
}
2023-01-02 05:57:44 +00:00
return pDispatch - > CreateSwapchainKHR ( device , pCreateInfo , pAllocator , pSwapchain ) ;
2023-09-29 14:22:37 +00:00
}
2022-12-15 07:25:15 +00:00
VkSwapchainCreateInfoKHR swapchainInfo = * pCreateInfo ;
2023-01-02 05:57:44 +00:00
2023-08-08 15:43:01 +00:00
const VkPresentModeKHR originalPresentMode = swapchainInfo . presentMode ;
const uint32_t limiterOverride = gamescopeFrameLimiterOverride ( ) ;
if ( limiterOverride = = 1 ) {
fprintf ( stderr , " [Gamescope WSI] Overriding present mode to FIFO from frame limiter override. \n " ) ;
swapchainInfo . presentMode = VK_PRESENT_MODE_FIFO_KHR ;
}
2023-01-02 05:57:44 +00:00
const bool canBypass = gamescopeSurface - > canBypassXWayland ( ) ;
// If we can't flip, fallback to the regular XCB surface on the XCB window.
if ( ! canBypass )
swapchainInfo . surface = gamescopeSurface - > fallbackSurface ;
2023-09-24 14:43:21 +00:00
// Force the colorspace to sRGB before sending to the driver.
swapchainInfo . imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR ;
fprintf ( stderr , " [Gamescope WSI] Creating swapchain for xid: 0x%0x - minImageCount: %u - format: %s - colorspace: %s - flip: %s \n " ,
gamescopeSurface - > window ,
pCreateInfo - > minImageCount ,
vkroots : : helpers : : enumString ( pCreateInfo - > imageFormat ) ,
vkroots : : helpers : : enumString ( pCreateInfo - > imageColorSpace ) ,
canBypass ? " true " : " false " ) ;
2023-01-07 10:35:38 +00:00
// Check for VkFormat support and return VK_ERROR_INITIALIZATION_FAILED
// if that VkFormat is unsupported for the underlying surface.
{
std : : vector < VkSurfaceFormatKHR > supportedSurfaceFormats ;
vkroots : : helpers : : enumerate (
pDispatch - > pPhysicalDeviceDispatch - > pInstanceDispatch - > GetPhysicalDeviceSurfaceFormatsKHR ,
supportedSurfaceFormats ,
pDispatch - > PhysicalDevice ,
swapchainInfo . surface ) ;
bool supportedSwapchainFormat = std : : find_if (
supportedSurfaceFormats . begin ( ) ,
supportedSurfaceFormats . end ( ) ,
[ = ] ( VkSurfaceFormatKHR value ) { return value . format = = swapchainInfo . imageFormat ; } )
! = supportedSurfaceFormats . end ( ) ;
if ( ! supportedSwapchainFormat ) {
fprintf ( stderr , " [Gamescope WSI] Refusing to make swapchain (unsupported VkFormat) for xid: 0x%0x - format: %s - colorspace: %s - flip: %s \n " ,
gamescopeSurface - > window ,
vkroots : : helpers : : enumString ( pCreateInfo - > imageFormat ) ,
vkroots : : helpers : : enumString ( pCreateInfo - > imageColorSpace ) ,
canBypass ? " true " : " false " ) ;
return VK_ERROR_INITIALIZATION_FAILED ;
}
}
2022-12-15 07:25:15 +00:00
2023-09-24 14:43:21 +00:00
auto serverId = xcb : : getPropertyValue < uint32_t > ( gamescopeSurface - > connection , " GAMESCOPE_XWAYLAND_SERVER_ID " sv ) ;
if ( ! serverId ) {
fprintf ( stderr , " [Gamescope WSI] Failed to get Xwayland server id. Failing swapchain creation. \n " ) ;
return VK_ERROR_SURFACE_LOST_KHR ;
}
auto gamescopeInstance = GamescopeInstance : : get ( gamescopeSurface - > instance ) ;
if ( ! gamescopeInstance ) {
fprintf ( stderr , " [Gamescope WSI] CreateSwapchainKHR: Instance for swapchain was already destroyed. (App use after free). \n " ) ;
return VK_ERROR_SURFACE_LOST_KHR ;
}
2022-12-17 14:26:39 +00:00
VkResult result = pDispatch - > CreateSwapchainKHR ( device , & swapchainInfo , pAllocator , pSwapchain ) ;
2023-09-24 14:43:21 +00:00
if ( result ! = VK_SUCCESS ) {
fprintf ( stderr , " [Gamescope WSI] Failed to create swapchain - vr: %s xid: 0x%x \n " , vkroots : : helpers : : enumString ( result ) , gamescopeSurface - > window ) ;
return result ;
2022-12-15 07:25:15 +00:00
}
2023-09-24 14:43:21 +00:00
gamescope_swapchain * gamescopeSwapchainObject = gamescope_swapchain_factory_create_swapchain (
gamescopeInstance - > gamescopeSwapchainFactory ,
gamescopeSurface - > surface ) ;
2023-09-28 20:16:01 +00:00
if ( canBypass )
gamescope_swapchain_override_window_content ( gamescopeSwapchainObject , * serverId , gamescopeSurface - > window ) ;
2023-09-24 14:43:21 +00:00
{
auto gamescopeSwapchain = GamescopeSwapchain : : create ( * pSwapchain , GamescopeSwapchainData {
. object = gamescopeSwapchainObject ,
. display = gamescopeInstance - > display ,
. surface = pCreateInfo - > surface , // Always the Wayland side surface.
. isBypassingXWayland = canBypass ,
. presentMode = swapchainInfo . presentMode , // The new present mode.
. originalPresentMode = originalPresentMode ,
} ) ;
2023-09-29 21:37:42 +00:00
gamescopeSwapchain - > pastPresentTimings . reserve ( MaxPastPresentationTimes ) ;
2023-09-24 14:43:21 +00:00
gamescope_swapchain_add_listener ( gamescopeSwapchainObject , & s_swapchainListener , reinterpret_cast < void * > ( gamescopeSwapchain . get ( ) ) ) ;
}
uint32_t imageCount = 0 ;
pDispatch - > GetSwapchainImagesKHR ( device , * pSwapchain , & imageCount , nullptr ) ;
fprintf ( stderr , " [Gamescope WSI] Created swapchain for xid: 0x%0x - imageCount: %u \n " ,
gamescopeSurface - > window ,
imageCount ) ;
gamescope_swapchain_swapchain_feedback (
gamescopeSwapchainObject ,
imageCount ,
uint32_t ( pCreateInfo - > imageFormat ) ,
uint32_t ( pCreateInfo - > imageColorSpace ) ,
uint32_t ( pCreateInfo - > compositeAlpha ) ,
uint32_t ( pCreateInfo - > preTransform ) ,
uint32_t ( pCreateInfo - > presentMode ) ,
uint32_t ( pCreateInfo - > clipped ) ) ;
return VK_SUCCESS ;
2022-12-15 07:25:15 +00:00
}
2023-01-02 05:57:44 +00:00
static VkResult QueuePresentKHR (
const vkroots : : VkDeviceDispatch * pDispatch ,
VkQueue queue ,
const VkPresentInfoKHR * pPresentInfo ) {
2023-08-08 15:43:01 +00:00
const uint32_t limiterOverride = gamescopeFrameLimiterOverride ( ) ;
2023-09-24 14:43:21 +00:00
auto pPresentTimes = vkroots : : FindInChain < VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE , const VkPresentTimesInfoGOOGLE > ( pPresentInfo ) ;
2023-09-28 20:54:02 +00:00
wl_display * display = nullptr ;
2023-09-24 14:43:21 +00:00
for ( uint32_t i = 0 ; i < pPresentInfo - > swapchainCount ; i + + ) {
if ( auto gamescopeSwapchain = GamescopeSwapchain : : get ( pPresentInfo - > pSwapchains [ i ] ) ) {
if ( pPresentTimes & & pPresentTimes - > pTimes ) {
assert ( pPresentTimes - > swapchainCount = = pPresentInfo - > swapchainCount ) ;
# if GAMESCOPE_WSI_DISPLAY_TIMING_DEBUG
fprintf ( stderr , " [Gamescope WSI] QueuePresentKHR: presentID: %u - desiredPresentTime: %lu - now: %lu \n " , pPresentTimes - > pTimes [ i ] . presentID , pPresentTimes - > pTimes [ i ] . desiredPresentTime , getTimeMonotonic ( ) ) ;
# endif
gamescope_swapchain_set_present_time (
gamescopeSwapchain - > object ,
pPresentTimes - > pTimes [ i ] . presentID ,
pPresentTimes - > pTimes [ i ] . desiredPresentTime > > 32 ,
pPresentTimes - > pTimes [ i ] . desiredPresentTime & 0xffffffff ) ;
}
2023-09-28 20:54:02 +00:00
assert ( display = = nullptr | | display = = gamescopeSwapchain - > display ) ;
display = gamescopeSwapchain - > display ;
2023-09-24 14:43:21 +00:00
}
}
2023-09-29 14:22:37 +00:00
if ( display ) {
2023-09-29 14:17:15 +00:00
waylandPumpEvents ( display ) ;
2023-09-29 14:22:37 +00:00
} else {
static bool s_warned = false ;
if ( ! s_warned ) {
2023-09-29 14:36:01 +00:00
int messageId = - 1 ;
messagey : : ShowSimple (
2023-09-29 14:50:16 +00:00
" QueuePresentKHR: Attempting to present to a non-hooked swapchain. \n Hooking has failed somewhere! \n You may have a bad Vulkan layer interfering. \n Press OK to try to power through this error, or Cancel to stop. " ,
2023-09-29 14:36:01 +00:00
" Gamescope WSI Layer Error " ,
messagey : : MessageBoxFlag : : Warning | messagey : : MessageBoxFlag : : Simple_Cancel | messagey : : MessageBoxFlag : : Simple_OK ,
& messageId ) ;
if ( messageId = = 0 ) // Cancel
abort ( ) ;
2023-09-29 14:22:37 +00:00
s_warned = true ;
}
}
2023-09-28 20:54:02 +00:00
2023-08-17 19:16:20 +00:00
VkResult result = pDispatch - > QueuePresentKHR ( queue , pPresentInfo ) ;
2023-01-02 05:57:44 +00:00
for ( uint32_t i = 0 ; i < pPresentInfo - > swapchainCount ; i + + ) {
VkSwapchainKHR swapchain = pPresentInfo - > pSwapchains [ i ] ;
2023-08-17 19:16:20 +00:00
auto UpdateSwapchainResult = [ & ] ( VkResult newResult ) {
if ( pPresentInfo - > pResults & & pPresentInfo - > pResults [ i ] > = VK_SUCCESS )
pPresentInfo - > pResults [ i ] = newResult ;
if ( result > = VK_SUCCESS )
result = newResult ;
} ;
2023-01-02 05:57:44 +00:00
if ( auto gamescopeSwapchain = GamescopeSwapchain : : get ( swapchain ) ) {
2023-08-08 15:43:01 +00:00
if ( ( limiterOverride = = 1 & & gamescopeSwapchain - > presentMode ! = VK_PRESENT_MODE_FIFO_KHR ) | |
( limiterOverride ! = 1 & & gamescopeSwapchain - > presentMode ! = gamescopeSwapchain - > originalPresentMode ) ) {
fprintf ( stderr , " [Gamescope WSI] Forcing swapchain recreation as frame limiter changed. \n " ) ;
2023-08-17 19:16:20 +00:00
UpdateSwapchainResult ( VK_ERROR_OUT_OF_DATE_KHR ) ;
2023-08-08 15:43:01 +00:00
}
2023-01-02 05:57:44 +00:00
auto gamescopeSurface = GamescopeSurface : : get ( gamescopeSwapchain - > surface ) ;
if ( ! gamescopeSurface ) {
fprintf ( stderr , " [Gamescope WSI] QueuePresentKHR: Surface for swapchain %u was already destroyed. (App use after free). \n " , i ) ;
abort ( ) ;
continue ;
}
const bool canBypass = gamescopeSurface - > canBypassXWayland ( ) ;
2023-08-17 19:16:20 +00:00
if ( canBypass ! = gamescopeSwapchain - > isBypassingXWayland )
UpdateSwapchainResult ( canBypass ? VK_SUBOPTIMAL_KHR : VK_ERROR_OUT_OF_DATE_KHR ) ;
2023-01-02 05:57:44 +00:00
}
}
2023-08-17 19:16:20 +00:00
return result ;
2023-01-02 05:57:44 +00:00
}
2022-12-15 07:25:15 +00:00
static void SetHdrMetadataEXT (
const vkroots : : VkDeviceDispatch * pDispatch ,
VkDevice device ,
uint32_t swapchainCount ,
const VkSwapchainKHR * pSwapchains ,
const VkHdrMetadataEXT * pMetadata ) {
2022-12-17 15:18:37 +00:00
for ( uint32_t i = 0 ; i < swapchainCount ; i + + ) {
auto gamescopeSwapchain = GamescopeSwapchain : : get ( pSwapchains [ i ] ) ;
if ( ! gamescopeSwapchain ) {
fprintf ( stderr , " [Gamescope WSI] SetHdrMetadataEXT: Swapchain %u does not support HDR. \n " , i ) ;
continue ;
}
const VkHdrMetadataEXT & metadata = pMetadata [ i ] ;
2023-09-24 14:43:21 +00:00
gamescope_swapchain_set_hdr_metadata (
gamescopeSwapchain - > object ,
2022-12-17 15:18:37 +00:00
color_xy_to_u16 ( metadata . displayPrimaryRed . x ) ,
color_xy_to_u16 ( metadata . displayPrimaryRed . y ) ,
color_xy_to_u16 ( metadata . displayPrimaryGreen . x ) ,
color_xy_to_u16 ( metadata . displayPrimaryGreen . y ) ,
color_xy_to_u16 ( metadata . displayPrimaryBlue . x ) ,
color_xy_to_u16 ( metadata . displayPrimaryBlue . y ) ,
color_xy_to_u16 ( metadata . whitePoint . x ) ,
color_xy_to_u16 ( metadata . whitePoint . y ) ,
nits_to_u16 ( metadata . maxLuminance ) ,
nits_to_u16_dark ( metadata . minLuminance ) ,
nits_to_u16 ( metadata . maxContentLightLevel ) ,
nits_to_u16 ( metadata . maxFrameAverageLightLevel ) ) ;
2023-05-12 19:11:19 +00:00
2023-09-24 14:43:21 +00:00
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 ) ;
2022-12-17 15:18:37 +00:00
}
2022-12-15 07:25:15 +00:00
}
2023-01-02 05:57:44 +00:00
2023-09-24 14:43:21 +00:00
static VkResult GetPastPresentationTimingGOOGLE (
const vkroots : : VkDeviceDispatch * pDispatch ,
VkDevice device ,
VkSwapchainKHR swapchain ,
uint32_t * pPresentationTimingCount ,
VkPastPresentationTimingGOOGLE * pPresentationTimings ) {
auto gamescopeSwapchain = GamescopeSwapchain : : get ( swapchain ) ;
if ( ! gamescopeSwapchain ) {
fprintf ( stderr , " [Gamescope WSI] GetPastPresentationTimingGOOGLE: Not a gamescope swapchain. \n " ) ;
return VK_ERROR_SURFACE_LOST_KHR ;
}
// Dispatch to get the latest timings.
2023-09-28 20:54:02 +00:00
if ( waylandPumpEvents ( gamescopeSwapchain - > display ) < 0 )
return VK_ERROR_SURFACE_LOST_KHR ;
2023-09-24 14:43:21 +00:00
uint32_t originalCount = * pPresentationTimingCount ;
std : : unique_lock ( * gamescopeSwapchain - > presentTimingMutex ) ;
auto & timings = gamescopeSwapchain - > pastPresentTimings ;
VkResult result = vkroots : : helpers : : array ( timings , pPresentationTimingCount , pPresentationTimings ) ;
// Erase those that we returned so we don't return them again.
timings . erase ( timings . begin ( ) , timings . begin ( ) + originalCount ) ;
return result ;
}
static VkResult GetRefreshCycleDurationGOOGLE (
const vkroots : : VkDeviceDispatch * pDispatch ,
VkDevice device ,
VkSwapchainKHR swapchain ,
VkRefreshCycleDurationGOOGLE * pDisplayTimingProperties ) {
auto gamescopeSwapchain = GamescopeSwapchain : : get ( swapchain ) ;
if ( ! gamescopeSwapchain ) {
fprintf ( stderr , " [Gamescope WSI] GetRefreshCycleDurationGOOGLE: Not a gamescope swapchain. \n " ) ;
return VK_ERROR_SURFACE_LOST_KHR ;
}
// Dispatch to get the latest cycle.
2023-09-28 20:54:02 +00:00
if ( waylandPumpEvents ( gamescopeSwapchain - > display ) < 0 )
return VK_ERROR_SURFACE_LOST_KHR ;
2023-09-24 14:43:21 +00:00
std : : unique_lock ( * gamescopeSwapchain - > presentTimingMutex ) ;
pDisplayTimingProperties - > refreshDuration = gamescopeSwapchain - > refreshCycle ;
return VK_SUCCESS ;
}
2022-12-15 07:25:15 +00:00
} ;
2022-12-02 18:18:36 +00:00
}
VKROOTS_DEFINE_LAYER_INTERFACES ( GamescopeWSILayer : : VkInstanceOverrides ,
vkroots : : NoOverrides ,
2022-12-15 08:02:41 +00:00
GamescopeWSILayer : : VkDeviceOverrides ) ;
2022-12-02 18:18:36 +00:00
VKROOTS_IMPLEMENT_SYNCHRONIZED_MAP_TYPE ( GamescopeWSILayer : : GamescopeInstance ) ;
VKROOTS_IMPLEMENT_SYNCHRONIZED_MAP_TYPE ( GamescopeWSILayer : : GamescopeSurface ) ;
2022-12-15 08:02:41 +00:00
VKROOTS_IMPLEMENT_SYNCHRONIZED_MAP_TYPE ( GamescopeWSILayer : : GamescopeSwapchain ) ;