kwin/libkwineffects/kwineffectquickview.cpp

378 lines
10 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kwineffectquickview.h"
#include "kwinglutils.h"
#include "logging_p.h"
#include <QQmlEngine>
#include <QQuickItem>
#include <QQmlContext>
#include <QQmlComponent>
#include <QQuickView>
#include <QQuickRenderControl>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QTimer>
#include <KDeclarative/QmlObjectSharedEngine>
namespace KWin
{
static std::unique_ptr<QOpenGLContext> s_shareContext;
class Q_DECL_HIDDEN EffectQuickView::Private
{
public:
QQuickWindow *m_view;
QQuickRenderControl *m_renderControl;
QScopedPointer<QOpenGLContext> m_glcontext;
QScopedPointer<QOffscreenSurface> m_offscreenSurface;
QScopedPointer<QOpenGLFramebufferObject> m_fbo;
QImage m_image;
QScopedPointer<GLTexture> m_textureExport;
// if we should capture a QImage after rendering into our BO.
// Used for either software QtQuick rendering and nonGL kwin rendering
bool m_useBlit = false;
bool m_visible = true;
void releaseResources();
};
class Q_DECL_HIDDEN EffectQuickScene::Private
{
public:
KDeclarative::QmlObjectSharedEngine *qmlObject = nullptr;
};
EffectQuickView::EffectQuickView(QObject *parent)
: EffectQuickView(parent, effects->isOpenGLCompositing() ? ExportMode::Texture : ExportMode::Image)
{
}
EffectQuickView::EffectQuickView(QObject *parent, ExportMode exportMode)
: QObject(parent)
, d(new EffectQuickView::Private)
{
d->m_renderControl = new QQuickRenderControl(this);
d->m_view = new QQuickWindow(d->m_renderControl);
d->m_view->setFlags(Qt::FramelessWindowHint);
d->m_view->setColor(Qt::transparent);
if (exportMode == ExportMode::Image) {
d->m_useBlit = true;
}
const bool usingGl = d->m_view->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL;
if (!usingGl) {
qCDebug(LIBKWINEFFECTS) << "QtQuick Software rendering mode detected";
d->m_useBlit = true;
d->m_renderControl->initialize(nullptr);
} else {
QSurfaceFormat format;
format.setOption(QSurfaceFormat::ResetNotification);
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
d->m_glcontext.reset(new QOpenGLContext);
d->m_glcontext->setShareContext(s_shareContext.get());
d->m_glcontext->setFormat(format);
d->m_glcontext->create();
// and the offscreen surface
d->m_offscreenSurface.reset(new QOffscreenSurface);
d->m_offscreenSurface->setFormat(d->m_glcontext->format());
d->m_offscreenSurface->create();
d->m_glcontext->makeCurrent(d->m_offscreenSurface.data());
d->m_renderControl->initialize(d->m_glcontext.data());
d->m_glcontext->doneCurrent();
if (!d->m_glcontext->shareContext()) {
qCDebug(LIBKWINEFFECTS) << "Failed to create a shared context, falling back to raster rendering";
qCDebug(LIBKWINEFFECTS) << "Extra debug:";
qCDebug(LIBKWINEFFECTS) << "our context:" << d->m_glcontext.data();
qCDebug(LIBKWINEFFECTS) << "share context:" << s_shareContext.get();
// still render via GL, but blit for presentation
d->m_useBlit = true;
}
}
auto updateSize = [this]() { contentItem()->setSize(d->m_view->size()); };
updateSize();
connect(d->m_view, &QWindow::widthChanged, this, updateSize);
connect(d->m_view, &QWindow::heightChanged, this, updateSize);
QTimer *t = new QTimer(this);
t->setSingleShot(true);
t->setInterval(10);
connect(t, &QTimer::timeout, this, &EffectQuickView::update);
connect(d->m_renderControl, &QQuickRenderControl::renderRequested, t, [t]() { t->start(); });
connect(d->m_renderControl, &QQuickRenderControl::sceneChanged, t, [t]() { t->start(); });
}
EffectQuickView::~EffectQuickView()
{
if (d->m_glcontext) {
d->m_glcontext->makeCurrent(d->m_offscreenSurface.data());
d->m_renderControl->invalidate();
d->m_glcontext->doneCurrent();
}
}
void EffectQuickView::update()
{
if (!d->m_visible) {
return;
}
if (d->m_view->size().isEmpty()) {
return;
}
bool usingGl = d->m_glcontext;
if (usingGl) {
if (!d->m_glcontext->makeCurrent(d->m_offscreenSurface.data())) {
// probably a context loss event, kwin is about to reset all the effects anyway
return;
}
if (d->m_fbo.isNull() || d->m_fbo->size() != d->m_view->size()) {
d->m_textureExport.reset(nullptr);
d->m_fbo.reset(new QOpenGLFramebufferObject(d->m_view->size(), QOpenGLFramebufferObject::CombinedDepthStencil));
if (!d->m_fbo->isValid()) {
d->m_fbo.reset();
d->m_glcontext->doneCurrent();
return;
}
}
d->m_view->setRenderTarget(d->m_fbo.data());
}
d->m_renderControl->polishItems();
d->m_renderControl->sync();
d->m_renderControl->render();
if (usingGl) {
d->m_view->resetOpenGLState();
}
if (d->m_useBlit) {
d->m_image = d->m_renderControl->grab();
}
if (usingGl) {
QOpenGLFramebufferObject::bindDefault();
d->m_glcontext->doneCurrent();
}
emit repaintNeeded();
}
void EffectQuickView::forwardMouseEvent(QEvent *e)
{
if (!d->m_visible) {
return;
}
switch (e->type()) {
case QEvent::MouseMove:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
{
QMouseEvent *me = static_cast<QMouseEvent *>(e);
const QPoint widgetPos = d->m_view->mapFromGlobal(me->pos());
QMouseEvent cloneEvent(me->type(), widgetPos, me->pos(), me->button(), me->buttons(), me->modifiers());
QCoreApplication::sendEvent(d->m_view, &cloneEvent);
e->setAccepted(cloneEvent.isAccepted());
return;
}
case QEvent::HoverEnter:
case QEvent::HoverLeave:
case QEvent::HoverMove:
{
QHoverEvent *he = static_cast<QHoverEvent *>(e);
const QPointF widgetPos = d->m_view->mapFromGlobal(he->pos());
const QPointF oldWidgetPos = d->m_view->mapFromGlobal(he->oldPos());
QHoverEvent cloneEvent(he->type(), widgetPos, oldWidgetPos, he->modifiers());
QCoreApplication::sendEvent(d->m_view, &cloneEvent);
e->setAccepted(cloneEvent.isAccepted());
return;
}
case QEvent::Wheel:
{
QWheelEvent *we = static_cast<QWheelEvent *>(e);
const QPointF widgetPos = d->m_view->mapFromGlobal(we->pos());
QWheelEvent cloneEvent(widgetPos, we->globalPosF(), we->pixelDelta(), we->angleDelta(), we->buttons(),
we->modifiers(), we->phase(), we->inverted());
QCoreApplication::sendEvent(d->m_view, &cloneEvent);
e->setAccepted(cloneEvent.isAccepted());
return;
}
default:
return;
}
}
void EffectQuickView::forwardKeyEvent(QKeyEvent *keyEvent)
{
if (!d->m_visible) {
return;
}
QCoreApplication::sendEvent(d->m_view, keyEvent);
}
void EffectQuickView::setShareContext(std::unique_ptr<QOpenGLContext> context)
{
s_shareContext = std::move(context);
}
QRect EffectQuickView::geometry() const
{
return d->m_view->geometry();
}
QQuickItem *EffectQuickView::contentItem() const
{
return d->m_view->contentItem();
}
void EffectQuickView::setVisible(bool visible)
{
if (d->m_visible == visible) {
return;
}
d->m_visible = visible;
if (visible){
d->m_renderControl->renderRequested();
} else {
// deferred to not change GL context
QTimer::singleShot(0, this, [this]() {
d->releaseResources();
});
}
}
bool EffectQuickView::isVisible() const
{
return d->m_visible;
}
void EffectQuickView::show()
{
setVisible(true);
}
void EffectQuickView::hide()
{
setVisible(false);
}
GLTexture *EffectQuickView::bufferAsTexture()
{
if (d->m_useBlit) {
if (d->m_image.isNull()) {
return nullptr;
}
d->m_textureExport.reset(new GLTexture(d->m_image));
} else {
if (!d->m_fbo) {
return nullptr;
}
if (!d->m_textureExport) {
d->m_textureExport.reset(new GLTexture(d->m_fbo->texture(), d->m_fbo->format().internalTextureFormat(), d->m_fbo->size()));
}
}
return d->m_textureExport.data();
}
QImage EffectQuickView::bufferAsImage() const
{
return d->m_image;
}
QSize EffectQuickView::size() const
{
return d->m_view->geometry().size();
}
void EffectQuickView::setGeometry(const QRect &rect)
{
const QRect oldGeometry = d->m_view->geometry();
d->m_view->setGeometry(rect);
emit geometryChanged(oldGeometry, rect);
}
void EffectQuickView::Private::releaseResources()
{
if (m_glcontext) {
m_glcontext->makeCurrent(m_offscreenSurface.data());
m_view->releaseResources();
m_glcontext->doneCurrent();
} else {
m_view->releaseResources();
}
}
EffectQuickScene::EffectQuickScene(QObject *parent)
: EffectQuickView(parent)
, d(new EffectQuickScene::Private)
{
d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
}
EffectQuickScene::EffectQuickScene(QObject *parent, EffectQuickView::ExportMode exportMode)
: EffectQuickView(parent, exportMode)
, d(new EffectQuickScene::Private)
{
d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
}
EffectQuickScene::~EffectQuickScene()
{
}
void EffectQuickScene::setSource(const QUrl &source)
{
d->qmlObject->setSource(source);
QQuickItem *item = rootItem();
if (!item) {
qCDebug(LIBKWINEFFECTS) << "Could not load effect quick view" << source;
return;
}
item->setParentItem(contentItem());
auto updateSize = [item, this]() { item->setSize(contentItem()->size()); };
updateSize();
connect(contentItem(), &QQuickItem::widthChanged, item, updateSize);
connect(contentItem(), &QQuickItem::heightChanged, item, updateSize);
}
QQmlContext *EffectQuickScene::rootContext() const
{
return d->qmlObject->rootContext();
}
QQuickItem *EffectQuickScene::rootItem() const
{
return qobject_cast<QQuickItem *>(d->qmlObject->rootObject());
}
} // namespace KWin