From dd6d0b22ccc566a9d48a5925e72f6de13394a7d4 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sat, 5 Feb 2022 11:47:00 +0200 Subject: [PATCH] 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. --- src/CMakeLists.txt | 3 + src/composite.cpp | 41 ++++++++++++++ src/composite.h | 2 + src/cursor.cpp | 12 ++++ src/cursor.h | 7 +++ src/cursorview.cpp | 40 ++++++++++++++ src/cursorview.h | 40 ++++++++++++++ src/cursorview_opengl.cpp | 76 ++++++++++++++++++++++++++ src/cursorview_opengl.h | 31 +++++++++++ src/cursorview_qpainter.cpp | 37 +++++++++++++ src/cursorview_qpainter.h | 24 ++++++++ src/scene.cpp | 23 -------- src/scene.h | 5 -- src/scenes/opengl/scene_opengl.cpp | 73 ------------------------- src/scenes/opengl/scene_opengl.h | 3 - src/scenes/qpainter/scene_qpainter.cpp | 19 ------- src/scenes/qpainter/scene_qpainter.h | 1 - 17 files changed, 313 insertions(+), 124 deletions(-) create mode 100644 src/cursorview.cpp create mode 100644 src/cursorview.h create mode 100644 src/cursorview_opengl.cpp create mode 100644 src/cursorview_opengl.h create mode 100644 src/cursorview_qpainter.cpp create mode 100644 src/cursorview_qpainter.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 260474acfc..2d4dda8536 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/composite.cpp b/src/composite.cpp index aec916cf3a..c7ac7145ad 100644 --- a/src/composite.cpp +++ b/src/composite.cpp @@ -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(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) diff --git a/src/composite.h b/src/composite.h index 3d4aad2a51..9c23ddd928 100644 --- a/src/composite.h +++ b/src/composite.h @@ -20,6 +20,7 @@ namespace KWin class AbstractOutput; class CompositorSelectionOwner; +class CursorView; class RenderBackend; class RenderLayer; class RenderLoop; @@ -148,6 +149,7 @@ private: QList m_unusedSupportProperties; QTimer m_unusedSupportPropertyTimer; Scene *m_scene = nullptr; + CursorView *m_cursorView = nullptr; RenderBackend *m_backend = nullptr; QHash m_superlayers; }; diff --git a/src/cursor.cpp b/src/cursor.cpp index a3a48fd306..ec45f2e0fa 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -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()); diff --git a/src/cursor.h b/src/cursor.h index 926ebe21bf..46ec5a7ceb 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -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); diff --git a/src/cursorview.cpp b/src/cursorview.cpp new file mode 100644 index 0000000000..f6cddb855a --- /dev/null +++ b/src/cursorview.cpp @@ -0,0 +1,40 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + 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 diff --git a/src/cursorview.h b/src/cursorview.h new file mode 100644 index 0000000000..4d995643db --- /dev/null +++ b/src/cursorview.h @@ -0,0 +1,40 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + 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 ®ion) = 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 diff --git a/src/cursorview_opengl.cpp b/src/cursorview_opengl.cpp new file mode 100644 index 0000000000..23adc3cd84 --- /dev/null +++ b/src/cursorview_opengl.cpp @@ -0,0 +1,76 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + 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 ®ion) +{ + 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 diff --git a/src/cursorview_opengl.h b/src/cursorview_opengl.h new file mode 100644 index 0000000000..0193344eb2 --- /dev/null +++ b/src/cursorview_opengl.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + 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 ®ion) override; + +private: + QScopedPointer m_cursorTexture; + bool m_cursorTextureDirty = false; +}; + +} // namespace KWin diff --git a/src/cursorview_qpainter.cpp b/src/cursorview_qpainter.cpp new file mode 100644 index 0000000000..e355f876ca --- /dev/null +++ b/src/cursorview_qpainter.cpp @@ -0,0 +1,37 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + 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 + +namespace KWin +{ + +QPainterCursorView::QPainterCursorView(QObject *parent) + : CursorView(parent) +{ +} + +void QPainterCursorView::paint(AbstractOutput *output, const QRegion ®ion) +{ + QImage *renderTarget = static_cast(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 diff --git a/src/cursorview_qpainter.h b/src/cursorview_qpainter.h new file mode 100644 index 0000000000..b403a01af2 --- /dev/null +++ b/src/cursorview_qpainter.h @@ -0,0 +1,24 @@ +/* + SPDX-FileCopyrightText: 2022 Vlad Zahorodnii + + 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 ®ion) override; +}; + +} // namespace KWin diff --git a/src/scene.cpp b/src/scene.cpp index 5259c382bb..4fbefb47f0 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -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(); diff --git a/src/scene.h b/src/scene.h index af023aeb58..8d4506d082 100644 --- a/src/scene.h +++ b/src/scene.h @@ -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 ®ion) = 0; friend class EffectsHandlerImpl; // called after all effects had their paintScreen() called void finalPaintScreen(int mask, const QRegion ®ion, 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 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 diff --git a/src/scenes/opengl/scene_opengl.cpp b/src/scenes/opengl/scene_opengl.cpp index c873c80263..716066e00a 100644 --- a/src/scenes/opengl/scene_opengl.cpp +++ b/src/scenes/opengl/scene_opengl.cpp @@ -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(); } diff --git a/src/scenes/opengl/scene_opengl.h b/src/scenes/opengl/scene_opengl.h index 86085eef6f..cca3af1e76 100644 --- a/src/scenes/opengl/scene_opengl.h +++ b/src/scenes/opengl/scene_opengl.h @@ -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 ®ion, WindowPaintData& data) override; - void paintCursor(AbstractOutput *output, const QRegion ®ion) 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 m_cursorTexture; - bool m_cursorTextureDirty = false; QMatrix4x4 m_screenProjectionMatrix; GLuint vao = 0; }; diff --git a/src/scenes/qpainter/scene_qpainter.cpp b/src/scenes/qpainter/scene_qpainter.cpp index a082aed883..74dd361191 100644 --- a/src/scenes/qpainter/scene_qpainter.cpp +++ b/src/scenes/qpainter/scene_qpainter.cpp @@ -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 ®ion) } } -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(); diff --git a/src/scenes/qpainter/scene_qpainter.h b/src/scenes/qpainter/scene_qpainter.h index ab23ec211e..2ee10e1d44 100644 --- a/src/scenes/qpainter/scene_qpainter.h +++ b/src/scenes/qpainter/scene_qpainter.h @@ -48,7 +48,6 @@ public: protected: void paintBackground(const QRegion ®ion) override; Scene::Window *createWindow(Toplevel *toplevel) override; - void paintCursor(AbstractOutput *output, const QRegion ®ion) override; void paintOffscreenQuickView(OffscreenQuickView *w) override; private: