Port software cursor to RenderLayer

Software cursor has always been a major source of problems. Hopefully,
porting it to RenderLayer will help us with that.

Note that the cursor layer is currently visible only when using software
cursor, however it will be changed once the Compositor can allocate
a real hardware cursor plane.

Currently, software cursor uses graphics-specific APIs (OpenGL and
QPainter) to paint itself. That will be changed in the future when
rendering parts are extracted from the Scene in a reusable helper.
This commit is contained in:
Vlad Zahorodnii 2022-02-05 11:47:00 +02:00
parent 8739258f2f
commit dd6d0b22cc
17 changed files with 313 additions and 124 deletions

View file

@ -36,6 +36,9 @@ target_sources(kwin PRIVATE
client_machine.cpp
composite.cpp
cursor.cpp
cursorview.cpp
cursorview_opengl.cpp
cursorview_qpainter.cpp
dbusinterface.cpp
debug_console.cpp
decorationitem.cpp

View file

@ -8,6 +8,8 @@
*/
#include "composite.h"
#include "abstract_output.h"
#include "cursorview_opengl.h"
#include "cursorview_qpainter.h"
#include "dbusinterface.h"
#include "x11client.h"
#include "decorations/decoratedclient.h"
@ -191,6 +193,7 @@ bool Compositor::attemptOpenGLCompositing()
m_backend = backend.take();
m_scene = scene.take();
m_cursorView = new OpenGLCursorView();
// set strict binding
if (options->isGlStrictBindingFollowsDriver()) {
@ -215,6 +218,7 @@ bool Compositor::attemptQPainterCompositing()
m_backend = backend.take();
m_scene = scene.take();
m_cursorView = new QPainterCursorView();
qCDebug(KWIN_CORE) << "QPainter compositing has been successfully initialized";
return true;
@ -412,12 +416,33 @@ AbstractOutput *Compositor::findOutput(RenderLoop *loop) const
void Compositor::addOutput(AbstractOutput *output)
{
Q_ASSERT(kwinApp()->operationMode() != Application::OperationModeX11);
auto workspaceLayer = new RenderLayer(output->renderLoop());
workspaceLayer->setDelegate(new SceneDelegate(m_scene, output));
workspaceLayer->setGeometry(output->geometry());
connect(output, &AbstractOutput::geometryChanged, this, [output, workspaceLayer]() {
workspaceLayer->setGeometry(output->geometry());
});
auto cursorLayer = new RenderLayer(output->renderLoop());
cursorLayer->setVisible(false);
cursorLayer->setDelegate(new CursorDelegate(output, m_cursorView));
cursorLayer->setParent(workspaceLayer);
cursorLayer->setSuperlayer(workspaceLayer);
auto updateCursorLayer = [output, workspaceLayer, cursorLayer]() {
const Cursor *cursor = Cursors::self()->currentCursor();
cursorLayer->setVisible(cursor->isOnOutput(output) && output->usesSoftwareCursor());
cursorLayer->setGeometry(workspaceLayer->mapFromGlobal(cursor->geometry()));
cursorLayer->addRepaintFull();
};
updateCursorLayer();
connect(output, &AbstractOutput::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);
}
@ -512,6 +537,9 @@ void Compositor::stop()
delete m_scene;
m_scene = nullptr;
delete m_cursorView;
m_cursorView = nullptr;
delete m_backend;
m_backend = nullptr;
@ -646,6 +674,19 @@ void Compositor::composite(RenderLoop *renderLoop)
}
postPaintPass(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<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)

View file

@ -20,6 +20,7 @@ namespace KWin
class AbstractOutput;
class CompositorSelectionOwner;
class CursorView;
class RenderBackend;
class RenderLayer;
class RenderLoop;
@ -148,6 +149,7 @@ private:
QList<xcb_atom_t> m_unusedSupportProperties;
QTimer m_unusedSupportPropertyTimer;
Scene *m_scene = nullptr;
CursorView *m_cursorView = nullptr;
RenderBackend *m_backend = nullptr;
QHash<RenderLoop *, RenderLayer *> m_superlayers;
};

View file

@ -9,6 +9,7 @@
#include "cursor.h"
// kwin
#include "abstract_output.h"
#include "input.h"
#include "keyboard_input.h"
#include "main.h"
@ -167,6 +168,17 @@ void Cursor::slotKGlobalSettingsNotifyChange(int type, int arg)
}
}
bool Cursor::isOnOutput(AbstractOutput *output) const
{
if (Cursors::self()->isCursorHidden()) {
return false;
}
if (!geometry().intersects(output->geometry())) {
return false;
}
return !image().isNull();
}
QRect Cursor::geometry() const
{
return rect().translated(m_pos - hotspot());

View file

@ -24,6 +24,8 @@ class QTimer;
namespace KWin
{
class AbstractOutput;
namespace ExtendedCursor {
/**
* Extension of Qt::CursorShape with values not currently present there
@ -165,6 +167,11 @@ public:
QRect geometry() const;
QRect rect() const;
/**
* Returns @c true if the cursor is visible on the given output; otherwise returns @c false.
*/
bool isOnOutput(AbstractOutput *output) const;
void updateCursor(const QImage &image, const QPoint &hotspot);
void markAsRendered(std::chrono::milliseconds timestamp);

40
src/cursorview.cpp Normal file
View file

@ -0,0 +1,40 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "cursorview.h"
#include "abstract_output.h"
#include "cursor.h"
#include "renderlayer.h"
namespace KWin
{
CursorView::CursorView(QObject *parent)
: QObject(parent)
{
}
CursorDelegate::CursorDelegate(AbstractOutput *output, CursorView *view)
: m_view(view)
, m_output(output)
{
}
void CursorDelegate::paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid)
{
const Cursor *cursor = Cursors::self()->currentCursor();
const QRegion cursorDamage = damage.intersected(cursor->geometry());
const QRegion cursorRepaint = repaint.intersected(cursor->geometry());
update = cursorDamage;
valid = cursorDamage.united(cursorRepaint);
if (!valid.isEmpty()) {
m_view->paint(m_output, valid);
}
}
} // namespace KWin

40
src/cursorview.h Normal file
View file

@ -0,0 +1,40 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "renderlayerdelegate.h"
namespace KWin
{
class AbstractOutput;
class KWIN_EXPORT CursorView : public QObject
{
Q_OBJECT
public:
explicit CursorView(QObject *parent = nullptr);
virtual void paint(AbstractOutput *output, const QRegion &region) = 0;
};
class KWIN_EXPORT CursorDelegate : public RenderLayerDelegate
{
Q_OBJECT
public:
CursorDelegate(AbstractOutput *output, CursorView *view);
void paint(const QRegion &damage, const QRegion &repaint, QRegion &update, QRegion &valid) override;
private:
CursorView *m_view;
AbstractOutput *m_output;
};
} // namespace KWin

76
src/cursorview_opengl.cpp Normal file
View file

@ -0,0 +1,76 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "cursorview_opengl.h"
#include "abstract_output.h"
#include "cursor.h"
#include "kwingltexture.h"
#include "kwinglutils.h"
namespace KWin
{
OpenGLCursorView::OpenGLCursorView(QObject *parent)
: CursorView(parent)
{
}
OpenGLCursorView::~OpenGLCursorView()
{
}
void OpenGLCursorView::paint(AbstractOutput *output, const QRegion &region)
{
const Cursor *cursor = Cursors::self()->currentCursor();
auto allocateTexture = [this]() {
const QImage img = Cursors::self()->currentCursor()->image();
if (img.isNull()) {
m_cursorTextureDirty = false;
return;
}
m_cursorTexture.reset(new GLTexture(img));
m_cursorTexture->setWrapMode(GL_CLAMP_TO_EDGE);
m_cursorTextureDirty = false;
};
// lazy init texture cursor only in case we need software rendering
if (!m_cursorTexture) {
allocateTexture();
// handle shape update on case cursor image changed
connect(Cursors::self(), &Cursors::currentCursorChanged, this, [this]() {
m_cursorTextureDirty = true;
});
} else if (m_cursorTextureDirty) {
const QImage image = Cursors::self()->currentCursor()->image();
if (image.size() == m_cursorTexture->size()) {
m_cursorTexture->update(image);
m_cursorTextureDirty = false;
} else {
allocateTexture();
}
}
const QRect cursorRect = cursor->geometry();
QMatrix4x4 mvp;
mvp.ortho(output->geometry());
mvp.translate(cursorRect.x(), cursorRect.y());
// Don't need to call GLVertexBuffer::beginFrame() and GLVertexBuffer::endOfFrame() because
// the GLVertexBuffer::streamingBuffer() is not being used when painting cursor.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m_cursorTexture->bind();
ShaderBinder binder(ShaderTrait::MapTexture);
binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
m_cursorTexture->render(region, QRect(0, 0, cursorRect.width(), cursorRect.height()));
m_cursorTexture->unbind();
glDisable(GL_BLEND);
}
} // namespace KWin

31
src/cursorview_opengl.h Normal file
View file

@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "cursorview.h"
namespace KWin
{
class GLTexture;
class OpenGLCursorView final : public CursorView
{
Q_OBJECT
public:
explicit OpenGLCursorView(QObject *parent = nullptr);
~OpenGLCursorView() override;
void paint(AbstractOutput *output, const QRegion &region) override;
private:
QScopedPointer<GLTexture> m_cursorTexture;
bool m_cursorTextureDirty = false;
};
} // namespace KWin

View file

@ -0,0 +1,37 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "cursorview_qpainter.h"
#include "abstract_output.h"
#include "composite.h"
#include "cursor.h"
#include "qpainterbackend.h"
#include <QPainter>
namespace KWin
{
QPainterCursorView::QPainterCursorView(QObject *parent)
: CursorView(parent)
{
}
void QPainterCursorView::paint(AbstractOutput *output, const QRegion &region)
{
QImage *renderTarget = static_cast<QPainterBackend *>(Compositor::self()->backend())->bufferForScreen(output);
if (Q_UNLIKELY(!renderTarget)) {
return;
}
const Cursor *cursor = Cursors::self()->currentCursor();
QPainter painter(renderTarget);
painter.setWindow(output->geometry());
painter.setClipRegion(region);
painter.drawImage(cursor->geometry(), cursor->image());
}
} // namespace KWin

24
src/cursorview_qpainter.h Normal file
View file

@ -0,0 +1,24 @@
/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "cursorview.h"
namespace KWin
{
class QPainterCursorView final : public CursorView
{
Q_OBJECT
public:
explicit QPainterCursorView(QObject *parent = nullptr);
void paint(AbstractOutput *output, const QRegion &region) override;
};
} // namespace KWin

View file

@ -148,23 +148,6 @@ void Scene::initialize()
connect(workspace(), &Workspace::geometryChanged, this, [this]() {
setGeometry(workspace()->geometry());
});
connect(Cursors::self(), &Cursors::currentCursorChanged, this, &Scene::addCursorRepaints);
connect(Cursors::self(), &Cursors::positionChanged, this, &Scene::addCursorRepaints);
}
void Scene::addCursorRepaints()
{
const auto outputs = kwinApp()->platform()->enabledOutputs();
QRegion repaintRegion = Cursors::self()->currentCursor()->geometry();
repaintRegion |= m_lastCursorGeometry;
for (const auto &output : outputs) {
auto intersection = repaintRegion.intersected(output->geometry());
if (!intersection.isEmpty() && output->usesSoftwareCursor()) {
addRepaint(intersection);
}
}
m_lastCursorGeometry = Cursors::self()->currentCursor()->geometry();
}
void Scene::addRepaintFull()
@ -297,12 +280,6 @@ void Scene::postPaint()
surface->frameRendered(frameTime.count());
}
}
if (!Cursors::self()->isCursorHidden()) {
Cursor *cursor = Cursors::self()->currentCursor();
if (cursor->geometry().intersects(painted_screen->geometry())) {
cursor->markAsRendered(frameTime);
}
}
}
clearStackingOrder();

View file

@ -224,8 +224,6 @@ protected:
// shared implementation, starts painting the screen
void paintScreen(const QRegion &damage, const QRegion &repaint,
QRegion *updateRegion, QRegion *validRegion);
// Render cursor texture in case hardware cursor is disabled/non-applicable
virtual void paintCursor(AbstractOutput *output, const QRegion &region) = 0;
friend class EffectsHandlerImpl;
// called after all effects had their paintScreen() called
void finalPaintScreen(int mask, const QRegion &region, ScreenPaintData& data);
@ -278,8 +276,6 @@ protected:
// windows in their stacking order
QVector< Window* > stacking_order;
private:
void addCursorRepaints();
std::chrono::milliseconds m_expectedPresentTimestamp = std::chrono::milliseconds::zero();
QList<SceneDelegate *> m_delegates;
QHash< Toplevel*, Window* > m_windows;
@ -289,7 +285,6 @@ private:
qreal m_renderTargetScale = 1;
// how many times finalPaintScreen() has been called
int m_paintScreenCount = 0;
QRect m_lastCursorGeometry;
};
// The base class for windows representations in composite backends

View file

@ -97,78 +97,6 @@ bool SceneOpenGL::initFailed() const
return !init_ok;
}
/**
* Render cursor texture in case hardware cursor is disabled.
* Useful for screen recording apps or backends that can't do planes.
*/
void SceneOpenGL::paintCursor(AbstractOutput *output, const QRegion &rendered)
{
Cursor* cursor = Cursors::self()->currentCursor();
// don't paint if we use hardware cursor or the cursor is hidden
if (!output || !output->usesSoftwareCursor()
|| Cursors::self()->isCursorHidden()
|| cursor->image().isNull()) {
return;
}
// figure out which part of the cursor needs to be repainted
const QPoint cursorPos = cursor->pos() - cursor->hotspot();
const QRect cursorRect = cursor->rect();
QRegion region;
for (const QRect &rect : rendered) {
region |= rect.translated(-cursorPos).intersected(cursorRect);
}
if (region.isEmpty()) {
return;
}
auto newTexture = [this] {
const QImage img = Cursors::self()->currentCursor()->image();
if (img.isNull()) {
m_cursorTextureDirty = false;
return;
}
m_cursorTexture.reset(new GLTexture(img));
m_cursorTexture->setWrapMode(GL_CLAMP_TO_EDGE);
m_cursorTextureDirty = false;
};
// lazy init texture cursor only in case we need software rendering
if (!m_cursorTexture) {
newTexture();
// handle shape update on case cursor image changed
connect(Cursors::self(), &Cursors::currentCursorChanged, this, [this] {
m_cursorTextureDirty = true;
});
} else if (m_cursorTextureDirty) {
const QImage image = Cursors::self()->currentCursor()->image();
if (image.size() == m_cursorTexture->size()) {
m_cursorTexture->update(image);
m_cursorTextureDirty = false;
} else {
newTexture();
}
}
// get cursor position in projection coordinates
QMatrix4x4 mvp = renderTargetProjectionMatrix();
mvp.translate(cursorPos.x(), cursorPos.y());
// handle transparence
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// paint texture in cursor offset
m_cursorTexture->bind();
ShaderBinder binder(ShaderTrait::MapTexture);
binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
m_cursorTexture->render(cursorRect);
m_cursorTexture->unbind();
glDisable(GL_BLEND);
}
void SceneOpenGL::aboutToStartPainting(AbstractOutput *output, const QRegion &damage)
{
m_backend->aboutToStartPainting(output, damage);
@ -178,7 +106,6 @@ void SceneOpenGL::paint(const QRegion &damage, const QRegion &repaint, QRegion &
{
GLVertexBuffer::streamingBuffer()->beginFrame();
paintScreen(damage, repaint, &update, &valid);
paintCursor(painted_screen, valid);
GLVertexBuffer::streamingBuffer()->endOfFrame();
}

View file

@ -69,7 +69,6 @@ protected:
void paintGenericScreen(int mask, const ScreenPaintData &data) override;
Scene::Window *createWindow(Toplevel *t) override;
void finalDrawWindow(EffectWindowImpl* w, int mask, const QRegion &region, WindowPaintData& data) override;
void paintCursor(AbstractOutput *output, const QRegion &region) override;
private:
void doPaintBackground(const QVector< float >& vertices);
@ -78,8 +77,6 @@ private:
bool init_ok = true;
OpenGLBackend *m_backend;
LanczosFilter *m_lanczosFilter = nullptr;
QScopedPointer<GLTexture> m_cursorTexture;
bool m_cursorTextureDirty = false;
QMatrix4x4 m_screenProjectionMatrix;
GLuint vao = 0;
};

View file

@ -75,7 +75,6 @@ void SceneQPainter::paint(const QRegion &damage, const QRegion &repaint, QRegion
m_painter->begin(buffer);
m_painter->setWindow(painted_screen->geometry());
paintScreen(damage, repaint, &update, &valid);
paintCursor(painted_screen, update);
m_painter->end();
}
}
@ -87,24 +86,6 @@ void SceneQPainter::paintBackground(const QRegion &region)
}
}
void SceneQPainter::paintCursor(AbstractOutput *output, const QRegion &rendered)
{
if (!output || !output->usesSoftwareCursor() || Cursors::self()->isCursorHidden()) {
return;
}
Cursor* cursor = Cursors::self()->currentCursor();
const QImage img = cursor->image();
if (img.isNull()) {
return;
}
m_painter->save();
m_painter->setClipRegion(rendered.intersected(cursor->geometry()));
m_painter->drawImage(cursor->geometry(), img);
m_painter->restore();
}
void SceneQPainter::paintOffscreenQuickView(OffscreenQuickView *w)
{
QPainter *painter = effects->scenePainter();

View file

@ -48,7 +48,6 @@ public:
protected:
void paintBackground(const QRegion &region) override;
Scene::Window *createWindow(Toplevel *toplevel) override;
void paintCursor(AbstractOutput *output, const QRegion &region) override;
void paintOffscreenQuickView(OffscreenQuickView *w) override;
private: