File indexing completed on 2024-11-10 04:56:57

0001 /*
0002     SPDX-FileCopyrightText: 2010 Fredrik Höglund <fredrik@kde.org>
0003     SPDX-FileCopyrightText: 2011 Philipp Knechtges <philipp-dev@knechtges.com>
0004     SPDX-FileCopyrightText: 2018 Alex Nemeth <alex.nemeth329@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "blur.h"
0010 // KConfigSkeleton
0011 #include "blurconfig.h"
0012 
0013 #include "core/rendertarget.h"
0014 #include "core/renderviewport.h"
0015 #include "effect/effecthandler.h"
0016 #include "opengl/glplatform.h"
0017 #include "utils/xcbutils.h"
0018 #include "wayland/blur.h"
0019 #include "wayland/display.h"
0020 #include "wayland/surface.h"
0021 
0022 #include <QGuiApplication>
0023 #include <QMatrix4x4>
0024 #include <QScreen>
0025 #include <QTime>
0026 #include <QTimer>
0027 #include <QWindow>
0028 #include <cmath> // for ceil()
0029 #include <cstdlib>
0030 
0031 #include <KConfigGroup>
0032 #include <KSharedConfig>
0033 
0034 #include <KDecoration2/Decoration>
0035 
0036 Q_LOGGING_CATEGORY(KWIN_BLUR, "kwin_effect_blur", QtWarningMsg)
0037 
0038 static void ensureResources()
0039 {
0040     // Must initialize resources manually because the effect is a static lib.
0041     Q_INIT_RESOURCE(blur);
0042 }
0043 
0044 namespace KWin
0045 {
0046 
0047 static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
0048 
0049 BlurManagerInterface *BlurEffect::s_blurManager = nullptr;
0050 QTimer *BlurEffect::s_blurManagerRemoveTimer = nullptr;
0051 
0052 BlurEffect::BlurEffect()
0053 {
0054     BlurConfig::instance(effects->config());
0055     ensureResources();
0056 
0057     m_downsamplePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture,
0058                                                                                 QStringLiteral(":/effects/blur/shaders/vertex.vert"),
0059                                                                                 QStringLiteral(":/effects/blur/shaders/downsample.frag"));
0060     if (!m_downsamplePass.shader) {
0061         qCWarning(KWIN_BLUR) << "Failed to load downsampling pass shader";
0062         return;
0063     } else {
0064         m_downsamplePass.mvpMatrixLocation = m_downsamplePass.shader->uniformLocation("modelViewProjectionMatrix");
0065         m_downsamplePass.offsetLocation = m_downsamplePass.shader->uniformLocation("offset");
0066         m_downsamplePass.halfpixelLocation = m_downsamplePass.shader->uniformLocation("halfpixel");
0067     }
0068 
0069     m_upsamplePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture,
0070                                                                               QStringLiteral(":/effects/blur/shaders/vertex.vert"),
0071                                                                               QStringLiteral(":/effects/blur/shaders/upsample.frag"));
0072     if (!m_upsamplePass.shader) {
0073         qCWarning(KWIN_BLUR) << "Failed to load upsampling pass shader";
0074         return;
0075     } else {
0076         m_upsamplePass.mvpMatrixLocation = m_upsamplePass.shader->uniformLocation("modelViewProjectionMatrix");
0077         m_upsamplePass.offsetLocation = m_upsamplePass.shader->uniformLocation("offset");
0078         m_upsamplePass.halfpixelLocation = m_upsamplePass.shader->uniformLocation("halfpixel");
0079     }
0080 
0081     m_noisePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture,
0082                                                                            QStringLiteral(":/effects/blur/shaders/vertex.vert"),
0083                                                                            QStringLiteral(":/effects/blur/shaders/noise.frag"));
0084     if (!m_noisePass.shader) {
0085         qCWarning(KWIN_BLUR) << "Failed to load noise pass shader";
0086         return;
0087     } else {
0088         m_noisePass.mvpMatrixLocation = m_noisePass.shader->uniformLocation("modelViewProjectionMatrix");
0089         m_noisePass.noiseTextureSizeLocation = m_noisePass.shader->uniformLocation("noiseTextureSize");
0090         m_noisePass.texStartPosLocation = m_noisePass.shader->uniformLocation("texStartPos");
0091     }
0092 
0093     initBlurStrengthValues();
0094     reconfigure(ReconfigureAll);
0095 
0096     if (effects->xcbConnection()) {
0097         net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this);
0098     }
0099 
0100     if (effects->waylandDisplay()) {
0101         if (!s_blurManagerRemoveTimer) {
0102             s_blurManagerRemoveTimer = new QTimer(QCoreApplication::instance());
0103             s_blurManagerRemoveTimer->setSingleShot(true);
0104             s_blurManagerRemoveTimer->callOnTimeout([]() {
0105                 s_blurManager->remove();
0106                 s_blurManager = nullptr;
0107             });
0108         }
0109         s_blurManagerRemoveTimer->stop();
0110         if (!s_blurManager) {
0111             s_blurManager = new BlurManagerInterface(effects->waylandDisplay(), s_blurManagerRemoveTimer);
0112         }
0113     }
0114 
0115     connect(effects, &EffectsHandler::windowAdded, this, &BlurEffect::slotWindowAdded);
0116     connect(effects, &EffectsHandler::windowDeleted, this, &BlurEffect::slotWindowDeleted);
0117     connect(effects, &EffectsHandler::screenRemoved, this, &BlurEffect::slotScreenRemoved);
0118     connect(effects, &EffectsHandler::propertyNotify, this, &BlurEffect::slotPropertyNotify);
0119     connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() {
0120         net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this);
0121     });
0122 
0123     // Fetch the blur regions for all windows
0124     const auto stackingOrder = effects->stackingOrder();
0125     for (EffectWindow *window : stackingOrder) {
0126         slotWindowAdded(window);
0127     }
0128 
0129     m_valid = true;
0130 }
0131 
0132 BlurEffect::~BlurEffect()
0133 {
0134     // When compositing is restarted, avoid removing the manager immediately.
0135     if (s_blurManager) {
0136         s_blurManagerRemoveTimer->start(1000);
0137     }
0138 }
0139 
0140 void BlurEffect::initBlurStrengthValues()
0141 {
0142     // This function creates an array of blur strength values that are evenly distributed
0143 
0144     // The range of the slider on the blur settings UI
0145     int numOfBlurSteps = 15;
0146     int remainingSteps = numOfBlurSteps;
0147 
0148     /*
0149      * Explanation for these numbers:
0150      *
0151      * The texture blur amount depends on the downsampling iterations and the offset value.
0152      * By changing the offset we can alter the blur amount without relying on further downsampling.
0153      * But there is a minimum and maximum value of offset per downsample iteration before we
0154      * get artifacts.
0155      *
0156      * The minOffset variable is the minimum offset value for an iteration before we
0157      * get blocky artifacts because of the downsampling.
0158      *
0159      * The maxOffset value is the maximum offset value for an iteration before we
0160      * get diagonal line artifacts because of the nature of the dual kawase blur algorithm.
0161      *
0162      * The expandSize value is the minimum value for an iteration before we reach the end
0163      * of a texture in the shader and sample outside of the area that was copied into the
0164      * texture from the screen.
0165      */
0166 
0167     // {minOffset, maxOffset, expandSize}
0168     blurOffsets.append({1.0, 2.0, 10}); // Down sample size / 2
0169     blurOffsets.append({2.0, 3.0, 20}); // Down sample size / 4
0170     blurOffsets.append({2.0, 5.0, 50}); // Down sample size / 8
0171     blurOffsets.append({3.0, 8.0, 150}); // Down sample size / 16
0172     // blurOffsets.append({5.0, 10.0, 400}); // Down sample size / 32
0173     // blurOffsets.append({7.0, ?.0});       // Down sample size / 64
0174 
0175     float offsetSum = 0;
0176 
0177     for (int i = 0; i < blurOffsets.size(); i++) {
0178         offsetSum += blurOffsets[i].maxOffset - blurOffsets[i].minOffset;
0179     }
0180 
0181     for (int i = 0; i < blurOffsets.size(); i++) {
0182         int iterationNumber = std::ceil((blurOffsets[i].maxOffset - blurOffsets[i].minOffset) / offsetSum * numOfBlurSteps);
0183         remainingSteps -= iterationNumber;
0184 
0185         if (remainingSteps < 0) {
0186             iterationNumber += remainingSteps;
0187         }
0188 
0189         float offsetDifference = blurOffsets[i].maxOffset - blurOffsets[i].minOffset;
0190 
0191         for (int j = 1; j <= iterationNumber; j++) {
0192             // {iteration, offset}
0193             blurStrengthValues.append({i + 1, blurOffsets[i].minOffset + (offsetDifference / iterationNumber) * j});
0194         }
0195     }
0196 }
0197 
0198 void BlurEffect::reconfigure(ReconfigureFlags flags)
0199 {
0200     BlurConfig::self()->read();
0201 
0202     int blurStrength = BlurConfig::blurStrength() - 1;
0203     m_iterationCount = blurStrengthValues[blurStrength].iteration;
0204     m_offset = blurStrengthValues[blurStrength].offset;
0205     m_expandSize = blurOffsets[m_iterationCount - 1].expandSize;
0206     m_noiseStrength = BlurConfig::noiseStrength();
0207 
0208     // Update all windows for the blur to take effect
0209     effects->addRepaintFull();
0210 }
0211 
0212 void BlurEffect::updateBlurRegion(EffectWindow *w)
0213 {
0214     std::optional<QRegion> content;
0215     std::optional<QRegion> frame;
0216 
0217     if (net_wm_blur_region != XCB_ATOM_NONE) {
0218         const QByteArray value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32);
0219         QRegion region;
0220         if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) {
0221             const uint32_t *cardinals = reinterpret_cast<const uint32_t *>(value.constData());
0222             for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) {
0223                 int x = cardinals[i++];
0224                 int y = cardinals[i++];
0225                 int w = cardinals[i++];
0226                 int h = cardinals[i++];
0227                 region += Xcb::fromXNative(QRect(x, y, w, h)).toRect();
0228             }
0229         }
0230         if (!value.isNull()) {
0231             content = region;
0232         }
0233     }
0234 
0235     SurfaceInterface *surf = w->surface();
0236 
0237     if (surf && surf->blur()) {
0238         content = surf->blur()->region();
0239     }
0240 
0241     if (auto internal = w->internalWindow()) {
0242         const auto property = internal->property("kwin_blur");
0243         if (property.isValid()) {
0244             content = property.value<QRegion>();
0245         }
0246     }
0247 
0248     if (w->decorationHasAlpha() && decorationSupportsBlurBehind(w)) {
0249         frame = decorationBlurRegion(w);
0250     }
0251 
0252     if (content.has_value() || frame.has_value()) {
0253         BlurEffectData &data = m_windows[w];
0254         data.content = content;
0255         data.frame = frame;
0256     } else {
0257         if (auto it = m_windows.find(w); it != m_windows.end()) {
0258             effects->makeOpenGLContextCurrent();
0259             m_windows.erase(it);
0260         }
0261     }
0262 }
0263 
0264 void BlurEffect::slotWindowAdded(EffectWindow *w)
0265 {
0266     SurfaceInterface *surf = w->surface();
0267 
0268     if (surf) {
0269         windowBlurChangedConnections[w] = connect(surf, &SurfaceInterface::blurChanged, this, [this, w]() {
0270             if (w) {
0271                 updateBlurRegion(w);
0272             }
0273         });
0274     }
0275     if (auto internal = w->internalWindow()) {
0276         internal->installEventFilter(this);
0277     }
0278 
0279     connect(w, &EffectWindow::windowDecorationChanged, this, &BlurEffect::setupDecorationConnections);
0280     setupDecorationConnections(w);
0281 
0282     updateBlurRegion(w);
0283 }
0284 
0285 void BlurEffect::slotWindowDeleted(EffectWindow *w)
0286 {
0287     if (auto it = m_windows.find(w); it != m_windows.end()) {
0288         effects->makeOpenGLContextCurrent();
0289         m_windows.erase(it);
0290     }
0291     if (auto it = windowBlurChangedConnections.find(w); it != windowBlurChangedConnections.end()) {
0292         disconnect(*it);
0293         windowBlurChangedConnections.erase(it);
0294     }
0295 }
0296 
0297 void BlurEffect::slotScreenRemoved(KWin::Output *screen)
0298 {
0299     for (auto &[window, data] : m_windows) {
0300         if (auto it = data.render.find(screen); it != data.render.end()) {
0301             effects->makeOpenGLContextCurrent();
0302             data.render.erase(it);
0303         }
0304     }
0305 }
0306 
0307 void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom)
0308 {
0309     if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) {
0310         updateBlurRegion(w);
0311     }
0312 }
0313 
0314 void BlurEffect::setupDecorationConnections(EffectWindow *w)
0315 {
0316     if (!w->decoration()) {
0317         return;
0318     }
0319 
0320     connect(w->decoration(), &KDecoration2::Decoration::blurRegionChanged, this, [this, w]() {
0321         updateBlurRegion(w);
0322     });
0323 }
0324 
0325 bool BlurEffect::eventFilter(QObject *watched, QEvent *event)
0326 {
0327     auto internal = qobject_cast<QWindow *>(watched);
0328     if (internal && event->type() == QEvent::DynamicPropertyChange) {
0329         QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent *>(event);
0330         if (pe->propertyName() == "kwin_blur") {
0331             if (auto w = effects->findWindow(internal)) {
0332                 updateBlurRegion(w);
0333             }
0334         }
0335     }
0336     return false;
0337 }
0338 
0339 bool BlurEffect::enabledByDefault()
0340 {
0341     GLPlatform *gl = GLPlatform::instance();
0342 
0343     if (gl->isIntel() && gl->chipClass() < SandyBridge) {
0344         return false;
0345     }
0346     if (gl->isPanfrost() && gl->chipClass() <= MaliT8XX) {
0347         return false;
0348     }
0349     // The blur effect works, but is painfully slow (FPS < 5) on Mali and VideoCore
0350     if (gl->isLima() || gl->isVideoCore4() || gl->isVideoCore3D()) {
0351         return false;
0352     }
0353     if (gl->isSoftwareEmulation()) {
0354         return false;
0355     }
0356 
0357     return true;
0358 }
0359 
0360 bool BlurEffect::supported()
0361 {
0362     return effects->isOpenGLCompositing() && GLFramebuffer::supported() && GLFramebuffer::blitSupported();
0363 }
0364 
0365 bool BlurEffect::decorationSupportsBlurBehind(const EffectWindow *w) const
0366 {
0367     return w->decoration() && !w->decoration()->blurRegion().isNull();
0368 }
0369 
0370 QRegion BlurEffect::decorationBlurRegion(const EffectWindow *w) const
0371 {
0372     if (!decorationSupportsBlurBehind(w)) {
0373         return QRegion();
0374     }
0375 
0376     QRegion decorationRegion = QRegion(w->decoration()->rect()) - w->decorationInnerRect().toRect();
0377     //! we return only blurred regions that belong to decoration region
0378     return decorationRegion.intersected(w->decoration()->blurRegion());
0379 }
0380 
0381 QRegion BlurEffect::blurRegion(EffectWindow *w) const
0382 {
0383     QRegion region;
0384 
0385     if (auto it = m_windows.find(w); it != m_windows.end()) {
0386         const std::optional<QRegion> &content = it->second.content;
0387         const std::optional<QRegion> &frame = it->second.frame;
0388         if (content.has_value()) {
0389             if (content->isEmpty()) {
0390                 // An empty region means that the blur effect should be enabled
0391                 // for the whole window.
0392                 region = w->rect().toRect();
0393             } else {
0394                 if (frame.has_value()) {
0395                     region = frame.value();
0396                 }
0397                 region += content->translated(w->contentsRect().topLeft().toPoint()) & w->decorationInnerRect().toRect();
0398             }
0399         } else if (frame.has_value()) {
0400             region = frame.value();
0401         }
0402     }
0403 
0404     return region;
0405 }
0406 
0407 void BlurEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0408 {
0409     m_paintedArea = QRegion();
0410     m_currentBlur = QRegion();
0411     m_currentScreen = effects->waylandDisplay() ? data.screen : nullptr;
0412 
0413     effects->prePaintScreen(data, presentTime);
0414 }
0415 
0416 void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
0417 {
0418     // this effect relies on prePaintWindow being called in the bottom to top order
0419 
0420     effects->prePaintWindow(w, data, presentTime);
0421 
0422     const QRegion oldOpaque = data.opaque;
0423     if (data.opaque.intersects(m_currentBlur)) {
0424         // to blur an area partially we have to shrink the opaque area of a window
0425         QRegion newOpaque;
0426         for (const QRect &rect : data.opaque) {
0427             newOpaque += rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize);
0428         }
0429         data.opaque = newOpaque;
0430 
0431         // we don't have to blur a region we don't see
0432         m_currentBlur -= newOpaque;
0433     }
0434 
0435     // if we have to paint a non-opaque part of this window that intersects with the
0436     // currently blurred region we have to redraw the whole region
0437     if ((data.paint - oldOpaque).intersects(m_currentBlur)) {
0438         data.paint += m_currentBlur;
0439     }
0440 
0441     // in case this window has regions to be blurred
0442     const QRegion blurArea = blurRegion(w).boundingRect().translated(w->pos().toPoint());
0443 
0444     // if this window or a window underneath the blurred area is painted again we have to
0445     // blur everything
0446     if (m_paintedArea.intersects(blurArea) || data.paint.intersects(blurArea)) {
0447         data.paint += blurArea;
0448         // we have to check again whether we do not damage a blurred area
0449         // of a window
0450         if (blurArea.intersects(m_currentBlur)) {
0451             data.paint += m_currentBlur;
0452         }
0453     }
0454 
0455     m_currentBlur += blurArea;
0456 
0457     m_paintedArea -= data.opaque;
0458     m_paintedArea += data.paint;
0459 }
0460 
0461 bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const
0462 {
0463     if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool()) {
0464         return false;
0465     }
0466 
0467     if (w->isDesktop()) {
0468         return false;
0469     }
0470 
0471     bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0);
0472     bool translated = data.xTranslation() || data.yTranslation();
0473 
0474     if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBlurRole).toBool()) {
0475         return false;
0476     }
0477 
0478     return true;
0479 }
0480 
0481 void BlurEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
0482 {
0483     blur(renderTarget, viewport, w, mask, region, data);
0484 
0485     // Draw the window over the blurred area
0486     effects->drawWindow(renderTarget, viewport, w, mask, region, data);
0487 }
0488 
0489 GLTexture *BlurEffect::ensureNoiseTexture()
0490 {
0491     if (m_noiseStrength == 0) {
0492         return nullptr;
0493     }
0494 
0495     const qreal scale = std::max(1.0, QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.0);
0496     if (!m_noisePass.noiseTexture || m_noisePass.noiseTextureScale != scale || m_noisePass.noiseTextureStength != m_noiseStrength) {
0497         // Init randomness based on time
0498         std::srand((uint)QTime::currentTime().msec());
0499 
0500         QImage noiseImage(QSize(256, 256), QImage::Format_Grayscale8);
0501 
0502         for (int y = 0; y < noiseImage.height(); y++) {
0503             uint8_t *noiseImageLine = (uint8_t *)noiseImage.scanLine(y);
0504 
0505             for (int x = 0; x < noiseImage.width(); x++) {
0506                 noiseImageLine[x] = std::rand() % m_noiseStrength;
0507             }
0508         }
0509 
0510         noiseImage = noiseImage.scaled(noiseImage.size() * scale);
0511 
0512         m_noisePass.noiseTexture = GLTexture::upload(noiseImage);
0513         if (!m_noisePass.noiseTexture) {
0514             return nullptr;
0515         }
0516         m_noisePass.noiseTexture->setFilter(GL_NEAREST);
0517         m_noisePass.noiseTexture->setWrapMode(GL_REPEAT);
0518         m_noisePass.noiseTextureScale = scale;
0519         m_noisePass.noiseTextureStength = m_noiseStrength;
0520     }
0521 
0522     return m_noisePass.noiseTexture.get();
0523 }
0524 
0525 void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
0526 {
0527     auto it = m_windows.find(w);
0528     if (it == m_windows.end()) {
0529         return;
0530     }
0531 
0532     BlurEffectData &blurInfo = it->second;
0533     BlurRenderData &renderInfo = blurInfo.render[m_currentScreen];
0534     if (!shouldBlur(w, mask, data)) {
0535         return;
0536     }
0537 
0538     // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape.
0539     QRegion blurShape = blurRegion(w).translated(w->pos().toPoint());
0540     if (data.xScale() != 1 || data.yScale() != 1) {
0541         QPoint pt = blurShape.boundingRect().topLeft();
0542         QRegion scaledShape;
0543         for (const QRect &r : blurShape) {
0544             const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(),
0545                                   pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation());
0546             const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1,
0547                                      std::floor(topLeft.y() + r.height() * data.yScale()) - 1);
0548             scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight);
0549         }
0550         blurShape = scaledShape;
0551     } else if (data.xTranslation() || data.yTranslation()) {
0552         QRegion translated;
0553         for (const QRect &r : blurShape) {
0554             const QRectF t = QRectF(r).translated(data.xTranslation(), data.yTranslation());
0555             const QPoint topLeft(std::ceil(t.x()), std::ceil(t.y()));
0556             const QPoint bottomRight(std::floor(t.x() + t.width() - 1), std::floor(t.y() + t.height() - 1));
0557             translated += QRect(topLeft, bottomRight);
0558         }
0559         blurShape = translated;
0560     }
0561 
0562     // Get the effective shape that will be actually blurred. It's possible that all of it will be clipped.
0563     const QRegion effectiveShape = blurShape & region;
0564     if (effectiveShape.isEmpty()) {
0565         return;
0566     }
0567 
0568     // Maybe reallocate offscreen render targets. Keep in mind that the first one contains
0569     // original background behind the window, it's not blurred.
0570     GLenum textureFormat = GL_RGBA8;
0571     if (renderTarget.texture()) {
0572         textureFormat = renderTarget.texture()->internalFormat();
0573     }
0574 
0575     const QRect backgroundRect = blurShape.boundingRect();
0576     const QRect deviceBackgroundRect = scaledRect(backgroundRect, viewport.scale()).toRect();
0577     const auto opacity = w->opacity() * data.opacity();
0578 
0579     if (renderInfo.framebuffers.size() != (m_iterationCount + 1) || renderInfo.textures[0]->size() != backgroundRect.size() || renderInfo.textures[0]->internalFormat() != textureFormat) {
0580         renderInfo.framebuffers.clear();
0581         renderInfo.textures.clear();
0582 
0583         for (size_t i = 0; i <= m_iterationCount; ++i) {
0584             auto texture = GLTexture::allocate(textureFormat, backgroundRect.size() / (1 << i));
0585             if (!texture) {
0586                 qCWarning(KWIN_BLUR) << "Failed to allocate an offscreen texture";
0587                 return;
0588             }
0589             texture->setFilter(GL_LINEAR);
0590             texture->setWrapMode(GL_CLAMP_TO_EDGE);
0591 
0592             auto framebuffer = std::make_unique<GLFramebuffer>(texture.get());
0593             if (!framebuffer->valid()) {
0594                 qCWarning(KWIN_BLUR) << "Failed to create an offscreen framebuffer";
0595                 return;
0596             }
0597             renderInfo.textures.push_back(std::move(texture));
0598             renderInfo.framebuffers.push_back(std::move(framebuffer));
0599         }
0600     }
0601 
0602     // Fetch the pixels behind the shape that is going to be blurred.
0603     const QRegion dirtyRegion = region & backgroundRect;
0604     for (const QRect &dirtyRect : dirtyRegion) {
0605         renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, dirtyRect.translated(-backgroundRect.topLeft()));
0606     }
0607 
0608     // Upload the geometry: the first 6 vertices are used when downsampling and upsampling offscreen,
0609     // the remaining vertices are used when rendering on the screen.
0610     GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0611     vbo->reset();
0612     vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D));
0613 
0614     const int vertexCount = effectiveShape.rectCount() * 6;
0615     if (auto result = vbo->map<GLVertex2D>(6 + vertexCount)) {
0616         auto map = *result;
0617 
0618         size_t vboIndex = 0;
0619 
0620         // The geometry that will be blurred offscreen, in logical pixels.
0621         {
0622             const QRectF localRect = QRectF(0, 0, backgroundRect.width(), backgroundRect.height());
0623 
0624             const float x0 = localRect.left();
0625             const float y0 = localRect.top();
0626             const float x1 = localRect.right();
0627             const float y1 = localRect.bottom();
0628 
0629             const float u0 = x0 / backgroundRect.width();
0630             const float v0 = 1.0f - y0 / backgroundRect.height();
0631             const float u1 = x1 / backgroundRect.width();
0632             const float v1 = 1.0f - y1 / backgroundRect.height();
0633 
0634             // first triangle
0635             map[vboIndex++] = GLVertex2D{
0636                 .position = QVector2D(x0, y0),
0637                 .texcoord = QVector2D(u0, v0),
0638             };
0639             map[vboIndex++] = GLVertex2D{
0640                 .position = QVector2D(x1, y1),
0641                 .texcoord = QVector2D(u1, v1),
0642             };
0643             map[vboIndex++] = GLVertex2D{
0644                 .position = QVector2D(x0, y1),
0645                 .texcoord = QVector2D(u0, v1),
0646             };
0647 
0648             // second triangle
0649             map[vboIndex++] = GLVertex2D{
0650                 .position = QVector2D(x0, y0),
0651                 .texcoord = QVector2D(u0, v0),
0652             };
0653             map[vboIndex++] = GLVertex2D{
0654                 .position = QVector2D(x1, y0),
0655                 .texcoord = QVector2D(u1, v0),
0656             };
0657             map[vboIndex++] = GLVertex2D{
0658                 .position = QVector2D(x1, y1),
0659                 .texcoord = QVector2D(u1, v1),
0660             };
0661         }
0662 
0663         // The geometry that will be painted on screen, in device pixels.
0664         for (const QRect &rect : effectiveShape) {
0665             const QRectF localRect = scaledRect(rect, viewport.scale()).translated(-deviceBackgroundRect.topLeft());
0666 
0667             const float x0 = std::round(localRect.left());
0668             const float y0 = std::round(localRect.top());
0669             const float x1 = std::round(localRect.right());
0670             const float y1 = std::round(localRect.bottom());
0671 
0672             const float u0 = x0 / deviceBackgroundRect.width();
0673             const float v0 = 1.0f - y0 / deviceBackgroundRect.height();
0674             const float u1 = x1 / deviceBackgroundRect.width();
0675             const float v1 = 1.0f - y1 / deviceBackgroundRect.height();
0676 
0677             // first triangle
0678             map[vboIndex++] = GLVertex2D{
0679                 .position = QVector2D(x0, y0),
0680                 .texcoord = QVector2D(u0, v0),
0681             };
0682             map[vboIndex++] = GLVertex2D{
0683                 .position = QVector2D(x1, y1),
0684                 .texcoord = QVector2D(u1, v1),
0685             };
0686             map[vboIndex++] = GLVertex2D{
0687                 .position = QVector2D(x0, y1),
0688                 .texcoord = QVector2D(u0, v1),
0689             };
0690 
0691             // second triangle
0692             map[vboIndex++] = GLVertex2D{
0693                 .position = QVector2D(x0, y0),
0694                 .texcoord = QVector2D(u0, v0),
0695             };
0696             map[vboIndex++] = GLVertex2D{
0697                 .position = QVector2D(x1, y0),
0698                 .texcoord = QVector2D(u1, v0),
0699             };
0700             map[vboIndex++] = GLVertex2D{
0701                 .position = QVector2D(x1, y1),
0702                 .texcoord = QVector2D(u1, v1),
0703             };
0704         }
0705 
0706         vbo->unmap();
0707     } else {
0708         qCWarning(KWIN_BLUR) << "Failed to map vertex buffer";
0709         return;
0710     }
0711 
0712     vbo->bindArrays();
0713 
0714     // The downsample pass of the dual Kawase algorithm: the background will be scaled down 50% every iteration.
0715     {
0716         ShaderManager::instance()->pushShader(m_downsamplePass.shader.get());
0717 
0718         QMatrix4x4 projectionMatrix;
0719         projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height()));
0720 
0721         m_downsamplePass.shader->setUniform(m_downsamplePass.mvpMatrixLocation, projectionMatrix);
0722         m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation, float(m_offset));
0723 
0724         for (size_t i = 1; i < renderInfo.framebuffers.size(); ++i) {
0725             const auto &read = renderInfo.framebuffers[i - 1];
0726             const auto &draw = renderInfo.framebuffers[i];
0727 
0728             const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
0729                                       0.5 / read->colorAttachment()->height());
0730             m_downsamplePass.shader->setUniform(m_downsamplePass.halfpixelLocation, halfpixel);
0731 
0732             read->colorAttachment()->bind();
0733 
0734             GLFramebuffer::pushFramebuffer(draw.get());
0735             vbo->draw(GL_TRIANGLES, 0, 6);
0736         }
0737 
0738         ShaderManager::instance()->popShader();
0739     }
0740 
0741     // The upsample pass of the dual Kawase algorithm: the background will be scaled up 200% every iteration.
0742     {
0743         ShaderManager::instance()->pushShader(m_upsamplePass.shader.get());
0744 
0745         QMatrix4x4 projectionMatrix;
0746         projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height()));
0747 
0748         m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix);
0749         m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation, float(m_offset));
0750 
0751         for (size_t i = renderInfo.framebuffers.size() - 1; i > 1; --i) {
0752             GLFramebuffer::popFramebuffer();
0753             const auto &read = renderInfo.framebuffers[i];
0754 
0755             const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
0756                                       0.5 / read->colorAttachment()->height());
0757             m_upsamplePass.shader->setUniform(m_upsamplePass.halfpixelLocation, halfpixel);
0758 
0759             read->colorAttachment()->bind();
0760 
0761             vbo->draw(GL_TRIANGLES, 0, 6);
0762         }
0763 
0764         // The last upsampling pass is rendered on the screen, not in framebuffers[0].
0765         GLFramebuffer::popFramebuffer();
0766         const auto &read = renderInfo.framebuffers[1];
0767 
0768         projectionMatrix = data.projectionMatrix();
0769         projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y());
0770         m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix);
0771 
0772         const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
0773                                   0.5 / read->colorAttachment()->height());
0774         m_upsamplePass.shader->setUniform(m_upsamplePass.halfpixelLocation, halfpixel);
0775 
0776         read->colorAttachment()->bind();
0777 
0778         // Modulate the blurred texture with the window opacity if the window isn't opaque
0779         if (opacity < 1.0) {
0780             glEnable(GL_BLEND);
0781             float o = 1.0f - (opacity);
0782             o = 1.0f - o * o;
0783             glBlendColor(0, 0, 0, o);
0784             glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
0785         }
0786 
0787         vbo->draw(GL_TRIANGLES, 6, vertexCount);
0788 
0789         if (opacity < 1.0) {
0790             glDisable(GL_BLEND);
0791         }
0792 
0793         ShaderManager::instance()->popShader();
0794     }
0795 
0796     if (m_noiseStrength > 0) {
0797         // Apply an additive noise onto the blurred image. The noise is useful to mask banding
0798         // artifacts, which often happens due to the smooth color transitions in the blurred image.
0799 
0800         glEnable(GL_BLEND);
0801         if (opacity < 1.0) {
0802             glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
0803         } else {
0804             glBlendFunc(GL_ONE, GL_ONE);
0805         }
0806 
0807         if (GLTexture *noiseTexture = ensureNoiseTexture()) {
0808             ShaderManager::instance()->pushShader(m_noisePass.shader.get());
0809 
0810             QMatrix4x4 projectionMatrix = data.projectionMatrix();
0811             projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y());
0812 
0813             m_noisePass.shader->setUniform(m_noisePass.mvpMatrixLocation, projectionMatrix);
0814             m_noisePass.shader->setUniform(m_noisePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height()));
0815             m_noisePass.shader->setUniform(m_noisePass.texStartPosLocation, QVector2D(deviceBackgroundRect.topLeft()));
0816 
0817             noiseTexture->bind();
0818 
0819             vbo->draw(GL_TRIANGLES, 6, vertexCount);
0820 
0821             ShaderManager::instance()->popShader();
0822         }
0823 
0824         glDisable(GL_BLEND);
0825     }
0826 
0827     vbo->unbindArrays();
0828 }
0829 
0830 bool BlurEffect::isActive() const
0831 {
0832     return m_valid && !effects->isScreenLocked();
0833 }
0834 
0835 bool BlurEffect::blocksDirectScanout() const
0836 {
0837     return false;
0838 }
0839 
0840 } // namespace KWin
0841 
0842 #include "moc_blur.cpp"