From ab115896be1a448bde0eb7673c26300ea4ca5040 Mon Sep 17 00:00:00 2001 From: sharkautarch <128002472+sharkautarch@users.noreply.github.com> Date: Sun, 19 May 2024 20:15:36 -0400 Subject: [PATCH 1/2] QueuePresent: canBypassXWayland(): fetch multiple xcb cookies initially before waiting on any of them --- layer/VkLayer_FROG_gamescope_wsi.cpp | 1 + layer/xcb_helpers.hpp | 105 +++++++++++++++++++++++---- 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp index 5844c2a63..ca44849f2 100644 --- a/layer/VkLayer_FROG_gamescope_wsi.cpp +++ b/layer/VkLayer_FROG_gamescope_wsi.cpp @@ -975,6 +975,7 @@ 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 8fac5635b..72d0ec092 100644 --- a/layer/xcb_helpers.hpp +++ b/layer/xcb_helpers.hpp @@ -4,22 +4,106 @@ #include #include #include +#include namespace xcb { + inline static constinit pthread_t g_cache_tid; //incase g_cache could otherwise be accessed by one thread, while it is being deleted by another thread + inline static constinit struct cookie_cache_t { + xcb_window_t window; + std::tuple cached_cookies; + std::tuple cached_replies; + } g_cache = {}; + + //Note: this class is currently only meant to be used within GamescopeWSILayer::VkDeviceOverrides::QueuePresentKHR: + struct Prefetcher { + explicit Prefetcher(xcb_connection_t* connection, const xcb_window_t window) { + g_cache = { + .window = window, + .cached_cookies = { + xcb_get_geometry(connection, window), + xcb_query_tree(connection, window) + } + }; + g_cache_tid = pthread_self(); + } + ~Prefetcher() { + g_cache_tid = {}; + free(std::get<0>(g_cache.cached_replies)); + free(std::get<1>(g_cache.cached_replies)); + g_cache.cached_replies = {nullptr,nullptr}; + } + }; + struct ReplyDeleter { + const bool m_bOwning = true; + consteval ReplyDeleter(bool bOwning = true) : m_bOwning{bOwning} {} template void operator()(T* ptr) const { - free(const_cast*>(ptr)); + if (m_bOwning) + free(const_cast*>(ptr)); } }; template using Reply = std::unique_ptr; + + template + class XcbFetch { + using cookie_f_ptr_t = Cookie_RetType (*)(XcbConn, Args...); + using reply_f_ptr_t = Reply_RetType* (*)(XcbConn, 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()(XcbConn conn, auto... args) { //have to use auto for argsTwo, since otherwise there'd be a type deduction conflict + return Reply { m_replyFunc(conn, m_cookieFunc(conn, args...), nullptr) }; + } + }; + + template + concept CacheableCookie = std::is_same::value + || std::is_same::value; + + template + class XcbFetch { + using cookie_f_ptr_t = Cookie_RetType (*)(xcb_connection_t*, xcb_window_t); + using reply_f_ptr_t = Reply_RetType* (*)(xcb_connection_t*, Cookie_RetType, xcb_generic_error_t**); + + const cookie_f_ptr_t m_cookieFunc; + const reply_f_ptr_t m_replyFunc; + + inline Reply getCachedReply(xcb_connection_t* connection) { + if (std::get(g_cache.cached_replies) == nullptr) { + std::get(g_cache.cached_replies) = m_replyFunc(connection, std::get(g_cache.cached_cookies), nullptr); + } + return Reply{std::get(g_cache.cached_replies), ReplyDeleter{false}}; // return 'non-owning' unique_ptr + } + + 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 = pthread_equal(g_cache_tid, pthread_self()) + && g_cache.window == window; + if (!tryCached) [[unlikely]] + return Reply { m_replyFunc(conn, m_cookieFunc(conn, window), nullptr) }; + + auto ret = getCachedReply(conn); + #if !defined(NDEBUG) || NDEBUG == 0 + if (!ret) + fprintf(stderr, "[Gamescope WSI] getCachedReply() failed.\n"); + #endif + return ret; + } + }; + 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 +118,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 +144,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 +159,7 @@ 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 +193,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 +210,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 && From 1b59621f4de5c05096d1f279cba2e04264124154 Mon Sep 17 00:00:00 2001 From: sharkautarch <128002472+sharkautarch@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:21:23 -0400 Subject: [PATCH 2/2] WSI: prefetcher: fix issue w/ attempting to prefetch xcb stuff for pure wayland surfaces --- layer/VkLayer_FROG_gamescope_wsi.cpp | 2 +- layer/xcb_helpers.hpp | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/layer/VkLayer_FROG_gamescope_wsi.cpp b/layer/VkLayer_FROG_gamescope_wsi.cpp index f26819a60..ce011dcd7 100644 --- a/layer/VkLayer_FROG_gamescope_wsi.cpp +++ b/layer/VkLayer_FROG_gamescope_wsi.cpp @@ -1234,7 +1234,7 @@ namespace GamescopeWSILayer { continue; } - xcb::Prefetcher prefetcher(gamescopeSurface->connection, gamescopeSurface->window); + auto prefetcher = xcb::Prefetcher::GetPrefetcherIf(!gamescopeSurface->isWayland(), 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 72d0ec092..f26aef38b 100644 --- a/layer/xcb_helpers.hpp +++ b/layer/xcb_helpers.hpp @@ -16,6 +16,13 @@ namespace xcb { //Note: this class is currently only meant to be used within GamescopeWSILayer::VkDeviceOverrides::QueuePresentKHR: struct Prefetcher { + static std::optional GetPrefetcherIf(bool bCond, xcb_connection_t* connection, const xcb_window_t window) { + if (bCond) + return std::optional(std::in_place_t{}, connection, window); + + return std::nullopt; + } + explicit Prefetcher(xcb_connection_t* connection, const xcb_window_t window) { g_cache = { .window = window, @@ -90,7 +97,7 @@ namespace xcb { inline Reply operator()(xcb_connection_t* conn, xcb_window_t window) { const bool tryCached = pthread_equal(g_cache_tid, pthread_self()) && g_cache.window == window; - if (!tryCached) [[unlikely]] + if (!tryCached) return Reply { m_replyFunc(conn, m_cookieFunc(conn, window), nullptr) }; auto ret = getCachedReply(conn);