layer: Implement VK_GOOGLE_display_timing + present wait/id for nested
Required a decent refactor of the protocol to be more swapchain based. Tested with Dota 2 + `-vulkan -vulkan_enable_google_display_timing` Should hopefully improve latency/pacing in nested a good amount too.
This commit is contained in:
parent
fc6e229506
commit
1bb7ef50ba
15 changed files with 843 additions and 258 deletions
|
|
@ -3,7 +3,7 @@
|
|||
#define VK_USE_PLATFORM_XLIB_KHR
|
||||
#include "vkroots.h"
|
||||
#include "xcb_helpers.hpp"
|
||||
#include "gamescope-xwayland-client-protocol.h"
|
||||
#include "gamescope-swapchain-client-protocol.h"
|
||||
#include "../src/color_helpers.h"
|
||||
#include "../src/layer_defines.h"
|
||||
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
#include <optional>
|
||||
|
||||
// For limiter file.
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
|
@ -21,6 +22,16 @@ using namespace std::literals;
|
|||
|
||||
namespace GamescopeWSILayer {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static bool contains(const std::vector<const char *> vec, std::string_view lookupValue) {
|
||||
return std::find_if(vec.begin(), vec.end(),
|
||||
[=](const char* value) { return value == lookupValue; }) != vec.end();
|
||||
|
|
@ -58,12 +69,13 @@ namespace GamescopeWSILayer {
|
|||
struct GamescopeInstanceData {
|
||||
wl_display* display;
|
||||
wl_compositor* compositor;
|
||||
gamescope_xwayland* gamescope;
|
||||
gamescope_swapchain_factory* gamescopeSwapchainFactory;
|
||||
};
|
||||
VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE(GamescopeInstance, VkInstance);
|
||||
|
||||
struct GamescopeSurfaceData {
|
||||
VkInstance instance;
|
||||
wl_display *display;
|
||||
VkSurfaceKHR fallbackSurface;
|
||||
wl_surface* surface;
|
||||
|
||||
|
|
@ -116,12 +128,55 @@ namespace GamescopeWSILayer {
|
|||
VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE(GamescopeSurface, VkSurfaceKHR);
|
||||
|
||||
struct GamescopeSwapchainData {
|
||||
gamescope_swapchain *object;
|
||||
wl_display* display;
|
||||
VkSurfaceKHR surface; // Always the Gamescope Surface surface -- so the Wayland one.
|
||||
bool isBypassingXWayland;
|
||||
VkPresentModeKHR presentMode;
|
||||
VkPresentModeKHR originalPresentMode;
|
||||
|
||||
std::unique_ptr<std::mutex> presentTimingMutex = std::make_unique<std::mutex>();
|
||||
std::vector<VkPastPresentationTimingGOOGLE> pastPresentTimings;
|
||||
uint64_t refreshCycle = 16'666'666;
|
||||
};
|
||||
VKROOTS_DEFINE_SYNCHRONIZED_MAP_TYPE(GamescopeSwapchain, VkSwapchainKHR);
|
||||
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
|
||||
});
|
||||
},
|
||||
|
||||
.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);
|
||||
}
|
||||
};
|
||||
|
||||
class VkInstanceOverrides {
|
||||
public:
|
||||
|
|
@ -168,8 +223,10 @@ namespace GamescopeWSILayer {
|
|||
});
|
||||
wl_registry_add_listener(registry, &s_registryListener, reinterpret_cast<void *>(state.get()));
|
||||
}
|
||||
// Dispatch then roundtrip to get registry info.
|
||||
wl_display_dispatch(display);
|
||||
wl_display_roundtrip(display);
|
||||
|
||||
wl_registry_destroy(registry);
|
||||
|
||||
return result;
|
||||
|
|
@ -413,13 +470,6 @@ namespace GamescopeWSILayer {
|
|||
if (auto prop = xcb::getPropertyValue<uint32_t>(connection, "GAMESCOPE_HDR_OUTPUT_FEEDBACK"sv))
|
||||
hdrOutput = !!*prop;
|
||||
|
||||
auto serverId = xcb::getPropertyValue<uint32_t>(connection, "GAMESCOPE_XWAYLAND_SERVER_ID"sv);
|
||||
if (!serverId) {
|
||||
fprintf(stderr, "[Gamescope WSI] Failed to get Xwayland server id. Failing surface creation.\n");
|
||||
return VK_ERROR_SURFACE_LOST_KHR;
|
||||
}
|
||||
gamescope_xwayland_override_window_content2(gamescopeInstance->gamescope, waylandSurface, *serverId, window);
|
||||
|
||||
wl_display_flush(gamescopeInstance->display);
|
||||
|
||||
VkWaylandSurfaceCreateInfoKHR waylandCreateInfo = {
|
||||
|
|
@ -453,6 +503,7 @@ namespace GamescopeWSILayer {
|
|||
fprintf(stderr, "[Gamescope WSI] Made gamescope surface for xid: 0x%x\n", window);
|
||||
auto gamescopeSurface = GamescopeSurface::create(*pSurface, GamescopeSurfaceData {
|
||||
.instance = instance,
|
||||
.display = gamescopeInstance->display,
|
||||
.fallbackSurface = fallbackSurface,
|
||||
.surface = waylandSurface,
|
||||
.connection = connection,
|
||||
|
|
@ -536,9 +587,9 @@ namespace GamescopeWSILayer {
|
|||
if (interface == "wl_compositor"sv) {
|
||||
instance->compositor = reinterpret_cast<wl_compositor *>(
|
||||
wl_registry_bind(registry, name, &wl_compositor_interface, version));
|
||||
} else if (interface == "gamescope_xwayland"sv) {
|
||||
instance->gamescope = reinterpret_cast<gamescope_xwayland *>(
|
||||
wl_registry_bind(registry, name, &gamescope_xwayland_interface, version));
|
||||
} else if (interface == "gamescope_swapchain_factory"sv) {
|
||||
instance->gamescopeSwapchainFactory = reinterpret_cast<gamescope_swapchain_factory *>(
|
||||
wl_registry_bind(registry, name, &gamescope_swapchain_factory_interface, version));
|
||||
}
|
||||
},
|
||||
.global_remove = [](void* data, wl_registry* registry, uint32_t name) {
|
||||
|
|
@ -554,6 +605,9 @@ namespace GamescopeWSILayer {
|
|||
VkDevice device,
|
||||
VkSwapchainKHR swapchain,
|
||||
const VkAllocationCallbacks* pAllocator) {
|
||||
if (auto state = GamescopeSwapchain::get(swapchain)) {
|
||||
gamescope_swapchain_destroy(state->object);
|
||||
}
|
||||
GamescopeSwapchain::remove(swapchain);
|
||||
pDispatch->DestroySwapchainKHR(device, swapchain, pAllocator);
|
||||
}
|
||||
|
|
@ -582,18 +636,15 @@ namespace GamescopeWSILayer {
|
|||
if (!canBypass)
|
||||
swapchainInfo.surface = gamescopeSurface->fallbackSurface;
|
||||
|
||||
if (gamescopeSurface) {
|
||||
// If this is a gamescope surface
|
||||
// Force the colorspace to sRGB before sending to the driver.
|
||||
swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||
// 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");
|
||||
}
|
||||
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");
|
||||
|
||||
// Check for VkFormat support and return VK_ERROR_INITIALIZATION_FAILED
|
||||
// if that VkFormat is unsupported for the underlying surface.
|
||||
|
|
@ -622,41 +673,61 @@ namespace GamescopeWSILayer {
|
|||
}
|
||||
}
|
||||
|
||||
VkResult result = pDispatch->CreateSwapchainKHR(device, &swapchainInfo, pAllocator, pSwapchain);
|
||||
if (gamescopeSurface) {
|
||||
if (result == VK_SUCCESS) {
|
||||
GamescopeSwapchain::create(*pSwapchain, GamescopeSwapchainData{
|
||||
.surface = pCreateInfo->surface, // Always the Wayland side surface.
|
||||
.isBypassingXWayland = canBypass,
|
||||
.presentMode = swapchainInfo.presentMode, // The new present mode.
|
||||
.originalPresentMode = originalPresentMode,
|
||||
});
|
||||
|
||||
auto gamescopeInstance = GamescopeInstance::get(gamescopeSurface->instance);
|
||||
if (gamescopeInstance) {
|
||||
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_xwayland_swapchain_feedback(
|
||||
gamescopeInstance->gamescope,
|
||||
gamescopeSurface->surface,
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "[Gamescope WSI] Failed to create swapchain - vr: %s xid: 0x%x\n", vkroots::helpers::enumString(result), gamescopeSurface->window);
|
||||
}
|
||||
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;
|
||||
}
|
||||
return result;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
VkResult result = pDispatch->CreateSwapchainKHR(device, &swapchainInfo, pAllocator, pSwapchain);
|
||||
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;
|
||||
}
|
||||
|
||||
gamescope_swapchain *gamescopeSwapchainObject = gamescope_swapchain_factory_create_swapchain(
|
||||
gamescopeInstance->gamescopeSwapchainFactory,
|
||||
gamescopeSurface->surface);
|
||||
|
||||
gamescope_swapchain_override_window_content(gamescopeSwapchainObject, *serverId, gamescopeSurface->window);
|
||||
|
||||
{
|
||||
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,
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static VkResult QueuePresentKHR(
|
||||
|
|
@ -665,6 +736,30 @@ namespace GamescopeWSILayer {
|
|||
const VkPresentInfoKHR* pPresentInfo) {
|
||||
const uint32_t limiterOverride = gamescopeFrameLimiterOverride();
|
||||
|
||||
auto pPresentTimes = vkroots::FindInChain<VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE, const VkPresentTimesInfoGOOGLE>(pPresentInfo);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Dispatch then flush to ensure any of our callbacks that did Wayland-y stuff
|
||||
// get their things flushed before QueuePresent's commit.
|
||||
wl_display_dispatch(gamescopeSwapchain->display);
|
||||
wl_display_flush(gamescopeSwapchain->display);
|
||||
}
|
||||
}
|
||||
|
||||
VkResult result = pDispatch->QueuePresentKHR(queue, pPresentInfo);
|
||||
|
||||
for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) {
|
||||
|
|
@ -714,24 +809,9 @@ namespace GamescopeWSILayer {
|
|||
continue;
|
||||
}
|
||||
|
||||
auto gamescopeSurface = GamescopeSurface::get(gamescopeSwapchain->surface);
|
||||
if (!gamescopeSurface) {
|
||||
fprintf(stderr, "[Gamescope WSI] SetHdrMetadataEXT: Surface for swapchain %u was already destroyed. (App use after free).\n", i);
|
||||
abort();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto gamescopeInstance = GamescopeInstance::get(gamescopeSurface->instance);
|
||||
if (!gamescopeInstance) {
|
||||
fprintf(stderr, "[Gamescope WSI] SetHdrMetadataEXT: Instance for swapchain %u was already destroyed. (App use after free).\n", i);
|
||||
abort();
|
||||
continue;
|
||||
}
|
||||
|
||||
const VkHdrMetadataEXT& metadata = pMetadata[i];
|
||||
gamescope_xwayland_set_hdr_metadata(
|
||||
gamescopeInstance->gamescope,
|
||||
gamescopeSurface->surface,
|
||||
gamescope_swapchain_set_hdr_metadata(
|
||||
gamescopeSwapchain->object,
|
||||
color_xy_to_u16(metadata.displayPrimaryRed.x),
|
||||
color_xy_to_u16(metadata.displayPrimaryRed.y),
|
||||
color_xy_to_u16(metadata.displayPrimaryGreen.x),
|
||||
|
|
@ -745,12 +825,59 @@ 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: 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);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
wl_display_dispatch(gamescopeSwapchain->display);
|
||||
|
||||
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.
|
||||
wl_display_dispatch(gamescopeSwapchain->display);
|
||||
|
||||
std::unique_lock(*gamescopeSwapchain->presentTimingMutex);
|
||||
pDisplayTimingProperties->refreshDuration = gamescopeSwapchain->refreshCycle;
|
||||
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@
|
|||
"functions": {
|
||||
"vkNegotiateLoaderLayerInterfaceVersion": "vkNegotiateLoaderLayerInterfaceVersion"
|
||||
},
|
||||
"device_extensions": [
|
||||
{
|
||||
"name" : "VK_GOOGLE_display_timing",
|
||||
"spec_version" : "1",
|
||||
"entrypoints": [
|
||||
"vkGetPastPresentationTimingGOOGLE",
|
||||
"vkGetRefreshCycleDurationGOOGLE"
|
||||
]
|
||||
}
|
||||
],
|
||||
"enable_environment": {
|
||||
"ENABLE_GAMESCOPE_WSI": "1"
|
||||
},
|
||||
|
|
|
|||
185
protocol/gamescope-swapchain.xml
Normal file
185
protocol/gamescope-swapchain.xml
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="gamescope_swapchain">
|
||||
|
||||
<copyright>
|
||||
Copyright © 2023 Joshua Ashton for Valve Software
|
||||
|
||||
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.
|
||||
</copyright>
|
||||
|
||||
<description summary="gamescope-specific swapchain protocol">
|
||||
This is a private Gamescope protocol. Regular Wayland clients must not use
|
||||
it.
|
||||
</description>
|
||||
|
||||
<interface name="gamescope_swapchain_factory" version="1">
|
||||
<request name="destroy" type="destructor"></request>
|
||||
|
||||
<request name="create_swapchain">
|
||||
<description summary="create Gamescope swapchain interface">
|
||||
</description>
|
||||
|
||||
<arg name="surface" type="object" interface="wl_surface"
|
||||
summary="target surface"/>
|
||||
<arg name="callback" type="new_id" interface="gamescope_swapchain"
|
||||
summary="new swapchain object"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="gamescope_swapchain" version="1">
|
||||
<request name="destroy" type="destructor"></request>
|
||||
|
||||
<request name="override_window_content">
|
||||
<description summary="override an X11's window wl_surface">
|
||||
Xwayland creates a wl_surface for each X11 window. It sends a
|
||||
WL_SURFACE_ID client message to indicate the mapping between the X11
|
||||
windows and the wl_surface objects.
|
||||
|
||||
This request overrides this mapping for a given X11 window, allowing an
|
||||
X11 client to submit buffers via the Wayland protocol. The override
|
||||
only affects buffer submission. Everything else (e.g. input events)
|
||||
still uses Xwayland's WL_SURFACE_ID.
|
||||
|
||||
x11_server is gotten by the GAMESCOPE_XWAYLAND_SERVER_ID property on the
|
||||
root window of the associated server.
|
||||
</description>
|
||||
<arg name="gamescope_xwayland_server_id" type="uint" summary="gamescope xwayland server ID"/>
|
||||
<arg name="x11_window" type="uint" summary="X11 window ID"/>
|
||||
</request>
|
||||
|
||||
<request name="swapchain_feedback">
|
||||
<description summary="provide swapchain feedback">
|
||||
Provide swapchain feedback to the compositor.
|
||||
|
||||
This is what the useless tearing protocol should have been.
|
||||
Absolutely not enough information in the final protocol to do what we want for SteamOS --
|
||||
which is have the Allow Tearing toggle apply to *both* Mailbox + Immediate and NOT fifo,
|
||||
essentially acting as an override for tearing on/off for games.
|
||||
The upstream protocol is very useless for our usecase here.
|
||||
|
||||
Provides image count ahead of time instead of needing to try and calculate it from
|
||||
an initial stall if we are doing low latency.
|
||||
|
||||
Provides colorspace info for us to do HDR for both HDR10 PQ and scRGB.
|
||||
The upstream HDR efforts seem to have no interest in supporting scRGB but we *need* that so /shrug
|
||||
We can do it here now! Yipee!
|
||||
|
||||
Swapchain feedback solves so many problems! :D
|
||||
</description>
|
||||
<arg name="image_count" type="uint" summary="image count of swapchain"/>
|
||||
<arg name="vk_format" type="uint" summary="VkFormat of swapchain"/>
|
||||
<arg name="vk_colorspace" type="uint" summary="VkColorSpaceKHR of swapchain"/>
|
||||
<arg name="vk_composite_alpha" type="uint" summary="VkCompositeAlphaFlagBitsKHR of swapchain"/>
|
||||
<arg name="vk_pre_transform" type="uint" summary="VkSurfaceTransformFlagBitsKHR of swapchain"/>
|
||||
<arg name="vk_present_mode" type="uint" summary="VkPresentModeKHR of swapchain"/>
|
||||
<arg name="vk_clipped" type="uint" summary="clipped (VkBool32) of swapchain"/>
|
||||
</request>
|
||||
|
||||
<request name="set_hdr_metadata">
|
||||
<description summary="set HDR metadata for a surface">
|
||||
Forward HDR metadata from Vulkan to the compositor.
|
||||
|
||||
HDR Metadata Infoframe as per CTA 861.G spec.
|
||||
This is expected to match exactly with the spec.
|
||||
|
||||
display_primary_*:
|
||||
Color Primaries of the Data.
|
||||
Specifies X and Y coordinates.
|
||||
These are coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
|
||||
white_point_*:
|
||||
White Point of Colorspace Data.
|
||||
Specifies X and Y coordinates.
|
||||
These are coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
|
||||
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.
|
||||
|
||||
max_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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
</description>
|
||||
<arg name="display_primary_red_x" type="uint" summary="red primary x coordinate"/>
|
||||
<arg name="display_primary_red_y" type="uint" summary="red primary y coordinate"/>
|
||||
<arg name="display_primary_green_x" type="uint" summary="green primary x coordinate"/>
|
||||
<arg name="display_primary_green_y" type="uint" summary="green primary y coordinate"/>
|
||||
<arg name="display_primary_blue_x" type="uint" summary="blue primary x coordinate"/>
|
||||
<arg name="display_primary_blue_y" type="uint" summary="blue primary y coordinate"/>
|
||||
<arg name="white_point_x" type="uint" summary="white point x coordinate"/>
|
||||
<arg name="white_point_y" type="uint" summary="white point y coordinate"/>
|
||||
<arg name="max_display_mastering_luminance" type="uint" summary="max display mastering luminance"/>
|
||||
<arg name="min_display_mastering_luminance" type="uint" summary="min display mastering luminance"/>
|
||||
<arg name="max_cll" type="uint" summary="max content light level"/>
|
||||
<arg name="max_fall" type="uint" summary="max frame average light level"/>
|
||||
</request>
|
||||
|
||||
<request name="set_present_time">
|
||||
<description summary="display timing of next commit">
|
||||
Sets the display timing of the next commit.
|
||||
|
||||
This gets reset to 0s in the compositor's state after a commit.
|
||||
</description>
|
||||
<arg name="present_id" type="uint" summary="application provided presentation id"/>
|
||||
<arg name="desired_present_time_hi" type="uint" summary="high part of the desired presentation time for this commit. Uses CLOCK_MONOTONIC. 0 = present as soon as possible."/>
|
||||
<arg name="desired_present_time_lo" type="uint" summary="low part of the desired presentation time for this commit. Uses CLOCK_MONOTONIC. 0 = present as soon as possible."/>
|
||||
</request>
|
||||
|
||||
<event name="past_present_timing">
|
||||
<description summary="information about past presentation">
|
||||
Gives information on the past presentation timing
|
||||
</description>
|
||||
<arg name="present_id" type="uint" summary="application provided presentation id"/>
|
||||
<arg name="desired_present_time_hi" type="uint" summary="high part of the desired presentation time for the commit. (from the app)"/>
|
||||
<arg name="desired_present_time_lo" type="uint" summary="low part of the desired presentation time for the commit. (from the app)"/>
|
||||
<arg name="actual_present_time_hi" type="uint" summary="high part of the actual present time for this commit."/>
|
||||
<arg name="actual_present_time_lo" type="uint" summary="low part of the actual present time for this commit."/>
|
||||
<arg name="earliest_present_time_hi" type="uint" summary="high part of the refresh time that Gamescope thought this commit was done by."/>
|
||||
<arg name="earliest_present_time_lo" type="uint" summary="low part of the refresh time that Gamescope thought this commit was done by."/>
|
||||
<arg name="present_margin_hi" type="uint" summary="high part of the difference between earliest present time and the earliest latch time"/>
|
||||
<arg name="present_margin_lo" type="uint" summary="low part of the difference between earliest present time and the earliest latch time"/>
|
||||
</event>
|
||||
|
||||
<event name="refresh_cycle">
|
||||
<description summary="information about refresh cycle for this swapchain">
|
||||
Gives information on the refresh cycle for this swapchain
|
||||
</description>
|
||||
<arg name="refresh_cycle_hi" type="uint" summary="high part of the refresh cycle in nanos"/>
|
||||
<arg name="refresh_cycle_lo" type="uint" summary="low part of the refresh cycle in nanos"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
@ -27,20 +27,14 @@
|
|||
<description summary="gamescope-specific xwayland protocol">
|
||||
This is a private Gamescope protocol. Regular Wayland clients must not use
|
||||
it.
|
||||
|
||||
This protocol has been superceded by the 'gamescope-swapchain' protocol.
|
||||
</description>
|
||||
|
||||
<interface name="gamescope_xwayland" version="2">
|
||||
<interface name="gamescope_xwayland" version="1">
|
||||
<request name="destroy" type="destructor"></request>
|
||||
|
||||
<request name="override_window_content">
|
||||
<description summary="override an X11's window wl_surface">
|
||||
See override_window_content2.
|
||||
</description>
|
||||
<arg name="surface" type="object" interface="wl_surface" summary="Wayland surface"/>
|
||||
<arg name="x11_window" type="uint" summary="X11 window ID"/>
|
||||
</request>
|
||||
|
||||
<request name="override_window_content2" since="2">
|
||||
<description summary="override an X11's window wl_surface">
|
||||
Xwayland creates a wl_surface for each X11 window. It sends a
|
||||
WL_SURFACE_ID client message to indicate the mapping between the X11
|
||||
|
|
@ -55,94 +49,7 @@
|
|||
root window of the associated server.
|
||||
</description>
|
||||
<arg name="surface" type="object" interface="wl_surface" summary="Wayland surface"/>
|
||||
<arg name="gamescope_xwayland_server_id" type="uint" summary="gamescope xwayland server ID"/>
|
||||
<arg name="x11_window" type="uint" summary="X11 window ID"/>
|
||||
</request>
|
||||
|
||||
<request name="swapchain_feedback" since="2">
|
||||
<description summary="provide swapchain feedback">
|
||||
Provide swapchain feedback to the compositor.
|
||||
|
||||
This is what the useless tearing protocol should have been.
|
||||
Absolutely not enough information in the final protocol to do what we want for SteamOS --
|
||||
which is have the Allow Tearing toggle apply to *both* Mailbox + Immediate and NOT fifo,
|
||||
essentially acting as an override for tearing on/off for games.
|
||||
The upstream protocol is very useless for our usecase here.
|
||||
|
||||
Provides image count ahead of time instead of needing to try and calculate it from
|
||||
an initial stall if we are doing low latency.
|
||||
|
||||
Provides colorspace info for us to do HDR for both HDR10 PQ and scRGB.
|
||||
The upstream HDR efforts seem to have no interest in supporting scRGB but we *need* that so /shrug
|
||||
We can do it here now! Yipee!
|
||||
|
||||
Swapchain feedback solves so many problems! :D
|
||||
</description>
|
||||
<arg name="surface" type="object" interface="wl_surface" summary="Wayland surface"/>
|
||||
<arg name="image_count" type="uint" summary="image count of swapchain"/>
|
||||
<arg name="vk_format" type="uint" summary="VkFormat of swapchain"/>
|
||||
<arg name="vk_colorspace" type="uint" summary="VkColorSpaceKHR of swapchain"/>
|
||||
<arg name="vk_composite_alpha" type="uint" summary="VkCompositeAlphaFlagBitsKHR of swapchain"/>
|
||||
<arg name="vk_pre_transform" type="uint" summary="VkSurfaceTransformFlagBitsKHR of swapchain"/>
|
||||
<arg name="vk_present_mode" type="uint" summary="VkPresentModeKHR of swapchain"/>
|
||||
<arg name="vk_clipped" type="uint" summary="clipped (VkBool32) of swapchain"/>
|
||||
</request>
|
||||
|
||||
<request name="set_hdr_metadata" since="2">
|
||||
<description summary="set HDR metadata for a surface">
|
||||
Forward HDR metadata from Vulkan to the compositor.
|
||||
|
||||
HDR Metadata Infoframe as per CTA 861.G spec.
|
||||
This is expected to match exactly with the spec.
|
||||
|
||||
display_primary_*:
|
||||
Color Primaries of the Data.
|
||||
Specifies X and Y coordinates.
|
||||
These are coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
|
||||
white_point_*:
|
||||
White Point of Colorspace Data.
|
||||
Specifies X and Y coordinates.
|
||||
These are coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
|
||||
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.
|
||||
|
||||
max_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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
</description>
|
||||
<arg name="surface" type="object" interface="wl_surface" summary="Wayland surface"/>
|
||||
<arg name="display_primary_red_x" type="uint" summary="red primary x coordinate"/>
|
||||
<arg name="display_primary_red_y" type="uint" summary="red primary y coordinate"/>
|
||||
<arg name="display_primary_green_x" type="uint" summary="green primary x coordinate"/>
|
||||
<arg name="display_primary_green_y" type="uint" summary="green primary y coordinate"/>
|
||||
<arg name="display_primary_blue_x" type="uint" summary="blue primary x coordinate"/>
|
||||
<arg name="display_primary_blue_y" type="uint" summary="blue primary y coordinate"/>
|
||||
<arg name="white_point_x" type="uint" summary="white point x coordinate"/>
|
||||
<arg name="white_point_y" type="uint" summary="white point y coordinate"/>
|
||||
<arg name="max_display_mastering_luminance" type="uint" summary="max display mastering luminance"/>
|
||||
<arg name="min_display_mastering_luminance" type="uint" summary="min display mastering luminance"/>
|
||||
<arg name="max_cll" type="uint" summary="max content light level"/>
|
||||
<arg name="max_fall" type="uint" summary="max frame average light level"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ protocols = [
|
|||
'gamescope-input-method',
|
||||
'gamescope-tearing-control-unstable-v1',
|
||||
'gamescope-control',
|
||||
'gamescope-swapchain',
|
||||
'xdg-shell',
|
||||
'presentation-time',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1646,7 +1646,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;
|
||||
g_uVblankDrawTimeNS = get_time_in_nanos() - g_SteamCompMgrVBlankTime.pipe_write_time;
|
||||
|
||||
if ( isPageFlip ) {
|
||||
// Wait for flip handler to unlock
|
||||
|
|
|
|||
|
|
@ -473,6 +473,9 @@ bool CVulkanDevice::createDevice()
|
|||
{
|
||||
enabledExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME );
|
||||
enabledExtensions.push_back( VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME );
|
||||
|
||||
enabledExtensions.push_back( VK_KHR_PRESENT_ID_EXTENSION_NAME );
|
||||
enabledExtensions.push_back( VK_KHR_PRESENT_WAIT_EXTENSION_NAME );
|
||||
}
|
||||
|
||||
if ( m_bSupportsModifiers )
|
||||
|
|
@ -511,9 +514,21 @@ bool CVulkanDevice::createDevice()
|
|||
.dynamicRendering = VK_TRUE,
|
||||
};
|
||||
|
||||
VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR,
|
||||
.pNext = &features13,
|
||||
.presentWait = VK_TRUE,
|
||||
};
|
||||
|
||||
VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR,
|
||||
.pNext = &presentWaitFeatures,
|
||||
.presentId = VK_TRUE,
|
||||
};
|
||||
|
||||
VkPhysicalDeviceFeatures2 features2 = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||
.pNext = &features13,
|
||||
.pNext = &presentIdFeatures,
|
||||
.features = {
|
||||
.shaderInt16 = m_bSupportsFp16,
|
||||
},
|
||||
|
|
@ -2517,18 +2532,58 @@ bool acquire_next_image( void )
|
|||
return g_device.vk.ResetFences( g_device.device(), 1, &g_output.acquireFence ) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static std::atomic<uint64_t> g_currentPresentWaitId = {0u};
|
||||
static std::mutex present_wait_lock;
|
||||
|
||||
static void present_wait_thread_func( void )
|
||||
{
|
||||
uint64_t present_wait_id = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
g_currentPresentWaitId.wait(present_wait_id);
|
||||
|
||||
// Lock to make sure swapchain destruction is waited on and that
|
||||
// it's for this swapchain.
|
||||
{
|
||||
std::unique_lock lock(present_wait_lock);
|
||||
present_wait_id = g_currentPresentWaitId.load();
|
||||
|
||||
if (present_wait_id != 0)
|
||||
{
|
||||
g_device.vk.WaitForPresentKHR( g_device.device(), g_output.swapChain, present_wait_id, 1'000'000'000lu );
|
||||
vblank_mark_possible_vblank( get_time_in_nanos() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vulkan_present_to_window( void )
|
||||
{
|
||||
static uint64_t s_lastPresentId = 0;
|
||||
|
||||
uint64_t presentId = ++s_lastPresentId;
|
||||
|
||||
VkPresentIdKHR presentIdInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR,
|
||||
.swapchainCount = 1,
|
||||
.pPresentIds = &presentId,
|
||||
};
|
||||
|
||||
VkPresentInfoKHR presentInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.pNext = &presentIdInfo,
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &g_output.swapChain,
|
||||
.pImageIndices = &g_output.nOutImage,
|
||||
};
|
||||
|
||||
if ( g_device.vk.QueuePresentKHR( g_device.queue(), &presentInfo ) != VK_SUCCESS )
|
||||
if ( g_device.vk.QueuePresentKHR( g_device.queue(), &presentInfo ) == VK_SUCCESS )
|
||||
g_currentPresentWaitId = presentId;
|
||||
else
|
||||
vulkan_remake_swapchain();
|
||||
|
||||
|
||||
while ( !acquire_next_image() )
|
||||
vulkan_remake_swapchain();
|
||||
}
|
||||
|
|
@ -2698,6 +2753,9 @@ bool vulkan_make_swapchain( VulkanOutput_t *pOutput )
|
|||
|
||||
bool vulkan_remake_swapchain( void )
|
||||
{
|
||||
std::unique_lock lock(present_wait_lock);
|
||||
g_currentPresentWaitId = 0;
|
||||
|
||||
VulkanOutput_t *pOutput = &g_output;
|
||||
g_device.waitIdle();
|
||||
g_device.vk.QueueWaitIdle( g_device.queue() );
|
||||
|
|
@ -3002,6 +3060,12 @@ bool vulkan_init( VkInstance instance, VkSurfaceKHR surface )
|
|||
if (!init_nis_data())
|
||||
return false;
|
||||
|
||||
if (BIsNested() && !BIsVRSession())
|
||||
{
|
||||
std::thread present_wait_thread( present_wait_thread_func );
|
||||
present_wait_thread.detach();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -654,6 +654,7 @@ static inline uint32_t div_roundup(uint32_t x, uint32_t y)
|
|||
VK_FUNC(UnmapMemory) \
|
||||
VK_FUNC(UpdateDescriptorSets) \
|
||||
VK_FUNC(WaitForFences) \
|
||||
VK_FUNC(WaitForPresentKHR) \
|
||||
VK_FUNC(WaitSemaphores)
|
||||
|
||||
class CVulkanDevice
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ extern float g_flHDRItmTargetNits;
|
|||
|
||||
extern std::atomic<uint64_t> g_lastVblank;
|
||||
|
||||
|
||||
std::string clipboard;
|
||||
std::string primarySelection;
|
||||
|
||||
|
|
@ -141,6 +142,9 @@ uint64_t timespec_to_nanos(struct timespec& spec)
|
|||
return spec.tv_sec * 1'000'000'000ul + spec.tv_nsec;
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
@ -636,6 +640,11 @@ struct commit_t
|
|||
|
||||
struct wlr_surface *surf = nullptr;
|
||||
std::vector<struct wl_resource*> presentation_feedbacks;
|
||||
|
||||
std::optional<uint32_t> present_id = std::nullopt;
|
||||
uint64_t desired_present_time = 0;
|
||||
uint64_t earliest_present_time = 0;
|
||||
uint64_t present_margin = 0;
|
||||
};
|
||||
|
||||
static std::vector<pollfd> pollfds;
|
||||
|
|
@ -735,7 +744,7 @@ bool synchronize;
|
|||
|
||||
std::mutex g_SteamCompMgrXWaylandServerMutex;
|
||||
|
||||
uint64_t g_SteamCompMgrVBlankTime = 0;
|
||||
VBlankTimeInfo_t g_SteamCompMgrVBlankTime = {};
|
||||
|
||||
static int g_nSteamCompMgrTargetFPS = 0;
|
||||
static uint64_t g_uDynamicRefreshEqualityTime = 0;
|
||||
|
|
@ -876,6 +885,7 @@ struct WaitListEntry_t
|
|||
// steamcompmgr thread in handle_done_commits, it is worth it.
|
||||
bool mangoapp_nudge;
|
||||
uint64_t commitID;
|
||||
uint64_t desiredPresentTime;
|
||||
};
|
||||
|
||||
sem waitListSem;
|
||||
|
|
@ -937,7 +947,7 @@ retry:
|
|||
|
||||
{
|
||||
std::unique_lock< std::mutex > lock( entry.doneCommits->listCommitsDoneLock );
|
||||
entry.doneCommits->listCommitsDone.push_back( entry.commitID );
|
||||
entry.doneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ entry.commitID, entry.desiredPresentTime } );
|
||||
}
|
||||
|
||||
nudge_steamcompmgr();
|
||||
|
|
@ -1185,7 +1195,7 @@ destroy_buffer( struct wl_listener *listener, void * )
|
|||
}
|
||||
|
||||
static std::shared_ptr<commit_t>
|
||||
import_commit ( struct wlr_surface *surf, struct wlr_buffer *buf, bool async, std::shared_ptr<wlserver_vk_swapchain_feedback> swapchain_feedback, std::vector<struct wl_resource*> presentation_feedbacks )
|
||||
import_commit ( struct wlr_surface *surf, struct wlr_buffer *buf, bool async, std::shared_ptr<wlserver_vk_swapchain_feedback> swapchain_feedback, std::vector<struct wl_resource*> presentation_feedbacks, std::optional<uint32_t> present_id, uint64_t desired_present_time )
|
||||
{
|
||||
std::shared_ptr<commit_t> commit = std::make_shared<commit_t>();
|
||||
std::unique_lock<std::mutex> lock( wlr_buffer_map_lock );
|
||||
|
|
@ -1196,6 +1206,8 @@ import_commit ( struct wlr_surface *surf, struct wlr_buffer *buf, bool async, st
|
|||
commit->presentation_feedbacks = std::move(presentation_feedbacks);
|
||||
if (swapchain_feedback)
|
||||
commit->feedback = *swapchain_feedback;
|
||||
commit->present_id = present_id;
|
||||
commit->desired_present_time = desired_present_time;
|
||||
|
||||
auto it = wlr_buffer_map.find( buf );
|
||||
if ( it != wlr_buffer_map.end() )
|
||||
|
|
@ -2571,9 +2583,9 @@ paint_all(bool async)
|
|||
{
|
||||
vulkan_present_to_window();
|
||||
}
|
||||
// Update the time it took us to present.
|
||||
// TODO: Use Vulkan present timing in future.
|
||||
g_uVblankDrawTimeNS = get_time_in_nanos() - g_SteamCompMgrVBlankTime;
|
||||
|
||||
// Update the time it took us to commit
|
||||
g_uVblankDrawTimeNS = get_time_in_nanos() - g_SteamCompMgrVBlankTime.pipe_write_time;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -5927,7 +5939,7 @@ register_systray(xwayland_ctx_t *ctx)
|
|||
XSetSelectionOwner(ctx->dpy, net_system_tray, ctx->ourWindow, 0);
|
||||
}
|
||||
|
||||
bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t commitID )
|
||||
bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t commitID, uint64_t earliestPresentTime, uint64_t earliestLatchTime )
|
||||
{
|
||||
bool bFoundWindow = false;
|
||||
uint32_t j;
|
||||
|
|
@ -5937,6 +5949,8 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co
|
|||
{
|
||||
gpuvis_trace_printf( "commit %lu done", w->commit_queue[ j ]->commitID );
|
||||
w->commit_queue[ j ]->done = true;
|
||||
w->commit_queue[ j ]->earliest_present_time = earliestPresentTime;
|
||||
w->commit_queue[ j ]->present_margin = earliestPresentTime - earliestLatchTime;
|
||||
bFoundWindow = true;
|
||||
|
||||
// Window just got a new available commit, determine if that's worth a repaint
|
||||
|
|
@ -6009,56 +6023,135 @@ bool handle_done_commit( steamcompmgr_win_t *w, xwayland_ctx_t *ctx, uint64_t co
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: Merge these two functions.
|
||||
void handle_done_commits_xwayland( xwayland_ctx_t *ctx )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( ctx->doneCommits.listCommitsDoneLock );
|
||||
|
||||
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;
|
||||
|
||||
uint64_t now = get_time_in_nanos();
|
||||
|
||||
// very fast loop yes
|
||||
for ( uint32_t i = 0; i < ctx->doneCommits.listCommitsDone.size(); i++ )
|
||||
for ( auto& entry : ctx->doneCommits.listCommitsDone )
|
||||
{
|
||||
if (!entry.earliestPresentTime)
|
||||
{
|
||||
entry.earliestPresentTime = next_refresh_time;
|
||||
entry.earliestLatchTime = now;
|
||||
}
|
||||
|
||||
if ( entry.desiredPresentTime > next_refresh_time )
|
||||
{
|
||||
commits_before_their_time.push_back( entry );
|
||||
break;
|
||||
}
|
||||
|
||||
for ( steamcompmgr_win_t *w = ctx->list; w; w = w->xwayland().next )
|
||||
{
|
||||
if (handle_done_commit(w, ctx, ctx->doneCommits.listCommitsDone[i]))
|
||||
if (handle_done_commit(w, ctx, entry.commitID, entry.earliestPresentTime, entry.earliestLatchTime))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->doneCommits.listCommitsDone.clear();
|
||||
ctx->doneCommits.listCommitsDone = std::move( commits_before_their_time );
|
||||
}
|
||||
|
||||
void handle_done_commits_xdg()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( g_steamcompmgr_xdg_done_commits.listCommitsDoneLock );
|
||||
|
||||
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;
|
||||
|
||||
uint64_t now = get_time_in_nanos();
|
||||
|
||||
// very fast loop yes
|
||||
for ( uint32_t i = 0; i < g_steamcompmgr_xdg_done_commits.listCommitsDone.size(); i++ )
|
||||
for ( auto& entry : g_steamcompmgr_xdg_done_commits.listCommitsDone )
|
||||
{
|
||||
if (!entry.earliestPresentTime)
|
||||
{
|
||||
entry.earliestPresentTime = next_refresh_time;
|
||||
entry.earliestLatchTime = now;
|
||||
}
|
||||
|
||||
if ( entry.desiredPresentTime > next_refresh_time )
|
||||
{
|
||||
commits_before_their_time.push_back( entry );
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto& xdg_win : g_steamcompmgr_xdg_wins)
|
||||
{
|
||||
if (handle_done_commit(xdg_win.get(), nullptr, g_steamcompmgr_xdg_done_commits.listCommitsDone[i]))
|
||||
if (handle_done_commit(xdg_win.get(), nullptr, entry.commitID, entry.earliestPresentTime, entry.earliestLatchTime))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_steamcompmgr_xdg_done_commits.listCommitsDone.clear();
|
||||
g_steamcompmgr_xdg_done_commits.listCommitsDone = std::move( commits_before_their_time );
|
||||
}
|
||||
|
||||
void handle_presented_for_window( steamcompmgr_win_t* w )
|
||||
{
|
||||
uint64_t next_refresh_time = g_SteamCompMgrVBlankTime.target_vblank_time;
|
||||
|
||||
uint64_t refresh_cycle = g_nSteamCompMgrTargetFPS && steamcompmgr_window_should_limit_fps( w )
|
||||
? g_SteamCompMgrLimitedAppRefreshCycle
|
||||
: g_SteamCompMgrAppRefreshCycle;
|
||||
|
||||
commit_t *lastCommit = get_window_last_done_commit_peek(w);
|
||||
if (lastCommit && !lastCommit->presentation_feedbacks.empty())
|
||||
if (lastCommit)
|
||||
{
|
||||
wlserver_lock();
|
||||
if (!lastCommit->presentation_feedbacks.empty() || lastCommit->present_id)
|
||||
{
|
||||
wlserver_lock();
|
||||
|
||||
int nRefresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh;
|
||||
if (!lastCommit->presentation_feedbacks.empty())
|
||||
{
|
||||
wlserver_presentation_feedback_presented(
|
||||
lastCommit->surf,
|
||||
lastCommit->presentation_feedbacks,
|
||||
next_refresh_time,
|
||||
refresh_cycle);
|
||||
}
|
||||
|
||||
wlserver_presentation_feedback_presented(
|
||||
lastCommit->surf,
|
||||
lastCommit->presentation_feedbacks,
|
||||
g_lastVblank.load(),
|
||||
nRefresh);
|
||||
if (lastCommit->present_id)
|
||||
{
|
||||
wlserver_past_present_timing(
|
||||
lastCommit->surf,
|
||||
*lastCommit->present_id,
|
||||
lastCommit->desired_present_time,
|
||||
next_refresh_time,
|
||||
lastCommit->earliest_present_time,
|
||||
lastCommit->present_margin);
|
||||
lastCommit->present_id = std::nullopt;
|
||||
}
|
||||
|
||||
wlserver_unlock();
|
||||
wlserver_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
if (struct wlr_surface *surface = w->current_surface())
|
||||
{
|
||||
auto info = get_wl_surface_info(surface);
|
||||
if (info->gamescope_swapchain != nullptr)
|
||||
{
|
||||
// Could have got the override set in this bubble.s
|
||||
surface = w->current_surface();
|
||||
|
||||
if (info->last_refresh_cycle != refresh_cycle)
|
||||
{
|
||||
wlserver_lock();
|
||||
info->last_refresh_cycle = refresh_cycle;
|
||||
wlserver_refresh_cycle(surface, refresh_cycle);
|
||||
wlserver_unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6142,7 +6235,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re
|
|||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<commit_t> newCommit = import_commit( reslistentry.surf, buf, reslistentry.async, std::move(reslistentry.feedback), std::move(reslistentry.presentation_feedbacks) );
|
||||
std::shared_ptr<commit_t> newCommit = import_commit( 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 )
|
||||
|
|
@ -6170,6 +6263,7 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re
|
|||
.fence = fence,
|
||||
.mangoapp_nudge = mango_nudge,
|
||||
.commitID = newCommit->commitID,
|
||||
.desiredPresentTime = newCommit->desired_present_time,
|
||||
};
|
||||
waitList.push_back( entry );
|
||||
}
|
||||
|
|
@ -6534,7 +6628,7 @@ dispatch_vblank( int fd )
|
|||
bool vblank = false;
|
||||
for (;;)
|
||||
{
|
||||
uint64_t vblanktime = 0;
|
||||
VBlankTimeInfo_t vblanktime = {};
|
||||
ssize_t ret = read( fd, &vblanktime, sizeof( vblanktime ) );
|
||||
if ( ret < 0 )
|
||||
{
|
||||
|
|
@ -6546,9 +6640,10 @@ dispatch_vblank( int fd )
|
|||
}
|
||||
|
||||
g_SteamCompMgrVBlankTime = vblanktime;
|
||||
uint64_t diff = get_time_in_nanos() - vblanktime;
|
||||
|
||||
// give it 1 ms of slack.. maybe too long
|
||||
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" );
|
||||
|
|
@ -7488,6 +7583,17 @@ steamcompmgr_main(int argc, char **argv)
|
|||
}
|
||||
}
|
||||
|
||||
if ( vblank )
|
||||
{
|
||||
int nRealRefresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh;
|
||||
int nTargetFPS = g_nSteamCompMgrTargetFPS ? g_nSteamCompMgrTargetFPS : nRealRefresh;
|
||||
int nMultiplier = nRealRefresh / nTargetFPS;
|
||||
|
||||
int nAppRefresh = nRealRefresh * nMultiplier;
|
||||
g_SteamCompMgrAppRefreshCycle = 1'000'000'000ul / nRealRefresh;
|
||||
g_SteamCompMgrLimitedAppRefreshCycle = 1'000'000'000ul / nAppRefresh;
|
||||
}
|
||||
|
||||
// Handle presentation-time stuff
|
||||
//
|
||||
// Notes:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ void steamcompmgr_main(int argc, char **argv);
|
|||
|
||||
#include "rendervulkan.hpp"
|
||||
#include "wlserver.hpp"
|
||||
#include "vblankmanager.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
|
@ -137,7 +138,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 uint64_t g_SteamCompMgrVBlankTime;
|
||||
extern VBlankTimeInfo_t g_SteamCompMgrVBlankTime;
|
||||
extern pid_t focusWindow_pid;
|
||||
|
||||
void init_xwayland_ctx(uint32_t serverId, gamescope_xwayland_server_t *xwayland_server);
|
||||
|
|
|
|||
|
|
@ -64,6 +64,21 @@ const uint64_t g_uVBlankDrawTimeMinCompositing = 2'400'000;
|
|||
|
||||
//#define VBLANK_DEBUG
|
||||
|
||||
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;
|
||||
|
||||
uint64_t lastVblank = g_lastVblank - offset;
|
||||
|
||||
uint64_t now = get_time_in_nanos();
|
||||
uint64_t targetPoint = lastVblank + nsecInterval;
|
||||
while ( targetPoint < now )
|
||||
targetPoint += nsecInterval;
|
||||
|
||||
return targetPoint;
|
||||
}
|
||||
|
||||
void vblankThreadRun( void )
|
||||
{
|
||||
pthread_setname_np( pthread_self(), "gamescope-vblk" );
|
||||
|
|
@ -155,19 +170,17 @@ void vblankThreadRun( void )
|
|||
lastOffset = offset;
|
||||
#endif
|
||||
|
||||
uint64_t lastVblank = g_lastVblank - offset;
|
||||
|
||||
uint64_t now = get_time_in_nanos();
|
||||
uint64_t targetPoint = lastVblank + nsecInterval;
|
||||
while ( targetPoint < now )
|
||||
targetPoint += nsecInterval;
|
||||
uint64_t targetPoint = vblank_next_target( offset );
|
||||
|
||||
sleep_until_nanos( targetPoint );
|
||||
|
||||
// give the time of vblank to steamcompmgr
|
||||
uint64_t vblanktime = get_time_in_nanos();
|
||||
VBlankTimeInfo_t time_info =
|
||||
{
|
||||
.target_vblank_time = targetPoint + offset,
|
||||
.pipe_write_time = get_time_in_nanos(),
|
||||
};
|
||||
|
||||
ssize_t ret = write( g_vblankPipe[ 1 ], &vblanktime, sizeof( vblanktime ) );
|
||||
ssize_t ret = write( g_vblankPipe[ 1 ], &time_info, sizeof( time_info ) );
|
||||
if ( ret <= 0 )
|
||||
{
|
||||
perror( "vblankmanager: write failed" );
|
||||
|
|
@ -182,22 +195,27 @@ void vblankThreadRun( void )
|
|||
}
|
||||
}
|
||||
|
||||
#if HAVE_OPENVR
|
||||
void vblankThreadVR()
|
||||
{
|
||||
pthread_setname_np( pthread_self(), "gamescope-vblkvr" );
|
||||
|
||||
while ( true )
|
||||
{
|
||||
#if HAVE_OPENVR
|
||||
vrsession_wait_until_visible();
|
||||
|
||||
// Includes redzone.
|
||||
vrsession_framesync( ~0u );
|
||||
#else
|
||||
abort();
|
||||
#endif
|
||||
|
||||
uint64_t vblanktime = get_time_in_nanos();
|
||||
ssize_t ret = write( g_vblankPipe[ 1 ], &vblanktime, sizeof( vblanktime ) );
|
||||
uint64_t now = 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,
|
||||
};
|
||||
|
||||
ssize_t ret = write( g_vblankPipe[ 1 ], &time_info, sizeof( time_info ) );
|
||||
if ( ret <= 0 )
|
||||
{
|
||||
perror( "vblankmanager: write failed" );
|
||||
|
|
@ -208,7 +226,7 @@ void vblankThreadVR()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int vblank_init( void )
|
||||
{
|
||||
|
|
@ -220,9 +238,17 @@ int vblank_init( void )
|
|||
|
||||
g_lastVblank = get_time_in_nanos();
|
||||
|
||||
std::thread vblankThread( BIsVRSession() ? vblankThreadVR : vblankThreadRun );
|
||||
vblankThread.detach();
|
||||
#if HAVE_OPENVR
|
||||
if ( BIsVRSession() )
|
||||
{
|
||||
std::thread vblankThread( vblankThreadVR );
|
||||
vblankThread.detach();
|
||||
return g_vblankPipe[ 0 ];
|
||||
}
|
||||
#endif
|
||||
|
||||
std::thread vblankThread( vblankThreadRun );
|
||||
vblankThread.detach();
|
||||
return g_vblankPipe[ 0 ];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
// Try to figure out when vblank is and notify steamcompmgr to render some time before it
|
||||
|
||||
struct VBlankTimeInfo_t
|
||||
{
|
||||
uint64_t target_vblank_time;
|
||||
uint64_t pipe_write_time;
|
||||
};
|
||||
|
||||
int vblank_init( void );
|
||||
|
||||
void vblank_mark_possible_vblank( uint64_t nanos );
|
||||
|
||||
uint64_t vblank_next_target( uint64_t offset = 0 );
|
||||
|
||||
extern std::atomic<uint64_t> g_uVblankDrawTimeNS;
|
||||
|
||||
const unsigned int g_uDefaultVBlankRedZone = 1'650'000;
|
||||
|
|
|
|||
193
src/wlserver.cpp
193
src/wlserver.cpp
|
|
@ -39,6 +39,7 @@ extern "C" {
|
|||
#include "gamescope-xwayland-protocol.h"
|
||||
#include "gamescope-pipewire-protocol.h"
|
||||
#include "gamescope-control-protocol.h"
|
||||
#include "gamescope-swapchain-protocol.h"
|
||||
#include "gamescope-tearing-control-unstable-v1-protocol.h"
|
||||
#include "presentation-time-protocol.h"
|
||||
|
||||
|
|
@ -83,7 +84,7 @@ std::mutex g_wlserver_xdg_shell_windows_lock;
|
|||
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 );
|
||||
static wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf);
|
||||
wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf);
|
||||
|
||||
std::vector<ResListEntry_t> gamescope_xwayland_server_t::retrieve_commits()
|
||||
{
|
||||
|
|
@ -108,7 +109,11 @@ void gamescope_xwayland_server_t::wayland_commit(struct wlr_surface *surf, struc
|
|||
.async = wlserver_surface_is_async(surf),
|
||||
.feedback = wlserver_surface_swapchain_feedback(surf),
|
||||
.presentation_feedbacks = std::move(wl_surf->pending_presentation_feedbacks),
|
||||
.present_id = wl_surf->present_id,
|
||||
.desired_present_time = wl_surf->desired_present_time,
|
||||
};
|
||||
wl_surf->present_id = std::nullopt;
|
||||
wl_surf->desired_present_time = 0;
|
||||
wl_surf->pending_presentation_feedbacks.clear();
|
||||
wayland_commit_queue.push_back( newEntry );
|
||||
}
|
||||
|
|
@ -428,7 +433,7 @@ static void wlserver_new_input(struct wl_listener *listener, void *data)
|
|||
|
||||
static struct wl_listener new_input_listener = { .notify = wlserver_new_input };
|
||||
|
||||
static wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf)
|
||||
wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf)
|
||||
{
|
||||
return reinterpret_cast<wlserver_wl_surface_info *>(wlr_surf->data);
|
||||
}
|
||||
|
|
@ -539,10 +544,8 @@ static void content_override_handle_surface_destroy( struct wl_listener *listene
|
|||
server->destroy_content_override( co );
|
||||
}
|
||||
|
||||
void gamescope_xwayland_server_t::handle_override_window_content( struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t x11_window )
|
||||
void gamescope_xwayland_server_t::handle_override_window_content( struct wl_client *client, struct wl_resource *resource, struct wlr_surface *surface, uint32_t x11_window )
|
||||
{
|
||||
struct wlr_surface *surface = wlr_surface_from_resource( surface_resource );
|
||||
|
||||
if ( content_overrides.count( x11_window ) ) {
|
||||
destroy_content_override( content_overrides[ x11_window ] );
|
||||
}
|
||||
|
|
@ -573,6 +576,10 @@ struct wlr_output *gamescope_xwayland_server_t::get_output()
|
|||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void gamescope_xwayland_handle_override_window_content( struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t x11_window )
|
||||
{
|
||||
// This should ideally use the surface's xwayland, but we don't know it.
|
||||
|
|
@ -589,18 +596,68 @@ static void gamescope_xwayland_handle_override_window_content( struct wl_client
|
|||
// So... Just assume it comes from server 0 for now.
|
||||
gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( 0 );
|
||||
assert( server );
|
||||
server->handle_override_window_content(client, resource, surface_resource, x11_window);
|
||||
struct wlr_surface *surface = wlr_surface_from_resource( surface_resource );
|
||||
server->handle_override_window_content(client, resource, surface, x11_window);
|
||||
}
|
||||
|
||||
static void gamescope_xwayland_handle_override_window_content2( struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t server_id, uint32_t x11_window )
|
||||
static void gamescope_xwayland_handle_destroy( struct wl_client *client, struct wl_resource *resource )
|
||||
{
|
||||
wl_resource_destroy( resource );
|
||||
}
|
||||
|
||||
static const struct gamescope_xwayland_interface gamescope_xwayland_impl = {
|
||||
.destroy = gamescope_xwayland_handle_destroy,
|
||||
.override_window_content = gamescope_xwayland_handle_override_window_content,
|
||||
};
|
||||
|
||||
static void gamescope_xwayland_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id )
|
||||
{
|
||||
struct wl_resource *resource = wl_resource_create( client, &gamescope_xwayland_interface, version, id );
|
||||
wl_resource_set_implementation( resource, &gamescope_xwayland_impl, NULL, NULL );
|
||||
}
|
||||
|
||||
static void create_gamescope_xwayland( void )
|
||||
{
|
||||
uint32_t version = 1;
|
||||
wl_global_create( wlserver.display, &gamescope_xwayland_interface, version, NULL, gamescope_xwayland_bind );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void gamescope_swapchain_destroy( struct wl_client *client, struct wl_resource *resource )
|
||||
{
|
||||
wlserver_wl_surface_info *wl_surface_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource );
|
||||
|
||||
wlserver_x11_surface_info *x11_surface = wl_surface_info->x11_surface;
|
||||
if (x11_surface)
|
||||
x11_surface->xwayland_server->destroy_content_override( x11_surface, wl_surface_info->wlr );
|
||||
|
||||
if (wl_surface_info->gamescope_swapchain == resource)
|
||||
wl_surface_info->gamescope_swapchain = nullptr;
|
||||
|
||||
wl_resource_destroy( resource );
|
||||
}
|
||||
|
||||
static void gamescope_swapchain_override_window_content( struct wl_client *client, struct wl_resource *resource, uint32_t server_id, uint32_t x11_window )
|
||||
{
|
||||
wlserver_wl_surface_info *wl_surface_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource );
|
||||
|
||||
gamescope_xwayland_server_t *server = wlserver_get_xwayland_server( server_id );
|
||||
assert( server );
|
||||
server->handle_override_window_content(client, resource, surface_resource, x11_window);
|
||||
server->handle_override_window_content(client, resource, wl_surface_info->wlr, x11_window);
|
||||
}
|
||||
|
||||
static void gamescope_xwayland_handle_swapchain_feedback( struct wl_client *client, struct wl_resource *resource,
|
||||
struct wl_resource *surface_resource,
|
||||
static void gamescope_swapchain_swapchain_feedback( struct wl_client *client, struct wl_resource *resource,
|
||||
uint32_t image_count,
|
||||
uint32_t vk_format,
|
||||
uint32_t vk_colorspace,
|
||||
|
|
@ -609,8 +666,7 @@ static void gamescope_xwayland_handle_swapchain_feedback( struct wl_client *clie
|
|||
uint32_t vk_present_mode,
|
||||
uint32_t vk_clipped)
|
||||
{
|
||||
struct wlr_surface *surface = wlr_surface_from_resource( surface_resource );
|
||||
wlserver_wl_surface_info *wl_info = get_wl_surface_info( surface );
|
||||
wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource );
|
||||
if ( wl_info )
|
||||
{
|
||||
wl_info->swapchain_feedback = std::make_unique<wlserver_vk_swapchain_feedback>(wlserver_vk_swapchain_feedback{
|
||||
|
|
@ -626,8 +682,7 @@ static void gamescope_xwayland_handle_swapchain_feedback( struct wl_client *clie
|
|||
}
|
||||
}
|
||||
|
||||
static void gamescope_xwayland_handle_set_hdr_metadata( struct wl_client *client, struct wl_resource *resource,
|
||||
struct wl_resource *surface_resource,
|
||||
static void gamescope_swapchain_set_hdr_metadata( struct wl_client *client, struct wl_resource *resource,
|
||||
uint32_t display_primary_red_x,
|
||||
uint32_t display_primary_red_y,
|
||||
uint32_t display_primary_green_x,
|
||||
|
|
@ -641,8 +696,7 @@ static void gamescope_xwayland_handle_set_hdr_metadata( struct wl_client *client
|
|||
uint32_t max_cll,
|
||||
uint32_t max_fall)
|
||||
{
|
||||
struct wlr_surface *surface = wlr_surface_from_resource( surface_resource );
|
||||
wlserver_wl_surface_info *wl_info = get_wl_surface_info( surface );
|
||||
wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource );
|
||||
if ( BIsNested() )
|
||||
{
|
||||
wl_log.infof("Ignoring HDR metadata when nested.");
|
||||
|
|
@ -685,31 +739,78 @@ static void gamescope_xwayland_handle_set_hdr_metadata( struct wl_client *client
|
|||
}
|
||||
}
|
||||
|
||||
static void gamescope_xwayland_handle_destroy( struct wl_client *client, struct wl_resource *resource )
|
||||
static void gamescope_swapchain_set_present_time( struct wl_client *client, struct wl_resource *resource,
|
||||
uint32_t present_id,
|
||||
uint32_t desired_present_time_hi,
|
||||
uint32_t desired_present_time_lo)
|
||||
{
|
||||
wlserver_wl_surface_info *wl_info = (wlserver_wl_surface_info *)wl_resource_get_user_data( resource );
|
||||
|
||||
if ( wl_info )
|
||||
{
|
||||
wl_info->present_id = present_id;
|
||||
wl_info->desired_present_time = (uint64_t(desired_present_time_hi) << 32) | desired_present_time_lo;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct gamescope_swapchain_interface gamescope_swapchain_impl = {
|
||||
.destroy = gamescope_swapchain_destroy,
|
||||
.override_window_content = gamescope_swapchain_override_window_content,
|
||||
.swapchain_feedback = gamescope_swapchain_swapchain_feedback,
|
||||
.set_hdr_metadata = gamescope_swapchain_set_hdr_metadata,
|
||||
.set_present_time = gamescope_swapchain_set_present_time,
|
||||
};
|
||||
|
||||
static void gamescope_swapchain_factory_destroy( struct wl_client *client, struct wl_resource *resource )
|
||||
{
|
||||
wl_resource_destroy( resource );
|
||||
}
|
||||
|
||||
static const struct gamescope_xwayland_interface gamescope_xwayland_impl = {
|
||||
.destroy = gamescope_xwayland_handle_destroy,
|
||||
.override_window_content = gamescope_xwayland_handle_override_window_content,
|
||||
.override_window_content2 = gamescope_xwayland_handle_override_window_content2,
|
||||
.swapchain_feedback = gamescope_xwayland_handle_swapchain_feedback,
|
||||
.set_hdr_metadata = gamescope_xwayland_handle_set_hdr_metadata,
|
||||
static void gamescope_swapchain_factory_create_swapchain( struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t id )
|
||||
{
|
||||
struct wlr_surface *surface = wlr_surface_from_resource( surface_resource );
|
||||
|
||||
wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface);
|
||||
|
||||
struct wl_resource *gamescope_swapchain_resource
|
||||
= wl_resource_create( client, &gamescope_swapchain_interface, wl_resource_get_version( resource ), id );
|
||||
wl_resource_set_implementation( gamescope_swapchain_resource, &gamescope_swapchain_impl, wl_surface_info, NULL );
|
||||
|
||||
if (wl_surface_info->gamescope_swapchain != nullptr)
|
||||
wl_log.errorf("create_swapchain: Surface already had a gamescope_swapchain! Overriding.");
|
||||
|
||||
wl_surface_info->gamescope_swapchain = gamescope_swapchain_resource;
|
||||
}
|
||||
|
||||
static const struct gamescope_swapchain_factory_interface gamescope_swapchain_factory_impl = {
|
||||
.destroy = gamescope_swapchain_factory_destroy,
|
||||
.create_swapchain = gamescope_swapchain_factory_create_swapchain,
|
||||
};
|
||||
|
||||
static void gamescope_xwayland_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id )
|
||||
static void gamescope_swapchain_factory_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id )
|
||||
{
|
||||
struct wl_resource *resource = wl_resource_create( client, &gamescope_xwayland_interface, version, id );
|
||||
wl_resource_set_implementation( resource, &gamescope_xwayland_impl, NULL, NULL );
|
||||
struct wl_resource *resource = wl_resource_create( client, &gamescope_swapchain_factory_interface, version, id );
|
||||
wl_resource_set_implementation( resource, &gamescope_swapchain_factory_impl, NULL, NULL );
|
||||
}
|
||||
|
||||
static void create_gamescope_xwayland( void )
|
||||
static void create_gamescope_swapchain_factory( void )
|
||||
{
|
||||
uint32_t version = 2;
|
||||
wl_global_create( wlserver.display, &gamescope_xwayland_interface, version, NULL, gamescope_xwayland_bind );
|
||||
uint32_t version = 1;
|
||||
wl_global_create( wlserver.display, &gamescope_swapchain_factory_interface, version, NULL, gamescope_swapchain_factory_bind );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if HAVE_PIPEWIRE
|
||||
static void gamescope_pipewire_handle_destroy( struct wl_client *client, struct wl_resource *resource )
|
||||
{
|
||||
|
|
@ -854,7 +955,7 @@ static void create_presentation_time( void )
|
|||
wl_global_create( wlserver.display, &wp_presentation_interface, version, NULL, presentation_time_bind );
|
||||
}
|
||||
|
||||
void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std::vector<struct wl_resource*>& presentation_feedbacks, uint64_t last_refresh_nsec, int refresh_rate )
|
||||
void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std::vector<struct wl_resource*>& presentation_feedbacks, uint64_t last_refresh_nsec, uint64_t refresh_cycle )
|
||||
{
|
||||
wlserver_wl_surface_info *wl_surface_info = get_wl_surface_info(surface);
|
||||
|
||||
|
|
@ -879,8 +980,6 @@ void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std:
|
|||
|
||||
for (auto& feedback : presentation_feedbacks)
|
||||
{
|
||||
const uint64_t nsecInterval = 1'000'000'000ul / refresh_rate;
|
||||
|
||||
timespec last_refresh_ts;
|
||||
last_refresh_ts.tv_sec = time_t(last_refresh_nsec / 1'000'000'000ul);
|
||||
last_refresh_ts.tv_nsec = long(last_refresh_nsec % 1'000'000'000ul);
|
||||
|
|
@ -890,7 +989,7 @@ void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std:
|
|||
last_refresh_ts.tv_sec >> 32,
|
||||
last_refresh_ts.tv_sec & 0xffffffff,
|
||||
last_refresh_ts.tv_nsec,
|
||||
uint32_t(nsecInterval),
|
||||
uint32_t(refresh_cycle),
|
||||
wl_surface_info->sequence >> 32,
|
||||
wl_surface_info->sequence & 0xffffffff,
|
||||
flags);
|
||||
|
|
@ -915,6 +1014,32 @@ 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 );
|
||||
gamescope_swapchain_send_past_present_timing(
|
||||
wl_info->gamescope_swapchain,
|
||||
present_id,
|
||||
desired_present_time >> 32,
|
||||
desired_present_time & 0xffffffff,
|
||||
actual_present_time >> 32,
|
||||
actual_present_time & 0xffffffff,
|
||||
earliest_present_time >> 32,
|
||||
earliest_present_time & 0xffffffff,
|
||||
present_margin >> 32,
|
||||
present_margin & 0xffffffff);
|
||||
}
|
||||
|
||||
void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle )
|
||||
{
|
||||
wlserver_wl_surface_info *wl_info = get_wl_surface_info( surface );
|
||||
gamescope_swapchain_send_refresh_cycle(
|
||||
wl_info->gamescope_swapchain,
|
||||
refresh_cycle >> 32,
|
||||
refresh_cycle & 0xffffffff);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
static void handle_session_active( struct wl_listener *listener, void *data )
|
||||
{
|
||||
|
|
@ -1267,6 +1392,8 @@ bool wlserver_init( void ) {
|
|||
|
||||
create_gamescope_xwayland();
|
||||
|
||||
create_gamescope_swapchain_factory();
|
||||
|
||||
#if HAVE_PIPEWIRE
|
||||
create_gamescope_pipewire();
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ struct ResListEntry_t {
|
|||
bool async;
|
||||
std::shared_ptr<wlserver_vk_swapchain_feedback> feedback;
|
||||
std::vector<struct wl_resource*> presentation_feedbacks;
|
||||
std::optional<uint32_t> present_id;
|
||||
uint64_t desired_present_time;
|
||||
};
|
||||
|
||||
struct wlserver_content_override;
|
||||
|
|
@ -66,7 +68,7 @@ public:
|
|||
|
||||
std::vector<ResListEntry_t> retrieve_commits();
|
||||
|
||||
void handle_override_window_content( struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t x11_window );
|
||||
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);
|
||||
void destroy_content_override(struct wlserver_content_override *co);
|
||||
|
||||
|
|
@ -235,7 +237,14 @@ struct wlserver_wl_surface_info
|
|||
|
||||
uint64_t sequence = 0;
|
||||
std::vector<struct wl_resource*> pending_presentation_feedbacks;
|
||||
|
||||
std::atomic<struct wl_resource *> gamescope_swapchain = { nullptr };
|
||||
std::optional<uint32_t> present_id = std::nullopt;
|
||||
uint64_t desired_present_time = 0;
|
||||
|
||||
uint64_t last_refresh_cycle = 0;
|
||||
};
|
||||
wlserver_wl_surface_info *get_wl_surface_info(struct wlr_surface *wlr_surf);
|
||||
|
||||
void wlserver_x11_surface_info_init( struct wlserver_x11_surface_info *surf, gamescope_xwayland_server_t *server, uint32_t x11_id );
|
||||
void wlserver_x11_surface_info_finish( struct wlserver_x11_surface_info *surf );
|
||||
|
|
@ -249,5 +258,8 @@ void wlserver_open_steam_menu( bool qam );
|
|||
uint32_t wlserver_make_new_xwayland_server();
|
||||
void wlserver_destroy_xwayland_server(gamescope_xwayland_server_t *server);
|
||||
|
||||
void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std::vector<struct wl_resource*>& presentation_feedbacks, uint64_t last_refresh_nsec, int refresh_rate );
|
||||
void wlserver_presentation_feedback_presented( struct wlr_surface *surface, std::vector<struct wl_resource*>& presentation_feedbacks, uint64_t last_refresh_nsec, uint64_t refresh_cycle );
|
||||
void wlserver_presentation_feedback_discard( struct wlr_surface *surface, std::vector<struct wl_resource*>& presentation_feedbacks );
|
||||
|
||||
void wlserver_past_present_timing( struct wlr_surface *surface, uint32_t present_id, uint64_t desired_present_time, uint64_t actual_present_time, uint64_t earliest_present_time, uint64_t present_margin );
|
||||
void wlserver_refresh_cycle( struct wlr_surface *surface, uint64_t refresh_cycle );
|
||||
|
|
|
|||
|
|
@ -33,10 +33,18 @@ struct focus_t
|
|||
bool outdatedInteractiveFocus;
|
||||
};
|
||||
|
||||
struct CommitDoneEntry_t
|
||||
{
|
||||
uint64_t commitID;
|
||||
uint64_t desiredPresentTime;
|
||||
uint64_t earliestPresentTime;
|
||||
uint64_t earliestLatchTime;
|
||||
};
|
||||
|
||||
struct CommitDoneList_t
|
||||
{
|
||||
std::mutex listCommitsDoneLock;
|
||||
std::vector< uint64_t > listCommitsDone;
|
||||
std::vector< CommitDoneEntry_t > listCommitsDone;
|
||||
};
|
||||
|
||||
struct xwayland_ctx_t
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue