File indexing completed on 2024-05-19 16:34:48

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 <kwinglplatform.h>
0017 
0018 #include "composite.h"
0019 #include "core/output.h"
0020 #include "decorations/decoratedclient.h"
0021 #include "scene/itemrenderer_opengl.h"
0022 #include "window.h"
0023 
0024 #include <cmath>
0025 #include <cstddef>
0026 
0027 #include <QMatrix4x4>
0028 #include <QPainter>
0029 #include <QStringList>
0030 #include <QVector2D>
0031 #include <QVector4D>
0032 #include <QtMath>
0033 
0034 namespace KWin
0035 {
0036 
0037 /************************************************
0038  * SceneOpenGL
0039  ***********************************************/
0040 
0041 WorkspaceSceneOpenGL::WorkspaceSceneOpenGL(OpenGLBackend *backend)
0042     : WorkspaceScene(std::make_unique<ItemRendererOpenGL>())
0043     , m_backend(backend)
0044 {
0045     // It is not legal to not have a vertex array object bound in a core context
0046     if (!GLPlatform::instance()->isGLES() && hasGLExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) {
0047         glGenVertexArrays(1, &vao);
0048         glBindVertexArray(vao);
0049     }
0050 }
0051 
0052 WorkspaceSceneOpenGL::~WorkspaceSceneOpenGL()
0053 {
0054     makeOpenGLContextCurrent();
0055 }
0056 
0057 bool WorkspaceSceneOpenGL::makeOpenGLContextCurrent()
0058 {
0059     return m_backend->makeCurrent();
0060 }
0061 
0062 void WorkspaceSceneOpenGL::doneOpenGLContextCurrent()
0063 {
0064     m_backend->doneCurrent();
0065 }
0066 
0067 bool WorkspaceSceneOpenGL::supportsNativeFence() const
0068 {
0069     return m_backend->supportsNativeFence();
0070 }
0071 
0072 std::unique_ptr<Shadow> WorkspaceSceneOpenGL::createShadow(Window *window)
0073 {
0074     return std::make_unique<SceneOpenGLShadow>(window);
0075 }
0076 
0077 DecorationRenderer *WorkspaceSceneOpenGL::createDecorationRenderer(Decoration::DecoratedClientImpl *impl)
0078 {
0079     return new SceneOpenGLDecorationRenderer(impl);
0080 }
0081 
0082 bool WorkspaceSceneOpenGL::animationsSupported() const
0083 {
0084     return !GLPlatform::instance()->isSoftwareEmulation();
0085 }
0086 
0087 std::shared_ptr<GLTexture> WorkspaceSceneOpenGL::textureForOutput(Output *output) const
0088 {
0089     return m_backend->textureForOutput(output);
0090 }
0091 
0092 //****************************************
0093 // SceneOpenGL::Shadow
0094 //****************************************
0095 class DecorationShadowTextureCache
0096 {
0097 public:
0098     ~DecorationShadowTextureCache();
0099     DecorationShadowTextureCache(const DecorationShadowTextureCache &) = delete;
0100     static DecorationShadowTextureCache &instance();
0101 
0102     void unregister(SceneOpenGLShadow *shadow);
0103     std::shared_ptr<GLTexture> getTexture(SceneOpenGLShadow *shadow);
0104 
0105 private:
0106     DecorationShadowTextureCache() = default;
0107     struct Data
0108     {
0109         std::shared_ptr<GLTexture> texture;
0110         QVector<SceneOpenGLShadow *> shadows;
0111     };
0112     QHash<KDecoration2::DecorationShadow *, Data> m_cache;
0113 };
0114 
0115 DecorationShadowTextureCache &DecorationShadowTextureCache::instance()
0116 {
0117     static DecorationShadowTextureCache s_instance;
0118     return s_instance;
0119 }
0120 
0121 DecorationShadowTextureCache::~DecorationShadowTextureCache()
0122 {
0123     Q_ASSERT(m_cache.isEmpty());
0124 }
0125 
0126 void DecorationShadowTextureCache::unregister(SceneOpenGLShadow *shadow)
0127 {
0128     auto it = m_cache.begin();
0129     while (it != m_cache.end()) {
0130         auto &d = it.value();
0131         // check whether the Vector of Shadows contains our shadow and remove all of them
0132         auto glIt = d.shadows.begin();
0133         while (glIt != d.shadows.end()) {
0134             if (*glIt == shadow) {
0135                 glIt = d.shadows.erase(glIt);
0136             } else {
0137                 glIt++;
0138             }
0139         }
0140         // if there are no shadows any more we can erase the cache entry
0141         if (d.shadows.isEmpty()) {
0142             it = m_cache.erase(it);
0143         } else {
0144             it++;
0145         }
0146     }
0147 }
0148 
0149 std::shared_ptr<GLTexture> DecorationShadowTextureCache::getTexture(SceneOpenGLShadow *shadow)
0150 {
0151     Q_ASSERT(shadow->hasDecorationShadow());
0152     unregister(shadow);
0153     const auto &decoShadow = shadow->decorationShadow().toStrongRef();
0154     Q_ASSERT(!decoShadow.isNull());
0155     auto it = m_cache.find(decoShadow.data());
0156     if (it != m_cache.end()) {
0157         Q_ASSERT(!it.value().shadows.contains(shadow));
0158         it.value().shadows << shadow;
0159         return it.value().texture;
0160     }
0161     Data d;
0162     d.shadows << shadow;
0163     d.texture = std::make_shared<GLTexture>(shadow->decorationShadowImage());
0164     m_cache.insert(decoShadow.data(), d);
0165     return d.texture;
0166 }
0167 
0168 SceneOpenGLShadow::SceneOpenGLShadow(Window *window)
0169     : Shadow(window)
0170 {
0171 }
0172 
0173 SceneOpenGLShadow::~SceneOpenGLShadow()
0174 {
0175     WorkspaceScene *scene = Compositor::self()->scene();
0176     if (scene) {
0177         scene->makeOpenGLContextCurrent();
0178         DecorationShadowTextureCache::instance().unregister(this);
0179         m_texture.reset();
0180     }
0181 }
0182 
0183 bool SceneOpenGLShadow::prepareBackend()
0184 {
0185     if (hasDecorationShadow()) {
0186         // simplifies a lot by going directly to
0187         WorkspaceScene *scene = Compositor::self()->scene();
0188         scene->makeOpenGLContextCurrent();
0189         m_texture = DecorationShadowTextureCache::instance().getTexture(this);
0190 
0191         return true;
0192     }
0193     const QSize top(shadowElement(ShadowElementTop).size());
0194     const QSize topRight(shadowElement(ShadowElementTopRight).size());
0195     const QSize right(shadowElement(ShadowElementRight).size());
0196     const QSize bottom(shadowElement(ShadowElementBottom).size());
0197     const QSize bottomLeft(shadowElement(ShadowElementBottomLeft).size());
0198     const QSize left(shadowElement(ShadowElementLeft).size());
0199     const QSize topLeft(shadowElement(ShadowElementTopLeft).size());
0200     const QSize bottomRight(shadowElement(ShadowElementBottomRight).size());
0201 
0202     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()});
0203     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()});
0204 
0205     if (width == 0 || height == 0) {
0206         return false;
0207     }
0208 
0209     QImage image(width, height, QImage::Format_ARGB32);
0210     image.fill(Qt::transparent);
0211 
0212     const int innerRectTop = std::max({topLeft.height(), top.height(), topRight.height()});
0213     const int innerRectLeft = std::max({topLeft.width(), left.width(), bottomLeft.width()});
0214 
0215     QPainter p;
0216     p.begin(&image);
0217 
0218     p.drawImage(QRectF(0, 0, topLeft.width(), topLeft.height()), shadowElement(ShadowElementTopLeft));
0219     p.drawImage(QRectF(innerRectLeft, 0, top.width(), top.height()), shadowElement(ShadowElementTop));
0220     p.drawImage(QRectF(width - topRight.width(), 0, topRight.width(), topRight.height()), shadowElement(ShadowElementTopRight));
0221 
0222     p.drawImage(QRectF(0, innerRectTop, left.width(), left.height()), shadowElement(ShadowElementLeft));
0223     p.drawImage(QRectF(width - right.width(), innerRectTop, right.width(), right.height()), shadowElement(ShadowElementRight));
0224 
0225     p.drawImage(QRectF(0, height - bottomLeft.height(), bottomLeft.width(), bottomLeft.height()), shadowElement(ShadowElementBottomLeft));
0226     p.drawImage(QRectF(innerRectLeft, height - bottom.height(), bottom.width(), bottom.height()), shadowElement(ShadowElementBottom));
0227     p.drawImage(QRectF(width - bottomRight.width(), height - bottomRight.height(), bottomRight.width(), bottomRight.height()), shadowElement(ShadowElementBottomRight));
0228 
0229     p.end();
0230 
0231     // Check if the image is alpha-only in practice, and if so convert it to an 8-bpp format
0232     if (!GLPlatform::instance()->isGLES() && GLTexture::supportsSwizzle() && GLTexture::supportsFormatRG()) {
0233         QImage alphaImage(image.size(), QImage::Format_Alpha8);
0234         bool alphaOnly = true;
0235 
0236         for (ptrdiff_t y = 0; alphaOnly && y < image.height(); y++) {
0237             const uint32_t *const src = reinterpret_cast<const uint32_t *>(image.scanLine(y));
0238             uint8_t *const dst = reinterpret_cast<uint8_t *>(alphaImage.scanLine(y));
0239 
0240             for (ptrdiff_t x = 0; x < image.width(); x++) {
0241                 if (src[x] & 0x00ffffff) {
0242                     alphaOnly = false;
0243                 }
0244 
0245                 dst[x] = qAlpha(src[x]);
0246             }
0247         }
0248 
0249         if (alphaOnly) {
0250             image = alphaImage;
0251         }
0252     }
0253 
0254     WorkspaceScene *scene = Compositor::self()->scene();
0255     scene->makeOpenGLContextCurrent();
0256     m_texture = std::make_shared<GLTexture>(image);
0257 
0258     if (m_texture->internalFormat() == GL_R8) {
0259         // Swizzle red to alpha and all other channels to zero
0260         m_texture->bind();
0261         m_texture->setSwizzle(GL_ZERO, GL_ZERO, GL_ZERO, GL_RED);
0262     }
0263 
0264     return true;
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::ceil(top.height() * devicePixelRatio);
0347     const int bottomHeight = std::ceil(bottom.height() * devicePixelRatio);
0348     const int leftWidth = std::ceil(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()) {
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.reset(new GLTexture(GL_RGBA8, size.width(), size.height()));
0462         m_texture->setYInverted(true);
0463         m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
0464         m_texture->clear();
0465     } else {
0466         m_texture.reset();
0467     }
0468 }
0469 
0470 int SceneOpenGLDecorationRenderer::toNativeSize(int size) const
0471 {
0472     return std::ceil(size * effectiveDevicePixelRatio());
0473 }
0474 
0475 } // namespace