/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2006 Lubos Lunak SPDX-License-Identifier: GPL-2.0-or-later */ #include "compositor_x11.h" #include "core/outputbackend.h" #include "core/overlaywindow.h" #include "core/renderbackend.h" #include "core/renderlayer.h" #include "effect/effecthandler.h" #include "ftrace.h" #include "opengl/glplatform.h" #include "options.h" #include "platformsupport/scenes/opengl/openglbackend.h" #include "scene/surfaceitem_x11.h" #include "scene/workspacescene_opengl.h" #include "utils/common.h" #include "utils/xcbutils.h" #include "window.h" #include "workspace.h" #include "x11syncmanager.h" #include #include #include #include #if KWIN_BUILD_NOTIFICATIONS #include #endif #include #include #include #include Q_DECLARE_METATYPE(KWin::X11Compositor::SuspendReason) namespace KWin { class X11CompositorSelectionOwner : public KSelectionOwner { Q_OBJECT public: X11CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, kwinApp()->x11Connection(), kwinApp()->x11RootWindow()) , m_owning(false) { connect(this, &X11CompositorSelectionOwner::lostOwnership, this, [this]() { m_owning = false; }); } bool owning() const { return m_owning; } void setOwning(bool own) { m_owning = own; } private: bool m_owning; }; X11Compositor *X11Compositor::create(QObject *parent) { Q_ASSERT(!s_compositor); auto *compositor = new X11Compositor(parent); s_compositor = compositor; return compositor; } X11Compositor::X11Compositor(QObject *parent) : Compositor(parent) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) { if (qEnvironmentVariableIsSet("KWIN_MAX_FRAMES_TESTED")) { m_framesToTestForSafety = qEnvironmentVariableIntValue("KWIN_MAX_FRAMES_TESTED"); } connect(options, &Options::configChanged, this, [this]() { if (m_suspended) { stop(); } else { reinitialize(); } }); m_releaseSelectionTimer.setSingleShot(true); m_releaseSelectionTimer.setInterval(2000); connect(&m_releaseSelectionTimer, &QTimer::timeout, this, &X11Compositor::releaseCompositorSelection); QAction *toggleAction = new QAction(this); toggleAction->setProperty("componentName", QStringLiteral("kwin")); toggleAction->setObjectName("Suspend Compositing"); toggleAction->setText(i18n("Suspend Compositing")); KGlobalAccel::self()->setDefaultShortcut(toggleAction, QList() << (Qt::SHIFT | Qt::ALT | Qt::Key_F12)); KGlobalAccel::self()->setShortcut(toggleAction, QList() << (Qt::SHIFT | Qt::ALT | Qt::Key_F12)); connect(toggleAction, &QAction::triggered, this, &X11Compositor::toggle); // 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. QTimer::singleShot(0, this, &Compositor::start); } X11Compositor::~X11Compositor() { Q_EMIT aboutToDestroy(); if (m_openGLFreezeProtectionThread) { m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); } stop(); // this can't be called in the destructor of Compositor destroyCompositorSelection(); } X11SyncManager *X11Compositor::syncManager() const { return m_syncManager.get(); } void X11Compositor::toggle() { 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; m_inhibitors.clear(); Compositor::reinitialize(); } void X11Compositor::suspend(X11Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; stop(); } void X11Compositor::resume(X11Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; if (reason & BlockRuleSuspend) { m_inhibitors.clear(); } start(); } void X11Compositor::destroyCompositorSelection() { m_selectionOwner.reset(); } void X11Compositor::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; } } bool X11Compositor::attemptOpenGLCompositing() { // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes: if (openGLCompositingIsBroken()) { qCWarning(KWIN_CORE) << "KWin has detected that your OpenGL library is unsafe to use"; return false; } createOpenGLSafePoint(OpenGLSafePoint::PreInit); auto safePointScope = qScopeGuard([this]() { createOpenGLSafePoint(OpenGLSafePoint::PostInit); }); std::unique_ptr backend = kwinApp()->outputBackend()->createOpenGLBackend(); if (!backend) { return false; } if (!backend->isFailed()) { backend->init(); } if (backend->isFailed()) { return false; } const QByteArray forceEnv = qgetenv("KWIN_COMPOSE"); if (!forceEnv.isEmpty()) { if (qstrcmp(forceEnv, "O2") == 0 || qstrcmp(forceEnv, "O2ES") == 0) { qCDebug(KWIN_CORE) << "OpenGL 2 compositing enforced by environment variable"; } else { // OpenGL 2 disabled by environment variable return false; } } else { if (backend->openglContext()->glPlatform()->recommendedCompositor() < OpenGLCompositing) { qCDebug(KWIN_CORE) << "Driver does not recommend OpenGL compositing"; return false; } } // We only support the OpenGL 2+ shader API, not GL_ARB_shader_objects if (!backend->openglContext()->hasVersion(Version(2, 0))) { qCDebug(KWIN_CORE) << "OpenGL 2.0 is not supported"; return false; } // set strict binding if (options->isGlStrictBindingFollowsDriver()) { options->setGlStrictBinding(!backend->openglContext()->glPlatform()->isLooseBinding()); } m_scene = std::make_unique(backend.get()); m_backend = std::move(backend); qCDebug(KWIN_CORE) << "OpenGL compositing has been successfully initialized"; return true; } 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"); } qCInfo(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!compositingPossible()) { qCWarning(KWIN_CORE) << "Compositing is not possible"; return; } if (kwinApp()->isTerminating()) { return; } if (m_state != State::Off) { return; } Q_EMIT aboutToToggleCompositing(); m_state = State::Starting; // Claim special _NET_WM_CM_S0 selection and redirect child windows of the root window. if (!m_selectionOwner) { m_selectionOwner = std::make_unique("_NET_WM_CM_S0"); connect(m_selectionOwner.get(), &X11CompositorSelectionOwner::lostOwnership, this, &X11Compositor::stop); } if (!m_selectionOwner->owning()) { // Force claim ownership. m_selectionOwner->claim(true); m_selectionOwner->setOwning(true); } xcb_composite_redirect_subwindows(kwinApp()->x11Connection(), kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); // Decide what compositing types can be used. QList candidateCompositors = kwinApp()->outputBackend()->supportedCompositors(); const auto userConfigIt = std::find(candidateCompositors.begin(), candidateCompositors.end(), options->compositingMode()); if (userConfigIt != candidateCompositors.end()) { candidateCompositors.erase(userConfigIt); candidateCompositors.prepend(options->compositingMode()); } else { qCWarning(KWIN_CORE) << "Configured compositor not supported by Platform. Falling back to defaults"; } for (auto type : std::as_const(candidateCompositors)) { bool stop = false; switch (type) { case OpenGLCompositing: qCDebug(KWIN_CORE) << "Attempting to load the OpenGL scene"; stop = attemptOpenGLCompositing(); if (stop) { QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); } break; case QPainterCompositing: qCDebug(KWIN_CORE) << "QPainter compositing is unsupported on X11"; break; case NoCompositing: qCDebug(KWIN_CORE) << "Starting without compositing..."; stop = true; break; } if (stop) { break; } else if (qEnvironmentVariableIsSet("KWIN_COMPOSE")) { qCCritical(KWIN_CORE) << "Could not fulfill the requested compositing mode in KWIN_COMPOSE:" << type << ". Exiting."; qApp->quit(); } } if (!m_backend) { m_state = State::Off; xcb_composite_unredirect_subwindows(kwinApp()->x11Connection(), kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); if (m_selectionOwner) { m_selectionOwner->setOwning(false); m_selectionOwner->release(); } return; } Q_EMIT sceneCreated(); kwinApp()->setX11CompositeWindow(backend()->overlayWindow()->window()); auto workspaceLayer = new RenderLayer(workspace()->outputs()[0]->renderLoop()); workspaceLayer->setDelegate(std::make_unique(m_scene.get(), nullptr)); workspaceLayer->setGeometry(workspace()->geometry()); connect(workspace(), &Workspace::geometryChanged, workspaceLayer, [workspaceLayer]() { workspaceLayer->setGeometry(workspace()->geometry()); }); addSuperLayer(workspaceLayer); m_state = State::On; const auto windows = workspace()->windows(); for (Window *window : windows) { window->setupCompositing(); } // Sets also the 'effects' pointer. kwinApp()->createEffectsHandler(this, m_scene.get()); m_syncManager.reset(X11SyncManager::create(m_backend.get())); if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } Q_EMIT compositingToggled(true); } void X11Compositor::stop() { if (m_state == State::Off || m_state == State::Stopping) { return; } m_state = State::Stopping; Q_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()) { const auto windows = workspace()->windows(); for (Window *window : windows) { window->finishCompositing(); } xcb_composite_unredirect_subwindows(kwinApp()->x11Connection(), kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } if (m_backend->compositingType() == OpenGLCompositing) { // some layers need a context current for destruction static_cast(m_backend.get())->makeCurrent(); } const auto superlayers = m_superlayers; for (auto it = superlayers.begin(); it != superlayers.end(); ++it) { removeSuperLayer(*it); } m_syncManager.reset(); m_scene.reset(); m_backend.reset(); kwinApp()->setX11CompositeWindow(XCB_WINDOW_NONE); m_state = State::Off; Q_EMIT compositingToggled(false); } void X11Compositor::composite(RenderLoop *renderLoop) { if (backend()->overlayWindow() && !backend()->overlayWindow()->isVisible()) { // Return since nothing is visible. return; } QList windows = workspace()->stackingOrder(); QList dirtyItems; // Reset the damage state of each window and fetch the damage region // without waiting for a reply for (Window *window : std::as_const(windows)) { SurfaceItemX11 *surfaceItem = static_cast(window->surfaceItem()); if (surfaceItem->fetchDamage()) { dirtyItems.append(surfaceItem); } } if (dirtyItems.count() > 0) { if (m_syncManager) { m_syncManager->triggerFence(); } xcb_flush(kwinApp()->x11Connection()); } // Get the replies for (SurfaceItemX11 *item : std::as_const(dirtyItems)) { item->waitForDamage(); } if (m_framesToTestForSafety > 0 && (backend()->compositingType() & OpenGLCompositing)) { createOpenGLSafePoint(OpenGLSafePoint::PreFrame); } if (m_backend->checkGraphicsReset()) { qCDebug(KWIN_CORE) << "Graphics reset occurred"; #if KWIN_BUILD_NOTIFICATIONS KNotification::event(QStringLiteral("graphicsreset"), i18n("Desktop effects were restarted due to a graphics reset")); #endif reinitialize(); return; } OutputLayer *primaryLayer = m_backend->primaryLayer(nullptr); fTraceDuration("Paint"); RenderLayer *superLayer = m_superlayers[renderLoop]; superLayer->setOutputLayer(primaryLayer); renderLoop->prepareNewFrame(); auto frame = std::make_shared(renderLoop, std::chrono::nanoseconds(1'000'000'000'000) / renderLoop->refreshRate()); if (primaryLayer->needsRepaint() || superLayer->needsRepaint()) { renderLoop->beginPaint(); QRegion surfaceDamage = primaryLayer->repaints(); primaryLayer->resetRepaints(); prePaintPass(superLayer, &surfaceDamage); if (auto beginInfo = primaryLayer->beginFrame()) { auto &[renderTarget, repaint] = beginInfo.value(); const QRegion bufferDamage = surfaceDamage.united(repaint).intersected(superLayer->rect().toAlignedRect()); paintPass(superLayer, renderTarget, bufferDamage); primaryLayer->endFrame(bufferDamage, surfaceDamage, frame.get()); } postPaintPass(superLayer); } m_backend->present(nullptr, frame); framePass(superLayer, frame.get()); if (m_syncManager) { if (!m_syncManager->endFrame()) { qCDebug(KWIN_CORE) << "Aborting explicit synchronization with the X command stream."; qCDebug(KWIN_CORE) << "Future frames will be rendered unsynchronized."; m_syncManager.reset(); } } if (m_framesToTestForSafety > 0) { if (backend()->compositingType() & OpenGLCompositing) { createOpenGLSafePoint(OpenGLSafePoint::PostFrame); } m_framesToTestForSafety--; if (m_framesToTestForSafety == 0 && (backend()->compositingType() & OpenGLCompositing)) { createOpenGLSafePoint(OpenGLSafePoint::PostLastGuardedFrame); } } } void X11Compositor::inhibit(Window *window) { m_inhibitors.insert(window); // Do NOT attempt to call suspend(true) from within the eventchain! if (!(m_suspended & BlockRuleSuspend)) { QMetaObject::invokeMethod( this, [this]() { suspend(BlockRuleSuspend); }, Qt::QueuedConnection); } } void X11Compositor::uninhibit(Window *window) { if (!m_inhibitors.remove(window)) { return; } if (m_suspended & BlockRuleSuspend) { if (m_inhibitors.isEmpty()) { // 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()); } bool X11Compositor::openGLCompositingIsBroken() const { auto timestamp = KConfigGroup(kwinApp()->config(), QStringLiteral("Compositing")).readEntry(QLatin1String("LastFailureTimestamp"), 0); if (timestamp > 0) { if (QDateTime::currentSecsSinceEpoch() - timestamp < 60) { return true; } } return false; } QString X11Compositor::compositingNotPossibleReason() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), QStringLiteral("Compositing")); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && openGLCompositingIsBroken()) { return i18n("OpenGL compositing (the default) has crashed KWin in the past.
" "This was most likely due to a driver bug." "

If you think that you have meanwhile upgraded to a stable driver,
" "you can reset this protection but be aware that this might result in an immediate crash!

"); } if (!Xcb::Extensions::self()->isCompositeAvailable() || !Xcb::Extensions::self()->isDamageAvailable()) { return i18n("Required X extensions (XComposite and XDamage) are not available."); } if (!Xcb::Extensions::self()->hasGlx()) { return i18n("GLX/OpenGL is not available."); } return QString(); } bool X11Compositor::compositingPossible() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), QStringLiteral("Compositing")); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && openGLCompositingIsBroken()) { qCWarning(KWIN_CORE) << "Compositing disabled: video driver seems unstable. If you think it's a false positive, please try again in a few minutes."; return false; } if (!Xcb::Extensions::self()->isCompositeAvailable()) { qCWarning(KWIN_CORE) << "Compositing disabled: no composite extension available"; return false; } if (!Xcb::Extensions::self()->isDamageAvailable()) { qCWarning(KWIN_CORE) << "Compositing disabled: no damage extension available"; return false; } if (Xcb::Extensions::self()->hasGlx()) { return true; } if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { return true; } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { return true; } qCWarning(KWIN_CORE) << "Compositing disabled: no OpenGL support"; return false; } void X11Compositor::createOpenGLSafePoint(OpenGLSafePoint safePoint) { auto group = KConfigGroup(kwinApp()->config(), QStringLiteral("Compositing")); switch (safePoint) { case OpenGLSafePoint::PreInit: // Explicitly write the failure timestamp so that if we crash during // OpenGL init, we know we should not try again. group.writeEntry(QLatin1String("LastFailureTimestamp"), QDateTime::currentSecsSinceEpoch()); group.sync(); // Deliberately continue with PreFrame Q_FALLTHROUGH(); case OpenGLSafePoint::PreFrame: if (m_openGLFreezeProtectionThread == nullptr) { Q_ASSERT(m_openGLFreezeProtection == nullptr); m_openGLFreezeProtectionThread = std::make_unique(); m_openGLFreezeProtectionThread->setObjectName("FreezeDetector"); m_openGLFreezeProtectionThread->start(); m_openGLFreezeProtection = std::make_unique(); m_openGLFreezeProtection->setInterval(15000); m_openGLFreezeProtection->setSingleShot(true); m_openGLFreezeProtection->start(); const QString configName = kwinApp()->config()->name(); m_openGLFreezeProtection->moveToThread(m_openGLFreezeProtectionThread.get()); connect( m_openGLFreezeProtection.get(), &QTimer::timeout, m_openGLFreezeProtection.get(), [configName] { auto group = KConfigGroup(KSharedConfig::openConfig(configName), QStringLiteral("Compositing")); group.writeEntry(QLatin1String("LastFailureTimestamp"), QDateTime::currentSecsSinceEpoch()); group.sync(); KCrash::setDrKonqiEnabled(false); qFatal("Freeze in OpenGL initialization detected"); }, Qt::DirectConnection); } else { Q_ASSERT(m_openGLFreezeProtection); QMetaObject::invokeMethod(m_openGLFreezeProtection.get(), QOverload<>::of(&QTimer::start), Qt::QueuedConnection); } break; case OpenGLSafePoint::PostInit: group.deleteEntry(QLatin1String("LastFailureTimestamp")); group.sync(); // Deliberately continue with PostFrame Q_FALLTHROUGH(); case OpenGLSafePoint::PostFrame: QMetaObject::invokeMethod(m_openGLFreezeProtection.get(), &QTimer::stop, Qt::QueuedConnection); break; case OpenGLSafePoint::PostLastGuardedFrame: m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); m_openGLFreezeProtectionThread.reset(); m_openGLFreezeProtection.reset(); break; } } } // namespace KWin #include "compositor_x11.moc" #include "moc_compositor_x11.cpp"