Skip to content

Commit

Permalink
QueuePresent: canBypassXWayland(): fetch multiple xcb cookies initial…
Browse files Browse the repository at this point in the history
…ly before waiting on any of them
  • Loading branch information
sharkautarch committed May 20, 2024
1 parent 14a1db3 commit ab11589
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 13 deletions.
1 change: 1 addition & 0 deletions layer/VkLayer_FROG_gamescope_wsi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
105 changes: 92 additions & 13 deletions layer/xcb_helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,106 @@
#include <xcb/composite.h>
#include <cstdio>
#include <optional>
#include <pthread.h>

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<xcb_get_geometry_cookie_t, xcb_query_tree_cookie_t> cached_cookies;
std::tuple<xcb_get_geometry_reply_t*, xcb_query_tree_reply_t*> 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 <typename T>
void operator()(T* ptr) const {
free(const_cast<std::remove_const_t<T>*>(ptr));
if (m_bOwning)
free(const_cast<std::remove_const_t<T>*>(ptr));
}
};

template <typename T>
using Reply = std::unique_ptr<T, ReplyDeleter>;

template <typename Cookie_RetType, typename Reply_RetType, typename XcbConn=xcb_connection_t*, typename... Args>
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<Reply_RetType> operator()(XcbConn conn, auto... args) { //have to use auto for argsTwo, since otherwise there'd be a type deduction conflict
return Reply<Reply_RetType> { m_replyFunc(conn, m_cookieFunc(conn, args...), nullptr) };
}
};

template <typename CookieType>
concept CacheableCookie = std::is_same<CookieType, xcb_get_geometry_cookie_t>::value
|| std::is_same<CookieType, xcb_query_tree_cookie_t>::value;

template <CacheableCookie Cookie_RetType, typename Reply_RetType>
class XcbFetch<Cookie_RetType, Reply_RetType, xcb_connection_t*, xcb_window_t> {
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<Reply_RetType> getCachedReply(xcb_connection_t* connection) {
if (std::get<Reply_RetType*>(g_cache.cached_replies) == nullptr) {
std::get<Reply_RetType*>(g_cache.cached_replies) = m_replyFunc(connection, std::get<Cookie_RetType>(g_cache.cached_cookies), nullptr);
}

return Reply<Reply_RetType>{std::get<Reply_RetType*>(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<Reply_RetType> 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<Reply_RetType> { 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<xcb_atom_t> 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_t>{ 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;
Expand All @@ -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_t>{ 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;
Expand All @@ -61,8 +144,7 @@ namespace xcb {

static std::optional<xcb_window_t> 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_t>{ 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);
Expand All @@ -77,8 +159,7 @@ namespace xcb {
}

static std::optional<VkRect2D> 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_t>{ 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;
Expand Down Expand Up @@ -112,8 +193,7 @@ namespace xcb {
static std::optional<VkExtent2D> 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_t>{ 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);
Expand All @@ -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_t>{ 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 &&
Expand Down

0 comments on commit ab11589

Please sign in to comment.