From ac8382cb562a9de8ea5e12e8945ff396150fa0f3 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Thu, 9 May 2024 15:01:16 +0200 Subject: [PATCH 01/28] blur: improve rounded corners implementation --- flake.lock | 6 +- src/blur.cpp | 268 ++++++++++++++++++--------- src/blur.h | 39 +++- src/blur.kcfg | 11 +- src/blur.qrc | 2 + src/kcm/blur_config.ui | 22 ++- src/shaders/roundedcorners.frag | 43 +++++ src/shaders/roundedcorners_core.frag | 47 +++++ 8 files changed, 332 insertions(+), 106 deletions(-) create mode 100644 src/shaders/roundedcorners.frag create mode 100644 src/shaders/roundedcorners_core.frag diff --git a/flake.lock b/flake.lock index 31efe10fb..b5cd9d8ff 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1713537308, - "narHash": "sha256-XtTSSIB2DA6tOv+l0FhvfDMiyCmhoRbNB+0SeInZkbk=", + "lastModified": 1715087517, + "narHash": "sha256-CLU5Tsg24Ke4+7sH8azHWXKd0CFd4mhLWfhYgUiDBpQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5c24cf2f0a12ad855f444c30b2421d044120c66f", + "rev": "b211b392b8486ee79df6cdfb1157ad2133427a29", "type": "github" }, "original": { diff --git a/src/blur.cpp b/src/blur.cpp index 4ecf88666..9d1e96489 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -14,6 +14,7 @@ #include "core/rendertarget.h" #include "core/renderviewport.h" #include "effect/effecthandler.h" +#include "opengl/glutils.h" #include "opengl/glplatform.h" #include "utils/xcbutils.h" #include "wayland/blur.h" @@ -93,10 +94,10 @@ BlurEffect::BlurEffect() } m_texturePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, - QStringLiteral(":/effects/blur/shaders/vertex.vert"), - QStringLiteral(":/effects/blur/shaders/texture.frag")); + QStringLiteral(":/effects/blur/shaders/vertex.vert"), + QStringLiteral(":/effects/blur/shaders/texture.frag")); if (!m_texturePass.shader) { - qCWarning(KWIN_BLUR) << "Failed to load noise pass shader"; + qCWarning(KWIN_BLUR) << "Failed to load texture pass shader"; return; } else { m_texturePass.mvpMatrixLocation = m_texturePass.shader->uniformLocation("modelViewProjectionMatrix"); @@ -104,6 +105,32 @@ BlurEffect::BlurEffect() m_texturePass.texStartPosLocation = m_texturePass.shader->uniformLocation("texStartPos"); } + m_roundedCorners.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, + QStringLiteral(":/effects/blur/shaders/vertex.vert"), + QStringLiteral(":/effects/blur/shaders/roundedcorners.frag")); + if (!m_roundedCorners.shader) { + qCWarning(KWIN_BLUR) << "Failed to load rounded corners shader"; + return; + } else { + m_roundedCorners.roundTopLeftCornerLocation = m_roundedCorners.shader->uniformLocation("roundTopLeftCorner"); + m_roundedCorners.roundTopRightCornerLocation = m_roundedCorners.shader->uniformLocation("roundTopRightCorner"); + m_roundedCorners.roundBottomLeftCornerLocation = m_roundedCorners.shader->uniformLocation("roundBottomLeftCorner"); + m_roundedCorners.roundBottomRightCornerLocation = m_roundedCorners.shader->uniformLocation("roundBottomRightCorner"); + + m_roundedCorners.topCornerRadiusLocation = m_roundedCorners.shader->uniformLocation("topCornerRadius"); + m_roundedCorners.bottomCornerRadiusLocation = m_roundedCorners.shader->uniformLocation("bottomCornerRadius"); + + m_roundedCorners.antialiasingLocation = m_roundedCorners.shader->uniformLocation("antialiasing"); + + m_roundedCorners.offsetLocation = m_roundedCorners.shader->uniformLocation("offset"); + m_roundedCorners.regionSizeLocation = m_roundedCorners.shader->uniformLocation("regionSize"); + + m_roundedCorners.beforeBlurTextureLocation = m_roundedCorners.shader->uniformLocation("beforeBlurTexture"); + m_roundedCorners.afterBlurTextureLocation = m_roundedCorners.shader->uniformLocation("afterBlurTexture"); + + m_roundedCorners.mvpMatrixLocation = m_roundedCorners.shader->uniformLocation("modelViewProjectionMatrix"); + } + initBlurStrengthValues(); reconfigure(ReconfigureAll); @@ -225,6 +252,7 @@ void BlurEffect::reconfigure(ReconfigureFlags flags) m_transparentBlur = BlurConfig::transparentBlur(); m_topCornerRadius = BlurConfig::topCornerRadius(); m_bottomCornerRadius = BlurConfig::bottomCornerRadius(); + m_roundedCornersAntialiasing = BlurConfig::roundedCornersAntialiasing(); m_roundCornersOfMaximizedWindows = BlurConfig::roundCornersOfMaximizedWindows(); m_blurMenus = BlurConfig::blurMenus(); m_blurDocks = BlurConfig::blurDocks(); @@ -238,8 +266,6 @@ void BlurEffect::reconfigure(ReconfigureFlags flags) m_texturePass.texture = GLTexture::upload(fakeBlurImage); } - updateCornerRegions(); - for (EffectWindow *w : effects->stackingOrder()) { updateBlurRegion(w); } @@ -308,36 +334,6 @@ void BlurEffect::updateBlurRegion(EffectWindow *w) } } -void BlurEffect::updateCornerRegions() -{ - QRegion square = QRegion(0, 0, m_topCornerRadius, m_topCornerRadius); - QRegion circle = QRegion(0, 0, 2 * m_topCornerRadius, 2 * m_topCornerRadius, QRegion::RegionType::Ellipse); - m_topLeftCorner = QRegion(0, 0, m_topCornerRadius, m_topCornerRadius); - m_topRightCorner = QRegion(0, 0, m_topCornerRadius, m_topCornerRadius); - - m_topLeftCorner &= circle; - m_topLeftCorner ^= square; - circle.translate(-m_topCornerRadius, 0); - m_topRightCorner &= circle; - m_topRightCorner ^= square; - - square = QRegion(0, 0, m_bottomCornerRadius, m_bottomCornerRadius); - circle = QRegion(0, 0, 2 * m_bottomCornerRadius, 2 * m_bottomCornerRadius, QRegion::RegionType::Ellipse); - - m_bottomLeftCorner = QRegion(0, 0, m_bottomCornerRadius, m_bottomCornerRadius); - m_bottomRightCorner = QRegion(0, 0, m_bottomCornerRadius, m_bottomCornerRadius); - circle.translate(0, -m_bottomCornerRadius); - m_bottomLeftCorner &= circle; - m_bottomLeftCorner ^= square; - - circle.translate(0, m_bottomCornerRadius); - circle.translate(-m_bottomCornerRadius, 0); - circle.translate(0, -m_bottomCornerRadius); - - m_bottomRightCorner &= circle; - m_bottomRightCorner ^= square; -} - void BlurEffect::slotWindowAdded(EffectWindow *w) { SurfaceInterface *surf = w->surface(); @@ -450,7 +446,7 @@ QRegion BlurEffect::decorationBlurRegion(const EffectWindow *w) const return decorationRegion.intersected(w->decoration()->blurRegion()); } -QRegion BlurEffect::blurRegion(EffectWindow *w, bool noRoundedCorners) const +QRegion BlurEffect::blurRegion(EffectWindow *w) const { QRegion region; @@ -473,23 +469,27 @@ QRegion BlurEffect::blurRegion(EffectWindow *w, bool noRoundedCorners) const } } - bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); - if (!noRoundedCorners && (!isMaximized || m_roundCornersOfMaximizedWindows)) { - if (m_topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { - QPoint topRightPosition = QPoint(w->rect().width() - m_topCornerRadius, 0); - region -= m_topLeftCorner; - region -= m_topRightCorner.translated(topRightPosition); - } + return region; +} - if (m_bottomCornerRadius) { - QPoint bottomLeftPosition = QPoint(0, w->rect().height() - m_bottomCornerRadius); - QPoint bottomRightPosition = QPoint(w->rect().width() - m_bottomCornerRadius, w->rect().height() - m_bottomCornerRadius); - region -= m_bottomLeftCorner.translated(bottomLeftPosition); - region -= m_bottomRightCorner.translated(bottomRightPosition); +QRegion BlurEffect::effectiveBlurRegion(QRegion blurRegion, const WindowPaintData &data) const +{ + if (data.xScale() != 1 || data.yScale() != 1) { + QPoint pt = blurRegion.boundingRect().topLeft(); + QRegion scaledShape; + for (const QRect &r : blurRegion) { + const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), + pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); + const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1, + std::floor(topLeft.y() + r.height() * data.yScale()) - 1); + scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight); } + blurRegion = scaledShape; + } else if (data.xTranslation() || data.yTranslation()) { + blurRegion.translate(std::round(data.xTranslation()), std::round(data.yTranslation())); } - return region; + return blurRegion; } void BlurEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) @@ -514,12 +514,7 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: } if (shouldForceBlur(w) && m_paintAsTranslucent) { - if (hasFakeBlur) { - // Remove rounded corners region - data.opaque -= blurRegion(w, true).translated(w->pos().toPoint()) - blurArea; - } else { - data.setTranslucent(); - } + data.setTranslucent(); } effects->prePaintWindow(w, data, presentTime); @@ -651,22 +646,40 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi } // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape. - QRegion blurShape = blurRegion(w).translated(w->pos().toPoint()); - if (data.xScale() != 1 || data.yScale() != 1) { - QPoint pt = blurShape.boundingRect().topLeft(); - QRegion scaledShape; - for (const QRect &r : blurShape) { - const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), - pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); - const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1, - std::floor(topLeft.y() + r.height() * data.yScale()) - 1); - scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight); + QRegion blurShape = effectiveBlurRegion(blurRegion(w).translated(w->pos().toPoint()), data); + + // Check whether the effective blur shape contains corners that need to be rounded. + // If the shape contains only a fragment of a corner, the entire corner will be added to the shape. + // TODO Corners shouldn't be blurred if they are clipped. + bool roundTopLeftCorner = false; + bool roundTopRightCorner = false; + bool roundBottomLeftCorner = false; + bool roundBottomRightCorner = false; + + // The Y axis is flipped on Wayland. + bool isWayland = effects->waylandDisplay(); + + bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); + if (!isMaximized || m_roundCornersOfMaximizedWindows) { + const auto windowGeometry = w->frameGeometry(); + if (m_topCornerRadius && (!isWayland || (!w->decoration() || (w->decoration() && m_blurDecorations)))) { + const QRect topLeftCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), m_topCornerRadius, m_topCornerRadius)),data).boundingRect(); + roundTopLeftCorner = blurShape.intersects(topLeftCorner); + + const QRect topRightCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_topCornerRadius, windowGeometry.y(),m_topCornerRadius, m_topCornerRadius)), data).boundingRect(); + roundTopRightCorner = blurShape.intersects(topRightCorner); + } + if (m_bottomCornerRadius && (isWayland || (!w->decoration() || (w->decoration() && m_blurDecorations)))) { + const QRect bottomLeftCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() +windowGeometry.height() -m_bottomCornerRadius,m_bottomCornerRadius,m_bottomCornerRadius)),data).boundingRect(); + roundBottomLeftCorner = blurShape.intersects(bottomLeftCorner); + + const QRect bottomRightCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_bottomCornerRadius,windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius, m_bottomCornerRadius)), data).boundingRect(); + roundBottomRightCorner = blurShape.intersects(bottomRightCorner); } - blurShape = scaledShape; - } else if (data.xTranslation() || data.yTranslation()) { - blurShape.translate(std::round(data.xTranslation()), std::round(data.yTranslation())); } + const bool hasRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; + const QRect backgroundRect = blurShape.boundingRect(); const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); const auto opacity = m_transparentBlur @@ -679,7 +692,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi if (region != infiniteRegion()) { for (const QRect &clipRect : region) { const QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, viewport.scale())) - .translated(-deviceBackgroundRect.topLeft()); + .translated(-deviceBackgroundRect.topLeft()); for (const QRect &shapeRect : blurShape) { const QRectF deviceShapeRect = snapToPixelGridF(scaledRect(shapeRect.translated(-backgroundRect.topLeft()), viewport.scale())); if (const QRectF intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) { @@ -727,6 +740,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi } // Fetch the pixels behind the shape that is going to be blurred. + // This framebuffer is left unchanged, so we can use that for rounding corners. const QRegion dirtyRegion = region & backgroundRect; for (const QRect &dirtyRect : dirtyRegion) { renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, dirtyRect.translated(-backgroundRect.topLeft())); @@ -836,11 +850,22 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi vbo->bindArrays(); + const auto finalBlurTexture = GLTexture::allocate(textureFormat, backgroundRect.size()); + finalBlurTexture->setFilter(GL_LINEAR); + finalBlurTexture->setWrapMode(GL_CLAMP_TO_EDGE); + const auto finalBlurFramebuffer = std::make_unique(finalBlurTexture.get()); + if (m_fakeBlur && m_hasValidFakeBlurTexture) { ShaderManager::instance()->pushShader(m_texturePass.shader.get()); - QMatrix4x4 projectionMatrix = data.projectionMatrix(); - projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + QMatrix4x4 projectionMatrix; + if (hasRoundedCorners) { + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); + GLFramebuffer::pushFramebuffer(finalBlurFramebuffer.get()); + } else { + projectionMatrix = data.projectionMatrix(); + projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + } m_texturePass.shader->setUniform(m_texturePass.mvpMatrixLocation, projectionMatrix); m_texturePass.shader->setUniform(m_texturePass.textureSizeLocation, QVector2D(m_texturePass.texture.get()->width(), m_texturePass.texture.get()->height())); @@ -848,17 +873,24 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi m_texturePass.texture.get()->bind(); - glEnable(GL_BLEND); - float o = 1.0f - (opacity); - o = 1.0f - o * o; - glBlendColor(0, 0, 0, o); - glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + if (!hasRoundedCorners) { + glEnable(GL_BLEND); + float o = 1.0f - (opacity); + o = 1.0f - o * o; + glBlendColor(0, 0, 0, o); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + } - vbo->draw(GL_TRIANGLES, 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, vertexCount); - glDisable(GL_BLEND); + if (!hasRoundedCorners) { + glDisable(GL_BLEND); + } ShaderManager::instance()->popShader(); + if (hasRoundedCorners) { + GLFramebuffer::popFramebuffer(); + } } else { // The downsample pass of the dual Kawase algorithm: the background will be scaled down 50% every iteration. { @@ -875,7 +907,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi const auto &draw = renderInfo.framebuffers[i]; const QVector2D halfpixel(0.5 / read->colorAttachment()->width(), - 0.5 / read->colorAttachment()->height()); + 0.5 / read->colorAttachment()->height()); m_downsamplePass.shader->setUniform(m_downsamplePass.halfpixelLocation, halfpixel); read->colorAttachment()->bind(); @@ -913,8 +945,14 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi GLFramebuffer::popFramebuffer(); const auto &read = renderInfo.framebuffers[1]; - projectionMatrix = data.projectionMatrix(); - projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + if (hasRoundedCorners) { + GLFramebuffer::pushFramebuffer(finalBlurFramebuffer.get()); + projectionMatrix = QMatrix4x4(); + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); + } else { + projectionMatrix = data.projectionMatrix(); + projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + } m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix); const QVector2D halfpixel(0.5 / read->colorAttachment()->width(), @@ -924,7 +962,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi read->colorAttachment()->bind(); // Modulate the blurred texture with the window opacity if the window isn't opaque - if (opacity < 1.0) { + if (!hasRoundedCorners && opacity < 1.0) { glEnable(GL_BLEND); float o = 1.0f - (opacity); o = 1.0f - o * o; @@ -932,9 +970,9 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - vbo->draw(GL_TRIANGLES, 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, vertexCount); - if (opacity < 1.0) { + if (!hasRoundedCorners && opacity < 1.0) { glDisable(GL_BLEND); } @@ -954,8 +992,13 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi if (GLTexture *noiseTexture = ensureNoiseTexture()) { ShaderManager::instance()->pushShader(m_noisePass.shader.get()); - QMatrix4x4 projectionMatrix = data.projectionMatrix(); - projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + QMatrix4x4 projectionMatrix; + if (hasRoundedCorners) { + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); + } else { + projectionMatrix = data.projectionMatrix(); + projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + } m_noisePass.shader->setUniform(m_noisePass.mvpMatrixLocation, projectionMatrix); m_noisePass.shader->setUniform(m_noisePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height())); @@ -963,13 +1006,64 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi noiseTexture->bind(); - vbo->draw(GL_TRIANGLES, 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, vertexCount); ShaderManager::instance()->popShader(); } glDisable(GL_BLEND); } + + if (hasRoundedCorners) { + GLFramebuffer::popFramebuffer(); + } + } + + if (hasRoundedCorners) { + QMatrix4x4 projectionMatrix = data.projectionMatrix(); + projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + + float yOffset = deviceBackgroundRect.y(); + if (!isWayland) { + yOffset = renderTarget.framebuffer()->size().height() - w->y() - w->height(); // why + } + + ShaderManager::instance()->pushShader(m_roundedCorners.shader.get()); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopLeftCornerLocation, roundTopLeftCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, roundTopRightCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, roundBottomLeftCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, roundBottomRightCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, isWayland ? m_topCornerRadius : m_bottomCornerRadius); + m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, isWayland ? m_bottomCornerRadius : m_topCornerRadius); + m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, m_roundedCornersAntialiasing); + m_roundedCorners.shader->setUniform(m_roundedCorners.offsetLocation, QVector2D(deviceBackgroundRect.x(), yOffset)); + m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(deviceBackgroundRect.size().width(), deviceBackgroundRect.size().height())); + m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); + + glUniform1i(m_roundedCorners.beforeBlurTextureLocation, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, renderInfo.textures[0]->texture()); + + glUniform1i(m_roundedCorners.afterBlurTextureLocation, 1); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, finalBlurTexture->texture()); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (opacity < 1.0f) { + float o = 1.0f - (opacity); + o = 1.0f - o * o; + glBlendColor(0, 0, 0, o); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + } + + vbo->draw(GL_TRIANGLES, 6, vertexCount); + + glDisable(GL_BLEND); + vbo->unbindArrays(); + renderInfo.textures[0]->unbind(); + finalBlurTexture->unbind(); + ShaderManager::instance()->popShader(); } vbo->unbindArrays(); diff --git a/src/blur.h b/src/blur.h index aa0739346..546ccde44 100644 --- a/src/blur.h +++ b/src/blur.h @@ -77,16 +77,17 @@ public Q_SLOTS: private: void initBlurStrengthValues(); - QRegion blurRegion(EffectWindow *w, bool noRoundedCorners = false) const; + QRegion blurRegion(EffectWindow *w) const; QRegion decorationBlurRegion(const EffectWindow *w) const; + QRegion effectiveBlurRegion(QRegion blurRegion, const WindowPaintData &data) const; bool decorationSupportsBlurBehind(const EffectWindow *w) const; bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const; bool shouldForceBlur(const EffectWindow *w) const; void updateBlurRegion(EffectWindow *w); - void updateCornerRegions(); void blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data); GLTexture *ensureNoiseTexture(); bool hasFakeBlur(EffectWindow *w) const; + inline QVector2D toVector2D(const QSizeF& s) { return {static_cast(s.width()), static_cast(s.height())}; } private: struct @@ -127,6 +128,29 @@ public Q_SLOTS: std::unique_ptr texture; } m_texturePass; + struct + { + std::unique_ptr shader; + + int roundTopLeftCornerLocation; + int roundTopRightCornerLocation; + int roundBottomLeftCornerLocation; + int roundBottomRightCornerLocation; + + int topCornerRadiusLocation; + int bottomCornerRadiusLocation; + + int antialiasingLocation; + + int regionSizeLocation; + int offsetLocation; + + int beforeBlurTextureLocation; + int afterBlurTextureLocation; + + int mvpMatrixLocation; + } m_roundedCorners; + bool m_valid = false; long net_wm_blur_region = 0; QRegion m_paintedArea; // keeps track of all painted areas (from bottom to top) @@ -142,8 +166,9 @@ public Q_SLOTS: bool m_blurNonMatching; bool m_blurDecorations; bool m_transparentBlur; - int m_topCornerRadius; - int m_bottomCornerRadius; + float m_topCornerRadius; + float m_bottomCornerRadius; + float m_roundedCornersAntialiasing; bool m_roundCornersOfMaximizedWindows; bool m_blurMenus; bool m_blurDocks; @@ -153,12 +178,6 @@ public Q_SLOTS: bool m_hasValidFakeBlurTexture; - // Regions to subtract from the blurred region - QRegion m_topLeftCorner; - QRegion m_topRightCorner; - QRegion m_bottomLeftCorner; - QRegion m_bottomRightCorner; - struct OffsetStruct { float minOffset; diff --git a/src/blur.kcfg b/src/blur.kcfg index 04544a0af..91dee7ee4 100644 --- a/src/blur.kcfg +++ b/src/blur.kcfg @@ -28,11 +28,14 @@ class3 true - - 0 + + 0.0 - - 0 + + 0.0 + + + 1.0 false diff --git a/src/blur.qrc b/src/blur.qrc index 8039e2b48..d7c8b16b0 100644 --- a/src/blur.qrc +++ b/src/blur.qrc @@ -4,6 +4,8 @@ shaders/downsample_core.frag shaders/noise.frag shaders/noise_core.frag + shaders/roundedcorners.frag + shaders/roundedcorners_core.frag shaders/texture.frag shaders/texture_core.frag shaders/upsample.frag diff --git a/src/kcm/blur_config.ui b/src/kcm/blur_config.ui index b89605845..617783a6f 100644 --- a/src/kcm/blur_config.ui +++ b/src/kcm/blur_config.ui @@ -326,7 +326,7 @@ - + 0 @@ -344,7 +344,25 @@ - + + + 0 + + + + + + + + + + + Antialiasing + + + + + 0 diff --git a/src/shaders/roundedcorners.frag b/src/shaders/roundedcorners.frag new file mode 100644 index 000000000..6e802b827 --- /dev/null +++ b/src/shaders/roundedcorners.frag @@ -0,0 +1,43 @@ +// Modified version of https://www.shadertoy.com/view/ldfSDj + +uniform bool roundTopLeftCorner; +uniform bool roundTopRightCorner; +uniform bool roundBottomLeftCorner; +uniform bool roundBottomRightCorner; + +uniform float topCornerRadius; +uniform float bottomCornerRadius; + +uniform float antialiasing; + +uniform vec2 regionSize; +uniform vec2 offset; + +uniform sampler2D beforeBlurTexture; +uniform sampler2D afterBlurTexture; + +varying vec2 uv; + +float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) +{ + float radius = 0.0; + if ((fragCoord.y <= topCornerRadius) + && ((roundTopLeftCorner && fragCoord.x <= topCornerRadius) + || (roundTopRightCorner && fragCoord.x >= regionSize.x - topCornerRadius))) { + radius = topCornerRadius; + } else if ((fragCoord.y >= regionSize.y - bottomCornerRadius) + && ((roundBottomLeftCorner && fragCoord.x <= bottomCornerRadius) + || (roundBottomRightCorner && fragCoord.x >= regionSize.x - bottomCornerRadius))) { + radius = bottomCornerRadius; + } + + return length(max(abs(p) - b + radius, 0.0)) - radius; +} + +void main(void) +{ + vec2 halfRegionSize = regionSize * 0.5; + vec2 textureLocation = (gl_FragCoord.xy - offset) / regionSize.xy; + float box = udRoundBox(-offset + gl_FragCoord.xy - halfRegionSize, halfRegionSize, gl_FragCoord.xy - offset); + gl_FragColor = vec4(mix(texture2D(afterBlurTexture, uv).rgb, texture2D(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); +} \ No newline at end of file diff --git a/src/shaders/roundedcorners_core.frag b/src/shaders/roundedcorners_core.frag new file mode 100644 index 000000000..dd24aaf8a --- /dev/null +++ b/src/shaders/roundedcorners_core.frag @@ -0,0 +1,47 @@ +#version 140 + +// Modified version of https://www.shadertoy.com/view/ldfSDj + +uniform bool roundTopLeftCorner; +uniform bool roundTopRightCorner; +uniform bool roundBottomLeftCorner; +uniform bool roundBottomRightCorner; + +uniform float topCornerRadius; +uniform float bottomCornerRadius; + +uniform float antialiasing; + +uniform vec2 regionSize; +uniform vec2 offset; + +uniform sampler2D beforeBlurTexture; +uniform sampler2D afterBlurTexture; + +in vec2 uv; + +out vec4 fragColor; + +float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) +{ + float radius = 0.0; + if ((fragCoord.y <= topCornerRadius) + && ((roundTopLeftCorner && fragCoord.x <= topCornerRadius) + || (roundTopRightCorner && fragCoord.x >= regionSize.x - topCornerRadius))) { + radius = topCornerRadius; + } else if ((fragCoord.y >= regionSize.y - bottomCornerRadius) + && ((roundBottomLeftCorner && fragCoord.x <= bottomCornerRadius) + || (roundBottomRightCorner && fragCoord.x >= regionSize.x - bottomCornerRadius))) { + radius = bottomCornerRadius; + } + + return length(max(abs(p) - b + radius, 0.0)) - radius; +} + +void main(void) +{ + vec2 halfRegionSize = regionSize * 0.5; + vec2 textureLocation = (gl_FragCoord.xy - offset) / regionSize.xy; + float box = udRoundBox(-offset + gl_FragCoord.xy - halfRegionSize, halfRegionSize, gl_FragCoord.xy - offset); + fragColor = vec4(mix(texture(afterBlurTexture, uv).rgb, texture(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); +} From 37e4446d24c7488e198282a4085c01501cbbabd8 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Fri, 10 May 2024 13:51:51 +0200 Subject: [PATCH 02/28] blur/rounded-corners: use uv to get pixel position instead of gl_FragCoord --- src/blur.cpp | 14 +++----------- src/blur.h | 1 - src/shaders/roundedcorners.frag | 15 +++++++-------- src/shaders/roundedcorners_core.frag | 5 ++--- 4 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index 9d1e96489..8852a92b1 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -122,7 +122,6 @@ BlurEffect::BlurEffect() m_roundedCorners.antialiasingLocation = m_roundedCorners.shader->uniformLocation("antialiasing"); - m_roundedCorners.offsetLocation = m_roundedCorners.shader->uniformLocation("offset"); m_roundedCorners.regionSizeLocation = m_roundedCorners.shader->uniformLocation("regionSize"); m_roundedCorners.beforeBlurTextureLocation = m_roundedCorners.shader->uniformLocation("beforeBlurTexture"); @@ -881,7 +880,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, hasRoundedCorners ? 6 : vertexCount); if (!hasRoundedCorners) { glDisable(GL_BLEND); @@ -970,7 +969,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, hasRoundedCorners ? 6 : vertexCount); if (!hasRoundedCorners && opacity < 1.0) { glDisable(GL_BLEND); @@ -1006,7 +1005,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi noiseTexture->bind(); - vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, vertexCount); + vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, hasRoundedCorners ? 6 : vertexCount); ShaderManager::instance()->popShader(); } @@ -1023,11 +1022,6 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi QMatrix4x4 projectionMatrix = data.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); - float yOffset = deviceBackgroundRect.y(); - if (!isWayland) { - yOffset = renderTarget.framebuffer()->size().height() - w->y() - w->height(); // why - } - ShaderManager::instance()->pushShader(m_roundedCorners.shader.get()); m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopLeftCornerLocation, roundTopLeftCorner); m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, roundTopRightCorner); @@ -1036,7 +1030,6 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, isWayland ? m_topCornerRadius : m_bottomCornerRadius); m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, isWayland ? m_bottomCornerRadius : m_topCornerRadius); m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, m_roundedCornersAntialiasing); - m_roundedCorners.shader->setUniform(m_roundedCorners.offsetLocation, QVector2D(deviceBackgroundRect.x(), yOffset)); m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(deviceBackgroundRect.size().width(), deviceBackgroundRect.size().height())); m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); @@ -1060,7 +1053,6 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi vbo->draw(GL_TRIANGLES, 6, vertexCount); glDisable(GL_BLEND); - vbo->unbindArrays(); renderInfo.textures[0]->unbind(); finalBlurTexture->unbind(); ShaderManager::instance()->popShader(); diff --git a/src/blur.h b/src/blur.h index 546ccde44..c25fa248a 100644 --- a/src/blur.h +++ b/src/blur.h @@ -143,7 +143,6 @@ public Q_SLOTS: int antialiasingLocation; int regionSizeLocation; - int offsetLocation; int beforeBlurTextureLocation; int afterBlurTextureLocation; diff --git a/src/shaders/roundedcorners.frag b/src/shaders/roundedcorners.frag index 6e802b827..f23c8ecc1 100644 --- a/src/shaders/roundedcorners.frag +++ b/src/shaders/roundedcorners.frag @@ -11,7 +11,6 @@ uniform float bottomCornerRadius; uniform float antialiasing; uniform vec2 regionSize; -uniform vec2 offset; uniform sampler2D beforeBlurTexture; uniform sampler2D afterBlurTexture; @@ -22,12 +21,12 @@ float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) { float radius = 0.0; if ((fragCoord.y <= topCornerRadius) - && ((roundTopLeftCorner && fragCoord.x <= topCornerRadius) - || (roundTopRightCorner && fragCoord.x >= regionSize.x - topCornerRadius))) { + && ((roundTopLeftCorner && fragCoord.x <= topCornerRadius) + || (roundTopRightCorner && fragCoord.x >= regionSize.x - topCornerRadius))) { radius = topCornerRadius; } else if ((fragCoord.y >= regionSize.y - bottomCornerRadius) - && ((roundBottomLeftCorner && fragCoord.x <= bottomCornerRadius) - || (roundBottomRightCorner && fragCoord.x >= regionSize.x - bottomCornerRadius))) { + && ((roundBottomLeftCorner && fragCoord.x <= bottomCornerRadius) + || (roundBottomRightCorner && fragCoord.x >= regionSize.x - bottomCornerRadius))) { radius = bottomCornerRadius; } @@ -37,7 +36,7 @@ float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) void main(void) { vec2 halfRegionSize = regionSize * 0.5; - vec2 textureLocation = (gl_FragCoord.xy - offset) / regionSize.xy; - float box = udRoundBox(-offset + gl_FragCoord.xy - halfRegionSize, halfRegionSize, gl_FragCoord.xy - offset); - gl_FragColor = vec4(mix(texture2D(afterBlurTexture, uv).rgb, texture2D(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); + vec2 fragCoord = uv * regionSize; + float box = udRoundBox(fragCoord - halfRegionSize, halfRegionSize, fragCoord); + gl_FragCoord = vec4(mix(texture2D(afterBlurTexture, uv).rgb, texture2D(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); } \ No newline at end of file diff --git a/src/shaders/roundedcorners_core.frag b/src/shaders/roundedcorners_core.frag index dd24aaf8a..c301ec6c3 100644 --- a/src/shaders/roundedcorners_core.frag +++ b/src/shaders/roundedcorners_core.frag @@ -13,7 +13,6 @@ uniform float bottomCornerRadius; uniform float antialiasing; uniform vec2 regionSize; -uniform vec2 offset; uniform sampler2D beforeBlurTexture; uniform sampler2D afterBlurTexture; @@ -41,7 +40,7 @@ float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) void main(void) { vec2 halfRegionSize = regionSize * 0.5; - vec2 textureLocation = (gl_FragCoord.xy - offset) / regionSize.xy; - float box = udRoundBox(-offset + gl_FragCoord.xy - halfRegionSize, halfRegionSize, gl_FragCoord.xy - offset); + vec2 fragCoord = uv * regionSize; + float box = udRoundBox(fragCoord - halfRegionSize, halfRegionSize, fragCoord); fragColor = vec4(mix(texture(afterBlurTexture, uv).rgb, texture(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); } From 0f9644c7acb05ae21c7fc82b0591b588c2380c9d Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Fri, 10 May 2024 14:40:08 +0200 Subject: [PATCH 03/28] blur/rounded-corners: fix corners sometimes not being rounded --- src/blur.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index 8852a92b1..bbf8bfab4 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -655,21 +655,19 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi bool roundBottomLeftCorner = false; bool roundBottomRightCorner = false; - // The Y axis is flipped on Wayland. - bool isWayland = effects->waylandDisplay(); - bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); if (!isMaximized || m_roundCornersOfMaximizedWindows) { const auto windowGeometry = w->frameGeometry(); - if (m_topCornerRadius && (!isWayland || (!w->decoration() || (w->decoration() && m_blurDecorations)))) { + if (m_topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { const QRect topLeftCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), m_topCornerRadius, m_topCornerRadius)),data).boundingRect(); roundTopLeftCorner = blurShape.intersects(topLeftCorner); const QRect topRightCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_topCornerRadius, windowGeometry.y(),m_topCornerRadius, m_topCornerRadius)), data).boundingRect(); roundTopRightCorner = blurShape.intersects(topRightCorner); } - if (m_bottomCornerRadius && (isWayland || (!w->decoration() || (w->decoration() && m_blurDecorations)))) { - const QRect bottomLeftCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() +windowGeometry.height() -m_bottomCornerRadius,m_bottomCornerRadius,m_bottomCornerRadius)),data).boundingRect(); + + if (m_bottomCornerRadius) { + const QRect bottomLeftCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius,m_bottomCornerRadius)),data).boundingRect(); roundBottomLeftCorner = blurShape.intersects(bottomLeftCorner); const QRect bottomRightCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_bottomCornerRadius,windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius, m_bottomCornerRadius)), data).boundingRect(); @@ -1022,13 +1020,15 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi QMatrix4x4 projectionMatrix = data.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); + // The Y axis is flipped in OpenGL. + // TODO Rename the uniforms ShaderManager::instance()->pushShader(m_roundedCorners.shader.get()); - m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopLeftCornerLocation, roundTopLeftCorner); - m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, roundTopRightCorner); - m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, roundBottomLeftCorner); - m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, roundBottomRightCorner); - m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, isWayland ? m_topCornerRadius : m_bottomCornerRadius); - m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, isWayland ? m_bottomCornerRadius : m_topCornerRadius); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopLeftCornerLocation, roundBottomLeftCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, roundBottomRightCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, roundTopLeftCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, roundTopRightCorner); + m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, m_bottomCornerRadius); + m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, m_topCornerRadius); m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, m_roundedCornersAntialiasing); m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(deviceBackgroundRect.size().width(), deviceBackgroundRect.size().height())); m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); From b7b4cd5b72b65b5c0cc141921d72058c5e1cf8a7 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Fri, 10 May 2024 14:51:25 +0200 Subject: [PATCH 04/28] blur/rounded-corners: don't use rounded corners shader when painting regions that don't have rounded corners --- src/blur.cpp | 59 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index bbf8bfab4..8ce99592f 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -647,36 +647,6 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape. QRegion blurShape = effectiveBlurRegion(blurRegion(w).translated(w->pos().toPoint()), data); - // Check whether the effective blur shape contains corners that need to be rounded. - // If the shape contains only a fragment of a corner, the entire corner will be added to the shape. - // TODO Corners shouldn't be blurred if they are clipped. - bool roundTopLeftCorner = false; - bool roundTopRightCorner = false; - bool roundBottomLeftCorner = false; - bool roundBottomRightCorner = false; - - bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); - if (!isMaximized || m_roundCornersOfMaximizedWindows) { - const auto windowGeometry = w->frameGeometry(); - if (m_topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { - const QRect topLeftCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), m_topCornerRadius, m_topCornerRadius)),data).boundingRect(); - roundTopLeftCorner = blurShape.intersects(topLeftCorner); - - const QRect topRightCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_topCornerRadius, windowGeometry.y(),m_topCornerRadius, m_topCornerRadius)), data).boundingRect(); - roundTopRightCorner = blurShape.intersects(topRightCorner); - } - - if (m_bottomCornerRadius) { - const QRect bottomLeftCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius,m_bottomCornerRadius)),data).boundingRect(); - roundBottomLeftCorner = blurShape.intersects(bottomLeftCorner); - - const QRect bottomRightCorner = effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_bottomCornerRadius,windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius, m_bottomCornerRadius)), data).boundingRect(); - roundBottomRightCorner = blurShape.intersects(bottomRightCorner); - } - } - - const bool hasRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; - const QRect backgroundRect = blurShape.boundingRect(); const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); const auto opacity = m_transparentBlur @@ -706,6 +676,35 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi return; } + // Check whether the effective blur shape contains corners that need to be rounded. + bool roundTopLeftCorner = false; + bool roundTopRightCorner = false; + bool roundBottomLeftCorner = false; + bool roundBottomRightCorner = false; + + bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); + if (!isMaximized || m_roundCornersOfMaximizedWindows) { + const auto windowGeometry = w->frameGeometry(); + for (const QRectF &rect : effectiveShape) { + if (m_topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { + const QRectF topLeftCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), m_topCornerRadius, m_topCornerRadius)),data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + roundTopLeftCorner = roundTopLeftCorner || rect.intersects(topLeftCorner); + + const QRectF topRightCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_topCornerRadius, windowGeometry.y(),m_topCornerRadius, m_topCornerRadius)), data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + roundTopRightCorner = roundTopRightCorner || rect.intersects(topRightCorner); + } + if (m_bottomCornerRadius) { + const QRectF bottomLeftCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius,m_bottomCornerRadius)),data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + roundBottomLeftCorner = roundBottomLeftCorner || rect.intersects(bottomLeftCorner); + + const QRectF bottomRightCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_bottomCornerRadius,windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius, m_bottomCornerRadius)), data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + roundBottomRightCorner = roundBottomRightCorner || rect.intersects(bottomRightCorner); + } + } + } + + const bool hasRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; + // Maybe reallocate offscreen render targets. Keep in mind that the first one contains // original background behind the window, it's not blurred. GLenum textureFormat = GL_RGBA8; From 7debe91a1b5727bc7645514f271b7547c44d7119 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Fri, 10 May 2024 16:06:48 +0200 Subject: [PATCH 05/28] blur: fix drawing images behind windows --- src/blur.cpp | 6 ++++-- src/blur.h | 1 + src/shaders/roundedcorners.frag | 2 +- src/shaders/texture.frag | 4 ++-- src/shaders/texture_core.frag | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index 8ce99592f..af8d00ece 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -103,6 +103,7 @@ BlurEffect::BlurEffect() m_texturePass.mvpMatrixLocation = m_texturePass.shader->uniformLocation("modelViewProjectionMatrix"); m_texturePass.textureSizeLocation = m_texturePass.shader->uniformLocation("textureSize"); m_texturePass.texStartPosLocation = m_texturePass.shader->uniformLocation("texStartPos"); + m_texturePass.regionSizeLocation = m_texturePass.shader->uniformLocation("regionSize"); } m_roundedCorners.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, @@ -865,7 +866,8 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi m_texturePass.shader->setUniform(m_texturePass.mvpMatrixLocation, projectionMatrix); m_texturePass.shader->setUniform(m_texturePass.textureSizeLocation, QVector2D(m_texturePass.texture.get()->width(), m_texturePass.texture.get()->height())); - m_texturePass.shader->setUniform(m_texturePass.texStartPosLocation, QVector2D(0, 0)); + m_texturePass.shader->setUniform(m_texturePass.texStartPosLocation, QVector2D(backgroundRect.x(), backgroundRect.y())); + m_texturePass.shader->setUniform(m_texturePass.regionSizeLocation, QVector2D(backgroundRect.width(), backgroundRect.height())); m_texturePass.texture.get()->bind(); @@ -1029,7 +1031,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, m_bottomCornerRadius); m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, m_topCornerRadius); m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, m_roundedCornersAntialiasing); - m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(deviceBackgroundRect.size().width(), deviceBackgroundRect.size().height())); + m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(deviceBackgroundRect.width(), deviceBackgroundRect.height())); m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); glUniform1i(m_roundedCorners.beforeBlurTextureLocation, 0); diff --git a/src/blur.h b/src/blur.h index c25fa248a..945d3c217 100644 --- a/src/blur.h +++ b/src/blur.h @@ -124,6 +124,7 @@ public Q_SLOTS: int mvpMatrixLocation; int textureSizeLocation; int texStartPosLocation; + int regionSizeLocation; std::unique_ptr texture; } m_texturePass; diff --git a/src/shaders/roundedcorners.frag b/src/shaders/roundedcorners.frag index f23c8ecc1..d9f4cbfbe 100644 --- a/src/shaders/roundedcorners.frag +++ b/src/shaders/roundedcorners.frag @@ -38,5 +38,5 @@ void main(void) vec2 halfRegionSize = regionSize * 0.5; vec2 fragCoord = uv * regionSize; float box = udRoundBox(fragCoord - halfRegionSize, halfRegionSize, fragCoord); - gl_FragCoord = vec4(mix(texture2D(afterBlurTexture, uv).rgb, texture2D(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); + gl_FragColor = vec4(mix(texture2D(afterBlurTexture, uv).rgb, texture2D(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); } \ No newline at end of file diff --git a/src/shaders/texture.frag b/src/shaders/texture.frag index 61b0dd133..2d0348e78 100644 --- a/src/shaders/texture.frag +++ b/src/shaders/texture.frag @@ -1,12 +1,12 @@ uniform sampler2D texUnit; uniform vec2 textureSize; uniform vec2 texStartPos; +uniform vec2 regionSize; varying vec2 uv; void main(void) { - vec2 tex = vec2((texStartPos.xy + gl_FragCoord.xy) / textureSize); - + vec2 tex = (texStartPos.xy + vec2(uv.x, 1.0 - uv.y) * regionSize) / textureSize; gl_FragColor = vec4(texture2D(texUnit, tex).rgb, 0); } diff --git a/src/shaders/texture_core.frag b/src/shaders/texture_core.frag index 3e3583361..afc99a28d 100644 --- a/src/shaders/texture_core.frag +++ b/src/shaders/texture_core.frag @@ -3,6 +3,7 @@ uniform sampler2D texUnit; uniform vec2 textureSize; uniform vec2 texStartPos; +uniform vec2 regionSize; in vec2 uv; @@ -10,7 +11,6 @@ out vec4 fragColor; void main(void) { - vec2 tex = vec2((texStartPos.xy + gl_FragCoord.xy) / textureSize); - + vec2 tex = (texStartPos.xy + vec2(uv.x, 1.0 - uv.y) * regionSize) / textureSize; fragColor = vec4(texture(texUnit, tex).rgb, 0); } From 87fbddbacd03acb7bc229e0333bb5db2802d7d17 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Fri, 10 May 2024 16:23:59 +0200 Subject: [PATCH 06/28] blur/rounded-corners: multiply radius by display scale --- src/blur.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index af8d00ece..f3a811e8c 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -677,6 +677,9 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi return; } + float topCornerRadius = m_topCornerRadius * viewport.scale(); + float bottomCornerRadius = m_bottomCornerRadius * viewport.scale(); + // Check whether the effective blur shape contains corners that need to be rounded. bool roundTopLeftCorner = false; bool roundTopRightCorner = false; @@ -687,18 +690,18 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi if (!isMaximized || m_roundCornersOfMaximizedWindows) { const auto windowGeometry = w->frameGeometry(); for (const QRectF &rect : effectiveShape) { - if (m_topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { - const QRectF topLeftCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), m_topCornerRadius, m_topCornerRadius)),data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + if (topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { + const QRectF topLeftCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), topCornerRadius, topCornerRadius)),data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); roundTopLeftCorner = roundTopLeftCorner || rect.intersects(topLeftCorner); - const QRectF topRightCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_topCornerRadius, windowGeometry.y(),m_topCornerRadius, m_topCornerRadius)), data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + const QRectF topRightCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - topCornerRadius, windowGeometry.y(),topCornerRadius, topCornerRadius)), data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); roundTopRightCorner = roundTopRightCorner || rect.intersects(topRightCorner); } - if (m_bottomCornerRadius) { - const QRectF bottomLeftCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius,m_bottomCornerRadius)),data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + if (bottomCornerRadius) { + const QRectF bottomLeftCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius,bottomCornerRadius)),data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); roundBottomLeftCorner = roundBottomLeftCorner || rect.intersects(bottomLeftCorner); - const QRectF bottomRightCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - m_bottomCornerRadius,windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius,m_bottomCornerRadius, m_bottomCornerRadius)), data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + const QRectF bottomRightCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - bottomCornerRadius,windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); roundBottomRightCorner = roundBottomRightCorner || rect.intersects(bottomRightCorner); } } @@ -1028,8 +1031,8 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, roundBottomRightCorner); m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, roundTopLeftCorner); m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, roundTopRightCorner); - m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, m_bottomCornerRadius); - m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, m_topCornerRadius); + m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, bottomCornerRadius); + m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, topCornerRadius); m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, m_roundedCornersAntialiasing); m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(deviceBackgroundRect.width(), deviceBackgroundRect.height())); m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); From 836981e0a753e37908591b17bf094fc1ead96f59 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Fri, 10 May 2024 19:49:52 +0200 Subject: [PATCH 07/28] blur/rounded-corners: set active texture to 0 after painting --- src/blur.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/blur.cpp b/src/blur.cpp index f3a811e8c..27a46d43d 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -1057,6 +1057,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi vbo->draw(GL_TRIANGLES, 6, vertexCount); glDisable(GL_BLEND); + glActiveTexture(GL_TEXTURE0); renderInfo.textures[0]->unbind(); finalBlurTexture->unbind(); ShaderManager::instance()->popShader(); From 0e65deed429ba43f84e764c328b14e397cc270bc Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sat, 18 May 2024 14:05:53 +0200 Subject: [PATCH 08/28] blur/rounded-corners: allow corners to occupy more than half of the rectangle's height --- src/shaders/roundedcorners.frag | 4 +++- src/shaders/roundedcorners_core.frag | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/shaders/roundedcorners.frag b/src/shaders/roundedcorners.frag index d9f4cbfbe..43c7165f5 100644 --- a/src/shaders/roundedcorners.frag +++ b/src/shaders/roundedcorners.frag @@ -24,13 +24,15 @@ float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) && ((roundTopLeftCorner && fragCoord.x <= topCornerRadius) || (roundTopRightCorner && fragCoord.x >= regionSize.x - topCornerRadius))) { radius = topCornerRadius; + p.y -= radius; } else if ((fragCoord.y >= regionSize.y - bottomCornerRadius) && ((roundBottomLeftCorner && fragCoord.x <= bottomCornerRadius) || (roundBottomRightCorner && fragCoord.x >= regionSize.x - bottomCornerRadius))) { radius = bottomCornerRadius; + p.y += radius; } - return length(max(abs(p) - b + radius, 0.0)) - radius; + return length(max(abs(p) - (b + vec2(0.0, radius)) + radius, 0.0)) - radius; } void main(void) diff --git a/src/shaders/roundedcorners_core.frag b/src/shaders/roundedcorners_core.frag index c301ec6c3..ebd0f49b0 100644 --- a/src/shaders/roundedcorners_core.frag +++ b/src/shaders/roundedcorners_core.frag @@ -28,13 +28,15 @@ float udRoundBox(vec2 p, vec2 b, vec2 fragCoord) && ((roundTopLeftCorner && fragCoord.x <= topCornerRadius) || (roundTopRightCorner && fragCoord.x >= regionSize.x - topCornerRadius))) { radius = topCornerRadius; + p.y -= radius; } else if ((fragCoord.y >= regionSize.y - bottomCornerRadius) && ((roundBottomLeftCorner && fragCoord.x <= bottomCornerRadius) || (roundBottomRightCorner && fragCoord.x >= regionSize.x - bottomCornerRadius))) { radius = bottomCornerRadius; + p.y += radius; } - return length(max(abs(p) - b + radius, 0.0)) - radius; + return length(max(abs(p) - (b + vec2(0.0, radius)) + radius, 0.0)) - radius; } void main(void) From bcb72da4415c55bf4e90ed6bb35b62dd94a66556 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sat, 18 May 2024 14:24:24 +0200 Subject: [PATCH 09/28] blur/rounded-corners: remove corners from opaque region when using fake blur --- src/blur.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/blur.cpp b/src/blur.cpp index 27a46d43d..0e5703d48 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -511,6 +511,16 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: bool hasFakeBlur = m_fakeBlur && m_hasValidFakeBlurTexture && !blurArea.isEmpty(); if (hasFakeBlur) { data.opaque += blurArea; + + const auto windowGeometry = w->frameGeometry(); + if (m_topCornerRadius) { + data.opaque -= QRect(windowGeometry.x(), windowGeometry.y(), m_topCornerRadius, m_topCornerRadius); + data.opaque -= QRect(windowGeometry.x() + windowGeometry.width() - m_topCornerRadius, windowGeometry.y(), m_topCornerRadius, m_topCornerRadius); + } + if (m_bottomCornerRadius) { + data.opaque -= QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius, m_bottomCornerRadius, m_bottomCornerRadius); + data.opaque -= QRect(windowGeometry.x() + windowGeometry.width() - m_bottomCornerRadius, windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius, m_bottomCornerRadius, m_bottomCornerRadius); + } } if (shouldForceBlur(w) && m_paintAsTranslucent) { From 51e1e0cf3d8e42dc631a3007194d82ea773bb455 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sat, 18 May 2024 14:55:38 +0200 Subject: [PATCH 10/28] index on develop: bcb72da blur/rounded-corners: remove corners from opaque region when using fake blur From 2ca3888bcff29a94d93d1cb476f4a6fa3701ff87 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:19:48 +0200 Subject: [PATCH 11/28] blur/rounded-corners: do some stuff --- src/blur.cpp | 283 +++++++++++++++++++++------ src/blur.h | 32 ++- src/blur.kcfg | 8 +- src/shaders/roundedcorners.frag | 15 +- src/shaders/roundedcorners_core.frag | 15 +- src/utils.h | 20 ++ 6 files changed, 294 insertions(+), 79 deletions(-) create mode 100644 src/utils.h diff --git a/src/blur.cpp b/src/blur.cpp index b8a9500da..6484ee42e 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -16,6 +16,7 @@ #include "effect/effecthandler.h" #include "opengl/glutils.h" #include "opengl/glplatform.h" +#include "utils.h" #include "utils/xcbutils.h" #include "wayland/blur.h" #include "wayland/display.h" @@ -260,12 +261,18 @@ void BlurEffect::reconfigure(ReconfigureFlags flags) m_fakeBlur = BlurConfig::fakeBlur(); m_fakeBlurImage = BlurConfig::fakeBlurImage(); + m_cornerRadiusOffset = m_roundedCornersAntialiasing == 0 ? 0 : std::round(m_roundedCornersAntialiasing) + 2; + m_topCornerRadius = std::max(0, m_topCornerRadius - m_cornerRadiusOffset); + m_bottomCornerRadius = std::max(0, m_bottomCornerRadius - m_cornerRadiusOffset); + QImage fakeBlurImage(m_fakeBlurImage); m_hasValidFakeBlurTexture = !fakeBlurImage.isNull(); if (m_hasValidFakeBlurTexture) { m_texturePass.texture = GLTexture::upload(fakeBlurImage); } + m_corners.clear(); + for (EffectWindow *w : effects->stackingOrder()) { updateBlurRegion(w); } @@ -334,6 +341,116 @@ void BlurEffect::updateBlurRegion(EffectWindow *w) } } +void BlurEffect::generateRoundedCornerMasks(int radius, QRegion &left, QRegion &right, bool top) const +{ + // This method uses OpenGL to draw circles, since the ones drawn by Qt are terrible. + + left = QRegion(); + right = QRegion(); + + if (radius == 0) { + return; + } + + float size = radius * 2; + auto cornersTexture = GLTexture::allocate(GL_RGB8, QSize(size, size)); + auto cornersFramebuffer = std::make_unique(cornersTexture.get()); + + GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); + vbo->reset(); + vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D)); + if (auto result = vbo->map(6)) { + auto map = *result; + + size_t vboIndex = 0; + + const float x0 = 0; + const float y0 = 0; + const float x1 = size; + const float y1 = size; + + const float u0 = x0 / size; + const float v0 = 1.0f - y0 / size; + const float u1 = x1 / size; + const float v1 = 1.0f - y1 / size; + + // first triangle + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x0, y0), + .texcoord = QVector2D(u0, v0), + }; + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x1, y1), + .texcoord = QVector2D(u1, v1), + }; + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x0, y1), + .texcoord = QVector2D(u0, v1), + }; + + // second triangle + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x0, y0), + .texcoord = QVector2D(u0, v0), + }; + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x1, y0), + .texcoord = QVector2D(u1, v0), + }; + map[vboIndex++] = GLVertex2D{ + .position = QVector2D(x1, y1), + .texcoord = QVector2D(u1, v1), + }; + + vbo->unmap(); + } else { + qCWarning(KWIN_BLUR) << "Failed to map vertex buffer"; + return; + } + + vbo->bindArrays(); + + QMatrix4x4 projectionMatrix; + projectionMatrix.ortho(QRect(0, 0, size, size)); + + ShaderManager::instance()->pushShader(m_roundedCorners.shader.get()); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopLeftCornerLocation, false); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, false); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, true); + m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, true); + m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, 0); + m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, radius); + m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, static_cast(0)); + m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(size, size)); + m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); + + GLFramebuffer::pushFramebuffer(cornersFramebuffer.get()); + vbo->draw(GL_TRIANGLES, 0, 6); + GLFramebuffer::popFramebuffer(); + ShaderManager::instance()->popShader(); + vbo->unbindArrays(); + + QImage img = cornersTexture->toImage().mirrored().copy(0, 0, radius, radius); + if (!top) { + img.mirror(); + } + + left = QRegion(QBitmap::fromImage(img.createMaskFromColor(QColor(Qt::black).rgb(), Qt::MaskOutColor), Qt::DiffuseAlphaDither)); + right = QRegion(QBitmap::fromImage(img.mirrored(true, false).createMaskFromColor(QColor(Qt::black).rgb(), Qt::MaskOutColor), Qt::DiffuseAlphaDither));; +} + +std::array BlurEffect::roundedCorners(qreal scale) +{ + if (m_corners.contains(scale)) { + return m_corners[scale]; + } + + std::array corners; + generateRoundedCornerMasks(std::round(m_topCornerRadius * scale), corners[0], corners[1], true); + generateRoundedCornerMasks(std::round(m_bottomCornerRadius * scale), corners[2], corners[3], false); + return m_corners[scale] = corners; +} + void BlurEffect::slotWindowAdded(EffectWindow *w) { SurfaceInterface *surf = w->surface(); @@ -472,7 +589,7 @@ QRegion BlurEffect::blurRegion(EffectWindow *w) const return region; } -QRegion BlurEffect::effectiveBlurRegion(QRegion blurRegion, const WindowPaintData &data) const +QRegion BlurEffect::transformedBlurRegion(QRegion blurRegion, const WindowPaintData &data) const { if (data.xScale() != 1 || data.yScale() != 1) { QPoint pt = blurRegion.boundingRect().topLeft(); @@ -642,6 +759,30 @@ GLTexture *BlurEffect::ensureNoiseTexture() return m_noisePass.noiseTexture.get(); } +QList BlurEffect::effectiveBlurShape(const QRect &backgroundRect, const QRect &deviceBackgroundRect, const QRegion &paintRegion, const QRegion &blurRegion, qreal screenScale, qreal regionScale) const +{ + QList effectiveShape; + effectiveShape.reserve(blurRegion.rectCount()); + if (paintRegion != infiniteRegion()) { + for (const QRect &clipRect : paintRegion) { + const QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, screenScale)) + .translated(-deviceBackgroundRect.topLeft()); + for (const QRect &shapeRect : blurRegion) { + const QRectF deviceShapeRect = snapToPixelGridF(scaledRect(shapeRect.translated(-backgroundRect.topLeft()), regionScale)); + if (const QRectF intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) { + effectiveShape.append(intersected); + } + } + } + } else { + for (const QRect &rect : blurRegion) { + effectiveShape.append(snapToPixelGridF(scaledRect(rect.translated(-backgroundRect.topLeft()), regionScale))); + } + } + + return effectiveShape; +} + void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) { auto it = m_windows.find(w); @@ -655,8 +796,25 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi return; } + bool hasRoundedCorners = (data.xScale() == 1 || data.yScale() == 1) && ((m_topCornerRadius || m_bottomCornerRadius)); + + // The blur region is scaled later, so we'll remove the corners for now and add them back after scaling, otherwise they'll look terrible. + QRegion blurShape = blurRegion(w); + if (hasRoundedCorners) { + const auto corners = roundedCorners(viewport.scale()); + const auto windowGeometry = w->frameGeometry(); + if (m_topCornerRadius) { + blurShape -= QRect(0, 0, m_topCornerRadius, m_topCornerRadius); + blurShape -= QRect(windowGeometry.width() - m_topCornerRadius, 0, m_topCornerRadius, m_topCornerRadius); + } + if (m_bottomCornerRadius) { + blurShape -= QRect(0, windowGeometry.height() - m_bottomCornerRadius, m_bottomCornerRadius, m_bottomCornerRadius); + blurShape -= QRect(windowGeometry.width() - m_bottomCornerRadius, windowGeometry.height() - m_bottomCornerRadius, m_bottomCornerRadius, m_bottomCornerRadius); + } + } + // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape. - QRegion blurShape = effectiveBlurRegion(blurRegion(w).translated(w->pos().toPoint()), data); + blurShape = transformedBlurRegion(blurShape.translated(w->pos().toPoint()), data); const QRect backgroundRect = blurShape.boundingRect(); const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); @@ -664,60 +822,62 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi ? w->opacity() * data.opacity() : data.opacity(); + int topCornerRadius = std::round(m_topCornerRadius * viewport.scale()); + int bottomCornerRadius = std::round(m_bottomCornerRadius * viewport.scale()); + // Get the effective shape that will be actually blurred. It's possible that all of it will be clipped. - QList effectiveShape; - effectiveShape.reserve(blurShape.rectCount()); - if (region != infiniteRegion()) { - for (const QRect &clipRect : region) { - const QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, viewport.scale())) - .translated(-deviceBackgroundRect.topLeft()); - for (const QRect &shapeRect : blurShape) { - const QRectF deviceShapeRect = snapToPixelGridF(scaledRect(shapeRect.translated(-backgroundRect.topLeft()), viewport.scale())); - if (const QRectF intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) { - effectiveShape.append(intersected); - } - } + QList effectiveShape = effectiveBlurShape(backgroundRect, deviceBackgroundRect, region, blurShape, viewport.scale(), viewport.scale()); + + // Add rounded corners to the blur region. + if (hasRoundedCorners) { + QRegion cornersRegion; + const auto corners = roundedCorners(viewport.scale()); + const auto windowGeometry = scaledRect(w->frameGeometry(), viewport.scale()); + if (topCornerRadius) { + QRegion square = QRect(0, 0, topCornerRadius, topCornerRadius); + cornersRegion += square - corners[0]; + cornersRegion += (square - corners[1]).translated(std::round(windowGeometry.width()) - topCornerRadius, 0); } - } else { - for (const QRect &rect : blurShape) { - effectiveShape.append(snapToPixelGridF(scaledRect(rect.translated(-backgroundRect.topLeft()), viewport.scale()))); + if (bottomCornerRadius) { + QRegion square = QRect(0, 0, bottomCornerRadius, bottomCornerRadius); + cornersRegion += (square - corners[2]).translated(0, std::round(windowGeometry.height()) - bottomCornerRadius); + cornersRegion += (square - corners[3]).translated(std::round(windowGeometry.width()) - bottomCornerRadius, std::round(windowGeometry.height()) - bottomCornerRadius); } + cornersRegion = cornersRegion.intersected(scaledRegion(blurRegion(w), viewport.scale())); + cornersRegion = transformedBlurRegion(cornersRegion.translated(w->pos().toPoint()), data); + effectiveShape.append(effectiveBlurShape(backgroundRect, deviceBackgroundRect, region, cornersRegion, viewport.scale(), 1)); } if (effectiveShape.isEmpty()) { return; } - float topCornerRadius = m_topCornerRadius * viewport.scale(); - float bottomCornerRadius = m_bottomCornerRadius * viewport.scale(); - // Check whether the effective blur shape contains corners that need to be rounded. bool roundTopLeftCorner = false; bool roundTopRightCorner = false; bool roundBottomLeftCorner = false; bool roundBottomRightCorner = false; - bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); - if (!isMaximized || m_roundCornersOfMaximizedWindows) { - const auto windowGeometry = w->frameGeometry(); + if (hasRoundedCorners && m_roundedCornersAntialiasing > 0) { + const auto windowGeometry = scaledRect(w->frameGeometry(), viewport.scale()); for (const QRectF &rect : effectiveShape) { if (topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { - const QRectF topLeftCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), topCornerRadius, topCornerRadius)),data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + const QRectF topLeftCorner = snapToPixelGridF(transformedBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), topCornerRadius, topCornerRadius)), data).boundingRect()).translated(-deviceBackgroundRect.topLeft()); roundTopLeftCorner = roundTopLeftCorner || rect.intersects(topLeftCorner); - const QRectF topRightCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - topCornerRadius, windowGeometry.y(),topCornerRadius, topCornerRadius)), data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + const QRectF topRightCorner = snapToPixelGridF(transformedBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - topCornerRadius, windowGeometry.y(),topCornerRadius, topCornerRadius)), data).boundingRect()).translated(-deviceBackgroundRect.topLeft()); roundTopRightCorner = roundTopRightCorner || rect.intersects(topRightCorner); } if (bottomCornerRadius) { - const QRectF bottomLeftCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius,bottomCornerRadius)),data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + const QRectF bottomLeftCorner = snapToPixelGridF(transformedBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect()).translated(-deviceBackgroundRect.topLeft()); roundBottomLeftCorner = roundBottomLeftCorner || rect.intersects(bottomLeftCorner); - const QRectF bottomRightCorner = snapToPixelGridF(scaledRect(effectiveBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - bottomCornerRadius,windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect(), viewport.scale())).translated(-deviceBackgroundRect.topLeft()); + const QRectF bottomRightCorner = snapToPixelGridF(transformedBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - bottomCornerRadius, windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect()).translated(-deviceBackgroundRect.topLeft()); roundBottomRightCorner = roundBottomRightCorner || rect.intersects(bottomRightCorner); } } } - const bool hasRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; + const bool hasAntialiasedRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; // Maybe reallocate offscreen render targets. Keep in mind that the first one contains // original background behind the window, it's not blurred. @@ -726,12 +886,12 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi textureFormat = renderTarget.texture()->internalFormat(); } - if (renderInfo.framebuffers.size() != (m_iterationCount + 1) || renderInfo.textures[0]->size() != deviceBackgroundRect.size() || renderInfo.textures[0]->internalFormat() != textureFormat) { + if (renderInfo.framebuffers.size() != (m_iterationCount + 1) || renderInfo.textures[0]->size() != backgroundRect.size() || renderInfo.textures[0]->internalFormat() != textureFormat) { renderInfo.framebuffers.clear(); renderInfo.textures.clear(); for (size_t i = 0; i <= m_iterationCount; ++i) { - auto texture = GLTexture::allocate(textureFormat, deviceBackgroundRect.size() / (1 << i)); + auto texture = GLTexture::allocate(textureFormat, backgroundRect.size() / (1 << i)); if (!texture) { qCWarning(KWIN_BLUR) << "Failed to allocate an offscreen texture"; return; @@ -753,7 +913,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi // This framebuffer is left unchanged, so we can use that for rounding corners. const QRegion dirtyRegion = region & backgroundRect; for (const QRect &dirtyRect : dirtyRegion) { - renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, snapToPixelGrid(scaledRect(dirtyRect.translated(-backgroundRect.topLeft()), viewport.scale()))); + renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, dirtyRect.translated(-backgroundRect.topLeft())); } // Upload the geometry: the first 6 vertices are used when downsampling and upsampling offscreen, @@ -770,17 +930,17 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi // The geometry that will be blurred offscreen, in logical pixels. { - const QRectF localRect = QRectF(0, 0, deviceBackgroundRect.width(), deviceBackgroundRect.height()); + const QRectF localRect = QRectF(0, 0, backgroundRect.width(), backgroundRect.height()); const float x0 = localRect.left(); const float y0 = localRect.top(); const float x1 = localRect.right(); const float y1 = localRect.bottom(); - const float u0 = x0 / deviceBackgroundRect.width(); - const float v0 = 1.0f - y0 / deviceBackgroundRect.height(); - const float u1 = x1 / deviceBackgroundRect.width(); - const float v1 = 1.0f - y1 / deviceBackgroundRect.height(); + const float u0 = x0 / backgroundRect.width(); + const float v0 = 1.0f - y0 / backgroundRect.height(); + const float u1 = x1 / backgroundRect.width(); + const float v1 = 1.0f - y1 / backgroundRect.height(); // first triangle map[vboIndex++] = GLVertex2D{ @@ -860,7 +1020,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi vbo->bindArrays(); - const auto finalBlurTexture = GLTexture::allocate(textureFormat, deviceBackgroundRect.size()); + const auto finalBlurTexture = GLTexture::allocate(textureFormat, backgroundRect.size()); finalBlurTexture->setFilter(GL_LINEAR); finalBlurTexture->setWrapMode(GL_CLAMP_TO_EDGE); const auto finalBlurFramebuffer = std::make_unique(finalBlurTexture.get()); @@ -869,8 +1029,8 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi ShaderManager::instance()->pushShader(m_texturePass.shader.get()); QMatrix4x4 projectionMatrix; - if (hasRoundedCorners) { - projectionMatrix.ortho(QRectF(0.0, 0.0, deviceBackgroundRect.width(), deviceBackgroundRect.height())); + if (hasAntialiasedRoundedCorners) { + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); GLFramebuffer::pushFramebuffer(finalBlurFramebuffer.get()); } else { projectionMatrix = data.projectionMatrix(); @@ -879,12 +1039,12 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi m_texturePass.shader->setUniform(m_texturePass.mvpMatrixLocation, projectionMatrix); m_texturePass.shader->setUniform(m_texturePass.textureSizeLocation, QVector2D(m_texturePass.texture.get()->width(), m_texturePass.texture.get()->height())); - m_texturePass.shader->setUniform(m_texturePass.texStartPosLocation, QVector2D(deviceBackgroundRect.x(), deviceBackgroundRect.y())); - m_texturePass.shader->setUniform(m_texturePass.regionSizeLocation, QVector2D(deviceBackgroundRect.width(), deviceBackgroundRect.height())); + m_texturePass.shader->setUniform(m_texturePass.texStartPosLocation, QVector2D(backgroundRect.x(), backgroundRect.y())); + m_texturePass.shader->setUniform(m_texturePass.regionSizeLocation, QVector2D(backgroundRect.width(), backgroundRect.height())); m_texturePass.texture.get()->bind(); - if (!hasRoundedCorners) { + if (!hasAntialiasedRoundedCorners) { glEnable(GL_BLEND); float o = 1.0f - (opacity); o = 1.0f - o * o; @@ -892,14 +1052,14 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, hasRoundedCorners ? 6 : vertexCount); + vbo->draw(GL_TRIANGLES, hasAntialiasedRoundedCorners ? 0 : 6, hasAntialiasedRoundedCorners ? 6 : vertexCount); - if (!hasRoundedCorners) { + if (!hasAntialiasedRoundedCorners) { glDisable(GL_BLEND); } ShaderManager::instance()->popShader(); - if (hasRoundedCorners) { + if (hasAntialiasedRoundedCorners) { GLFramebuffer::popFramebuffer(); } } else { @@ -908,7 +1068,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi ShaderManager::instance()->pushShader(m_downsamplePass.shader.get()); QMatrix4x4 projectionMatrix; - projectionMatrix.ortho(QRectF(0.0, 0.0, deviceBackgroundRect.width(), deviceBackgroundRect.height())); + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); m_downsamplePass.shader->setUniform(m_downsamplePass.mvpMatrixLocation, projectionMatrix); m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation, float(m_offset)); @@ -934,7 +1094,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi ShaderManager::instance()->pushShader(m_upsamplePass.shader.get()); QMatrix4x4 projectionMatrix; - projectionMatrix.ortho(QRectF(0.0, 0.0, deviceBackgroundRect.width(), deviceBackgroundRect.height())); + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix); m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation, float(m_offset)); @@ -956,10 +1116,10 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi GLFramebuffer::popFramebuffer(); const auto &read = renderInfo.framebuffers[1]; - if (hasRoundedCorners) { + if (hasAntialiasedRoundedCorners) { GLFramebuffer::pushFramebuffer(finalBlurFramebuffer.get()); projectionMatrix = QMatrix4x4(); - projectionMatrix.ortho(QRectF(0.0, 0.0, deviceBackgroundRect.width(), deviceBackgroundRect.height())); + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); } else { projectionMatrix = data.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); @@ -973,7 +1133,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi read->colorAttachment()->bind(); // Modulate the blurred texture with the window opacity if the window isn't opaque - if (!hasRoundedCorners && opacity < 1.0) { + if (!hasAntialiasedRoundedCorners && opacity < 1.0) { glEnable(GL_BLEND); float o = 1.0f - (opacity); o = 1.0f - o * o; @@ -981,9 +1141,9 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } - vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, hasRoundedCorners ? 6 : vertexCount); + vbo->draw(GL_TRIANGLES, hasAntialiasedRoundedCorners ? 0 : 6, hasAntialiasedRoundedCorners ? 6 : vertexCount); - if (!hasRoundedCorners && opacity < 1.0) { + if (!hasAntialiasedRoundedCorners && opacity < 1.0) { glDisable(GL_BLEND); } @@ -1004,8 +1164,8 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi ShaderManager::instance()->pushShader(m_noisePass.shader.get()); QMatrix4x4 projectionMatrix; - if (hasRoundedCorners) { - projectionMatrix.ortho(QRectF(0.0, 0.0, deviceBackgroundRect.width(), deviceBackgroundRect.height())); + if (hasAntialiasedRoundedCorners) { + projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); } else { projectionMatrix = data.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); @@ -1017,7 +1177,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi noiseTexture->bind(); - vbo->draw(GL_TRIANGLES, hasRoundedCorners ? 0 : 6, hasRoundedCorners ? 6 : vertexCount); + vbo->draw(GL_TRIANGLES, hasAntialiasedRoundedCorners ? 0 : 6, hasAntialiasedRoundedCorners ? 6 : vertexCount); ShaderManager::instance()->popShader(); } @@ -1025,12 +1185,12 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi glDisable(GL_BLEND); } - if (hasRoundedCorners) { + if (hasAntialiasedRoundedCorners) { GLFramebuffer::popFramebuffer(); } } - if (hasRoundedCorners) { + if (hasAntialiasedRoundedCorners) { QMatrix4x4 projectionMatrix = data.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); @@ -1041,8 +1201,8 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, roundBottomRightCorner); m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, roundTopLeftCorner); m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, roundTopRightCorner); - m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, bottomCornerRadius); - m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, topCornerRadius); + m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, bottomCornerRadius + m_cornerRadiusOffset); + m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, topCornerRadius + m_cornerRadiusOffset); m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, m_roundedCornersAntialiasing); m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(deviceBackgroundRect.width(), deviceBackgroundRect.height())); m_roundedCorners.shader->setUniform(m_roundedCorners.mvpMatrixLocation, projectionMatrix); @@ -1086,11 +1246,6 @@ bool BlurEffect::blocksDirectScanout() const return false; } -bool BlurEffect::hasFakeBlur(EffectWindow *w) const -{ - return m_fakeBlur && m_hasValidFakeBlurTexture && !blurRegion(w).isEmpty(); -} - } // namespace KWin #include "moc_blur.cpp" diff --git a/src/blur.h b/src/blur.h index 945d3c217..e2a6ca652 100644 --- a/src/blur.h +++ b/src/blur.h @@ -79,15 +79,31 @@ public Q_SLOTS: void initBlurStrengthValues(); QRegion blurRegion(EffectWindow *w) const; QRegion decorationBlurRegion(const EffectWindow *w) const; - QRegion effectiveBlurRegion(QRegion blurRegion, const WindowPaintData &data) const; + QRegion transformedBlurRegion(QRegion blurRegion, const WindowPaintData &data) const; bool decorationSupportsBlurBehind(const EffectWindow *w) const; bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const; bool shouldForceBlur(const EffectWindow *w) const; void updateBlurRegion(EffectWindow *w); void blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data); GLTexture *ensureNoiseTexture(); - bool hasFakeBlur(EffectWindow *w) const; - inline QVector2D toVector2D(const QSizeF& s) { return {static_cast(s.width()), static_cast(s.height())}; } + + /* + * @param regionScale How much to scale the blur region. This parameter should be 1 if the region is rounded. + * @returns The area to be blurred. It's possible that all of it will be clipped. + */ + QList effectiveBlurShape(const QRect &backgroundRect, const QRect &deviceBackgroundRect, const QRegion &paintRegion, const QRegion &blurRegion, qreal screenScale, qreal regionScale) const; + + /* + * @returns An array containing rounded corner masks for the given screen scale. If no masks exist for the given screen scale, + * they will be generated. + */ + std::array roundedCorners(qreal scale); + + /* + * Generates rounded corner masks for the left and right corner for the given radius. + * @param top Whether TODO + */ + void generateRoundedCornerMasks(int radius, QRegion &left, QRegion &right, bool top) const; private: struct @@ -166,8 +182,8 @@ public Q_SLOTS: bool m_blurNonMatching; bool m_blurDecorations; bool m_transparentBlur; - float m_topCornerRadius; - float m_bottomCornerRadius; + int m_topCornerRadius; + int m_bottomCornerRadius; float m_roundedCornersAntialiasing; bool m_roundCornersOfMaximizedWindows; bool m_blurMenus; @@ -178,6 +194,12 @@ public Q_SLOTS: bool m_hasValidFakeBlurTexture; + int m_cornerRadiusOffset; + + // Corner masks where the key is the screen scale and the value is an array of the masks + // (top left, top right, bottom left, bottom right). Used for rounding the blur region. + std::unordered_map> m_corners; + struct OffsetStruct { float minOffset; diff --git a/src/blur.kcfg b/src/blur.kcfg index 91dee7ee4..54c2082e7 100644 --- a/src/blur.kcfg +++ b/src/blur.kcfg @@ -28,11 +28,11 @@ class3 true - - 0.0 + + 0 - - 0.0 + + 0 1.0 diff --git a/src/shaders/roundedcorners.frag b/src/shaders/roundedcorners.frag index 43c7165f5..58cfd2653 100644 --- a/src/shaders/roundedcorners.frag +++ b/src/shaders/roundedcorners.frag @@ -5,8 +5,8 @@ uniform bool roundTopRightCorner; uniform bool roundBottomLeftCorner; uniform bool roundBottomRightCorner; -uniform float topCornerRadius; -uniform float bottomCornerRadius; +uniform int topCornerRadius; +uniform int bottomCornerRadius; uniform float antialiasing; @@ -40,5 +40,14 @@ void main(void) vec2 halfRegionSize = regionSize * 0.5; vec2 fragCoord = uv * regionSize; float box = udRoundBox(fragCoord - halfRegionSize, halfRegionSize, fragCoord); - gl_FragColor = vec4(mix(texture2D(afterBlurTexture, uv).rgb, texture2D(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); + + // If antialiasing is 0, the shader will be used to generate corner masks. + vec3 foreground = vec3(1.0, 1.0, 1.0); + vec3 background = vec3(0.0, 0.0, 0.0); + if (antialiasing > 0.0) { + foreground = texture2D(afterBlurTexture, uv).rgb; + background = texture2D(beforeBlurTexture, uv).rgb; + } + + gl_FragColor = vec4(mix(foreground, background, smoothstep(0.0, antialiasing, box)), 1.0); } \ No newline at end of file diff --git a/src/shaders/roundedcorners_core.frag b/src/shaders/roundedcorners_core.frag index ebd0f49b0..e0233d703 100644 --- a/src/shaders/roundedcorners_core.frag +++ b/src/shaders/roundedcorners_core.frag @@ -7,8 +7,8 @@ uniform bool roundTopRightCorner; uniform bool roundBottomLeftCorner; uniform bool roundBottomRightCorner; -uniform float topCornerRadius; -uniform float bottomCornerRadius; +uniform int topCornerRadius; +uniform int bottomCornerRadius; uniform float antialiasing; @@ -44,5 +44,14 @@ void main(void) vec2 halfRegionSize = regionSize * 0.5; vec2 fragCoord = uv * regionSize; float box = udRoundBox(fragCoord - halfRegionSize, halfRegionSize, fragCoord); - fragColor = vec4(mix(texture(afterBlurTexture, uv).rgb, texture(beforeBlurTexture, uv).rgb, smoothstep(0.0, antialiasing, box)), 1.0); + + // If antialiasing is 0, the shader will be used to generate corner masks. + vec3 foreground = vec3(1.0, 1.0, 1.0); + vec3 background = vec3(0.0, 0.0, 0.0); + if (antialiasing > 0.0) { + foreground = texture(afterBlurTexture, uv).rgb; + background = texture(beforeBlurTexture, uv).rgb; + } + + fragColor = vec4(mix(foreground, background, smoothstep(0.0, antialiasing, box)), 1.0); } diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 000000000..e8aca7489 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include "core/pixelgrid.h" +#include + +namespace KWin +{ + +inline QRegion scaledRegion(const QRegion ®ion, qreal scale) +{ + QRegion scaledRegion; + for (const QRect &rect : region) { + scaledRegion += QRect(std::round(rect.x() * scale), std::round(rect.y() * scale), std::round(rect.width() * scale), std::round(rect.height() * scale)); + } + + return scaledRegion; +} + +} \ No newline at end of file From cc35f84fa1aefd001874bad7454098d97bd24e02 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:56:19 +0200 Subject: [PATCH 12/28] blur/rounded-corners: disable for docks --- src/blur.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blur.cpp b/src/blur.cpp index 1f9fb4e97..db150818c 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -478,7 +478,7 @@ QRegion BlurEffect::blurRegion(EffectWindow *w, bool noRoundedCorners) const } bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); - if (!noRoundedCorners && (!isMaximized || m_roundCornersOfMaximizedWindows)) { + if (!w->isDock() && !noRoundedCorners && (!isMaximized || m_roundCornersOfMaximizedWindows)) { if (m_topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { QPoint topRightPosition = QPoint(w->rect().width() - m_topCornerRadius, 0); region -= m_topLeftCorner; From 8c8ad5a7d95c76a578e4487d2feb2452a6af8253 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sat, 15 Jun 2024 14:40:48 +0200 Subject: [PATCH 13/28] blur/rounded-corners: fix various issues when using scaling --- flake.lock | 6 +- src/blur.cpp | 175 +++++++++++++++++++++++---------------------------- src/utils.h | 3 +- 3 files changed, 81 insertions(+), 103 deletions(-) diff --git a/flake.lock b/flake.lock index b5cd9d8ff..409ad87c0 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1715087517, - "narHash": "sha256-CLU5Tsg24Ke4+7sH8azHWXKd0CFd4mhLWfhYgUiDBpQ=", + "lastModified": 1718160348, + "narHash": "sha256-9YrUjdztqi4Gz8n3mBuqvCkMo4ojrA6nASwyIKWMpus=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b211b392b8486ee79df6cdfb1157ad2133427a29", + "rev": "57d6973abba7ea108bac64ae7629e7431e0199b6", "type": "github" }, "original": { diff --git a/src/blur.cpp b/src/blur.cpp index 6484ee42e..fe7fe1195 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -261,9 +261,8 @@ void BlurEffect::reconfigure(ReconfigureFlags flags) m_fakeBlur = BlurConfig::fakeBlur(); m_fakeBlurImage = BlurConfig::fakeBlurImage(); + // Antialiasing does take up a bit of space, so the corner radius will be reduced by the offset in order to leave some space. m_cornerRadiusOffset = m_roundedCornersAntialiasing == 0 ? 0 : std::round(m_roundedCornersAntialiasing) + 2; - m_topCornerRadius = std::max(0, m_topCornerRadius - m_cornerRadiusOffset); - m_bottomCornerRadius = std::max(0, m_bottomCornerRadius - m_cornerRadiusOffset); QImage fakeBlurImage(m_fakeBlurImage); m_hasValidFakeBlurTexture = !fakeBlurImage.isNull(); @@ -324,7 +323,6 @@ void BlurEffect::updateBlurRegion(EffectWindow *w) if (shouldForceBlur(w)) { content = w->expandedGeometry().toRect().translated(-w->x(), -w->y()); if (m_blurDecorations && w->decoration()) { - const QMargins borders = w->decoration()->borders(); frame = w->frameGeometry().toRect().translated(-w->x(), -w->y()); } } @@ -446,8 +444,8 @@ std::array BlurEffect::roundedCorners(qreal scale) } std::array corners; - generateRoundedCornerMasks(std::round(m_topCornerRadius * scale), corners[0], corners[1], true); - generateRoundedCornerMasks(std::round(m_bottomCornerRadius * scale), corners[2], corners[3], false); + generateRoundedCornerMasks(std::max(0, (int)std::round(m_topCornerRadius * scale) - m_cornerRadiusOffset), corners[0], corners[1], true); + generateRoundedCornerMasks(std::max(0, (int)std::round(m_bottomCornerRadius * scale) - m_cornerRadiusOffset), corners[2], corners[3], false); return m_corners[scale] = corners; } @@ -759,30 +757,6 @@ GLTexture *BlurEffect::ensureNoiseTexture() return m_noisePass.noiseTexture.get(); } -QList BlurEffect::effectiveBlurShape(const QRect &backgroundRect, const QRect &deviceBackgroundRect, const QRegion &paintRegion, const QRegion &blurRegion, qreal screenScale, qreal regionScale) const -{ - QList effectiveShape; - effectiveShape.reserve(blurRegion.rectCount()); - if (paintRegion != infiniteRegion()) { - for (const QRect &clipRect : paintRegion) { - const QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, screenScale)) - .translated(-deviceBackgroundRect.topLeft()); - for (const QRect &shapeRect : blurRegion) { - const QRectF deviceShapeRect = snapToPixelGridF(scaledRect(shapeRect.translated(-backgroundRect.topLeft()), regionScale)); - if (const QRectF intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) { - effectiveShape.append(intersected); - } - } - } - } else { - for (const QRect &rect : blurRegion) { - effectiveShape.append(snapToPixelGridF(scaledRect(rect.translated(-backgroundRect.topLeft()), regionScale))); - } - } - - return effectiveShape; -} - void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) { auto it = m_windows.find(w); @@ -796,88 +770,93 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi return; } - bool hasRoundedCorners = (data.xScale() == 1 || data.yScale() == 1) && ((m_topCornerRadius || m_bottomCornerRadius)); + int topCornerRadius = std::max(0, (int)std::round(m_topCornerRadius * viewport.scale()) - m_cornerRadiusOffset); + int bottomCornerRadius = std::max(0, (int)std::round(m_bottomCornerRadius * viewport.scale()) - m_cornerRadiusOffset); + bool hasRoundedCorners = m_topCornerRadius || m_bottomCornerRadius; + + const QRegion blurShape = transformedBlurRegion(blurRegion(w).translated(w->pos().toPoint()), data); + const QRect backgroundRect = blurShape.boundingRect(); + + // The blur shape has to be moved to 0,0 before being scaled, otherwise the size may end up being off by 1 pixel. + QRegion scaledBlurShape = scaledRegion(blurShape.translated(-backgroundRect.topLeft()), viewport.scale()); + const QRegion scaledBlurShapeNotTranslated = scaledRegion(blurShape, viewport.scale()); + const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); - // The blur region is scaled later, so we'll remove the corners for now and add them back after scaling, otherwise they'll look terrible. - QRegion blurShape = blurRegion(w); + bool roundTopLeftCorner = false; + bool roundTopRightCorner = false; + bool roundBottomLeftCorner = false; + bool roundBottomRightCorner = false; if (hasRoundedCorners) { + if (w->isDock()) { + // TODO Add option to toggle rounding for the dock + roundTopLeftCorner = roundTopRightCorner = topCornerRadius; + roundBottomLeftCorner = roundBottomRightCorner = bottomCornerRadius; + } else { + // Ensure the blur region corners touch the window corners before rounding them. + const QRect windowGeometry = w->frameGeometry().toRect(); + if (topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { + const QRect topLeftCorner = snapToPixelGrid(scaledRect(transformedBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), topCornerRadius, topCornerRadius)), data).boundingRect(), viewport.scale())); + roundTopLeftCorner = scaledBlurShapeNotTranslated.intersects(topLeftCorner); + + const QRect topRightCorner = snapToPixelGrid(scaledRect(transformedBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - topCornerRadius, windowGeometry.y(),topCornerRadius, topCornerRadius)), data).boundingRect(), viewport.scale())); + roundTopRightCorner = scaledBlurShapeNotTranslated.intersects(topRightCorner); + } + if (bottomCornerRadius) { + const QRect bottomLeftCorner = snapToPixelGrid(scaledRect(transformedBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect(), viewport.scale())); + roundBottomLeftCorner = scaledBlurShapeNotTranslated.intersects(bottomLeftCorner); + + const QRect bottomRightCorner = snapToPixelGrid(scaledRect(transformedBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - bottomCornerRadius, windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect(), viewport.scale())); + roundBottomRightCorner = scaledBlurShapeNotTranslated.intersects(bottomRightCorner); + } + + hasRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; + } + const auto corners = roundedCorners(viewport.scale()); - const auto windowGeometry = w->frameGeometry(); - if (m_topCornerRadius) { - blurShape -= QRect(0, 0, m_topCornerRadius, m_topCornerRadius); - blurShape -= QRect(windowGeometry.width() - m_topCornerRadius, 0, m_topCornerRadius, m_topCornerRadius); + const QRect blurRect = scaledBlurShape.boundingRect(); + + if (roundTopLeftCorner) { + scaledBlurShape -= corners[0]; } - if (m_bottomCornerRadius) { - blurShape -= QRect(0, windowGeometry.height() - m_bottomCornerRadius, m_bottomCornerRadius, m_bottomCornerRadius); - blurShape -= QRect(windowGeometry.width() - m_bottomCornerRadius, windowGeometry.height() - m_bottomCornerRadius, m_bottomCornerRadius, m_bottomCornerRadius); + if (roundTopRightCorner) { + scaledBlurShape -= corners[1].translated(blurRect.width() - topCornerRadius, 0); } - } - // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape. - blurShape = transformedBlurRegion(blurShape.translated(w->pos().toPoint()), data); + if (roundBottomLeftCorner) { + scaledBlurShape -= corners[2].translated(0, blurRect.height() - bottomCornerRadius); + } + if (roundBottomRightCorner) { + scaledBlurShape -= corners[3].translated(blurRect.width() - bottomCornerRadius, blurRect.height() - bottomCornerRadius); + } + } - const QRect backgroundRect = blurShape.boundingRect(); - const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); const auto opacity = m_transparentBlur ? w->opacity() * data.opacity() : data.opacity(); - int topCornerRadius = std::round(m_topCornerRadius * viewport.scale()); - int bottomCornerRadius = std::round(m_bottomCornerRadius * viewport.scale()); - // Get the effective shape that will be actually blurred. It's possible that all of it will be clipped. - QList effectiveShape = effectiveBlurShape(backgroundRect, deviceBackgroundRect, region, blurShape, viewport.scale(), viewport.scale()); - - // Add rounded corners to the blur region. - if (hasRoundedCorners) { - QRegion cornersRegion; - const auto corners = roundedCorners(viewport.scale()); - const auto windowGeometry = scaledRect(w->frameGeometry(), viewport.scale()); - if (topCornerRadius) { - QRegion square = QRect(0, 0, topCornerRadius, topCornerRadius); - cornersRegion += square - corners[0]; - cornersRegion += (square - corners[1]).translated(std::round(windowGeometry.width()) - topCornerRadius, 0); + QRegion effectiveShape; + if (region != infiniteRegion()) { + for (const QRect &clipRect : region) { + const QRect deviceClipRect = snapToPixelGrid(scaledRect(clipRect, viewport.scale())) + .translated(-deviceBackgroundRect.topLeft()); + for (const QRect &shapeRect : scaledBlurShape) { + const QRect deviceShapeRect = shapeRect; + if (const QRect intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) { + effectiveShape += intersected; + } + } } - if (bottomCornerRadius) { - QRegion square = QRect(0, 0, bottomCornerRadius, bottomCornerRadius); - cornersRegion += (square - corners[2]).translated(0, std::round(windowGeometry.height()) - bottomCornerRadius); - cornersRegion += (square - corners[3]).translated(std::round(windowGeometry.width()) - bottomCornerRadius, std::round(windowGeometry.height()) - bottomCornerRadius); + } else { + for (const QRect &rect : scaledBlurShape) { + effectiveShape += rect; } - cornersRegion = cornersRegion.intersected(scaledRegion(blurRegion(w), viewport.scale())); - cornersRegion = transformedBlurRegion(cornersRegion.translated(w->pos().toPoint()), data); - effectiveShape.append(effectiveBlurShape(backgroundRect, deviceBackgroundRect, region, cornersRegion, viewport.scale(), 1)); } - if (effectiveShape.isEmpty()) { + if (!effectiveShape.rectCount()) { return; } - // Check whether the effective blur shape contains corners that need to be rounded. - bool roundTopLeftCorner = false; - bool roundTopRightCorner = false; - bool roundBottomLeftCorner = false; - bool roundBottomRightCorner = false; - - if (hasRoundedCorners && m_roundedCornersAntialiasing > 0) { - const auto windowGeometry = scaledRect(w->frameGeometry(), viewport.scale()); - for (const QRectF &rect : effectiveShape) { - if (topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { - const QRectF topLeftCorner = snapToPixelGridF(transformedBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), topCornerRadius, topCornerRadius)), data).boundingRect()).translated(-deviceBackgroundRect.topLeft()); - roundTopLeftCorner = roundTopLeftCorner || rect.intersects(topLeftCorner); - - const QRectF topRightCorner = snapToPixelGridF(transformedBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - topCornerRadius, windowGeometry.y(),topCornerRadius, topCornerRadius)), data).boundingRect()).translated(-deviceBackgroundRect.topLeft()); - roundTopRightCorner = roundTopRightCorner || rect.intersects(topRightCorner); - } - if (bottomCornerRadius) { - const QRectF bottomLeftCorner = snapToPixelGridF(transformedBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect()).translated(-deviceBackgroundRect.topLeft()); - roundBottomLeftCorner = roundBottomLeftCorner || rect.intersects(bottomLeftCorner); - - const QRectF bottomRightCorner = snapToPixelGridF(transformedBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - bottomCornerRadius, windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect()).translated(-deviceBackgroundRect.topLeft()); - roundBottomRightCorner = roundBottomRightCorner || rect.intersects(bottomRightCorner); - } - } - } - - const bool hasAntialiasedRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; + const bool hasAntialiasedRoundedCorners = hasRoundedCorners && m_roundedCornersAntialiasing > 0; // Maybe reallocate offscreen render targets. Keep in mind that the first one contains // original background behind the window, it's not blurred. @@ -922,7 +901,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi vbo->reset(); vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D)); - const int vertexCount = effectiveShape.size() * 6; + const int vertexCount = effectiveShape.rectCount() * 6; if (auto result = vbo->map(6 + vertexCount)) { auto map = *result; @@ -1033,7 +1012,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); GLFramebuffer::pushFramebuffer(finalBlurFramebuffer.get()); } else { - projectionMatrix = data.projectionMatrix(); + projectionMatrix = viewport.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); } @@ -1121,7 +1100,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi projectionMatrix = QMatrix4x4(); projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); } else { - projectionMatrix = data.projectionMatrix(); + projectionMatrix = viewport.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); } m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix); @@ -1167,7 +1146,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi if (hasAntialiasedRoundedCorners) { projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); } else { - projectionMatrix = data.projectionMatrix(); + projectionMatrix = viewport.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); } @@ -1191,7 +1170,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi } if (hasAntialiasedRoundedCorners) { - QMatrix4x4 projectionMatrix = data.projectionMatrix(); + QMatrix4x4 projectionMatrix = viewport.projectionMatrix(); projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); // The Y axis is flipped in OpenGL. diff --git a/src/utils.h b/src/utils.h index e8aca7489..b5275f0cb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -2,7 +2,6 @@ #include #include "core/pixelgrid.h" -#include namespace KWin { @@ -11,7 +10,7 @@ inline QRegion scaledRegion(const QRegion ®ion, qreal scale) { QRegion scaledRegion; for (const QRect &rect : region) { - scaledRegion += QRect(std::round(rect.x() * scale), std::round(rect.y() * scale), std::round(rect.width() * scale), std::round(rect.height() * scale)); + scaledRegion += snapToPixelGridF(scaledRect(QRectF(rect), scale)).toRect(); } return scaledRegion; From bcbf1ff7ee7ef871885626805afcf299f097f6f8 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sat, 15 Jun 2024 15:51:47 +0200 Subject: [PATCH 14/28] blur/rounded-corners: don't round floating panels --- src/blur.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index b05c998fa..c92fd42fc 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -792,9 +792,13 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi bool roundBottomRightCorner = false; if (hasRoundedCorners) { if (w->isDock()) { - // TODO Add option to toggle rounding for the dock - roundTopLeftCorner = roundTopRightCorner = topCornerRadius; - roundBottomLeftCorner = roundBottomRightCorner = bottomCornerRadius; + // Only round floating panels. If the pixel at (0, height / 2) for horizontal panels and (width / 2, 0) + // for vertical panels doesn't belong to the blur region, the panel is most likely floating. The (0,0) + // pixel may be outside the blur region if the panel can float but isn't at the moment. + if (!blurRegion(w).intersects(QRect(0, w->height() / 2, 1, 1)) && !blurRegion(w).intersects(QRect(w->width() / 2, 0, 1, 1))) { + roundTopLeftCorner = roundTopRightCorner = topCornerRadius; + roundBottomLeftCorner = roundBottomRightCorner = bottomCornerRadius; + } } else { // Ensure the blur region corners touch the window corners before rounding them. const QRect windowGeometry = w->frameGeometry().toRect(); From 6170c1013d33895e5250ab7a2d2cfd4ebec22cf6 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 10:36:34 +0200 Subject: [PATCH 15/28] blur/rounded-corners: fix corners not being rounded when the window is transformed in WindowPaintData --- src/blur.cpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index c92fd42fc..9b0cc3e5f 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -778,12 +778,12 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi int bottomCornerRadius = std::max(0, (int)std::round(m_bottomCornerRadius * viewport.scale()) - m_cornerRadiusOffset); bool hasRoundedCorners = m_topCornerRadius || m_bottomCornerRadius; - const QRegion blurShape = transformedBlurRegion(blurRegion(w).translated(w->pos().toPoint()), data); + const QRegion rawBlurRegion = blurRegion(w); + const QRegion blurShape = transformedBlurRegion(rawBlurRegion.translated(w->pos().toPoint()), data); const QRect backgroundRect = blurShape.boundingRect(); // The blur shape has to be moved to 0,0 before being scaled, otherwise the size may end up being off by 1 pixel. QRegion scaledBlurShape = scaledRegion(blurShape.translated(-backgroundRect.topLeft()), viewport.scale()); - const QRegion scaledBlurShapeNotTranslated = scaledRegion(blurShape, viewport.scale()); const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); bool roundTopLeftCorner = false; @@ -795,28 +795,20 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi // Only round floating panels. If the pixel at (0, height / 2) for horizontal panels and (width / 2, 0) // for vertical panels doesn't belong to the blur region, the panel is most likely floating. The (0,0) // pixel may be outside the blur region if the panel can float but isn't at the moment. - if (!blurRegion(w).intersects(QRect(0, w->height() / 2, 1, 1)) && !blurRegion(w).intersects(QRect(w->width() / 2, 0, 1, 1))) { + if (!rawBlurRegion.intersects(QRect(0, w->height() / 2, 1, 1)) && !rawBlurRegion.intersects(QRect(w->width() / 2, 0, 1, 1))) { roundTopLeftCorner = roundTopRightCorner = topCornerRadius; roundBottomLeftCorner = roundBottomRightCorner = bottomCornerRadius; } } else { // Ensure the blur region corners touch the window corners before rounding them. - const QRect windowGeometry = w->frameGeometry().toRect(); if (topCornerRadius && (!w->decoration() || (w->decoration() && m_blurDecorations))) { - const QRect topLeftCorner = snapToPixelGrid(scaledRect(transformedBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y(), topCornerRadius, topCornerRadius)), data).boundingRect(), viewport.scale())); - roundTopLeftCorner = scaledBlurShapeNotTranslated.intersects(topLeftCorner); - - const QRect topRightCorner = snapToPixelGrid(scaledRect(transformedBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - topCornerRadius, windowGeometry.y(),topCornerRadius, topCornerRadius)), data).boundingRect(), viewport.scale())); - roundTopRightCorner = scaledBlurShapeNotTranslated.intersects(topRightCorner); + roundTopLeftCorner = rawBlurRegion.intersects(QRect(0, 0, topCornerRadius, topCornerRadius)); + roundTopRightCorner = rawBlurRegion.intersects(QRect(w->width() - topCornerRadius, 0, topCornerRadius, topCornerRadius)); } if (bottomCornerRadius) { - const QRect bottomLeftCorner = snapToPixelGrid(scaledRect(transformedBlurRegion(QRegion(QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect(), viewport.scale())); - roundBottomLeftCorner = scaledBlurShapeNotTranslated.intersects(bottomLeftCorner); - - const QRect bottomRightCorner = snapToPixelGrid(scaledRect(transformedBlurRegion(QRegion(QRect(windowGeometry.x() + windowGeometry.width() - bottomCornerRadius, windowGeometry.y() + windowGeometry.height() - bottomCornerRadius,bottomCornerRadius, bottomCornerRadius)), data).boundingRect(), viewport.scale())); - roundBottomRightCorner = scaledBlurShapeNotTranslated.intersects(bottomRightCorner); + roundBottomLeftCorner = rawBlurRegion.intersects(QRect(0, w->height() - bottomCornerRadius, bottomCornerRadius, bottomCornerRadius)); + roundBottomRightCorner = rawBlurRegion.intersects(QRect(w->width() - bottomCornerRadius, w->height() - bottomCornerRadius, bottomCornerRadius, bottomCornerRadius)); } - hasRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; } From c23daee678d8c1a9fd43f1ef663bf7b67b606384 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 10:44:23 +0200 Subject: [PATCH 16/28] kcm: use QSpinBox for corner radius instead of QDoubleSpinBox --- src/kcm/blur_config.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kcm/blur_config.ui b/src/kcm/blur_config.ui index 617783a6f..26597a28f 100644 --- a/src/kcm/blur_config.ui +++ b/src/kcm/blur_config.ui @@ -326,7 +326,7 @@ - + 0 @@ -344,7 +344,7 @@ - + 0 From bb070a4496e4d2b89378ed8f4c7bd122601f6f86 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 11:13:07 +0200 Subject: [PATCH 17/28] blur/rounded-corners: fix noise texture behavior when antialiasing is enabled --- src/blur.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blur.cpp b/src/blur.cpp index 9b0cc3e5f..d563e4a7a 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -1150,9 +1150,13 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); } + const QVector2D textureStartPosition = hasAntialiasedRoundedCorners + ? QVector2D(deviceBackgroundRect.x() * 2, std::abs(deviceBackgroundRect.height() - deviceBackgroundRect.y() * 2)) + : QVector2D(deviceBackgroundRect.topLeft()); + m_noisePass.shader->setUniform(m_noisePass.mvpMatrixLocation, projectionMatrix); m_noisePass.shader->setUniform(m_noisePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height())); - m_noisePass.shader->setUniform(m_noisePass.texStartPosLocation, QVector2D(deviceBackgroundRect.topLeft())); + m_noisePass.shader->setUniform(m_noisePass.texStartPosLocation, textureStartPosition); noiseTexture->bind(); From e57bf4a0767377c7b4b33c343bff8ec2fc209235 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 11:32:51 +0200 Subject: [PATCH 18/28] Revert "blur/rounded-corners: fix noise texture behavior when antialiasing is enabled" This reverts commit bb070a4496e4d2b89378ed8f4c7bd122601f6f86. --- src/blur.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index d563e4a7a..9b0cc3e5f 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -1150,13 +1150,9 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); } - const QVector2D textureStartPosition = hasAntialiasedRoundedCorners - ? QVector2D(deviceBackgroundRect.x() * 2, std::abs(deviceBackgroundRect.height() - deviceBackgroundRect.y() * 2)) - : QVector2D(deviceBackgroundRect.topLeft()); - m_noisePass.shader->setUniform(m_noisePass.mvpMatrixLocation, projectionMatrix); m_noisePass.shader->setUniform(m_noisePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height())); - m_noisePass.shader->setUniform(m_noisePass.texStartPosLocation, textureStartPosition); + m_noisePass.shader->setUniform(m_noisePass.texStartPosLocation, QVector2D(deviceBackgroundRect.topLeft())); noiseTexture->bind(); From 134d432afb9a036b389b122b107b9896edc0a460 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:17:48 +0200 Subject: [PATCH 19/28] blur: add workaround for blur flickering when transforming windows --- src/blur.cpp | 24 +++++++++++++++++++----- src/blur.h | 3 ++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index db150818c..4b91eddaf 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -566,9 +566,11 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: m_paintedArea += data.paint; } -bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const +bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) { - if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool()) { + const bool hasForceBlurRole = w->data(WindowForceBlurRole).toBool(); + + if (effects->activeFullScreenEffect() && !hasForceBlurRole) { return false; } @@ -578,12 +580,24 @@ bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintDa bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0); bool translated = data.xTranslation() || data.yTranslation(); + if (!(scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED)))) { + return true; + } - if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBlurRole).toBool()) { - return false; + // The force blur role may be removed while the window is still transformed, causing the blur to disappear for + // a short time. To avoid that, we allow the window to be blurred for another 30 paints. + if (hasForceBlurRole) { + m_paintsSinceForceBlurRoleRemoval[w] = 0; + return true; + } else if (m_paintsSinceForceBlurRoleRemoval.contains(w)) { + m_paintsSinceForceBlurRoleRemoval[w]++; + if (m_paintsSinceForceBlurRoleRemoval[w] == 29) { + m_paintsSinceForceBlurRoleRemoval.erase(w); + } + return true; } - return true; + return false; } bool BlurEffect::shouldForceBlur(const EffectWindow *w) const diff --git a/src/blur.h b/src/blur.h index aa0739346..3757787da 100644 --- a/src/blur.h +++ b/src/blur.h @@ -80,7 +80,7 @@ public Q_SLOTS: QRegion blurRegion(EffectWindow *w, bool noRoundedCorners = false) const; QRegion decorationBlurRegion(const EffectWindow *w) const; bool decorationSupportsBlurBehind(const EffectWindow *w) const; - bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const; + bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data); bool shouldForceBlur(const EffectWindow *w) const; void updateBlurRegion(EffectWindow *w); void updateCornerRegions(); @@ -179,6 +179,7 @@ public Q_SLOTS: QMap windowBlurChangedConnections; QMap windowExpandedGeometryChangedConnections; std::unordered_map m_windows; + std::unordered_map m_paintsSinceForceBlurRoleRemoval; static BlurManagerInterface *s_blurManager; static QTimer *s_blurManagerRemoveTimer; From 1c4c74e4d7ffcd4ec652c0473f4fbe2eed12ef52 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:37:26 +0200 Subject: [PATCH 20/28] blur: keep windows blurred until the transformation finishes It's probably a better idea to do this instead, since the previous approach won't work with slow animations. --- src/blur.cpp | 20 +++++++++++--------- src/blur.h | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index 4b91eddaf..8a4dcebcb 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -380,6 +380,10 @@ void BlurEffect::slotWindowDeleted(EffectWindow *w) disconnect(*it); windowExpandedGeometryChangedConnections.erase(it); } + + if (m_blurWhenTransformed.contains(w)) { + m_blurWhenTransformed.erase(w); + } } void BlurEffect::slotScreenRemoved(KWin::Output *screen) @@ -581,19 +585,17 @@ bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintDa bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0); bool translated = data.xTranslation() || data.yTranslation(); if (!(scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED)))) { + if (m_blurWhenTransformed.contains(w)) { + m_blurWhenTransformed.erase(w); + } + return true; } // The force blur role may be removed while the window is still transformed, causing the blur to disappear for - // a short time. To avoid that, we allow the window to be blurred for another 30 paints. - if (hasForceBlurRole) { - m_paintsSinceForceBlurRoleRemoval[w] = 0; - return true; - } else if (m_paintsSinceForceBlurRoleRemoval.contains(w)) { - m_paintsSinceForceBlurRoleRemoval[w]++; - if (m_paintsSinceForceBlurRoleRemoval[w] == 29) { - m_paintsSinceForceBlurRoleRemoval.erase(w); - } + // a short time. To avoid that, we allow the window to be blurred until it's not transformed anymore.' + if (hasForceBlurRole || m_blurWhenTransformed.contains(w)) { + m_blurWhenTransformed[w] = true; return true; } diff --git a/src/blur.h b/src/blur.h index 3757787da..0fb53caad 100644 --- a/src/blur.h +++ b/src/blur.h @@ -179,7 +179,7 @@ public Q_SLOTS: QMap windowBlurChangedConnections; QMap windowExpandedGeometryChangedConnections; std::unordered_map m_windows; - std::unordered_map m_paintsSinceForceBlurRoleRemoval; + std::unordered_map m_blurWhenTransformed; static BlurManagerInterface *s_blurManager; static QTimer *s_blurManagerRemoveTimer; From c79c6ad34ca5b886bbeeeb3a0e50569b7a7400a1 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 20:51:24 +0200 Subject: [PATCH 21/28] blur/rounded-corners: add back condition to check whether window is maximized/fullscreen --- src/blur.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blur.cpp b/src/blur.cpp index a746ea07c..5d9d9a7a3 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -806,7 +806,8 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi bool roundTopRightCorner = false; bool roundBottomLeftCorner = false; bool roundBottomRightCorner = false; - if (hasRoundedCorners) { + const bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); + if (hasRoundedCorners && ((!w->isFullScreen() && !isMaximized) || m_roundCornersOfMaximizedWindows)) { if (w->isDock()) { // Only round floating panels. If the pixel at (0, height / 2) for horizontal panels and (width / 2, 0) // for vertical panels doesn't belong to the blur region, the panel is most likely floating. The (0,0) From fc3e9cdb001f374110dba526933f53c11d71402f Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:02:07 +0200 Subject: [PATCH 22/28] readme: update --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d162c5005..425847e60 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # kwin-effects-forceblur [![AUR Version](https://img.shields.io/aur/version/kwin-effects-forceblur)](https://aur.archlinux.org/packages/kwin-effects-forceblur) -A fork of the KWin Blur effect for KDE Plasma 6 with the ability to blur any window on Wayland and X11. +Kwin-effects-forceblur (name subject to change) is a fork of the KWin Blur effect for KDE Plasma 6 with several improvements and bug fixes. Latest features are available on the ``develop`` branch. @@ -8,9 +8,15 @@ Latest features are available on the ``develop`` branch. # Features - Wayland support +- Force blur +- Rounded corners with optional anti-aliasing - Draw image behind windows instead of blurring (can be used with a blurred image of the wallpaper in order to achieve a very similar effect to blur but with **much** lower GPU usage) -- Rounded corners -- Fix for [artifacts](https://github.com/taj-ny/kwin-effects-forceblur/pull/38) when using a transparent color scheme + +### Bug fixes +Fixes for blur-related Plasma bugs that haven't been patched yet. + +- Blur may sometimes disappear during animations +- [Transparent color schemes don't work properly with the Breeze application style](https://github.com/taj-ny/kwin-effects-forceblur/pull/38) # Installation
From 1e6a5bdd0b07fc7c106f55848f72d01cf6846699 Mon Sep 17 00:00:00 2001 From: taj_ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:15:10 +0200 Subject: [PATCH 23/28] readme: update screenshot --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 425847e60..485ca67a0 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ Kwin-effects-forceblur (name subject to change) is a fork of the KWin Blur effec Latest features are available on the ``develop`` branch. -![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/9d2f337e-badd-4d95-ba55-96c80202e196) -Window opacity has been set to 85% for System Settings and Dolphin, Firefox uses a transparent theme | [NixOS configuration](https://github.com/taj-ny/nix-config) +![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/1078cf12-e6da-43c7-80b4-d90a8b0f3404) +Window opacity has been set to 85% for System Settings, Dolphin and VSCodium, Firefox uses a transparent theme | [NixOS configuration](https://github.com/taj-ny/nix-config) # Features - Wayland support From b08ff3fd36675e76532947d2c9bd3cdab3e9373a Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:52:12 +0200 Subject: [PATCH 24/28] blur: use QMap instead of std::unordered_map for blur flickering workaround --- src/blur.cpp | 13 +++++++------ src/blur.h | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index 8a4dcebcb..9ee260ddb 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -382,7 +382,7 @@ void BlurEffect::slotWindowDeleted(EffectWindow *w) } if (m_blurWhenTransformed.contains(w)) { - m_blurWhenTransformed.erase(w); + m_blurWhenTransformed.removeOne(w); } } @@ -586,20 +586,21 @@ bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintDa bool translated = data.xTranslation() || data.yTranslation(); if (!(scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED)))) { if (m_blurWhenTransformed.contains(w)) { - m_blurWhenTransformed.erase(w); + m_blurWhenTransformed.removeOne(w); } return true; } // The force blur role may be removed while the window is still transformed, causing the blur to disappear for - // a short time. To avoid that, we allow the window to be blurred until it's not transformed anymore.' - if (hasForceBlurRole || m_blurWhenTransformed.contains(w)) { - m_blurWhenTransformed[w] = true; + // a short time. To avoid that, we allow the window to be blurred until it's not transformed anymore. + if (m_blurWhenTransformed.contains(w)) { return true; + } else if (hasForceBlurRole) { + m_blurWhenTransformed.append(w); } - return false; + return hasForceBlurRole; } bool BlurEffect::shouldForceBlur(const EffectWindow *w) const diff --git a/src/blur.h b/src/blur.h index 0fb53caad..fa135646c 100644 --- a/src/blur.h +++ b/src/blur.h @@ -176,10 +176,12 @@ public Q_SLOTS: QList blurStrengthValues; + // Windows to blur even when transformed. + QList m_blurWhenTransformed; + QMap windowBlurChangedConnections; QMap windowExpandedGeometryChangedConnections; std::unordered_map m_windows; - std::unordered_map m_blurWhenTransformed; static BlurManagerInterface *s_blurManager; static QTimer *s_blurManagerRemoveTimer; From 5e5953bd896ed5d3ce92ed717fa0af1eddf3fe43 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Sun, 16 Jun 2024 22:06:17 +0200 Subject: [PATCH 25/28] blur/rounded-corners: add comment describing how anti-aliasing is implemented --- src/blur.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/blur.cpp b/src/blur.cpp index 5d9d9a7a3..7f06fcf41 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -1016,6 +1016,20 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi vbo->bindArrays(); + /* + * The anti-aliasing implementation is actually really bad, but that's the best I can do for now. Suprisingly, + * there are no performance issues. + * + * Anti-aliasing is done by a shader that paints rounded rectangles. It's a modified version of + * https://www.shadertoy.com/view/ldfSDj. + * The shader requires two textures: the blur region before being blurred and after being blurred. + * The first texture can simply be taken from renderInfo.textures[0], as it's left unchanged. + * The second texture is more tricky. We could just blit, but that's slow. A faster solution is to create a virtual + * framebuffer with a texture attached to it and paint the blur in that framebuffer instead of the screen. + * + * Since only a fragment of the window may be painted, the shader allows to toggle rounding for each corner. + */ + const auto finalBlurTexture = GLTexture::allocate(textureFormat, backgroundRect.size()); finalBlurTexture->setFilter(GL_LINEAR); finalBlurTexture->setWrapMode(GL_CLAMP_TO_EDGE); From 2aec5a16289e2fc3f1d7e11ab0c7f5e991cc3f3a Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:55:08 +0200 Subject: [PATCH 26/28] blur/rounded-corners: allow setting separate radius for menus and docks --- src/blur.cpp | 68 +++++++++++++++++++++++++++--------------- src/blur.h | 20 +++++-------- src/blur.kcfg | 6 ++++ src/kcm/blur_config.ui | 42 ++++++++++++++++++++++++-- src/utils.h | 13 ++++++++ 5 files changed, 110 insertions(+), 39 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index 7f06fcf41..077c293e1 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -251,8 +251,10 @@ void BlurEffect::reconfigure(ReconfigureFlags flags) m_blurDecorations = BlurConfig::blurDecorations(); m_windowClasses = BlurConfig::windowClasses().split("\n"); m_transparentBlur = BlurConfig::transparentBlur(); - m_topCornerRadius = BlurConfig::topCornerRadius(); - m_bottomCornerRadius = BlurConfig::bottomCornerRadius(); + m_windowTopCornerRadius = BlurConfig::topCornerRadius(); + m_windowBottomCornerRadius = BlurConfig::bottomCornerRadius(); + m_menuCornerRadius = BlurConfig::menuCornerRadius(); + m_dockCornerRadius = BlurConfig::dockCornerRadius(); m_roundedCornersAntialiasing = BlurConfig::roundedCornersAntialiasing(); m_roundCornersOfMaximizedWindows = BlurConfig::roundCornersOfMaximizedWindows(); m_blurMenus = BlurConfig::blurMenus(); @@ -437,16 +439,17 @@ void BlurEffect::generateRoundedCornerMasks(int radius, QRegion &left, QRegion & right = QRegion(QBitmap::fromImage(img.mirrored(true, false).createMaskFromColor(QColor(Qt::black).rgb(), Qt::MaskOutColor), Qt::DiffuseAlphaDither));; } -std::array BlurEffect::roundedCorners(qreal scale) +std::array BlurEffect::roundedCorners(int topCornerRadius, int bottomCornerRadius, qreal scale) { - if (m_corners.contains(scale)) { - return m_corners[scale]; + const auto key = std::make_tuple(topCornerRadius, bottomCornerRadius, scale); + if (m_corners.contains(key)) { + return m_corners[key]; } std::array corners; - generateRoundedCornerMasks(std::max(0, (int)std::round(m_topCornerRadius * scale) - m_cornerRadiusOffset), corners[0], corners[1], true); - generateRoundedCornerMasks(std::max(0, (int)std::round(m_bottomCornerRadius * scale) - m_cornerRadiusOffset), corners[2], corners[3], false); - return m_corners[scale] = corners; + generateRoundedCornerMasks(topCornerRadius, corners[0], corners[1], true); + generateRoundedCornerMasks(bottomCornerRadius, corners[2], corners[3], false); + return m_corners[key] = corners; } void BlurEffect::slotWindowAdded(EffectWindow *w) @@ -635,14 +638,23 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: if (hasFakeBlur) { data.opaque += blurArea; - const auto windowGeometry = w->frameGeometry(); - if (m_topCornerRadius) { - data.opaque -= QRect(windowGeometry.x(), windowGeometry.y(), m_topCornerRadius, m_topCornerRadius); - data.opaque -= QRect(windowGeometry.x() + windowGeometry.width() - m_topCornerRadius, windowGeometry.y(), m_topCornerRadius, m_topCornerRadius); + int topCornerRadius; + int bottomCornerRadius; + if (isMenu(w)) { + topCornerRadius = bottomCornerRadius = m_menuCornerRadius; + } else if (w->isDock()) { + topCornerRadius = bottomCornerRadius = m_dockCornerRadius; + } else { + topCornerRadius = m_windowTopCornerRadius; + bottomCornerRadius = m_windowBottomCornerRadius; } - if (m_bottomCornerRadius) { - data.opaque -= QRect(windowGeometry.x(), windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius, m_bottomCornerRadius, m_bottomCornerRadius); - data.opaque -= QRect(windowGeometry.x() + windowGeometry.width() - m_bottomCornerRadius, windowGeometry.y() + windowGeometry.height() - m_bottomCornerRadius, m_bottomCornerRadius, m_bottomCornerRadius); + + if (!w->isDock() || (w->isDock() && isDockFloating(w, blurArea))) { + const QRect blurRect = blurArea.boundingRect(); + data.opaque -= QRect(blurRect.x(), blurRect.y(), topCornerRadius, topCornerRadius); + data.opaque -= QRect(blurRect.x() + blurRect.width() - topCornerRadius, blurRect.y(), topCornerRadius, topCornerRadius); + data.opaque -= QRect(blurRect.x(), blurRect.y() + blurRect.height() - bottomCornerRadius, bottomCornerRadius, bottomCornerRadius); + data.opaque -= QRect(blurRect.x() + blurRect.width() - bottomCornerRadius, blurRect.y() + blurRect.height() - bottomCornerRadius, bottomCornerRadius, bottomCornerRadius); } } @@ -724,7 +736,7 @@ bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintDa bool BlurEffect::shouldForceBlur(const EffectWindow *w) const { - if ((!m_blurDocks && w->isDock()) || (!m_blurMenus && (w->isMenu() || w->isDropdownMenu() || w->isPopupMenu() || w->isPopupWindow()))) { + if ((!m_blurDocks && w->isDock()) || (!m_blurMenus && isMenu(w))) { return false; } @@ -790,9 +802,20 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi return; } - int topCornerRadius = std::max(0, (int)std::round(m_topCornerRadius * viewport.scale()) - m_cornerRadiusOffset); - int bottomCornerRadius = std::max(0, (int)std::round(m_bottomCornerRadius * viewport.scale()) - m_cornerRadiusOffset); - bool hasRoundedCorners = m_topCornerRadius || m_bottomCornerRadius; + int topCornerRadius; + int bottomCornerRadius; + if (isMenu(w)) { + topCornerRadius = bottomCornerRadius = m_menuCornerRadius; + } else if (w->isDock()) { + topCornerRadius = bottomCornerRadius = m_dockCornerRadius; + } else { + topCornerRadius = m_windowTopCornerRadius; + bottomCornerRadius = m_windowBottomCornerRadius; + } + topCornerRadius = std::max(0, (int)std::round(topCornerRadius * viewport.scale()) - m_cornerRadiusOffset); + bottomCornerRadius = std::max(0, (int)std::round(bottomCornerRadius * viewport.scale()) - m_cornerRadiusOffset); + + bool hasRoundedCorners = topCornerRadius || bottomCornerRadius; const QRegion rawBlurRegion = blurRegion(w); const QRegion blurShape = transformedBlurRegion(rawBlurRegion.translated(w->pos().toPoint()), data); @@ -809,10 +832,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi const bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); if (hasRoundedCorners && ((!w->isFullScreen() && !isMaximized) || m_roundCornersOfMaximizedWindows)) { if (w->isDock()) { - // Only round floating panels. If the pixel at (0, height / 2) for horizontal panels and (width / 2, 0) - // for vertical panels doesn't belong to the blur region, the panel is most likely floating. The (0,0) - // pixel may be outside the blur region if the panel can float but isn't at the moment. - if (!rawBlurRegion.intersects(QRect(0, w->height() / 2, 1, 1)) && !rawBlurRegion.intersects(QRect(w->width() / 2, 0, 1, 1))) { + if (isDockFloating(w, rawBlurRegion)) { roundTopLeftCorner = roundTopRightCorner = topCornerRadius; roundBottomLeftCorner = roundBottomRightCorner = bottomCornerRadius; } @@ -829,7 +849,7 @@ void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &vi hasRoundedCorners = roundTopLeftCorner || roundTopRightCorner || roundBottomLeftCorner || roundBottomRightCorner; } - const auto corners = roundedCorners(viewport.scale()); + const auto corners = roundedCorners(topCornerRadius, bottomCornerRadius, viewport.scale()); const QRect blurRect = scaledBlurShape.boundingRect(); if (roundTopLeftCorner) { diff --git a/src/blur.h b/src/blur.h index 8bbf1a21b..fabd22fae 100644 --- a/src/blur.h +++ b/src/blur.h @@ -87,17 +87,11 @@ public Q_SLOTS: void blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data); GLTexture *ensureNoiseTexture(); - /* - * @param regionScale How much to scale the blur region. This parameter should be 1 if the region is rounded. - * @returns The area to be blurred. It's possible that all of it will be clipped. - */ - QList effectiveBlurShape(const QRect &backgroundRect, const QRect &deviceBackgroundRect, const QRegion &paintRegion, const QRegion &blurRegion, qreal screenScale, qreal regionScale) const; - /* * @returns An array containing rounded corner masks for the given screen scale. If no masks exist for the given screen scale, * they will be generated. */ - std::array roundedCorners(qreal scale); + std::array roundedCorners(int topCornerRadius, int bottomCornerRadius, qreal scale); /* * Generates rounded corner masks for the left and right corner for the given radius. @@ -182,10 +176,6 @@ public Q_SLOTS: bool m_blurNonMatching; bool m_blurDecorations; bool m_transparentBlur; - int m_topCornerRadius; - int m_bottomCornerRadius; - float m_roundedCornersAntialiasing; - bool m_roundCornersOfMaximizedWindows; bool m_blurMenus; bool m_blurDocks; bool m_paintAsTranslucent; @@ -194,11 +184,17 @@ public Q_SLOTS: bool m_hasValidFakeBlurTexture; + int m_windowTopCornerRadius; + int m_windowBottomCornerRadius; + int m_menuCornerRadius; + int m_dockCornerRadius; + float m_roundedCornersAntialiasing; + bool m_roundCornersOfMaximizedWindows; int m_cornerRadiusOffset; // Corner masks where the key is the screen scale and the value is an array of the masks // (top left, top right, bottom left, bottom right). Used for rounding the blur region. - std::unordered_map> m_corners; + std::map, std::array> m_corners; struct OffsetStruct { diff --git a/src/blur.kcfg b/src/blur.kcfg index 54c2082e7..734cf1af6 100644 --- a/src/blur.kcfg +++ b/src/blur.kcfg @@ -34,6 +34,12 @@ class3 0 + + 0 + + + 0 + 1.0 diff --git a/src/kcm/blur_config.ui b/src/kcm/blur_config.ui index 26597a28f..4d542f015 100644 --- a/src/kcm/blur_config.ui +++ b/src/kcm/blur_config.ui @@ -321,7 +321,7 @@ - Top corner radius + Window top corner radius @@ -339,7 +339,7 @@ - Bottom corner radius + Window bottom corner radius @@ -357,7 +357,43 @@ - Antialiasing + Menu corner radius + + + + + + + 0 + + + + + + + + + + + Dock corner radius + + + + + + + 0 + + + + + + + + + + + Anti-aliasing diff --git a/src/utils.h b/src/utils.h index b5275f0cb..218793153 100644 --- a/src/utils.h +++ b/src/utils.h @@ -16,4 +16,17 @@ inline QRegion scaledRegion(const QRegion ®ion, qreal scale) return scaledRegion; } +inline bool isMenu(const EffectWindow *w) +{ + return w->isMenu() || w->isDropdownMenu() || w->isPopupMenu() || w->isPopupWindow(); +} + +inline bool isDockFloating(const EffectWindow *dock, const QRegion blurRegion) +{ + // If the pixel at (0, height / 2) for horizontal panels and (width / 2, 0) for vertical panels doesn't belong to + // the blur region, the dock is most likely floating. The (0,0) pixel may be outside the blur region if the dock + // can float but isn't at the moment. + return !blurRegion.intersects(QRect(0, dock->height() / 2, 1, 1)) && !blurRegion.intersects(QRect(dock->width() / 2, 0, 1, 1)); +} + } \ No newline at end of file From f3c073dfe84bef938279cb9291466eb4f5ab3693 Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:59:11 +0200 Subject: [PATCH 27/28] fix comments --- src/blur.cpp | 2 +- src/blur.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/blur.cpp b/src/blur.cpp index 077c293e1..f3ff999e5 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -418,7 +418,7 @@ void BlurEffect::generateRoundedCornerMasks(int radius, QRegion &left, QRegion & m_roundedCorners.shader->setUniform(m_roundedCorners.roundTopRightCornerLocation, false); m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomLeftCornerLocation, true); m_roundedCorners.shader->setUniform(m_roundedCorners.roundBottomRightCornerLocation, true); - m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, 0); + m_roundedCorners.shader->setUniform(m_roundedCorners.topCornerRadiusLocation, static_cast(0)); m_roundedCorners.shader->setUniform(m_roundedCorners.bottomCornerRadiusLocation, radius); m_roundedCorners.shader->setUniform(m_roundedCorners.antialiasingLocation, static_cast(0)); m_roundedCorners.shader->setUniform(m_roundedCorners.regionSizeLocation, QVector2D(size, size)); diff --git a/src/blur.h b/src/blur.h index fabd22fae..28b71e3ba 100644 --- a/src/blur.h +++ b/src/blur.h @@ -88,14 +88,14 @@ public Q_SLOTS: GLTexture *ensureNoiseTexture(); /* - * @returns An array containing rounded corner masks for the given screen scale. If no masks exist for the given screen scale, - * they will be generated. + * @returns An array containing rounded corner masks for the given screen scale and radii. If no masks exist, they + * will be generated. */ std::array roundedCorners(int topCornerRadius, int bottomCornerRadius, qreal scale); /* - * Generates rounded corner masks for the left and right corner for the given radius. - * @param top Whether TODO + * Generates rounded corner masks for the left and right corner of the given radius. + * @param top Whether the corners belong to the top part of the window. */ void generateRoundedCornerMasks(int radius, QRegion &left, QRegion &right, bool top) const; From af0693d88b74dc37510095bfd76be759fe3451aa Mon Sep 17 00:00:00 2001 From: taj-ny <79316397+taj-ny@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:05:05 +0200 Subject: [PATCH 28/28] bump version --- CMakeLists.txt | 2 +- package.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c806ff784..dc2203b8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16.0) project(forceblur) -set(PROJECT_VERSION "1.2.1") +set(PROJECT_VERSION "1.3.0") set(PROJECT_VERSION_MAJOR 0) set(KF_MIN_VERSION "5.240.0") diff --git a/package.nix b/package.nix index cfd8ef533..8e92c706c 100644 --- a/package.nix +++ b/package.nix @@ -9,7 +9,7 @@ stdenv.mkDerivation rec { pname = "kwin-effects-forceblur"; - version = "1.2.1"; + version = "1.3.0"; src = ./.;