16#include <QGuiApplication>
17#include <QQmlComponent>
21#include <QQuickRenderControl>
25#include <QOffscreenSurface>
26#include <QOpenGLContext>
27#include <QOpenGLFramebufferObject>
28#include <QQuickGraphicsDevice>
29#include <QQuickOpenGLUtils>
30#include <QQuickRenderTarget>
32#include <private/qeventpoint_p.h>
40 std::unique_ptr<QQuickWindow>
m_view;
44 std::unique_ptr<QOpenGLFramebufferObject>
m_fbo;
51 bool m_useBlit =
false;
52 bool m_visible =
true;
53 bool m_hasAlphaChannel =
true;
54 bool m_automaticRepaint =
true;
59 ulong lastMousePressTime = 0;
60 Qt::MouseButton lastMousePressButton = Qt::NoButton;
81 d->m_renderControl = std::make_unique<QQuickRenderControl>();
83 d->m_view = std::make_unique<QQuickWindow>(d->m_renderControl.get());
84 Q_ASSERT(d->m_view->setProperty(
"_KWIN_WINDOW_IS_OFFSCREEN",
true) ||
true);
85 d->m_view->setFlags(Qt::FramelessWindowHint);
86 d->m_view->setColor(Qt::transparent);
88 d->m_hasAlphaChannel = alpha;
93 const bool usingGl = d->m_view->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL;
96 qCDebug(LIBKWINEFFECTS) <<
"QtQuick Software rendering mode detected";
98 d->m_renderControl->initialize();
101 format.setOption(QSurfaceFormat::ResetNotification);
102 format.setDepthBufferSize(16);
103 format.setStencilBufferSize(8);
105 format.setAlphaBufferSize(8);
108 d->m_view->setFormat(
format);
110 auto shareContext = QOpenGLContext::globalShareContext();
111 d->m_glcontext = std::make_unique<QOpenGLContext>();
112 d->m_glcontext->setShareContext(shareContext);
113 d->m_glcontext->setFormat(
format);
114 d->m_glcontext->create();
117 d->m_offscreenSurface = std::make_unique<QOffscreenSurface>();
118 d->m_offscreenSurface->setFormat(d->m_glcontext->format());
119 d->m_offscreenSurface->create();
121 d->m_glcontext->makeCurrent(d->m_offscreenSurface.get());
122 d->m_view->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(d->m_glcontext.get()));
123 d->m_renderControl->initialize();
124 d->m_glcontext->doneCurrent();
127 if (shareContext && !d->m_glcontext->shareContext()) {
128 qCDebug(LIBKWINEFFECTS) <<
"Failed to create a shared context, falling back to raster rendering";
134 auto updateSize = [
this]() {
138 connect(d->m_view.get(), &QWindow::widthChanged,
this, updateSize);
139 connect(d->m_view.get(), &QWindow::heightChanged,
this, updateSize);
141 d->m_repaintTimer = std::make_unique<QTimer>();
142 d->m_repaintTimer->setSingleShot(
true);
143 d->m_repaintTimer->setInterval(10);
146 connect(d->m_renderControl.get(), &QQuickRenderControl::renderRequested,
this, &OffscreenQuickView::handleRenderRequested);
147 connect(d->m_renderControl.get(), &QQuickRenderControl::sceneChanged,
this, &OffscreenQuickView::handleSceneChanged);
149 d->touchDevice =
new QPointingDevice(QStringLiteral(
"ForwardingTouchDevice"), {}, QInputDevice::DeviceType::TouchScreen, QPointingDevice::PointerType::Finger, QInputDevice::Capability::Position, 10, {});
154 disconnect(d->m_renderControl.get(), &QQuickRenderControl::renderRequested,
this, &OffscreenQuickView::handleRenderRequested);
155 disconnect(d->m_renderControl.get(), &QQuickRenderControl::sceneChanged,
this, &OffscreenQuickView::handleSceneChanged);
157 if (d->m_glcontext) {
159 d->m_glcontext->makeCurrent(d->m_offscreenSurface.get());
163 d->m_renderControl.reset();
168 return d->m_automaticRepaint;
173 if (d->m_automaticRepaint != set) {
174 d->m_automaticRepaint = set;
177 if (!d->m_automaticRepaint) {
178 d->m_repaintTimer->stop();
183void OffscreenQuickView::handleSceneChanged()
185 if (d->m_automaticRepaint) {
186 d->m_repaintTimer->start();
191void OffscreenQuickView::handleRenderRequested()
193 if (d->m_automaticRepaint) {
194 d->m_repaintTimer->start();
204 if (d->m_view->size().isEmpty()) {
208 bool usingGl = d->m_glcontext !=
nullptr;
211 if (!d->m_glcontext->makeCurrent(d->m_offscreenSurface.get())) {
216 const QSize nativeSize = d->m_view->size() * d->m_view->devicePixelRatio();
217 if (!d->m_fbo || d->m_fbo->size() != nativeSize) {
218 d->m_textureExport.reset(
nullptr);
220 QOpenGLFramebufferObjectFormat fboFormat;
221 fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
222 fboFormat.setInternalTextureFormat(GL_RGBA8);
224 d->m_fbo = std::make_unique<QOpenGLFramebufferObject>(nativeSize, fboFormat);
225 if (!d->m_fbo->isValid()) {
227 d->m_glcontext->doneCurrent();
232 QQuickRenderTarget renderTarget = QQuickRenderTarget::fromOpenGLTexture(d->m_fbo->texture(), d->m_fbo->size());
233 renderTarget.setDevicePixelRatio(d->m_view->devicePixelRatio());
235 d->m_view->setRenderTarget(renderTarget);
238 d->m_renderControl->polishItems();
239 d->m_renderControl->beginFrame();
240 d->m_renderControl->sync();
241 d->m_renderControl->render();
242 d->m_renderControl->endFrame();
245 QQuickOpenGLUtils::resetOpenGLState();
250 d->m_image = d->m_fbo->toImage();
251 d->m_image.setDevicePixelRatio(d->m_view->devicePixelRatio());
253 d->m_image = d->m_view->grabWindow();
258 QOpenGLFramebufferObject::bindDefault();
259 d->m_glcontext->doneCurrent();
270 case QEvent::MouseMove:
271 case QEvent::MouseButtonPress:
272 case QEvent::MouseButtonRelease: {
273 QMouseEvent *me =
static_cast<QMouseEvent *
>(e);
274 const QPoint widgetPos = d->m_view->mapFromGlobal(me->pos());
275 QMouseEvent cloneEvent(me->type(), widgetPos, me->pos(), me->button(), me->buttons(), me->modifiers());
276 cloneEvent.setAccepted(
false);
277 QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent);
278 e->setAccepted(cloneEvent.isAccepted());
280 if (e->type() == QEvent::MouseButtonPress) {
281 const ulong doubleClickInterval =
static_cast<ulong
>(QGuiApplication::styleHints()->mouseDoubleClickInterval());
282 const bool doubleClick = (me->timestamp() - d->lastMousePressTime < doubleClickInterval) && me->button() == d->lastMousePressButton;
283 d->lastMousePressTime = me->timestamp();
284 d->lastMousePressButton = me->button();
286 d->lastMousePressButton = Qt::NoButton;
287 QMouseEvent doubleClickEvent(QEvent::MouseButtonDblClick, me->localPos(), me->windowPos(), me->screenPos(), me->button(), me->buttons(), me->modifiers());
288 QCoreApplication::sendEvent(d->m_view.get(), &doubleClickEvent);
294 case QEvent::HoverEnter:
295 case QEvent::HoverLeave:
296 case QEvent::HoverMove: {
297 QHoverEvent *he =
static_cast<QHoverEvent *
>(e);
298 const QPointF widgetPos = d->m_view->mapFromGlobal(he->pos());
299 const QPointF oldWidgetPos = d->m_view->mapFromGlobal(he->oldPos());
300 QHoverEvent cloneEvent(he->type(), widgetPos, oldWidgetPos, he->modifiers());
301 cloneEvent.setAccepted(
false);
302 QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent);
303 e->setAccepted(cloneEvent.isAccepted());
306 case QEvent::Wheel: {
307 QWheelEvent *we =
static_cast<QWheelEvent *
>(e);
308 const QPointF widgetPos = d->m_view->mapFromGlobal(we->position().toPoint());
309 QWheelEvent cloneEvent(widgetPos, we->globalPosition(), we->pixelDelta(), we->angleDelta(), we->buttons(),
310 we->modifiers(), we->phase(), we->inverted());
311 cloneEvent.setAccepted(
false);
312 QCoreApplication::sendEvent(d->m_view.get(), &cloneEvent);
313 e->setAccepted(cloneEvent.isAccepted());
326 QCoreApplication::sendEvent(d->m_view.get(), keyEvent);
331 d->updateTouchState(Qt::TouchPointPressed,
id, pos);
333 QTouchEvent event(QEvent::TouchBegin, d->touchDevice, Qt::NoModifier, d->touchPoints);
334 event.setTimestamp(std::chrono::duration_cast<std::chrono::milliseconds>(time).count());
335 event.setAccepted(
false);
336 QCoreApplication::sendEvent(d->m_view.get(), &event);
338 return event.isAccepted();
343 d->updateTouchState(Qt::TouchPointMoved,
id, pos);
345 QTouchEvent event(QEvent::TouchUpdate, d->touchDevice, Qt::NoModifier, d->touchPoints);
346 event.setTimestamp(std::chrono::duration_cast<std::chrono::milliseconds>(time).count());
347 event.setAccepted(
false);
348 QCoreApplication::sendEvent(d->m_view.get(), &event);
350 return event.isAccepted();
355 d->updateTouchState(Qt::TouchPointReleased,
id, QPointF{});
357 QTouchEvent event(QEvent::TouchEnd, d->touchDevice, Qt::NoModifier, d->touchPoints);
358 event.setTimestamp(std::chrono::duration_cast<std::chrono::milliseconds>(time).count());
359 event.setAccepted(
false);
360 QCoreApplication::sendEvent(d->m_view.get(), &event);
362 return event.isAccepted();
367 return d->m_view->geometry();
372 d->m_view->setOpacity(
opacity);
377 return d->m_view->opacity();
382 return d->m_hasAlphaChannel;
387 return d->m_view->contentItem();
392 return d->m_view.get();
397 if (d->m_visible == visible) {
400 d->m_visible = visible;
403 Q_EMIT d->m_renderControl->renderRequested();
406 QTimer::singleShot(0,
this, [
this]() {
407 d->releaseResources();
435 if (!d->m_textureExport) {
439 return d->m_textureExport.get();
449 return d->m_view->geometry().size();
454 const QRect oldGeometry = d->m_view->geometry();
455 d->m_view->setGeometry(rect);
457 d->m_view->setScreen(QGuiApplication::screenAt(rect.center()));
461void OffscreenQuickView::Private::releaseResources()
464 m_glcontext->makeCurrent(m_offscreenSurface.get());
465 m_view->releaseResources();
466 m_glcontext->doneCurrent();
468 m_view->releaseResources();
472void OffscreenQuickView::Private::updateTouchState(Qt::TouchPointState state, qint32
id,
const QPointF &pos)
478 touchPoints.erase(std::remove_if(touchPoints.begin(), touchPoints.end(), [](QTouchEvent::TouchPoint &point) {
479 if (point.state() == QEventPoint::Released) {
482 QMutableEventPoint::setState(point, QEventPoint::Stationary);
491 static const qint32 idOffset = 111;
495 auto changed = std::find_if(touchPoints.begin(), touchPoints.end(), [
id](
const QTouchEvent::TouchPoint &point) {
496 return point.id() == id + idOffset;
500 case Qt::TouchPointPressed: {
501 if (changed != touchPoints.end()) {
505 QTouchEvent::TouchPoint point;
506 QMutableEventPoint::setState(point, QEventPoint::Pressed);
507 QMutableEventPoint::setId(point,
id + idOffset);
508 QMutableEventPoint::setGlobalPosition(point, pos);
509 QMutableEventPoint::setScenePosition(point, m_view->mapFromGlobal(pos.toPoint()));
510 QMutableEventPoint::setPosition(point, m_view->mapFromGlobal(pos.toPoint()));
512 touchPoints.append(point);
514 case Qt::TouchPointMoved: {
515 if (changed == touchPoints.end()) {
519 auto &point = *changed;
520 QMutableEventPoint::setGlobalLastPosition(point, point.globalPosition());
521 QMutableEventPoint::setState(point, QEventPoint::Updated);
522 QMutableEventPoint::setScenePosition(point, m_view->mapFromGlobal(pos.toPoint()));
523 QMutableEventPoint::setPosition(point, m_view->mapFromGlobal(pos.toPoint()));
524 QMutableEventPoint::setGlobalPosition(point, pos);
526 case Qt::TouchPointReleased: {
527 if (changed == touchPoints.end()) {
531 auto &point = *changed;
532 QMutableEventPoint::setGlobalLastPosition(point, point.globalPosition());
533 QMutableEventPoint::setState(point, QEventPoint::Released);
555 if (!d->qmlComponent) {
559 d->qmlComponent->loadUrl(source);
560 if (d->qmlComponent->isError()) {
561 qCWarning(LIBKWINEFFECTS).nospace() <<
"Failed to load effect quick view " << source <<
": " << d->qmlComponent->errors();
562 d->qmlComponent.reset();
566 d->quickItem.reset();
568 std::unique_ptr<QObject> qmlObject(d->qmlComponent->createWithInitialProperties(initialProperties));
569 QQuickItem *item = qobject_cast<QQuickItem *>(qmlObject.get());
571 qCWarning(LIBKWINEFFECTS) <<
"Root object of effect quick view" << source <<
"is not a QQuickItem";
576 d->quickItem.reset(item);
580 auto updateSize = [item,
this]() {
584 connect(
contentItem(), &QQuickItem::widthChanged, item, updateSize);
585 connect(
contentItem(), &QQuickItem::heightChanged, item, updateSize);
590 return d->quickItem.get();
595#include "moc_offscreenquickview.cpp"
QQmlEngine * qmlEngine() const
static std::unique_ptr< GLTexture > createNonOwningWrapper(GLuint textureId, GLenum internalFormat, const QSize &size)
static std::unique_ptr< GLTexture > upload(const QImage &image)
QQuickItem * rootItem() const
std::unique_ptr< QQmlComponent > qmlComponent
std::unique_ptr< QQuickItem > quickItem
void setSource(const QUrl &source)
The KwinQuickView class provides a convenient API for exporting QtQuick scenes as buffers that can be...
std::unique_ptr< QQuickRenderControl > m_renderControl
QQuickWindow * window() const
OffscreenQuickView(ExportMode exportMode=ExportMode::Texture, bool alpha=true)
QQuickItem * contentItem() const
void setVisible(bool visible)
Marks the window as visible/invisible This can be used to release resources used by the window The de...
std::unique_ptr< QOpenGLFramebufferObject > m_fbo
std::unique_ptr< QQuickWindow > m_view
QImage bufferAsImage() const
std::unique_ptr< QOffscreenSurface > m_offscreenSurface
void setOpacity(qreal opacity)
bool forwardTouchDown(qint32 id, const QPointF &pos, std::chrono::microseconds time)
std::unique_ptr< QTimer > m_repaintTimer
std::unique_ptr< GLTexture > m_textureExport
void updateTouchState(Qt::TouchPointState state, qint32 id, const QPointF &pos)
bool automaticRepaint() const
void setGeometry(const QRect &rect)
bool forwardTouchUp(qint32 id, std::chrono::microseconds time)
bool forwardTouchMotion(qint32 id, const QPointF &pos, std::chrono::microseconds time)
std::unique_ptr< QOpenGLContext > m_glcontext
GLTexture * bufferAsTexture()
QPointingDevice * touchDevice
void setAutomaticRepaint(bool set)
QList< QEventPoint > touchPoints
void forwardKeyEvent(QKeyEvent *keyEvent)
bool hasAlphaChannel() const
void forwardMouseEvent(QEvent *mouseEvent)
void geometryChanged(const QRect &oldGeometry, const QRect &newGeometry)