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