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.
wilder/Plasma/6.2
Vlad Zahorodnii 3 years ago
parent 6029a68433
commit fc148cb668
  1. 471
      src/compositor.cpp
  2. 42
      src/compositor.h
  3. 327
      src/compositor_wayland.cpp
  4. 9
      src/compositor_wayland.h
  5. 268
      src/compositor_x11.cpp
  6. 26
      src/compositor_x11.h

@ -11,38 +11,23 @@
#include <config-kwin.h>
#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 <KLocalizedString>
#if KWIN_BUILD_NOTIFICATIONS
#include <KNotification>
#endif
#include <KSelectionOwner>
#include <QQuickWindow>
#include <xcb/composite.h>
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<OpenGLBackend> 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<WorkspaceSceneOpenGL>(backend.get());
m_cursorScene = std::make_unique<CursorScene>(std::make_unique<ItemRendererOpenGL>());
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<QPainterBackend> backend(kwinApp()->outputBackend()->createQPainterBackend());
if (!backend || backend->isFailed()) {
return false;
}
m_scene = std::make_unique<WorkspaceSceneQPainter>(backend.get());
m_cursorScene = std::make_unique<CursorScene>(std::make_unique<ItemRendererQPainter>());
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<CompositorSelectionOwner>("_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<CompositingType> availableCompositors = kwinApp()->outputBackend()->supportedCompositors();
QVector<CompositingType> 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<Output *> outputs = workspace()->outputs();
if (kwinApp()->operationMode() == Application::OperationModeX11) {
auto workspaceLayer = new RenderLayer(outputs.constFirst()->renderLoop());
workspaceLayer->setDelegate(std::make_unique<SceneDelegate>(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<SceneDelegate>(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<CursorDelegateOpenGL>(output));
} else {
cursorLayer->setDelegate(std::make_unique<CursorDelegateQPainter>(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<SceneDelegate>(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<OpenGLBackend *>(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"

@ -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<CompositorSelectionOwner> m_selectionOwner;
QTimer m_releaseSelectionTimer;
QList<xcb_atom_t> m_unusedSupportProperties;
QTimer m_unusedSupportPropertyTimer;
std::unique_ptr<WorkspaceScene> m_scene;
std::unique_ptr<CursorScene> m_cursorScene;
std::unique_ptr<RenderBackend> m_backend;
QHash<RenderLoop *, RenderLayer *> m_superlayers;
CompositingType m_selectedCompositor = NoCompositing;
};
} // namespace KWin

@ -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 <QQuickWindow>
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<OpenGLBackend> 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<WorkspaceSceneOpenGL>(backend.get());
m_cursorScene = std::make_unique<CursorScene>(std::make_unique<ItemRendererOpenGL>());
m_backend = std::move(backend);
qCDebug(KWIN_CORE) << "OpenGL compositing has been successfully initialized";
return true;
}
bool WaylandCompositor::attemptQPainterCompositing()
{
std::unique_ptr<QPainterBackend> backend(kwinApp()->outputBackend()->createQPainterBackend());
if (!backend || backend->isFailed()) {
return false;
}
m_scene = std::make_unique<WorkspaceSceneQPainter>(backend.get());
m_cursorScene = std::make_unique<CursorScene>(std::make_unique<ItemRendererQPainter>());
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<CompositingType> availableCompositors = kwinApp()->outputBackend()->supportedCompositors();
QVector<CompositingType> 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<Output *> 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<OpenGLBackend *>(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<SceneDelegate>(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<CursorDelegateOpenGL>(output));
} else {
cursorLayer->setDelegate(std::make_unique<CursorDelegateQPainter>(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<SceneDelegate>(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

@ -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

@ -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 <KCrash>
#include <KGlobalAccel>
#include <KLocalizedString>
#include <KSelectionOwner>
#include <QAction>
#include <QOpenGLContext>
@ -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<OpenGLBackend> 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<WorkspaceSceneOpenGL>(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<X11CompositorSelectionOwner>("_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<CompositingType> 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<SceneDelegate>(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<OpenGLBackend *>(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"

@ -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<QThread> m_openGLFreezeProtectionThread;
std::unique_ptr<QTimer> m_openGLFreezeProtection;
std::unique_ptr<X11SyncManager> m_syncManager;
std::unique_ptr<X11CompositorSelectionOwner> m_selectionOwner;
QTimer m_releaseSelectionTimer;
/**
* Whether the Compositor is currently suspended, 8 bits encoding the reason
*/

Loading…
Cancel
Save