KWin
Loading...
Searching...
No Matches
blur.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2010 Fredrik Höglund <fredrik@kde.org>
3 SPDX-FileCopyrightText: 2011 Philipp Knechtges <philipp-dev@knechtges.com>
4 SPDX-FileCopyrightText: 2018 Alex Nemeth <alex.nemeth329@gmail.com>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "blur.h"
10// KConfigSkeleton
11#include "blurconfig.h"
12
13#include "core/pixelgrid.h"
14#include "core/rendertarget.h"
15#include "core/renderviewport.h"
17#include "opengl/glplatform.h"
18#include "utils/xcbutils.h"
19#include "wayland/blur.h"
20#include "wayland/display.h"
21#include "wayland/surface.h"
22
23#include <QGuiApplication>
24#include <QMatrix4x4>
25#include <QScreen>
26#include <QTime>
27#include <QTimer>
28#include <QWindow>
29#include <cmath> // for ceil()
30#include <cstdlib>
31
32#include <KConfigGroup>
33#include <KSharedConfig>
34
35#include <KDecoration2/Decoration>
36
37Q_LOGGING_CATEGORY(KWIN_BLUR, "kwin_effect_blur", QtWarningMsg)
38
39static void ensureResources()
40{
41 // Must initialize resources manually because the effect is a static lib.
42 Q_INIT_RESOURCE(blur);
43}
44
45namespace KWin
46{
47
48static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
49
50BlurManagerInterface *BlurEffect::s_blurManager = nullptr;
51QTimer *BlurEffect::s_blurManagerRemoveTimer = nullptr;
52
54{
55 BlurConfig::instance(effects->config());
56 ensureResources();
57
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";
63 return;
64 } else {
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");
68 }
69
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";
75 return;
76 } else {
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");
80 }
81
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";
87 return;
88 } else {
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");
92 }
93
94 initBlurStrengthValues();
96
97 if (effects->xcbConnection()) {
98 net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this);
99 }
100
101 if (effects->waylandDisplay()) {
102 if (!s_blurManagerRemoveTimer) {
103 s_blurManagerRemoveTimer = new QTimer(QCoreApplication::instance());
104 s_blurManagerRemoveTimer->setSingleShot(true);
105 s_blurManagerRemoveTimer->callOnTimeout([]() {
106 s_blurManager->remove();
107 s_blurManager = nullptr;
108 });
109 }
110 s_blurManagerRemoveTimer->stop();
111 if (!s_blurManager) {
112 s_blurManager = new BlurManagerInterface(effects->waylandDisplay(), s_blurManagerRemoveTimer);
113 }
114 }
115
120 connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() {
121 net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this);
122 });
123
124 // Fetch the blur regions for all windows
125 const auto stackingOrder = effects->stackingOrder();
126 for (EffectWindow *window : stackingOrder) {
127 slotWindowAdded(window);
128 }
129
130 m_valid = true;
131}
132
134{
135 // When compositing is restarted, avoid removing the manager immediately.
136 if (s_blurManager) {
137 s_blurManagerRemoveTimer->start(1000);
138 }
139}
140
141void BlurEffect::initBlurStrengthValues()
142{
143 // This function creates an array of blur strength values that are evenly distributed
144
145 // The range of the slider on the blur settings UI
146 int numOfBlurSteps = 15;
147 int remainingSteps = numOfBlurSteps;
148
149 /*
150 * Explanation for these numbers:
151 *
152 * The texture blur amount depends on the downsampling iterations and the offset value.
153 * By changing the offset we can alter the blur amount without relying on further downsampling.
154 * But there is a minimum and maximum value of offset per downsample iteration before we
155 * get artifacts.
156 *
157 * The minOffset variable is the minimum offset value for an iteration before we
158 * get blocky artifacts because of the downsampling.
159 *
160 * The maxOffset value is the maximum offset value for an iteration before we
161 * get diagonal line artifacts because of the nature of the dual kawase blur algorithm.
162 *
163 * The expandSize value is the minimum value for an iteration before we reach the end
164 * of a texture in the shader and sample outside of the area that was copied into the
165 * texture from the screen.
166 */
167
168 // {minOffset, maxOffset, expandSize}
169 blurOffsets.append({1.0, 2.0, 10}); // Down sample size / 2
170 blurOffsets.append({2.0, 3.0, 20}); // Down sample size / 4
171 blurOffsets.append({2.0, 5.0, 50}); // Down sample size / 8
172 blurOffsets.append({3.0, 8.0, 150}); // Down sample size / 16
173 // blurOffsets.append({5.0, 10.0, 400}); // Down sample size / 32
174 // blurOffsets.append({7.0, ?.0}); // Down sample size / 64
175
176 float offsetSum = 0;
177
178 for (int i = 0; i < blurOffsets.size(); i++) {
179 offsetSum += blurOffsets[i].maxOffset - blurOffsets[i].minOffset;
180 }
181
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;
185
186 if (remainingSteps < 0) {
187 iterationNumber += remainingSteps;
188 }
189
190 float offsetDifference = blurOffsets[i].maxOffset - blurOffsets[i].minOffset;
191
192 for (int j = 1; j <= iterationNumber; j++) {
193 // {iteration, offset}
194 blurStrengthValues.append({i + 1, blurOffsets[i].minOffset + (offsetDifference / iterationNumber) * j});
195 }
196 }
197}
198
199void BlurEffect::reconfigure(ReconfigureFlags flags)
200{
201 BlurConfig::self()->read();
202
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();
208
209 // Update all windows for the blur to take effect
211}
212
213void BlurEffect::updateBlurRegion(EffectWindow *w)
214{
215 std::optional<QRegion> content;
216 std::optional<QRegion> frame;
217
218 if (net_wm_blur_region != XCB_ATOM_NONE) {
219 const QByteArray value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32);
220 QRegion region;
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++];
228 region += Xcb::fromXNative(QRect(x, y, w, h)).toRect();
229 }
230 }
231 if (!value.isNull()) {
232 content = region;
233 }
234 }
235
236 SurfaceInterface *surf = w->surface();
237
238 if (surf && surf->blur()) {
239 content = surf->blur()->region();
240 }
241
242 if (auto internal = w->internalWindow()) {
243 const auto property = internal->property("kwin_blur");
244 if (property.isValid()) {
245 content = property.value<QRegion>();
246 }
247 }
248
249 if (w->decorationHasAlpha() && decorationSupportsBlurBehind(w)) {
250 frame = decorationBlurRegion(w);
251 }
252
253 if (content.has_value() || frame.has_value()) {
254 BlurEffectData &data = m_windows[w];
255 data.content = content;
256 data.frame = frame;
257 } else {
258 if (auto it = m_windows.find(w); it != m_windows.end()) {
260 m_windows.erase(it);
261 }
262 }
263}
264
266{
267 SurfaceInterface *surf = w->surface();
268
269 if (surf) {
270 windowBlurChangedConnections[w] = connect(surf, &SurfaceInterface::blurChanged, this, [this, w]() {
271 if (w) {
272 updateBlurRegion(w);
273 }
274 });
275 }
276 if (auto internal = w->internalWindow()) {
277 internal->installEventFilter(this);
278 }
279
282
283 updateBlurRegion(w);
284}
285
287{
288 if (auto it = m_windows.find(w); it != m_windows.end()) {
290 m_windows.erase(it);
291 }
292 if (auto it = windowBlurChangedConnections.find(w); it != windowBlurChangedConnections.end()) {
293 disconnect(*it);
294 windowBlurChangedConnections.erase(it);
295 }
296}
297
299{
300 for (auto &[window, data] : m_windows) {
301 if (auto it = data.render.find(screen); it != data.render.end()) {
303 data.render.erase(it);
304 }
305 }
306}
307
309{
310 if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) {
311 updateBlurRegion(w);
312 }
313}
314
316{
317 if (!w->decoration()) {
318 return;
319 }
320
321 connect(w->decoration(), &KDecoration2::Decoration::blurRegionChanged, this, [this, w]() {
322 updateBlurRegion(w);
323 });
324}
325
326bool BlurEffect::eventFilter(QObject *watched, QEvent *event)
327{
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") {
332 if (auto w = effects->findWindow(internal)) {
333 updateBlurRegion(w);
334 }
335 }
336 }
337 return false;
338}
339
341{
343
344 if (gl->isIntel() && gl->chipClass() < SandyBridge) {
345 return false;
346 }
347 if (gl->isPanfrost() && gl->chipClass() <= MaliT8XX) {
348 return false;
349 }
350 // The blur effect works, but is painfully slow (FPS < 5) on Mali and VideoCore
351 if (gl->isLima() || gl->isVideoCore4() || gl->isVideoCore3D()) {
352 return false;
353 }
354 if (gl->isSoftwareEmulation()) {
355 return false;
356 }
357
358 return true;
359}
360
365
366bool BlurEffect::decorationSupportsBlurBehind(const EffectWindow *w) const
367{
368 return w->decoration() && !w->decoration()->blurRegion().isNull();
369}
370
371QRegion BlurEffect::decorationBlurRegion(const EffectWindow *w) const
372{
373 if (!decorationSupportsBlurBehind(w)) {
374 return QRegion();
375 }
376
377 QRegion decorationRegion = QRegion(w->decoration()->rect()) - w->decorationInnerRect().toRect();
379 return decorationRegion.intersected(w->decoration()->blurRegion());
380}
381
382QRegion BlurEffect::blurRegion(EffectWindow *w) const
383{
384 QRegion region;
385
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()) {
391 // An empty region means that the blur effect should be enabled
392 // for the whole window.
393 region = w->rect().toRect();
394 } else {
395 if (frame.has_value()) {
396 region = frame.value();
397 }
398 region += content->translated(w->contentsRect().topLeft().toPoint()) & w->decorationInnerRect().toRect();
399 }
400 } else if (frame.has_value()) {
401 region = frame.value();
402 }
403 }
404
405 return region;
406}
407
408void BlurEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
409{
410 m_paintedArea = QRegion();
411 m_currentBlur = QRegion();
412 m_currentScreen = effects->waylandDisplay() ? data.screen : nullptr;
413
414 effects->prePaintScreen(data, presentTime);
415}
416
417void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime)
418{
419 // this effect relies on prePaintWindow being called in the bottom to top order
420
421 effects->prePaintWindow(w, data, presentTime);
422
423 const QRegion oldOpaque = data.opaque;
424 if (data.opaque.intersects(m_currentBlur)) {
425 // to blur an area partially we have to shrink the opaque area of a window
426 QRegion newOpaque;
427 for (const QRect &rect : data.opaque) {
428 newOpaque += rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize);
429 }
430 data.opaque = newOpaque;
431
432 // we don't have to blur a region we don't see
433 m_currentBlur -= newOpaque;
434 }
435
436 // if we have to paint a non-opaque part of this window that intersects with the
437 // currently blurred region we have to redraw the whole region
438 if ((data.paint - oldOpaque).intersects(m_currentBlur)) {
439 data.paint += m_currentBlur;
440 }
441
442 // in case this window has regions to be blurred
443 const QRegion blurArea = blurRegion(w).boundingRect().translated(w->pos().toPoint());
444
445 // if this window or a window underneath the blurred area is painted again we have to
446 // blur everything
447 if (m_paintedArea.intersects(blurArea) || data.paint.intersects(blurArea)) {
448 data.paint += blurArea;
449 // we have to check again whether we do not damage a blurred area
450 // of a window
451 if (blurArea.intersects(m_currentBlur)) {
452 data.paint += m_currentBlur;
453 }
454 }
455
456 m_currentBlur += blurArea;
457
458 m_paintedArea -= data.opaque;
459 m_paintedArea += data.paint;
460}
461
462bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const
463{
464 if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool()) {
465 return false;
466 }
467
468 if (w->isDesktop()) {
469 return false;
470 }
471
472 bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0);
473 bool translated = data.xTranslation() || data.yTranslation();
474
475 if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBlurRole).toBool()) {
476 return false;
477 }
478
479 return true;
480}
481
482void BlurEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
483{
484 blur(renderTarget, viewport, w, mask, region, data);
485
486 // Draw the window over the blurred area
487 effects->drawWindow(renderTarget, viewport, w, mask, region, data);
488}
489
490GLTexture *BlurEffect::ensureNoiseTexture()
491{
492 if (m_noiseStrength == 0) {
493 return nullptr;
494 }
495
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) {
498 // Init randomness based on time
499 std::srand((uint)QTime::currentTime().msec());
500
501 QImage noiseImage(QSize(256, 256), QImage::Format_Grayscale8);
502
503 for (int y = 0; y < noiseImage.height(); y++) {
504 uint8_t *noiseImageLine = (uint8_t *)noiseImage.scanLine(y);
505
506 for (int x = 0; x < noiseImage.width(); x++) {
507 noiseImageLine[x] = std::rand() % m_noiseStrength;
508 }
509 }
510
511 noiseImage = noiseImage.scaled(noiseImage.size() * scale);
512
513 m_noisePass.noiseTexture = GLTexture::upload(noiseImage);
514 if (!m_noisePass.noiseTexture) {
515 return nullptr;
516 }
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;
521 }
522
523 return m_noisePass.noiseTexture.get();
524}
525
526void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
527{
528 auto it = m_windows.find(w);
529 if (it == m_windows.end()) {
530 return;
531 }
532
533 BlurEffectData &blurInfo = it->second;
534 BlurRenderData &renderInfo = blurInfo.render[m_currentScreen];
535 if (!shouldBlur(w, mask, data)) {
536 return;
537 }
538
539 // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape.
540 QRegion blurShape = blurRegion(w).translated(w->pos().toPoint());
541 if (data.xScale() != 1 || data.yScale() != 1) {
542 QPoint pt = blurShape.boundingRect().topLeft();
543 QRegion scaledShape;
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);
550 }
551 blurShape = scaledShape;
552 } else if (data.xTranslation() || data.yTranslation()) {
553 QRegion translated;
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);
559 }
560 blurShape = translated;
561 }
562
563 const QRect backgroundRect = blurShape.boundingRect();
564 const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale()));
565 const auto opacity = w->opacity() * data.opacity();
566
567 // Get the effective shape that will be actually blurred. It's possible that all of it will be clipped.
568 QList<QRectF> effectiveShape;
569 effectiveShape.reserve(blurShape.rectCount());
570 if (region != infiniteRegion()) {
571 for (const QRect &clipRect : region) {
572 const QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, viewport.scale()))
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);
578 }
579 }
580 }
581 } else {
582 for (const QRect &rect : blurShape) {
583 effectiveShape.append(snapToPixelGridF(scaledRect(rect.translated(-backgroundRect.topLeft()), viewport.scale())));
584 }
585 }
586 if (effectiveShape.isEmpty()) {
587 return;
588 }
589
590 // Maybe reallocate offscreen render targets. Keep in mind that the first one contains
591 // original background behind the window, it's not blurred.
592 GLenum textureFormat = GL_RGBA8;
593 if (renderTarget.texture()) {
594 textureFormat = renderTarget.texture()->internalFormat();
595 }
596
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();
600
601 for (size_t i = 0; i <= m_iterationCount; ++i) {
602 auto texture = GLTexture::allocate(textureFormat, backgroundRect.size() / (1 << i));
603 if (!texture) {
604 qCWarning(KWIN_BLUR) << "Failed to allocate an offscreen texture";
605 return;
606 }
607 texture->setFilter(GL_LINEAR);
608 texture->setWrapMode(GL_CLAMP_TO_EDGE);
609
610 auto framebuffer = std::make_unique<GLFramebuffer>(texture.get());
611 if (!framebuffer->valid()) {
612 qCWarning(KWIN_BLUR) << "Failed to create an offscreen framebuffer";
613 return;
614 }
615 renderInfo.textures.push_back(std::move(texture));
616 renderInfo.framebuffers.push_back(std::move(framebuffer));
617 }
618 }
619
620 // Fetch the pixels behind the shape that is going to be blurred.
621 const QRegion dirtyRegion = region & backgroundRect;
622 for (const QRect &dirtyRect : dirtyRegion) {
623 renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, dirtyRect.translated(-backgroundRect.topLeft()));
624 }
625
626 // Upload the geometry: the first 6 vertices are used when downsampling and upsampling offscreen,
627 // the remaining vertices are used when rendering on the screen.
628 GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
629 vbo->reset();
630 vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D));
631
632 const int vertexCount = effectiveShape.size() * 6;
633 if (auto result = vbo->map<GLVertex2D>(6 + vertexCount)) {
634 auto map = *result;
635
636 size_t vboIndex = 0;
637
638 // The geometry that will be blurred offscreen, in logical pixels.
639 {
640 const QRectF localRect = QRectF(0, 0, backgroundRect.width(), backgroundRect.height());
641
642 const float x0 = localRect.left();
643 const float y0 = localRect.top();
644 const float x1 = localRect.right();
645 const float y1 = localRect.bottom();
646
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();
651
652 // first triangle
653 map[vboIndex++] = GLVertex2D{
654 .position = QVector2D(x0, y0),
655 .texcoord = QVector2D(u0, v0),
656 };
657 map[vboIndex++] = GLVertex2D{
658 .position = QVector2D(x1, y1),
659 .texcoord = QVector2D(u1, v1),
660 };
661 map[vboIndex++] = GLVertex2D{
662 .position = QVector2D(x0, y1),
663 .texcoord = QVector2D(u0, v1),
664 };
665
666 // second triangle
667 map[vboIndex++] = GLVertex2D{
668 .position = QVector2D(x0, y0),
669 .texcoord = QVector2D(u0, v0),
670 };
671 map[vboIndex++] = GLVertex2D{
672 .position = QVector2D(x1, y0),
673 .texcoord = QVector2D(u1, v0),
674 };
675 map[vboIndex++] = GLVertex2D{
676 .position = QVector2D(x1, y1),
677 .texcoord = QVector2D(u1, v1),
678 };
679 }
680
681 // The geometry that will be painted on screen, in device pixels.
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();
687
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();
692
693 // first triangle
694 map[vboIndex++] = GLVertex2D{
695 .position = QVector2D(x0, y0),
696 .texcoord = QVector2D(u0, v0),
697 };
698 map[vboIndex++] = GLVertex2D{
699 .position = QVector2D(x1, y1),
700 .texcoord = QVector2D(u1, v1),
701 };
702 map[vboIndex++] = GLVertex2D{
703 .position = QVector2D(x0, y1),
704 .texcoord = QVector2D(u0, v1),
705 };
706
707 // second triangle
708 map[vboIndex++] = GLVertex2D{
709 .position = QVector2D(x0, y0),
710 .texcoord = QVector2D(u0, v0),
711 };
712 map[vboIndex++] = GLVertex2D{
713 .position = QVector2D(x1, y0),
714 .texcoord = QVector2D(u1, v0),
715 };
716 map[vboIndex++] = GLVertex2D{
717 .position = QVector2D(x1, y1),
718 .texcoord = QVector2D(u1, v1),
719 };
720 }
721
722 vbo->unmap();
723 } else {
724 qCWarning(KWIN_BLUR) << "Failed to map vertex buffer";
725 return;
726 }
727
728 vbo->bindArrays();
729
730 // The downsample pass of the dual Kawase algorithm: the background will be scaled down 50% every iteration.
731 {
732 ShaderManager::instance()->pushShader(m_downsamplePass.shader.get());
733
734 QMatrix4x4 projectionMatrix;
735 projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height()));
736
737 m_downsamplePass.shader->setUniform(m_downsamplePass.mvpMatrixLocation, projectionMatrix);
738 m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation, float(m_offset));
739
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];
743
744 const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
745 0.5 / read->colorAttachment()->height());
746 m_downsamplePass.shader->setUniform(m_downsamplePass.halfpixelLocation, halfpixel);
747
748 read->colorAttachment()->bind();
749
751 vbo->draw(GL_TRIANGLES, 0, 6);
752 }
753
755 }
756
757 // The upsample pass of the dual Kawase algorithm: the background will be scaled up 200% every iteration.
758 {
759 ShaderManager::instance()->pushShader(m_upsamplePass.shader.get());
760
761 QMatrix4x4 projectionMatrix;
762 projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height()));
763
764 m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix);
765 m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation, float(m_offset));
766
767 for (size_t i = renderInfo.framebuffers.size() - 1; i > 1; --i) {
769 const auto &read = renderInfo.framebuffers[i];
770
771 const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
772 0.5 / read->colorAttachment()->height());
773 m_upsamplePass.shader->setUniform(m_upsamplePass.halfpixelLocation, halfpixel);
774
775 read->colorAttachment()->bind();
776
777 vbo->draw(GL_TRIANGLES, 0, 6);
778 }
779
780 // The last upsampling pass is rendered on the screen, not in framebuffers[0].
782 const auto &read = renderInfo.framebuffers[1];
783
784 projectionMatrix = data.projectionMatrix();
785 projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y());
786 m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix);
787
788 const QVector2D halfpixel(0.5 / read->colorAttachment()->width(),
789 0.5 / read->colorAttachment()->height());
790 m_upsamplePass.shader->setUniform(m_upsamplePass.halfpixelLocation, halfpixel);
791
792 read->colorAttachment()->bind();
793
794 // Modulate the blurred texture with the window opacity if the window isn't opaque
795 if (opacity < 1.0) {
796 glEnable(GL_BLEND);
797 float o = 1.0f - (opacity);
798 o = 1.0f - o * o;
799 glBlendColor(0, 0, 0, o);
800 glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
801 }
802
803 vbo->draw(GL_TRIANGLES, 6, vertexCount);
804
805 if (opacity < 1.0) {
806 glDisable(GL_BLEND);
807 }
808
810 }
811
812 if (m_noiseStrength > 0) {
813 // Apply an additive noise onto the blurred image. The noise is useful to mask banding
814 // artifacts, which often happens due to the smooth color transitions in the blurred image.
815
816 glEnable(GL_BLEND);
817 if (opacity < 1.0) {
818 glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
819 } else {
820 glBlendFunc(GL_ONE, GL_ONE);
821 }
822
823 if (GLTexture *noiseTexture = ensureNoiseTexture()) {
824 ShaderManager::instance()->pushShader(m_noisePass.shader.get());
825
826 QMatrix4x4 projectionMatrix = data.projectionMatrix();
827 projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y());
828
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()));
832
833 noiseTexture->bind();
834
835 vbo->draw(GL_TRIANGLES, 6, vertexCount);
836
838 }
839
840 glDisable(GL_BLEND);
841 }
842
843 vbo->unbindArrays();
844}
845
847{
848 return m_valid && !effects->isScreenLocked();
849}
850
852{
853 return false;
854}
855
856} // namespace KWin
857
858#include "moc_blur.cpp"
void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) override
Definition blur.cpp:482
void reconfigure(ReconfigureFlags flags) override
Definition blur.cpp:199
~BlurEffect() override
Definition blur.cpp:133
bool isActive() const override
Definition blur.cpp:846
std::unique_ptr< GLTexture > noiseTexture
Definition blur.h:111
bool eventFilter(QObject *watched, QEvent *event) override
Definition blur.cpp:326
void slotPropertyNotify(KWin::EffectWindow *w, long atom)
Definition blur.cpp:308
void slotScreenRemoved(KWin::Output *screen)
Definition blur.cpp:298
bool blocksDirectScanout() const override
Definition blur.cpp:851
void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) override
Definition blur.cpp:417
void slotWindowDeleted(KWin::EffectWindow *w)
Definition blur.cpp:286
static bool enabledByDefault()
Definition blur.cpp:340
void setupDecorationConnections(EffectWindow *w)
Definition blur.cpp:315
static bool supported()
Definition blur.cpp:361
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override
Definition blur.cpp:408
void slotWindowAdded(KWin::EffectWindow *w)
Definition blur.cpp:265
QRegion region()
Definition blur.cpp:140
Represents the Global for org_kde_kwin_blur_manager interface.
Definition blur.h:32
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
bool isDesktop() const
KDecoration2::Decoration * decoration() const
QWindow * internalWindow
Display * waylandDisplay() const
void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, 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.
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 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 bool supported()
static GLFramebuffer * popFramebuffer()
static void pushFramebuffer(GLFramebuffer *fbo)
static bool blitSupported()
bool isVideoCore3D() const
bool isVideoCore4() const
static GLPlatform * instance()
Definition glplatform.h:394
bool isPanfrost() const
bool isLima() const
ChipClass chipClass() const
bool isIntel() const
bool isSoftwareEmulation() const
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.
Definition surface.h:80
BlurInterface * blur() const
Definition surface.cpp:876
qreal yTranslation() const
Definition effect.cpp:131
qreal yScale() const
Definition effect.cpp:61
qreal xScale() const
Definition effect.cpp:56
qreal xTranslation() const
Definition effect.cpp:126
@ PAINT_WINDOW_TRANSFORMED
Definition effect.h:552
@ ReconfigureAll
Definition effect.h:601
qreal fromXNative(int value)
Definition xcbutils.cpp:632
KWIN_EXPORT QRect infiniteRegion()
Definition globals.h:234
@ WindowForceBlurRole
For fullscreen effects to enforce blurring of windows,.
KWIN_EXPORT QPointF snapToPixelGridF(const QPointF &point)
Definition pixelgrid.h:21
KWIN_EXPORT QPoint snapToPixelGrid(const QPointF &point)
Definition pixelgrid.h:16
@ SandyBridge
Definition glplatform.h:104
@ MaliT8XX
Definition glplatform.h:134
KWIN_EXPORT QRectF scaledRect(const QRectF &rect, qreal scale)
Definition globals.h:243
EffectsHandler * effects