File indexing completed on 2024-11-10 04:57:17
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 ®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::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"