Commit 7b13393b authored by Roman Gilg's avatar Roman Gilg

[platforms/wayland] Multi output support

Summary:
This patch rewrites large parts of the Wayland platform plugin, in order to
facilitate the testing of multi output behavior in nested KWin sessions.

For that a new class WaylandOutput is introduced, which is based on
AbstractOutput and by that shares functionality with our virtual and DRM
platform plugins.

The EGL/GBM and QPainter backends have been remodelled after the DRM one,
sharing similiarities there as well now.

Pointer grabbing has been rewritten to support multiple outputs, now using
pointer locking instead of confining and drawing in this case onto a sub-
surface, which get dynamically recreated in between the different output
surfaces while the cursor is being moved.

Window resizing is possible if host supports xdg-shell, but currently the
mode size does not yet fill the new window size.

The number of outputs can be set by command line argument `--output-count`,
scaling is also supported by setting the argument `--scale`.

Further steps could be:
* Enabling automatic fill of resized windows via Wayland mode change
* Multiple diverging initial sizes and scale factors for mulitple outputs

**Watch it in action:** https://youtu.be/FYItn1jvkbI

Test Plan: Tested it in live session.

Reviewers: #kwin, davidedmundson

Reviewed By: #kwin, davidedmundson

Subscribers: davidedmundson, zzag, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D18465
parent 0ab907c7
......@@ -2,6 +2,7 @@ set(WAYLAND_BACKEND_SOURCES
logging.cpp
scene_qpainter_wayland_backend.cpp
wayland_backend.cpp
wayland_output.cpp
)
if(HAVE_WAYLAND_EGL)
......
......@@ -2,7 +2,8 @@
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
Copyright 2013 Martin Gräßlin <mgraesslin@kde.org>
Copyright 2019 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -30,9 +31,34 @@ struct wl_shm;
namespace KWin
{
namespace Wayland {
class WaylandBackend;
}
namespace Wayland
{
class WaylandBackend;
class WaylandOutput;
class EglWaylandBackend;
class EglWaylandOutput : public QObject
{
Q_OBJECT
public:
EglWaylandOutput(WaylandOutput *output, QObject *parent = nullptr);
~EglWaylandOutput() = default;
bool init(EglWaylandBackend *backend);
void updateSize(const QSize &size);
private:
WaylandOutput *m_waylandOutput;
wl_egl_window *m_overlay = nullptr;
EGLSurface m_eglSurface = EGL_NO_SURFACE;
int m_bufferAge = 0;
/**
* @brief The damage history for the past 10 frames.
*/
QVector<QRegion> m_damageHistory;
friend class EglWaylandBackend;
};
/**
* @brief OpenGL Backend using Egl on a Wayland surface.
......@@ -50,29 +76,38 @@ class EglWaylandBackend : public AbstractEglBackend
{
Q_OBJECT
public:
EglWaylandBackend(Wayland::WaylandBackend *b);
EglWaylandBackend(WaylandBackend *b);
virtual ~EglWaylandBackend();
virtual void screenGeometryChanged(const QSize &size);
virtual SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override;
virtual QRegion prepareRenderingFrame();
virtual void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion);
void screenGeometryChanged(const QSize &size) override;
SceneOpenGLTexturePrivate *createBackendTexture(SceneOpenGLTexture *texture) override;
QRegion prepareRenderingFrame() override;
QRegion prepareRenderingForScreen(int screenId) override;
void endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) override;
void endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) override;
virtual bool usesOverlayWindow() const override;
bool perScreenRendering() const override;
void init() override;
protected:
virtual void present();
private Q_SLOTS:
void overlaySizeChanged(const QSize &size);
bool havePlatformBase() const {
return m_havePlatformBase;
}
private:
bool initializeEgl();
bool initBufferConfigs();
bool initRenderingContext();
bool makeContextCurrent();
int m_bufferAge;
Wayland::WaylandBackend *m_wayland;
wl_egl_window *m_overlay;
bool createEglWaylandOutput(WaylandOutput *output);
void cleanupSurfaces() override;
void cleanupOutput(EglWaylandOutput *output);
bool makeContextCurrent(EglWaylandOutput *output);
void present() override;
void presentOnSurface(EglWaylandOutput *output);
WaylandBackend *m_backend;
QVector<EglWaylandOutput*> m_outputs;
bool m_havePlatformBase;
friend class EglWaylandTexture;
};
......@@ -90,6 +125,7 @@ private:
EglWaylandTexture(SceneOpenGLTexture *texture, EglWaylandBackend *backend);
};
} // namespace
}
}
#endif // KWIN_EGL_ON_X_BACKEND_H
#endif
......@@ -2,7 +2,8 @@
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2013, 2015 Martin Gräßlin <mgraesslin@kde.org>
Copyright 2019 Roman Gilg <subdiff@gmail.com>
Copyright 2013, 2015 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -18,57 +19,60 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "scene_qpainter_wayland_backend.h"
#include "wayland_backend.h"
#include "wayland_output.h"
#include "composite.h"
#include "logging.h"
#include "wayland_backend.h"
#include <KWayland/Client/buffer.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/surface.h>
namespace KWin
{
namespace Wayland
{
WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b)
: QPainterBackend()
, m_backend(b)
, m_needsFullRepaint(true)
, m_backBuffer(QImage(QSize(), QImage::Format_RGB32))
, m_buffer()
WaylandQPainterOutput::WaylandQPainterOutput(WaylandOutput *output, QObject *parent)
: QObject(parent)
, m_waylandOutput(output)
{
connect(b->shmPool(), SIGNAL(poolResized()), SLOT(remapBuffer()));
connect(b, &Wayland::WaylandBackend::shellSurfaceSizeChanged,
this, &WaylandQPainterBackend::screenGeometryChanged);
connect(b->surface(), &KWayland::Client::Surface::frameRendered,
Compositor::self(), &Compositor::bufferSwapComplete);
}
WaylandQPainterBackend::~WaylandQPainterBackend()
WaylandQPainterOutput::~WaylandQPainterOutput()
{
if (m_buffer) {
m_buffer.toStrongRef()->setUsed(false);
}
}
bool WaylandQPainterBackend::usesOverlayWindow() const
bool WaylandQPainterOutput::init(KWayland::Client::ShmPool *pool)
{
return false;
m_pool = pool;
m_backBuffer = QImage(QSize(), QImage::Format_RGB32);
connect(pool, &KWayland::Client::ShmPool::poolResized, this, &WaylandQPainterOutput::remapBuffer);
connect(m_waylandOutput, &WaylandOutput::sizeChanged, this, &WaylandQPainterOutput::updateSize);
return true;
}
void WaylandQPainterBackend::present(int mask, const QRegion &damage)
void WaylandQPainterOutput::remapBuffer()
{
Q_UNUSED(mask)
if (m_backBuffer.isNull()) {
if (!m_buffer) {
return;
}
Compositor::self()->aboutToSwapBuffers();
m_needsFullRepaint = false;
auto s = m_backend->surface();
s->attachBuffer(m_buffer);
s->damage(damage);
s->commit();
auto b = m_buffer.toStrongRef();
if (!b->isUsed()){
return;
}
const QSize size = m_backBuffer.size();
m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_RGB32);
qCDebug(KWIN_WAYLAND_BACKEND) << "Remapped back buffer of surface" << m_waylandOutput->surface();
}
void WaylandQPainterBackend::screenGeometryChanged(const QSize &size)
void WaylandQPainterOutput::updateSize(const QSize &size)
{
Q_UNUSED(size)
if (!m_buffer) {
......@@ -78,12 +82,15 @@ void WaylandQPainterBackend::screenGeometryChanged(const QSize &size)
m_buffer.clear();
}
QImage *WaylandQPainterBackend::buffer()
void WaylandQPainterOutput::present(const QRegion &damage)
{
return &m_backBuffer;
auto s = m_waylandOutput->surface();
s->attachBuffer(m_buffer);
s->damage(damage);
s->commit();
}
void WaylandQPainterBackend::prepareRenderingFrame()
void WaylandQPainterOutput::prepareRenderingFrame()
{
if (m_buffer) {
auto b = m_buffer.toStrongRef();
......@@ -97,33 +104,101 @@ void WaylandQPainterBackend::prepareRenderingFrame()
}
}
m_buffer.clear();
const QSize size(m_backend->shellSurfaceSize());
m_buffer = m_backend->shmPool()->getBuffer(size, size.width() * 4);
const QSize size(m_waylandOutput->geometry().size());
m_buffer = m_pool->getBuffer(size, size.width() * 4);
if (!m_buffer) {
qCDebug(KWIN_WAYLAND_BACKEND) << "Did not get a new Buffer from Shm Pool";
m_backBuffer = QImage();
return;
}
auto b = m_buffer.toStrongRef();
b->setUsed(true);
m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_RGB32);
m_backBuffer.fill(Qt::transparent);
m_needsFullRepaint = true;
qCDebug(KWIN_WAYLAND_BACKEND) << "Created a new back buffer";
// qCDebug(KWIN_WAYLAND_BACKEND) << "Created a new back buffer for output surface" << m_waylandOutput->surface();
}
void WaylandQPainterBackend::remapBuffer()
WaylandQPainterBackend::WaylandQPainterBackend(Wayland::WaylandBackend *b)
: QPainterBackend()
, m_backend(b)
, m_needsFullRepaint(true)
{
if (!m_buffer) {
return;
const auto waylandOutputs = m_backend->waylandOutputs();
for (auto *output: waylandOutputs) {
createOutput(output);
}
auto b = m_buffer.toStrongRef();
if (!b->isUsed()){
return;
connect(m_backend, &WaylandBackend::outputAdded, this, &WaylandQPainterBackend::createOutput);
connect(m_backend, &WaylandBackend::outputRemoved, this,
[this] (WaylandOutput *waylandOutput) {
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
[waylandOutput] (WaylandQPainterOutput *output) {
return output->m_waylandOutput == waylandOutput;
}
);
if (it == m_outputs.end()) {
return;
}
delete *it;
m_outputs.erase(it);
}
);
}
WaylandQPainterBackend::~WaylandQPainterBackend()
{
}
bool WaylandQPainterBackend::usesOverlayWindow() const
{
return false;
}
bool WaylandQPainterBackend::perScreenRendering() const
{
return true;
}
void WaylandQPainterBackend::createOutput(WaylandOutput *waylandOutput)
{
auto *output = new WaylandQPainterOutput(waylandOutput, this);
output->init(m_backend->shmPool());
m_outputs << output;
}
void WaylandQPainterBackend::present(int mask, const QRegion &damage)
{
Q_UNUSED(mask)
Compositor::self()->aboutToSwapBuffers();
m_needsFullRepaint = false;
for (auto *output : m_outputs) {
output->present(damage);
}
const QSize size = m_backBuffer.size();
m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_RGB32);
qCDebug(KWIN_WAYLAND_BACKEND) << "Remapped our back buffer";
}
QImage *WaylandQPainterBackend::buffer()
{
return bufferForScreen(0);
}
QImage *WaylandQPainterBackend::bufferForScreen(int screenId)
{
auto *output = m_outputs[screenId];
return &output->m_backBuffer;
}
void WaylandQPainterBackend::prepareRenderingFrame()
{
for (auto *output : m_outputs) {
output->prepareRenderingFrame();
}
m_needsFullRepaint = true;
}
bool WaylandQPainterBackend::needsFullRepaint() const
......@@ -132,3 +207,4 @@ bool WaylandQPainterBackend::needsFullRepaint() const
}
}
}
......@@ -2,7 +2,8 @@
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2013, 2015 Martin Gräßlin <mgraesslin@kde.org>
Copyright 2019 Roman Gilg <subdiff@gmail.com>
Copyright 2013, 2015 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -30,6 +31,7 @@ namespace KWayland
{
namespace Client
{
class ShmPool;
class Buffer;
}
}
......@@ -39,30 +41,62 @@ namespace KWin
namespace Wayland
{
class WaylandBackend;
}
class WaylandOutput;
class WaylandQPainterBackend;
class WaylandQPainterOutput : public QObject
{
Q_OBJECT
public:
WaylandQPainterOutput(WaylandOutput *output, QObject *parent = nullptr);
~WaylandQPainterOutput();
bool init(KWayland::Client::ShmPool *pool);
void updateSize(const QSize &size);
void remapBuffer();
void prepareRenderingFrame();
void present(const QRegion &damage);
private:
WaylandOutput *m_waylandOutput;
KWayland::Client::ShmPool *m_pool;
QWeakPointer<KWayland::Client::Buffer> m_buffer;
QImage m_backBuffer;
friend class WaylandQPainterBackend;
};
class WaylandQPainterBackend : public QObject, public QPainterBackend
{
Q_OBJECT
public:
explicit WaylandQPainterBackend(Wayland::WaylandBackend *b);
explicit WaylandQPainterBackend(WaylandBackend *b);
virtual ~WaylandQPainterBackend();
virtual void present(int mask, const QRegion& damage) override;
virtual bool usesOverlayWindow() const override;
virtual void screenGeometryChanged(const QSize &size) override;
virtual QImage *buffer() override;
QImage *bufferForScreen(int screenId) override;
virtual void present(int mask, const QRegion& damage) override;
virtual void prepareRenderingFrame() override;
virtual bool needsFullRepaint() const override;
private Q_SLOTS:
void remapBuffer();
bool perScreenRendering() const override;
private:
Wayland::WaylandBackend *m_backend;
void createOutput(WaylandOutput *waylandOutput);
void frameRendered();
WaylandBackend *m_backend;
bool m_needsFullRepaint;
QImage m_backBuffer;
QWeakPointer<KWayland::Client::Buffer> m_buffer;
QVector<WaylandQPainterOutput*> m_outputs;
};
}
}
#endif
......@@ -2,7 +2,8 @@
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
Copyright 2019 Roman Gilg <subdiff@gmail.com>
Copyright 2013 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -43,7 +44,6 @@ namespace Client
class Buffer;
class ShmPool;
class Compositor;
class ConfinedPointer;
class ConnectionThread;
class EventQueue;
class Keyboard;
......@@ -53,13 +53,15 @@ class PointerGestures;
class PointerSwipeGesture;
class PointerPinchGesture;
class Registry;
class RelativePointer;
class RelativePointerManager;
class Seat;
class Shell;
class ShellSurface;
class SubCompositor;
class SubSurface;
class Surface;
class Touch;
class XdgShell;
class XdgShellSurface;
}
}
......@@ -72,6 +74,60 @@ namespace Wayland
class WaylandBackend;
class WaylandSeat;
class WaylandOutput;
class WaylandCursor : public QObject
{
Q_OBJECT
public:
explicit WaylandCursor(WaylandBackend *backend);
virtual ~WaylandCursor();
virtual void init();
virtual void move(const QPointF &globalPosition) {
Q_UNUSED(globalPosition)
}
void installImage();
protected:
void resetSurface();
virtual void doInstallImage(wl_buffer *image, const QSize &size);
void drawSurface(wl_buffer *image, const QSize &size);
KWayland::Client::Surface *surface() const {
return m_surface;
}
WaylandBackend *backend() const {
return m_backend;
}
private:
WaylandBackend *m_backend;
KWayland::Client::Pointer *m_pointer;
KWayland::Client::Surface *m_surface = nullptr;
};
class WaylandSubSurfaceCursor : public WaylandCursor
{
Q_OBJECT
public:
explicit WaylandSubSurfaceCursor(WaylandBackend *backend);
virtual ~WaylandSubSurfaceCursor();
void init() override;
void move(const QPointF &globalPosition) override;
private:
void changeOutput(WaylandOutput *output);
void doInstallImage(wl_buffer *image, const QSize &size) override;
void createSubSurface();
QPointF absoluteToRelativePosition(const QPointF &position);
WaylandOutput *m_output = nullptr;
KWayland::Client::SubSurface *m_subSurface = nullptr;
};
class WaylandSeat : public QObject
{
......@@ -80,13 +136,6 @@ public:
WaylandSeat(wl_seat *seat, WaylandBackend *backend);
virtual ~WaylandSeat();
void installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotspot);
void installCursorImage(const QImage &image, const QPoint &hotspot);
void setInstallCursor(bool install);
bool isInstallCursor() const {
return m_installCursor;
}
KWayland::Client::Pointer *pointer() const {
return m_pointer;
}
......@@ -101,25 +150,26 @@ private:
void destroyKeyboard();
void destroyTouch();
void setupPointerGestures();
KWayland::Client::Seat *m_seat;
KWayland::Client::Pointer *m_pointer;
KWayland::Client::Keyboard *m_keyboard;
KWayland::Client::Touch *m_touch;
KWayland::Client::Surface *m_cursor;
KWayland::Client::PointerGestures *m_gesturesInterface = nullptr;
KWayland::Client::PointerPinchGesture *m_pinchGesture = nullptr;
KWayland::Client::PointerSwipeGesture *m_swipeGesture = nullptr;
uint32_t m_enteredSerial;
WaylandBackend *m_backend;
bool m_installCursor;
};
/**
* @brief Class encapsulating all Wayland data structures needed by the Egl backend.
*
* It creates the connection to the Wayland Compositor, sets up the registry and creates
* the Wayland surface and its shell mapping.
**/
* @brief Class encapsulating all Wayland data structures needed by the Egl backend.
*
* It creates the connection to the Wayland Compositor, sets up the registry and creates
* the Wayland output surfaces and its shell mappings.
*/
class KWIN_EXPORT WaylandBackend : public Platform
{
Q_OBJECT
......@@ -131,51 +181,77 @@ public:
void init() override;
wl_display *display();
KWayland::Client::Compositor *compositor();
KWayland::Client::SubCompositor *subCompositor();
KWayland::Client::ShmPool *shmPool();
KWayland::Client::Surface *surface() const;
QSize shellSurfaceSize() const;
Screens *createScreens(QObject *parent = nullptr) override;
OpenGLBackend *createOpenGLBackend() override;
QPainterBackend *createQPainterBackend() override;
QSize screenSize() const override {
return shellSurfaceSize();
void flush();
WaylandSeat *seat() const {
return m_seat;
}
KWayland::Client::PointerConstraints *pointerConstraints() const {
return m_pointerConstraints;
}
void flush();
void pointerMotionRelativeToOutput(const QPointF &position, quint32 time);
void togglePointerConfinement();
bool supportsPointerLock();
void togglePointerLock();
bool pointerIsLocked();
QVector<CompositingType> supportedCompositors() const override;
void checkBufferSwap();
WaylandOutput* getOutputAt(const QPointF globalPosition);
Outputs outputs() const override;
Outputs enabledOutputs() const override;
QVector<WaylandOutput*> waylandOutputs() const {
return m_outputs;