diff --git a/CMakeModules/DuckStationDependencies.cmake b/CMakeModules/DuckStationDependencies.cmake index f7e592bb0..7562ea09a 100644 --- a/CMakeModules/DuckStationDependencies.cmake +++ b/CMakeModules/DuckStationDependencies.cmake @@ -28,8 +28,8 @@ endif() if(ENABLE_X11) find_package(X11 REQUIRED) - if (NOT X11_Xrandr_FOUND) - message(FATAL_ERROR "XRandR extension is required") + if (NOT X11_xcb_FOUND OR NOT X11_xcb_randr_FOUND OR NOT X11_X11_xcb_FOUND) + message(FATAL_ERROR "XCB, XCB-randr and X11-xcb are required") endif() endif() diff --git a/src/duckstation-qt/displaywidget.cpp b/src/duckstation-qt/displaywidget.cpp index b49b72c3a..15beea1a5 100644 --- a/src/duckstation-qt/displaywidget.cpp +++ b/src/duckstation-qt/displaywidget.cpp @@ -58,9 +58,9 @@ int DisplayWidget::scaledWindowHeight() const static_cast(std::ceil(static_cast(height()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1); } -std::optional DisplayWidget::getWindowInfo(Error* error) +std::optional DisplayWidget::getWindowInfo(RenderAPI render_api, Error* error) { - std::optional ret(QtUtils::GetWindowInfoForWidget(this, error)); + std::optional ret = QtUtils::GetWindowInfoForWidget(this, render_api, error); if (ret.has_value()) { m_last_window_width = ret->surface_width; diff --git a/src/duckstation-qt/displaywidget.h b/src/duckstation-qt/displaywidget.h index 8b9fc1bdf..1d5c5fb63 100644 --- a/src/duckstation-qt/displaywidget.h +++ b/src/duckstation-qt/displaywidget.h @@ -13,6 +13,8 @@ class Error; +enum class RenderAPI : u8; + class QCloseEvent; class DisplayWidget final : public QWidget @@ -28,7 +30,7 @@ public: int scaledWindowWidth() const; int scaledWindowHeight() const; - std::optional getWindowInfo(Error* error); + std::optional getWindowInfo(RenderAPI render_api, Error* error); void updateRelativeMode(bool enabled); void updateCursor(bool hidden); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index b9381c2be..8f04c8e8f 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -221,8 +221,8 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr #endif -std::optional MainWindow::acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless, - bool use_main_window_pos, Error* error) +std::optional MainWindow::acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool render_to_main, + bool surfaceless, bool use_main_window_pos, Error* error) { DEV_LOG("acquireRenderWindow() fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}", fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false", @@ -265,7 +265,7 @@ std::optional MainWindow::acquireRenderWindow(bool fullscreen, bool updateWindowState(); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - return m_display_widget->getWindowInfo(error); + return m_display_widget->getWindowInfo(render_api, error); } destroyDisplayWidget(surfaceless); @@ -277,7 +277,7 @@ std::optional MainWindow::acquireRenderWindow(bool fullscreen, bool createDisplayWidget(fullscreen, render_to_main, use_main_window_pos); - std::optional wi = m_display_widget->getWindowInfo(error); + std::optional wi = m_display_widget->getWindowInfo(render_api, error); if (!wi.has_value()) { QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); @@ -2483,9 +2483,9 @@ void MainWindow::checkForSettingChanges() std::optional MainWindow::getWindowInfo() { if (!m_display_widget || isRenderingToMain()) - return QtUtils::GetWindowInfoForWidget(this); + return QtUtils::GetWindowInfoForWidget(this, RenderAPI::None); else if (QWidget* widget = getDisplayContainer()) - return QtUtils::GetWindowInfoForWidget(widget); + return QtUtils::GetWindowInfoForWidget(widget, RenderAPI::None); else return std::nullopt; } @@ -2565,7 +2565,7 @@ void MainWindow::onAchievementsChallengeModeChanged(bool 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, Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error) { @@ -2573,7 +2573,7 @@ bool MainWindow::onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width if (!widget) return false; - const std::optional owi = QtUtils::GetWindowInfoForWidget(widget, error); + const std::optional owi = QtUtils::GetWindowInfoForWidget(widget, render_api, error); if (!owi.has_value()) { widget->destroy(); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 4c0d24932..32551795f 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -35,6 +35,7 @@ class MemoryScannerWindow; struct SystemBootParameters; +enum class RenderAPI : u8; class GPUDevice; namespace Achievements { enum class LoginRequestReason; @@ -127,8 +128,8 @@ private Q_SLOTS: bool confirmMessage(const QString& title, const QString& message); void onStatusMessage(const QString& message); - std::optional acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless, - bool use_main_window_pos, Error* error); + std::optional acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool render_to_main, + bool surfaceless, bool use_main_window_pos, Error* error); void displayResizeRequested(qint32 width, qint32 height); void releaseRenderWindow(); void focusDisplayWidget(); @@ -145,8 +146,9 @@ private Q_SLOTS: void onMediaCaptureStopped(); void onAchievementsLoginRequested(Achievements::LoginRequestReason reason); void onAchievementsChallengeModeChanged(bool enabled); - bool onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width, quint32 height, const QString& title, - const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata, + bool onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, qint32 y, quint32 width, quint32 height, + const QString& title, const QString& icon_name, + Host::AuxiliaryRenderWindowUserData userdata, Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error); void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index a7d9e0584..e8a55a015 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -133,6 +133,7 @@ void QtHost::RegisterTypes() qRegisterMetaType>("std::function"); qRegisterMetaType>(); qRegisterMetaType(); + qRegisterMetaType("RenderAPI"); qRegisterMetaType("GPURenderer"); qRegisterMetaType("InputBindingKey"); qRegisterMetaType("std::string"); @@ -954,7 +955,8 @@ void EmuThread::requestDisplaySize(float scale) System::RequestDisplaySize(scale); } -std::optional EmuThread::acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error) +std::optional EmuThread::acquireRenderWindow(RenderAPI render_api, bool fullscreen, + bool exclusive_fullscreen, Error* error) { DebugAssert(g_gpu_device); @@ -964,8 +966,8 @@ std::optional EmuThread::acquireRenderWindow(bool fullscreen, bool e const bool render_to_main = !fullscreen && m_is_rendering_to_main; const bool use_main_window_pos = shouldRenderToMain(); - return emit onAcquireRenderWindowRequested(window_fullscreen, render_to_main, m_is_surfaceless, use_main_window_pos, - error); + return emit onAcquireRenderWindowRequested(render_api, window_fullscreen, render_to_main, m_is_surfaceless, + use_main_window_pos, error); } 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, AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error) { - return emit g_emu_thread->onCreateAuxiliaryRenderWindow(x, y, width, height, QtUtils::StringViewToQString(title), - QtUtils::StringViewToQString(icon_name), userdata, handle, wi, - error); + return emit g_emu_thread->onCreateAuxiliaryRenderWindow( + g_gpu_device->GetRenderAPI(), x, y, width, height, QtUtils::StringViewToQString(title), + QtUtils::StringViewToQString(icon_name), userdata, handle, wi, error); } void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32* pos_x, s32* pos_y, u32* width, @@ -2002,7 +2004,7 @@ void Host::CommitBaseSettingChanges() std::optional Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, 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() diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 5e234c91c..aa4e71468 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -41,6 +41,7 @@ class QTranslator; class INISettingsInterface; +enum class RenderAPI : u8; class GPUDevice; class MainWindow; @@ -94,7 +95,8 @@ public: ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; } ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; } - std::optional acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error); + std::optional acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, + Error* error); void connectDisplaySignals(DisplayWidget* widget); void releaseRenderWindow(); @@ -137,8 +139,8 @@ Q_SIGNALS: void systemPaused(); void systemResumed(); void gameListRefreshed(); - std::optional onAcquireRenderWindowRequested(bool fullscreen, bool render_to_main, bool surfaceless, - bool use_main_window_pos, Error* error); + std::optional onAcquireRenderWindowRequested(RenderAPI render_api, bool fullscreen, bool render_to_main, + bool surfaceless, bool use_main_window_pos, Error* error); void onResizeRenderWindowRequested(qint32 width, qint32 height); void onReleaseRenderWindowRequested(); void focusDisplayWidgetRequested(); @@ -153,8 +155,9 @@ Q_SIGNALS: void mediaCaptureStarted(); void mediaCaptureStopped(); - bool onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width, quint32 height, const QString& title, - const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata, + bool onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, qint32 y, quint32 width, quint32 height, + const QString& title, const QString& icon_name, + Host::AuxiliaryRenderWindowUserData userdata, Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error); void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size); diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index f613aa2a6..d2082af0f 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -7,6 +7,8 @@ #include "core/game_list.h" #include "core/system.h" +#include "util/gpu_device.h" + #include "common/error.h" #include "common/log.h" @@ -30,6 +32,8 @@ #include #include #include +#include +#include #include #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(1); } -std::optional QtUtils::GetWindowInfoForWidget(QWidget* widget, Error* error) +std::optional QtUtils::GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error) { WindowInfo wi; @@ -333,8 +337,20 @@ std::optional QtUtils::GetWindowInfoForWidget(QWidget* widget, Error const QString platform_name = QGuiApplication::platformName(); if (platform_name == QStringLiteral("xcb")) { - wi.type = WindowInfo::Type::X11; - wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle()); + // 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.window_handle = reinterpret_cast(widget->winId()); } else if (platform_name == QStringLiteral("wayland")) @@ -356,9 +372,12 @@ std::optional QtUtils::GetWindowInfoForWidget(QWidget* widget, Error wi.surface_scale = static_cast(dpr); // Query refresh rate, we need it for sync. - std::optional surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi); + Error refresh_rate_error; + std::optional surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi, &refresh_rate_error); 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. const QScreen* widget_screen = widget->screen(); if (!widget_screen) diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index a16b80fd5..2999d1f6f 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -32,6 +32,8 @@ class QVariant; class QWidget; class QUrl; +enum class RenderAPI : u8; + enum class ConsoleRegion : u8; enum class DiscRegion : u8; namespace GameDatabase { @@ -116,7 +118,7 @@ QIcon GetIconForCompatibility(GameDatabase::CompatibilityRating rating); qreal GetDevicePixelRatioForWidget(const QWidget* widget); /// Returns the common window info structure for a Qt widget. -std::optional GetWindowInfoForWidget(QWidget* widget, Error* error = nullptr); +std::optional GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error = nullptr); /// 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); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index ceb5be2a4..27110353e 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -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) if(ENABLE_X11) + target_sources(util PRIVATE + x11_tools.cpp + x11_tools.h + ) 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() if(ENABLE_WAYLAND) @@ -121,8 +125,10 @@ if(ENABLE_OPENGL) if(ENABLE_X11) target_sources(util PRIVATE - opengl_context_egl_x11.cpp - opengl_context_egl_x11.h + opengl_context_egl_xcb.cpp + opengl_context_egl_xcb.h + opengl_context_egl_xlib.cpp + opengl_context_egl_xlib.h ) endif() if(ENABLE_WAYLAND) diff --git a/src/util/opengl_context.cpp b/src/util/opengl_context.cpp index 49b5b6927..b8222f29c 100644 --- a/src/util/opengl_context.cpp +++ b/src/util/opengl_context.cpp @@ -27,7 +27,8 @@ #include "opengl_context_egl_wayland.h" #endif #ifdef ENABLE_X11 -#include "opengl_context_egl_x11.h" +#include "opengl_context_egl_xcb.h" +#include "opengl_context_egl_xlib.h" #endif #endif #endif @@ -154,8 +155,10 @@ std::unique_ptr OpenGLContext::Create(WindowInfo& wi, SurfaceHand context = OpenGLContextEGLAndroid::Create(wi, surface, versions_to_try, error); #else #if defined(ENABLE_X11) - if (wi.type == WindowInfo::Type::X11) - context = OpenGLContextEGLX11::Create(wi, surface, versions_to_try, error); + if (wi.type == WindowInfo::Type::Xlib) + 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 #if defined(ENABLE_WAYLAND) if (wi.type == WindowInfo::Type::Wayland) diff --git a/src/util/opengl_context_egl_xcb.cpp b/src/util/opengl_context_egl_xcb.cpp new file mode 100644 index 000000000..452ec4de9 --- /dev/null +++ b/src/util/opengl_context_egl_xcb.cpp @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// 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 OpenGLContextEGLXCB::Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error) +{ + std::unique_ptr context = std::make_unique(); + if (!context->Initialize(wi, surface, versions_to_try, error)) + return nullptr; + + return context; +} + +std::unique_ptr OpenGLContextEGLXCB::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, + Error* error) +{ + std::unique_ptr context = std::make_unique(); + 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(reinterpret_cast(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(wi.display_connection), xcb_window, + static_cast(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); +} diff --git a/src/util/opengl_context_egl_xcb.h b/src/util/opengl_context_egl_xcb.h new file mode 100644 index 000000000..cbff132bb --- /dev/null +++ b/src/util/opengl_context_egl_xcb.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "opengl_context_egl.h" +#include "x11_tools.h" + +#include + +class OpenGLContextEGLXCB final : public OpenGLContextEGL +{ +public: + OpenGLContextEGLXCB(); + ~OpenGLContextEGLXCB() override; + + static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error); + + std::unique_ptr 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; + + X11WindowMap m_x11_windows; + bool m_using_platform_display = false; +}; diff --git a/src/util/opengl_context_egl_x11.cpp b/src/util/opengl_context_egl_xlib.cpp similarity index 55% rename from src/util/opengl_context_egl_x11.cpp rename to src/util/opengl_context_egl_xlib.cpp index bc4b53108..cbd36a225 100644 --- a/src/util/opengl_context_egl_x11.cpp +++ b/src/util/opengl_context_egl_xlib.cpp @@ -1,28 +1,28 @@ // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // 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" -OpenGLContextEGLX11::OpenGLContextEGLX11() = default; +OpenGLContextEGLXlib::OpenGLContextEGLXlib() = default; -OpenGLContextEGLX11::~OpenGLContextEGLX11() = default; +OpenGLContextEGLXlib::~OpenGLContextEGLXlib() = default; -std::unique_ptr OpenGLContextEGLX11::Create(WindowInfo& wi, SurfaceHandle* surface, - std::span versions_to_try, Error* error) +std::unique_ptr OpenGLContextEGLXlib::Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error) { - std::unique_ptr context = std::make_unique(); + std::unique_ptr context = std::make_unique(); if (!context->Initialize(wi, surface, versions_to_try, error)) return nullptr; return context; } -std::unique_ptr OpenGLContextEGLX11::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, - Error* error) +std::unique_ptr OpenGLContextEGLXlib::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, + Error* error) { - std::unique_ptr context = std::make_unique(); + std::unique_ptr context = std::make_unique(); context->m_display = m_display; if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error)) @@ -31,7 +31,7 @@ std::unique_ptr OpenGLContextEGLX11::CreateSharedContext(WindowIn 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"); if (dpy == EGL_NO_DISPLAY) @@ -40,7 +40,7 @@ EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(const WindowInfo& wi, Error* 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 // version requires the window itself, casted to void*... diff --git a/src/util/opengl_context_egl_x11.h b/src/util/opengl_context_egl_xlib.h similarity index 84% rename from src/util/opengl_context_egl_x11.h rename to src/util/opengl_context_egl_xlib.h index c37044de4..d1ac6ccdc 100644 --- a/src/util/opengl_context_egl_x11.h +++ b/src/util/opengl_context_egl_xlib.h @@ -5,11 +5,11 @@ #include "opengl_context_egl.h" -class OpenGLContextEGLX11 final : public OpenGLContextEGL +class OpenGLContextEGLXlib final : public OpenGLContextEGL { public: - OpenGLContextEGLX11(); - ~OpenGLContextEGLX11() override; + OpenGLContextEGLXlib(); + ~OpenGLContextEGLXlib() override; static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, std::span versions_to_try, Error* error); diff --git a/src/util/platform_misc.h b/src/util/platform_misc.h index 72cf9e5f6..b874d64d0 100644 --- a/src/util/platform_misc.h +++ b/src/util/platform_misc.h @@ -25,5 +25,5 @@ std::optional GetTopLevelWindowInfo(); // TODO: Move all the other Cocoa stuff in here. namespace CocoaTools { /// Returns the refresh rate of the display the window is placed on. -std::optional GetViewRefreshRate(const WindowInfo& wi); -} +std::optional GetViewRefreshRate(const WindowInfo& wi, Error* error); +} // namespace CocoaTools diff --git a/src/util/platform_misc_mac.mm b/src/util/platform_misc_mac.mm index 43da74413..a0077aa21 100644 --- a/src/util/platform_misc_mac.mm +++ b/src/util/platform_misc_mac.mm @@ -129,12 +129,12 @@ void CocoaTools::DestroyMetalLayer(const WindowInfo& wi, void* layer) [clayer release]; } -std::optional CocoaTools::GetViewRefreshRate(const WindowInfo& wi) +std::optional CocoaTools::GetViewRefreshRate(const WindowInfo& wi, Error* error) { if (![NSThread isMainThread]) { std::optional 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; } @@ -146,6 +146,10 @@ std::optional CocoaTools::GetViewRefreshRate(const WindowInfo& wi) ret = CGDisplayModeGetRefreshRate(mode); CGDisplayModeRelease(mode); } + else + { + Error::SetStringView(error, "CGDisplayCopyDisplayMode() failed"); + } return ret; } diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index c56d19431..568c7172e 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -57,7 +57,10 @@ true - + + true + + true @@ -101,6 +104,9 @@ + + true + @@ -159,7 +165,10 @@ true - + + true + + true @@ -202,6 +211,9 @@ + + true + diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 83622b0b9..314230bc2 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -66,13 +66,15 @@ - + + + @@ -146,7 +148,7 @@ - + @@ -154,6 +156,8 @@ + + diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index dd016f11d..924ffde3f 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -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)) return false; #endif -#if defined(VK_USE_PLATFORM_XLIB_KHR) - if (wi.type == WindowInfo::Type::X11 && !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true)) +#if defined(VK_USE_PLATFORM_XCB_KHR) + if (wi.type == WindowInfo::Type::XCB && !SupportsExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME, true)) return false; #endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) diff --git a/src/util/vulkan_entry_points.inl b/src/util/vulkan_entry_points.inl index 90320b332..1825cfc21 100644 --- a/src/util/vulkan_entry_points.inl +++ b/src/util/vulkan_entry_points.inl @@ -40,12 +40,10 @@ VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceSurfacePresentModesKHR, false) #if defined(VK_USE_PLATFORM_WIN32_KHR) VULKAN_INSTANCE_ENTRY_POINT(vkCreateWin32SurfaceKHR, false) -VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceWin32PresentationSupportKHR, false) #endif -#if defined(VK_USE_PLATFORM_XLIB_KHR) -VULKAN_INSTANCE_ENTRY_POINT(vkCreateXlibSurfaceKHR, false) -VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceXlibPresentationSupportKHR, false) +#if defined(VK_USE_PLATFORM_XCB_KHR) +VULKAN_INSTANCE_ENTRY_POINT(vkCreateXcbSurfaceKHR, false) #endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) diff --git a/src/util/vulkan_loader.h b/src/util/vulkan_loader.h index 692d40ccf..ef2c6f874 100644 --- a/src/util/vulkan_loader.h +++ b/src/util/vulkan_loader.h @@ -18,7 +18,7 @@ class Error; #define VK_USE_PLATFORM_ANDROID_KHR #else #ifdef ENABLE_X11 -#define VK_USE_PLATFORM_XLIB_KHR +#define VK_USE_PLATFORM_XCB_KHR #endif #ifdef ENABLE_WAYLAND @@ -28,48 +28,6 @@ class Error; #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" // We include vk_mem_alloc globally, so we don't accidentally include it before the vulkan header somewhere. diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp index 6f37ddb5a..233270f08 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -138,17 +138,17 @@ bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physic } #endif -#if defined(VK_USE_PLATFORM_XLIB_KHR) - if (m_window_info.type == WindowInfo::Type::X11) +#if defined(VK_USE_PLATFORM_XCB_KHR) + if (m_window_info.type == WindowInfo::Type::XCB) { - const VkXlibSurfaceCreateInfoKHR surface_create_info = { - .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, - .dpy = static_cast(m_window_info.display_connection), - .window = reinterpret_cast(m_window_info.window_handle)}; - const VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); + const VkXcbSurfaceCreateInfoKHR surface_create_info = { + .sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, + .connection = static_cast(m_window_info.display_connection), + .window = static_cast(reinterpret_cast(m_window_info.window_handle))}; + const VkResult res = vkCreateXcbSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { - Vulkan::SetErrorObject(error, "vkCreateXlibSurfaceKHR failed: ", res); + Vulkan::SetErrorObject(error, "vkCreateXcbSurfaceKHR failed: ", res); return false; } diff --git a/src/util/window_info.cpp b/src/util/window_info.cpp index bc74d8a2b..b54b00e3d 100644 --- a/src/util/window_info.cpp +++ b/src/util/window_info.cpp @@ -16,13 +16,13 @@ LOG_CHANNEL(WindowInfo); #include "common/windows_headers.h" #include -static std::optional GetRefreshRateFromDisplayConfig(HWND hwnd) +static std::optional GetRefreshRateFromDisplayConfig(HWND hwnd, Error* error) { // Partially based on Chromium ui/display/win/display_config_helper.cc. const HMONITOR monitor = MonitorFromWindow(hwnd, 0); if (!monitor) [[unlikely]] { - ERROR_LOG("{}() failed: {}", "MonitorFromWindow", Error::CreateWin32(GetLastError()).GetDescription()); + Error::SetWin32(error, "MonitorFromWindow() failed: ", GetLastError()); return std::nullopt; } @@ -30,7 +30,7 @@ static std::optional GetRefreshRateFromDisplayConfig(HWND hwnd) mi.cbSize = sizeof(mi); if (!GetMonitorInfoW(monitor, &mi)) { - ERROR_LOG("{}() failed: {}", "GetMonitorInfoW", Error::CreateWin32(GetLastError()).GetDescription()); + Error::SetWin32(error, "GetMonitorInfoW() failed: ", GetLastError()); return std::nullopt; } @@ -44,7 +44,7 @@ static std::optional GetRefreshRateFromDisplayConfig(HWND hwnd) LONG res = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_size, &mode_size); if (res != ERROR_SUCCESS) { - ERROR_LOG("{}() failed: {}", "GetDisplayConfigBufferSizes", Error::CreateWin32(res).GetDescription()); + Error::SetWin32(error, "GetDisplayConfigBufferSizes() failed: ", res); return std::nullopt; } @@ -56,7 +56,7 @@ static std::optional GetRefreshRateFromDisplayConfig(HWND hwnd) break; if (res != ERROR_INSUFFICIENT_BUFFER) { - ERROR_LOG("{}() failed: {}", "QueryDisplayConfig", Error::CreateWin32(res).GetDescription()); + Error::SetWin32(error, "QueryDisplayConfig() failed: ", res); return std::nullopt; } } @@ -70,7 +70,7 @@ static std::optional GetRefreshRateFromDisplayConfig(HWND hwnd) LONG res = DisplayConfigGetDeviceInfo(&sdn.header); if (res != ERROR_SUCCESS) { - ERROR_LOG("{}() failed: {}", "DisplayConfigGetDeviceInfo", Error::CreateWin32(res).GetDescription()); + Error::SetWin32(error, "DisplayConfigGetDeviceInfo() failed: ", res); continue; } @@ -85,15 +85,19 @@ static std::optional GetRefreshRateFromDisplayConfig(HWND hwnd) return std::nullopt; } -static std::optional GetRefreshRateFromDWM(HWND hwnd) +static std::optional GetRefreshRateFromDWM(HWND hwnd, Error* error) { 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; + } DWM_TIMING_INFO ti = {}; ti.cbSize = sizeof(ti); - HRESULT hr = DwmGetCompositionTimingInfo(nullptr, &ti); + hr = DwmGetCompositionTimingInfo(nullptr, &ti); if (SUCCEEDED(hr)) { if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0) @@ -101,15 +105,21 @@ static std::optional GetRefreshRateFromDWM(HWND hwnd) return static_cast(ti.rateRefresh.uiNumerator) / static_cast(ti.rateRefresh.uiDenominator); } - - return std::nullopt; + else + { + Error::SetHResult(error, "DwmGetCompositionTimingInfo() failed: ", hr); + return std::nullopt; + } } -static std::optional GetRefreshRateFromMonitor(HWND hwnd) +static std::optional GetRefreshRateFromMonitor(HWND hwnd, Error* error) { HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (!mon) + { + Error::SetWin32(error, "MonitorFromWindow() failed: ", GetLastError()); return std::nullopt; + } MONITORINFOEXW mi = {}; mi.cbSize = sizeof(mi); @@ -120,26 +130,46 @@ static std::optional GetRefreshRateFromMonitor(HWND hwnd) // 0/1 are reserved for "defaults". if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1) + { return static_cast(dm.dmDisplayFrequency); + } + else + { + Error::SetWin32(error, "EnumDisplaySettingsW() failed: ", GetLastError()); + return std::nullopt; + } + } + else + { + Error::SetWin32(error, "GetMonitorInfoW() failed: ", GetLastError()); + return std::nullopt; } - - return std::nullopt; } -std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) +std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error) { std::optional ret; if (wi.type != Type::Win32 || !wi.window_handle) + { + Error::SetStringView(error, "Invalid window type."); return ret; + } // Try DWM first, then fall back to integer values. const HWND hwnd = static_cast(wi.window_handle); - ret = GetRefreshRateFromDisplayConfig(hwnd); + Error local_error; + ret = GetRefreshRateFromDisplayConfig(hwnd, &local_error); if (!ret.has_value()) { - ret = GetRefreshRateFromDWM(hwnd); + WARNING_LOG("GetRefreshRateFromDisplayConfig() failed: {}", local_error.GetDescription()); + + ret = GetRefreshRateFromDWM(hwnd, &local_error); if (!ret.has_value()) - ret = GetRefreshRateFromMonitor(hwnd); + { + WARNING_LOG("GetRefreshRateFromDWM() failed: {}", local_error.GetDescription()); + + ret = GetRefreshRateFromMonitor(hwnd, error); + } } return ret; @@ -149,157 +179,29 @@ std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) #include "util/platform_misc.h" -std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) +std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error) { if (wi.type == WindowInfo::Type::MacOS) - return CocoaTools::GetViewRefreshRate(wi); + return CocoaTools::GetViewRefreshRate(wi, error); + Error::SetStringView(error, "Invalid window type."); return std::nullopt; } #else #ifdef ENABLE_X11 - -#include -#include -#include - -// 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 GetRefreshRateFromXRandR(const WindowInfo& wi) -{ - Display* display = static_cast(wi.display_connection); - Window window = static_cast(reinterpret_cast(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(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(static_cast(mode->dotClock) / - (static_cast(mode->hTotal) * static_cast(mode->vTotal))); -} - -#endif // ENABLE_X11 - -std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) -{ -#if defined(ENABLE_X11) - if (wi.type == WindowInfo::Type::X11) - return GetRefreshRateFromXRandR(wi); +#include "x11_tools.h" #endif +std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error) +{ +#if defined(ENABLE_X11) + if (wi.type == WindowInfo::Type::Xlib || wi.type == WindowInfo::Type::XCB) + return GetRefreshRateFromXRandR(wi, error); +#endif + + Error::SetStringView(error, "Invalid window type."); return std::nullopt; } diff --git a/src/util/window_info.h b/src/util/window_info.h index 61d9570c7..4bb672b46 100644 --- a/src/util/window_info.h +++ b/src/util/window_info.h @@ -8,6 +8,8 @@ #include +class Error; + // Contains the information required to create a graphics context in a window. struct WindowInfo { @@ -15,7 +17,8 @@ struct WindowInfo { Surfaceless, Win32, - X11, + Xlib, + XCB, Wayland, MacOS, Android, @@ -32,5 +35,5 @@ struct WindowInfo ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; } - static std::optional QueryRefreshRateForWindow(const WindowInfo& wi); + static std::optional QueryRefreshRateForWindow(const WindowInfo& wi, Error* error = nullptr); }; diff --git a/src/util/x11_tools.cpp b/src/util/x11_tools.cpp new file mode 100644 index 000000000..7c79a5781 --- /dev/null +++ b/src/util/x11_tools.cpp @@ -0,0 +1,325 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// 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 +#include +#include + +#include + +LOG_CHANNEL(WindowInfo); + +namespace { +template +struct XCBPointerDeleter +{ + void operator()(T* ptr) { free(ptr); } +}; + +template +using XCBPointer = std::unique_ptr>; +} // 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 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 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 GetRefreshRateFromXRandR(const WindowInfo& wi, Error* error) +{ + xcb_connection_t* connection = nullptr; + if (wi.type == WindowInfo::Type::Xlib) + { + connection = XGetXCBConnection(static_cast(wi.display_connection)); + } + else if (wi.type == WindowInfo::Type::XCB) + { + connection = static_cast(wi.display_connection); + } + + xcb_window_t window = static_cast(reinterpret_cast(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 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 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 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 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(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(static_cast(mode->dot_clock) / + (static_cast(mode->htotal) * static_cast(mode->vtotal))); +} diff --git a/src/util/x11_tools.h b/src/util/x11_tools.h new file mode 100644 index 000000000..24dfad6e3 --- /dev/null +++ b/src/util/x11_tools.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "common/types.h" + +#include +#include + +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 GetRefreshRateFromXRandR(const WindowInfo& wi, Error* error);