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 ®ion, 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 ®ion, 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"