/* 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.h" #include #include "core/output.h" #include "core/outputbackend.h" #include "core/outputlayer.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 "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 { Compositor *Compositor::s_compositor = nullptr; 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) { connect(options, &Options::configChanged, this, &Compositor::configChanged); connect(options, &Options::animationSpeedChanged, this, &Compositor::configChanged); // 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. QTimer::singleShot(0, this, &Compositor::start); // register DBus new CompositorDBusInterface(this); FTraceLogger::create(); } 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(); for (Output *output : outputs) { if (output->renderLoop() == loop) { return output; } } 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); connect(layer->loop(), &RenderLoop::frameRequested, this, &Compositor::handleFrameRequested); } void Compositor::removeSuperLayer(RenderLayer *layer) { m_superlayers.remove(layer->loop()); disconnect(layer->loop(), &RenderLoop::frameRequested, this, &Compositor::handleFrameRequested); 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); } 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 : std::as_const(m_unusedSupportProperties)) { // remove property from root window xcb_delete_property(con, kwinApp()->x11RootWindow(), atom); } m_unusedSupportProperties.clear(); } } void Compositor::configChanged() { reinitialize(); } void Compositor::reinitialize() { // Restart compositing stop(); start(); } void Compositor::handleFrameRequested(RenderLoop *renderLoop) { composite(renderLoop); } uint Compositor::outputFormat(Output *output) { OutputLayer *primaryLayer = m_backend->primaryLayer(output); return primaryLayer->format(); } void Compositor::composite(RenderLoop *renderLoop) { 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; } Output *output = findOutput(renderLoop); OutputLayer *primaryLayer = m_backend->primaryLayer(output); fTraceDuration("Paint (", output->name(), ")"); RenderLayer *superLayer = m_superlayers[renderLoop]; superLayer->setOutputLayer(primaryLayer); renderLoop->prepareNewFrame(); if (superLayer->needsRepaint()) { renderLoop->beginPaint(); prePaintPass(superLayer); SurfaceItem *scanoutCandidate = superLayer->delegate()->scanoutCandidate(); renderLoop->setFullscreenSurface(scanoutCandidate); output->setContentType(scanoutCandidate ? scanoutCandidate->contentType() : ContentType::None); bool directScanout = false; if (scanoutCandidate) { const auto sublayers = superLayer->sublayers(); const bool scanoutPossible = std::none_of(sublayers.begin(), sublayers.end(), [](RenderLayer *sublayer) { return sublayer->isVisible(); }); if (scanoutPossible && !output->directScanoutInhibited()) { directScanout = primaryLayer->scanout(scanoutCandidate); } } if (!directScanout) { QRegion surfaceDamage = primaryLayer->repaints(); primaryLayer->resetRepaints(); preparePaintPass(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); } } postPaintPass(superLayer); } m_backend->present(output); framePass(superLayer); // TODO: Put it inside the cursor layer once the cursor layer can be backed by a real output layer. if (waylandServer()) { const std::chrono::milliseconds frameTime = std::chrono::duration_cast(output->renderLoop()->lastPresentationTimestamp()); if (!Cursors::self()->isCursorHidden()) { Cursor *cursor = Cursors::self()->currentCursor(); if (cursor->geometry().intersects(output->geometry())) { cursor->markAsRendered(frameTime); } } } } void Compositor::framePass(RenderLayer *layer) { layer->delegate()->frame(); const auto sublayers = layer->sublayers(); for (RenderLayer *sublayer : sublayers) { framePass(sublayer); } } void Compositor::prePaintPass(RenderLayer *layer) { layer->delegate()->prePaint(); const auto sublayers = layer->sublayers(); for (RenderLayer *sublayer : sublayers) { prePaintPass(sublayer); } } void Compositor::postPaintPass(RenderLayer *layer) { layer->delegate()->postPaint(); const auto sublayers = layer->sublayers(); for (RenderLayer *sublayer : sublayers) { postPaintPass(sublayer); } } void Compositor::preparePaintPass(RenderLayer *layer, QRegion *repaint) { // TODO: Cull opaque region. *repaint += layer->mapToGlobal(layer->repaints() + layer->delegate()->repaints()); layer->resetRepaints(); const auto sublayers = layer->sublayers(); for (RenderLayer *sublayer : sublayers) { if (sublayer->isVisible()) { preparePaintPass(sublayer, repaint); } } } void Compositor::paintPass(RenderLayer *layer, const RenderTarget &renderTarget, const QRegion ®ion) { layer->delegate()->paint(renderTarget, region); const auto sublayers = layer->sublayers(); for (RenderLayer *sublayer : sublayers) { if (sublayer->isVisible()) { paintPass(sublayer, renderTarget, region); } } } bool Compositor::isActive() { return m_state == State::On; } bool Compositor::compositingPossible() const { return true; } QString Compositor::compositingNotPossibleReason() const { return QString(); } bool Compositor::openGLCompositingIsBroken() const { return false; } void Compositor::createOpenGLSafePoint(OpenGLSafePoint safePoint) { } } // namespace KWin // included for CompositorSelectionOwner #include "compositor.moc" #include "moc_compositor.cpp"