mirror of
https://github.com/stenzek/duckstation.git
synced 2024-11-22 21:39:40 +00:00
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
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:
parent
816ef45199
commit
e69f0d3cce
@ -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()
|
||||
|
||||
|
@ -58,9 +58,9 @@ int DisplayWidget::scaledWindowHeight() const
|
||||
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())
|
||||
{
|
||||
m_last_window_width = ret->surface_width;
|
||||
|
@ -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<WindowInfo> getWindowInfo(Error* error);
|
||||
std::optional<WindowInfo> getWindowInfo(RenderAPI render_api, Error* error);
|
||||
|
||||
void updateRelativeMode(bool enabled);
|
||||
void updateCursor(bool hidden);
|
||||
|
@ -221,8 +221,8 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
|
||||
|
||||
#endif
|
||||
|
||||
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless,
|
||||
bool use_main_window_pos, Error* error)
|
||||
std::optional<WindowInfo> 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<WindowInfo> 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<WindowInfo> MainWindow::acquireRenderWindow(bool fullscreen, bool
|
||||
|
||||
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())
|
||||
{
|
||||
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()
|
||||
{
|
||||
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<WindowInfo> owi = QtUtils::GetWindowInfoForWidget(widget, error);
|
||||
const std::optional<WindowInfo> owi = QtUtils::GetWindowInfoForWidget(widget, render_api, error);
|
||||
if (!owi.has_value())
|
||||
{
|
||||
widget->destroy();
|
||||
|
@ -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<WindowInfo> acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless,
|
||||
bool use_main_window_pos, Error* error);
|
||||
std::optional<WindowInfo> 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);
|
||||
|
||||
|
@ -133,6 +133,7 @@ void QtHost::RegisterTypes()
|
||||
qRegisterMetaType<std::function<void()>>("std::function<void()>");
|
||||
qRegisterMetaType<std::shared_ptr<SystemBootParameters>>();
|
||||
qRegisterMetaType<const GameList::Entry*>();
|
||||
qRegisterMetaType<RenderAPI>("RenderAPI");
|
||||
qRegisterMetaType<GPURenderer>("GPURenderer");
|
||||
qRegisterMetaType<InputBindingKey>("InputBindingKey");
|
||||
qRegisterMetaType<std::string>("std::string");
|
||||
@ -954,7 +955,8 @@ void EmuThread::requestDisplaySize(float 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);
|
||||
|
||||
@ -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 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<WindowInfo> 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()
|
||||
|
@ -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<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 releaseRenderWindow();
|
||||
|
||||
@ -137,8 +139,8 @@ Q_SIGNALS:
|
||||
void systemPaused();
|
||||
void systemResumed();
|
||||
void gameListRefreshed();
|
||||
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool fullscreen, bool render_to_main, bool surfaceless,
|
||||
bool use_main_window_pos, Error* error);
|
||||
std::optional<WindowInfo> 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);
|
||||
|
||||
|
@ -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 <QtWidgets/QTreeView>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Error* error)
|
||||
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error)
|
||||
{
|
||||
WindowInfo wi;
|
||||
|
||||
@ -333,8 +337,20 @@ std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Error
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
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.window_handle = reinterpret_cast<void*>(widget->winId());
|
||||
}
|
||||
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);
|
||||
|
||||
// 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())
|
||||
{
|
||||
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)
|
||||
|
@ -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<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.
|
||||
bool SaveWindowGeometry(std::string_view window_name, QWidget* widget, bool auto_commit_changes = true);
|
||||
|
@ -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)
|
||||
|
@ -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> 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)
|
||||
|
105
src/util/opengl_context_egl_xcb.cpp
Normal file
105
src/util/opengl_context_egl_xcb.cpp
Normal 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);
|
||||
}
|
34
src/util/opengl_context_egl_xcb.h
Normal file
34
src/util/opengl_context_egl_xcb.h
Normal 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;
|
||||
};
|
@ -1,28 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// 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<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::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))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLXlib::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
|
||||
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;
|
||||
|
||||
if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
|
||||
@ -31,7 +31,7 @@ std::unique_ptr<OpenGLContext> 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*...
|
@ -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<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error);
|
@ -25,5 +25,5 @@ std::optional<WindowInfo> 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<float> GetViewRefreshRate(const WindowInfo& wi);
|
||||
}
|
||||
std::optional<float> GetViewRefreshRate(const WindowInfo& wi, Error* error);
|
||||
} // namespace CocoaTools
|
||||
|
@ -129,12 +129,12 @@ void CocoaTools::DestroyMetalLayer(const WindowInfo& wi, void* layer)
|
||||
[clayer release];
|
||||
}
|
||||
|
||||
std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
|
||||
std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi, Error* error)
|
||||
{
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -146,6 +146,10 @@ std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
|
||||
ret = CGDisplayModeGetRefreshRate(mode);
|
||||
CGDisplayModeRelease(mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Error::SetStringView(error, "CGDisplayCopyDisplayMode() failed");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -57,7 +57,10 @@
|
||||
<ClInclude Include="opengl_context_egl_wayland.h">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</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>
|
||||
</ClInclude>
|
||||
<ClInclude Include="opengl_context_wgl.h">
|
||||
@ -101,6 +104,9 @@
|
||||
<ClInclude Include="wav_reader_writer.h" />
|
||||
<ClInclude Include="win32_raw_input_source.h" />
|
||||
<ClInclude Include="window_info.h" />
|
||||
<ClInclude Include="x11_tools.h">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="xinput_source.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -159,7 +165,10 @@
|
||||
<ClCompile Include="opengl_context_egl_wayland.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</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>
|
||||
</ClCompile>
|
||||
<ClCompile Include="opengl_context_wgl.cpp">
|
||||
@ -202,6 +211,9 @@
|
||||
<ClCompile Include="wav_reader_writer.cpp" />
|
||||
<ClCompile Include="win32_raw_input_source.cpp" />
|
||||
<ClCompile Include="window_info.cpp" />
|
||||
<ClCompile Include="x11_tools.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="xinput_source.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -66,13 +66,15 @@
|
||||
<ClInclude Include="opengl_context_agl.h" />
|
||||
<ClInclude Include="opengl_context_egl.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="image.h" />
|
||||
<ClInclude Include="sockets.h" />
|
||||
<ClInclude Include="media_capture.h" />
|
||||
<ClInclude Include="compress_helpers.h" />
|
||||
<ClInclude Include="elf_file.h" />
|
||||
<ClInclude Include="x11_tools.h" />
|
||||
<ClInclude Include="opengl_context_egl_xlib.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="state_wrapper.cpp" />
|
||||
@ -146,7 +148,7 @@
|
||||
<ClCompile Include="opengl_context.cpp" />
|
||||
<ClCompile Include="opengl_context_egl.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="image.cpp" />
|
||||
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||
@ -154,6 +156,8 @@
|
||||
<ClCompile Include="media_capture.cpp" />
|
||||
<ClCompile Include="compress_helpers.cpp" />
|
||||
<ClCompile Include="elf_file.cpp" />
|
||||
<ClCompile Include="x11_tools.cpp" />
|
||||
<ClCompile Include="opengl_context_egl_xlib.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="metal_shaders.metal" />
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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<Display*>(m_window_info.display_connection),
|
||||
.window = reinterpret_cast<Window>(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<xcb_connection_t*>(m_window_info.display_connection),
|
||||
.window = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(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;
|
||||
}
|
||||
|
||||
|
@ -16,13 +16,13 @@ LOG_CHANNEL(WindowInfo);
|
||||
#include "common/windows_headers.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.
|
||||
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<float> 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<float> 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<float> 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<float> 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<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<float> GetRefreshRateFromDWM(HWND hwnd)
|
||||
static std::optional<float> 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<float> GetRefreshRateFromDWM(HWND hwnd)
|
||||
|
||||
return static_cast<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Error::SetHResult(error, "DwmGetCompositionTimingInfo() failed: ", hr);
|
||||
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);
|
||||
if (!mon)
|
||||
{
|
||||
Error::SetWin32(error, "MonitorFromWindow() failed: ", GetLastError());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MONITORINFOEXW mi = {};
|
||||
mi.cbSize = sizeof(mi);
|
||||
@ -120,26 +130,46 @@ static std::optional<float> GetRefreshRateFromMonitor(HWND hwnd)
|
||||
|
||||
// 0/1 are reserved for "defaults".
|
||||
if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1)
|
||||
{
|
||||
return static_cast<float>(dm.dmDisplayFrequency);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Error::SetWin32(error, "EnumDisplaySettingsW() failed: ", GetLastError());
|
||||
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;
|
||||
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<HWND>(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<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
|
||||
|
||||
#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)
|
||||
return CocoaTools::GetViewRefreshRate(wi);
|
||||
return CocoaTools::GetViewRefreshRate(wi, error);
|
||||
|
||||
Error::SetStringView(error, "Invalid window type.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifdef ENABLE_X11
|
||||
#include "x11_tools.h"
|
||||
#endif
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#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)
|
||||
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi, Error* error)
|
||||
{
|
||||
#if defined(ENABLE_X11)
|
||||
if (wi.type == WindowInfo::Type::X11)
|
||||
return GetRefreshRateFromXRandR(wi);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include <optional>
|
||||
|
||||
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<float> QueryRefreshRateForWindow(const WindowInfo& wi);
|
||||
static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi, Error* error = nullptr);
|
||||
};
|
||||
|
325
src/util/x11_tools.cpp
Normal file
325
src/util/x11_tools.cpp
Normal 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
45
src/util/x11_tools.h
Normal 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);
|
Loading…
Reference in New Issue
Block a user