File indexing completed on 2024-05-19 16:34:52

0001 /*
0002     SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
0003     SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
0004     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "windowthumbnailitem.h"
0010 #include "composite.h"
0011 #include "core/renderbackend.h"
0012 #include "effects.h"
0013 #include "scene/itemrenderer.h"
0014 #include "scene/windowitem.h"
0015 #include "scene/workspacescene.h"
0016 #include "scripting_logging.h"
0017 #include "virtualdesktops.h"
0018 #include "window.h"
0019 #include "workspace.h"
0020 
0021 #include <kwingltexture.h>
0022 #include <kwinglutils.h>
0023 
0024 #include <QQuickWindow>
0025 #include <QRunnable>
0026 #include <QSGImageNode>
0027 #include <QSGTextureProvider>
0028 
0029 namespace KWin
0030 {
0031 class ThumbnailTextureProvider : public QSGTextureProvider
0032 {
0033 public:
0034     explicit ThumbnailTextureProvider(QQuickWindow *window);
0035 
0036     QSGTexture *texture() const override;
0037     void setTexture(const std::shared_ptr<GLTexture> &nativeTexture);
0038     void setTexture(QSGTexture *texture);
0039 
0040 private:
0041     QQuickWindow *m_window;
0042     std::shared_ptr<GLTexture> m_nativeTexture;
0043     std::unique_ptr<QSGTexture> m_texture;
0044 };
0045 
0046 ThumbnailTextureProvider::ThumbnailTextureProvider(QQuickWindow *window)
0047     : m_window(window)
0048 {
0049 }
0050 
0051 QSGTexture *ThumbnailTextureProvider::texture() const
0052 {
0053     return m_texture.get();
0054 }
0055 
0056 void ThumbnailTextureProvider::setTexture(const std::shared_ptr<GLTexture> &nativeTexture)
0057 {
0058     if (m_nativeTexture != nativeTexture) {
0059         const GLuint textureId = nativeTexture->texture();
0060         m_nativeTexture = nativeTexture;
0061 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0062         m_texture.reset(m_window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture,
0063                                                                 &textureId, 0,
0064                                                                 nativeTexture->size(),
0065                                                                 QQuickWindow::TextureHasAlphaChannel));
0066 #else
0067         m_texture.reset(QNativeInterface::QSGOpenGLTexture::fromNative(textureId, m_window,
0068                                                                        nativeTexture->size(),
0069                                                                        QQuickWindow::TextureHasAlphaChannel));
0070 #endif
0071         m_texture->setFiltering(QSGTexture::Linear);
0072         m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge);
0073         m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge);
0074     }
0075 
0076     // The textureChanged signal must be emitted also if only texture data changes.
0077     Q_EMIT textureChanged();
0078 }
0079 
0080 void ThumbnailTextureProvider::setTexture(QSGTexture *texture)
0081 {
0082     m_nativeTexture = nullptr;
0083     m_texture.reset(texture);
0084     Q_EMIT textureChanged();
0085 }
0086 
0087 class ThumbnailTextureProviderCleanupJob : public QRunnable
0088 {
0089 public:
0090     explicit ThumbnailTextureProviderCleanupJob(ThumbnailTextureProvider *provider)
0091         : m_provider(provider)
0092     {
0093     }
0094 
0095     void run() override
0096     {
0097         m_provider.reset();
0098     }
0099 
0100 private:
0101     std::unique_ptr<ThumbnailTextureProvider> m_provider;
0102 };
0103 
0104 WindowThumbnailItem::WindowThumbnailItem(QQuickItem *parent)
0105     : QQuickItem(parent)
0106 {
0107     setFlag(ItemHasContents);
0108     updateFrameRenderingConnection();
0109 
0110     connect(Compositor::self(), &Compositor::aboutToToggleCompositing,
0111             this, &WindowThumbnailItem::destroyOffscreenTexture);
0112     connect(Compositor::self(), &Compositor::compositingToggled,
0113             this, &WindowThumbnailItem::updateFrameRenderingConnection);
0114 }
0115 
0116 WindowThumbnailItem::~WindowThumbnailItem()
0117 {
0118     destroyOffscreenTexture();
0119 
0120     if (m_provider) {
0121         if (window()) {
0122             window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider),
0123                                         QQuickWindow::AfterSynchronizingStage);
0124         } else {
0125             qCCritical(KWIN_SCRIPTING) << "Can't destroy thumbnail texture provider because window is null";
0126         }
0127     }
0128 }
0129 
0130 void WindowThumbnailItem::releaseResources()
0131 {
0132     if (m_provider) {
0133         window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider),
0134                                     QQuickWindow::AfterSynchronizingStage);
0135         m_provider = nullptr;
0136     }
0137 }
0138 
0139 void WindowThumbnailItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
0140 {
0141     if (change == QQuickItem::ItemSceneChange) {
0142         updateFrameRenderingConnection();
0143     }
0144     QQuickItem::itemChange(change, value);
0145 }
0146 
0147 bool WindowThumbnailItem::isTextureProvider() const
0148 {
0149     return true;
0150 }
0151 
0152 QSGTextureProvider *WindowThumbnailItem::textureProvider() const
0153 {
0154     if (QQuickItem::isTextureProvider()) {
0155         return QQuickItem::textureProvider();
0156     }
0157     if (!m_provider) {
0158         m_provider = new ThumbnailTextureProvider(window());
0159     }
0160     return m_provider;
0161 }
0162 
0163 void WindowThumbnailItem::updateFrameRenderingConnection()
0164 {
0165     disconnect(m_frameRenderingConnection);
0166 
0167     if (!Compositor::compositing()) {
0168         return;
0169     }
0170     if (!window()) {
0171         return;
0172     }
0173 
0174     if (useGlThumbnails()) {
0175         m_frameRenderingConnection = connect(Compositor::self()->scene(), &WorkspaceScene::preFrameRender, this, &WindowThumbnailItem::updateOffscreenTexture);
0176     }
0177 }
0178 
0179 bool WindowThumbnailItem::useGlThumbnails()
0180 {
0181     static bool qtQuickIsSoftware = QStringList({QStringLiteral("software"), QStringLiteral("softwarecontext")}).contains(QQuickWindow::sceneGraphBackend());
0182     return Compositor::self()->backend()->compositingType() == OpenGLCompositing && !qtQuickIsSoftware;
0183 }
0184 
0185 QSize WindowThumbnailItem::sourceSize() const
0186 {
0187     return m_sourceSize;
0188 }
0189 
0190 void WindowThumbnailItem::setSourceSize(const QSize &sourceSize)
0191 {
0192     if (m_sourceSize != sourceSize) {
0193         m_sourceSize = sourceSize;
0194         invalidateOffscreenTexture();
0195         Q_EMIT sourceSizeChanged();
0196     }
0197 }
0198 
0199 void WindowThumbnailItem::destroyOffscreenTexture()
0200 {
0201     if (!Compositor::compositing()) {
0202         return;
0203     }
0204     if (!useGlThumbnails()) {
0205         return;
0206     }
0207 
0208     if (m_offscreenTexture) {
0209         WorkspaceScene *scene = Compositor::self()->scene();
0210         scene->makeOpenGLContextCurrent();
0211         m_offscreenTarget.reset();
0212         m_offscreenTexture.reset();
0213 
0214         if (m_acquireFence) {
0215             glDeleteSync(m_acquireFence);
0216             m_acquireFence = 0;
0217         }
0218         scene->doneOpenGLContextCurrent();
0219     }
0220 }
0221 
0222 QSGNode *WindowThumbnailItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
0223 {
0224     if (Compositor::compositing() && !m_offscreenTexture) {
0225         return oldNode;
0226     }
0227 
0228     // Wait for rendering commands to the offscreen texture complete if there are any.
0229     if (m_acquireFence) {
0230         glClientWaitSync(m_acquireFence, GL_SYNC_FLUSH_COMMANDS_BIT, 5000);
0231         glDeleteSync(m_acquireFence);
0232         m_acquireFence = 0;
0233     }
0234 
0235     if (!m_provider) {
0236         m_provider = new ThumbnailTextureProvider(window());
0237     }
0238 
0239     if (m_offscreenTexture) {
0240         m_provider->setTexture(m_offscreenTexture);
0241     } else {
0242         const QImage placeholderImage = fallbackImage();
0243         m_provider->setTexture(window()->createTextureFromImage(placeholderImage));
0244         m_devicePixelRatio = placeholderImage.devicePixelRatio();
0245     }
0246 
0247     QSGImageNode *node = static_cast<QSGImageNode *>(oldNode);
0248     if (!node) {
0249         node = window()->createImageNode();
0250         node->setFiltering(QSGTexture::Linear);
0251     }
0252     node->setTexture(m_provider->texture());
0253 
0254     if (m_offscreenTexture && m_offscreenTexture->isYInverted()) {
0255         node->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically);
0256     } else {
0257         node->setTextureCoordinatesTransform(QSGImageNode::NoTransform);
0258     }
0259 
0260     node->setRect(paintedRect());
0261 
0262     return node;
0263 }
0264 
0265 qreal WindowThumbnailItem::saturation() const
0266 {
0267     return 1;
0268 }
0269 
0270 void WindowThumbnailItem::setSaturation(qreal saturation)
0271 {
0272     qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.saturation is removed. Use a shader effect to change saturation";
0273 }
0274 
0275 qreal WindowThumbnailItem::brightness() const
0276 {
0277     return 1;
0278 }
0279 
0280 void WindowThumbnailItem::setBrightness(qreal brightness)
0281 {
0282     qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.brightness is removed. Use a shader effect to change brightness";
0283 }
0284 
0285 QQuickItem *WindowThumbnailItem::clipTo() const
0286 {
0287     return nullptr;
0288 }
0289 
0290 void WindowThumbnailItem::setClipTo(QQuickItem *clip)
0291 {
0292     qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.clipTo is removed and it has no replacements";
0293 }
0294 
0295 QUuid WindowThumbnailItem::wId() const
0296 {
0297     return m_wId;
0298 }
0299 
0300 void WindowThumbnailItem::setWId(const QUuid &wId)
0301 {
0302     if (m_wId == wId) {
0303         return;
0304     }
0305     m_wId = wId;
0306     if (!m_wId.isNull()) {
0307         setClient(workspace()->findToplevel(wId));
0308     } else if (m_client) {
0309         m_client = nullptr;
0310         updateImplicitSize();
0311         Q_EMIT clientChanged();
0312     }
0313     Q_EMIT wIdChanged();
0314 }
0315 
0316 Window *WindowThumbnailItem::client() const
0317 {
0318     return m_client;
0319 }
0320 
0321 void WindowThumbnailItem::setClient(Window *client)
0322 {
0323     if (m_client == client) {
0324         return;
0325     }
0326     if (m_client) {
0327         disconnect(m_client, &Window::frameGeometryChanged,
0328                    this, &WindowThumbnailItem::invalidateOffscreenTexture);
0329         disconnect(m_client, &Window::damaged,
0330                    this, &WindowThumbnailItem::invalidateOffscreenTexture);
0331         disconnect(m_client, &Window::frameGeometryChanged,
0332                    this, &WindowThumbnailItem::updateImplicitSize);
0333     }
0334     m_client = client;
0335     if (m_client) {
0336         connect(m_client, &Window::frameGeometryChanged,
0337                 this, &WindowThumbnailItem::invalidateOffscreenTexture);
0338         connect(m_client, &Window::damaged,
0339                 this, &WindowThumbnailItem::invalidateOffscreenTexture);
0340         connect(m_client, &Window::frameGeometryChanged,
0341                 this, &WindowThumbnailItem::updateImplicitSize);
0342         setWId(m_client->internalId());
0343     } else {
0344         setWId(QUuid());
0345     }
0346     invalidateOffscreenTexture();
0347     updateImplicitSize();
0348     Q_EMIT clientChanged();
0349 }
0350 
0351 void WindowThumbnailItem::updateImplicitSize()
0352 {
0353     QSize frameSize;
0354     if (m_client) {
0355         frameSize = m_client->frameGeometry().toAlignedRect().size();
0356     }
0357     setImplicitSize(frameSize.width(), frameSize.height());
0358 }
0359 
0360 QImage WindowThumbnailItem::fallbackImage() const
0361 {
0362     if (m_client) {
0363         return m_client->icon().pixmap(window(), boundingRect().size().toSize()).toImage();
0364     }
0365     return QImage();
0366 }
0367 
0368 static QRectF centeredSize(const QRectF &boundingRect, const QSizeF &size)
0369 {
0370     const QSizeF scaled = size.scaled(boundingRect.size(), Qt::KeepAspectRatio);
0371     const qreal x = boundingRect.x() + (boundingRect.width() - scaled.width()) / 2;
0372     const qreal y = boundingRect.y() + (boundingRect.height() - scaled.height()) / 2;
0373     return QRectF(QPointF(x, y), scaled);
0374 }
0375 
0376 QRectF WindowThumbnailItem::paintedRect() const
0377 {
0378     if (!m_client) {
0379         return QRectF();
0380     }
0381     if (!m_offscreenTexture) {
0382         const QSizeF iconSize = m_client->icon().actualSize(window(), boundingRect().size().toSize());
0383         return centeredSize(boundingRect(), iconSize);
0384     }
0385 
0386     const QRectF visibleGeometry = m_client->visibleGeometry();
0387     const QRectF frameGeometry = m_client->frameGeometry();
0388     const QSizeF scaled = QSizeF(frameGeometry.size()).scaled(boundingRect().size(), Qt::KeepAspectRatio);
0389 
0390     const qreal xScale = scaled.width() / frameGeometry.width();
0391     const qreal yScale = scaled.height() / frameGeometry.height();
0392 
0393     QRectF paintedRect(boundingRect().x() + (boundingRect().width() - scaled.width()) / 2,
0394                        boundingRect().y() + (boundingRect().height() - scaled.height()) / 2,
0395                        visibleGeometry.width() * xScale,
0396                        visibleGeometry.height() * yScale);
0397 
0398     paintedRect.moveLeft(paintedRect.x() + (visibleGeometry.x() - frameGeometry.x()) * xScale);
0399     paintedRect.moveTop(paintedRect.y() + (visibleGeometry.y() - frameGeometry.y()) * yScale);
0400 
0401     return paintedRect;
0402 }
0403 
0404 void WindowThumbnailItem::invalidateOffscreenTexture()
0405 {
0406     m_dirty = true;
0407     update();
0408 }
0409 
0410 void WindowThumbnailItem::updateOffscreenTexture()
0411 {
0412     if (m_acquireFence || !m_dirty || !m_client) {
0413         return;
0414     }
0415     Q_ASSERT(window());
0416 
0417     const QRectF geometry = m_client->visibleGeometry();
0418     QSize textureSize = geometry.toAlignedRect().size();
0419     if (sourceSize().width() > 0) {
0420         textureSize.setWidth(sourceSize().width());
0421     }
0422     if (sourceSize().height() > 0) {
0423         textureSize.setHeight(sourceSize().height());
0424     }
0425 
0426     m_devicePixelRatio = window()->devicePixelRatio();
0427     textureSize *= m_devicePixelRatio;
0428 
0429     if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) {
0430         m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize));
0431         m_offscreenTexture->setFilter(GL_LINEAR);
0432         m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
0433         m_offscreenTarget.reset(new GLFramebuffer(m_offscreenTexture.get()));
0434     }
0435 
0436     GLFramebuffer::pushFramebuffer(m_offscreenTarget.get());
0437     glClearColor(0.0, 0.0, 0.0, 0.0);
0438     glClear(GL_COLOR_BUFFER_BIT);
0439 
0440     auto scale = Compositor::self()->scene()->renderer()->renderTargetScale();
0441 
0442     QMatrix4x4 projectionMatrix;
0443     projectionMatrix.ortho(geometry.x() * scale, (geometry.x() + geometry.width()) * scale,
0444                            geometry.y() * scale, (geometry.y() + geometry.height()) * scale, -1, 1);
0445 
0446     WindowPaintData data;
0447     data.setProjectionMatrix(projectionMatrix);
0448 
0449     // The thumbnail must be rendered using kwin's opengl context as VAOs are not
0450     // shared across contexts. Unfortunately, this also introduces a latency of 1
0451     // frame, which is not ideal, but it is acceptable for things such as thumbnails.
0452     const int mask = Scene::PAINT_WINDOW_TRANSFORMED;
0453     Compositor::self()->scene()->renderer()->renderItem(m_client->windowItem(), mask, infiniteRegion(), data);
0454     GLFramebuffer::popFramebuffer();
0455 
0456     // The fence is needed to avoid the case where qtquick renderer starts using
0457     // the texture while all rendering commands to it haven't completed yet.
0458     m_dirty = false;
0459     m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
0460 
0461     // We know that the texture has changed, so schedule an item update.
0462     update();
0463 }
0464 
0465 } // namespace KWin