diff --git a/composite.cpp b/composite.cpp index 73d7a1562..2b4943ead 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,983 +1,1014 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "composite.h" #include "dbusinterface.h" #include "x11client.h" #include "decorations/decoratedclient.h" #include "deleted.h" #include "effects.h" #include "internal_client.h" #include "overlaywindow.h" #include "platform.h" #include "scene.h" #include "screens.h" #include "shadow.h" #include "xdgshellclient.h" #include "unmanaged.h" #include "useractions.h" #include "utils.h" #include "wayland_server.h" #include "workspace.h" #include "xcbutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::X11Compositor::SuspendReason) namespace KWin { // See main.cpp: extern int screen_number; extern bool is_multihead; extern int currentRefreshRate(); Compositor *Compositor::s_compositor = nullptr; Compositor *Compositor::self() { return s_compositor; } WaylandCompositor *WaylandCompositor::create(QObject *parent) { Q_ASSERT(!s_compositor); auto *compositor = new WaylandCompositor(parent); s_compositor = compositor; return compositor; } X11Compositor *X11Compositor::create(QObject *parent) { Q_ASSERT(!s_compositor); auto *compositor = new X11Compositor(parent); s_compositor = compositor; return compositor; } class CompositorSelectionOwner : public KSelectionOwner { Q_OBJECT public: CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, connection(), rootWindow()) , m_owning(false) { connect (this, &CompositorSelectionOwner::lostOwnership, this, [this]() { m_owning = false; }); } bool owning() const { return m_owning; } void setOwning(bool own) { m_owning = own; } private: bool m_owning; }; static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; } static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); } Compositor::Compositor(QObject* workspace) : QObject(workspace) , m_state(State::Off) , m_selectionOwner(nullptr) - , m_timerOffset(0) - , m_bufferSwapPending(false) + , vBlankInterval(0) + , fpsInterval(0) + , m_timeSinceLastVBlank(0) , m_scene(nullptr) + , m_bufferSwapPending(false) + , m_composeAtSwapCompletion(false) { connect(options, &Options::configChanged, this, &Compositor::configChanged); connect(options, &Options::animationSpeedChanged, this, &Compositor::configChanged); m_monotonicClock.start(); // 2 sec which should be enough to restart the compositor. static const int compositorLostMessageDelay = 2000; m_releaseSelectionTimer.setSingleShot(true); m_releaseSelectionTimer.setInterval(compositorLostMessageDelay); connect(&m_releaseSelectionTimer, &QTimer::timeout, this, &Compositor::releaseCompositorSelection); m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay); m_unusedSupportPropertyTimer.setSingleShot(true); connect(&m_unusedSupportPropertyTimer, &QTimer::timeout, this, &Compositor::deleteUnusedSupportProperties); // Delay the call to start by one event cycle. // The ctor of this class is invoked from the Workspace ctor, that means before // Workspace is completely constructed, so calling Workspace::self() would result // in undefined behavior. This is fixed by using a delayed invocation. if (kwinApp()->platform()->isReady()) { QTimer::singleShot(0, this, &Compositor::start); } connect(kwinApp()->platform(), &Platform::readyChanged, this, [this] (bool ready) { if (ready) { start(); } else { stop(); } }, Qt::QueuedConnection ); if (qEnvironmentVariableIsSet("KWIN_MAX_FRAMES_TESTED")) m_framesToTestForSafety = qEnvironmentVariableIntValue("KWIN_MAX_FRAMES_TESTED"); // register DBus new CompositorDBusInterface(this); } Compositor::~Compositor() { emit aboutToDestroy(); stop(); deleteUnusedSupportProperties(); destroyCompositorSelection(); s_compositor = nullptr; } bool Compositor::setupStart() { if (kwinApp()->isTerminating()) { // Don't start while KWin is terminating. An event to restart might be lingering // in the event queue due to graphics reset. return false; } if (m_state != State::Off) { return false; } m_state = State::Starting; options->reloadCompositingSettings(true); setupX11Support(); // There might still be a deleted around, needs to be cleared before // creating the scene (BUG 333275). if (Workspace::self()) { while (!Workspace::self()->deletedList().isEmpty()) { Workspace::self()->deletedList().first()->discard(); } } emit aboutToToggleCompositing(); auto supportedCompositors = kwinApp()->platform()->supportedCompositors(); const auto userConfigIt = std::find(supportedCompositors.begin(), supportedCompositors.end(), options->compositingMode()); if (userConfigIt != supportedCompositors.end()) { supportedCompositors.erase(userConfigIt); supportedCompositors.prepend(options->compositingMode()); } else { qCWarning(KWIN_CORE) << "Configured compositor not supported by Platform. Falling back to defaults"; } const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.scenes")); for (auto type : qAsConst(supportedCompositors)) { const auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [type] (const auto &plugin) { const auto &metaData = plugin.rawData(); auto it = metaData.find(QStringLiteral("CompositingType")); if (it != metaData.end()) { if ((*it).toInt() == int{type}) { return true; } } return false; }); if (pluginIt != availablePlugins.end()) { std::unique_ptr factory{ qobject_cast(pluginIt->instantiate()) }; if (factory) { m_scene = factory->create(this); if (m_scene) { if (!m_scene->initFailed()) { qCDebug(KWIN_CORE) << "Instantiated compositing plugin:" << pluginIt->name(); break; } else { delete m_scene; m_scene = nullptr; } } } } } if (m_scene == nullptr || m_scene->initFailed()) { qCCritical(KWIN_CORE) << "Failed to initialize compositing, compositing disabled"; m_state = State::Off; delete m_scene; m_scene = nullptr; if (m_selectionOwner) { m_selectionOwner->setOwning(false); m_selectionOwner->release(); } if (!supportedCompositors.contains(NoCompositing)) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return false; } CompositingType compositingType = m_scene->compositingType(); if (compositingType & OpenGLCompositing) { // Override for OpenGl sub-type OpenGL2Compositing. compositingType = OpenGLCompositing; } kwinApp()->platform()->setSelectedCompositor(compositingType); if (!Workspace::self() && m_scene && m_scene->compositingType() == QPainterCompositing) { // Force Software QtQuick on first startup with QPainter. QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); } connect(m_scene, &Scene::resetCompositing, this, &Compositor::reinitialize); emit sceneCreated(); return true; } void Compositor::claimCompositorSelection() { if (!m_selectionOwner) { char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber()); m_selectionOwner = new CompositorSelectionOwner(selection_name); connect(m_selectionOwner, &CompositorSelectionOwner::lostOwnership, this, &Compositor::stop); } if (!m_selectionOwner) { // No X11 yet. return; } if (!m_selectionOwner->owning()) { // Force claim ownership. m_selectionOwner->claim(true); m_selectionOwner->setOwning(true); } } void Compositor::setupX11Support() { auto *con = kwinApp()->x11Connection(); if (!con) { delete m_selectionOwner; m_selectionOwner = nullptr; return; } claimCompositorSelection(); xcb_composite_redirect_subwindows(con, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } void Compositor::startupWithWorkspace() { connect(kwinApp(), &Application::x11ConnectionChanged, this, &Compositor::setupX11Support, Qt::UniqueConnection); Workspace::self()->markXStackingOrderAsDirty(); Q_ASSERT(m_scene); connect(workspace(), &Workspace::destroyed, this, [this] { compositeTimer.stop(); }); setupX11Support(); + fpsInterval = options->maxFpsInterval(); + + const auto rate = currentRefreshRate(); + Q_ASSERT(rate != 0); // There is a fallback in options.cpp, so why check at all? + + // If we do vsync, set the fps to the next multiple of the vblank rate. + vBlankInterval = milliToNano(1000) / currentRefreshRate(); + fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); // Sets also the 'effects' pointer. kwinApp()->platform()->createEffectsHandler(this, m_scene); connect(Workspace::self(), &Workspace::deletedRemoved, m_scene, &Scene::removeToplevel); connect(effects, &EffectsHandler::screenGeometryChanged, this, &Compositor::addRepaintFull); for (X11Client *c : Workspace::self()->clientList()) { c->setupCompositing(); c->updateShadow(); } for (X11Client *c : Workspace::self()->desktopList()) { c->setupCompositing(); } for (Unmanaged *c : Workspace::self()->unmanagedList()) { c->setupCompositing(); c->updateShadow(); } for (InternalClient *client : workspace()->internalClients()) { client->setupCompositing(); client->updateShadow(); } if (auto *server = waylandServer()) { const auto clients = server->clients(); for (XdgShellClient *c : clients) { c->setupCompositing(); c->updateShadow(); } } m_state = State::On; emit compositingToggled(true); if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } // Render at least once. addRepaintFull(); performCompositing(); } void Compositor::scheduleRepaint() { if (m_state != State::On) { return; } // Don't repaint if all outputs are disabled if (!kwinApp()->platform()->areOutputsEnabled()) { return; } // TODO: Make this distinction not on the question if there is a swap event but if per screen // rendering? On X we get swap events but they are aligned with the "wrong" screen if // it the primary/first one is not the one with the highest refresh rate. // But on the other side Present extension does not allow to sync with another screen // anyway. - setCompositeTimer(); - + if (m_scene->hasSwapEvent()) { + // TODO: If we don't call it back from the event loop we often crash on Wayland + // in AnimationEffect::postPaintScreen. Why? + // Theory is that effects call addRepaintFull in there and then performCompositing + // is called again while still in the first paint. So queing it here makes sense! + compositeTimer.start(0, this); + } else { + setCompositeTimer(); + } } void Compositor::stop() { if (m_state == State::Off || m_state == State::Stopping) { return; } m_state = State::Stopping; emit aboutToToggleCompositing(); m_releaseSelectionTimer.start(); // Some effects might need access to effect windows when they are about to // be destroyed, for example to unreference deleted windows, so we have to // make sure that effect windows outlive effects. delete effects; effects = nullptr; if (Workspace::self()) { for (X11Client *c : Workspace::self()->clientList()) { m_scene->removeToplevel(c); } for (X11Client *c : Workspace::self()->desktopList()) { m_scene->removeToplevel(c); } for (Unmanaged *c : Workspace::self()->unmanagedList()) { m_scene->removeToplevel(c); } for (InternalClient *client : workspace()->internalClients()) { m_scene->removeToplevel(client); } for (X11Client *c : Workspace::self()->clientList()) { c->finishCompositing(); } for (X11Client *c : Workspace::self()->desktopList()) { c->finishCompositing(); } for (Unmanaged *c : Workspace::self()->unmanagedList()) { c->finishCompositing(); } for (InternalClient *client : workspace()->internalClients()) { client->finishCompositing(); } if (auto *con = kwinApp()->x11Connection()) { xcb_composite_unredirect_subwindows(con, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } while (!workspace()->deletedList().isEmpty()) { workspace()->deletedList().first()->discard(); } } if (waylandServer()) { for (XdgShellClient *c : waylandServer()->clients()) { m_scene->removeToplevel(c); } for (XdgShellClient *c : waylandServer()->clients()) { c->finishCompositing(); } } delete m_scene; m_scene = nullptr; m_bufferSwapPending = false; + m_composeAtSwapCompletion = false; compositeTimer.stop(); repaints_region = QRegion(); m_state = State::Off; emit compositingToggled(false); } void Compositor::destroyCompositorSelection() { delete m_selectionOwner; m_selectionOwner = nullptr; } void Compositor::releaseCompositorSelection() { switch (m_state) { case State::On: // We are compositing at the moment. Don't release. break; case State::Off: if (m_selectionOwner) { qCDebug(KWIN_CORE) << "Releasing compositor selection"; m_selectionOwner->setOwning(false); m_selectionOwner->release(); } break; case State::Starting: case State::Stopping: // Still starting or shutting down the compositor. Starting might fail // or after stopping a restart might follow. So test again later on. m_releaseSelectionTimer.start(); break; } } void Compositor::keepSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties.removeAll(atom); } void Compositor::removeSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties << atom; m_unusedSupportPropertyTimer.start(); } void Compositor::deleteUnusedSupportProperties() { if (m_state == State::Starting || m_state == State::Stopping) { // Currently still maybe restarting the compositor. m_unusedSupportPropertyTimer.start(); return; } if (auto *con = kwinApp()->x11Connection()) { for (const xcb_atom_t &atom : qAsConst(m_unusedSupportProperties)) { // remove property from root window xcb_delete_property(con, kwinApp()->x11RootWindow(), atom); } m_unusedSupportProperties.clear(); } } void Compositor::configChanged() { reinitialize(); addRepaintFull(); } void Compositor::reinitialize() { // Reparse config. Config options will be reloaded by start() kwinApp()->config()->reparseConfiguration(); // Restart compositing stop(); start(); if (effects) { // start() may fail effects->reconfigure(); } } void Compositor::addRepaint(int x, int y, int w, int h) { if (m_state != State::On) { return; } repaints_region += QRegion(x, y, w, h); scheduleRepaint(); } void Compositor::addRepaint(const QRect& r) { if (m_state != State::On) { return; } repaints_region += r; scheduleRepaint(); } void Compositor::addRepaint(const QRegion& r) { if (m_state != State::On) { return; } repaints_region += r; scheduleRepaint(); } void Compositor::addRepaintFull() { if (m_state != State::On) { return; } const QSize &s = screens()->size(); repaints_region = QRegion(0, 0, s.width(), s.height()); scheduleRepaint(); } void Compositor::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { performCompositing(); } else QObject::timerEvent(te); } void Compositor::aboutToSwapBuffers() { Q_ASSERT(!m_bufferSwapPending); m_bufferSwapPending = true; } void Compositor::bufferSwapComplete() { Q_ASSERT(m_bufferSwapPending); m_bufferSwapPending = false; + emit bufferSwapCompleted(); - performCompositing(); + + if (m_composeAtSwapCompletion) { + m_composeAtSwapCompletion = false; + performCompositing(); + } } void Compositor::performCompositing() { - compositeTimer.stop(); - // If a buffer swap is still pending, we return to the event loop and // continue processing events until the swap has completed. if (m_bufferSwapPending) { + m_composeAtSwapCompletion = true; + compositeTimer.stop(); return; } // If outputs are disabled, we return to the event loop and // continue processing events until the outputs are enabled again if (!kwinApp()->platform()->areOutputsEnabled()) { + compositeTimer.stop(); return; } // Create a list of all windows in the stacking order QList windows = Workspace::self()->xStackingOrder(); QList damaged; // Reset the damage state of each window and fetch the damage region // without waiting for a reply for (Toplevel *win : windows) { if (win->resetAndFetchDamage()) { damaged << win; } } if (damaged.count() > 0) { m_scene->triggerFence(); if (auto c = kwinApp()->x11Connection()) { xcb_flush(c); } } // Move elevated windows to the top of the stacking order for (EffectWindow *c : static_cast(effects)->elevatedWindows()) { Toplevel *t = static_cast(c)->window(); windows.removeAll(t); windows.append(t); } // Get the replies for (Toplevel *win : damaged) { // Discard the cached lanczos texture if (win->effectWindow()) { const QVariant texture = win->effectWindow()->data(LanczosCacheRole); if (texture.isValid()) { delete static_cast(texture.value()); win->effectWindow()->setData(LanczosCacheRole, QVariant()); } } win->getDamageRegionReply(); } if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); - - // This means the next time we composite it is done without timer delay. - m_timerOffset = 1000 / refreshRate(); + m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" + // Note: It would seem here we should undo suspended unredirect, but when scenes need + // it for some reason, e.g. transformations or translucency, the next pass that does not + // need this anymore and paints normally will also reset the suspended unredirect. + // Otherwise the window would not be painted normally anyway. + compositeTimer.stop(); return; } // Skip windows that are not yet ready for being painted and if screen is locked skip windows // that are neither lockscreen nor inputmethod windows. // // TODO? This cannot be used so carelessly - needs protections against broken clients, the // window should not get focus before it's displayed, handle unredirected windows properly and // so on. for (Toplevel *win : windows) { if (!win->readyForPainting()) { windows.removeAll(win); } if (waylandServer() && waylandServer()->isScreenLocked()) { if(!win->isLockScreen() && !win->isInputMethod()) { windows.removeAll(win); } } } QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); if (m_framesToTestForSafety > 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreFrame); } Q_ASSERT(!m_bufferSwapPending); // Start the actual painting process. - m_timerOffset = m_scene->paint(repaints, windows) / 1000 / 1000; + m_timeSinceLastVBlank = m_scene->paint(repaints, windows); - // Either the backend will provide a swap event and a buffer swap is pending now or there is no - // pending buffer swap and by that no swap event received later on for the current paint call. - Q_ASSERT(m_scene->hasSwapEvent() ^ !m_bufferSwapPending); + // TODO: In case we have swap events the buffer swap should now be pending, but this is not + // always the case on X11 standalone platform. Look into that. +// Q_ASSERT(m_scene->hasSwapEvent() ^ !m_bufferSwapPending); if (m_framesToTestForSafety > 0) { if (m_scene->compositingType() & OpenGLCompositing) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostFrame); } m_framesToTestForSafety--; if (m_framesToTestForSafety == 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint( Platform::OpenGLSafePoint::PostLastGuardedFrame); } } if (waylandServer()) { const auto currentTime = static_cast(m_monotonicClock.elapsed()); for (Toplevel *win : qAsConst(windows)) { if (auto surface = win->surface()) { surface->frameRendered(currentTime); } } } + // Stop here to ensure *we* cause the next repaint schedule - not some effect + // through m_scene->paint(). compositeTimer.stop(); - if (m_scene->hasSwapEvent()) { - m_timerOffset = 1000 / refreshRate(); - } else { - setCompositeTimer(); - } + + // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() + // is called the next time. If there would be nothing pending, it will not restart the timer and + // scheduleRepaint() would restart it again somewhen later, called from functions that + // would again add something pending. + scheduleRepaint(); } template static bool repaintsPending(const QList &windows) { return std::any_of(windows.begin(), windows.end(), [](T *t) { return !t->repaints().isEmpty(); }); } bool Compositor::windowRepaintsPending() const { if (repaintsPending(Workspace::self()->clientList())) { return true; } if (repaintsPending(Workspace::self()->desktopList())) { return true; } if (repaintsPending(Workspace::self()->unmanagedList())) { return true; } if (repaintsPending(Workspace::self()->deletedList())) { return true; } if (auto *server = waylandServer()) { const auto &clients = server->clients(); auto test = [](XdgShellClient *c) { return c->readyForPainting() && !c->repaints().isEmpty(); }; if (std::any_of(clients.begin(), clients.end(), test)) { return true; } } const auto &internalClients = workspace()->internalClients(); auto internalTest = [] (InternalClient *client) { return client->isShown(true) && !client->repaints().isEmpty(); }; if (std::any_of(internalClients.begin(), internalClients.end(), internalTest)) { return true; } return false; } void Compositor::setCompositeTimer() { if (compositeTimer.isActive()) { return; } - uint waitTime = 1000 / refreshRate() - m_timerOffset; + uint waitTime = 1000 / refreshRate(); // Force 4fps minimum: compositeTimer.start(qMin(waitTime, 250u), this); } bool Compositor::isActive() { return m_state == State::On; } WaylandCompositor::WaylandCompositor(QObject *parent) : Compositor(parent) { connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &WaylandCompositor::destroyCompositorSelection); } void WaylandCompositor::toggleCompositing() { // For the shortcut. Not possible on Wayland because we always composite. } void WaylandCompositor::start() { if (!Compositor::setupStart()) { // Internal setup failed, abort. return; } // TODO: This is problematic on Wayland. We should get the highest refresh rate // and not the one of the first output. Also on hotplug reevaluate. // On X11 do it also like this? m_refreshRate = KWin::currentRefreshRate(); if (Workspace::self()) { startupWithWorkspace(); } else { connect(kwinApp(), &Application::workspaceCreated, this, &WaylandCompositor::startupWithWorkspace); } } int WaylandCompositor::refreshRate() const { return m_refreshRate; } X11Compositor::X11Compositor(QObject *parent) : Compositor(parent) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) , m_xrrRefreshRate(0) { } void X11Compositor::toggleCompositing() { if (m_suspended) { // Direct user call; clear all bits. resume(AllReasonSuspend); } else { // But only set the user one (sufficient to suspend). suspend(UserSuspend); } } void X11Compositor::reinitialize() { // Resume compositing if suspended. m_suspended = NoReasonSuspend; Compositor::reinitialize(); } void X11Compositor::configChanged() { if (m_suspended) { stop(); return; } Compositor::configChanged(); } void X11Compositor::suspend(X11Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; if (reason & ScriptSuspend) { // When disabled show a shortcut how the user can get back compositing. const auto shortcuts = KGlobalAccel::self()->shortcut( workspace()->findChild(QStringLiteral("Suspend Compositing"))); if (!shortcuts.isEmpty()) { // Display notification only if there is the shortcut. const QString message = i18n("Desktop effects have been suspended by another application.
" "You can resume using the '%1' shortcut.", shortcuts.first().toString(QKeySequence::NativeText)); KNotification::event(QStringLiteral("compositingsuspendeddbus"), message); } } stop(); } void X11Compositor::resume(X11Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; start(); } void X11Compositor::start() { if (m_suspended) { QStringList reasons; if (m_suspended & UserSuspend) { reasons << QStringLiteral("Disabled by User"); } if (m_suspended & BlockRuleSuspend) { reasons << QStringLiteral("Disabled by Window"); } if (m_suspended & ScriptSuspend) { reasons << QStringLiteral("Disabled by Script"); } qCDebug(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!kwinApp()->platform()->compositingPossible()) { qCCritical(KWIN_CORE) << "Compositing is not possible"; return; } if (!Compositor::setupStart()) { // Internal setup failed, abort. return; } m_xrrRefreshRate = KWin::currentRefreshRate(); startupWithWorkspace(); } void X11Compositor::performCompositing() { if (scene()->usesOverlayWindow() && !isOverlayWindowVisible()) { // Return since nothing is visible. return; } Compositor::performCompositing(); } bool X11Compositor::checkForOverlayWindow(WId w) const { if (!scene()) { // No scene, so it cannot be the overlay window. return false; } if (!scene()->overlayWindow()) { // No overlay window, it cannot be the overlay. return false; } // Compare the window ID's. return w == scene()->overlayWindow()->window(); } bool X11Compositor::isOverlayWindowVisible() const { if (!scene()) { return false; } if (!scene()->overlayWindow()) { return false; } return scene()->overlayWindow()->isVisible(); } int X11Compositor::refreshRate() const { return m_xrrRefreshRate; } void X11Compositor::updateClientCompositeBlocking(X11Client *c) { if (c) { if (c->isBlockingCompositing()) { // Do NOT attempt to call suspend(true) from within the eventchain! if (!(m_suspended & BlockRuleSuspend)) QMetaObject::invokeMethod(this, [this]() { suspend(BlockRuleSuspend); }, Qt::QueuedConnection); } } else if (m_suspended & BlockRuleSuspend) { // If !c we just check if we can resume in case a blocking client was lost. bool shouldResume = true; for (auto it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { shouldResume = false; break; } } if (shouldResume) { // Do NOT attempt to call suspend(false) from within the eventchain! QMetaObject::invokeMethod(this, [this]() { resume(BlockRuleSuspend); }, Qt::QueuedConnection); } } } X11Compositor *X11Compositor::self() { return qobject_cast(Compositor::self()); } } // included for CompositorSelectionOwner #include "composite.moc" diff --git a/composite.h b/composite.h index 133a458b6..1c90df79c 100644 --- a/composite.h +++ b/composite.h @@ -1,275 +1,277 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Arthur Arlt Copyright (C) 2012 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #pragma once #include #include #include #include #include #include namespace KWin { class CompositorSelectionOwner; class Scene; class X11Client; class KWIN_EXPORT Compositor : public QObject { Q_OBJECT public: enum class State { On = 0, Off, Starting, Stopping }; ~Compositor() override; static Compositor *self(); // when adding repaints caused by a window, you probably want to use // either Toplevel::addRepaint() or Toplevel::addWorkspaceRepaint() void addRepaint(const QRect& r); void addRepaint(const QRegion& r); void addRepaint(int x, int y, int w, int h); void addRepaintFull(); /** * Schedules a new repaint if no repaint is currently scheduled. */ void scheduleRepaint(); /** * Notifies the compositor that SwapBuffers() is about to be called. * Rendering of the next frame will be deferred until bufferSwapComplete() * is called. */ void aboutToSwapBuffers(); /** * Notifies the compositor that a pending buffer swap has completed. */ void bufferSwapComplete(); /** * Toggles compositing, that is if the Compositor is suspended it will be resumed * and if the Compositor is active it will be suspended. * Invoked by keybinding (shortcut default: Shift + Alt + F12). */ virtual void toggleCompositing() = 0; /** * Re-initializes the Compositor completely. * Connected to the D-Bus signal org.kde.KWin /KWin reinitCompositing */ virtual void reinitialize(); /** * Whether the Compositor is active. That is a Scene is present and the Compositor is * not shutting down itself. */ bool isActive(); virtual int refreshRate() const = 0; Scene *scene() const { return m_scene; } /** * @brief Static check to test whether the Compositor is available and active. * * @return bool @c true if there is a Compositor and it is active, @c false otherwise */ static bool compositing() { return s_compositor != nullptr && s_compositor->isActive(); } // for delayed supportproperty management of effects void keepSupportProperty(xcb_atom_t atom); void removeSupportProperty(xcb_atom_t atom); Q_SIGNALS: void compositingToggled(bool active); void aboutToDestroy(); void aboutToToggleCompositing(); void sceneCreated(); void bufferSwapCompleted(); protected: explicit Compositor(QObject *parent = nullptr); void timerEvent(QTimerEvent *te) override; virtual void start() = 0; void stop(); /** * @brief Prepares start. * @return bool @c true if start should be continued and @c if not. */ bool setupStart(); /** * Continues the startup after Scene And Workspace are created */ void startupWithWorkspace(); virtual void performCompositing(); virtual void configChanged(); void destroyCompositorSelection(); static Compositor *s_compositor; private: void claimCompositorSelection(); void setupX11Support(); void setCompositeTimer(); bool windowRepaintsPending() const; void releaseCompositorSelection(); void deleteUnusedSupportProperties(); State m_state; QBasicTimer compositeTimer; CompositorSelectionOwner *m_selectionOwner; QTimer m_releaseSelectionTimer; QList m_unusedSupportProperties; QTimer m_unusedSupportPropertyTimer; + qint64 vBlankInterval, fpsInterval; QRegion repaints_region; - // Compositing pause decrease through paint duration (in ms). - qint64 m_timerOffset; - bool m_bufferSwapPending; + qint64 m_timeSinceLastVBlank; Scene *m_scene; + bool m_bufferSwapPending; + bool m_composeAtSwapCompletion; + int m_framesToTestForSafety = 3; QElapsedTimer m_monotonicClock; }; class KWIN_EXPORT WaylandCompositor : public Compositor { Q_OBJECT public: static WaylandCompositor *create(QObject *parent = nullptr); int refreshRate() const override; void toggleCompositing() override; protected: void start() override; private: explicit WaylandCompositor(QObject *parent); int m_refreshRate; }; class KWIN_EXPORT X11Compositor : public Compositor { Q_OBJECT public: enum SuspendReason { NoReasonSuspend = 0, UserSuspend = 1 << 0, BlockRuleSuspend = 1 << 1, ScriptSuspend = 1 << 2, AllReasonSuspend = 0xff }; Q_DECLARE_FLAGS(SuspendReasons, SuspendReason) Q_ENUM(SuspendReason) Q_FLAG(SuspendReasons) static X11Compositor *create(QObject *parent = nullptr); /** * @brief Suspends the Compositor if it is currently active. * * Note: it is possible that the Compositor is not able to suspend. Use isActive to check * whether the Compositor has been suspended. * * @return void * @see resume * @see isActive */ void suspend(SuspendReason reason); /** * @brief Resumes the Compositor if it is currently suspended. * * Note: it is possible that the Compositor cannot be resumed, that is there might be Clients * blocking the usage of Compositing or the Scene might be broken. Use isActive to check * whether the Compositor has been resumed. Also check isCompositingPossible and * isOpenGLBroken. * * Note: The starting of the Compositor can require some time and is partially done threaded. * After this method returns the setup may not have been completed. * * @return void * @see suspend * @see isActive * @see isCompositingPossible * @see isOpenGLBroken */ void resume(SuspendReason reason); void toggleCompositing() override; void reinitialize() override; void configChanged() override; /** * Checks whether @p w is the Scene's overlay window. */ bool checkForOverlayWindow(WId w) const; /** * @returns Whether the Scene's Overlay X Window is visible. */ bool isOverlayWindowVisible() const; int refreshRate() const override; void updateClientCompositeBlocking(X11Client *client = nullptr); static X11Compositor *self(); protected: void start() override; void performCompositing() override; private: explicit X11Compositor(QObject *parent); /** * Whether the Compositor is currently suspended, 8 bits encoding the reason */ SuspendReasons m_suspended; int m_xrrRefreshRate; }; } diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp index 3603a8166..ee3f892ec 100644 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -1,885 +1,878 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2012 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "glxbackend.h" #include "logging.h" #include "glx_context_attribute_builder.h" // kwin #include "options.h" #include "overlaywindow.h" #include "composite.h" #include "platform.h" #include "scene.h" #include "screens.h" #include "xcbutils.h" #include "texture.h" // kwin libs #include #include #include #include // Qt +#include #include #include #include // system #include #include #include #if HAVE_DL_LIBRARY #include #endif #ifndef XCB_GLX_BUFFER_SWAP_COMPLETE #define XCB_GLX_BUFFER_SWAP_COMPLETE 1 typedef struct xcb_glx_buffer_swap_complete_event_t { uint8_t response_type; /**< */ uint8_t pad0; /**< */ uint16_t sequence; /**< */ uint16_t event_type; /**< */ uint8_t pad1[2]; /**< */ xcb_glx_drawable_t drawable; /**< */ uint32_t ust_hi; /**< */ uint32_t ust_lo; /**< */ uint32_t msc_hi; /**< */ uint32_t msc_lo; /**< */ uint32_t sbc; /**< */ } xcb_glx_buffer_swap_complete_event_t; #endif #include #include namespace KWin { SwapEventFilter::SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable) : X11EventFilter(Xcb::Extensions::self()->glxEventBase() + XCB_GLX_BUFFER_SWAP_COMPLETE), m_drawable(drawable), m_glxDrawable(glxDrawable) { } bool SwapEventFilter::event(xcb_generic_event_t *event) { xcb_glx_buffer_swap_complete_event_t *ev = reinterpret_cast(event); // The drawable field is the X drawable when the event was synthesized // by a WireToEvent handler, and the GLX drawable when the event was // received over the wire if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) { Compositor::self()->bufferSwapComplete(); return true; } return false; } // ----------------------------------------------------------------------- GlxBackend::GlxBackend(Display *display) : OpenGLBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) , window(None) , fbconfig(nullptr) , glxWindow(None) , ctx(nullptr) , m_bufferAge(0) , m_x11Display(display) { // Force initialization of GLX integration in the Qt's xcb backend // to make it call XESetWireToEvent callbacks, which is required // by Mesa when using DRI2. QOpenGLContext::supportsThreadedOpenGL(); } GlxBackend::~GlxBackend() { if (isFailed()) { m_overlayWindow->destroy(); } // TODO: cleanup in error case // do cleanup after initBuffer() cleanupGL(); doneCurrent(); EffectQuickView::setShareContext(nullptr); if (ctx) glXDestroyContext(display(), ctx); if (glxWindow) glXDestroyWindow(display(), glxWindow); if (window) XDestroyWindow(display(), window); qDeleteAll(m_fbconfigHash); m_fbconfigHash.clear(); overlayWindow()->destroy(); delete m_overlayWindow; } typedef void (*glXFuncPtr)(); static glXFuncPtr getProcAddress(const char* name) { glXFuncPtr ret = nullptr; #if HAVE_EPOXY_GLX ret = glXGetProcAddress((const GLubyte*) name); #endif #if HAVE_DL_LIBRARY if (ret == nullptr) ret = (glXFuncPtr) dlsym(RTLD_DEFAULT, name); #endif return ret; } glXSwapIntervalMESA_func glXSwapIntervalMESA; void GlxBackend::init() { // Require at least GLX 1.3 if (!checkVersion()) { setFailed(QStringLiteral("Requires at least GLX 1.3")); return; } initExtensions(); // resolve glXSwapIntervalMESA if available if (hasExtension(QByteArrayLiteral("GLX_MESA_swap_control"))) { glXSwapIntervalMESA = (glXSwapIntervalMESA_func) getProcAddress("glXSwapIntervalMESA"); } else { glXSwapIntervalMESA = nullptr; } initVisualDepthHashTable(); if (!initBuffer()) { setFailed(QStringLiteral("Could not initialize the buffer")); return; } if (!initRenderingContext()) { setFailed(QStringLiteral("Could not initialize rendering context")); return; } // Initialize OpenGL GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(GlxPlatformInterface); options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen glPlatform->printResults(); initGL(&getProcAddress); // Check whether certain features are supported m_haveMESACopySubBuffer = hasExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); m_haveMESASwapControl = hasExtension(QByteArrayLiteral("GLX_MESA_swap_control")); m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); // Allow to disable Intel swap event with env variable. There were problems in the past. // See BUG 342582. if (hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") != QByteArrayLiteral("0")) { m_swapEventFilter = std::make_unique(window, glxWindow); glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); } setSupportsBufferAge(false); if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } if (m_haveEXTSwapControl) { glXSwapIntervalEXT(display(), glxWindow, 1); } else if (m_haveMESASwapControl) { glXSwapIntervalMESA(1); } else { qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! glSwapInterval is not supported"; } if (glPlatform->isVirtualBox()) { // VirtualBox does not support glxQueryDrawable // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension // and the GLPlatform has not been initialized at the moment when initGLX() is called. glXQueryDrawable = nullptr; } setIsDirectRendering(bool(glXIsDirect(display(), ctx))); qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); } bool GlxBackend::checkVersion() { int major, minor; glXQueryVersion(display(), &major, &minor); return kVersionNumber(major, minor) >= kVersionNumber(1, 3); } void GlxBackend::initExtensions() { const QByteArray string = (const char *) glXQueryExtensionsString(display(), QX11Info::appScreen()); setExtensions(string.split(' ')); } bool GlxBackend::initRenderingContext() { const bool direct = true; // Use glXCreateContextAttribsARB() when it's available if (hasExtension(QByteArrayLiteral("GLX_ARB_create_context"))) { const bool have_robustness = hasExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness")); const bool haveVideoMemoryPurge = hasExtension(QByteArrayLiteral("GLX_NV_robustness_video_memory_purge")); std::vector candidates; if (options->glCoreProfile()) { if (have_robustness) { if (haveVideoMemoryPurge) { GlxContextAttributeBuilder purgeMemoryCore; purgeMemoryCore.setVersion(3, 1); purgeMemoryCore.setRobust(true); purgeMemoryCore.setResetOnVideoMemoryPurge(true); candidates.emplace_back(std::move(purgeMemoryCore)); } GlxContextAttributeBuilder robustCore; robustCore.setVersion(3, 1); robustCore.setRobust(true); candidates.emplace_back(std::move(robustCore)); } GlxContextAttributeBuilder core; core.setVersion(3, 1); candidates.emplace_back(std::move(core)); } else { if (have_robustness) { if (haveVideoMemoryPurge) { GlxContextAttributeBuilder purgeMemoryLegacy; purgeMemoryLegacy.setRobust(true); purgeMemoryLegacy.setResetOnVideoMemoryPurge(true); candidates.emplace_back(std::move(purgeMemoryLegacy)); } GlxContextAttributeBuilder robustLegacy; robustLegacy.setRobust(true); candidates.emplace_back(std::move(robustLegacy)); } GlxContextAttributeBuilder legacy; legacy.setVersion(2, 1); candidates.emplace_back(std::move(legacy)); } for (auto it = candidates.begin(); it != candidates.end(); it++) { const auto attribs = it->build(); ctx = glXCreateContextAttribsARB(display(), fbconfig, nullptr, true, attribs.data()); if (ctx) { qCDebug(KWIN_X11STANDALONE) << "Created GLX context with attributes:" << &(*it); break; } } } if (!ctx) ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, nullptr, direct); if (!ctx) { qCDebug(KWIN_X11STANDALONE) << "Failed to create an OpenGL context."; return false; } if (!glXMakeCurrent(display(), glxWindow, ctx)) { qCDebug(KWIN_X11STANDALONE) << "Failed to make the OpenGL context current."; glXDestroyContext(display(), ctx); ctx = nullptr; return false; } auto qtContext = new QOpenGLContext; QGLXNativeContext native(ctx, display()); qtContext->setNativeHandle(QVariant::fromValue(native)); qtContext->create(); EffectQuickView::setShareContext(std::unique_ptr(qtContext)); return true; } bool GlxBackend::initBuffer() { if (!initFbConfig()) return false; if (overlayWindow()->create()) { xcb_connection_t * const c = connection(); // Try to create double-buffered window in the overlay xcb_visualid_t visual; glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, (int *) &visual); if (!visual) { qCCritical(KWIN_X11STANDALONE) << "The GLXFBConfig does not have an associated X visual"; return false; } xcb_colormap_t colormap = xcb_generate_id(c); xcb_create_colormap(c, false, colormap, rootWindow(), visual); const QSize size = screens()->size(); window = xcb_generate_id(c); xcb_create_window(c, visualDepth(visual), window, overlayWindow()->window(), 0, 0, size.width(), size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual, XCB_CW_COLORMAP, &colormap); glxWindow = glXCreateWindow(display(), fbconfig, window, nullptr); overlayWindow()->setup(window); } else { qCCritical(KWIN_X11STANDALONE) << "Failed to create overlay window"; return false; } return true; } bool GlxBackend::initFbConfig() { const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, 0 }; const int attribs_srgb[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, true, 0 }; bool llvmpipe = false; // Note that we cannot use GLPlatform::driver() here, because it has not been initialized at this point if (hasExtension(QByteArrayLiteral("GLX_MESA_query_renderer"))) { const QByteArray device = glXQueryRendererStringMESA(display(), DefaultScreen(display()), 0, GLX_RENDERER_DEVICE_ID_MESA); if (device.contains(QByteArrayLiteral("llvmpipe"))) { llvmpipe = true; } } // Try to find a double buffered sRGB capable configuration int count = 0; GLXFBConfig *configs = nullptr; // Don't request an sRGB configuration with LLVMpipe when the default depth is 16. See bug #408594. if (!llvmpipe || Xcb::defaultDepth() > 16) { configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs_srgb, &count); } if (count == 0) { // Try to find a double buffered non-sRGB capable configuration configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); } struct FBConfig { GLXFBConfig config; int depth; int stencil; }; std::deque candidates; for (int i = 0; i < count; i++) { int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); candidates.emplace_back(FBConfig{configs[i], depth, stencil}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { fbconfig = candidates.front().config; int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil, srgb; glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID, &fbconfig_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, &visual_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE, &blue); glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE, &alpha); glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); glXGetFBConfigAttrib(display(), fbconfig, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &srgb); qCDebug(KWIN_X11STANDALONE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d sRGB: %d", fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil, srgb); } if (fbconfig == nullptr) { qCCritical(KWIN_X11STANDALONE) << "Failed to find a usable framebuffer configuration"; return false; } return true; } void GlxBackend::initVisualDepthHashTable() { const xcb_setup_t *setup = xcb_get_setup(connection()); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { const int len = xcb_depth_visuals_length(depth.data); const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); for (int i = 0; i < len; i++) m_visualDepthHash.insert(visuals[i].visual_id, depth.data->depth); } } } int GlxBackend::visualDepth(xcb_visualid_t visual) const { return m_visualDepthHash.value(visual); } static inline int bitCount(uint32_t mask) { #if defined(__GNUC__) return __builtin_popcount(mask); #else int count = 0; while (mask) { count += (mask & 1); mask >>= 1; } return count; #endif } FBConfigInfo *GlxBackend::infoForVisual(xcb_visualid_t visual) { auto it = m_fbconfigHash.constFind(visual); if (it != m_fbconfigHash.constEnd()) { return it.value(); } FBConfigInfo *info = new FBConfigInfo; m_fbconfigHash.insert(visual, info); info->fbconfig = nullptr; info->bind_texture_format = 0; info->texture_targets = 0; info->y_inverted = 0; info->mipmap = 0; const xcb_render_pictformat_t format = XRenderUtils::findPictFormat(visual); const xcb_render_directformat_t *direct = XRenderUtils::findPictFormatInfo(format); if (!direct) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a picture format for visual 0x" << hex << visual; return info; } const int red_bits = bitCount(direct->red_mask); const int green_bits = bitCount(direct->green_mask); const int blue_bits = bitCount(direct->blue_mask); const int alpha_bits = bitCount(direct->alpha_mask); const int depth = visualDepth(visual); const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits); const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, int(GLX_DONT_CARE), // The ARGB32 visual is marked sRGB capable in mesa/i965 GLX_BUFFER_SIZE, red_bits + green_bits + blue_bits + alpha_bits, GLX_RED_SIZE, red_bits, GLX_GREEN_SIZE, green_bits, GLX_BLUE_SIZE, blue_bits, GLX_ALPHA_SIZE, alpha_bits, GLX_STENCIL_SIZE, 0, GLX_DEPTH_SIZE, 0, 0 }; int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); if (count < 1) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a framebuffer configuration for visual 0x" << hex << visual; return info; } struct FBConfig { GLXFBConfig config; int depth; int stencil; int format; }; std::deque candidates; for (int i = 0; i < count; i++) { int red, green, blue; glXGetFBConfigAttrib(display(), configs[i], GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), configs[i], GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), configs[i], GLX_BLUE_SIZE, &blue); if (std::tie(red, green, blue) != rgb_sizes) continue; xcb_visualid_t visual; glXGetFBConfigAttrib(display(), configs[i], GLX_VISUAL_ID, (int *) &visual); if (visualDepth(visual) != depth) continue; int bind_rgb, bind_rgba; glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba); glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb); if (!bind_rgb && !bind_rgba) continue; int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); int texture_format; if (alpha_bits) texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; else texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; candidates.emplace_back(FBConfig{configs[i], depth, stencil, texture_format}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { const FBConfig &candidate = candidates.front(); int y_inverted, texture_targets; glXGetFBConfigAttrib(display(), candidate.config, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets); glXGetFBConfigAttrib(display(), candidate.config, GLX_Y_INVERTED_EXT, &y_inverted); info->fbconfig = candidate.config; info->bind_texture_format = candidate.format; info->texture_targets = texture_targets; info->y_inverted = y_inverted; info->mipmap = 0; } if (info->fbconfig) { int fbc_id = 0; int visual_id = 0; glXGetFBConfigAttrib(display(), info->fbconfig, GLX_FBCONFIG_ID, &fbc_id); glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID, &visual_id); qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id; } return info; } void GlxBackend::present() { if (lastDamage().isEmpty()) return; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); - const bool canSwapBuffers = supportsBufferAge() || (lastDamage() == displayRegion); - m_needsCompositeTimerStart = true; + const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); - if (canSwapBuffers) { - if (supportsSwapEvents()) { - m_needsCompositeTimerStart = false; + if (fullRepaint) { + if (hasSwapEvent()) { Compositor::self()->aboutToSwapBuffers(); } glXSwapBuffers(display(), glxWindow); if (supportsBufferAge()) { glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); } } else if (m_haveMESACopySubBuffer) { for (const QRect &r : lastDamage()) { // convert to OpenGL coordinates int y = screenSize.height() - r.y() - r.height(); glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); } - } else { - // Copy Pixels (horribly slow on Mesa). + } else { // Copy Pixels (horribly slow on Mesa) glDrawBuffer(GL_FRONT); copyPixels(lastDamage()); glDrawBuffer(GL_BACK); } setLastDamage(QRegion()); if (!supportsBufferAge()) { glXWaitGL(); XFlush(display()); } } void GlxBackend::screenGeometryChanged(const QSize &size) { doneCurrent(); XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height()); overlayWindow()->setup(window); Xcb::sync(); makeCurrent(); glViewport(0, 0, size.width(), size.height()); // The back buffer contents are now undefined m_bufferAge = 0; } SceneOpenGLTexturePrivate *GlxBackend::createBackendTexture(SceneOpenGLTexture *texture) { return new GlxTexture(texture, this); } QRegion GlxBackend::prepareRenderingFrame() { QRegion repaint; if (supportsBufferAge()) repaint = accumulatedDamageHistory(m_bufferAge); startRenderTimer(); return repaint; } void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { setLastDamage(QRegion()); // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.isEmpty()) glFlush(); m_bufferAge = 1; return; } setLastDamage(renderedRegion); present(); if (overlayWindow()->window()) // show the window only after the first pass, overlayWindow()->show(); // since that pass may take long // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } bool GlxBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = glXMakeCurrent(display(), glxWindow, ctx); return current; } void GlxBackend::doneCurrent() { glXMakeCurrent(display(), None, nullptr); } OverlayWindow* GlxBackend::overlayWindow() const { return m_overlayWindow; } bool GlxBackend::usesOverlayWindow() const { return true; } -bool GlxBackend::supportsSwapEvents() const -{ - return m_swapEventFilter != nullptr; -} - bool GlxBackend::hasSwapEvent() const { - return !m_needsCompositeTimerStart; + return m_swapEventFilter != nullptr; } /******************************************************** * GlxTexture *******************************************************/ GlxTexture::GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend) : SceneOpenGLTexturePrivate() , q(texture) , m_backend(backend) , m_glxpixmap(None) { } GlxTexture::~GlxTexture() { if (m_glxpixmap != None) { if (!options->isGlStrictBinding()) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); } glXDestroyPixmap(display(), m_glxpixmap); m_glxpixmap = None; } } void GlxTexture::onDamage() { if (options->isGlStrictBinding() && m_glxpixmap) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); } GLTexturePrivate::onDamage(); } bool GlxTexture::loadTexture(xcb_pixmap_t pixmap, const QSize &size, xcb_visualid_t visual) { if (pixmap == XCB_NONE || size.isEmpty() || visual == XCB_NONE) return false; const FBConfigInfo *info = m_backend->infoForVisual(visual); if (!info || info->fbconfig == nullptr) return false; if (info->texture_targets & GLX_TEXTURE_2D_BIT_EXT) { m_target = GL_TEXTURE_2D; m_scale.setWidth(1.0f / m_size.width()); m_scale.setHeight(1.0f / m_size.height()); } else { Q_ASSERT(info->texture_targets & GLX_TEXTURE_RECTANGLE_BIT_EXT); m_target = GL_TEXTURE_RECTANGLE; m_scale.setWidth(1.0f); m_scale.setHeight(1.0f); } const int attrs[] = { GLX_TEXTURE_FORMAT_EXT, info->bind_texture_format, GLX_MIPMAP_TEXTURE_EXT, false, GLX_TEXTURE_TARGET_EXT, m_target == GL_TEXTURE_2D ? GLX_TEXTURE_2D_EXT : GLX_TEXTURE_RECTANGLE_EXT, 0 }; m_glxpixmap = glXCreatePixmap(display(), info->fbconfig, pixmap, attrs); m_size = size; m_yInverted = info->y_inverted ? true : false; m_canUseMipmaps = false; glGenTextures(1, &m_texture); q->setDirty(); q->setFilter(GL_NEAREST); glBindTexture(m_target, m_texture); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); updateMatrix(); return true; } bool GlxTexture::loadTexture(WindowPixmap *pixmap) { Toplevel *t = pixmap->toplevel(); return loadTexture(pixmap->pixmap(), t->bufferGeometry().size(), t->visual()); } OpenGLBackend *GlxTexture::backend() { return m_backend; } } // namespace diff --git a/plugins/platforms/x11/standalone/glxbackend.h b/plugins/platforms/x11/standalone/glxbackend.h index 71ea98cfb..71c9c3e25 100644 --- a/plugins/platforms/x11/standalone/glxbackend.h +++ b/plugins/platforms/x11/standalone/glxbackend.h @@ -1,145 +1,143 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_GLX_BACKEND_H #define KWIN_GLX_BACKEND_H #include "backend.h" #include "texture.h" #include "x11eventfilter.h" #include #include #include #include namespace KWin { // GLX_MESA_swap_interval using glXSwapIntervalMESA_func = int (*)(unsigned int interval); extern glXSwapIntervalMESA_func glXSwapIntervalMESA; class FBConfigInfo { public: GLXFBConfig fbconfig; int bind_texture_format; int texture_targets; int y_inverted; int mipmap; }; // ------------------------------------------------------------------ class SwapEventFilter : public X11EventFilter { public: SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable); bool event(xcb_generic_event_t *event) override; private: xcb_drawable_t m_drawable; xcb_glx_drawable_t m_glxDrawable; }; /** * @brief OpenGL Backend using GLX over an X overlay window. */ class GlxBackend : public OpenGLBackend { public: GlxBackend(Display *display); ~GlxBackend() override; void screenGeometryChanged(const QSize &size) override; SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override; QRegion prepareRenderingFrame() override; void endRenderingFrame(const QRegion &damage, const QRegion &damagedRegion) override; bool makeCurrent() override; void doneCurrent() override; OverlayWindow* overlayWindow() const override; bool usesOverlayWindow() const override; bool hasSwapEvent() const override; void init() override; protected: void present() override; private: bool initBuffer(); bool checkVersion(); void initExtensions(); bool initRenderingContext(); bool initFbConfig(); void initVisualDepthHashTable(); Display *display() const { return m_x11Display; } - bool supportsSwapEvents() const; int visualDepth(xcb_visualid_t visual) const; FBConfigInfo *infoForVisual(xcb_visualid_t visual); /** * @brief The OverlayWindow used by this Backend. */ OverlayWindow *m_overlayWindow; Window window; GLXFBConfig fbconfig; GLXWindow glxWindow; GLXContext ctx; QHash m_fbconfigHash; QHash m_visualDepthHash; std::unique_ptr m_swapEventFilter; int m_bufferAge; bool m_haveMESACopySubBuffer = false; bool m_haveMESASwapControl = false; bool m_haveEXTSwapControl = false; - bool m_needsCompositeTimerStart = false; Display *m_x11Display; friend class GlxTexture; }; /** * @brief Texture using an GLXPixmap. */ class GlxTexture : public SceneOpenGLTexturePrivate { public: ~GlxTexture() override; void onDamage() override; bool loadTexture(WindowPixmap *pixmap) override; OpenGLBackend *backend() override; private: friend class GlxBackend; GlxTexture(SceneOpenGLTexture *texture, GlxBackend *backend); bool loadTexture(xcb_pixmap_t pix, const QSize &size, xcb_visualid_t visual); Display *display() const { return m_backend->m_x11Display; } SceneOpenGLTexture *q; GlxBackend *m_backend; GLXPixmap m_glxpixmap; // the glx pixmap the texture is bound to }; } // namespace #endif // KWIN_GLX_BACKEND_H