KWin
Loading...
Searching...
No Matches
windowthumbnailitem.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
3 SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
4 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
10#include "compositor.h"
11#include "core/renderbackend.h"
12#include "core/rendertarget.h"
13#include "core/renderviewport.h"
14#include "effect/effect.h"
16#include "scene/itemrenderer.h"
17#include "scene/windowitem.h"
19#include "scripting_logging.h"
20#include "window.h"
21#include "workspace.h"
22
23#include "opengl/gltexture.h"
24
25#include <QOpenGLContext>
26#include <QQuickWindow>
27#include <QRunnable>
28#include <QSGImageNode>
29#include <QSGTextureProvider>
30
31namespace KWin
32{
33
34static bool useGlThumbnails()
35{
36 static bool qtQuickIsSoftware = QStringList({QStringLiteral("software"), QStringLiteral("softwarecontext")}).contains(QQuickWindow::sceneGraphBackend());
37 return Compositor::self()->backend() && Compositor::self()->backend()->compositingType() == OpenGLCompositing && !qtQuickIsSoftware;
38}
39
41 : m_view(view)
42 , m_handle(handle)
43{
44 connect(handle, &Window::frameGeometryChanged, this, [this]() {
45 m_dirty = true;
46 Q_EMIT changed();
47 });
48 connect(handle, &Window::damaged, this, [this]() {
49 m_dirty = true;
50 Q_EMIT changed();
51 });
52
53 connect(Compositor::self()->scene(), &WorkspaceScene::preFrameRender, this, &WindowThumbnailSource::update);
54}
55
57{
58 if (!m_offscreenTexture) {
59 return;
60 }
61 if (!QOpenGLContext::currentContext()) {
63 }
64 m_offscreenTarget.reset();
65 m_offscreenTexture.reset();
66
67 if (m_acquireFence) {
68 glDeleteSync(m_acquireFence);
69 m_acquireFence = 0;
70 }
71}
72
73std::shared_ptr<WindowThumbnailSource> WindowThumbnailSource::getOrCreate(QQuickWindow *window, Window *handle)
74{
75 using WindowThumbnailSourceKey = std::pair<QQuickWindow *, Window *>;
76 const WindowThumbnailSourceKey key{window, handle};
77
78 static std::map<WindowThumbnailSourceKey, std::weak_ptr<WindowThumbnailSource>> sources;
79 auto &source = sources[key];
80 if (!source.expired()) {
81 return source.lock();
82 }
83
84 auto s = std::make_shared<WindowThumbnailSource>(window, handle);
85 source = s;
86
87 QObject::connect(handle, &Window::destroyed, [key]() {
88 sources.erase(key);
89 });
90 QObject::connect(window, &QQuickWindow::destroyed, [key]() {
91 sources.erase(key);
92 });
93 return s;
94}
95
97{
98 return Frame{
99 .texture = m_offscreenTexture,
100 .fence = std::exchange(m_acquireFence, nullptr),
101 };
102}
103
104void WindowThumbnailSource::update()
105{
106 if (m_acquireFence || !m_dirty || !m_handle) {
107 return;
108 }
109 Q_ASSERT(m_view);
110
111 const QRectF geometry = m_handle->visibleGeometry();
112 const qreal devicePixelRatio = m_view->devicePixelRatio();
113 const QSize textureSize = geometry.toAlignedRect().size() * devicePixelRatio;
114
115 if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) {
116 m_offscreenTexture = GLTexture::allocate(GL_RGBA8, textureSize);
117 if (!m_offscreenTexture) {
118 return;
119 }
120 m_offscreenTexture->setFilter(GL_LINEAR);
121 m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
122 m_offscreenTarget = std::make_unique<GLFramebuffer>(m_offscreenTexture.get());
123 }
124
125 RenderTarget offscreenRenderTarget(m_offscreenTarget.get());
126 RenderViewport offscreenViewport(geometry, devicePixelRatio, offscreenRenderTarget);
127 GLFramebuffer::pushFramebuffer(m_offscreenTarget.get());
128 glClearColor(0.0, 0.0, 0.0, 0.0);
129 glClear(GL_COLOR_BUFFER_BIT);
130
131 QMatrix4x4 projectionMatrix;
132 projectionMatrix.ortho(geometry.x() * devicePixelRatio, (geometry.x() + geometry.width()) * devicePixelRatio,
133 geometry.y() * devicePixelRatio, (geometry.y() + geometry.height()) * devicePixelRatio, -1, 1);
134
135 WindowPaintData data;
136 data.setProjectionMatrix(projectionMatrix);
137
138 // The thumbnail must be rendered using kwin's opengl context as VAOs are not
139 // shared across contexts. Unfortunately, this also introduces a latency of 1
140 // frame, which is not ideal, but it is acceptable for things such as thumbnails.
141 const int mask = Scene::PAINT_WINDOW_TRANSFORMED;
142 Compositor::self()->scene()->renderer()->renderItem(offscreenRenderTarget, offscreenViewport, m_handle->windowItem(), mask, infiniteRegion(), data);
144
145 // The fence is needed to avoid the case where qtquick renderer starts using
146 // the texture while all rendering commands to it haven't completed yet.
147 m_dirty = false;
148 m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
149
150 Q_EMIT changed();
151}
152
153class ThumbnailTextureProvider : public QSGTextureProvider
154{
155public:
156 explicit ThumbnailTextureProvider(QQuickWindow *window);
157
158 QSGTexture *texture() const override;
159 void setTexture(const std::shared_ptr<GLTexture> &nativeTexture);
160 void setTexture(QSGTexture *texture);
161
162private:
163 QQuickWindow *m_window;
164 std::shared_ptr<GLTexture> m_nativeTexture;
165 std::unique_ptr<QSGTexture> m_texture;
166};
167
169 : m_window(window)
170{
171}
172
174{
175 return m_texture.get();
176}
177
178void ThumbnailTextureProvider::setTexture(const std::shared_ptr<GLTexture> &nativeTexture)
179{
180 if (m_nativeTexture != nativeTexture) {
181 const GLuint textureId = nativeTexture->texture();
182 m_nativeTexture = nativeTexture;
183 m_texture.reset(QNativeInterface::QSGOpenGLTexture::fromNative(textureId, m_window,
184 nativeTexture->size(),
185 QQuickWindow::TextureHasAlphaChannel));
186 m_texture->setFiltering(QSGTexture::Linear);
187 m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge);
188 m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge);
189 }
190
191 // The textureChanged signal must be emitted also if only texture data changes.
192 Q_EMIT textureChanged();
193}
194
196{
197 m_nativeTexture = nullptr;
198 m_texture.reset(texture);
199 Q_EMIT textureChanged();
200}
201
203{
204public:
206 : m_provider(provider)
207 {
208 }
209
210 void run() override
211 {
212 m_provider.reset();
213 }
214
215private:
216 std::unique_ptr<ThumbnailTextureProvider> m_provider;
217};
218
220 : QQuickItem(parent)
221{
222 setFlag(ItemHasContents);
223
225 this, &WindowThumbnailItem::resetSource);
227 this, &WindowThumbnailItem::updateSource);
228}
229
231{
232 if (m_provider) {
233 if (window()) {
234 window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider),
235 QQuickWindow::AfterSynchronizingStage);
236 } else {
237 qCCritical(KWIN_SCRIPTING) << "Can't destroy thumbnail texture provider because window is null";
238 }
239 }
240}
241
243{
244 if (m_provider) {
245 window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider),
246 QQuickWindow::AfterSynchronizingStage);
247 m_provider = nullptr;
248 }
249}
250
251void WindowThumbnailItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
252{
253 if (change == QQuickItem::ItemSceneChange) {
254 updateSource();
255 }
256 QQuickItem::itemChange(change, value);
257}
258
260{
261 return true;
262}
263
264QSGTextureProvider *WindowThumbnailItem::textureProvider() const
265{
266 if (QQuickItem::isTextureProvider()) {
267 return QQuickItem::textureProvider();
268 }
269 if (!m_provider) {
270 m_provider = new ThumbnailTextureProvider(window());
271 }
272 return m_provider;
273}
274
275void WindowThumbnailItem::resetSource()
276{
277 m_source.reset();
278}
279
280void WindowThumbnailItem::updateSource()
281{
282 if (useGlThumbnails() && window() && m_client) {
283 m_source = WindowThumbnailSource::getOrCreate(window(), m_client);
284 connect(m_source.get(), &WindowThumbnailSource::changed, this, &WindowThumbnailItem::update);
285 } else {
286 m_source.reset();
287 }
288}
289
290QSGNode *WindowThumbnailItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
291{
293 if (!m_source) {
294 return oldNode;
295 }
296
297 auto [texture, acquireFence] = m_source->acquire();
298 if (!texture) {
299 return oldNode;
300 }
301
302 // Wait for rendering commands to the offscreen texture complete if there are any.
303 if (acquireFence) {
304 glWaitSync(acquireFence, 0, GL_TIMEOUT_IGNORED);
305 glDeleteSync(acquireFence);
306 }
307
308 if (!m_provider) {
309 m_provider = new ThumbnailTextureProvider(window());
310 }
311 m_provider->setTexture(texture);
312 } else {
313 if (!m_provider) {
314 m_provider = new ThumbnailTextureProvider(window());
315 }
316
317 const QImage placeholderImage = fallbackImage();
318 m_provider->setTexture(window()->createTextureFromImage(placeholderImage));
319 }
320
321 QSGImageNode *node = static_cast<QSGImageNode *>(oldNode);
322 if (!node) {
323 node = window()->createImageNode();
324 node->setFiltering(QSGTexture::Linear);
325 }
326 node->setTexture(m_provider->texture());
327 node->setTextureCoordinatesTransform(QSGImageNode::NoTransform);
328 node->setRect(paintedRect());
329
330 return node;
331}
332
333QUuid WindowThumbnailItem::wId() const
334{
335 return m_wId;
336}
337
338void WindowThumbnailItem::setWId(const QUuid &wId)
339{
340 if (m_wId == wId) {
341 return;
342 }
343 m_wId = wId;
344 if (!m_wId.isNull()) {
345 setClient(workspace()->findWindow(wId));
346 } else if (m_client) {
347 m_client = nullptr;
348 updateSource();
349 updateImplicitSize();
350 Q_EMIT clientChanged();
351 }
352
353 Q_EMIT wIdChanged();
354}
355
357{
358 return m_client;
359}
360
362{
363 if (m_client == client) {
364 return;
365 }
366 if (m_client) {
367 disconnect(m_client, &Window::frameGeometryChanged,
368 this, &WindowThumbnailItem::updateImplicitSize);
369 }
370 m_client = client;
371 if (m_client) {
372 connect(m_client, &Window::frameGeometryChanged,
373 this, &WindowThumbnailItem::updateImplicitSize);
374 setWId(m_client->internalId());
375 } else {
376 setWId(QUuid());
377 }
378 updateSource();
379 updateImplicitSize();
380 Q_EMIT clientChanged();
381}
382
383void WindowThumbnailItem::updateImplicitSize()
384{
385 QSize frameSize;
386 if (m_client) {
387 frameSize = m_client->frameGeometry().toAlignedRect().size();
388 }
389 setImplicitSize(frameSize.width(), frameSize.height());
390}
391
392QImage WindowThumbnailItem::fallbackImage() const
393{
394 if (m_client) {
395 return m_client->icon().pixmap(window(), boundingRect().size().toSize()).toImage();
396 }
397 return QImage();
398}
399
400static QRectF centeredSize(const QRectF &boundingRect, const QSizeF &size)
401{
402 const QSizeF scaled = size.scaled(boundingRect.size(), Qt::KeepAspectRatio);
403 const qreal x = boundingRect.x() + (boundingRect.width() - scaled.width()) / 2;
404 const qreal y = boundingRect.y() + (boundingRect.height() - scaled.height()) / 2;
405 return QRectF(QPointF(x, y), scaled);
406}
407
408QRectF WindowThumbnailItem::paintedRect() const
409{
410 if (!m_client) {
411 return QRectF();
412 }
414 const QSizeF iconSize = m_client->icon().actualSize(window(), boundingRect().size().toSize());
415 return centeredSize(boundingRect(), iconSize);
416 }
417
418 const QRectF visibleGeometry = m_client->visibleGeometry();
419 const QRectF frameGeometry = m_client->frameGeometry();
420 const QSizeF scaled = QSizeF(frameGeometry.size()).scaled(boundingRect().size(), Qt::KeepAspectRatio);
421
422 const qreal xScale = scaled.width() / frameGeometry.width();
423 const qreal yScale = scaled.height() / frameGeometry.height();
424
425 QRectF paintedRect(boundingRect().x() + (boundingRect().width() - scaled.width()) / 2,
426 boundingRect().y() + (boundingRect().height() - scaled.height()) / 2,
427 visibleGeometry.width() * xScale,
428 visibleGeometry.height() * yScale);
429
430 paintedRect.moveLeft(paintedRect.x() + (visibleGeometry.x() - frameGeometry.x()) * xScale);
431 paintedRect.moveTop(paintedRect.y() + (visibleGeometry.y() - frameGeometry.y()) * yScale);
432
433 return paintedRect;
434}
435
436} // namespace KWin
437
438#include "moc_windowthumbnailitem.cpp"
void compositingToggled(bool active)
void aboutToToggleCompositing()
WorkspaceScene * scene() const
Definition compositor.h:60
static bool compositing()
Static check to test whether the Compositor is available and active.
Definition compositor.h:78
RenderBackend * backend() const
Definition compositor.h:68
static Compositor * self()
static GLFramebuffer * popFramebuffer()
static void pushFramebuffer(GLFramebuffer *fbo)
static std::unique_ptr< GLTexture > allocate(GLenum internalFormat, const QSize &size, int levels=1)
virtual void renderItem(const RenderTarget &renderTarget, const RenderViewport &viewport, Item *item, int mask, const QRegion &region, const WindowPaintData &data)=0
virtual CompositingType compositingType() const =0
@ PAINT_WINDOW_TRANSFORMED
Definition scene.h:55
ItemRenderer * renderer() const
Definition scene.cpp:76
ThumbnailTextureProviderCleanupJob(ThumbnailTextureProvider *provider)
void setTexture(const std::shared_ptr< GLTexture > &nativeTexture)
QSGTexture * texture() const override
ThumbnailTextureProvider(QQuickWindow *window)
void frameGeometryChanged(const QRectF &oldGeometry)
void damaged(KWin::Window *window)
void setWId(qulonglong wId)
QSGNode * updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override
QSGTextureProvider * textureProvider() const override
bool isTextureProvider() const override
WindowThumbnailItem(QQuickItem *parent=nullptr)
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override
WindowThumbnailSource(QQuickWindow *view, Window *handle)
static std::shared_ptr< WindowThumbnailSource > getOrCreate(QQuickWindow *window, Window *handle)
virtual bool makeOpenGLContextCurrent()
KWIN_EXPORT QRect infiniteRegion()
Definition globals.h:234
Workspace * workspace()
Definition workspace.h:830
@ OpenGLCompositing
Definition globals.h:37
std::shared_ptr< GLTexture > texture