From fc148cb668d8972f33a9b0f3f912580b60f09cc1 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Thu, 21 Sep 2023 10:18:13 +0300 Subject: [PATCH] Split X11 and Wayland specific compositor initialization code paths With the current vision for how output backends work, the compositor should take up more responsibilities. There are a few good reasons: some things just don't make sense to be in backends, to allow sharing code across backends easier, etc. On the other hand, we have X11, with its own ways of doing things which are not always compatible with what we want to do on Wayland. The goal of this patch is to start splitting the compositor into platform specific counterparts, with potentially moving X11 compositing in kwin_x11. The main benefit of this is that we will be able to push forward with wayland things more freely. Ideally it would be great if we could make kwin_x11 have its own low level compositing code paths that are nicely encapsulated in that executable and don't leak into libkwin abstractions. The biggest drawback of this approach is that there is going to be some code duplication between x11 and wayland compositing code paths. But I expect it to be the case only for a short term until we start landing more abstractions in kwin_wayland, e.g. render devices, proper output layer support, etc. --- src/compositor.cpp | 471 +------------------------------------ src/compositor.h | 42 +--- src/compositor_wayland.cpp | 327 ++++++++++++++++++++++++- src/compositor_wayland.h | 9 + src/compositor_x11.cpp | 268 ++++++++++++++++++++- src/compositor_x11.h | 26 +- 6 files changed, 625 insertions(+), 518 deletions(-) diff --git a/src/compositor.cpp b/src/compositor.cpp index 0b61f7627a..21a988a225 100644 --- a/src/compositor.cpp +++ b/src/compositor.cpp @@ -11,38 +11,23 @@ #include #include "core/output.h" -#include "core/outputbackend.h" #include "core/outputlayer.h" +#include "core/renderbackend.h" #include "core/renderlayer.h" #include "core/renderloop.h" #include "dbusinterface.h" -#include "effects.h" #include "ftrace.h" -#include "platformsupport/scenes/opengl/openglbackend.h" -#include "platformsupport/scenes/qpainter/qpainterbackend.h" -#include "scene/cursordelegate_opengl.h" -#include "scene/cursordelegate_qpainter.h" #include "scene/cursorscene.h" -#include "scene/itemrenderer_opengl.h" -#include "scene/itemrenderer_qpainter.h" #include "scene/surfaceitem.h" -#include "scene/workspacescene_opengl.h" -#include "scene/workspacescene_qpainter.h" +#include "scene/workspacescene.h" #include "utils/common.h" #include "wayland_server.h" #include "workspace.h" -#include "libkwineffects/glplatform.h" - #include #if KWIN_BUILD_NOTIFICATIONS #include #endif -#include - -#include - -#include namespace KWin { @@ -53,32 +38,6 @@ Compositor *Compositor::self() return s_compositor; } -class CompositorSelectionOwner : public KSelectionOwner -{ - Q_OBJECT -public: - CompositorSelectionOwner(const char *selection) - : KSelectionOwner(selection, kwinApp()->x11Connection(), kwinApp()->x11RootWindow()) - , 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; -}; - Compositor::Compositor(QObject *workspace) : QObject(workspace) { @@ -88,11 +47,6 @@ Compositor::Compositor(QObject *workspace) // 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, @@ -112,228 +66,9 @@ Compositor::Compositor(QObject *workspace) Compositor::~Compositor() { deleteUnusedSupportProperties(); - destroyCompositorSelection(); s_compositor = nullptr; } -bool Compositor::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 (GLPlatform::instance()->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 (!hasGLVersion(2, 0)) { - qCDebug(KWIN_CORE) << "OpenGL 2.0 is not supported"; - return false; - } - - m_scene = std::make_unique(backend.get()); - m_cursorScene = std::make_unique(std::make_unique()); - m_backend = std::move(backend); - - // set strict binding - if (options->isGlStrictBindingFollowsDriver()) { - options->setGlStrictBinding(!GLPlatform::instance()->supports(GLFeature::LooseBinding)); - } - - qCDebug(KWIN_CORE) << "OpenGL compositing has been successfully initialized"; - return true; -} - -bool Compositor::attemptQPainterCompositing() -{ - std::unique_ptr backend(kwinApp()->outputBackend()->createQPainterBackend()); - if (!backend || backend->isFailed()) { - return false; - } - - m_scene = std::make_unique(backend.get()); - m_cursorScene = std::make_unique(std::make_unique()); - m_backend = std::move(backend); - - qCDebug(KWIN_CORE) << "QPainter compositing has been successfully initialized"; - return true; -} - -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; - - if (kwinApp()->operationMode() == Application::OperationModeX11) { - if (!m_selectionOwner) { - m_selectionOwner = std::make_unique("_NET_WM_CM_S0"); - connect(m_selectionOwner.get(), &CompositorSelectionOwner::lostOwnership, this, &Compositor::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); - } - - Q_EMIT aboutToToggleCompositing(); - - const QVector availableCompositors = kwinApp()->outputBackend()->supportedCompositors(); - QVector candidateCompositors; - - // If compositing has been restarted, try to use the last used compositing type. - if (m_selectedCompositor != NoCompositing) { - candidateCompositors.append(m_selectedCompositor); - } else { - candidateCompositors = availableCompositors; - - 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(); - break; - case QPainterCompositing: - qCDebug(KWIN_CORE) << "Attempting to load the QPainter scene"; - stop = attemptQPainterCompositing(); - 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; - - if (kwinApp()->operationMode() == Application::OperationModeX11) { - xcb_composite_unredirect_subwindows(kwinApp()->x11Connection(), - kwinApp()->x11RootWindow(), - XCB_COMPOSITE_REDIRECT_MANUAL); - if (m_selectionOwner) { - m_selectionOwner->setOwning(false); - m_selectionOwner->release(); - } - } - if (!availableCompositors.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; - } - - m_selectedCompositor = m_backend->compositingType(); - - if (!Workspace::self() && m_backend && m_backend->compositingType() == QPainterCompositing) { - // Force Software QtQuick on first startup with QPainter. - QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); - } - - Q_EMIT sceneCreated(); - - return true; -} - -void Compositor::startupWithWorkspace() -{ - Q_ASSERT(m_scene); - m_scene->initialize(); - m_cursorScene->initialize(); - - const QList outputs = workspace()->outputs(); - if (kwinApp()->operationMode() == Application::OperationModeX11) { - auto workspaceLayer = new RenderLayer(outputs.constFirst()->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); - } else { - for (Output *output : outputs) { - addOutput(output); - } - connect(workspace(), &Workspace::outputAdded, this, &Compositor::addOutput); - connect(workspace(), &Workspace::outputRemoved, this, &Compositor::removeOutput); - } - - 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()); - - Q_EMIT compositingToggled(true); - - if (m_releaseSelectionTimer.isActive()) { - m_releaseSelectionTimer.stop(); - } -} - Output *Compositor::findOutput(RenderLoop *loop) const { const auto outputs = workspace()->outputs(); @@ -345,125 +80,6 @@ Output *Compositor::findOutput(RenderLoop *loop) const return nullptr; } -void Compositor::addOutput(Output *output) -{ - Q_ASSERT(kwinApp()->operationMode() != Application::OperationModeX11); - - auto workspaceLayer = new RenderLayer(output->renderLoop()); - workspaceLayer->setDelegate(std::make_unique(m_scene.get(), output)); - workspaceLayer->setGeometry(output->rect()); - connect(output, &Output::geometryChanged, workspaceLayer, [output, workspaceLayer]() { - workspaceLayer->setGeometry(output->rect()); - }); - - auto cursorLayer = new RenderLayer(output->renderLoop()); - cursorLayer->setVisible(false); - if (m_backend->compositingType() == OpenGLCompositing) { - cursorLayer->setDelegate(std::make_unique(output)); - } else { - cursorLayer->setDelegate(std::make_unique(output)); - } - cursorLayer->setParent(workspaceLayer); - cursorLayer->setSuperlayer(workspaceLayer); - - static bool valid; - static const bool forceSoftwareCursor = qEnvironmentVariableIntValue("KWIN_FORCE_SW_CURSOR", &valid) == 1 && valid; - - auto updateCursorLayer = [this, output, cursorLayer]() { - const Cursor *cursor = Cursors::self()->currentCursor(); - const QRectF outputLocalRect = output->mapFromGlobal(cursor->geometry()); - const auto outputLayer = m_backend->cursorLayer(output); - if (!cursor->isOnOutput(output)) { - if (outputLayer && outputLayer->isEnabled()) { - outputLayer->setEnabled(false); - output->updateCursorLayer(); - } - cursorLayer->setVisible(false); - return true; - } - const auto renderHardwareCursor = [&]() { - if (!outputLayer || forceSoftwareCursor) { - return false; - } - const QMatrix4x4 monitorMatrix = Output::logicalToNativeMatrix(output->rect(), output->scale(), output->transform()); - QRectF nativeCursorRect = monitorMatrix.mapRect(outputLocalRect); - QSize bufferSize(std::ceil(nativeCursorRect.width()), std::ceil(nativeCursorRect.height())); - if (const auto fixedSize = outputLayer->fixedSize()) { - if (fixedSize->width() < bufferSize.width() || fixedSize->height() < bufferSize.height()) { - return false; - } - bufferSize = *fixedSize; - nativeCursorRect = monitorMatrix.mapRect(QRectF(outputLocalRect.topLeft(), QSizeF(bufferSize) / output->scale())); - } - outputLayer->setPosition(nativeCursorRect.topLeft()); - outputLayer->setHotspot(Output::logicalToNativeMatrix(QRectF(QPointF(), QSizeF(bufferSize) / output->scale()), output->scale(), output->transform()).map(cursor->hotspot())); - outputLayer->setSize(bufferSize); - if (auto beginInfo = outputLayer->beginFrame()) { - const RenderTarget &renderTarget = beginInfo->renderTarget; - - RenderLayer renderLayer(output->renderLoop()); - renderLayer.setDelegate(std::make_unique(m_cursorScene.get(), output)); - renderLayer.setOutputLayer(outputLayer); - - renderLayer.delegate()->prePaint(); - renderLayer.delegate()->paint(renderTarget, infiniteRegion()); - renderLayer.delegate()->postPaint(); - - if (!outputLayer->endFrame(infiniteRegion(), infiniteRegion())) { - return false; - } - } - outputLayer->setEnabled(true); - return output->updateCursorLayer(); - }; - if (renderHardwareCursor()) { - cursorLayer->setVisible(false); - return true; - } else { - if (outputLayer && outputLayer->isEnabled()) { - outputLayer->setEnabled(false); - output->updateCursorLayer(); - } - cursorLayer->setVisible(cursor->isOnOutput(output)); - cursorLayer->setGeometry(outputLocalRect); - cursorLayer->addRepaintFull(); - return false; - } - }; - auto moveCursorLayer = [this, output, cursorLayer, updateCursorLayer]() { - const Cursor *cursor = Cursors::self()->currentCursor(); - const QRectF outputLocalRect = output->mapFromGlobal(cursor->geometry()); - const auto outputLayer = m_backend->cursorLayer(output); - bool hardwareCursor = false; - if (outputLayer) { - if (outputLayer->isEnabled()) { - const QMatrix4x4 monitorMatrix = Output::logicalToNativeMatrix(output->rect(), output->scale(), output->transform()); - const QRectF nativeCursorRect = monitorMatrix.mapRect(QRectF(outputLocalRect.topLeft(), outputLayer->size() / output->scale())); - outputLayer->setPosition(nativeCursorRect.topLeft()); - hardwareCursor = output->updateCursorLayer(); - } else if (!cursorLayer->isVisible() && !forceSoftwareCursor) { - // this is for the case that the cursor wasn't visible because it was on a different output before - hardwareCursor = updateCursorLayer(); - } - } - cursorLayer->setVisible(cursor->isOnOutput(output) && !hardwareCursor); - cursorLayer->setGeometry(outputLocalRect); - cursorLayer->addRepaintFull(); - }; - updateCursorLayer(); - connect(output, &Output::geometryChanged, cursorLayer, updateCursorLayer); - connect(Cursors::self(), &Cursors::currentCursorChanged, cursorLayer, updateCursorLayer); - connect(Cursors::self(), &Cursors::hiddenChanged, cursorLayer, updateCursorLayer); - connect(Cursors::self(), &Cursors::positionChanged, cursorLayer, moveCursorLayer); - - addSuperLayer(workspaceLayer); -} - -void Compositor::removeOutput(Output *output) -{ - removeSuperLayer(m_superlayers[output->renderLoop()]); -} - void Compositor::addSuperLayer(RenderLayer *layer) { m_superlayers.insert(layer->loop(), layer); @@ -477,82 +93,6 @@ void Compositor::removeSuperLayer(RenderLayer *layer) delete layer; } -void Compositor::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(); - } - if (kwinApp()->operationMode() == Application::OperationModeX11) { - xcb_composite_unredirect_subwindows(kwinApp()->x11Connection(), - kwinApp()->x11RootWindow(), - XCB_COMPOSITE_REDIRECT_MANUAL); - } - - disconnect(workspace(), &Workspace::outputAdded, this, &Compositor::addOutput); - disconnect(workspace(), &Workspace::outputRemoved, this, &Compositor::removeOutput); - } - - 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_scene.reset(); - m_cursorScene.reset(); - m_backend.reset(); - - m_state = State::Off; - Q_EMIT compositingToggled(false); -} - -void Compositor::destroyCompositorSelection() -{ - m_selectionOwner.reset(); -} - -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); @@ -750,10 +290,6 @@ bool Compositor::openGLCompositingIsBroken() const return false; } -void Compositor::createOpenGLSafePoint(OpenGLSafePoint safePoint) -{ -} - void Compositor::inhibit(Window *window) { } @@ -764,7 +300,4 @@ void Compositor::uninhibit(Window *window) } // namespace KWin -// included for CompositorSelectionOwner -#include "compositor.moc" - #include "moc_compositor.cpp" diff --git a/src/compositor.h b/src/compositor.h index 64a4f83856..20861584fb 100644 --- a/src/compositor.h +++ b/src/compositor.h @@ -21,7 +21,6 @@ namespace KWin { class Output; -class CompositorSelectionOwner; class CursorScene; class RenderBackend; class RenderLayer; @@ -108,22 +107,6 @@ public: * @see createOpenGLSafePoint */ virtual bool openGLCompositingIsBroken() const; - enum class OpenGLSafePoint { - PreInit, - PostInit, - PreFrame, - PostFrame, - PostLastGuardedFrame - }; - /** - * This method is invoked before and after creating the OpenGL rendering Scene. - * An implementing Platform can use it to detect crashes triggered by the OpenGL implementation. - * This can be used for openGLCompositingIsBroken. - * - * The default implementation does nothing. - * @see openGLCompositingIsBroken. - */ - virtual void createOpenGLSafePoint(OpenGLSafePoint safePoint); /** * @returns the format of the contents in the @p output @@ -145,22 +128,10 @@ protected: explicit Compositor(QObject *parent = nullptr); virtual void start() = 0; - virtual 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 stop() = 0; virtual void configChanged(); - void destroyCompositorSelection(); - static Compositor *s_compositor; protected Q_SLOTS: @@ -169,16 +140,10 @@ protected Q_SLOTS: private Q_SLOTS: void handleFrameRequested(RenderLoop *renderLoop); -private: - void releaseCompositorSelection(); +protected: void deleteUnusedSupportProperties(); - bool attemptOpenGLCompositing(); - bool attemptQPainterCompositing(); - Output *findOutput(RenderLoop *loop) const; - void addOutput(Output *output); - void removeOutput(Output *output); void addSuperLayer(RenderLayer *layer); void removeSuperLayer(RenderLayer *layer); @@ -190,15 +155,12 @@ private: void framePass(RenderLayer *layer); State m_state = State::Off; - std::unique_ptr m_selectionOwner; - QTimer m_releaseSelectionTimer; QList m_unusedSupportProperties; QTimer m_unusedSupportPropertyTimer; std::unique_ptr m_scene; std::unique_ptr m_cursorScene; std::unique_ptr m_backend; QHash m_superlayers; - CompositingType m_selectedCompositor = NoCompositing; }; } // namespace KWin diff --git a/src/compositor_wayland.cpp b/src/compositor_wayland.cpp index 1b87859460..4f93b5fa45 100644 --- a/src/compositor_wayland.cpp +++ b/src/compositor_wayland.cpp @@ -8,9 +8,26 @@ */ #include "compositor_wayland.h" +#include "core/output.h" +#include "core/outputbackend.h" +#include "core/renderbackend.h" +#include "core/renderlayer.h" +#include "libkwineffects/glplatform.h" #include "main.h" +#include "platformsupport/scenes/opengl/openglbackend.h" +#include "platformsupport/scenes/qpainter/qpainterbackend.h" +#include "scene/cursordelegate_opengl.h" +#include "scene/cursordelegate_qpainter.h" +#include "scene/cursorscene.h" +#include "scene/itemrenderer_opengl.h" +#include "scene/itemrenderer_qpainter.h" +#include "scene/workspacescene_opengl.h" +#include "scene/workspacescene_qpainter.h" +#include "window.h" #include "workspace.h" +#include + namespace KWin { @@ -33,14 +50,318 @@ WaylandCompositor::~WaylandCompositor() stop(); // this can't be called in the destructor of Compositor } +bool WaylandCompositor::attemptOpenGLCompositing() +{ + 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 (GLPlatform::instance()->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 (!hasGLVersion(2, 0)) { + qCDebug(KWIN_CORE) << "OpenGL 2.0 is not supported"; + return false; + } + + m_scene = std::make_unique(backend.get()); + m_cursorScene = std::make_unique(std::make_unique()); + m_backend = std::move(backend); + + qCDebug(KWIN_CORE) << "OpenGL compositing has been successfully initialized"; + return true; +} + +bool WaylandCompositor::attemptQPainterCompositing() +{ + std::unique_ptr backend(kwinApp()->outputBackend()->createQPainterBackend()); + if (!backend || backend->isFailed()) { + return false; + } + + m_scene = std::make_unique(backend.get()); + m_cursorScene = std::make_unique(std::make_unique()); + m_backend = std::move(backend); + + qCDebug(KWIN_CORE) << "QPainter compositing has been successfully initialized"; + return true; +} + void WaylandCompositor::start() { - if (!Compositor::setupStart()) { - // Internal setup failed, abort. + if (kwinApp()->isTerminating()) { + return; + } + if (m_state != State::Off) { + return; + } + + Q_EMIT aboutToToggleCompositing(); + m_state = State::Starting; + + // If compositing has been restarted, try to use the last used compositing type. + const QVector availableCompositors = kwinApp()->outputBackend()->supportedCompositors(); + QVector candidateCompositors; + + if (m_selectedCompositor != NoCompositing) { + candidateCompositors.append(m_selectedCompositor); + } else { + candidateCompositors = availableCompositors; + + 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(); + break; + case QPainterCompositing: + qCDebug(KWIN_CORE) << "Attempting to load the QPainter scene"; + stop = attemptQPainterCompositing(); + 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; + + 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; + } + + if (m_selectedCompositor == NoCompositing) { + m_selectedCompositor = m_backend->compositingType(); + + // Force qtquick to software rendering if kwin uses software rendering too. + if (m_selectedCompositor == QPainterCompositing) { + QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); + } + } + + Q_EMIT sceneCreated(); + + Q_ASSERT(m_scene); + m_scene->initialize(); + m_cursorScene->initialize(); + + const QList outputs = workspace()->outputs(); + for (Output *output : outputs) { + addOutput(output); + } + connect(workspace(), &Workspace::outputAdded, this, &WaylandCompositor::addOutput); + connect(workspace(), &Workspace::outputRemoved, this, &WaylandCompositor::removeOutput); + + 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()); + + Q_EMIT compositingToggled(true); +} + +void WaylandCompositor::stop() +{ + if (m_state == State::Off || m_state == State::Stopping) { return; } + m_state = State::Stopping; + Q_EMIT aboutToToggleCompositing(); + + // 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(); + } + disconnect(workspace(), &Workspace::outputAdded, this, &WaylandCompositor::addOutput); + disconnect(workspace(), &Workspace::outputRemoved, this, &WaylandCompositor::removeOutput); + } + + 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_scene.reset(); + m_cursorScene.reset(); + m_backend.reset(); + + m_state = State::Off; + Q_EMIT compositingToggled(false); +} + +void WaylandCompositor::addOutput(Output *output) +{ + auto workspaceLayer = new RenderLayer(output->renderLoop()); + workspaceLayer->setDelegate(std::make_unique(m_scene.get(), output)); + workspaceLayer->setGeometry(output->rect()); + connect(output, &Output::geometryChanged, workspaceLayer, [output, workspaceLayer]() { + workspaceLayer->setGeometry(output->rect()); + }); + + auto cursorLayer = new RenderLayer(output->renderLoop()); + cursorLayer->setVisible(false); + if (m_backend->compositingType() == OpenGLCompositing) { + cursorLayer->setDelegate(std::make_unique(output)); + } else { + cursorLayer->setDelegate(std::make_unique(output)); + } + cursorLayer->setParent(workspaceLayer); + cursorLayer->setSuperlayer(workspaceLayer); + + static bool valid; + static const bool forceSoftwareCursor = qEnvironmentVariableIntValue("KWIN_FORCE_SW_CURSOR", &valid) == 1 && valid; + + auto updateCursorLayer = [this, output, cursorLayer]() { + const Cursor *cursor = Cursors::self()->currentCursor(); + const QRectF outputLocalRect = output->mapFromGlobal(cursor->geometry()); + const auto outputLayer = m_backend->cursorLayer(output); + if (!cursor->isOnOutput(output)) { + if (outputLayer && outputLayer->isEnabled()) { + outputLayer->setEnabled(false); + output->updateCursorLayer(); + } + cursorLayer->setVisible(false); + return true; + } + const auto renderHardwareCursor = [&]() { + if (!outputLayer || forceSoftwareCursor) { + return false; + } + const QMatrix4x4 monitorMatrix = Output::logicalToNativeMatrix(output->rect(), output->scale(), output->transform()); + QRectF nativeCursorRect = monitorMatrix.mapRect(outputLocalRect); + QSize bufferSize(std::ceil(nativeCursorRect.width()), std::ceil(nativeCursorRect.height())); + if (const auto fixedSize = outputLayer->fixedSize()) { + if (fixedSize->width() < bufferSize.width() || fixedSize->height() < bufferSize.height()) { + return false; + } + bufferSize = *fixedSize; + nativeCursorRect = monitorMatrix.mapRect(QRectF(outputLocalRect.topLeft(), QSizeF(bufferSize) / output->scale())); + } + outputLayer->setPosition(nativeCursorRect.topLeft()); + outputLayer->setHotspot(Output::logicalToNativeMatrix(QRectF(QPointF(), QSizeF(bufferSize) / output->scale()), output->scale(), output->transform()).map(cursor->hotspot())); + outputLayer->setSize(bufferSize); + if (auto beginInfo = outputLayer->beginFrame()) { + const RenderTarget &renderTarget = beginInfo->renderTarget; + + RenderLayer renderLayer(output->renderLoop()); + renderLayer.setDelegate(std::make_unique(m_cursorScene.get(), output)); + renderLayer.setOutputLayer(outputLayer); + + renderLayer.delegate()->prePaint(); + renderLayer.delegate()->paint(renderTarget, infiniteRegion()); + renderLayer.delegate()->postPaint(); - startupWithWorkspace(); + if (!outputLayer->endFrame(infiniteRegion(), infiniteRegion())) { + return false; + } + } + outputLayer->setEnabled(true); + return output->updateCursorLayer(); + }; + if (renderHardwareCursor()) { + cursorLayer->setVisible(false); + return true; + } else { + if (outputLayer && outputLayer->isEnabled()) { + outputLayer->setEnabled(false); + output->updateCursorLayer(); + } + cursorLayer->setVisible(cursor->isOnOutput(output)); + cursorLayer->setGeometry(outputLocalRect); + cursorLayer->addRepaintFull(); + return false; + } + }; + auto moveCursorLayer = [this, output, cursorLayer, updateCursorLayer]() { + const Cursor *cursor = Cursors::self()->currentCursor(); + const QRectF outputLocalRect = output->mapFromGlobal(cursor->geometry()); + const auto outputLayer = m_backend->cursorLayer(output); + bool hardwareCursor = false; + if (outputLayer) { + if (outputLayer->isEnabled()) { + const QMatrix4x4 monitorMatrix = Output::logicalToNativeMatrix(output->rect(), output->scale(), output->transform()); + const QRectF nativeCursorRect = monitorMatrix.mapRect(QRectF(outputLocalRect.topLeft(), outputLayer->size() / output->scale())); + outputLayer->setPosition(nativeCursorRect.topLeft()); + hardwareCursor = output->updateCursorLayer(); + } else if (!cursorLayer->isVisible() && !forceSoftwareCursor) { + // this is for the case that the cursor wasn't visible because it was on a different output before + hardwareCursor = updateCursorLayer(); + } + } + cursorLayer->setVisible(cursor->isOnOutput(output) && !hardwareCursor); + cursorLayer->setGeometry(outputLocalRect); + cursorLayer->addRepaintFull(); + }; + updateCursorLayer(); + connect(output, &Output::geometryChanged, cursorLayer, updateCursorLayer); + connect(Cursors::self(), &Cursors::currentCursorChanged, cursorLayer, updateCursorLayer); + connect(Cursors::self(), &Cursors::hiddenChanged, cursorLayer, updateCursorLayer); + connect(Cursors::self(), &Cursors::positionChanged, cursorLayer, moveCursorLayer); + + addSuperLayer(workspaceLayer); +} + +void WaylandCompositor::removeOutput(Output *output) +{ + removeSuperLayer(m_superlayers[output->renderLoop()]); } } // namespace KWin diff --git a/src/compositor_wayland.h b/src/compositor_wayland.h index a0f0df1b58..0d0529f5a6 100644 --- a/src/compositor_wayland.h +++ b/src/compositor_wayland.h @@ -24,9 +24,18 @@ public: protected: void start() override; + void stop() override; private: explicit WaylandCompositor(QObject *parent); + + bool attemptOpenGLCompositing(); + bool attemptQPainterCompositing(); + + void addOutput(Output *output); + void removeOutput(Output *output); + + CompositingType m_selectedCompositor = NoCompositing; }; } // namespace KWin diff --git a/src/compositor_x11.cpp b/src/compositor_x11.cpp index a3cf1ad846..abcd2790bf 100644 --- a/src/compositor_x11.cpp +++ b/src/compositor_x11.cpp @@ -8,19 +8,25 @@ */ #include "compositor_x11.h" +#include "core/outputbackend.h" #include "core/overlaywindow.h" #include "core/renderbackend.h" +#include "core/renderlayer.h" +#include "libkwineffects/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 "x11window.h" #include #include #include +#include #include #include @@ -31,6 +37,32 @@ 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); @@ -47,6 +79,10 @@ X11Compositor::X11Compositor(QObject *parent) m_framesToTestForSafety = qEnvironmentVariableIntValue("KWIN_MAX_FRAMES_TESTED"); } + 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"); @@ -64,6 +100,7 @@ X11Compositor::~X11Compositor() m_openGLFreezeProtectionThread->wait(); } stop(); // this can't be called in the destructor of Compositor + destroyCompositorSelection(); } X11SyncManager *X11Compositor::syncManager() const @@ -116,6 +153,90 @@ void X11Compositor::resume(X11Compositor::SuspendReason reason) 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 (GLPlatform::instance()->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 (!hasGLVersion(2, 0)) { + qCDebug(KWIN_CORE) << "OpenGL 2.0 is not supported"; + return false; + } + + m_scene = std::make_unique(backend.get()); + m_backend = std::move(backend); + + // set strict binding + if (options->isGlStrictBindingFollowsDriver()) { + options->setGlStrictBinding(!GLPlatform::instance()->supports(GLFeature::LooseBinding)); + } + + qCDebug(KWIN_CORE) << "OpenGL compositing has been successfully initialized"; + return true; +} + void X11Compositor::start() { if (m_suspended) { @@ -132,20 +253,156 @@ void X11Compositor::start() qCWarning(KWIN_CORE) << "Compositing is not possible"; return; } - if (!Compositor::setupStart()) { - // Internal setup failed, abort. + + 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. + QVector 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(); + 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()); - startupWithWorkspace(); + + Q_ASSERT(m_scene); + m_scene->initialize(); + + 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()); + 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(); - Compositor::stop(); + m_scene.reset(); + m_backend.reset(); + kwinApp()->setX11CompositeWindow(XCB_WINDOW_NONE); + + m_state = State::Off; + Q_EMIT compositingToggled(false); } void X11Compositor::composite(RenderLoop *renderLoop) @@ -357,4 +614,5 @@ void X11Compositor::createOpenGLSafePoint(OpenGLSafePoint safePoint) } // namespace KWin +#include "compositor_x11.moc" #include "moc_compositor_x11.cpp" diff --git a/src/compositor_x11.h b/src/compositor_x11.h index 60c49d4ddf..ccfb483b20 100644 --- a/src/compositor_x11.h +++ b/src/compositor_x11.h @@ -15,6 +15,7 @@ namespace KWin { +class X11CompositorSelectionOwner; class X11SyncManager; class X11Window; @@ -75,6 +76,23 @@ public: */ void resume(SuspendReason reason); + enum class OpenGLSafePoint { + PreInit, + PostInit, + PreFrame, + PostFrame, + PostLastGuardedFrame + }; + /** + * This method is invoked before and after creating the OpenGL rendering Scene. + * An implementing Platform can use it to detect crashes triggered by the OpenGL implementation. + * This can be used for openGLCompositingIsBroken. + * + * The default implementation does nothing. + * @see openGLCompositingIsBroken. + */ + void createOpenGLSafePoint(OpenGLSafePoint safePoint); + void inhibit(Window *window) override; void uninhibit(Window *window) override; @@ -83,7 +101,6 @@ public: bool compositingPossible() const override; QString compositingNotPossibleReason() const override; bool openGLCompositingIsBroken() const override; - void createOpenGLSafePoint(OpenGLSafePoint safePoint) override; static X11Compositor *self(); @@ -95,9 +112,16 @@ protected: private: explicit X11Compositor(QObject *parent); + bool attemptOpenGLCompositing(); + + void releaseCompositorSelection(); + void destroyCompositorSelection(); + std::unique_ptr m_openGLFreezeProtectionThread; std::unique_ptr m_openGLFreezeProtection; std::unique_ptr m_syncManager; + std::unique_ptr m_selectionOwner; + QTimer m_releaseSelectionTimer; /** * Whether the Compositor is currently suspended, 8 bits encoding the reason */