634e032201
Compositor::setupStart() already calls initializeX11(). This fixes BadAccess warning on startup. CCBUG: 455167
1007 lines
30 KiB
C++
1007 lines
30 KiB
C++
/*
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
#include "composite.h"
|
|
|
|
#include <config-kwin.h>
|
|
|
|
#include "cursordelegate_opengl.h"
|
|
#include "cursordelegate_qpainter.h"
|
|
#include "dbusinterface.h"
|
|
#include "decorations/decoratedclient.h"
|
|
#include "deleted.h"
|
|
#include "effects.h"
|
|
#include "ftrace.h"
|
|
#include "internalwindow.h"
|
|
#include "openglbackend.h"
|
|
#include "output.h"
|
|
#include "outputlayer.h"
|
|
#include "overlaywindow.h"
|
|
#include "platform.h"
|
|
#include "qpainterbackend.h"
|
|
#include "renderlayer.h"
|
|
#include "renderloop.h"
|
|
#include "scene.h"
|
|
#include "scenes/opengl/scene_opengl.h"
|
|
#include "scenes/qpainter/scene_qpainter.h"
|
|
#include "screens.h"
|
|
#include "shadow.h"
|
|
#include "surfaceitem_x11.h"
|
|
#include "unmanaged.h"
|
|
#include "useractions.h"
|
|
#include "utils/common.h"
|
|
#include "utils/xcbutils.h"
|
|
#include "wayland/surface_interface.h"
|
|
#include "wayland_server.h"
|
|
#include "workspace.h"
|
|
#include "x11window.h"
|
|
#include "x11syncmanager.h"
|
|
|
|
#include <kwinglplatform.h>
|
|
#include <kwingltexture.h>
|
|
|
|
#include <KGlobalAccel>
|
|
#include <KLocalizedString>
|
|
#if KWIN_BUILD_NOTIFICATIONS
|
|
#include <KNotification>
|
|
#endif
|
|
#include <KSelectionOwner>
|
|
|
|
#include <QDateTime>
|
|
#include <QFutureWatcher>
|
|
#include <QMenu>
|
|
#include <QOpenGLContext>
|
|
#include <QQuickWindow>
|
|
#include <QTextStream>
|
|
#include <QTimerEvent>
|
|
#include <QtConcurrentRun>
|
|
|
|
#include <xcb/composite.h>
|
|
#include <xcb/damage.h>
|
|
|
|
#include <cstdio>
|
|
|
|
Q_DECLARE_METATYPE(KWin::X11Compositor::SuspendReason)
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
// See main.cpp:
|
|
extern int screen_number;
|
|
|
|
extern bool is_multihead;
|
|
|
|
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, 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.
|
|
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);
|
|
|
|
connect(kwinApp(), &Application::x11ConnectionChanged, this, &Compositor::initializeX11);
|
|
connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &Compositor::cleanupX11);
|
|
|
|
// 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 (kwinApp()->platform()->openGLCompositingIsBroken()) {
|
|
qCWarning(KWIN_CORE) << "KWin has detected that your OpenGL library is unsafe to use";
|
|
return false;
|
|
}
|
|
|
|
kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreInit);
|
|
auto safePointScope = qScopeGuard([]() {
|
|
kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostInit);
|
|
});
|
|
|
|
QScopedPointer<OpenGLBackend> backend(kwinApp()->platform()->createOpenGLBackend());
|
|
if (!backend) {
|
|
return false;
|
|
}
|
|
if (!backend->isFailed()) {
|
|
backend->init();
|
|
}
|
|
if (backend->isFailed()) {
|
|
return false;
|
|
}
|
|
|
|
QScopedPointer<Scene> scene(SceneOpenGL::createScene(backend.data(), this));
|
|
if (!scene || scene->initFailed()) {
|
|
return false;
|
|
}
|
|
|
|
m_backend = backend.take();
|
|
m_scene = scene.take();
|
|
|
|
// set strict binding
|
|
if (options->isGlStrictBindingFollowsDriver()) {
|
|
options->setGlStrictBinding(!GLPlatform::instance()->supports(LooseBinding));
|
|
}
|
|
|
|
qCDebug(KWIN_CORE) << "OpenGL compositing has been successfully initialized";
|
|
return true;
|
|
}
|
|
|
|
bool Compositor::attemptQPainterCompositing()
|
|
{
|
|
QScopedPointer<QPainterBackend> backend(kwinApp()->platform()->createQPainterBackend());
|
|
if (!backend || backend->isFailed()) {
|
|
return false;
|
|
}
|
|
|
|
QScopedPointer<Scene> scene(SceneQPainter::createScene(backend.data(), this));
|
|
if (!scene || scene->initFailed()) {
|
|
return false;
|
|
}
|
|
|
|
m_backend = backend.take();
|
|
m_scene = scene.take();
|
|
|
|
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;
|
|
|
|
options->reloadCompositingSettings(true);
|
|
|
|
initializeX11();
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
Q_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";
|
|
}
|
|
|
|
for (auto type : qAsConst(supportedCompositors)) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!m_backend) {
|
|
m_state = State::Off;
|
|
|
|
if (auto *con = kwinApp()->x11Connection()) {
|
|
xcb_composite_unredirect_subwindows(con, kwinApp()->x11RootWindow(),
|
|
XCB_COMPOSITE_REDIRECT_MANUAL);
|
|
}
|
|
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;
|
|
}
|
|
|
|
kwinApp()->platform()->setSelectedCompositor(m_backend->compositingType());
|
|
|
|
if (!Workspace::self() && m_backend && m_backend->compositingType() == QPainterCompositing) {
|
|
// Force Software QtQuick on first startup with QPainter.
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);
|
|
#else
|
|
QQuickWindow::setGraphicsApi(QSGRendererInterface::Software);
|
|
#endif
|
|
}
|
|
|
|
Q_EMIT sceneCreated();
|
|
|
|
return true;
|
|
}
|
|
|
|
void Compositor::initializeX11()
|
|
{
|
|
xcb_connection_t *connection = kwinApp()->x11Connection();
|
|
if (!connection) {
|
|
return;
|
|
}
|
|
|
|
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->owning()) {
|
|
// Force claim ownership.
|
|
m_selectionOwner->claim(true);
|
|
m_selectionOwner->setOwning(true);
|
|
}
|
|
|
|
xcb_composite_redirect_subwindows(connection, kwinApp()->x11RootWindow(),
|
|
XCB_COMPOSITE_REDIRECT_MANUAL);
|
|
}
|
|
|
|
void Compositor::cleanupX11()
|
|
{
|
|
delete m_selectionOwner;
|
|
m_selectionOwner = nullptr;
|
|
}
|
|
|
|
void Compositor::startupWithWorkspace()
|
|
{
|
|
Q_ASSERT(m_scene);
|
|
m_scene->initialize();
|
|
|
|
const QVector<Output *> outputs = kwinApp()->platform()->enabledOutputs();
|
|
if (kwinApp()->operationMode() == Application::OperationModeX11) {
|
|
auto workspaceLayer = new RenderLayer(outputs.constFirst()->renderLoop());
|
|
workspaceLayer->setDelegate(new SceneDelegate(m_scene));
|
|
workspaceLayer->setGeometry(workspace()->geometry());
|
|
connect(workspace(), &Workspace::geometryChanged, workspaceLayer, [workspaceLayer]() {
|
|
workspaceLayer->setGeometry(workspace()->geometry());
|
|
});
|
|
addSuperLayer(workspaceLayer);
|
|
} else {
|
|
for (Output *output : outputs) {
|
|
addOutput(output);
|
|
}
|
|
connect(kwinApp()->platform(), &Platform::outputEnabled, this, &Compositor::addOutput);
|
|
connect(kwinApp()->platform(), &Platform::outputDisabled, this, &Compositor::removeOutput);
|
|
}
|
|
|
|
m_state = State::On;
|
|
|
|
for (X11Window *window : Workspace::self()->clientList()) {
|
|
window->setupCompositing();
|
|
}
|
|
for (Unmanaged *window : Workspace::self()->unmanagedList()) {
|
|
window->setupCompositing();
|
|
}
|
|
for (InternalWindow *window : workspace()->internalWindows()) {
|
|
window->setupCompositing();
|
|
}
|
|
|
|
if (auto *server = waylandServer()) {
|
|
const auto windows = server->windows();
|
|
for (Window *window : windows) {
|
|
window->setupCompositing();
|
|
}
|
|
}
|
|
|
|
// Sets also the 'effects' pointer.
|
|
kwinApp()->platform()->createEffectsHandler(this, m_scene);
|
|
|
|
Q_EMIT compositingToggled(true);
|
|
|
|
if (m_releaseSelectionTimer.isActive()) {
|
|
m_releaseSelectionTimer.stop();
|
|
}
|
|
}
|
|
|
|
Output *Compositor::findOutput(RenderLoop *loop) const
|
|
{
|
|
const auto outputs = kwinApp()->platform()->enabledOutputs();
|
|
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(new SceneDelegate(m_scene, 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(new CursorDelegateOpenGL());
|
|
} else {
|
|
cursorLayer->setDelegate(new CursorDelegateQPainter());
|
|
}
|
|
cursorLayer->setParent(workspaceLayer);
|
|
cursorLayer->setSuperlayer(workspaceLayer);
|
|
|
|
auto updateCursorLayer = [output, cursorLayer]() {
|
|
const Cursor *cursor = Cursors::self()->currentCursor();
|
|
cursorLayer->setVisible(cursor->isOnOutput(output) && output->usesSoftwareCursor());
|
|
cursorLayer->setGeometry(output->mapFromGlobal(cursor->geometry()));
|
|
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, updateCursorLayer);
|
|
|
|
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::scheduleRepaint()
|
|
{
|
|
for (auto it = m_superlayers.constBegin(); it != m_superlayers.constEnd(); ++it) {
|
|
it.key()->scheduleRepaint();
|
|
}
|
|
}
|
|
|
|
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()) {
|
|
for (X11Window *window : Workspace::self()->clientList()) {
|
|
window->finishCompositing();
|
|
}
|
|
for (Unmanaged *window : Workspace::self()->unmanagedList()) {
|
|
window->finishCompositing();
|
|
}
|
|
for (InternalWindow *window : workspace()->internalWindows()) {
|
|
window->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()) {
|
|
const QList<Window *> toFinishCompositing = waylandServer()->windows();
|
|
for (Window *window : toFinishCompositing) {
|
|
window->finishCompositing();
|
|
}
|
|
}
|
|
|
|
const auto superlayers = m_superlayers;
|
|
for (auto it = superlayers.begin(); it != superlayers.end(); ++it) {
|
|
removeSuperLayer(*it);
|
|
}
|
|
|
|
disconnect(kwinApp()->platform(), &Platform::outputEnabled, this, &Compositor::addOutput);
|
|
disconnect(kwinApp()->platform(), &Platform::outputDisabled, this, &Compositor::removeOutput);
|
|
|
|
delete m_scene;
|
|
m_scene = nullptr;
|
|
|
|
delete m_backend;
|
|
m_backend = nullptr;
|
|
|
|
m_state = State::Off;
|
|
Q_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();
|
|
}
|
|
|
|
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::handleFrameRequested(RenderLoop *renderLoop)
|
|
{
|
|
composite(renderLoop);
|
|
}
|
|
|
|
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 *outputLayer = m_backend->primaryLayer(output);
|
|
fTraceDuration("Paint (", output->name(), ")");
|
|
|
|
RenderLayer *superLayer = m_superlayers[renderLoop];
|
|
prePaintPass(superLayer);
|
|
superLayer->setOutputLayer(outputLayer);
|
|
|
|
SurfaceItem *scanoutCandidate = superLayer->delegate()->scanoutCandidate();
|
|
renderLoop->setFullscreenSurface(scanoutCandidate);
|
|
|
|
renderLoop->beginFrame();
|
|
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 = outputLayer->scanout(scanoutCandidate);
|
|
}
|
|
}
|
|
|
|
if (!directScanout) {
|
|
QRegion surfaceDamage = outputLayer->repaints();
|
|
outputLayer->resetRepaints();
|
|
preparePaintPass(superLayer, &surfaceDamage);
|
|
|
|
OutputLayerBeginFrameInfo beginInfo = outputLayer->beginFrame();
|
|
beginInfo.renderTarget.setDevicePixelRatio(output->scale());
|
|
|
|
const QRegion bufferDamage = surfaceDamage.united(beginInfo.repaint).intersected(superLayer->rect());
|
|
outputLayer->aboutToStartPainting(bufferDamage);
|
|
|
|
paintPass(superLayer, &beginInfo.renderTarget, bufferDamage);
|
|
outputLayer->endFrame(bufferDamage, surfaceDamage);
|
|
}
|
|
renderLoop->endFrame();
|
|
|
|
postPaintPass(superLayer);
|
|
|
|
m_backend->present(output);
|
|
|
|
// 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<std::chrono::milliseconds>(output->renderLoop()->lastPresentationTimestamp());
|
|
|
|
if (!Cursors::self()->isCursorHidden()) {
|
|
Cursor *cursor = Cursors::self()->currentCursor();
|
|
if (cursor->geometry().intersects(output->geometry())) {
|
|
cursor->markAsRendered(frameTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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, RenderTarget *target, const QRegion ®ion)
|
|
{
|
|
layer->delegate()->paint(target, region);
|
|
|
|
const auto sublayers = layer->sublayers();
|
|
for (RenderLayer *sublayer : sublayers) {
|
|
if (sublayer->isVisible()) {
|
|
paintPass(sublayer, target, region);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Compositor::isActive()
|
|
{
|
|
return m_state == State::On;
|
|
}
|
|
|
|
WaylandCompositor::WaylandCompositor(QObject *parent)
|
|
: Compositor(parent)
|
|
{
|
|
connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed,
|
|
this, &WaylandCompositor::destroyCompositorSelection);
|
|
}
|
|
|
|
WaylandCompositor::~WaylandCompositor()
|
|
{
|
|
Q_EMIT aboutToDestroy();
|
|
stop(); // this can't be called in the destructor of Compositor
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (Workspace::self()) {
|
|
startupWithWorkspace();
|
|
} else {
|
|
connect(kwinApp(), &Application::workspaceCreated,
|
|
this, &WaylandCompositor::startupWithWorkspace);
|
|
}
|
|
}
|
|
|
|
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");
|
|
}
|
|
}
|
|
|
|
X11Compositor::~X11Compositor()
|
|
{
|
|
Q_EMIT aboutToDestroy();
|
|
stop(); // this can't be called in the destructor of Compositor
|
|
}
|
|
|
|
X11SyncManager *X11Compositor::syncManager() const
|
|
{
|
|
return m_syncManager.data();
|
|
}
|
|
|
|
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<QAction *>(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.<br/>"
|
|
"You can resume using the '%1' shortcut.",
|
|
shortcuts.first().toString(QKeySequence::NativeText));
|
|
#if KWIN_BUILD_NOTIFICATIONS
|
|
KNotification::event(QStringLiteral("compositingsuspendeddbus"), message);
|
|
#endif
|
|
}
|
|
}
|
|
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");
|
|
}
|
|
qCInfo(KWIN_CORE) << "Compositing is suspended, reason:" << reasons;
|
|
return;
|
|
} else if (!kwinApp()->platform()->compositingPossible()) {
|
|
qCWarning(KWIN_CORE) << "Compositing is not possible";
|
|
return;
|
|
}
|
|
if (!Compositor::setupStart()) {
|
|
// Internal setup failed, abort.
|
|
return;
|
|
}
|
|
startupWithWorkspace();
|
|
m_syncManager.reset(X11SyncManager::create());
|
|
}
|
|
|
|
void X11Compositor::stop()
|
|
{
|
|
m_syncManager.reset();
|
|
Compositor::stop();
|
|
}
|
|
|
|
void X11Compositor::composite(RenderLoop *renderLoop)
|
|
{
|
|
if (backend()->overlayWindow() && !isOverlayWindowVisible()) {
|
|
// Return since nothing is visible.
|
|
return;
|
|
}
|
|
|
|
QList<Window *> windows = workspace()->stackingOrder();
|
|
QList<SurfaceItemX11 *> dirtyItems;
|
|
|
|
// Reset the damage state of each window and fetch the damage region
|
|
// without waiting for a reply
|
|
for (Window *window : qAsConst(windows)) {
|
|
SurfaceItemX11 *surfaceItem = static_cast<SurfaceItemX11 *>(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 : qAsConst(dirtyItems)) {
|
|
item->waitForDamage();
|
|
}
|
|
|
|
if (m_framesToTestForSafety > 0 && (backend()->compositingType() & OpenGLCompositing)) {
|
|
kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreFrame);
|
|
}
|
|
|
|
Compositor::composite(renderLoop);
|
|
|
|
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) {
|
|
kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostFrame);
|
|
}
|
|
m_framesToTestForSafety--;
|
|
if (m_framesToTestForSafety == 0 && (backend()->compositingType() & OpenGLCompositing)) {
|
|
kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostLastGuardedFrame);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool X11Compositor::checkForOverlayWindow(WId w) const
|
|
{
|
|
if (!backend()) {
|
|
// No backend, so it cannot be the overlay window.
|
|
return false;
|
|
}
|
|
if (!backend()->overlayWindow()) {
|
|
// No overlay window, it cannot be the overlay.
|
|
return false;
|
|
}
|
|
// Compare the window ID's.
|
|
return w == backend()->overlayWindow()->window();
|
|
}
|
|
|
|
bool X11Compositor::isOverlayWindowVisible() const
|
|
{
|
|
if (!backend()) {
|
|
return false;
|
|
}
|
|
if (!backend()->overlayWindow()) {
|
|
return false;
|
|
}
|
|
return backend()->overlayWindow()->isVisible();
|
|
}
|
|
|
|
void X11Compositor::updateClientCompositeBlocking(X11Window *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<X11Compositor *>(Compositor::self());
|
|
}
|
|
|
|
}
|
|
|
|
// included for CompositorSelectionOwner
|
|
#include "composite.moc"
|