File indexing completed on 2025-03-16 11:21:24
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