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: