File indexing completed on 2025-04-27 11:33:01

0001 /*
0002     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "x11_standalone_sgivideosyncvsyncmonitor.h"
0008 #include "x11_standalone_glxconvenience.h"
0009 #include "x11_standalone_logging.h"
0010 
0011 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0012 #include <private/qtx11extras_p.h>
0013 #else
0014 #include <QX11Info>
0015 #endif
0016 
0017 namespace KWin
0018 {
0019 
0020 std::unique_ptr<SGIVideoSyncVsyncMonitor> SGIVideoSyncVsyncMonitor::create()
0021 {
0022     const char *extensions = glXQueryExtensionsString(QX11Info::display(),
0023                                                       QX11Info::appScreen());
0024     if (!strstr(extensions, "GLX_SGI_video_sync")) {
0025         return nullptr; // GLX_SGI_video_sync is unsupported.
0026     }
0027 
0028     std::unique_ptr<SGIVideoSyncVsyncMonitor> monitor{new SGIVideoSyncVsyncMonitor()};
0029     if (monitor->isValid()) {
0030         return monitor;
0031     } else {
0032         return nullptr;
0033     }
0034 }
0035 
0036 SGIVideoSyncVsyncMonitorHelper::SGIVideoSyncVsyncMonitorHelper()
0037 {
0038     // Establish a new X11 connection to avoid locking up the main X11 connection.
0039     m_display = XOpenDisplay(DisplayString(QX11Info::display()));
0040     if (!m_display) {
0041         qCDebug(KWIN_X11STANDALONE) << "Failed to establish vsync monitor X11 connection";
0042         return;
0043     }
0044 
0045     ::Window rootWindow = DefaultRootWindow(m_display);
0046 
0047     const int attribs[] = {
0048         GLX_RENDER_TYPE, GLX_RGBA_BIT,
0049         GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
0050         0};
0051 
0052     GLXFBConfig config = chooseGlxFbConfig(m_display, attribs);
0053     if (!config) {
0054         qCDebug(KWIN_X11STANDALONE) << "Couldn't find any suitable FBConfig for vsync monitor";
0055         return;
0056     }
0057 
0058     XVisualInfo *visualInfo = glXGetVisualFromFBConfig(m_display, config);
0059     if (!visualInfo) {
0060         return;
0061     }
0062 
0063     Visual *visual = visualInfo->visual;
0064     const int depth = visualInfo->depth;
0065     XFree(visualInfo);
0066 
0067     Colormap colormap = XCreateColormap(m_display, rootWindow, visual, AllocNone);
0068     XSetWindowAttributes attributes;
0069     attributes.colormap = colormap;
0070 
0071     m_dummyWindow = XCreateWindow(m_display, rootWindow, 0, 0, 1, 1, 0, depth,
0072                                   InputOutput, visual, CWColormap, &attributes);
0073     XFreeColormap(m_display, colormap);
0074     if (!m_dummyWindow) {
0075         qCDebug(KWIN_X11STANDALONE) << "Failed to create a dummy window for vsync monitor";
0076         return;
0077     }
0078 
0079     m_drawable = glXCreateWindow(m_display, config, m_dummyWindow, nullptr);
0080     if (!m_drawable) {
0081         qCDebug(KWIN_X11STANDALONE) << "Failed to create GLXWindow for dummy window";
0082         return;
0083     }
0084 
0085     m_localContext = glXCreateNewContext(m_display, config, GLX_RGBA_TYPE, 0, true);
0086     if (!m_localContext) {
0087         qCDebug(KWIN_X11STANDALONE) << "Failed to create opengl context for vsync monitor";
0088         return;
0089     }
0090 }
0091 
0092 SGIVideoSyncVsyncMonitorHelper::~SGIVideoSyncVsyncMonitorHelper()
0093 {
0094     if (m_localContext) {
0095         glXDestroyContext(m_display, m_localContext);
0096     }
0097     if (m_drawable) {
0098         glXDestroyWindow(m_display, m_drawable);
0099     }
0100     if (m_dummyWindow) {
0101         XDestroyWindow(m_display, m_dummyWindow);
0102     }
0103     if (m_display) {
0104         XCloseDisplay(m_display);
0105     }
0106 }
0107 
0108 bool SGIVideoSyncVsyncMonitorHelper::isValid() const
0109 {
0110     return m_display && m_localContext && m_drawable;
0111 }
0112 
0113 void SGIVideoSyncVsyncMonitorHelper::poll()
0114 {
0115     if (!glXMakeCurrent(m_display, m_drawable, m_localContext)) {
0116         qCDebug(KWIN_X11STANDALONE) << "Failed to make vsync monitor OpenGL context current";
0117         Q_EMIT errorOccurred();
0118         return;
0119     }
0120 
0121     uint count;
0122 
0123     glXGetVideoSyncSGI(&count);
0124     glXWaitVideoSyncSGI(2, (count + 1) % 2, &count);
0125 
0126     // Using monotonic clock is inaccurate, but it's still a pretty good estimate.
0127     Q_EMIT vblankOccurred(std::chrono::steady_clock::now().time_since_epoch());
0128 }
0129 
0130 SGIVideoSyncVsyncMonitor::SGIVideoSyncVsyncMonitor()
0131 {
0132     m_helper.moveToThread(&m_thread);
0133 
0134     connect(&m_helper, &SGIVideoSyncVsyncMonitorHelper::errorOccurred,
0135             this, &SGIVideoSyncVsyncMonitor::errorOccurred);
0136     connect(&m_helper, &SGIVideoSyncVsyncMonitorHelper::vblankOccurred,
0137             this, &SGIVideoSyncVsyncMonitor::vblankOccurred);
0138 
0139     m_thread.setObjectName(QStringLiteral("vsync event monitor"));
0140     m_thread.start();
0141 }
0142 
0143 SGIVideoSyncVsyncMonitor::~SGIVideoSyncVsyncMonitor()
0144 {
0145     m_thread.quit();
0146     m_thread.wait();
0147 }
0148 
0149 bool SGIVideoSyncVsyncMonitor::isValid() const
0150 {
0151     return m_helper.isValid();
0152 }
0153 
0154 void SGIVideoSyncVsyncMonitor::arm()
0155 {
0156     QMetaObject::invokeMethod(&m_helper, &SGIVideoSyncVsyncMonitorHelper::poll);
0157 }
0158 
0159 } // namespace KWin