diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp index 5844c2a633..2d52fb51a4 100644 --- a/layer/VkLayer_FROG_gamescope_wsi.cpp +++ b/layer/VkLayer_FROG_gamescope_wsi.cpp @@ -975,7 +975,9 @@ namespace GamescopeWSILayer { continue; } + xcb::Prefetcher prefetcher(gamescopeSurface->connection, gamescopeSurface->window); const bool canBypass = gamescopeSurface->canBypassXWayland(); + if (canBypass != gamescopeSwapchain->isBypassingXWayland) UpdateSwapchainResult(canBypass ? VK_SUBOPTIMAL_KHR : VK_ERROR_OUT_OF_DATE_KHR); } diff --git a/layer/xcb_helpers.hpp b/layer/xcb_helpers.hpp index 8fac5635b3..ef38632ad6 100644 --- a/layer/xcb_helpers.hpp +++ b/layer/xcb_helpers.hpp @@ -4,9 +4,56 @@ #include #include #include +#include + +//extra includes below only needed for clang-tidy to work: +#include +#include +#include +#include namespace xcb { + typedef struct { + xcb_window_t window; + xcb_get_geometry_cookie_t geo; + xcb_query_tree_cookie_t q_tree; + std::tuple cached_replies; + } cookie_cache_t; + + static thread_local constinit bool g_cache_bIsValid = false; //thread_local just incase g_cache could otherwise be accessed by one thread, while it is being deleted by another thread + static constinit cookie_cache_t g_cache = {}; + + class Prefetcher + { //Note: this class is currently only meant to be used within GamescopeWSILayer::VkDeviceOverrides::QueuePresentKHR + + //The g_class struct has a thread local g_cache_bIsValid boolean, + //to prevent any issue with another thread calling another GamescopeWSILayer:: function which calls an xcb:: func + //that might otherwise access g_cache while g_cache is being cleared at the end of GamescopeWSILayer::VkDeviceOverrides::QueuePresentKHR() + public: + Prefetcher() = delete; + explicit Prefetcher(xcb_connection_t* __restrict__ connection, const xcb_window_t window) { + g_cache = { + .window = window, + .geo = xcb_get_geometry(connection, window), + .q_tree = xcb_query_tree(connection, window) + }; + g_cache_bIsValid = true; + } + + ~Prefetcher() { + g_cache_bIsValid = false; + if ( std::get<0>(g_cache.cached_replies) != nullptr ) { + free(std::get<0>(g_cache.cached_replies)); + std::get<0>(g_cache.cached_replies) = nullptr; + } + if ( std::get<1>(g_cache.cached_replies) != nullptr ) { + free(std::get<1>(g_cache.cached_replies)); + std::get<1>(g_cache.cached_replies) = nullptr; + } + } + }; + struct ReplyDeleter { template void operator()(T* ptr) const { @@ -16,10 +63,99 @@ namespace xcb { template using Reply = std::unique_ptr; + + static Reply CachedReply(xcb_connection_t* __restrict__ connection, const xcb_get_geometry_cookie_t cookie); + static Reply CachedReply(xcb_connection_t* __restrict__ connection, const xcb_query_tree_cookie_t cookie); + + //using constexpr constructor, so that all type deduction is figured out via the xcb cookie/reply funcs at compile time + template + class XcbFetch { + using cookie_f_ptr_t = cookie_retType (*)(first, ArgsTwo...); + using reply_f_ptr_t = reply_retType (*)(first, cookie_retType, xcb_generic_error_t**); + + const cookie_f_ptr_t m_cookieFunc; + const reply_f_ptr_t m_replyFunc; + + public: + consteval XcbFetch(cookie_f_ptr_t cookieFunc, reply_f_ptr_t replyFunc) : m_cookieFunc{cookieFunc}, m_replyFunc{replyFunc} {} + + inline Reply> operator()(first conn, auto... argsTwo) { //have to use auto for argsTwo, since otherwise there'd be a type deduction conflict + return Reply> { m_replyFunc(conn, m_cookieFunc(conn, argsTwo...), nullptr) }; + } + }; + + template <> + class XcbFetch { + using cookie_f_ptr_t = xcb_get_geometry_cookie_t (*)(xcb_connection_t*, xcb_window_t); + using reply_f_ptr_t = xcb_get_geometry_reply_t* (*)(xcb_connection_t*, xcb_get_geometry_cookie_t, xcb_generic_error_t**); + + const cookie_f_ptr_t m_cookieFunc; + const reply_f_ptr_t m_replyFunc; + + public: + consteval XcbFetch(cookie_f_ptr_t cookieFunc, reply_f_ptr_t replyFunc) : m_cookieFunc{cookieFunc}, m_replyFunc{replyFunc} {} + + inline Reply operator()(xcb_connection_t* conn, xcb_window_t window) { + const bool tryCached = (g_cache_bIsValid && g_cache.window == window); + + xcb_get_geometry_cookie_t cookie = tryCached ? g_cache.geo : m_cookieFunc(conn, window); + + if (tryCached) [[likely]] + return CachedReply(conn, cookie); + return Reply { m_replyFunc(conn, cookie, nullptr) }; + } + }; + + template <> + class XcbFetch { + using cookie_f_ptr_t = xcb_query_tree_cookie_t (*)(xcb_connection_t*, xcb_window_t); + using reply_f_ptr_t = xcb_query_tree_reply_t* (*)(xcb_connection_t*, xcb_query_tree_cookie_t, xcb_generic_error_t**); + + const cookie_f_ptr_t m_cookieFunc; + const reply_f_ptr_t m_replyFunc; + + public: + consteval XcbFetch(cookie_f_ptr_t cookieFunc, reply_f_ptr_t replyFunc) : m_cookieFunc{cookieFunc}, m_replyFunc{replyFunc} {} + + inline Reply operator()(xcb_connection_t* conn, xcb_window_t window) { + const bool tryCached = (g_cache_bIsValid && g_cache.window == window); + + xcb_query_tree_cookie_t cookie = tryCached ? g_cache.q_tree : m_cookieFunc(conn, window); + + if (tryCached) [[likely]] + return CachedReply(conn, cookie); + return Reply { m_replyFunc(conn, cookie, nullptr) }; + } + }; + static Reply CachedReply(xcb_connection_t* __restrict__ connection, const xcb_get_geometry_cookie_t cookie) + { //using aligned_alloc, so that some of the vectorized moves in memcpy will be aligned instead of unaligned + //(better performance on older cpus) + static constexpr size_t alignment = 128, space = sizeof(xcb_get_geometry_reply_t); + xcb_get_geometry_reply_t* __restrict__ reply_copied = reinterpret_cast(std::aligned_alloc(alignment,space)); + if (std::get<0>(g_cache.cached_replies) == nullptr) { + std::get<0>(g_cache.cached_replies) = xcb_get_geometry_reply(connection, cookie, nullptr); + } + + std::memcpy(reply_copied,std::get<0>(g_cache.cached_replies), space); + return Reply{reply_copied}; + } + + static Reply CachedReply(xcb_connection_t* __restrict__ connection, const xcb_query_tree_cookie_t cookie) + { //using aligned_alloc, so that some of the vectorized moves in memcpy will be aligned instead of unaligned + //(better performance on older cpus) + static constexpr size_t alignment = 256, space = sizeof(xcb_query_tree_reply_t); + xcb_query_tree_reply_t* __restrict__ reply_copied = reinterpret_cast(std::aligned_alloc(alignment,space)); + if (std::get<1>(g_cache.cached_replies) == nullptr) { + std::get<1>(g_cache.cached_replies) = xcb_query_tree_reply(connection, cookie, nullptr); + } + + std::memcpy( reply_copied, std::get<1>(g_cache.cached_replies), space); + return Reply{reply_copied}; + } + static std::optional getAtom(xcb_connection_t* connection, std::string_view name) { - xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, false, name.length(), name.data()); - auto reply = Reply{ xcb_intern_atom_reply(connection, cookie, nullptr) }; + auto reply = XcbFetch{xcb_intern_atom, xcb_intern_atom_reply}(connection, false, name.length(), name.data()); if (!reply) { fprintf(stderr, "[Gamescope WSI] Failed to get xcb atom.\n"); return std::nullopt; @@ -34,8 +170,7 @@ namespace xcb { xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data; - xcb_get_property_cookie_t cookie = xcb_get_property(connection, false, screen->root, atom, XCB_ATOM_CARDINAL, 0, sizeof(T) / sizeof(uint32_t)); - auto reply = Reply{ xcb_get_property_reply(connection, cookie, nullptr) }; + auto reply = XcbFetch{xcb_get_property, xcb_get_property_reply}(connection, false, screen->root, atom, XCB_ATOM_CARDINAL, 0, sizeof(T) / sizeof(uint32_t)); if (!reply) { fprintf(stderr, "[Gamescope WSI] Failed to read T root window property.\n"); return std::nullopt; @@ -61,8 +196,7 @@ namespace xcb { static std::optional getToplevelWindow(xcb_connection_t* connection, xcb_window_t window) { for (;;) { - xcb_query_tree_cookie_t cookie = xcb_query_tree(connection, window); - auto reply = Reply{ xcb_query_tree_reply(connection, cookie, nullptr) }; + auto reply = XcbFetch{xcb_query_tree, xcb_query_tree_reply}(connection, window); if (!reply) { fprintf(stderr, "[Gamescope WSI] getToplevelWindow: xcb_query_tree failed for window 0x%x.\n", window); @@ -77,8 +211,8 @@ namespace xcb { } static std::optional getWindowRect(xcb_connection_t* connection, xcb_window_t window) { - xcb_get_geometry_cookie_t cookie = xcb_get_geometry(connection, window); - auto reply = Reply{ xcb_get_geometry_reply(connection, cookie, nullptr) }; + auto reply = XcbFetch{xcb_get_geometry, xcb_get_geometry_reply}(connection, window); + if (!reply) { fprintf(stderr, "[Gamescope WSI] getWindowRect: xcb_get_geometry failed for window 0x%x.\n", window); return std::nullopt; @@ -112,8 +246,7 @@ namespace xcb { static std::optional getLargestObscuringChildWindowSize(xcb_connection_t* connection, xcb_window_t window) { VkExtent2D largestExtent = {}; - xcb_query_tree_cookie_t cookie = xcb_query_tree(connection, window); - auto reply = Reply{ xcb_query_tree_reply(connection, cookie, nullptr) }; + auto reply = XcbFetch{xcb_query_tree, xcb_query_tree_reply}(connection, window); if (!reply) { fprintf(stderr, "[Gamescope WSI] getLargestObscuringWindowSize: xcb_query_tree failed for window 0x%x.\n", window); @@ -130,8 +263,7 @@ namespace xcb { for (uint32_t i = 0; i < reply->children_len; i++) { xcb_window_t child = children[i]; - xcb_get_window_attributes_cookie_t attributeCookie = xcb_get_window_attributes(connection, child); - auto attributeReply = Reply{ xcb_get_window_attributes_reply(connection, attributeCookie, nullptr) }; + auto attributeReply = XcbFetch{xcb_get_window_attributes, xcb_get_window_attributes_reply}(connection, child); const bool obscuring = attributeReply &&