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 ®ion) 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