From 342714301775f2f8d8a77f21e479f945b49f1766 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Wed, 2 Jun 2021 11:45:50 +0300 Subject: [PATCH] Improve thumbnail item integration Currently, thumbnail items are rendered by kwin. This means that qtquick code cannot do things such as applying shader effects to window thumbnails or simply draw custom controls on top of thumbnails. With this change, task switchers and qml extensions will be able to place their own contents on top of thumbnails and apply custom effects to them. In order to integrate window thumbnails, a window is rendered on kwin side using its own opengl context. A fence is inserted in the command stream to ensure that the qtquick machinery doesn't start using the offscreen texture while there are still rendering commands being executed. Thumbnails are rendered into offscreen textures as we don't have full control over when qtquick windows render their contents and to work around the fact that things such as VAOs can't be shared across OpenGL contexts. WindowThumbnailItem and DesktopThumbnailItem act as texture providers. --- src/CMakeLists.txt | 2 +- src/effects.cpp | 44 +--- src/effects.h | 20 -- src/scene.cpp | 124 --------- src/scene.h | 6 +- src/scripting/scripting.cpp | 2 +- src/scripting/thumbnailitem.cpp | 446 ++++++++++++++++++++++++++++++++ src/scripting/thumbnailitem.h | 140 ++++++++++ src/tabbox/tabboxhandler.cpp | 1 - src/thumbnailitem.cpp | 209 --------------- src/thumbnailitem.h | 139 ---------- 11 files changed, 592 insertions(+), 541 deletions(-) create mode 100644 src/scripting/thumbnailitem.cpp create mode 100644 src/scripting/thumbnailitem.h delete mode 100644 src/thumbnailitem.cpp delete mode 100644 src/thumbnailitem.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69270754e5..dd54a7ae4a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -105,6 +105,7 @@ set(kwin_SRCS scripting/scripting_logging.cpp scripting/scripting_model.cpp scripting/scriptingutils.cpp + scripting/thumbnailitem.cpp scripting/workspace_wrapper.cpp session.cpp session_consolekit.cpp @@ -120,7 +121,6 @@ set(kwin_SRCS surfaceitem_x11.cpp syncalarmx11filter.cpp tablet_input.cpp - thumbnailitem.cpp toplevel.cpp touch_hide_cursor_spy.cpp touch_input.cpp diff --git a/src/effects.cpp b/src/effects.cpp index b341d04d38..b26dad7ab5 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -31,7 +31,6 @@ #include "scripting/scriptedeffect.h" #include "screens.h" #include "screenlockerwatcher.h" -#include "thumbnailitem.h" #include "virtualdesktops.h" #include "window_property_notify_x11_filter.h" #include "workspace.h" @@ -39,6 +38,8 @@ #include "kwineffectquickview.h" #include +#include +#include #include @@ -2098,47 +2099,6 @@ void EffectWindowImpl::elevate(bool elevate) effects->setElevatedWindow(this, elevate); } -void EffectWindowImpl::registerThumbnail(AbstractThumbnailItem *item) -{ - if (WindowThumbnailItem *thumb = qobject_cast(item)) { - insertThumbnail(thumb); - connect(thumb, &QObject::destroyed, this, &EffectWindowImpl::thumbnailDestroyed); - connect(thumb, &WindowThumbnailItem::wIdChanged, this, &EffectWindowImpl::thumbnailTargetChanged); - } else if (DesktopThumbnailItem *desktopThumb = qobject_cast(item)) { - m_desktopThumbnails.append(desktopThumb); - connect(desktopThumb, &QObject::destroyed, this, &EffectWindowImpl::desktopThumbnailDestroyed); - } -} - -void EffectWindowImpl::thumbnailDestroyed(QObject *object) -{ - // we know it is a ThumbnailItem - m_thumbnails.remove(static_cast(object)); -} - -void EffectWindowImpl::thumbnailTargetChanged() -{ - if (WindowThumbnailItem *item = qobject_cast(sender())) { - insertThumbnail(item); - } -} - -void EffectWindowImpl::insertThumbnail(WindowThumbnailItem *item) -{ - EffectWindow *w = effects->findWindow(item->wId()); - if (w) { - m_thumbnails.insert(item, QPointer(static_cast(w))); - } else { - m_thumbnails.insert(item, QPointer()); - } -} - -void EffectWindowImpl::desktopThumbnailDestroyed(QObject *object) -{ - // we know it is a DesktopThumbnailItem - m_desktopThumbnails.removeAll(static_cast(object)); -} - void EffectWindowImpl::minimize() { if (auto client = qobject_cast(toplevel)) { diff --git a/src/effects.h b/src/effects.h index ee9ed48f8b..f7427bd2ab 100644 --- a/src/effects.h +++ b/src/effects.h @@ -35,11 +35,6 @@ class QDBusServiceWatcher; namespace KWin { - -class AbstractThumbnailItem; -class DesktopThumbnailItem; -class WindowThumbnailItem; - class AbstractClient; class Compositor; class Deleted; @@ -508,25 +503,10 @@ public: void setData(int role, const QVariant &data) override; QVariant data(int role) const override; - void registerThumbnail(AbstractThumbnailItem *item); - QHash > const &thumbnails() const { - return m_thumbnails; - } - QList const &desktopThumbnails() const { - return m_desktopThumbnails; - } - -private Q_SLOTS: - void thumbnailDestroyed(QObject *object); - void thumbnailTargetChanged(); - void desktopThumbnailDestroyed(QObject *object); private: - void insertThumbnail(WindowThumbnailItem *item); Toplevel* toplevel; Scene::Window* sw; // This one is used only during paint pass. QHash dataMap; - QHash > m_thumbnails; - QList m_desktopThumbnails; bool managed = false; bool waylandClient; bool x11Client; diff --git a/src/scene.cpp b/src/scene.cpp index 9b4ad518a2..97a62dee91 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -74,7 +74,6 @@ #include "screens.h" #include "shadow.h" #include "wayland_server.h" -#include "thumbnailitem.h" #include "composite.h" namespace KWin @@ -484,8 +483,6 @@ void Scene::clearStackingOrder() stacking_order.clear(); } -static Scene::Window *s_recursionCheck = nullptr; - void Scene::paintWindow(Window* w, int mask, const QRegion &_region) { // no painting outside visible screen (and no transformations) @@ -497,129 +494,8 @@ void Scene::paintWindow(Window* w, int mask, const QRegion &_region) return; } - if (s_recursionCheck == w) { - return; - } - WindowPaintData data(w->window()->effectWindow(), screenProjectionMatrix()); effects->paintWindow(effectWindow(w), mask, region, data); - // paint thumbnails on top of window - paintWindowThumbnails(w, region, data.opacity(), data.brightness(), data.saturation()); - // and desktop thumbnails - paintDesktopThumbnails(w); -} - -static void adjustClipRegion(AbstractThumbnailItem *item, QRegion &clippingRegion) -{ - if (item->clip() && item->clipTo()) { - // the x/y positions of the parent item are not correct. The margins are added, though the size seems fine - // that's why we have to get the offset by inspecting the anchors properties - QQuickItem *parentItem = item->clipTo(); - QPointF offset; - QVariant anchors = parentItem->property("anchors"); - if (anchors.isValid()) { - if (QObject *anchorsObject = anchors.value()) { - offset.setX(anchorsObject->property("leftMargin").toReal()); - offset.setY(anchorsObject->property("topMargin").toReal()); - } - } - QRectF rect = QRectF(parentItem->position() - offset, QSizeF(parentItem->width(), parentItem->height())); - if (QQuickItem *p = parentItem->parentItem()) { - rect = p->mapRectToScene(rect); - } - clippingRegion &= rect.adjusted(0,0,-1,-1).translated(item->window()->position()).toRect(); - } -} - -void Scene::paintWindowThumbnails(Scene::Window *w, const QRegion ®ion, qreal opacity, qreal brightness, qreal saturation) -{ - EffectWindowImpl *wImpl = static_cast(effectWindow(w)); - for (QHash >::const_iterator it = wImpl->thumbnails().constBegin(); - it != wImpl->thumbnails().constEnd(); - ++it) { - if (it.value().isNull()) { - continue; - } - WindowThumbnailItem *item = it.key(); - if (!item->isVisible()) { - continue; - } - EffectWindowImpl *thumb = it.value().data(); - WindowPaintData thumbData(thumb, screenProjectionMatrix()); - thumbData.setOpacity(opacity); - thumbData.setBrightness(brightness * item->brightness()); - thumbData.setSaturation(saturation * item->saturation()); - - const QRect visualThumbRect(thumb->expandedGeometry()); - - QSizeF size = QSizeF(visualThumbRect.size()); - size.scale(QSizeF(item->width(), item->height()), Qt::KeepAspectRatio); - if (size.width() > visualThumbRect.width() || size.height() > visualThumbRect.height()) { - size = QSizeF(visualThumbRect.size()); - } - thumbData.setXScale(size.width() / static_cast(visualThumbRect.width())); - thumbData.setYScale(size.height() / static_cast(visualThumbRect.height())); - - if (!item->window()) { - continue; - } - const QPointF point = item->mapToScene(QPointF(0,0)); - qreal x = point.x() + w->x() + (item->width() - size.width())/2; - qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; - x -= thumb->x(); - y -= thumb->y(); - // compensate shadow topleft padding - x += (thumb->x()-visualThumbRect.x())*thumbData.xScale(); - y += (thumb->y()-visualThumbRect.y())*thumbData.yScale(); - thumbData.setXTranslation(x); - thumbData.setYTranslation(y); - int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS; - if (thumbData.opacity() == 1.0) { - thumbMask |= PAINT_WINDOW_OPAQUE; - } else { - thumbMask |= PAINT_WINDOW_TRANSLUCENT; - } - QRegion clippingRegion = region; - clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); - adjustClipRegion(item, clippingRegion); - effects->drawWindow(thumb, thumbMask, clippingRegion, thumbData); - } -} - -void Scene::paintDesktopThumbnails(Scene::Window *w) -{ - EffectWindowImpl *wImpl = static_cast(effectWindow(w)); - for (QList::const_iterator it = wImpl->desktopThumbnails().constBegin(); - it != wImpl->desktopThumbnails().constEnd(); - ++it) { - DesktopThumbnailItem *item = *it; - if (!item->isVisible()) { - continue; - } - if (!item->window()) { - continue; - } - s_recursionCheck = w; - - ScreenPaintData data; - const QSize &screenSize = screens()->size(); - QSize size = screenSize; - - size.scale(item->width(), item->height(), Qt::KeepAspectRatio); - data *= QVector2D(size.width() / double(screenSize.width()), - size.height() / double(screenSize.height())); - const QPointF point = item->mapToScene(item->position()); - const qreal x = point.x() + w->x() + (item->width() - size.width())/2; - const qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; - const QRect region = QRect(x, y, item->width(), item->height()); - QRegion clippingRegion = region; - clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); - adjustClipRegion(item, clippingRegion); - data += QPointF(x, y); - const int desktopMask = PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; - paintDesktop(item->desktop(), desktopMask, clippingRegion, data); - s_recursionCheck = nullptr; - } } void Scene::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) diff --git a/src/scene.h b/src/scene.h index 7b8cd1782c..d411b3b88b 100644 --- a/src/scene.h +++ b/src/scene.h @@ -26,7 +26,6 @@ class DecoratedClientImpl; } class AbstractOutput; -class AbstractThumbnailItem; class DecorationRenderer; class Deleted; class EffectFrameImpl; @@ -193,6 +192,8 @@ public: virtual PlatformSurfaceTexture *createPlatformSurfaceTextureX11(SurfacePixmapX11 *pixmap); virtual PlatformSurfaceTexture *createPlatformSurfaceTextureWayland(SurfacePixmapWayland *pixmap); + virtual void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data); + Q_SIGNALS: void frameRendered(); void resetCompositing(); @@ -236,7 +237,6 @@ protected: // let the scene decide whether it's better to paint more of the screen, eg. in order to allow a buffer swap // the default is NOOP virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); - virtual void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data); virtual void paintEffectQuickView(EffectQuickView *w) = 0; @@ -263,8 +263,6 @@ protected: // windows in their stacking order QVector< Window* > stacking_order; private: - void paintWindowThumbnails(Scene::Window *w, const QRegion ®ion, qreal opacity, qreal brightness, qreal saturation); - void paintDesktopThumbnails(Scene::Window *w); std::chrono::milliseconds m_expectedPresentTimestamp = std::chrono::milliseconds::zero(); void reallocRepaints(); QHash< Toplevel*, Window* > m_windows; diff --git a/src/scripting/scripting.cpp b/src/scripting/scripting.cpp index 27352af485..27e842bdc0 100644 --- a/src/scripting/scripting.cpp +++ b/src/scripting/scripting.cpp @@ -17,10 +17,10 @@ #include "screenedgeitem.h" #include "scripting_model.h" #include "scripting_logging.h" +#include "thumbnailitem.h" #include "options.h" #include "screenedge.h" -#include "thumbnailitem.h" #include "workspace.h" #include "x11client.h" // KDE diff --git a/src/scripting/thumbnailitem.cpp b/src/scripting/thumbnailitem.cpp new file mode 100644 index 0000000000..58879a9d57 --- /dev/null +++ b/src/scripting/thumbnailitem.cpp @@ -0,0 +1,446 @@ +/* + SPDX-FileCopyrightText: 2011 Martin Gräßlin + SPDX-FileCopyrightText: 2020 David Edmundson + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "thumbnailitem.h" +#include "abstract_client.h" +#include "composite.h" +#include "effects.h" +#include "scene.h" +#include "screens.h" +#include "scripting_logging.h" +#include "workspace.h" + +#include +#include + +#include +#include +#include +#include + +namespace KWin +{ +class ThumbnailTextureProvider : public QSGTextureProvider +{ +public: + explicit ThumbnailTextureProvider(QQuickWindow *window); + + QSGTexture *texture() const override; + void setTexture(const QSharedPointer &nativeTexture); + void setTexture(QSGTexture *texture); + +private: + QQuickWindow *m_window; + QSharedPointer m_nativeTexture; + QScopedPointer m_texture; +}; + +ThumbnailTextureProvider::ThumbnailTextureProvider(QQuickWindow *window) + : m_window(window) +{ +} + +QSGTexture *ThumbnailTextureProvider::texture() const +{ + return m_texture.data(); +} + +void ThumbnailTextureProvider::setTexture(const QSharedPointer &nativeTexture) +{ + if (m_nativeTexture != nativeTexture) { + const GLuint textureId = nativeTexture->texture(); + m_nativeTexture = nativeTexture; + m_texture.reset(m_window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, + &textureId, 0, + nativeTexture->size(), + QQuickWindow::TextureHasAlphaChannel)); + m_texture->setFiltering(QSGTexture::Linear); + m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge); + m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge); + } + + // The textureChanged signal must be emitted also if only texture data changes. + Q_EMIT textureChanged(); +} + +void ThumbnailTextureProvider::setTexture(QSGTexture *texture) +{ + m_nativeTexture = nullptr; + m_texture.reset(texture); + Q_EMIT textureChanged(); +} + +class ThumbnailTextureProviderCleanupJob : public QRunnable +{ +public: + explicit ThumbnailTextureProviderCleanupJob(ThumbnailTextureProvider *provider) + : m_provider(provider) + { + } + + void run() override + { + m_provider.reset(); + } + +private: + QScopedPointer m_provider; +}; + +ThumbnailItemBase::ThumbnailItemBase(QQuickItem *parent) + : QQuickItem(parent) +{ + setFlag(ItemHasContents); + handleCompositingToggled(); + + connect(Compositor::self(), &Compositor::aboutToToggleCompositing, + this, &ThumbnailItemBase::destroyOffscreenTexture); + connect(Compositor::self(), &Compositor::compositingToggled, + this, &ThumbnailItemBase::handleCompositingToggled); +} + +ThumbnailItemBase::~ThumbnailItemBase() +{ + destroyOffscreenTexture(); + + if (m_provider) { + if (window()) { + window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider), + QQuickWindow::AfterSynchronizingStage); + } else { + qCCritical(KWIN_SCRIPTING) << "Can't destroy thumbnail texture provider because window is null"; + } + } +} + +void ThumbnailItemBase::releaseResources() +{ + if (m_provider) { + window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider), + QQuickWindow::AfterSynchronizingStage); + m_provider = nullptr; + } +} + +bool ThumbnailItemBase::isTextureProvider() const +{ + return true; +} + +QSGTextureProvider *ThumbnailItemBase::textureProvider() const +{ + if (QQuickItem::isTextureProvider()) { + return QQuickItem::textureProvider(); + } + if (!m_provider) { + m_provider = new ThumbnailTextureProvider(window()); + } + return m_provider; +} + +void ThumbnailItemBase::handleCompositingToggled() +{ + Scene *scene = Compositor::self()->scene(); + if (scene && scene->compositingType() == OpenGLCompositing) { + connect(scene, &Scene::frameRendered, this, &ThumbnailItemBase::updateOffscreenTexture); + } +} + +QSize ThumbnailItemBase::sourceSize() const +{ + return m_sourceSize; +} + +void ThumbnailItemBase::setSourceSize(const QSize &sourceSize) +{ + if (m_sourceSize != sourceSize) { + m_sourceSize = sourceSize; + invalidateOffscreenTexture(); + Q_EMIT sourceSizeChanged(); + } +} + +void ThumbnailItemBase::destroyOffscreenTexture() +{ + Scene *scene = Compositor::self()->scene(); + if (!scene || scene->compositingType() != OpenGLCompositing) { + return; + } + + if (m_offscreenTexture) { + scene->makeOpenGLContextCurrent(); + m_offscreenTarget.reset(); + m_offscreenTexture.reset(); + + if (m_acquireFence) { + glDeleteSync(m_acquireFence); + m_acquireFence = 0; + } + scene->doneOpenGLContextCurrent(); + } +} + +QSGNode *ThumbnailItemBase::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) +{ + if (Compositor::compositing() && !m_offscreenTexture) { + return oldNode; + } + + // Wait for rendering commands to the offscreen texture complete if there are any. + if (m_acquireFence) { + glClientWaitSync(m_acquireFence, GL_SYNC_FLUSH_COMMANDS_BIT, 5000); + glDeleteSync(m_acquireFence); + m_acquireFence = 0; + } + + if (!m_provider) { + m_provider = new ThumbnailTextureProvider(window()); + } + + if (m_offscreenTexture) { + m_provider->setTexture(m_offscreenTexture); + } else { + m_provider->setTexture(window()->createTextureFromImage(fallbackImage())); + } + + QSGImageNode *node = static_cast(oldNode); + if (!node) { + node = window()->createImageNode(); + } + node->setTexture(m_provider->texture()); + + if (m_offscreenTexture && m_offscreenTexture->isYInverted()) { + node->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically); + } else { + node->setTextureCoordinatesTransform(QSGImageNode::NoTransform); + } + + const QSizeF size = QSizeF(node->texture()->textureSize()) + .scaled(boundingRect().size(), Qt::KeepAspectRatio); + const qreal x = boundingRect().x() + (boundingRect().width() - size.width()) / 2; + const qreal y = boundingRect().y() + (boundingRect().height() - size.height()) / 2; + node->setRect(QRectF(QPointF(x, y), size)); + + return node; +} + +void ThumbnailItemBase::setSaturation(qreal saturation) +{ + Q_UNUSED(saturation) + qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.saturation is removed. Use a shader effect to change saturation"; +} + +void ThumbnailItemBase::setBrightness(qreal brightness) +{ + Q_UNUSED(brightness) + qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.brightness is removed. Use a shader effect to change brightness"; +} + +void ThumbnailItemBase::setClipTo(QQuickItem *clip) +{ + Q_UNUSED(clip) + qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.clipTo is removed and it has no replacements"; +} + +WindowThumbnailItem::WindowThumbnailItem(QQuickItem *parent) + : ThumbnailItemBase(parent) +{ +} + +QUuid WindowThumbnailItem::wId() const +{ + return m_wId; +} + +void WindowThumbnailItem::setWId(const QUuid &wId) +{ + if (m_wId == wId) { + return; + } + m_wId = wId; + if (!m_wId.isNull()) { + setClient(workspace()->findAbstractClient(wId)); + } else if (m_client) { + m_client = nullptr; + Q_EMIT clientChanged(); + } + Q_EMIT wIdChanged(); +} + +AbstractClient *WindowThumbnailItem::client() const +{ + return m_client; +} + +void WindowThumbnailItem::setClient(AbstractClient *client) +{ + if (m_client == client) { + return; + } + if (m_client) { + disconnect(m_client, &AbstractClient::frameGeometryChanged, + this, &WindowThumbnailItem::invalidateOffscreenTexture); + disconnect(m_client, &AbstractClient::damaged, + this, &WindowThumbnailItem::invalidateOffscreenTexture); + } + m_client = client; + if (m_client) { + connect(m_client, &AbstractClient::frameGeometryChanged, + this, &WindowThumbnailItem::invalidateOffscreenTexture); + connect(m_client, &AbstractClient::damaged, + this, &WindowThumbnailItem::invalidateOffscreenTexture); + setWId(m_client->internalId()); + } else { + setWId(QUuid()); + } + invalidateOffscreenTexture(); + Q_EMIT clientChanged(); +} + +QImage WindowThumbnailItem::fallbackImage() const +{ + if (m_client) { + return m_client->icon().pixmap(boundingRect().size().toSize()).toImage(); + } + return QImage(); +} + +void WindowThumbnailItem::invalidateOffscreenTexture() +{ + m_dirty = true; + update(); +} + +void WindowThumbnailItem::updateOffscreenTexture() +{ + if (m_acquireFence || !m_dirty || !m_client) { + return; + } + + const QRect geometry = m_client->frameGeometry(); + QSize textureSize = geometry.size(); + if (sourceSize().width() > 0) { + textureSize.setWidth(sourceSize().width()); + } + if (sourceSize().height() > 0) { + textureSize.setHeight(sourceSize().height()); + } + + if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) { + m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize)); + m_offscreenTexture->setFilter(GL_LINEAR); + m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); + m_offscreenTarget.reset(new GLRenderTarget(*m_offscreenTexture)); + } + + GLRenderTarget::pushRenderTarget(m_offscreenTarget.data()); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + QMatrix4x4 projectionMatrix; + projectionMatrix.ortho(geometry.x(), geometry.x() + geometry.width(), + geometry.y(), geometry.y() + geometry.height(), -1, 1); + + EffectWindowImpl *effectWindow = m_client->effectWindow(); + WindowPaintData data(effectWindow); + data.setProjectionMatrix(projectionMatrix); + + // The thumbnail must be rendered using kwin's opengl context as VAOs are not + // shared across contexts. Unfortunately, this also introduces a latency of 1 + // frame, which is not ideal, but it is acceptable for things such as thumbnails. + const int mask = Scene::PAINT_WINDOW_TRANSFORMED; + effectWindow->sceneWindow()->performPaint(mask, infiniteRegion(), data); + GLRenderTarget::popRenderTarget(); + + // The fence is needed to avoid the case where qtquick renderer starts using + // the texture while all rendering commands to it haven't completed yet. + m_dirty = false; + m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + + // We know that the texture has changed, so schedule an item update. + update(); +} + +DesktopThumbnailItem::DesktopThumbnailItem(QQuickItem *parent) + : ThumbnailItemBase(parent) +{ +} + +int DesktopThumbnailItem::desktop() const +{ + return m_desktop; +} + +void DesktopThumbnailItem::setDesktop(int desktop) +{ + desktop = qBound(1, desktop, VirtualDesktopManager::self()->count()); + if (m_desktop != desktop) { + m_desktop = desktop; + invalidateOffscreenTexture(); + Q_EMIT desktopChanged(); + } +} + +QImage DesktopThumbnailItem::fallbackImage() const +{ + return QImage(); +} + +void DesktopThumbnailItem::invalidateOffscreenTexture() +{ + update(); +} + +void DesktopThumbnailItem::updateOffscreenTexture() +{ + if (m_acquireFence) { + return; + } + + const QRect geometry = screens()->geometry(); + QSize textureSize = geometry.size(); + if (sourceSize().width() > 0) { + textureSize.setWidth(sourceSize().width()); + } + if (sourceSize().height() > 0) { + textureSize.setHeight(sourceSize().height()); + } + + if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) { + m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize)); + m_offscreenTexture->setFilter(GL_LINEAR); + m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); + m_offscreenTexture->setYInverted(true); + m_offscreenTarget.reset(new GLRenderTarget(*m_offscreenTexture)); + } + + GLRenderTarget::pushRenderTarget(m_offscreenTarget.data()); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + QMatrix4x4 projectionMatrix; + projectionMatrix.ortho(geometry); + ScreenPaintData data(projectionMatrix); + + // The thumbnail must be rendered using kwin's opengl context as VAOs are not + // shared across contexts. Unfortunately, this also introduces a latency of 1 + // frame, which is not ideal, but it is acceptable for things such as thumbnails. + const int mask = Scene::PAINT_WINDOW_TRANSFORMED | Scene::PAINT_SCREEN_TRANSFORMED; + Scene *scene = Compositor::self()->scene(); + scene->paintDesktop(m_desktop, mask, infiniteRegion(), data); + GLRenderTarget::popRenderTarget(); + + // The fence is needed to avoid the case where qtquick renderer starts using + // the texture while all rendering commands to it haven't completed yet. + m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + + // We know that the texture has changed, so schedule an item update. + update(); +} + +} // namespace KWin diff --git a/src/scripting/thumbnailitem.h b/src/scripting/thumbnailitem.h new file mode 100644 index 0000000000..b53b16e325 --- /dev/null +++ b/src/scripting/thumbnailitem.h @@ -0,0 +1,140 @@ +/* + SPDX-FileCopyrightText: 2011 Martin Gräßlin + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include + +#include + +namespace KWin +{ +class AbstractClient; +class GLRenderTarget; +class GLTexture; +class ThumbnailTextureProvider; + +class ThumbnailItemBase : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize NOTIFY sourceSizeChanged) + /** + * TODO Plasma 6: Remove. + * @deprecated use a shader effect to change the brightness + */ + Q_PROPERTY(qreal brightness READ brightness WRITE setBrightness NOTIFY brightnessChanged) + /** + * TODO Plasma 6: Remove. + * @deprecated use a shader effect to change color saturation + */ + Q_PROPERTY(qreal saturation READ saturation WRITE setSaturation NOTIFY saturationChanged) + /** + * TODO Plasma 6: Remove. + * @deprecated clipTo has no replacement + */ + Q_PROPERTY(QQuickItem *clipTo READ clipTo WRITE setClipTo NOTIFY clipToChanged) + +public: + explicit ThumbnailItemBase(QQuickItem *parent = nullptr); + ~ThumbnailItemBase() override; + + qreal brightness() const { return 1; } + void setBrightness(qreal brightness); + + qreal saturation() const { return 1; } + void setSaturation(qreal saturation); + + QQuickItem *clipTo() const { return nullptr; } + void setClipTo(QQuickItem *clip); + + QSize sourceSize() const; + void setSourceSize(const QSize &sourceSize); + + QSGTextureProvider *textureProvider() const override; + bool isTextureProvider() const override; + QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) override; + +Q_SIGNALS: + void brightnessChanged(); + void saturationChanged(); + void clipToChanged(); + void sourceSizeChanged(); + +protected: + void releaseResources() override; + + virtual QImage fallbackImage() const = 0; + virtual void invalidateOffscreenTexture() = 0; + virtual void updateOffscreenTexture() = 0; + void destroyOffscreenTexture(); + + mutable ThumbnailTextureProvider *m_provider = nullptr; + QSharedPointer m_offscreenTexture; + QScopedPointer m_offscreenTarget; + GLsync m_acquireFence = 0; + +private: + void handleCompositingToggled(); + + QSize m_sourceSize; +}; + +class WindowThumbnailItem : public ThumbnailItemBase +{ + Q_OBJECT + Q_PROPERTY(QUuid wId READ wId WRITE setWId NOTIFY wIdChanged) + Q_PROPERTY(KWin::AbstractClient *client READ client WRITE setClient NOTIFY clientChanged) + +public: + explicit WindowThumbnailItem(QQuickItem *parent = nullptr); + + QUuid wId() const; + void setWId(const QUuid &wId); + + AbstractClient *client() const; + void setClient(AbstractClient *client); + +Q_SIGNALS: + void wIdChanged(); + void clientChanged(); + +protected: + QImage fallbackImage() const override; + void invalidateOffscreenTexture() override; + void updateOffscreenTexture() override; + +private: + QUuid m_wId; + QPointer m_client; + bool m_dirty = false; +}; + +class DesktopThumbnailItem : public ThumbnailItemBase +{ + Q_OBJECT + Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) + +public: + explicit DesktopThumbnailItem(QQuickItem *parent = nullptr); + + int desktop() const; + void setDesktop(int desktop); + +Q_SIGNALS: + void desktopChanged(); + +protected: + QImage fallbackImage() const override; + void invalidateOffscreenTexture() override; + void updateOffscreenTexture() override; + +private: + int m_desktop = 1; +}; + +} // namespace KWin diff --git a/src/tabbox/tabboxhandler.cpp b/src/tabbox/tabboxhandler.cpp index 9a8500c3eb..6082446887 100644 --- a/src/tabbox/tabboxhandler.cpp +++ b/src/tabbox/tabboxhandler.cpp @@ -15,7 +15,6 @@ // tabbox #include "clientmodel.h" #include "desktopmodel.h" -#include "thumbnailitem.h" #include "scripting/scripting.h" #include "switcheritem.h" #include "tabbox_logging.h" diff --git a/src/thumbnailitem.cpp b/src/thumbnailitem.cpp deleted file mode 100644 index e1b3284d7d..0000000000 --- a/src/thumbnailitem.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* - KWin - the KDE window manager - This file is part of the KDE project. - - SPDX-FileCopyrightText: 2011 Martin Gräßlin - - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#include "thumbnailitem.h" -// KWin -#include "x11client.h" -#include "composite.h" -#include "effects.h" -#include "workspace.h" -#include "wayland_server.h" -// Qt -#include -#include -#include - -namespace KWin -{ - -AbstractThumbnailItem::AbstractThumbnailItem(QQuickItem *parent) - : QQuickPaintedItem(parent) - , m_brightness(1.0) - , m_saturation(1.0) - , m_clipToItem() -{ - connect(Compositor::self(), &Compositor::compositingToggled, this, &AbstractThumbnailItem::compositingToggled); - compositingToggled(); - QTimer::singleShot(0, this, &AbstractThumbnailItem::init); -} - -AbstractThumbnailItem::~AbstractThumbnailItem() -{ -} - -void AbstractThumbnailItem::compositingToggled() -{ - m_parent.clear(); - if (effects) { - connect(effects, &EffectsHandler::windowAdded, this, &AbstractThumbnailItem::effectWindowAdded); - connect(effects, &EffectsHandler::windowDamaged, this, &AbstractThumbnailItem::repaint); - effectWindowAdded(); - } -} - -void AbstractThumbnailItem::init() -{ - findParentEffectWindow(); - if (m_parent) { - m_parent->registerThumbnail(this); - } -} - -void AbstractThumbnailItem::findParentEffectWindow() -{ - if (effects) { - QQuickWindow *qw = window(); - if (!qw) { - qCDebug(KWIN_CORE) << "No QQuickWindow assigned yet"; - return; - } - if (auto *w = static_cast(effects->findWindow(qw))) { - m_parent = QPointer(w); - } - } -} - -void AbstractThumbnailItem::effectWindowAdded() -{ - // the window might be added before the EffectWindow is created - // by using this slot we can register the thumbnail when it is finally created - if (m_parent.isNull()) { - findParentEffectWindow(); - if (m_parent) { - m_parent->registerThumbnail(this); - } - } -} - -void AbstractThumbnailItem::setBrightness(qreal brightness) -{ - if (qFuzzyCompare(brightness, m_brightness)) { - return; - } - m_brightness = brightness; - update(); - Q_EMIT brightnessChanged(); -} - -void AbstractThumbnailItem::setSaturation(qreal saturation) -{ - if (qFuzzyCompare(saturation, m_saturation)) { - return; - } - m_saturation = saturation; - update(); - Q_EMIT saturationChanged(); -} - -void AbstractThumbnailItem::setClipTo(QQuickItem *clip) -{ - m_clipToItem = QPointer(clip); - Q_EMIT clipToChanged(); -} - -WindowThumbnailItem::WindowThumbnailItem(QQuickItem* parent) - : AbstractThumbnailItem(parent) - , m_wId(nullptr) - , m_client(nullptr) -{ -} - -WindowThumbnailItem::~WindowThumbnailItem() -{ -} - -void WindowThumbnailItem::setWId(const QUuid &wId) -{ - if (m_wId == wId) { - return; - } - m_wId = wId; - if (m_wId != nullptr) { - setClient(workspace()->findAbstractClient([this] (const AbstractClient *c) { return c->internalId() == m_wId; })); - } else if (m_client) { - m_client = nullptr; - Q_EMIT clientChanged(); - } - Q_EMIT wIdChanged(wId); -} - -void WindowThumbnailItem::setClient(AbstractClient *client) -{ - if (m_client == client) { - return; - } - m_client = client; - if (m_client) { - setWId(m_client->internalId()); - } else { - setWId({}); - } - Q_EMIT clientChanged(); -} - -void WindowThumbnailItem::paint(QPainter *painter) -{ - if (effects) { - return; - } - auto client = workspace()->findAbstractClient([this] (const AbstractClient *c) { return c->internalId() == m_wId; }); - if (!client) { - return; - } - QPixmap pixmap = client->icon().pixmap(boundingRect().size().toSize()); - const QSize size(boundingRect().size().toSize() - pixmap.size()); - painter->drawPixmap(boundingRect().adjusted(size.width()/2.0, size.height()/2.0, -size.width()/2.0, -size.height()/2.0).toRect(), - pixmap); -} - -void WindowThumbnailItem::repaint(KWin::EffectWindow *w) -{ - if (static_cast(w)->window()->internalId() == m_wId) { - update(); - } -} - -DesktopThumbnailItem::DesktopThumbnailItem(QQuickItem *parent) - : AbstractThumbnailItem(parent) - , m_desktop(0) -{ -} - -DesktopThumbnailItem::~DesktopThumbnailItem() -{ -} - -void DesktopThumbnailItem::setDesktop(int desktop) -{ - desktop = qBound(1, desktop, VirtualDesktopManager::self()->count()); - if (desktop == m_desktop) { - return; - } - m_desktop = desktop; - update(); - Q_EMIT desktopChanged(m_desktop); -} - -void DesktopThumbnailItem::paint(QPainter *painter) -{ - Q_UNUSED(painter) - if (effects) { - return; - } - // TODO: render icon -} - -void DesktopThumbnailItem::repaint(EffectWindow *w) -{ - if (w->isOnDesktop(m_desktop)) { - update(); - } -} - -} // namespace KWin diff --git a/src/thumbnailitem.h b/src/thumbnailitem.h deleted file mode 100644 index e9395afe29..0000000000 --- a/src/thumbnailitem.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - KWin - the KDE window manager - This file is part of the KDE project. - - SPDX-FileCopyrightText: 2011 Martin Gräßlin - - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#ifndef KWIN_THUMBNAILITEM_H -#define KWIN_THUMBNAILITEM_H - -#include -#include -#include -#include - -namespace KWin -{ - -class AbstractClient; -class EffectWindow; -class EffectWindowImpl; - -class AbstractThumbnailItem : public QQuickPaintedItem -{ - Q_OBJECT - Q_PROPERTY(qreal brightness READ brightness WRITE setBrightness NOTIFY brightnessChanged) - Q_PROPERTY(qreal saturation READ saturation WRITE setSaturation NOTIFY saturationChanged) - Q_PROPERTY(QQuickItem *clipTo READ clipTo WRITE setClipTo NOTIFY clipToChanged) -public: - ~AbstractThumbnailItem() override; - qreal brightness() const; - qreal saturation() const; - QQuickItem *clipTo() const; - -public Q_SLOTS: - void setBrightness(qreal brightness); - void setSaturation(qreal saturation); - void setClipTo(QQuickItem *clip); - -Q_SIGNALS: - void brightnessChanged(); - void saturationChanged(); - void clipToChanged(); - -protected: - explicit AbstractThumbnailItem(QQuickItem *parent = nullptr); - -protected Q_SLOTS: - virtual void repaint(KWin::EffectWindow* w) = 0; - -private Q_SLOTS: - void init(); - void effectWindowAdded(); - void compositingToggled(); - -private: - void findParentEffectWindow(); - QPointer m_parent; - qreal m_brightness; - qreal m_saturation; - QPointer m_clipToItem; -}; - -class WindowThumbnailItem : public AbstractThumbnailItem -{ - Q_OBJECT - Q_PROPERTY(QUuid wId READ wId WRITE setWId NOTIFY wIdChanged SCRIPTABLE true) - Q_PROPERTY(KWin::AbstractClient *client READ client WRITE setClient NOTIFY clientChanged) -public: - explicit WindowThumbnailItem(QQuickItem *parent = nullptr); - ~WindowThumbnailItem() override; - - QUuid wId() const { - return m_wId; - } - void setWId(const QUuid &wId); - AbstractClient *client() const; - void setClient(AbstractClient *client); - void paint(QPainter *painter) override; -Q_SIGNALS: - void wIdChanged(const QUuid &wid); - void clientChanged(); -protected Q_SLOTS: - void repaint(KWin::EffectWindow* w) override; -private: - QUuid m_wId; - AbstractClient *m_client; -}; - -class DesktopThumbnailItem : public AbstractThumbnailItem -{ - Q_OBJECT - Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) -public: - DesktopThumbnailItem(QQuickItem *parent = nullptr); - ~DesktopThumbnailItem() override; - - int desktop() const { - return m_desktop; - } - void setDesktop(int desktop); - void paint(QPainter *painter) override; -Q_SIGNALS: - void desktopChanged(int desktop); -protected Q_SLOTS: - void repaint(KWin::EffectWindow* w) override; -private: - int m_desktop; -}; - -inline -qreal AbstractThumbnailItem::brightness() const -{ - return m_brightness; -} - -inline -qreal AbstractThumbnailItem::saturation() const -{ - return m_saturation; -} - -inline -QQuickItem* AbstractThumbnailItem::clipTo() const -{ - return m_clipToItem.data(); -} - -inline -AbstractClient *WindowThumbnailItem::client() const -{ - return m_client; -} - -} // KWin - -#endif // KWIN_THUMBNAILITEM_H