11#include "blurconfig.h"
23#include <QGuiApplication>
32#include <KConfigGroup>
33#include <KSharedConfig>
35#include <KDecoration2/Decoration>
37Q_LOGGING_CATEGORY(KWIN_BLUR,
"kwin_effect_blur", QtWarningMsg)
39static void ensureResources()
42 Q_INIT_RESOURCE(blur);
48static const QByteArray s_blurAtomName = QByteArrayLiteral(
"_KDE_NET_WM_BLUR_BEHIND_REGION");
50BlurManagerInterface *BlurEffect::s_blurManager =
nullptr;
51QTimer *BlurEffect::s_blurManagerRemoveTimer =
nullptr;
59 QStringLiteral(
":/effects/blur/shaders/vertex.vert"),
60 QStringLiteral(
":/effects/blur/shaders/downsample.frag"));
61 if (!m_downsamplePass.shader) {
62 qCWarning(KWIN_BLUR) <<
"Failed to load downsampling pass shader";
65 m_downsamplePass.mvpMatrixLocation = m_downsamplePass.shader->uniformLocation(
"modelViewProjectionMatrix");
66 m_downsamplePass.offsetLocation = m_downsamplePass.shader->uniformLocation(
"offset");
67 m_downsamplePass.halfpixelLocation = m_downsamplePass.shader->uniformLocation(
"halfpixel");
71 QStringLiteral(
":/effects/blur/shaders/vertex.vert"),
72 QStringLiteral(
":/effects/blur/shaders/upsample.frag"));
73 if (!m_upsamplePass.shader) {
74 qCWarning(KWIN_BLUR) <<
"Failed to load upsampling pass shader";
77 m_upsamplePass.mvpMatrixLocation = m_upsamplePass.shader->uniformLocation(
"modelViewProjectionMatrix");
78 m_upsamplePass.offsetLocation = m_upsamplePass.shader->uniformLocation(
"offset");
79 m_upsamplePass.halfpixelLocation = m_upsamplePass.shader->uniformLocation(
"halfpixel");
83 QStringLiteral(
":/effects/blur/shaders/vertex.vert"),
84 QStringLiteral(
":/effects/blur/shaders/noise.frag"));
85 if (!m_noisePass.shader) {
86 qCWarning(KWIN_BLUR) <<
"Failed to load noise pass shader";
89 m_noisePass.mvpMatrixLocation = m_noisePass.shader->uniformLocation(
"modelViewProjectionMatrix");
90 m_noisePass.noiseTextureSizeLocation = m_noisePass.shader->uniformLocation(
"noiseTextureSize");
91 m_noisePass.texStartPosLocation = m_noisePass.shader->uniformLocation(
"texStartPos");
94 initBlurStrengthValues();
102 if (!s_blurManagerRemoveTimer) {
103 s_blurManagerRemoveTimer =
new QTimer(QCoreApplication::instance());
104 s_blurManagerRemoveTimer->setSingleShot(
true);
105 s_blurManagerRemoveTimer->callOnTimeout([]() {
107 s_blurManager =
nullptr;
110 s_blurManagerRemoveTimer->stop();
111 if (!s_blurManager) {
137 s_blurManagerRemoveTimer->start(1000);
141void BlurEffect::initBlurStrengthValues()
146 int numOfBlurSteps = 15;
147 int remainingSteps = numOfBlurSteps;
169 blurOffsets.append({1.0, 2.0, 10});
170 blurOffsets.append({2.0, 3.0, 20});
171 blurOffsets.append({2.0, 5.0, 50});
172 blurOffsets.append({3.0, 8.0, 150});
178 for (
int i = 0; i < blurOffsets.size(); i++) {
179 offsetSum += blurOffsets[i].maxOffset - blurOffsets[i].minOffset;
182 for (
int i = 0; i < blurOffsets.size(); i++) {
183 int iterationNumber = std::ceil((blurOffsets[i].maxOffset - blurOffsets[i].minOffset) / offsetSum * numOfBlurSteps);
184 remainingSteps -= iterationNumber;
186 if (remainingSteps < 0) {
187 iterationNumber += remainingSteps;
190 float offsetDifference = blurOffsets[i].maxOffset - blurOffsets[i].minOffset;
192 for (
int j = 1; j <= iterationNumber; j++) {
194 blurStrengthValues.append({i + 1, blurOffsets[i].minOffset + (offsetDifference / iterationNumber) * j});
201 BlurConfig::self()->read();
203 int blurStrength = BlurConfig::blurStrength() - 1;
204 m_iterationCount = blurStrengthValues[blurStrength].iteration;
205 m_offset = blurStrengthValues[blurStrength].offset;
206 m_expandSize = blurOffsets[m_iterationCount - 1].expandSize;
207 m_noiseStrength = BlurConfig::noiseStrength();
215 std::optional<QRegion> content;
216 std::optional<QRegion> frame;
218 if (net_wm_blur_region != XCB_ATOM_NONE) {
219 const QByteArray value = w->
readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32);
221 if (value.size() > 0 && !(value.size() % (4 *
sizeof(uint32_t)))) {
222 const uint32_t *cardinals =
reinterpret_cast<const uint32_t *
>(value.constData());
223 for (
unsigned int i = 0; i < value.size() /
sizeof(uint32_t);) {
224 int x = cardinals[i++];
225 int y = cardinals[i++];
226 int w = cardinals[i++];
227 int h = cardinals[i++];
231 if (!value.isNull()) {
236 SurfaceInterface *surf = w->
surface();
238 if (surf && surf->blur()) {
242 if (
auto internal = w->internalWindow()) {
243 const auto property = internal->property(
"kwin_blur");
244 if (property.isValid()) {
245 content =
property.value<QRegion>();
249 if (w->decorationHasAlpha() && decorationSupportsBlurBehind(w)) {
250 frame = decorationBlurRegion(w);
253 if (content.has_value() || frame.has_value()) {
254 BlurEffectData &data = m_windows[w];
255 data.content = content;
258 if (
auto it = m_windows.find(w); it != m_windows.end()) {
277 internal->installEventFilter(
this);
288 if (
auto it = m_windows.find(w); it != m_windows.end()) {
292 if (
auto it = windowBlurChangedConnections.find(w); it != windowBlurChangedConnections.end()) {
294 windowBlurChangedConnections.erase(it);
300 for (
auto &[window, data] : m_windows) {
301 if (
auto it = data.render.find(screen); it != data.render.end()) {
303 data.render.erase(it);
310 if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) {
321 connect(w->
decoration(), &KDecoration2::Decoration::blurRegionChanged,
this, [
this, w]() {
328 auto internal = qobject_cast<QWindow *>(watched);
329 if (internal && event->type() == QEvent::DynamicPropertyChange) {
330 QDynamicPropertyChangeEvent *pe =
static_cast<QDynamicPropertyChangeEvent *
>(event);
331 if (pe->propertyName() ==
"kwin_blur") {
366bool BlurEffect::decorationSupportsBlurBehind(
const EffectWindow *w)
const
371QRegion BlurEffect::decorationBlurRegion(
const EffectWindow *w)
const
373 if (!decorationSupportsBlurBehind(w)) {
377 QRegion decorationRegion = QRegion(w->decoration()->rect()) - w->decorationInnerRect().toRect();
379 return decorationRegion.intersected(w->decoration()->blurRegion());
382QRegion BlurEffect::blurRegion(EffectWindow *w)
const
386 if (
auto it = m_windows.find(w); it != m_windows.end()) {
387 const std::optional<QRegion> &content = it->second.content;
388 const std::optional<QRegion> &frame = it->second.frame;
389 if (content.has_value()) {
390 if (content->isEmpty()) {
393 region = w->rect().toRect();
395 if (frame.has_value()) {
396 region = frame.value();
398 region += content->translated(w->contentsRect().topLeft().toPoint()) & w->decorationInnerRect().toRect();
400 }
else if (frame.has_value()) {
401 region = frame.value();
410 m_paintedArea = QRegion();
411 m_currentBlur = QRegion();
423 const QRegion oldOpaque = data.
opaque;
424 if (data.
opaque.intersects(m_currentBlur)) {
427 for (
const QRect &rect : data.
opaque) {
428 newOpaque += rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize);
433 m_currentBlur -= newOpaque;
438 if ((data.
paint - oldOpaque).intersects(m_currentBlur)) {
439 data.
paint += m_currentBlur;
443 const QRegion blurArea = blurRegion(w).boundingRect().translated(w->
pos().toPoint());
447 if (m_paintedArea.intersects(blurArea) || data.
paint.intersects(blurArea)) {
448 data.
paint += blurArea;
451 if (blurArea.intersects(m_currentBlur)) {
452 data.
paint += m_currentBlur;
456 m_currentBlur += blurArea;
458 m_paintedArea -= data.
opaque;
459 m_paintedArea += data.
paint;
472 bool scaled = !qFuzzyCompare(data.
xScale(), 1.0) && !qFuzzyCompare(data.
yScale(), 1.0);
484 blur(renderTarget, viewport, w, mask, region, data);
490GLTexture *BlurEffect::ensureNoiseTexture()
492 if (m_noiseStrength == 0) {
496 const qreal scale = std::max(1.0, QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.0);
497 if (!m_noisePass.noiseTexture || m_noisePass.noiseTextureScale != scale || m_noisePass.noiseTextureStength != m_noiseStrength) {
499 std::srand((uint)QTime::currentTime().msec());
501 QImage noiseImage(QSize(256, 256), QImage::Format_Grayscale8);
503 for (
int y = 0; y < noiseImage.height(); y++) {
504 uint8_t *noiseImageLine = (uint8_t *)noiseImage.scanLine(y);
506 for (
int x = 0; x < noiseImage.width(); x++) {
507 noiseImageLine[x] = std::rand() % m_noiseStrength;
511 noiseImage = noiseImage.scaled(noiseImage.size() * scale);
514 if (!m_noisePass.noiseTexture) {
517 m_noisePass.noiseTexture->setFilter(GL_NEAREST);
518 m_noisePass.noiseTexture->setWrapMode(GL_REPEAT);
519 m_noisePass.noiseTextureScale = scale;
520 m_noisePass.noiseTextureStength = m_noiseStrength;
523 return m_noisePass.noiseTexture.get();
526void BlurEffect::blur(
const RenderTarget &renderTarget,
const RenderViewport &viewport, EffectWindow *w,
int mask,
const QRegion ®ion, WindowPaintData &data)
528 auto it = m_windows.find(w);
529 if (it == m_windows.end()) {
533 BlurEffectData &blurInfo = it->second;
534 BlurRenderData &renderInfo = blurInfo.render[m_currentScreen];
535 if (!shouldBlur(w, mask, data)) {
540 QRegion blurShape = blurRegion(w).translated(w->pos().toPoint());
541 if (data.xScale() != 1 || data.yScale() != 1) {
542 QPoint pt = blurShape.boundingRect().topLeft();
544 for (
const QRect &r : blurShape) {
545 const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(),
546 pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation());
547 const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1,
548 std::floor(topLeft.y() + r.height() * data.yScale()) - 1);
549 scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight);
551 blurShape = scaledShape;
552 }
else if (data.xTranslation() || data.yTranslation()) {
554 for (
const QRect &r : blurShape) {
555 const QRectF t = QRectF(r).translated(data.xTranslation(), data.yTranslation());
556 const QPoint topLeft(std::ceil(t.x()), std::ceil(t.y()));
557 const QPoint bottomRight(std::floor(t.x() + t.width() - 1), std::floor(t.y() + t.height() - 1));
558 translated += QRect(topLeft, bottomRight);
560 blurShape = translated;
563 const QRect backgroundRect = blurShape.boundingRect();
565 const auto opacity = w->opacity() * data.opacity();
568 QList<QRectF> effectiveShape;
569 effectiveShape.reserve(blurShape.rectCount());
571 for (
const QRect &clipRect : region) {
573 .translated(-deviceBackgroundRect.topLeft());
574 for (
const QRect &shapeRect : blurShape) {
575 const QRectF deviceShapeRect =
snapToPixelGridF(
scaledRect(shapeRect.translated(-backgroundRect.topLeft()), viewport.scale()));
576 if (
const QRectF intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) {
577 effectiveShape.append(intersected);
582 for (
const QRect &rect : blurShape) {
586 if (effectiveShape.isEmpty()) {
592 GLenum textureFormat = GL_RGBA8;
593 if (renderTarget.texture()) {
594 textureFormat = renderTarget.texture()->internalFormat();
597 if (renderInfo.framebuffers.size() != (m_iterationCount + 1) || renderInfo.textures[0]->size() != backgroundRect.size() || renderInfo.textures[0]->internalFormat() != textureFormat) {
598 renderInfo.framebuffers.clear();
599 renderInfo.textures.clear();
601 for (
size_t i = 0; i <= m_iterationCount; ++i) {
604 qCWarning(KWIN_BLUR) <<
"Failed to allocate an offscreen texture";
607 texture->setFilter(GL_LINEAR);
608 texture->setWrapMode(GL_CLAMP_TO_EDGE);
610 auto framebuffer = std::make_unique<GLFramebuffer>(texture.get());
611 if (!framebuffer->valid()) {
612 qCWarning(KWIN_BLUR) <<
"Failed to create an offscreen framebuffer";
615 renderInfo.textures.push_back(std::move(texture));
616 renderInfo.framebuffers.push_back(std::move(framebuffer));
621 const QRegion dirtyRegion = region & backgroundRect;
622 for (
const QRect &dirtyRect : dirtyRegion) {
623 renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, dirtyRect.translated(-backgroundRect.topLeft()));
632 const int vertexCount = effectiveShape.size() * 6;
633 if (
auto result = vbo->map<GLVertex2D>(6 + vertexCount)) {
640 const QRectF localRect = QRectF(0, 0, backgroundRect.width(), backgroundRect.height());
642 const float x0 = localRect.left();
643 const float y0 = localRect.top();
644 const float x1 = localRect.right();
645 const float y1 = localRect.bottom();
647 const float u0 = x0 / backgroundRect.width();
648 const float v0 = 1.0f - y0 / backgroundRect.height();
649 const float u1 = x1 / backgroundRect.width();
650 const float v1 = 1.0f - y1 / backgroundRect.height();
653 map[vboIndex++] = GLVertex2D{
654 .position = QVector2D(x0, y0),
655 .texcoord = QVector2D(u0, v0),
657 map[vboIndex++] = GLVertex2D{
658 .position = QVector2D(x1, y1),
659 .texcoord = QVector2D(u1, v1),
661 map[vboIndex++] = GLVertex2D{
662 .position = QVector2D(x0, y1),
663 .texcoord = QVector2D(u0, v1),
667 map[vboIndex++] = GLVertex2D{
668 .position = QVector2D(x0, y0),
669 .texcoord = QVector2D(u0, v0),
671 map[vboIndex++] = GLVertex2D{
672 .position = QVector2D(x1, y0),
673 .texcoord = QVector2D(u1, v0),
675 map[vboIndex++] = GLVertex2D{
676 .position = QVector2D(x1, y1),
677 .texcoord = QVector2D(u1, v1),
682 for (
const QRectF &rect : effectiveShape) {
683 const float x0 = rect.left();
684 const float y0 = rect.top();
685 const float x1 = rect.right();
686 const float y1 = rect.bottom();
688 const float u0 = x0 / deviceBackgroundRect.width();
689 const float v0 = 1.0f - y0 / deviceBackgroundRect.height();
690 const float u1 = x1 / deviceBackgroundRect.width();
691 const float v1 = 1.0f - y1 / deviceBackgroundRect.height();
694 map[vboIndex++] = GLVertex2D{
695 .position = QVector2D(x0, y0),
696 .texcoord = QVector2D(u0, v0),
698 map[vboIndex++] = GLVertex2D{
699 .position = QVector2D(x1, y1),
700 .texcoord = QVector2D(u1, v1),
702 map[vboIndex++] = GLVertex2D{
703 .position = QVector2D(x0, y1),
704 .texcoord = QVector2D(u0, v1),
708 map[vboIndex++] = GLVertex2D{
709 .position = QVector2D(x0, y0),
710 .texcoord = QVector2D(u0, v0),
712 map[vboIndex++] = GLVertex2D{
713 .position = QVector2D(x1, y0),
714 .texcoord = QVector2D(u1, v0),
716 map[vboIndex++] = GLVertex2D{
717 .position = QVector2D(x1, y1),
718 .texcoord = QVector2D(u1, v1),
724 qCWarning(KWIN_BLUR) <<
"Failed to map vertex buffer";
734 QMatrix4x4 projectionMatrix;
735 projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height()));
737 m_downsamplePass.shader->setUniform(m_downsamplePass.mvpMatrixLocation, projectionMatrix);
738 m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation,
float(m_offset));
740 for (
size_t i = 1; i < renderInfo.framebuffers.size(); ++i) {
741 const auto &read = renderInfo.framebuffers[i - 1];
742 const auto &draw = renderInfo.framebuffers[i];
744 const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
745 0.5 / read->colorAttachment()->height());
746 m_downsamplePass.shader->setUniform(m_downsamplePass.halfpixelLocation, halfpixel);
748 read->colorAttachment()->bind();
751 vbo->draw(GL_TRIANGLES, 0, 6);
761 QMatrix4x4 projectionMatrix;
762 projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height()));
764 m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix);
765 m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation,
float(m_offset));
767 for (
size_t i = renderInfo.framebuffers.size() - 1; i > 1; --i) {
769 const auto &read = renderInfo.framebuffers[i];
771 const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
772 0.5 / read->colorAttachment()->height());
773 m_upsamplePass.shader->setUniform(m_upsamplePass.halfpixelLocation, halfpixel);
775 read->colorAttachment()->bind();
777 vbo->draw(GL_TRIANGLES, 0, 6);
782 const auto &read = renderInfo.framebuffers[1];
784 projectionMatrix = data.projectionMatrix();
785 projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y());
786 m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix);
788 const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
789 0.5 / read->colorAttachment()->height());
790 m_upsamplePass.shader->setUniform(m_upsamplePass.halfpixelLocation, halfpixel);
792 read->colorAttachment()->bind();
797 float o = 1.0f - (opacity);
799 glBlendColor(0, 0, 0, o);
800 glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
803 vbo->draw(GL_TRIANGLES, 6, vertexCount);
812 if (m_noiseStrength > 0) {
818 glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
820 glBlendFunc(GL_ONE, GL_ONE);
826 QMatrix4x4 projectionMatrix = data.projectionMatrix();
827 projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y());
829 m_noisePass.shader->setUniform(m_noisePass.mvpMatrixLocation, projectionMatrix);
830 m_noisePass.shader->setUniform(m_noisePass.noiseTextureSizeLocation, QVector2D(
noiseTexture->width(),
noiseTexture->height()));
831 m_noisePass.shader->setUniform(m_noisePass.texStartPosLocation, QVector2D(deviceBackgroundRect.topLeft()));
835 vbo->draw(GL_TRIANGLES, 6, vertexCount);
858#include "moc_blur.cpp"
void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override
void reconfigure(ReconfigureFlags flags) override
bool isActive() const override
std::unique_ptr< GLTexture > noiseTexture
bool eventFilter(QObject *watched, QEvent *event) override
void slotPropertyNotify(KWin::EffectWindow *w, long atom)
void slotScreenRemoved(KWin::Output *screen)
bool blocksDirectScanout() const override
void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) override
void slotWindowDeleted(KWin::EffectWindow *w)
static bool enabledByDefault()
void setupDecorationConnections(EffectWindow *w)
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override
void slotWindowAdded(KWin::EffectWindow *w)
Represents the Global for org_kde_kwin_blur_manager interface.
Representation of a window used by/for Effect classes.
void windowDecorationChanged(KWin::EffectWindow *window)
Q_SCRIPTABLE QVariant data(int role) const
QByteArray readProperty(long atom, long type, int format) const
SurfaceInterface * surface() const
KDecoration2::Decoration * decoration() const
Display * waylandDisplay() const
void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data)
void propertyNotify(KWin::EffectWindow *w, long atom)
void windowDeleted(KWin::EffectWindow *w)
Q_SCRIPTABLE KWin::EffectWindow * findWindow(WId id) const
void screenRemoved(KWin::Output *screen)
QList< EffectWindow * > stackingOrder
bool makeOpenGLContextCurrent()
Makes the OpenGL compositing context current.
bool isScreenLocked() const
xcb_atom_t announceSupportProperty(const QByteArray &propertyName, Effect *effect)
Announces support for the feature with the given name. If no other Effect has announced support for t...
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
bool isOpenGLCompositing() const
Whether the Compositor is OpenGL based (either GL 1 or 2).
void xcbConnectionChanged()
void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
xcb_connection_t * xcbConnection() const
KSharedConfigPtr config() const
void windowAdded(KWin::EffectWindow *w)
Q_SCRIPTABLE void addRepaintFull()
Effect * activeFullScreenEffect() const
static GLFramebuffer * popFramebuffer()
static void pushFramebuffer(GLFramebuffer *fbo)
static bool blitSupported()
static std::unique_ptr< GLTexture > allocate(GLenum internalFormat, const QSize &size, int levels=1)
static std::unique_ptr< GLTexture > upload(const QImage &image)
static constexpr std::array GLVertex2DLayout
static GLVertexBuffer * streamingBuffer()
std::unique_ptr< GLShader > generateShaderFromFile(ShaderTraits traits, const QString &vertexFile=QString(), const QString &fragmentFile=QString())
static ShaderManager * instance()
GLShader * pushShader(ShaderTraits traits)
Resource representing a wl_surface.
BlurInterface * blur() const
qreal yTranslation() const
qreal xTranslation() const
@ PAINT_WINDOW_TRANSFORMED
qreal fromXNative(int value)
KWIN_EXPORT QRect infiniteRegion()
@ WindowForceBlurRole
For fullscreen effects to enforce blurring of windows,.
KWIN_EXPORT QPointF snapToPixelGridF(const QPointF &point)
KWIN_EXPORT QPoint snapToPixelGrid(const QPointF &point)
KWIN_EXPORT QRectF scaledRect(const QRectF &rect, qreal scale)