1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "kwineffectquickview.h"
11 
12 #include "kwinglutils.h"
13 #include "logging_p.h"
14 
15 #include <QQmlEngine>
16 #include <QQuickItem>
17 #include <QQmlContext>
18 #include <QQmlComponent>
19 #include <QQuickView>
20 #include <QQuickRenderControl>
21 
22 #include <QOffscreenSurface>
23 #include <QOpenGLContext>
24 #include <QOpenGLFramebufferObject>
25 #include <QTimer>
26 
27 #include <KDeclarative/QmlObjectSharedEngine>
28 
29 namespace KWin
30 {
31 
32 class EffectQuickRenderControl : public QQuickRenderControl
33 {
34     Q_OBJECT
35 
36 public:
EffectQuickRenderControl(QWindow * renderWindow,QObject * parent=nullptr)37     explicit EffectQuickRenderControl(QWindow *renderWindow, QObject *parent = nullptr)
38         : QQuickRenderControl(parent)
39         , m_renderWindow(renderWindow)
40     {
41     }
42 
renderWindow(QPoint * offset)43     QWindow *renderWindow(QPoint *offset) override
44     {
45         if (offset) {
46             *offset = QPoint(0, 0);
47         }
48         return m_renderWindow;
49     }
50 
51 private:
52     QPointer<QWindow> m_renderWindow;
53 };
54 
55 class Q_DECL_HIDDEN EffectQuickView::Private
56 {
57 public:
58     QQuickWindow *m_view;
59     QQuickRenderControl *m_renderControl;
60     QScopedPointer<QOpenGLContext> m_glcontext;
61     QScopedPointer<QOffscreenSurface> m_offscreenSurface;
62     QScopedPointer<QOpenGLFramebufferObject> m_fbo;
63 
64     QTimer *m_repaintTimer;
65     QImage m_image;
66     QScopedPointer<GLTexture> m_textureExport;
67     // if we should capture a QImage after rendering into our BO.
68     // Used for either software QtQuick rendering and nonGL kwin rendering
69     bool m_useBlit = false;
70     bool m_visible = true;
71     bool m_automaticRepaint = true;
72 
73     void releaseResources();
74 };
75 
76 class Q_DECL_HIDDEN EffectQuickScene::Private
77 {
78 public:
79     KDeclarative::QmlObjectSharedEngine *qmlObject = nullptr;
80 };
81 
EffectQuickView(QObject * parent)82 EffectQuickView::EffectQuickView(QObject *parent)
83     : EffectQuickView(parent, effects ? ExportMode::Texture : ExportMode::Image)
84 {
85 }
86 
EffectQuickView(QObject * parent,ExportMode exportMode)87 EffectQuickView::EffectQuickView(QObject *parent, ExportMode exportMode)
88     : EffectQuickView(parent, nullptr, exportMode)
89 {
90 }
91 
EffectQuickView(QObject * parent,QWindow * renderWindow)92 EffectQuickView::EffectQuickView(QObject *parent, QWindow *renderWindow)
93     : EffectQuickView(parent, renderWindow, effects ? ExportMode::Texture : ExportMode::Image)
94 {
95 }
96 
EffectQuickView(QObject * parent,QWindow * renderWindow,ExportMode exportMode)97 EffectQuickView::EffectQuickView(QObject *parent, QWindow *renderWindow, ExportMode exportMode)
98     : QObject(parent)
99     , d(new EffectQuickView::Private)
100 {
101     d->m_renderControl = new EffectQuickRenderControl(renderWindow, this);
102 
103     d->m_view = new QQuickWindow(d->m_renderControl);
104     d->m_view->setFlags(Qt::FramelessWindowHint);
105     d->m_view->setColor(Qt::transparent);
106 
107     if (exportMode == ExportMode::Image) {
108         d->m_useBlit = true;
109     }
110 
111     const bool usingGl = d->m_view->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL;
112 
113     if (!usingGl) {
114         qCDebug(LIBKWINEFFECTS) << "QtQuick Software rendering mode detected";
115         d->m_useBlit = true;
116         d->m_renderControl->initialize(nullptr);
117     } else {
118         QSurfaceFormat format;
119         format.setOption(QSurfaceFormat::ResetNotification);
120         format.setDepthBufferSize(16);
121         format.setStencilBufferSize(8);
122 
123         auto shareContext = QOpenGLContext::globalShareContext();
124         d->m_glcontext.reset(new QOpenGLContext);
125         d->m_glcontext->setShareContext(shareContext);
126         d->m_glcontext->setFormat(format);
127         d->m_glcontext->create();
128 
129         // and the offscreen surface
130         d->m_offscreenSurface.reset(new QOffscreenSurface);
131         d->m_offscreenSurface->setFormat(d->m_glcontext->format());
132         d->m_offscreenSurface->create();
133 
134         d->m_glcontext->makeCurrent(d->m_offscreenSurface.data());
135         d->m_renderControl->initialize(d->m_glcontext.data());
136         d->m_glcontext->doneCurrent();
137 
138         // On Wayland, contexts are implicitly shared and QOpenGLContext::globalShareContext() is null.
139         if (shareContext && !d->m_glcontext->shareContext()) {
140             qCDebug(LIBKWINEFFECTS) << "Failed to create a shared context, falling back to raster rendering";
141             // still render via GL, but blit for presentation
142             d->m_useBlit = true;
143         }
144     }
145 
146     auto updateSize = [this]() { contentItem()->setSize(d->m_view->size()); };
147     updateSize();
148     connect(d->m_view, &QWindow::widthChanged, this, updateSize);
149     connect(d->m_view, &QWindow::heightChanged, this, updateSize);
150 
151     d->m_repaintTimer = new QTimer(this);
152     d->m_repaintTimer->setSingleShot(true);
153     d->m_repaintTimer->setInterval(10);
154 
155     connect(d->m_repaintTimer, &QTimer::timeout, this, &EffectQuickView::update);
156     connect(d->m_renderControl, &QQuickRenderControl::renderRequested, this, &EffectQuickView::handleRenderRequested);
157     connect(d->m_renderControl, &QQuickRenderControl::sceneChanged, this, &EffectQuickView::handleSceneChanged);
158 }
159 
~EffectQuickView()160 EffectQuickView::~EffectQuickView()
161 {
162     if (d->m_glcontext) {
163         // close the view whilst we have an active GL context
164         d->m_glcontext->makeCurrent(d->m_offscreenSurface.data());
165     }
166 
167     delete d->m_renderControl; // Always delete render control first.
168     d->m_renderControl = nullptr;
169 
170     delete d->m_view;
171     d->m_view = nullptr;
172 }
173 
automaticRepaint() const174 bool EffectQuickView::automaticRepaint() const
175 {
176     return d->m_automaticRepaint;
177 }
178 
setAutomaticRepaint(bool set)179 void EffectQuickView::setAutomaticRepaint(bool set)
180 {
181     if (d->m_automaticRepaint != set) {
182         d->m_automaticRepaint = set;
183 
184         // If there's an in-flight update, disable it.
185         if (!d->m_automaticRepaint) {
186             d->m_repaintTimer->stop();
187         }
188     }
189 }
190 
handleSceneChanged()191 void EffectQuickView::handleSceneChanged()
192 {
193     if (d->m_automaticRepaint) {
194         d->m_repaintTimer->start();
195     }
196     Q_EMIT sceneChanged();
197 }
198 
handleRenderRequested()199 void EffectQuickView::handleRenderRequested()
200 {
201     if (d->m_automaticRepaint) {
202         d->m_repaintTimer->start();
203     }
204     Q_EMIT renderRequested();
205 }
206 
update()207 void EffectQuickView::update()
208 {
209     if (!d->m_visible) {
210         return;
211     }
212     if (d->m_view->size().isEmpty()) {
213         return;
214     }
215 
216     bool usingGl = d->m_glcontext;
217 
218     if (usingGl) {
219         if (!d->m_glcontext->makeCurrent(d->m_offscreenSurface.data())) {
220             // probably a context loss event, kwin is about to reset all the effects anyway
221             return;
222         }
223 
224         const QSize nativeSize = d->m_view->size() * d->m_view->effectiveDevicePixelRatio();
225         if (d->m_fbo.isNull() || d->m_fbo->size() != nativeSize) {
226             d->m_textureExport.reset(nullptr);
227             d->m_fbo.reset(new QOpenGLFramebufferObject(nativeSize, QOpenGLFramebufferObject::CombinedDepthStencil));
228             if (!d->m_fbo->isValid()) {
229                 d->m_fbo.reset();
230                 d->m_glcontext->doneCurrent();
231                 return;
232             }
233         }
234         d->m_view->setRenderTarget(d->m_fbo.data());
235     }
236 
237     d->m_renderControl->polishItems();
238     d->m_renderControl->sync();
239 
240     d->m_renderControl->render();
241     if (usingGl) {
242         d->m_view->resetOpenGLState();
243     }
244 
245     if (d->m_useBlit) {
246         d->m_image = d->m_renderControl->grab();
247     }
248 
249     if (usingGl) {
250         QOpenGLFramebufferObject::bindDefault();
251         d->m_glcontext->doneCurrent();
252     }
253     Q_EMIT repaintNeeded();
254 }
255 
forwardMouseEvent(QEvent * e)256 void EffectQuickView::forwardMouseEvent(QEvent *e)
257 {
258     if (!d->m_visible) {
259         return;
260     }
261     switch (e->type()) {
262     case QEvent::MouseMove:
263     case QEvent::MouseButtonPress:
264     case QEvent::MouseButtonRelease:
265     case QEvent::MouseButtonDblClick:
266     {
267         QMouseEvent *me = static_cast<QMouseEvent *>(e);
268         const QPoint widgetPos = d->m_view->mapFromGlobal(me->pos());
269         QMouseEvent cloneEvent(me->type(), widgetPos, me->pos(), me->button(), me->buttons(), me->modifiers());
270         QCoreApplication::sendEvent(d->m_view, &cloneEvent);
271         e->setAccepted(cloneEvent.isAccepted());
272         return;
273     }
274     case QEvent::HoverEnter:
275     case QEvent::HoverLeave:
276     case QEvent::HoverMove:
277     {
278         QHoverEvent *he = static_cast<QHoverEvent *>(e);
279         const QPointF widgetPos = d->m_view->mapFromGlobal(he->pos());
280         const QPointF oldWidgetPos = d->m_view->mapFromGlobal(he->oldPos());
281         QHoverEvent cloneEvent(he->type(), widgetPos, oldWidgetPos, he->modifiers());
282         QCoreApplication::sendEvent(d->m_view, &cloneEvent);
283         e->setAccepted(cloneEvent.isAccepted());
284         return;
285     }
286     case QEvent::Wheel:
287     {
288         QWheelEvent *we = static_cast<QWheelEvent *>(e);
289         const QPointF widgetPos = d->m_view->mapFromGlobal(we->pos());
290         QWheelEvent cloneEvent(widgetPos, we->globalPosF(), we->pixelDelta(), we->angleDelta(), we->buttons(),
291                                we->modifiers(), we->phase(), we->inverted());
292         QCoreApplication::sendEvent(d->m_view, &cloneEvent);
293         e->setAccepted(cloneEvent.isAccepted());
294         return;
295     }
296     default:
297         return;
298     }
299 }
300 
forwardKeyEvent(QKeyEvent * keyEvent)301 void EffectQuickView::forwardKeyEvent(QKeyEvent *keyEvent)
302 {
303     if (!d->m_visible) {
304         return;
305     }
306     QCoreApplication::sendEvent(d->m_view, keyEvent);
307 }
308 
geometry() const309 QRect EffectQuickView::geometry() const
310 {
311     return d->m_view->geometry();
312 }
313 
contentItem() const314 QQuickItem *EffectQuickView::contentItem() const
315 {
316     return d->m_view->contentItem();
317 }
318 
setVisible(bool visible)319 void EffectQuickView::setVisible(bool visible)
320 {
321     if (d->m_visible == visible) {
322         return;
323     }
324     d->m_visible = visible;
325 
326     if (visible){
327         Q_EMIT d->m_renderControl->renderRequested();
328     } else {
329         // deferred to not change GL context
330         QTimer::singleShot(0, this, [this]() {
331             d->releaseResources();
332         });
333     }
334 }
335 
isVisible() const336 bool EffectQuickView::isVisible() const
337 {
338     return d->m_visible;
339 }
340 
show()341 void EffectQuickView::show()
342 {
343     setVisible(true);
344 }
345 
hide()346 void EffectQuickView::hide()
347 {
348     setVisible(false);
349 }
350 
bufferAsTexture()351 GLTexture *EffectQuickView::bufferAsTexture()
352 {
353     if (d->m_useBlit) {
354         if (d->m_image.isNull()) {
355             return nullptr;
356         }
357         d->m_textureExport.reset(new GLTexture(d->m_image));
358     } else {
359         if (!d->m_fbo) {
360             return nullptr;
361         }
362         if (!d->m_textureExport) {
363             d->m_textureExport.reset(new GLTexture(d->m_fbo->texture(), d->m_fbo->format().internalTextureFormat(), d->m_fbo->size()));
364         }
365     }
366     return d->m_textureExport.data();
367 }
368 
bufferAsImage() const369 QImage EffectQuickView::bufferAsImage() const
370 {
371     return d->m_image;
372 }
373 
size() const374 QSize EffectQuickView::size() const
375 {
376     return d->m_view->geometry().size();
377 }
378 
setGeometry(const QRect & rect)379 void EffectQuickView::setGeometry(const QRect &rect)
380 {
381     const QRect oldGeometry = d->m_view->geometry();
382     d->m_view->setGeometry(rect);
383     Q_EMIT geometryChanged(oldGeometry, rect);
384 }
385 
releaseResources()386 void EffectQuickView::Private::releaseResources()
387 {
388     if (m_glcontext) {
389         m_glcontext->makeCurrent(m_offscreenSurface.data());
390         m_view->releaseResources();
391         m_glcontext->doneCurrent();
392     } else {
393         m_view->releaseResources();
394     }
395 }
396 
EffectQuickScene(QObject * parent)397 EffectQuickScene::EffectQuickScene(QObject *parent)
398     : EffectQuickView(parent)
399     , d(new EffectQuickScene::Private)
400 {
401     d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
402 }
403 
EffectQuickScene(QObject * parent,QWindow * renderWindow)404 EffectQuickScene::EffectQuickScene(QObject *parent, QWindow *renderWindow)
405     : EffectQuickView(parent, renderWindow)
406     , d(new EffectQuickScene::Private)
407 {
408     d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
409 }
410 
EffectQuickScene(QObject * parent,QWindow * renderWindow,ExportMode exportMode)411 EffectQuickScene::EffectQuickScene(QObject *parent, QWindow *renderWindow, ExportMode exportMode)
412     : EffectQuickView(parent, renderWindow, exportMode)
413     , d(new EffectQuickScene::Private)
414 {
415     d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
416 }
417 
EffectQuickScene(QObject * parent,EffectQuickView::ExportMode exportMode)418 EffectQuickScene::EffectQuickScene(QObject *parent, EffectQuickView::ExportMode exportMode)
419     : EffectQuickView(parent, exportMode)
420     , d(new EffectQuickScene::Private)
421 {
422     d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
423 }
424 
~EffectQuickScene()425 EffectQuickScene::~EffectQuickScene()
426 {
427     delete d->qmlObject;
428 }
429 
setSource(const QUrl & source)430 void EffectQuickScene::setSource(const QUrl &source)
431 {
432     d->qmlObject->setSource(source);
433 
434     QQuickItem *item = rootItem();
435     if (!item) {
436         qCDebug(LIBKWINEFFECTS) << "Could not load effect quick view" << source;
437         return;
438     }
439     item->setParentItem(contentItem());
440 
441     auto updateSize = [item, this]() { item->setSize(contentItem()->size()); };
442     updateSize();
443     connect(contentItem(), &QQuickItem::widthChanged, item, updateSize);
444     connect(contentItem(), &QQuickItem::heightChanged, item, updateSize);
445 }
446 
rootContext() const447 QQmlContext *EffectQuickScene::rootContext() const
448 {
449     return d->qmlObject->rootContext();
450 }
451 
rootItem() const452 QQuickItem *EffectQuickScene::rootItem() const
453 {
454     return qobject_cast<QQuickItem *>(d->qmlObject->rootObject());
455 }
456 
457 } // namespace KWin
458 
459 #include "kwineffectquickview.moc"
460