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