kwin/src/scripting/thumbnailitem.cpp
2021-08-19 06:30:55 +00:00

505 lines
15 KiB
C++

/*
SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "thumbnailitem.h"
#include "abstract_client.h"
#include "composite.h"
#include "effects.h"
#include "scene.h"
#include "screens.h"
#include "scripting_logging.h"
#include "virtualdesktops.h"
#include "workspace.h"
#include <kwingltexture.h>
#include <kwinglutils.h>
#include <QSGImageNode>
#include <QRunnable>
#include <QQuickWindow>
#include <QSGTextureProvider>
namespace KWin
{
class ThumbnailTextureProvider : public QSGTextureProvider
{
public:
explicit ThumbnailTextureProvider(QQuickWindow *window);
QSGTexture *texture() const override;
void setTexture(const QSharedPointer<GLTexture> &nativeTexture);
void setTexture(QSGTexture *texture);
private:
QQuickWindow *m_window;
QSharedPointer<GLTexture> m_nativeTexture;
QScopedPointer<QSGTexture> m_texture;
};
ThumbnailTextureProvider::ThumbnailTextureProvider(QQuickWindow *window)
: m_window(window)
{
}
QSGTexture *ThumbnailTextureProvider::texture() const
{
return m_texture.data();
}
void ThumbnailTextureProvider::setTexture(const QSharedPointer<GLTexture> &nativeTexture)
{
if (m_nativeTexture != nativeTexture) {
const GLuint textureId = nativeTexture->texture();
m_nativeTexture = nativeTexture;
m_texture.reset(m_window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture,
&textureId, 0,
nativeTexture->size(),
QQuickWindow::TextureHasAlphaChannel));
m_texture->setFiltering(QSGTexture::Linear);
m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge);
m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge);
}
// The textureChanged signal must be emitted also if only texture data changes.
Q_EMIT textureChanged();
}
void ThumbnailTextureProvider::setTexture(QSGTexture *texture)
{
m_nativeTexture = nullptr;
m_texture.reset(texture);
Q_EMIT textureChanged();
}
class ThumbnailTextureProviderCleanupJob : public QRunnable
{
public:
explicit ThumbnailTextureProviderCleanupJob(ThumbnailTextureProvider *provider)
: m_provider(provider)
{
}
void run() override
{
m_provider.reset();
}
private:
QScopedPointer<ThumbnailTextureProvider> m_provider;
};
ThumbnailItemBase::ThumbnailItemBase(QQuickItem *parent)
: QQuickItem(parent)
{
setFlag(ItemHasContents);
updateFrameRenderingConnection();
connect(Compositor::self(), &Compositor::aboutToToggleCompositing,
this, &ThumbnailItemBase::destroyOffscreenTexture);
connect(Compositor::self(), &Compositor::compositingToggled,
this, &ThumbnailItemBase::updateFrameRenderingConnection);
connect(this, &QQuickItem::windowChanged,
this, &ThumbnailItemBase::updateFrameRenderingConnection);
}
ThumbnailItemBase::~ThumbnailItemBase()
{
destroyOffscreenTexture();
if (m_provider) {
if (window()) {
window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider),
QQuickWindow::AfterSynchronizingStage);
} else {
qCCritical(KWIN_SCRIPTING) << "Can't destroy thumbnail texture provider because window is null";
}
}
}
void ThumbnailItemBase::releaseResources()
{
if (m_provider) {
window()->scheduleRenderJob(new ThumbnailTextureProviderCleanupJob(m_provider),
QQuickWindow::AfterSynchronizingStage);
m_provider = nullptr;
}
}
bool ThumbnailItemBase::isTextureProvider() const
{
return true;
}
QSGTextureProvider *ThumbnailItemBase::textureProvider() const
{
if (QQuickItem::isTextureProvider()) {
return QQuickItem::textureProvider();
}
if (!m_provider) {
m_provider = new ThumbnailTextureProvider(window());
}
return m_provider;
}
void ThumbnailItemBase::updateFrameRenderingConnection()
{
disconnect(m_frameRenderingConnection);
if (!Compositor::self()) {
return;
}
Scene *scene = Compositor::self()->scene();
if (!window()) {
return;
}
if (scene && scene->compositingType() == OpenGLCompositing) {
m_frameRenderingConnection = connect(scene, &Scene::frameRendered, this, &ThumbnailItemBase::updateOffscreenTexture);
}
}
QSize ThumbnailItemBase::sourceSize() const
{
return m_sourceSize;
}
void ThumbnailItemBase::setSourceSize(const QSize &sourceSize)
{
if (m_sourceSize != sourceSize) {
m_sourceSize = sourceSize;
invalidateOffscreenTexture();
Q_EMIT sourceSizeChanged();
}
}
void ThumbnailItemBase::destroyOffscreenTexture()
{
if (!Compositor::self()) {
return;
}
Scene *scene = Compositor::self()->scene();
if (!scene || scene->compositingType() != OpenGLCompositing) {
return;
}
if (m_offscreenTexture) {
scene->makeOpenGLContextCurrent();
m_offscreenTarget.reset();
m_offscreenTexture.reset();
if (m_acquireFence) {
glDeleteSync(m_acquireFence);
m_acquireFence = 0;
}
scene->doneOpenGLContextCurrent();
}
}
QSGNode *ThumbnailItemBase::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
if (Compositor::compositing() && !m_offscreenTexture) {
return oldNode;
}
// Wait for rendering commands to the offscreen texture complete if there are any.
if (m_acquireFence) {
glClientWaitSync(m_acquireFence, GL_SYNC_FLUSH_COMMANDS_BIT, 5000);
glDeleteSync(m_acquireFence);
m_acquireFence = 0;
}
if (!m_provider) {
m_provider = new ThumbnailTextureProvider(window());
}
if (m_offscreenTexture) {
m_provider->setTexture(m_offscreenTexture);
} else {
const QImage placeholderImage = fallbackImage();
m_provider->setTexture(window()->createTextureFromImage(placeholderImage));
m_devicePixelRatio = placeholderImage.devicePixelRatio();
}
QSGImageNode *node = static_cast<QSGImageNode *>(oldNode);
if (!node) {
node = window()->createImageNode();
}
node->setTexture(m_provider->texture());
if (m_offscreenTexture && m_offscreenTexture->isYInverted()) {
node->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically);
} else {
node->setTextureCoordinatesTransform(QSGImageNode::NoTransform);
}
node->setRect(paintedRect());
return node;
}
void ThumbnailItemBase::setSaturation(qreal saturation)
{
Q_UNUSED(saturation)
qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.saturation is removed. Use a shader effect to change saturation";
}
void ThumbnailItemBase::setBrightness(qreal brightness)
{
Q_UNUSED(brightness)
qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.brightness is removed. Use a shader effect to change brightness";
}
void ThumbnailItemBase::setClipTo(QQuickItem *clip)
{
Q_UNUSED(clip)
qCWarning(KWIN_SCRIPTING) << "ThumbnailItem.clipTo is removed and it has no replacements";
}
WindowThumbnailItem::WindowThumbnailItem(QQuickItem *parent)
: ThumbnailItemBase(parent)
{
}
QUuid WindowThumbnailItem::wId() const
{
return m_wId;
}
void WindowThumbnailItem::setWId(const QUuid &wId)
{
if (m_wId == wId) {
return;
}
m_wId = wId;
if (!m_wId.isNull()) {
setClient(workspace()->findAbstractClient(wId));
} else if (m_client) {
m_client = nullptr;
Q_EMIT clientChanged();
}
Q_EMIT wIdChanged();
}
AbstractClient *WindowThumbnailItem::client() const
{
return m_client;
}
void WindowThumbnailItem::setClient(AbstractClient *client)
{
if (m_client == client) {
return;
}
if (m_client) {
disconnect(m_client, &AbstractClient::frameGeometryChanged,
this, &WindowThumbnailItem::invalidateOffscreenTexture);
disconnect(m_client, &AbstractClient::damaged,
this, &WindowThumbnailItem::invalidateOffscreenTexture);
}
m_client = client;
if (m_client) {
connect(m_client, &AbstractClient::frameGeometryChanged,
this, &WindowThumbnailItem::invalidateOffscreenTexture);
connect(m_client, &AbstractClient::damaged,
this, &WindowThumbnailItem::invalidateOffscreenTexture);
setWId(m_client->internalId());
} else {
setWId(QUuid());
}
invalidateOffscreenTexture();
Q_EMIT clientChanged();
}
QImage WindowThumbnailItem::fallbackImage() const
{
if (m_client) {
return m_client->icon().pixmap(window(), boundingRect().size().toSize()).toImage();
}
return QImage();
}
static QRectF centeredSize(const QRectF &boundingRect, const QSizeF &size)
{
const QSizeF scaled = size.scaled(boundingRect.size(), Qt::KeepAspectRatio);
const qreal x = boundingRect.x() + (boundingRect.width() - scaled.width()) / 2;
const qreal y = boundingRect.y() + (boundingRect.height() - scaled.height()) / 2;
return QRectF(QPointF(x, y), scaled);
}
QRectF WindowThumbnailItem::paintedRect() const
{
if (!m_offscreenTexture) {
const QSizeF iconSize = m_client->icon().actualSize(window(), boundingRect().size().toSize());
return centeredSize(boundingRect(), iconSize);
}
const QRect visibleGeometry = m_client->visibleGeometry();
const QRect frameGeometry = m_client->frameGeometry();
const QSizeF scaled = QSizeF(frameGeometry.size()).scaled(boundingRect().size(), Qt::KeepAspectRatio);
const qreal xScale = scaled.width() / frameGeometry.width();
const qreal yScale = scaled.height() / frameGeometry.height();
QRectF paintedRect(boundingRect().x() + (boundingRect().width() - scaled.width()) / 2,
boundingRect().y() + (boundingRect().height() - scaled.height()) / 2,
visibleGeometry.width() * xScale,
visibleGeometry.height() * yScale);
paintedRect.moveLeft(paintedRect.x() + (visibleGeometry.x() - frameGeometry.x()) * xScale);
paintedRect.moveTop(paintedRect.y() + (visibleGeometry.y() - frameGeometry.y()) * yScale);
return paintedRect;
}
void WindowThumbnailItem::invalidateOffscreenTexture()
{
m_dirty = true;
update();
}
void WindowThumbnailItem::updateOffscreenTexture()
{
if (m_acquireFence || !m_dirty || !m_client) {
return;
}
Q_ASSERT(window());
const QRect geometry = m_client->visibleGeometry();
QSize textureSize = geometry.size();
if (sourceSize().width() > 0) {
textureSize.setWidth(sourceSize().width());
}
if (sourceSize().height() > 0) {
textureSize.setHeight(sourceSize().height());
}
m_devicePixelRatio = window()->devicePixelRatio();
textureSize *= m_devicePixelRatio;
if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) {
m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize));
m_offscreenTexture->setFilter(GL_LINEAR);
m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
m_offscreenTarget.reset(new GLRenderTarget(*m_offscreenTexture));
}
GLRenderTarget::pushRenderTarget(m_offscreenTarget.data());
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
QMatrix4x4 projectionMatrix;
projectionMatrix.ortho(geometry.x(), geometry.x() + geometry.width(),
geometry.y(), geometry.y() + geometry.height(), -1, 1);
EffectWindowImpl *effectWindow = m_client->effectWindow();
WindowPaintData data(effectWindow);
data.setProjectionMatrix(projectionMatrix);
// The thumbnail must be rendered using kwin's opengl context as VAOs are not
// shared across contexts. Unfortunately, this also introduces a latency of 1
// frame, which is not ideal, but it is acceptable for things such as thumbnails.
const int mask = Scene::PAINT_WINDOW_TRANSFORMED;
effectWindow->sceneWindow()->performPaint(mask, infiniteRegion(), data);
GLRenderTarget::popRenderTarget();
// The fence is needed to avoid the case where qtquick renderer starts using
// the texture while all rendering commands to it haven't completed yet.
m_dirty = false;
m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// We know that the texture has changed, so schedule an item update.
update();
}
DesktopThumbnailItem::DesktopThumbnailItem(QQuickItem *parent)
: ThumbnailItemBase(parent)
{
}
int DesktopThumbnailItem::desktop() const
{
return m_desktop;
}
void DesktopThumbnailItem::setDesktop(int desktop)
{
desktop = qBound<int>(1, desktop, VirtualDesktopManager::self()->count());
if (m_desktop != desktop) {
m_desktop = desktop;
invalidateOffscreenTexture();
Q_EMIT desktopChanged();
}
}
QImage DesktopThumbnailItem::fallbackImage() const
{
return QImage();
}
QRectF DesktopThumbnailItem::paintedRect() const
{
return centeredSize(boundingRect(), screens()->size());
}
void DesktopThumbnailItem::invalidateOffscreenTexture()
{
update();
}
void DesktopThumbnailItem::updateOffscreenTexture()
{
if (m_acquireFence) {
return;
}
const QRect geometry = screens()->geometry();
QSize textureSize = geometry.size();
if (sourceSize().width() > 0) {
textureSize.setWidth(sourceSize().width());
}
if (sourceSize().height() > 0) {
textureSize.setHeight(sourceSize().height());
}
m_devicePixelRatio = window()->devicePixelRatio();
textureSize *= m_devicePixelRatio;
if (!m_offscreenTexture || m_offscreenTexture->size() != textureSize) {
m_offscreenTexture.reset(new GLTexture(GL_RGBA8, textureSize));
m_offscreenTexture->setFilter(GL_LINEAR);
m_offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE);
m_offscreenTexture->setYInverted(true);
m_offscreenTarget.reset(new GLRenderTarget(*m_offscreenTexture));
}
GLRenderTarget::pushRenderTarget(m_offscreenTarget.data());
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
QMatrix4x4 projectionMatrix;
projectionMatrix.ortho(geometry);
ScreenPaintData data(projectionMatrix);
// The thumbnail must be rendered using kwin's opengl context as VAOs are not
// shared across contexts. Unfortunately, this also introduces a latency of 1
// frame, which is not ideal, but it is acceptable for things such as thumbnails.
const int mask = Scene::PAINT_WINDOW_TRANSFORMED | Scene::PAINT_SCREEN_TRANSFORMED;
Scene *scene = Compositor::self()->scene();
scene->paintDesktop(m_desktop, mask, infiniteRegion(), data);
GLRenderTarget::popRenderTarget();
// The fence is needed to avoid the case where qtquick renderer starts using
// the texture while all rendering commands to it haven't completed yet.
m_acquireFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// We know that the texture has changed, so schedule an item update.
update();
}
} // namespace KWin