File indexing completed on 2024-05-19 05:32:22

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
0006     SPDX-FileCopyrightText: 2009, 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
0007     SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0008 
0009     Based on glcompmgr code by Felix Bellaby.
0010     Using code from Compiz and Beryl.
0011 
0012     SPDX-License-Identifier: GPL-2.0-or-later
0013 */
0014 #include "workspacescene_opengl.h"
0015 
0016 #include "opengl/glplatform.h"
0017 
0018 #include "compositor.h"
0019 #include "core/output.h"
0020 #include "decorations/decoratedclient.h"
0021 #include "scene/itemrenderer_opengl.h"
0022 #include "shadow.h"
0023 #include "window.h"
0024 
0025 #include <cmath>
0026 #include <cstddef>
0027 
0028 #include <QMatrix4x4>
0029 #include <QPainter>
0030 #include <QStringList>
0031 #include <QVector2D>
0032 #include <QVector4D>
0033 #include <QtMath>
0034 
0035 namespace KWin
0036 {
0037 
0038 /************************************************
0039  * SceneOpenGL
0040  ***********************************************/
0041 
0042 WorkspaceSceneOpenGL::WorkspaceSceneOpenGL(OpenGLBackend *backend)
0043     : WorkspaceScene(std::make_unique<ItemRendererOpenGL>())
0044     , m_backend(backend)
0045 {
0046 }
0047 
0048 WorkspaceSceneOpenGL::~WorkspaceSceneOpenGL()
0049 {
0050     makeOpenGLContextCurrent();
0051 }
0052 
0053 bool WorkspaceSceneOpenGL::makeOpenGLContextCurrent()
0054 {
0055     return m_backend->makeCurrent();
0056 }
0057 
0058 void WorkspaceSceneOpenGL::doneOpenGLContextCurrent()
0059 {
0060     m_backend->doneCurrent();
0061 }
0062 
0063 bool WorkspaceSceneOpenGL::supportsNativeFence() const
0064 {
0065     return m_backend->supportsNativeFence();
0066 }
0067 
0068 std::unique_ptr<DecorationRenderer> WorkspaceSceneOpenGL::createDecorationRenderer(Decoration::DecoratedClientImpl *impl)
0069 {
0070     return std::make_unique<SceneOpenGLDecorationRenderer>(impl);
0071 }
0072 
0073 std::unique_ptr<ShadowTextureProvider> WorkspaceSceneOpenGL::createShadowTextureProvider(Shadow *shadow)
0074 {
0075     return std::make_unique<OpenGLShadowTextureProvider>(shadow);
0076 }
0077 
0078 bool WorkspaceSceneOpenGL::animationsSupported() const
0079 {
0080     return !GLPlatform::instance()->isSoftwareEmulation();
0081 }
0082 
0083 std::pair<std::shared_ptr<GLTexture>, ColorDescription> WorkspaceSceneOpenGL::textureForOutput(Output *output) const
0084 {
0085     return m_backend->textureForOutput(output);
0086 }
0087 
0088 //****************************************
0089 // SceneOpenGL::Shadow
0090 //****************************************
0091 class DecorationShadowTextureCache
0092 {
0093 public:
0094     ~DecorationShadowTextureCache();
0095     DecorationShadowTextureCache(const DecorationShadowTextureCache &) = delete;
0096     static DecorationShadowTextureCache &instance();
0097 
0098     void unregister(ShadowTextureProvider *provider);
0099     std::shared_ptr<GLTexture> getTexture(ShadowTextureProvider *provider);
0100 
0101 private:
0102     DecorationShadowTextureCache() = default;
0103     struct Data
0104     {
0105         std::shared_ptr<GLTexture> texture;
0106         QList<ShadowTextureProvider *> providers;
0107     };
0108     QHash<KDecoration2::DecorationShadow *, Data> m_cache;
0109 };
0110 
0111 DecorationShadowTextureCache &DecorationShadowTextureCache::instance()
0112 {
0113     static DecorationShadowTextureCache s_instance;
0114     return s_instance;
0115 }
0116 
0117 DecorationShadowTextureCache::~DecorationShadowTextureCache()
0118 {
0119     Q_ASSERT(m_cache.isEmpty());
0120 }
0121 
0122 void DecorationShadowTextureCache::unregister(ShadowTextureProvider *provider)
0123 {
0124     auto it = m_cache.begin();
0125     while (it != m_cache.end()) {
0126         auto &d = it.value();
0127         // check whether the Vector of Shadows contains our shadow and remove all of them
0128         auto glIt = d.providers.begin();
0129         while (glIt != d.providers.end()) {
0130             if (*glIt == provider) {
0131                 glIt = d.providers.erase(glIt);
0132             } else {
0133                 glIt++;
0134             }
0135         }
0136         // if there are no shadows any more we can erase the cache entry
0137         if (d.providers.isEmpty()) {
0138             it = m_cache.erase(it);
0139         } else {
0140             it++;
0141         }
0142     }
0143 }
0144 
0145 std::shared_ptr<GLTexture> DecorationShadowTextureCache::getTexture(ShadowTextureProvider *provider)
0146 {
0147     Shadow *shadow = provider->shadow();
0148     Q_ASSERT(shadow->hasDecorationShadow());
0149     unregister(provider);
0150     const auto decoShadow = shadow->decorationShadow().lock();
0151     Q_ASSERT(decoShadow);
0152     auto it = m_cache.find(decoShadow.get());
0153     if (it != m_cache.end()) {
0154         Q_ASSERT(!it.value().providers.contains(provider));
0155         it.value().providers << provider;
0156         return it.value().texture;
0157     }
0158     Data d;
0159     d.providers << provider;
0160     d.texture = GLTexture::upload(shadow->decorationShadowImage());
0161     if (!d.texture) {
0162         return nullptr;
0163     }
0164     d.texture->setFilter(GL_LINEAR);
0165     d.texture->setWrapMode(GL_CLAMP_TO_EDGE);
0166     m_cache.insert(decoShadow.get(), d);
0167     return d.texture;
0168 }
0169 
0170 OpenGLShadowTextureProvider::OpenGLShadowTextureProvider(Shadow *shadow)
0171     : ShadowTextureProvider(shadow)
0172 {
0173 }
0174 
0175 OpenGLShadowTextureProvider::~OpenGLShadowTextureProvider()
0176 {
0177     if (m_texture) {
0178         Compositor::self()->scene()->makeOpenGLContextCurrent();
0179         DecorationShadowTextureCache::instance().unregister(this);
0180         m_texture.reset();
0181     }
0182 }
0183 
0184 void OpenGLShadowTextureProvider::update()
0185 {
0186     if (m_shadow->hasDecorationShadow()) {
0187         // simplifies a lot by going directly to
0188         m_texture = DecorationShadowTextureCache::instance().getTexture(this);
0189         return;
0190     }
0191 
0192     const QSize top(m_shadow->shadowElement(Shadow::ShadowElementTop).size());
0193     const QSize topRight(m_shadow->shadowElement(Shadow::ShadowElementTopRight).size());
0194     const QSize right(m_shadow->shadowElement(Shadow::ShadowElementRight).size());
0195     const QSize bottom(m_shadow->shadowElement(Shadow::ShadowElementBottom).size());
0196     const QSize bottomLeft(m_shadow->shadowElement(Shadow::ShadowElementBottomLeft).size());
0197     const QSize left(m_shadow->shadowElement(Shadow::ShadowElementLeft).size());
0198     const QSize topLeft(m_shadow->shadowElement(Shadow::ShadowElementTopLeft).size());
0199     const QSize bottomRight(m_shadow->shadowElement(Shadow::ShadowElementBottomRight).size());
0200 
0201     const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()});
0202     const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()});
0203 
0204     if (width == 0 || height == 0) {
0205         return;
0206     }
0207 
0208     QImage image(width, height, QImage::Format_ARGB32);
0209     image.fill(Qt::transparent);
0210 
0211     const int innerRectTop = std::max({topLeft.height(), top.height(), topRight.height()});
0212     const int innerRectLeft = std::max({topLeft.width(), left.width(), bottomLeft.width()});
0213 
0214     QPainter p;
0215     p.begin(&image);
0216 
0217     p.drawImage(QRectF(0, 0, topLeft.width(), topLeft.height()), m_shadow->shadowElement(Shadow::ShadowElementTopLeft));
0218     p.drawImage(QRectF(innerRectLeft, 0, top.width(), top.height()), m_shadow->shadowElement(Shadow::ShadowElementTop));
0219     p.drawImage(QRectF(width - topRight.width(), 0, topRight.width(), topRight.height()), m_shadow->shadowElement(Shadow::ShadowElementTopRight));
0220 
0221     p.drawImage(QRectF(0, innerRectTop, left.width(), left.height()), m_shadow->shadowElement(Shadow::ShadowElementLeft));
0222     p.drawImage(QRectF(width - right.width(), innerRectTop, right.width(), right.height()), m_shadow->shadowElement(Shadow::ShadowElementRight));
0223 
0224     p.drawImage(QRectF(0, height - bottomLeft.height(), bottomLeft.width(), bottomLeft.height()), m_shadow->shadowElement(Shadow::ShadowElementBottomLeft));
0225     p.drawImage(QRectF(innerRectLeft, height - bottom.height(), bottom.width(), bottom.height()), m_shadow->shadowElement(Shadow::ShadowElementBottom));
0226     p.drawImage(QRectF(width - bottomRight.width(), height - bottomRight.height(), bottomRight.width(), bottomRight.height()), m_shadow->shadowElement(Shadow::ShadowElementBottomRight));
0227 
0228     p.end();
0229 
0230     // Check if the image is alpha-only in practice, and if so convert it to an 8-bpp format
0231     if (!GLPlatform::instance()->isGLES() && GLTexture::supportsSwizzle() && GLTexture::supportsFormatRG()) {
0232         QImage alphaImage(image.size(), QImage::Format_Alpha8);
0233         bool alphaOnly = true;
0234 
0235         for (ptrdiff_t y = 0; alphaOnly && y < image.height(); y++) {
0236             const uint32_t *const src = reinterpret_cast<const uint32_t *>(image.scanLine(y));
0237             uint8_t *const dst = reinterpret_cast<uint8_t *>(alphaImage.scanLine(y));
0238 
0239             for (ptrdiff_t x = 0; x < image.width(); x++) {
0240                 if (src[x] & 0x00ffffff) {
0241                     alphaOnly = false;
0242                 }
0243 
0244                 dst[x] = qAlpha(src[x]);
0245             }
0246         }
0247 
0248         if (alphaOnly) {
0249             image = alphaImage;
0250         }
0251     }
0252 
0253     m_texture = GLTexture::upload(image);
0254     if (!m_texture) {
0255         return;
0256     }
0257     m_texture->setFilter(GL_LINEAR);
0258     m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
0259 
0260     if (m_texture->internalFormat() == GL_R8) {
0261         // Swizzle red to alpha and all other channels to zero
0262         m_texture->bind();
0263         m_texture->setSwizzle(GL_ZERO, GL_ZERO, GL_ZERO, GL_RED);
0264     }
0265 }
0266 
0267 SceneOpenGLDecorationRenderer::SceneOpenGLDecorationRenderer(Decoration::DecoratedClientImpl *client)
0268     : DecorationRenderer(client)
0269     , m_texture()
0270 {
0271 }
0272 
0273 SceneOpenGLDecorationRenderer::~SceneOpenGLDecorationRenderer()
0274 {
0275     if (WorkspaceScene *scene = Compositor::self()->scene()) {
0276         scene->makeOpenGLContextCurrent();
0277     }
0278 }
0279 
0280 static void clamp_row(int left, int width, int right, const uint32_t *src, uint32_t *dest)
0281 {
0282     std::fill_n(dest, left, *src);
0283     std::copy(src, src + width, dest + left);
0284     std::fill_n(dest + left + width, right, *(src + width - 1));
0285 }
0286 
0287 static void clamp_sides(int left, int width, int right, const uint32_t *src, uint32_t *dest)
0288 {
0289     std::fill_n(dest, left, *src);
0290     std::fill_n(dest + left + width, right, *(src + width - 1));
0291 }
0292 
0293 static void clamp(QImage &image, const QRect &viewport)
0294 {
0295     Q_ASSERT(image.depth() == 32);
0296     if (viewport.isEmpty()) {
0297         image = {};
0298         return;
0299     }
0300 
0301     const QRect rect = image.rect();
0302 
0303     const int left = viewport.left() - rect.left();
0304     const int top = viewport.top() - rect.top();
0305     const int right = rect.right() - viewport.right();
0306     const int bottom = rect.bottom() - viewport.bottom();
0307 
0308     const int width = rect.width() - left - right;
0309     const int height = rect.height() - top - bottom;
0310 
0311     const uint32_t *firstRow = reinterpret_cast<uint32_t *>(image.scanLine(top));
0312     const uint32_t *lastRow = reinterpret_cast<uint32_t *>(image.scanLine(top + height - 1));
0313 
0314     for (int i = 0; i < top; ++i) {
0315         uint32_t *dest = reinterpret_cast<uint32_t *>(image.scanLine(i));
0316         clamp_row(left, width, right, firstRow + left, dest);
0317     }
0318 
0319     for (int i = 0; i < height; ++i) {
0320         uint32_t *dest = reinterpret_cast<uint32_t *>(image.scanLine(top + i));
0321         clamp_sides(left, width, right, dest + left, dest);
0322     }
0323 
0324     for (int i = 0; i < bottom; ++i) {
0325         uint32_t *dest = reinterpret_cast<uint32_t *>(image.scanLine(top + height + i));
0326         clamp_row(left, width, right, lastRow + left, dest);
0327     }
0328 }
0329 
0330 void SceneOpenGLDecorationRenderer::render(const QRegion &region)
0331 {
0332     if (areImageSizesDirty()) {
0333         resizeTexture();
0334         resetImageSizesDirty();
0335     }
0336 
0337     if (!m_texture) {
0338         // for invalid sizes we get no texture, see BUG 361551
0339         return;
0340     }
0341 
0342     QRectF left, top, right, bottom;
0343     client()->window()->layoutDecorationRects(left, top, right, bottom);
0344 
0345     const qreal devicePixelRatio = effectiveDevicePixelRatio();
0346     const int topHeight = std::round(top.height() * devicePixelRatio);
0347     const int bottomHeight = std::round(bottom.height() * devicePixelRatio);
0348     const int leftWidth = std::round(left.width() * devicePixelRatio);
0349 
0350     const QPoint topPosition(0, 0);
0351     const QPoint bottomPosition(0, topPosition.y() + topHeight + (2 * TexturePad));
0352     const QPoint leftPosition(0, bottomPosition.y() + bottomHeight + (2 * TexturePad));
0353     const QPoint rightPosition(0, leftPosition.y() + leftWidth + (2 * TexturePad));
0354 
0355     const QRect dirtyRect = region.boundingRect();
0356 
0357     renderPart(top.toRect().intersected(dirtyRect), top.toRect(), topPosition, devicePixelRatio);
0358     renderPart(bottom.toRect().intersected(dirtyRect), bottom.toRect(), bottomPosition, devicePixelRatio);
0359     renderPart(left.toRect().intersected(dirtyRect), left.toRect(), leftPosition, devicePixelRatio, true);
0360     renderPart(right.toRect().intersected(dirtyRect), right.toRect(), rightPosition, devicePixelRatio, true);
0361 }
0362 
0363 void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &partRect,
0364                                                const QPoint &textureOffset,
0365                                                qreal devicePixelRatio, bool rotated)
0366 {
0367     if (!rect.isValid() || !m_texture) {
0368         return;
0369     }
0370     // We allow partial decoration updates and it might just so happen that the
0371     // dirty region is completely contained inside the decoration part, i.e.
0372     // the dirty region doesn't touch any of the decoration's edges. In that
0373     // case, we should **not** pad the dirty region.
0374     const QMargins padding = texturePadForPart(rect, partRect);
0375     int verticalPadding = padding.top() + padding.bottom();
0376     int horizontalPadding = padding.left() + padding.right();
0377 
0378     QSize imageSize(toNativeSize(rect.width()), toNativeSize(rect.height()));
0379     if (rotated) {
0380         imageSize = QSize(imageSize.height(), imageSize.width());
0381     }
0382     QSize paddedImageSize = imageSize;
0383     paddedImageSize.rheight() += verticalPadding;
0384     paddedImageSize.rwidth() += horizontalPadding;
0385     QImage image(paddedImageSize, QImage::Format_ARGB32_Premultiplied);
0386     image.setDevicePixelRatio(devicePixelRatio);
0387     image.fill(Qt::transparent);
0388 
0389     QRect padClip = QRect(padding.left(), padding.top(), imageSize.width(), imageSize.height());
0390     QPainter painter(&image);
0391     const qreal inverseScale = 1.0 / devicePixelRatio;
0392     painter.scale(inverseScale, inverseScale);
0393     painter.setRenderHint(QPainter::Antialiasing);
0394     painter.setClipRect(padClip);
0395     painter.translate(padding.left(), padding.top());
0396     if (rotated) {
0397         painter.translate(0, imageSize.height());
0398         painter.rotate(-90);
0399     }
0400     painter.scale(devicePixelRatio, devicePixelRatio);
0401     painter.translate(-rect.topLeft());
0402     renderToPainter(&painter, rect);
0403     painter.end();
0404 
0405     // fill padding pixels by copying from the neighbour row
0406     clamp(image, padClip);
0407 
0408     QPoint dirtyOffset = (rect.topLeft() - partRect.topLeft()) * devicePixelRatio;
0409     if (padding.top() == 0) {
0410         dirtyOffset.ry() += TexturePad;
0411     }
0412     if (padding.left() == 0) {
0413         dirtyOffset.rx() += TexturePad;
0414     }
0415     m_texture->update(image, textureOffset + dirtyOffset);
0416 }
0417 
0418 const QMargins SceneOpenGLDecorationRenderer::texturePadForPart(
0419     const QRect &rect, const QRect &partRect)
0420 {
0421     QMargins result = QMargins(0, 0, 0, 0);
0422     if (rect.top() == partRect.top()) {
0423         result.setTop(TexturePad);
0424     }
0425     if (rect.bottom() == partRect.bottom()) {
0426         result.setBottom(TexturePad);
0427     }
0428     if (rect.left() == partRect.left()) {
0429         result.setLeft(TexturePad);
0430     }
0431     if (rect.right() == partRect.right()) {
0432         result.setRight(TexturePad);
0433     }
0434     return result;
0435 }
0436 
0437 static int align(int value, int align)
0438 {
0439     return (value + align - 1) & ~(align - 1);
0440 }
0441 
0442 void SceneOpenGLDecorationRenderer::resizeTexture()
0443 {
0444     QRectF left, top, right, bottom;
0445     client()->window()->layoutDecorationRects(left, top, right, bottom);
0446     QSize size;
0447 
0448     size.rwidth() = toNativeSize(std::max(std::max(top.width(), bottom.width()),
0449                                           std::max(left.height(), right.height())));
0450     size.rheight() = toNativeSize(top.height()) + toNativeSize(bottom.height()) + toNativeSize(left.width()) + toNativeSize(right.width());
0451 
0452     size.rheight() += 4 * (2 * TexturePad);
0453     size.rwidth() += 2 * TexturePad;
0454     size.rwidth() = align(size.width(), 128);
0455 
0456     if (m_texture && m_texture->size() == size) {
0457         return;
0458     }
0459 
0460     if (!size.isEmpty()) {
0461         m_texture = GLTexture::allocate(GL_RGBA8, size);
0462         if (!m_texture) {
0463             return;
0464         }
0465         m_texture->setContentTransform(OutputTransform::FlipY);
0466         m_texture->setFilter(GL_LINEAR);
0467         m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
0468         m_texture->clear();
0469     } else {
0470         m_texture.reset();
0471     }
0472 }
0473 
0474 int SceneOpenGLDecorationRenderer::toNativeSize(int size) const
0475 {
0476     return std::round(size * effectiveDevicePixelRatio());
0477 }
0478 
0479 } // namespace
0480 
0481 #include "moc_workspacescene_opengl.cpp"