OpenGLDevice: Support both XCB and Xlib
Some checks failed
Create rolling release / Windows x64 Build (push) Has been cancelled
Create rolling release / Windows x64 SSE2 Build (push) Has been cancelled
Create rolling release / Windows ARM64 Build (push) Has been cancelled
Create rolling release / Linux x64 AppImage (push) Has been cancelled
Create rolling release / Linux x64 SSE2 AppImage (push) Has been cancelled
Create rolling release / Linux Flatpak Build (push) Has been cancelled
Create rolling release / MacOS Universal Build (push) Has been cancelled
Create rolling release / Create Release (push) Has been cancelled

Required for NVIDIA+XWayland.
This commit is contained in:
Stenzek 2024-11-11 20:12:26 +10:00
parent 816ef45199
commit e69f0d3cce
No known key found for this signature in database
27 changed files with 708 additions and 279 deletions

View File

@ -28,8 +28,8 @@ endif()
if(ENABLE_X11) if(ENABLE_X11)
find_package(X11 REQUIRED) find_package(X11 REQUIRED)
if (NOT X11_Xrandr_FOUND) if (NOT X11_xcb_FOUND OR NOT X11_xcb_randr_FOUND OR NOT X11_X11_xcb_FOUND)
message(FATAL_ERROR "XRandR extension is required") message(FATAL_ERROR "XCB, XCB-randr and X11-xcb are required")
endif() endif()
endif() endif()

View File

@ -58,9 +58,9 @@ int DisplayWidget::scaledWindowHeight() const
static_cast<int>(std::ceil(static_cast<qreal>(height()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1); static_cast<int>(std::ceil(static_cast<qreal>(height()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1);
} }
std::optional<WindowInfo> DisplayWidget::getWindowInfo(Error* error) std::optional<WindowInfo> DisplayWidget::getWindowInfo(RenderAPI render_api, Error* error)
{ {
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWidget(this, error)); std::optional<WindowInfo> ret = QtUtils::GetWindowInfoForWidget(this, render_api, error);
if (ret.has_value()) if (ret.has_value())
{ {
m_last_window_width = ret->surface_width; m_last_window_width = ret->surface_width;

View File

@ -13,6 +13,8 @@
class Error; class Error;
enum class RenderAPI : u8;
class QCloseEvent; class QCloseEvent;
class DisplayWidget final : public QWidget class DisplayWidget final : public QWidget
@ -28,7 +30,7 @@ public:
int scaledWindowWidth() const; int scaledWindowWidth() const;
int scaledWindowHeight() const; int scaledWindowHeight() const;
std::optional<WindowInfo> getWindowInfo(Error* error); std::optional<WindowInfo> getWindowInfo(RenderAPI render_api, Error* error);
void updateRelativeMode(bool enabled); void updateRelativeMode(bool enabled);
void updateCursor(bool hidden); void updateCursor(bool hidden);

View File

@ -221,8 +221,8 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
#endif #endif
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless, std::optional<WindowInfo> MainWindow::acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool render_to_main,
bool use_main_window_pos, Error* error) bool surfaceless, bool use_main_window_pos, Error* error)
{ {
DEV_LOG("acquireRenderWindow() fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}", DEV_LOG("acquireRenderWindow() fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}",
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false",
@ -265,7 +265,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool fullscreen, bool
updateWindowState(); updateWindowState();
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
return m_display_widget->getWindowInfo(error); return m_display_widget->getWindowInfo(render_api, error);
} }
destroyDisplayWidget(surfaceless); destroyDisplayWidget(surfaceless);
@ -277,7 +277,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool fullscreen, bool
createDisplayWidget(fullscreen, render_to_main, use_main_window_pos); createDisplayWidget(fullscreen, render_to_main, use_main_window_pos);
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo(error); std::optional<WindowInfo> wi = m_display_widget->getWindowInfo(render_api, error);
if (!wi.has_value()) if (!wi.has_value())
{ {
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
@ -2483,9 +2483,9 @@ void MainWindow::checkForSettingChanges()
std::optional<WindowInfo> MainWindow::getWindowInfo() std::optional<WindowInfo> MainWindow::getWindowInfo()
{ {
if (!m_display_widget || isRenderingToMain()) if (!m_display_widget || isRenderingToMain())
return QtUtils::GetWindowInfoForWidget(this); return QtUtils::GetWindowInfoForWidget(this, RenderAPI::None);
else if (QWidget* widget = getDisplayContainer()) else if (QWidget* widget = getDisplayContainer())
return QtUtils::GetWindowInfoForWidget(widget); return QtUtils::GetWindowInfoForWidget(widget, RenderAPI::None);
else else
return std::nullopt; return std::nullopt;
} }
@ -2565,7 +2565,7 @@ void MainWindow::onAchievementsChallengeModeChanged(bool enabled)
updateEmulationActions(false, System::IsValid(), enabled); updateEmulationActions(false, System::IsValid(), enabled);
} }
bool MainWindow::onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width, quint32 height, const QString& title, bool MainWindow::onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, qint32 y, quint32 width, quint32 height, const QString& title,
const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata, const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error) Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error)
{ {
@ -2573,7 +2573,7 @@ bool MainWindow::onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width
if (!widget) if (!widget)
return false; return false;
const std::optional<WindowInfo> owi = QtUtils::GetWindowInfoForWidget(widget, error); const std::optional<WindowInfo> owi = QtUtils::GetWindowInfoForWidget(widget, render_api, error);
if (!owi.has_value()) if (!owi.has_value())
{ {
widget->destroy(); widget->destroy();

View File

@ -35,6 +35,7 @@ class MemoryScannerWindow;
struct SystemBootParameters; struct SystemBootParameters;
enum class RenderAPI : u8;
class GPUDevice; class GPUDevice;
namespace Achievements { namespace Achievements {
enum class LoginRequestReason; enum class LoginRequestReason;
@ -127,8 +128,8 @@ private Q_SLOTS:
bool confirmMessage(const QString& title, const QString& message); bool confirmMessage(const QString& title, const QString& message);
void onStatusMessage(const QString& message); void onStatusMessage(const QString& message);
std::optional<WindowInfo> acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless, std::optional<WindowInfo> acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool render_to_main,
bool use_main_window_pos, Error* error); bool surfaceless, bool use_main_window_pos, Error* error);
void displayResizeRequested(qint32 width, qint32 height); void displayResizeRequested(qint32 width, qint32 height);
void releaseRenderWindow(); void releaseRenderWindow();
void focusDisplayWidget(); void focusDisplayWidget();
@ -145,8 +146,9 @@ private Q_SLOTS:
void onMediaCaptureStopped(); void onMediaCaptureStopped();
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason); void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
void onAchievementsChallengeModeChanged(bool enabled); void onAchievementsChallengeModeChanged(bool enabled);
bool onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width, quint32 height, const QString& title, bool onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, qint32 y, quint32 width, quint32 height,
const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata, const QString& title, const QString& icon_name,
Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error); Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error);
void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size); void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size);

View File

@ -133,6 +133,7 @@ void QtHost::RegisterTypes()
qRegisterMetaType<std::function<void()>>("std::function<void()>"); qRegisterMetaType<std::function<void()>>("std::function<void()>");
qRegisterMetaType<std::shared_ptr<SystemBootParameters>>(); qRegisterMetaType<std::shared_ptr<SystemBootParameters>>();
qRegisterMetaType<const GameList::Entry*>(); qRegisterMetaType<const GameList::Entry*>();
qRegisterMetaType<RenderAPI>("RenderAPI");
qRegisterMetaType<GPURenderer>("GPURenderer"); qRegisterMetaType<GPURenderer>("GPURenderer");
qRegisterMetaType<InputBindingKey>("InputBindingKey"); qRegisterMetaType<InputBindingKey>("InputBindingKey");
qRegisterMetaType<std::string>("std::string"); qRegisterMetaType<std::string>("std::string");
@ -954,7 +955,8 @@ void EmuThread::requestDisplaySize(float scale)
System::RequestDisplaySize(scale); System::RequestDisplaySize(scale);
} }
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error) std::optional<WindowInfo> EmuThread::acquireRenderWindow(RenderAPI render_api, bool fullscreen,
bool exclusive_fullscreen, Error* error)
{ {
DebugAssert(g_gpu_device); DebugAssert(g_gpu_device);
@ -964,8 +966,8 @@ std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool fullscreen, bool e
const bool render_to_main = !fullscreen && m_is_rendering_to_main; const bool render_to_main = !fullscreen && m_is_rendering_to_main;
const bool use_main_window_pos = shouldRenderToMain(); const bool use_main_window_pos = shouldRenderToMain();
return emit onAcquireRenderWindowRequested(window_fullscreen, render_to_main, m_is_surfaceless, use_main_window_pos, return emit onAcquireRenderWindowRequested(render_api, window_fullscreen, render_to_main, m_is_surfaceless,
error); use_main_window_pos, error);
} }
void EmuThread::releaseRenderWindow() void EmuThread::releaseRenderWindow()
@ -1655,9 +1657,9 @@ bool Host::CreateAuxiliaryRenderWindow(s32 x, s32 y, u32 width, u32 height, std:
std::string_view icon_name, AuxiliaryRenderWindowUserData userdata, std::string_view icon_name, AuxiliaryRenderWindowUserData userdata,
AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error) AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error)
{ {
return emit g_emu_thread->onCreateAuxiliaryRenderWindow(x, y, width, height, QtUtils::StringViewToQString(title), return emit g_emu_thread->onCreateAuxiliaryRenderWindow(
QtUtils::StringViewToQString(icon_name), userdata, handle, wi, g_gpu_device->GetRenderAPI(), x, y, width, height, QtUtils::StringViewToQString(title),
error); QtUtils::StringViewToQString(icon_name), userdata, handle, wi, error);
} }
void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32* pos_x, s32* pos_y, u32* width, void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32* pos_x, s32* pos_y, u32* width,
@ -2002,7 +2004,7 @@ void Host::CommitBaseSettingChanges()
std::optional<WindowInfo> Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, std::optional<WindowInfo> Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
Error* error) Error* error)
{ {
return g_emu_thread->acquireRenderWindow(fullscreen, exclusive_fullscreen, error); return g_emu_thread->acquireRenderWindow(render_api, fullscreen, exclusive_fullscreen, error);
} }
void Host::ReleaseRenderWindow() void Host::ReleaseRenderWindow()

View File

@ -41,6 +41,7 @@ class QTranslator;
class INISettingsInterface; class INISettingsInterface;
enum class RenderAPI : u8;
class GPUDevice; class GPUDevice;
class MainWindow; class MainWindow;
@ -94,7 +95,8 @@ public:
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; } ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; } ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
std::optional<WindowInfo> acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error); std::optional<WindowInfo> acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
Error* error);
void connectDisplaySignals(DisplayWidget* widget); void connectDisplaySignals(DisplayWidget* widget);
void releaseRenderWindow(); void releaseRenderWindow();
@ -137,8 +139,8 @@ Q_SIGNALS:
void systemPaused(); void systemPaused();
void systemResumed(); void systemResumed();
void gameListRefreshed(); void gameListRefreshed();
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool fullscreen, bool render_to_main, bool surfaceless, std::optional<WindowInfo> onAcquireRenderWindowRequested(RenderAPI render_api, bool fullscreen, bool render_to_main,
bool use_main_window_pos, Error* error); bool surfaceless, bool use_main_window_pos, Error* error);
void onResizeRenderWindowRequested(qint32 width, qint32 height); void onResizeRenderWindowRequested(qint32 width, qint32 height);
void onReleaseRenderWindowRequested(); void onReleaseRenderWindowRequested();
void focusDisplayWidgetRequested(); void focusDisplayWidgetRequested();
@ -153,8 +155,9 @@ Q_SIGNALS:
void mediaCaptureStarted(); void mediaCaptureStarted();
void mediaCaptureStopped(); void mediaCaptureStopped();
bool onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width, quint32 height, const QString& title, bool onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, qint32 y, quint32 width, quint32 height,
const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata, const QString& title, const QString& icon_name,
Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error); Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error);
void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size); void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size);

View File

@ -7,6 +7,8 @@
#include "core/game_list.h" #include "core/game_list.h"
#include "core/system.h" #include "core/system.h"
#include "util/gpu_device.h"
#include "common/error.h" #include "common/error.h"
#include "common/log.h" #include "common/log.h"
@ -30,6 +32,8 @@
#include <QtWidgets/QTreeView> #include <QtWidgets/QTreeView>
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cstdlib>
#include <cstring>
#include <map> #include <map>
#if !defined(_WIN32) && !defined(APPLE) #if !defined(_WIN32) && !defined(APPLE)
@ -317,7 +321,7 @@ qreal QtUtils::GetDevicePixelRatioForWidget(const QWidget* widget)
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1); return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
} }
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Error* error) std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error)
{ {
WindowInfo wi; WindowInfo wi;
@ -333,8 +337,20 @@ std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Error
const QString platform_name = QGuiApplication::platformName(); const QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("xcb")) if (platform_name == QStringLiteral("xcb"))
{ {
wi.type = WindowInfo::Type::X11; // This is fucking ridiculous. NVIDIA+XWayland doesn't support Xlib, and NVIDIA+Xorg doesn't support XCB.
// Use Xlib if we're not running under Wayland, or we're not requesting OpenGL. Vulkan+XCB seems fine.
const char* xdg_session_type = std::getenv("XDG_SESSION_TYPE");
const bool is_running_on_xwayland = (xdg_session_type && std::strstr(xdg_session_type, "wayland"));
if (is_running_on_xwayland || render_api == RenderAPI::Vulkan)
{
wi.type = WindowInfo::Type::XCB;
wi.display_connection = pni->nativeResourceForWindow("connection", widget->windowHandle());
}
else
{
wi.type = WindowInfo::Type::Xlib;
wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle()); wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle());
}
wi.window_handle = reinterpret_cast<void*>(widget->winId()); wi.window_handle = reinterpret_cast<void*>(widget->winId());
} }
else if (platform_name == QStringLiteral("wayland")) else if (platform_name == QStringLiteral("wayland"))
@ -356,9 +372,12 @@ std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Error
wi.surface_scale = static_cast<float>(dpr); wi.surface_scale = static_cast<float>(dpr);
// Query refresh rate, we need it for sync. // Query refresh rate, we need it for sync.
std::optional<float> surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi); Error refresh_rate_error;
std::optional<float> surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi, &refresh_rate_error);
if (!surface_refresh_rate.has_value()) if (!surface_refresh_rate.has_value())
{ {
WARNING_LOG("Failed to get refresh rate for window, falling back to Qt: {}", refresh_rate_error.GetDescription());
// Fallback to using the screen, getting the rate for Wayland is an utter mess otherwise. // Fallback to using the screen, getting the rate for Wayland is an utter mess otherwise.
const QScreen* widget_screen = widget->screen(); const QScreen* widget_screen = widget->screen();
if (!widget_screen) if (!widget_screen)

View File

@ -32,6 +32,8 @@ class QVariant;
class QWidget; class QWidget;
class QUrl; class QUrl;
enum class RenderAPI : u8;
enum class ConsoleRegion : u8; enum class ConsoleRegion : u8;
enum class DiscRegion : u8; enum class DiscRegion : u8;
namespace GameDatabase { namespace GameDatabase {
@ -116,7 +118,7 @@ QIcon GetIconForCompatibility(GameDatabase::CompatibilityRating rating);
qreal GetDevicePixelRatioForWidget(const QWidget* widget); qreal GetDevicePixelRatioForWidget(const QWidget* widget);
/// Returns the common window info structure for a Qt widget. /// Returns the common window info structure for a Qt widget.
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget, Error* error = nullptr); std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error = nullptr);
/// Saves a window's geometry to configuration. Returns false if the configuration was changed. /// Saves a window's geometry to configuration. Returns false if the configuration was changed.
bool SaveWindowGeometry(std::string_view window_name, QWidget* widget, bool auto_commit_changes = true); bool SaveWindowGeometry(std::string_view window_name, QWidget* widget, bool auto_commit_changes = true);

View File

@ -80,8 +80,12 @@ target_link_libraries(util PUBLIC common simpleini imgui)
target_link_libraries(util PRIVATE libchdr lzma JPEG::JPEG PNG::PNG WebP::libwebp lunasvg::lunasvg ZLIB::ZLIB SoundTouch::SoundTouchDLL xxhash Zstd::Zstd reshadefx) target_link_libraries(util PRIVATE libchdr lzma JPEG::JPEG PNG::PNG WebP::libwebp lunasvg::lunasvg ZLIB::ZLIB SoundTouch::SoundTouchDLL xxhash Zstd::Zstd reshadefx)
if(ENABLE_X11) if(ENABLE_X11)
target_sources(util PRIVATE
x11_tools.cpp
x11_tools.h
)
target_compile_definitions(util PRIVATE "-DENABLE_X11=1") target_compile_definitions(util PRIVATE "-DENABLE_X11=1")
target_link_libraries(util PRIVATE X11::X11 X11::Xrandr) target_link_libraries(util PRIVATE X11::xcb X11::xcb_randr X11::X11_xcb)
endif() endif()
if(ENABLE_WAYLAND) if(ENABLE_WAYLAND)
@ -121,8 +125,10 @@ if(ENABLE_OPENGL)
if(ENABLE_X11) if(ENABLE_X11)
target_sources(util PRIVATE target_sources(util PRIVATE
opengl_context_egl_x11.cpp opengl_context_egl_xcb.cpp
opengl_context_egl_x11.h opengl_context_egl_xcb.h
opengl_context_egl_xlib.cpp
opengl_context_egl_xlib.h
) )
endif() endif()
if(ENABLE_WAYLAND) if(ENABLE_WAYLAND)

View File

@ -27,7 +27,8 @@
#include "opengl_context_egl_wayland.h" #include "opengl_context_egl_wayland.h"
#endif #endif
#ifdef ENABLE_X11 #ifdef ENABLE_X11
#include "opengl_context_egl_x11.h" #include "opengl_context_egl_xcb.h"
#include "opengl_context_egl_xlib.h"
#endif #endif
#endif #endif
#endif #endif
@ -154,8 +155,10 @@ std::unique_ptr<OpenGLContext> OpenGLContext::Create(WindowInfo& wi, SurfaceHand
context = OpenGLContextEGLAndroid::Create(wi, surface, versions_to_try, error); context = OpenGLContextEGLAndroid::Create(wi, surface, versions_to_try, error);
#else #else
#if defined(ENABLE_X11) #if defined(ENABLE_X11)
if (wi.type == WindowInfo::Type::X11) if (wi.type == WindowInfo::Type::Xlib)
context = OpenGLContextEGLX11::Create(wi, surface, versions_to_try, error); context = OpenGLContextEGLXlib::Create(wi, surface, versions_to_try, error);
else if (wi.type == WindowInfo::Type::XCB)
context = OpenGLContextEGLXCB::Create(wi, surface, versions_to_try, error);
#endif #endif
#if defined(ENABLE_WAYLAND) #if defined(ENABLE_WAYLAND)
if (wi.type == WindowInfo::Type::Wayland) if (wi.type == WindowInfo::Type::Wayland)

View File

@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "opengl_context_egl_xcb.h"
#include "common/error.h"
#include "common/log.h"
LOG_CHANNEL(GPUDevice);
OpenGLContextEGLXCB::OpenGLContextEGLXCB() = default;
OpenGLContextEGLXCB::~OpenGLContextEGLXCB() = default;
std::unique_ptr<OpenGLContext> OpenGLContextEGLXCB::Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error)
{
std::unique_ptr<OpenGLContextEGLXCB> context = std::make_unique<OpenGLContextEGLXCB>();
if (!context->Initialize(wi, surface, versions_to_try, error))
return nullptr;
return context;
}
std::unique_ptr<OpenGLContext> OpenGLContextEGLXCB::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
Error* error)
{
std::unique_ptr<OpenGLContextEGLXCB> context = std::make_unique<OpenGLContextEGLXCB>();
context->m_display = m_display;
if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
return nullptr;
return context;
}
EGLDisplay OpenGLContextEGLXCB::GetPlatformDisplay(const WindowInfo& wi, Error* error)
{
EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_XCB_EXT, "EGL_EXT_platform_xcb");
m_using_platform_display = (dpy != EGL_NO_DISPLAY);
if (!m_using_platform_display)
dpy = GetFallbackDisplay(wi.display_connection, error);
return dpy;
}
EGLSurface OpenGLContextEGLXCB::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error)
{
// Try YOLO'ing it, if the depth/visual is compatible we don't need to create a subwindow.
// Seems to be the case with Mesa, but not on NVIDIA.
xcb_window_t xcb_window = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(wi.window_handle));
EGLSurface surface = m_using_platform_display ? TryCreatePlatformSurface(config, &xcb_window, error) :
CreateFallbackSurface(config, wi.window_handle, error);
if (surface != EGL_NO_SURFACE)
{
// Yay, no subwindow.
return surface;
}
// Why do we need this shit? XWayland on NVIDIA....
EGLint native_visual_id = 0;
if (!eglGetConfigAttrib(m_display, m_config, EGL_NATIVE_VISUAL_ID, &native_visual_id))
{
Error::SetStringView(error, "Failed to get XCB visual ID");
return EGL_NO_SURFACE;
}
X11Window subwindow;
if (!subwindow.Create(static_cast<xcb_connection_t*>(wi.display_connection), xcb_window,
static_cast<xcb_visualid_t>(native_visual_id), error))
{
Error::AddPrefix(error, "Failed to create subwindow");
return EGL_NO_SURFACE;
}
// This is hideous.. the EXT version requires a pointer to the window, whereas the base
// version requires the window itself, casted to void*...
surface = TryCreatePlatformSurface(config, subwindow.GetWindowPtr(), error);
if (surface == EGL_NO_SURFACE)
surface = CreateFallbackSurface(config, wi.window_handle, error);
if (surface != EGL_NO_SURFACE)
{
DEV_LOG("Created {}x{} subwindow with visual ID {}", subwindow.GetWidth(), subwindow.GetHeight(), native_visual_id);
m_x11_windows.emplace(surface, std::move(subwindow));
}
return surface;
}
void OpenGLContextEGLXCB::DestroyPlatformSurface(EGLSurface surface)
{
OpenGLContextEGL::DestroyPlatformSurface(surface);
auto it = m_x11_windows.find((EGLSurface)surface);
if (it != m_x11_windows.end())
m_x11_windows.erase(it);
}
void OpenGLContextEGLXCB::ResizeSurface(WindowInfo& wi, SurfaceHandle handle)
{
const auto it = m_x11_windows.find((EGLSurface)handle);
if (it != m_x11_windows.end())
it->second.Resize(wi.surface_width, wi.surface_height);
}

View File

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "opengl_context_egl.h"
#include "x11_tools.h"
#include <unordered_map>
class OpenGLContextEGLXCB final : public OpenGLContextEGL
{
public:
OpenGLContextEGLXCB();
~OpenGLContextEGLXCB() override;
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error);
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
protected:
EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error) override;
EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) override;
void DestroyPlatformSurface(EGLSurface surface) override;
private:
using X11WindowMap = std::unordered_map<EGLSurface, X11Window>;
X11WindowMap m_x11_windows;
bool m_using_platform_display = false;
};

View File

@ -1,28 +1,28 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "opengl_context_egl_x11.h" #include "opengl_context_egl_xlib.h"
#include "common/error.h" #include "common/error.h"
OpenGLContextEGLX11::OpenGLContextEGLX11() = default; OpenGLContextEGLXlib::OpenGLContextEGLXlib() = default;
OpenGLContextEGLX11::~OpenGLContextEGLX11() = default; OpenGLContextEGLXlib::~OpenGLContextEGLXlib() = default;
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::Create(WindowInfo& wi, SurfaceHandle* surface, std::unique_ptr<OpenGLContext> OpenGLContextEGLXlib::Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error) std::span<const Version> versions_to_try, Error* error)
{ {
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>(); std::unique_ptr<OpenGLContextEGLXlib> context = std::make_unique<OpenGLContextEGLXlib>();
if (!context->Initialize(wi, surface, versions_to_try, error)) if (!context->Initialize(wi, surface, versions_to_try, error))
return nullptr; return nullptr;
return context; return context;
} }
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, std::unique_ptr<OpenGLContext> OpenGLContextEGLXlib::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
Error* error) Error* error)
{ {
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>(); std::unique_ptr<OpenGLContextEGLXlib> context = std::make_unique<OpenGLContextEGLXlib>();
context->m_display = m_display; context->m_display = m_display;
if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error)) if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
@ -31,7 +31,7 @@ std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::CreateSharedContext(WindowIn
return context; return context;
} }
EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(const WindowInfo& wi, Error* error) EGLDisplay OpenGLContextEGLXlib::GetPlatformDisplay(const WindowInfo& wi, Error* error)
{ {
EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_X11_KHR, "EGL_EXT_platform_x11"); EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_X11_KHR, "EGL_EXT_platform_x11");
if (dpy == EGL_NO_DISPLAY) if (dpy == EGL_NO_DISPLAY)
@ -40,7 +40,7 @@ EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(const WindowInfo& wi, Error*
return dpy; return dpy;
} }
EGLSurface OpenGLContextEGLX11::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) EGLSurface OpenGLContextEGLXlib::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error)
{ {
// This is hideous.. the EXT version requires a pointer to the window, whereas the base // This is hideous.. the EXT version requires a pointer to the window, whereas the base
// version requires the window itself, casted to void*... // version requires the window itself, casted to void*...

View File

@ -5,11 +5,11 @@
#include "opengl_context_egl.h" #include "opengl_context_egl.h"
class OpenGLContextEGLX11 final : public OpenGLContextEGL class OpenGLContextEGLXlib final : public OpenGLContextEGL
{ {
public: public:
OpenGLContextEGLX11(); OpenGLContextEGLXlib();
~OpenGLContextEGLX11() override; ~OpenGLContextEGLXlib() override;
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface, static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error); std::span<const Version> versions_to_try, Error* error);

View File

@ -25,5 +25,5 @@ std::optional<WindowInfo> GetTopLevelWindowInfo();
// TODO: Move all the other Cocoa stuff in here. // TODO: Move all the other Cocoa stuff in here.
namespace CocoaTools { namespace CocoaTools {
/// Returns the refresh rate of the display the window is placed on. /// Returns the refresh rate of the display the window is placed on.
std::optional<float> GetViewRefreshRate(const WindowInfo& wi); std::optional<float> GetViewRefreshRate(const WindowInfo& wi, Error* error);
} } // namespace CocoaTools

View File

@ -129,12 +129,12 @@ void CocoaTools::DestroyMetalLayer(const WindowInfo& wi, void* layer)
[clayer release]; [clayer release];
} }
std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi) std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi, Error* error)
{ {
if (![NSThread isMainThread]) if (![NSThread isMainThread])
{ {
std::optional<float> ret; std::optional<float> ret;
dispatch_sync(dispatch_get_main_queue(), [&ret, wi] { ret = GetViewRefreshRate(wi); }); dispatch_sync(dispatch_get_main_queue(), [&ret, wi, error] { ret = GetViewRefreshRate(wi, error); });
return ret; return ret;
} }
@ -146,6 +146,10 @@ std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
ret = CGDisplayModeGetRefreshRate(mode); ret = CGDisplayModeGetRefreshRate(mode);
CGDisplayModeRelease(mode); CGDisplayModeRelease(mode);
} }
else
{
Error::SetStringView(error, "CGDisplayCopyDisplayMode() failed");
}
return ret; return ret;
} }

View File

@ -57,7 +57,10 @@
<ClInclude Include="opengl_context_egl_wayland.h"> <ClInclude Include="opengl_context_egl_wayland.h">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude> </ClInclude>
<ClInclude Include="opengl_context_egl_x11.h"> <ClInclude Include="opengl_context_egl_xcb.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="opengl_context_egl_xlib.h">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude> </ClInclude>
<ClInclude Include="opengl_context_wgl.h"> <ClInclude Include="opengl_context_wgl.h">
@ -101,6 +104,9 @@
<ClInclude Include="wav_reader_writer.h" /> <ClInclude Include="wav_reader_writer.h" />
<ClInclude Include="win32_raw_input_source.h" /> <ClInclude Include="win32_raw_input_source.h" />
<ClInclude Include="window_info.h" /> <ClInclude Include="window_info.h" />
<ClInclude Include="x11_tools.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="xinput_source.h" /> <ClInclude Include="xinput_source.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -159,7 +165,10 @@
<ClCompile Include="opengl_context_egl_wayland.cpp"> <ClCompile Include="opengl_context_egl_wayland.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="opengl_context_egl_x11.cpp"> <ClCompile Include="opengl_context_egl_xcb.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="opengl_context_egl_xlib.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="opengl_context_wgl.cpp"> <ClCompile Include="opengl_context_wgl.cpp">
@ -202,6 +211,9 @@
<ClCompile Include="wav_reader_writer.cpp" /> <ClCompile Include="wav_reader_writer.cpp" />
<ClCompile Include="win32_raw_input_source.cpp" /> <ClCompile Include="win32_raw_input_source.cpp" />
<ClCompile Include="window_info.cpp" /> <ClCompile Include="window_info.cpp" />
<ClCompile Include="x11_tools.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="xinput_source.cpp" /> <ClCompile Include="xinput_source.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -66,13 +66,15 @@
<ClInclude Include="opengl_context_agl.h" /> <ClInclude Include="opengl_context_agl.h" />
<ClInclude Include="opengl_context_egl.h" /> <ClInclude Include="opengl_context_egl.h" />
<ClInclude Include="opengl_context_egl_wayland.h" /> <ClInclude Include="opengl_context_egl_wayland.h" />
<ClInclude Include="opengl_context_egl_x11.h" /> <ClInclude Include="opengl_context_egl_xcb.h" />
<ClInclude Include="opengl_context_wgl.h" /> <ClInclude Include="opengl_context_wgl.h" />
<ClInclude Include="image.h" /> <ClInclude Include="image.h" />
<ClInclude Include="sockets.h" /> <ClInclude Include="sockets.h" />
<ClInclude Include="media_capture.h" /> <ClInclude Include="media_capture.h" />
<ClInclude Include="compress_helpers.h" /> <ClInclude Include="compress_helpers.h" />
<ClInclude Include="elf_file.h" /> <ClInclude Include="elf_file.h" />
<ClInclude Include="x11_tools.h" />
<ClInclude Include="opengl_context_egl_xlib.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="state_wrapper.cpp" /> <ClCompile Include="state_wrapper.cpp" />
@ -146,7 +148,7 @@
<ClCompile Include="opengl_context.cpp" /> <ClCompile Include="opengl_context.cpp" />
<ClCompile Include="opengl_context_egl.cpp" /> <ClCompile Include="opengl_context_egl.cpp" />
<ClCompile Include="opengl_context_egl_wayland.cpp" /> <ClCompile Include="opengl_context_egl_wayland.cpp" />
<ClCompile Include="opengl_context_egl_x11.cpp" /> <ClCompile Include="opengl_context_egl_xcb.cpp" />
<ClCompile Include="opengl_context_wgl.cpp" /> <ClCompile Include="opengl_context_wgl.cpp" />
<ClCompile Include="image.cpp" /> <ClCompile Include="image.cpp" />
<ClCompile Include="sdl_audio_stream.cpp" /> <ClCompile Include="sdl_audio_stream.cpp" />
@ -154,6 +156,8 @@
<ClCompile Include="media_capture.cpp" /> <ClCompile Include="media_capture.cpp" />
<ClCompile Include="compress_helpers.cpp" /> <ClCompile Include="compress_helpers.cpp" />
<ClCompile Include="elf_file.cpp" /> <ClCompile Include="elf_file.cpp" />
<ClCompile Include="x11_tools.cpp" />
<ClCompile Include="opengl_context_egl_xlib.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="metal_shaders.metal" /> <None Include="metal_shaders.metal" />

View File

@ -248,8 +248,8 @@ bool VulkanDevice::SelectInstanceExtensions(ExtensionList* extension_list, const
if (wi.type == WindowInfo::Type::Win32 && !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true)) if (wi.type == WindowInfo::Type::Win32 && !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))
return false; return false;
#endif #endif
#if defined(VK_USE_PLATFORM_XLIB_KHR) #if defined(VK_USE_PLATFORM_XCB_KHR)
if (wi.type == WindowInfo::Type::X11 && !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true)) if (wi.type == WindowInfo::Type::XCB && !SupportsExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME, true))
return false; return false;
#endif #endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR) #if defined(VK_USE_PLATFORM_WAYLAND_KHR)

View File

@ -40,12 +40,10 @@ VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceSurfacePresentModesKHR, false)
#if defined(VK_USE_PLATFORM_WIN32_KHR) #if defined(VK_USE_PLATFORM_WIN32_KHR)
VULKAN_INSTANCE_ENTRY_POINT(vkCreateWin32SurfaceKHR, false) VULKAN_INSTANCE_ENTRY_POINT(vkCreateWin32SurfaceKHR, false)
VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceWin32PresentationSupportKHR, false)
#endif #endif
#if defined(VK_USE_PLATFORM_XLIB_KHR) #if defined(VK_USE_PLATFORM_XCB_KHR)
VULKAN_INSTANCE_ENTRY_POINT(vkCreateXlibSurfaceKHR, false) VULKAN_INSTANCE_ENTRY_POINT(vkCreateXcbSurfaceKHR, false)
VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceXlibPresentationSupportKHR, false)
#endif #endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR) #if defined(VK_USE_PLATFORM_WAYLAND_KHR)

View File

@ -18,7 +18,7 @@ class Error;
#define VK_USE_PLATFORM_ANDROID_KHR #define VK_USE_PLATFORM_ANDROID_KHR
#else #else
#ifdef ENABLE_X11 #ifdef ENABLE_X11
#define VK_USE_PLATFORM_XLIB_KHR #define VK_USE_PLATFORM_XCB_KHR
#endif #endif
#ifdef ENABLE_WAYLAND #ifdef ENABLE_WAYLAND
@ -28,48 +28,6 @@ class Error;
#include "vulkan/vulkan.h" #include "vulkan/vulkan.h"
#if defined(ENABLE_X11)
// This breaks a bunch of our code. They shouldn't be #defines in the first place.
#ifdef None
#undef None
#endif
#ifdef Always
#undef Always
#endif
#ifdef Status
#undef Status
#endif
#ifdef CursorShape
#undef CursorShape
#endif
#ifdef KeyPress
#undef KeyPress
#endif
#ifdef KeyRelease
#undef KeyRelease
#endif
#ifdef FocusIn
#undef FocusIn
#endif
#ifdef FocusOut
#undef FocusOut
#endif
#ifdef FontChange
#undef FontChange
#endif
#ifdef Expose
#undef Expose
#endif
#ifdef Unsorted
#undef Unsorted
#endif
#ifdef Bool
#undef Bool
#endif
#endif
#include "vulkan_entry_points.h" #include "vulkan_entry_points.h"
// We include vk_mem_alloc globally, so we don't accidentally include it before the vulkan header somewhere. // We include vk_mem_alloc globally, so we don't accidentally include it before the vulkan header somewhere.

View File

@ -138,17 +138,17 @@ bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physic
} }
#endif #endif
#if defined(VK_USE_PLATFORM_XLIB_KHR) #if defined(VK_USE_PLATFORM_XCB_KHR)
if (m_window_info.type == WindowInfo::Type::X11) if (m_window_info.type == WindowInfo::Type::XCB)
{ {
const VkXlibSurfaceCreateInfoKHR surface_create_info = { const VkXcbSurfaceCreateInfoKHR surface_create_info = {
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, .sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR,
.dpy = static_cast<Display*>(m_window_info.display_connection), .connection = static_cast<xcb_connection_t*>(m_window_info.display_connection),
.window = reinterpret_cast<Window>(m_window_info.window_handle)}; .window = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(m_window_info.window_handle))};
const VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); const VkResult res = vkCreateXcbSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
Vulkan::SetErrorObject(error, "vkCreateXlibSurfaceKHR failed: ", res); Vulkan::SetErrorObject(error, "vkCreateXcbSurfaceKHR failed: ", res);
return false; return false;
} }

View File

@ -16,13 +16,13 @@ LOG_CHANNEL(WindowInfo);
#include "common/windows_headers.h" #include "common/windows_headers.h"
#include <dwmapi.h> #include <dwmapi.h>
static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd) static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd, Error* error)
{ {
// Partially based on Chromium ui/display/win/display_config_helper.cc. // Partially based on Chromium ui/display/win/display_config_helper.cc.
const HMONITOR monitor = MonitorFromWindow(hwnd, 0); const HMONITOR monitor = MonitorFromWindow(hwnd, 0);
if (!monitor) [[unlikely]] if (!monitor) [[unlikely]]
{ {
ERROR_LOG("{}() failed: {}", "MonitorFromWindow", Error::CreateWin32(GetLastError()).GetDescription()); Error::SetWin32(error, "MonitorFromWindow() failed: ", GetLastError());
return std::nullopt; return std::nullopt;
} }
@ -30,7 +30,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
mi.cbSize = sizeof(mi); mi.cbSize = sizeof(mi);
if (!GetMonitorInfoW(monitor, &mi)) if (!GetMonitorInfoW(monitor, &mi))
{ {
ERROR_LOG("{}() failed: {}", "GetMonitorInfoW", Error::CreateWin32(GetLastError()).GetDescription()); Error::SetWin32(error, "GetMonitorInfoW() failed: ", GetLastError());
return std::nullopt; return std::nullopt;
} }
@ -44,7 +44,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
LONG res = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_size, &mode_size); LONG res = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_size, &mode_size);
if (res != ERROR_SUCCESS) if (res != ERROR_SUCCESS)
{ {
ERROR_LOG("{}() failed: {}", "GetDisplayConfigBufferSizes", Error::CreateWin32(res).GetDescription()); Error::SetWin32(error, "GetDisplayConfigBufferSizes() failed: ", res);
return std::nullopt; return std::nullopt;
} }
@ -56,7 +56,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
break; break;
if (res != ERROR_INSUFFICIENT_BUFFER) if (res != ERROR_INSUFFICIENT_BUFFER)
{ {
ERROR_LOG("{}() failed: {}", "QueryDisplayConfig", Error::CreateWin32(res).GetDescription()); Error::SetWin32(error, "QueryDisplayConfig() failed: ", res);
return std::nullopt; return std::nullopt;
} }
} }
@ -70,7 +70,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
LONG res = DisplayConfigGetDeviceInfo(&sdn.header); LONG res = DisplayConfigGetDeviceInfo(&sdn.header);
if (res != ERROR_SUCCESS) if (res != ERROR_SUCCESS)
{ {
ERROR_LOG("{}() failed: {}", "DisplayConfigGetDeviceInfo", Error::CreateWin32(res).GetDescription()); Error::SetWin32(error, "DisplayConfigGetDeviceInfo() failed: ", res);
continue; continue;
} }
@ -85,15 +85,19 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
return std::nullopt; return std::nullopt;
} }
static std::optional<float> GetRefreshRateFromDWM(HWND hwnd) static std::optional<float> GetRefreshRateFromDWM(HWND hwnd, Error* error)
{ {
BOOL composition_enabled; BOOL composition_enabled;
if (FAILED(DwmIsCompositionEnabled(&composition_enabled))) HRESULT hr = DwmIsCompositionEnabled(&composition_enabled);
if (FAILED(hr))
{
Error::SetHResult(error, "DwmIsCompositionEnabled() failed: ", hr);
return std::nullopt; return std::nullopt;
}
DWM_TIMING_INFO ti = {}; DWM_TIMING_INFO ti = {};
ti.cbSize = sizeof(ti); ti.cbSize = sizeof(ti);
HRESULT hr = DwmGetCompositionTimingInfo(nullptr, &ti); hr = DwmGetCompositionTimingInfo(nullptr, &ti);
if (SUCCEEDED(hr)) if (SUCCEEDED(hr))
{ {
if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0) if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0)
@ -101,15 +105,21 @@ static std::optional<float> GetRefreshRateFromDWM(HWND hwnd)
return static_cast<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator); return static_cast<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator);
} }
else
{
Error::SetHResult(error, "DwmGetCompositionTimingInfo() failed: ", hr);
return std::nullopt; return std::nullopt;
} }
}
static std::optional<float> GetRefreshRateFromMonitor(HWND hwnd) static std::optional<float> GetRefreshRateFromMonitor(HWND hwnd, Error* error)
{ {
HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (!mon) if (!mon)
{
Error::SetWin32(error, "MonitorFromWindow() failed: ", GetLastError());
return std::nullopt; return std::nullopt;
}
MONITORINFOEXW mi = {}; MONITORINFOEXW mi = {};
mi.cbSize = sizeof(mi); mi.cbSize = sizeof(mi);
@ -120,26 +130,46 @@ static std::optional<float> GetRefreshRateFromMonitor(HWND hwnd)
// 0/1 are reserved for "defaults". // 0/1 are reserved for "defaults".
if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1) if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1)
{
return static_cast<float>(dm.dmDisplayFrequency); return static_cast<float>(dm.dmDisplayFrequency);
} }
else
{
Error::SetWin32(error, "EnumDisplaySettingsW() failed: ", GetLastError());
return std::nullopt; return std::nullopt;
} }
}
else
{
Error::SetWin32(error, "GetMonitorInfoW() failed: ", GetLastError());
return std::nullopt;
}
}
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error)
{ {
std::optional<float> ret; std::optional<float> ret;
if (wi.type != Type::Win32 || !wi.window_handle) if (wi.type != Type::Win32 || !wi.window_handle)
{
Error::SetStringView(error, "Invalid window type.");
return ret; return ret;
}
// Try DWM first, then fall back to integer values. // Try DWM first, then fall back to integer values.
const HWND hwnd = static_cast<HWND>(wi.window_handle); const HWND hwnd = static_cast<HWND>(wi.window_handle);
ret = GetRefreshRateFromDisplayConfig(hwnd); Error local_error;
ret = GetRefreshRateFromDisplayConfig(hwnd, &local_error);
if (!ret.has_value()) if (!ret.has_value())
{ {
ret = GetRefreshRateFromDWM(hwnd); WARNING_LOG("GetRefreshRateFromDisplayConfig() failed: {}", local_error.GetDescription());
ret = GetRefreshRateFromDWM(hwnd, &local_error);
if (!ret.has_value()) if (!ret.has_value())
ret = GetRefreshRateFromMonitor(hwnd); {
WARNING_LOG("GetRefreshRateFromDWM() failed: {}", local_error.GetDescription());
ret = GetRefreshRateFromMonitor(hwnd, error);
}
} }
return ret; return ret;
@ -149,157 +179,29 @@ std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
#include "util/platform_misc.h" #include "util/platform_misc.h"
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error)
{ {
if (wi.type == WindowInfo::Type::MacOS) if (wi.type == WindowInfo::Type::MacOS)
return CocoaTools::GetViewRefreshRate(wi); return CocoaTools::GetViewRefreshRate(wi, error);
Error::SetStringView(error, "Invalid window type.");
return std::nullopt; return std::nullopt;
} }
#else #else
#ifdef ENABLE_X11 #ifdef ENABLE_X11
#include "x11_tools.h"
#endif
#include <X11/Xlib.h> std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error)
#include <X11/Xutil.h>
#include <X11/extensions/Xrandr.h>
// Helper class for managing X errors
namespace {
class X11InhibitErrors;
static X11InhibitErrors* s_current_error_inhibiter;
class X11InhibitErrors
{
public:
X11InhibitErrors()
{
Assert(!s_current_error_inhibiter);
m_old_handler = XSetErrorHandler(ErrorHandler);
s_current_error_inhibiter = this;
}
~X11InhibitErrors()
{
Assert(s_current_error_inhibiter == this);
s_current_error_inhibiter = nullptr;
XSetErrorHandler(m_old_handler);
}
ALWAYS_INLINE bool HadError() const { return m_had_error; }
private:
static int ErrorHandler(Display* display, XErrorEvent* ee)
{
char error_string[256] = {};
XGetErrorText(display, ee->error_code, error_string, sizeof(error_string));
WARNING_LOG("X11 Error: {} (Error {} Minor {} Request {})", error_string, ee->error_code, ee->minor_code,
ee->request_code);
s_current_error_inhibiter->m_had_error = true;
return 0;
}
XErrorHandler m_old_handler = {};
bool m_had_error = false;
};
} // namespace
static std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi)
{
Display* display = static_cast<Display*>(wi.display_connection);
Window window = static_cast<Window>(reinterpret_cast<uintptr_t>(wi.window_handle));
if (!display || !window)
return std::nullopt;
X11InhibitErrors inhibiter;
XRRScreenResources* res = XRRGetScreenResources(display, window);
if (!res)
{
ERROR_LOG("XRRGetScreenResources() failed");
return std::nullopt;
}
ScopedGuard res_guard([res]() { XRRFreeScreenResources(res); });
int num_monitors;
XRRMonitorInfo* mi = XRRGetMonitors(display, window, True, &num_monitors);
if (num_monitors < 0)
{
ERROR_LOG("XRRGetMonitors() failed");
return std::nullopt;
}
else if (num_monitors > 1)
{
WARNING_LOG("XRRGetMonitors() returned {} monitors, using first", num_monitors);
}
ScopedGuard mi_guard([mi]() { XRRFreeMonitors(mi); });
if (mi->noutput <= 0)
{
ERROR_LOG("Monitor has no outputs");
return std::nullopt;
}
else if (mi->noutput > 1)
{
WARNING_LOG("Monitor has {} outputs, using first", mi->noutput);
}
XRROutputInfo* oi = XRRGetOutputInfo(display, res, mi->outputs[0]);
if (!oi)
{
ERROR_LOG("XRRGetOutputInfo() failed");
return std::nullopt;
}
ScopedGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); });
XRRCrtcInfo* ci = XRRGetCrtcInfo(display, res, oi->crtc);
if (!ci)
{
ERROR_LOG("XRRGetCrtcInfo() failed");
return std::nullopt;
}
ScopedGuard ci_guard([ci]() { XRRFreeCrtcInfo(ci); });
XRRModeInfo* mode = nullptr;
for (int i = 0; i < res->nmode; i++)
{
if (res->modes[i].id == ci->mode)
{
mode = &res->modes[i];
break;
}
}
if (!mode)
{
ERROR_LOG("Failed to look up mode {} (of {})", static_cast<int>(ci->mode), res->nmode);
return std::nullopt;
}
if (mode->dotClock == 0 || mode->hTotal == 0 || mode->vTotal == 0)
{
ERROR_LOG("Modeline is invalid: {}/{}/{}", mode->dotClock, mode->hTotal, mode->vTotal);
return std::nullopt;
}
return static_cast<float>(static_cast<double>(mode->dotClock) /
(static_cast<double>(mode->hTotal) * static_cast<double>(mode->vTotal)));
}
#endif // ENABLE_X11
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
{ {
#if defined(ENABLE_X11) #if defined(ENABLE_X11)
if (wi.type == WindowInfo::Type::X11) if (wi.type == WindowInfo::Type::Xlib || wi.type == WindowInfo::Type::XCB)
return GetRefreshRateFromXRandR(wi); return GetRefreshRateFromXRandR(wi, error);
#endif #endif
Error::SetStringView(error, "Invalid window type.");
return std::nullopt; return std::nullopt;
} }

View File

@ -8,6 +8,8 @@
#include <optional> #include <optional>
class Error;
// Contains the information required to create a graphics context in a window. // Contains the information required to create a graphics context in a window.
struct WindowInfo struct WindowInfo
{ {
@ -15,7 +17,8 @@ struct WindowInfo
{ {
Surfaceless, Surfaceless,
Win32, Win32,
X11, Xlib,
XCB,
Wayland, Wayland,
MacOS, MacOS,
Android, Android,
@ -32,5 +35,5 @@ struct WindowInfo
ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; } ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; }
static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi); static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi, Error* error = nullptr);
}; };

325
src/util/x11_tools.cpp Normal file
View File

@ -0,0 +1,325 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "x11_tools.h"
#include "window_info.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/log.h"
#include "common/scoped_guard.h"
#include <X11/Xlib-xcb.h>
#include <xcb/randr.h>
#include <xcb/xcb.h>
#include <memory>
LOG_CHANNEL(WindowInfo);
namespace {
template<typename T>
struct XCBPointerDeleter
{
void operator()(T* ptr) { free(ptr); }
};
template<typename T>
using XCBPointer = std::unique_ptr<T, XCBPointerDeleter<T>>;
} // namespace
X11Window::X11Window() = default;
X11Window::X11Window(X11Window&& move)
{
m_connection = move.m_connection;
m_parent_window = move.m_parent_window;
m_window = move.m_window;
m_colormap = move.m_colormap;
m_width = move.m_width;
m_height = move.m_height;
move.m_connection = nullptr;
move.m_parent_window = {};
move.m_window = {};
move.m_colormap = {};
move.m_width = 0;
move.m_height = 0;
}
X11Window::~X11Window()
{
Destroy();
}
X11Window& X11Window::operator=(X11Window&& move)
{
m_connection = move.m_connection;
m_parent_window = move.m_parent_window;
m_window = move.m_window;
m_colormap = move.m_colormap;
m_width = move.m_width;
m_height = move.m_height;
move.m_connection = nullptr;
move.m_parent_window = {};
move.m_window = {};
move.m_colormap = {};
move.m_width = 0;
move.m_height = 0;
return *this;
}
static void SetErrorObject(Error* error, const char* prefix, const xcb_generic_error_t* xerror)
{
Error::SetStringFmt(error, "{} failed: EC={} Major={} Minor={} Resource={:X}", xerror->error_code,
xerror->response_type, xerror->major_code, xerror->minor_code, xerror->resource_id);
}
bool X11Window::Create(xcb_connection_t* connection, xcb_window_t parent_window, xcb_visualid_t vi, Error* error)
{
xcb_generic_error_t* xerror;
m_connection = connection;
m_parent_window = parent_window;
XCBPointer<xcb_get_geometry_reply_t> gwa(
xcb_get_geometry_reply(connection, xcb_get_geometry(connection, parent_window), &xerror));
if (!gwa)
{
SetErrorObject(error, "xcb_get_geometry_reply() failed: ", xerror);
return false;
}
m_width = gwa->width;
m_height = gwa->height;
// Need to find the root window to get an appropriate depth. Needed for NVIDIA+XWayland.
int visual_depth = XCB_COPY_FROM_PARENT;
for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(connection)); it.rem != 0;
xcb_screen_next(&it))
{
if (it.data->root == gwa->root)
{
for (xcb_depth_iterator_t dit = xcb_screen_allowed_depths_iterator(it.data); dit.rem != 0; xcb_depth_next(&dit))
{
const int len = xcb_depth_visuals_length(dit.data);
const xcb_visualtype_t* visuals = xcb_depth_visuals(dit.data);
int idx = 0;
for (; idx < len; idx++)
{
if (vi == visuals[idx].visual_id)
{
visual_depth = dit.data->depth;
break;
}
}
}
break;
}
}
if (visual_depth == XCB_COPY_FROM_PARENT)
WARNING_LOG("Could not find visual's depth.");
// ID isn't "used" until the call succeeds.
m_colormap = xcb_generate_id(connection);
if ((xerror = xcb_request_check(
connection, xcb_create_colormap_checked(connection, XCB_COLORMAP_ALLOC_NONE, m_colormap, parent_window, vi))))
{
SetErrorObject(error, "xcb_create_colormap_checked() failed: ", xerror);
m_colormap = {};
return false;
}
m_window = xcb_generate_id(connection);
const u32 window_values[] = {XCB_PIXMAP_NONE, 0u, m_colormap};
xerror = xcb_request_check(
connection, xcb_create_window_checked(connection, visual_depth, m_window, parent_window, 0, 0, m_width, m_height, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, vi,
XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP, window_values));
if (xerror)
{
SetErrorObject(error, "xcb_create_window_checked() failed: ", xerror);
m_window = {};
return false;
}
xerror = xcb_request_check(connection, xcb_map_window_checked(connection, m_window));
if (xerror)
{
SetErrorObject(error, "xcb_map_window_checked() failed: ", xerror);
return false;
}
return true;
}
void X11Window::Destroy()
{
xcb_generic_error_t* xerror;
Error error;
if (m_window)
{
if ((xerror = xcb_request_check(m_connection, xcb_unmap_window_checked(m_connection, m_window))))
{
SetErrorObject(&error, "xcb_unmap_window_checked() failed: ", xerror);
ERROR_LOG(error.GetDescription());
}
if ((xerror = xcb_request_check(m_connection, xcb_destroy_window_checked(m_connection, m_window))))
{
SetErrorObject(&error, "xcb_destroy_window_checked() failed: ", xerror);
ERROR_LOG(error.GetDescription());
}
m_window = {};
m_parent_window = {};
}
if (m_colormap)
{
if ((xerror = xcb_request_check(m_connection, xcb_free_colormap_checked(m_connection, m_colormap))))
{
SetErrorObject(&error, "xcb_free_colormap_checked() failed: ", xerror);
ERROR_LOG(error.GetDescription());
}
m_colormap = {};
}
}
void X11Window::Resize(u16 width, u16 height)
{
xcb_generic_error_t* xerror;
Error error;
if (width != 0 && height != 0)
{
m_width = width;
m_height = height;
}
else
{
XCBPointer<xcb_get_geometry_reply_t> gwa(
xcb_get_geometry_reply(m_connection, xcb_get_geometry(m_connection, m_parent_window), &xerror));
if (!gwa)
{
SetErrorObject(&error, "xcb_get_geometry() failed: ", xerror);
ERROR_LOG(error.GetDescription());
return;
}
m_width = gwa->width;
m_height = gwa->height;
}
u32 values[] = {width, height};
if ((xerror = xcb_request_check(
m_connection, xcb_configure_window_checked(m_connection, m_window,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values))))
{
SetErrorObject(&error, "xcb_configure_window_checked() failed: ", xerror);
ERROR_LOG(error.GetDescription());
}
}
std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi, Error* error)
{
xcb_connection_t* connection = nullptr;
if (wi.type == WindowInfo::Type::Xlib)
{
connection = XGetXCBConnection(static_cast<Display*>(wi.display_connection));
}
else if (wi.type == WindowInfo::Type::XCB)
{
connection = static_cast<xcb_connection_t*>(wi.display_connection);
}
xcb_window_t window = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(wi.window_handle));
if (wi.type != WindowInfo::Type::XCB || !connection || window == XCB_NONE)
{
Error::SetStringView(error, "Invalid window handle.");
return std::nullopt;
}
xcb_generic_error_t* xerror;
XCBPointer<xcb_randr_get_screen_resources_reply_t> gsr(
xcb_randr_get_screen_resources_reply(connection, xcb_randr_get_screen_resources(connection, window), &xerror));
if (xerror)
{
SetErrorObject(error, "xcb_randr_get_screen_resources() failed: ", xerror);
return std::nullopt;
}
XCBPointer<xcb_randr_get_monitors_reply_t> gm(
xcb_randr_get_monitors_reply(connection, xcb_randr_get_monitors(connection, window, true), &xerror));
if (xerror || gm->nMonitors < 0)
{
SetErrorObject(error, "xcb_randr_get_screen_resources() failed: ", xerror);
return std::nullopt;
}
if (gm->nMonitors > 1)
WARNING_LOG("xcb_randr_get_monitors() returned {} monitors, using first", gm->nMonitors);
if (gm->nOutputs <= 0)
{
Error::SetStringView(error, "Monitor has no outputs");
return std::nullopt;
}
else if (gm->nOutputs > 1)
{
WARNING_LOG("Monitor has {} outputs, using first", gm->nOutputs);
}
xcb_randr_monitor_info_t* monitor_info = xcb_randr_get_monitors_monitors_iterator(gm.get()).data;
DebugAssert(monitor_info);
xcb_randr_output_t* monitor_outputs = xcb_randr_monitor_info_outputs(monitor_info);
DebugAssert(monitor_outputs);
XCBPointer<xcb_randr_get_output_info_reply_t> goi(
xcb_randr_get_output_info_reply(connection, xcb_randr_get_output_info(connection, monitor_outputs[0], 0), &xerror));
if (xerror)
{
SetErrorObject(error, "xcb_randr_get_output_info() failed: ", xerror);
return std::nullopt;
}
XCBPointer<xcb_randr_get_crtc_info_reply_t> gci(
xcb_randr_get_crtc_info_reply(connection, xcb_randr_get_crtc_info(connection, goi->crtc, 0), &xerror));
if (xerror)
{
SetErrorObject(error, "xcb_randr_get_crtc_info_reply() failed: ", xerror);
return std::nullopt;
}
xcb_randr_mode_info_t* mode = nullptr;
for (xcb_randr_mode_info_iterator_t it = xcb_randr_get_screen_resources_modes_iterator(gsr.get()); it.rem != 0;
xcb_randr_mode_info_next(&it))
{
if (it.data->id == gci->mode)
{
mode = it.data;
break;
}
}
if (!mode)
{
Error::SetStringFmt(error, "Failed to look up mode ID {}", static_cast<int>(gci->mode));
return std::nullopt;
}
if (mode->dot_clock == 0 || mode->htotal == 0 || mode->vtotal == 0)
{
ERROR_LOG("Modeline is invalid: {}/{}/{}", mode->dot_clock, mode->htotal, mode->vtotal);
return std::nullopt;
}
return static_cast<float>(static_cast<double>(mode->dot_clock) /
(static_cast<double>(mode->htotal) * static_cast<double>(mode->vtotal)));
}

45
src/util/x11_tools.h Normal file
View File

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "common/types.h"
#include <xcb/xcb.h>
#include <xcb/xproto.h>
class Error;
struct WindowInfo;
class X11Window
{
public:
X11Window();
X11Window(const X11Window&) = delete;
X11Window(X11Window&& move);
~X11Window();
X11Window& operator=(const X11Window&) = delete;
X11Window& operator=(X11Window&& move);
ALWAYS_INLINE xcb_window_t GetWindow() const { return m_window; }
ALWAYS_INLINE xcb_window_t* GetWindowPtr() { return &m_window; }
ALWAYS_INLINE u32 GetWidth() const { return m_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_height; }
bool Create(xcb_connection_t* connection, xcb_window_t parent_window, xcb_visualid_t vi, Error* error = nullptr);
void Destroy();
// Setting a width/height of 0 will use parent dimensions.
void Resize(u16 width = 0, u16 height = 0);
private:
xcb_connection_t* m_connection = nullptr;
xcb_window_t m_parent_window = {};
xcb_window_t m_window = {};
xcb_colormap_t m_colormap = {};
u16 m_width = 0;
u16 m_height = 0;
};
std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi, Error* error);