blur.cpp 27.1 KB
Newer Older
1
/*
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
2
3
4
5
6
7
    SPDX-FileCopyrightText: 2010 Fredrik Höglund <fredrik@kde.org>
    SPDX-FileCopyrightText: 2011 Philipp Knechtges <philipp-dev@knechtges.com>
    SPDX-FileCopyrightText: 2018 Alex Nemeth <alex.nemeth329@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
8
9
10

#include "blur.h"
#include "blurshader.h"
11
12
// KConfigSkeleton
#include "blurconfig.h"
13

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
14
#include <QGuiApplication>
Martin Flöser's avatar
Martin Flöser committed
15
#include <QMatrix4x4>
Alex Nemeth's avatar
Alex Nemeth committed
16
17
#include <QScreen> // for QGuiApplication
#include <QTime>
18
#include <QWindow>
19
#include <cmath> // for ceil()
20

21
22
23
24
#include <KWaylandServer/surface_interface.h>
#include <KWaylandServer/blur_interface.h>
#include <KWaylandServer/shadow_interface.h>
#include <KWaylandServer/display.h>
Alex Nemeth's avatar
Alex Nemeth committed
25
26
#include <KSharedConfig>
#include <KConfigGroup>
27

28
29
namespace KWin
{
30

31
32
static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");

33
34
BlurEffect::BlurEffect()
{
35
    initConfig<BlurConfig>();
36
    m_shader = new BlurShader(this);
37

38
    initBlurStrengthValues();
39
40
    reconfigure(ReconfigureAll);

41
42
    // ### Hackish way to announce support.
    //     Should be included in _NET_SUPPORTED instead.
43
    if (m_shader && m_shader->isValid() && m_renderTargetsValid) {
44
        net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this);
45
        KWaylandServer::Display *display = effects->waylandDisplay();
46
47
48
        if (display) {
            m_blurManager = display->createBlurManager(this);
        }
49
    } else {
50
        net_wm_blur_region = 0;
51
    }
52

53
54
55
56
    connect(effects, &EffectsHandler::windowAdded, this, &BlurEffect::slotWindowAdded);
    connect(effects, &EffectsHandler::windowDeleted, this, &BlurEffect::slotWindowDeleted);
    connect(effects, &EffectsHandler::propertyNotify, this, &BlurEffect::slotPropertyNotify);
    connect(effects, &EffectsHandler::screenGeometryChanged, this, &BlurEffect::slotScreenGeometryChanged);
57
58
    connect(effects, &EffectsHandler::xcbConnectionChanged, this,
        [this] {
59
            if (m_shader && m_shader->isValid() && m_renderTargetsValid) {
60
61
62
63
                net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this);
            }
        }
    );
64
65
66
67

    // Fetch the blur regions for all windows
    foreach (EffectWindow *window, effects->stackingOrder())
        updateBlurRegion(window);
68
69
70
71
}

BlurEffect::~BlurEffect()
{
72
    deleteFBOs();
73
74
}

75
76
void BlurEffect::slotScreenGeometryChanged()
{
77
78
    effects->makeOpenGLContextCurrent();
    updateTexture();
79

80
81
82
83
84
85
    // Fetch the blur regions for all windows
    foreach (EffectWindow *window, effects->stackingOrder())
        updateBlurRegion(window);
    effects->doneOpenGLContextCurrent();
}

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
bool BlurEffect::renderTargetsValid() const
{
    return !m_renderTargets.isEmpty() && std::find_if(m_renderTargets.cbegin(), m_renderTargets.cend(),
        [](const GLRenderTarget *target) {
            return !target->valid();
        }) == m_renderTargets.cend();
}

void BlurEffect::deleteFBOs()
{
    qDeleteAll(m_renderTargets);

    m_renderTargets.clear();
    m_renderTextures.clear();
}

void BlurEffect::updateTexture()
{
    deleteFBOs();

    /* Reserve memory for:
     *  - The original sized texture (1)
     *  - The downsized textures (m_downSampleIterations)
     *  - The helper texture (1)
     */
    m_renderTargets.reserve(m_downSampleIterations + 2);
    m_renderTextures.reserve(m_downSampleIterations + 2);

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
    GLenum textureFormat = GL_RGBA8;

    // Check the color encoding of the default framebuffer
    if (!GLPlatform::instance()->isGLES()) {
        GLuint prevFbo = 0;
        glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, reinterpret_cast<GLint *>(&prevFbo));

        if (prevFbo != 0) {
            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        }

        GLenum colorEncoding = GL_LINEAR;
        glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_BACK_LEFT,
                                              GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING,
                                              reinterpret_cast<GLint *>(&colorEncoding));

        if (prevFbo != 0) {
            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prevFbo);
        }

        if (colorEncoding == GL_SRGB) {
            textureFormat = GL_SRGB8_ALPHA8;
        }
    }
138

139
    for (int i = 0; i <= m_downSampleIterations; i++) {
140
        m_renderTextures.append(GLTexture(textureFormat, effects->virtualScreenSize() / (1 << i)));
141
142
143
144
145
146
147
        m_renderTextures.last().setFilter(GL_LINEAR);
        m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE);

        m_renderTargets.append(new GLRenderTarget(m_renderTextures.last()));
    }

    // This last set is used as a temporary helper texture
148
    m_renderTextures.append(GLTexture(textureFormat, effects->virtualScreenSize()));
149
150
151
152
153
154
155
156
157
    m_renderTextures.last().setFilter(GL_LINEAR);
    m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE);

    m_renderTargets.append(new GLRenderTarget(m_renderTextures.last()));

    m_renderTargetsValid = renderTargetsValid();

    // Prepare the stack for the rendering
    m_renderTargetStack.clear();
Alex Nemeth's avatar
Alex Nemeth committed
158
    m_renderTargetStack.reserve(m_downSampleIterations * 2);
159
160
161
162
163
164
165
166
167
168
169
170
171

    // Upsample
    for (int i = 1; i < m_downSampleIterations; i++) {
        m_renderTargetStack.push(m_renderTargets[i]);
    }

    // Downsample
    for (int i = m_downSampleIterations; i > 0; i--) {
        m_renderTargetStack.push(m_renderTargets[i]);
    }

    // Copysample
    m_renderTargetStack.push(m_renderTargets[0]);
Alex Nemeth's avatar
Alex Nemeth committed
172
173
174

    // Generate the noise helper texture
    generateNoiseTexture();
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
}

void BlurEffect::initBlurStrengthValues()
{
    // This function creates an array of blur strength values that are evenly distributed

    // The range of the slider on the blur settings UI
    int numOfBlurSteps = 15;
    int remainingSteps = numOfBlurSteps;

    /*
     * Explanation for these numbers:
     *
     * The texture blur amount depends on the downsampling iterations and the offset value.
     * By changing the offset we can alter the blur amount without relying on further downsampling.
     * But there is a minimum and maximum value of offset per downsample iteration before we
     * get artifacts.
     *
     * The minOffset variable is the minimum offset value for an iteration before we
     * get blocky artifacts because of the downsampling.
     *
     * The maxOffset value is the maximum offset value for an iteration before we
     * get diagonal line artifacts because of the nature of the dual kawase blur algorithm.
     *
     * The expandSize value is the minimum value for an iteration before we reach the end
     * of a texture in the shader and sample outside of the area that was copied into the
     * texture from the screen.
     */

    // {minOffset, maxOffset, expandSize}
    blurOffsets.append({1.0, 2.0, 10});     // Down sample size / 2
    blurOffsets.append({2.0, 3.0, 20});     // Down sample size / 4
    blurOffsets.append({2.0, 5.0, 50});     // Down sample size / 8
    blurOffsets.append({3.0, 8.0, 150});    // Down sample size / 16
    //blurOffsets.append({5.0, 10.0, 400}); // Down sample size / 32
    //blurOffsets.append({7.0, ?.0});       // Down sample size / 64

    float offsetSum = 0;

    for (int i = 0; i < blurOffsets.size(); i++) {
        offsetSum += blurOffsets[i].maxOffset - blurOffsets[i].minOffset;
    }

    for (int i = 0; i < blurOffsets.size(); i++) {
        int iterationNumber = std::ceil((blurOffsets[i].maxOffset - blurOffsets[i].minOffset) / offsetSum * numOfBlurSteps);
        remainingSteps -= iterationNumber;

        if (remainingSteps < 0) {
            iterationNumber += remainingSteps;
        }

        float offsetDifference = blurOffsets[i].maxOffset - blurOffsets[i].minOffset;
227

228
229
230
231
232
        for (int j = 1; j <= iterationNumber; j++) {
            // {iteration, offset}
            blurStrengthValues.append({i + 1, blurOffsets[i].minOffset + (offsetDifference / iterationNumber) * j});
        }
    }
233
234
}

235
236
237
238
void BlurEffect::reconfigure(ReconfigureFlags flags)
{
    Q_UNUSED(flags)

239
    BlurConfig::self()->read();
240

241
242
243
244
    int blurStrength = BlurConfig::blurStrength() - 1;
    m_downSampleIterations = blurStrengthValues[blurStrength].iteration;
    m_offset = blurStrengthValues[blurStrength].offset;
    m_expandSize = blurOffsets[m_downSampleIterations - 1].expandSize;
Alex Nemeth's avatar
Alex Nemeth committed
245
246
    m_noiseStrength = BlurConfig::noiseStrength();

247
    m_scalingFactor = qMax(1.0, QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.0);
248

249
250
251
    updateTexture();

    if (!m_shader || !m_shader->isValid()) {
252
        effects->removeSupportProperty(s_blurAtomName, this);
253
254
255
        delete m_blurManager;
        m_blurManager = nullptr;
    }
256
257
258

    // Update all windows for the blur to take effect
    effects->addRepaintFull();
259
260
}

261
262
263
void BlurEffect::updateBlurRegion(EffectWindow *w) const
{
    QRegion region;
264
265
266
267
268
269
270
271
272
273
274
275
276
    QByteArray value;

    if (net_wm_blur_region != XCB_ATOM_NONE) {
        value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32);
        if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) {
            const uint32_t *cardinals = reinterpret_cast<const uint32_t*>(value.constData());
            for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) {
                int x = cardinals[i++];
                int y = cardinals[i++];
                int w = cardinals[i++];
                int h = cardinals[i++];
                region += QRect(x, y, w, h);
            }
277
278
279
        }
    }

280
    KWaylandServer::SurfaceInterface *surf = w->surface();
281
282
283
284
285

    if (surf && surf->blur()) {
        region = surf->blur()->region();
    }

286
287
288
289
290
291
292
    if (auto internal = w->internalWindow()) {
        const auto property = internal->property("kwin_blur");
        if (property.isValid()) {
            region = property.value<QRegion>();
        }
    }

293
294
295
    //!value.isNull() full window in X11 case, surf->blur()
    //valid, full window in wayland case
    if (region.isEmpty() && (!value.isNull() || (surf && surf->blur()))) {
296
297
298
        // Set the data to a dummy value.
        // This is needed to be able to distinguish between the value not
        // being set, and being set to an empty region.
299
        w->setData(WindowBlurBehindRole, 1);
300
    } else
301
        w->setData(WindowBlurBehindRole, region);
302
303
}

304
void BlurEffect::slotWindowAdded(EffectWindow *w)
305
{
306
    KWaylandServer::SurfaceInterface *surf = w->surface();
307
308

    if (surf) {
309
        windowBlurChangedConnections[w] = connect(surf, &KWaylandServer::SurfaceInterface::blurChanged, this, [this, w] () {
310
311
312
313
314
            if (w) {
                updateBlurRegion(w);
            }
        });
    }
315
316
317
    if (auto internal = w->internalWindow()) {
        internal->installEventFilter(this);
    }
318

319
320
321
    updateBlurRegion(w);
}

322
323
void BlurEffect::slotWindowDeleted(EffectWindow *w)
{
324
325
326
    auto it = windowBlurChangedConnections.find(w);
    if (it == windowBlurChangedConnections.end()) {
        return;
327
    }
328
329
    disconnect(*it);
    windowBlurChangedConnections.erase(it);
330
331
}

332
void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom)
333
{
334
    if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) {
335
        updateBlurRegion(w);
Philipp Knechtges's avatar
Philipp Knechtges committed
336
    }
337
338
}

339
340
341
342
343
344
345
346
347
348
349
350
351
352
bool BlurEffect::eventFilter(QObject *watched, QEvent *event)
{
    auto internal = qobject_cast<QWindow*>(watched);
    if (internal && event->type() == QEvent::DynamicPropertyChange) {
        QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent*>(event);
        if (pe->propertyName() == "kwin_blur") {
            if (auto w = effects->findWindow(internal)) {
                updateBlurRegion(w);
            }
        }
    }
    return false;
}

353
354
355
356
bool BlurEffect::enabledByDefault()
{
    GLPlatform *gl = GLPlatform::instance();

357
    if (gl->isIntel() && gl->chipClass() < SandyBridge)
358
        return false;
359
360
361
    if (gl->isSoftwareEmulation()) {
        return false;
    }
362
363
364
365

    return true;
}

366
367
bool BlurEffect::supported()
{
368
    bool supported = effects->isOpenGLCompositing() && GLRenderTarget::supported() && GLRenderTarget::blitSupported();
369
370
371
372
373

    if (supported) {
        int maxTexSize;
        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);

374
375
        const QSize screenSize = effects->virtualScreenSize();
        if (screenSize.width() > maxTexSize || screenSize.height() > maxTexSize)
376
377
            supported = false;
    }
378
    return supported;
379
380
381
382
}

QRect BlurEffect::expand(const QRect &rect) const
{
383
    return rect.adjusted(-m_expandSize, -m_expandSize, m_expandSize, m_expandSize);
384
385
386
387
388
389
}

QRegion BlurEffect::expand(const QRegion &region) const
{
    QRegion expanded;

390
    for (const QRect &rect : region) {
391
        expanded += expand(rect);
392
    }
393
394
395
396

    return expanded;
}

397
398
399
400
QRegion BlurEffect::blurRegion(const EffectWindow *w) const
{
    QRegion region;

401
    const QVariant value = w->data(WindowBlurBehindRole);
402
403
404
    if (value.isValid()) {
        const QRegion appRegion = qvariant_cast<QRegion>(value);
        if (!appRegion.isEmpty()) {
405
            if (w->decorationHasAlpha() && effects->decorationSupportsBlurBehind()) {
406
                region = w->shape() & w->rect();
407
                region -= w->decorationInnerRect();
408
409
410
            }
            region |= appRegion.translated(w->contentsRect().topLeft()) &
                      w->decorationInnerRect();
411
412
413
        } else {
            // An empty region means that the blur effect should be enabled
            // for the whole window.
414
            region = w->shape() & w->rect();
415
        }
416
    } else if (w->decorationHasAlpha() && effects->decorationSupportsBlurBehind()) {
417
418
        // If the client hasn't specified a blur region, we'll only enable
        // the effect behind the decoration.
419
        region = w->shape() & w->rect();
420
421
        region -= w->decorationInnerRect();
    }
422
423
424
425

    return region;
}

426
void BlurEffect::uploadRegion(QVector2D *&map, const QRegion &region, const int downSampleIterations)
427
{
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
    for (int i = 0; i <= downSampleIterations; i++) {
        const int divisionRatio = (1 << i);

        for (const QRect &r : region) {
            const QVector2D topLeft(     r.x() / divisionRatio,               r.y() / divisionRatio);
            const QVector2D topRight(   (r.x() + r.width()) / divisionRatio,  r.y() / divisionRatio);
            const QVector2D bottomLeft(  r.x() / divisionRatio,              (r.y() + r.height()) / divisionRatio);
            const QVector2D bottomRight((r.x() + r.width()) / divisionRatio, (r.y() + r.height()) / divisionRatio);

            // First triangle
            *(map++) = topRight;
            *(map++) = topLeft;
            *(map++) = bottomLeft;

            // Second triangle
            *(map++) = bottomLeft;
            *(map++) = bottomRight;
            *(map++) = topRight;
        }
447
    }
448
449
}

450
void BlurEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &blurRegion, const QRegion &windowRegion)
451
{
452
453
    const int vertexCount = ((blurRegion.rectCount() * (m_downSampleIterations + 1)) + windowRegion.rectCount()) * 6;

454
455
    if (!vertexCount)
        return;
456

457
    QVector2D *map = (QVector2D *) vbo->map(vertexCount * sizeof(QVector2D));
458
459
460
461

    uploadRegion(map, blurRegion, m_downSampleIterations);
    uploadRegion(map, windowRegion, 0);

462
463
464
465
466
467
468
469
    vbo->unmap();

    const GLVertexAttrib layout[] = {
        { VA_Position, 2, GL_FLOAT, 0 },
        { VA_TexCoord, 2, GL_FLOAT, 0 }
    };

    vbo->setAttribLayout(layout, 2, sizeof(QVector2D));
470
471
}

472
void BlurEffect::prePaintScreen(ScreenPrePaintData &data, int time)
473
{
474
    m_paintedArea = QRegion();
475
    m_currentBlur = QRegion();
476
477

    effects->prePaintScreen(data, time);
478
}
479

480
481
482
void BlurEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time)
{
    // this effect relies on prePaintWindow being called in the bottom to top order
483

484
    effects->prePaintWindow(w, data, time);
485

486
    if (!w->isPaintingEnabled()) {
487
488
        return;
    }
489
    if (!m_shader || !m_shader->isValid()) {
Martin Flöser's avatar
Martin Flöser committed
490
491
        return;
    }
492

493
494
495
    // to blur an area partially we have to shrink the opaque area of a window
    QRegion newClip;
    const QRegion oldClip = data.clip;
496
    for (const QRect &rect : data.clip) {
497
        newClip |= rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize);
498
499
500
    }
    data.clip = newClip;

Martin Flöser's avatar
Martin Flöser committed
501
    // we don't have to blur a region we don't see
502
503
    m_currentBlur -= newClip;
    // if we have to paint a non-opaque part of this window that intersects with the
504
505
    // currently blurred region we have to redraw the whole region
    if ((data.paint - oldClip).intersects(m_currentBlur)) {
506
507
508
509
        data.paint |= m_currentBlur;
    }

    // in case this window has regions to be blurred
510
    const QRect screen = effects->virtualScreenGeometry();
511
    const QRegion blurArea = blurRegion(w).translated(w->pos()) & screen;
512
513
514
515
516
517
518
519
520
521
    const QRegion expandedBlur = (w->isDock() ? blurArea : expand(blurArea)) & screen;

    // if this window or a window underneath the blurred area is painted again we have to
    // blur everything
    if (m_paintedArea.intersects(expandedBlur) || data.paint.intersects(blurArea)) {
        data.paint |= expandedBlur;
        // we have to check again whether we do not damage a blurred area
        // of a window
        if (expandedBlur.intersects(m_currentBlur)) {
            data.paint |= m_currentBlur;
522
523
524
        }
    }

525
526
    m_currentBlur |= expandedBlur;

527
528
    m_paintedArea -= data.clip;
    m_paintedArea |= data.paint;
529
530
}

531
bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const
532
{
533
    if (!m_renderTargetsValid || !m_shader || !m_shader->isValid())
534
535
536
537
538
539
540
541
        return false;

    if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool())
        return false;

    if (w->isDesktop())
        return false;

542
    bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0);
543
    bool translated = data.xTranslation() || data.yTranslation();
544

545
    if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBlurRole).toBool())
546
547
548
549
550
        return false;

    bool blurBehindDecos = effects->decorationsHaveAlpha() &&
                effects->decorationSupportsBlurBehind();

551
    if (!w->hasAlpha() && w->opacity() >= 1.0 && !(blurBehindDecos && w->hasDecoration()))
552
553
554
555
556
        return false;

    return true;
}

Albert Astals Cid's avatar
Albert Astals Cid committed
557
void BlurEffect::drawWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
558
{
559
    const QRect screen = GLRenderTarget::virtualScreenGeometry();
560
    if (shouldBlur(w, mask, data)) {
Martin Flöser's avatar
Martin Flöser committed
561
562
563
        QRegion shape = region & blurRegion(w).translated(w->pos()) & screen;

        // let's do the evil parts - someone wants to blur behind a transformed window
564
565
566
567
        const bool translated = data.xTranslation() || data.yTranslation();
        const bool scaled = data.xScale() != 1 || data.yScale() != 1;
        if (scaled) {
            QPoint pt = shape.boundingRect().topLeft();
568
569
            QRegion scaledShape;
            for (QRect r : shape) {
570
571
572
573
                r.moveTo(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(),
                            pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation());
                r.setWidth(r.width() * data.xScale());
                r.setHeight(r.height() * data.yScale());
574
                scaledShape |= r;
575
            }
576
            shape = scaledShape & region;
577
578
579

        //Only translated, not scaled
        } else if (translated) {
580
            shape = shape.translated(data.xTranslation(), data.yTranslation());
Martin Flöser's avatar
Martin Flöser committed
581
582
            shape = shape & region;
        }
583
584
585
        
        EffectWindow* modal = w->transientFor();
        const bool transientForIsDock = (modal ? modal->isDock() : false);
586

587
        if (!shape.isEmpty()) {
588
            doBlur(shape, screen, data.opacity(), data.screenProjectionMatrix(), w->isDock() || transientForIsDock, w->geometry());
589
        }
590
591
592
593
594
595
    }

    // Draw the window over the blurred area
    effects->drawWindow(w, mask, region, data);
}

Albert Astals Cid's avatar
Albert Astals Cid committed
596
void BlurEffect::paintEffectFrame(EffectFrame *frame, const QRegion &region, double opacity, double frameOpacity)
597
{
598
    const QRect screen = effects->virtualScreenGeometry();
599
600
601
602
    bool valid = m_renderTargetsValid && m_shader && m_shader->isValid();

    QRegion shape = frame->geometry().adjusted(-borderSize, -borderSize, borderSize, borderSize) & screen;

603
    if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) {
Alex Nemeth's avatar
Alex Nemeth committed
604
        doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix(), false, frame->geometry());
605
606
607
608
    }
    effects->paintEffectFrame(frame, region, opacity, frameOpacity);
}

Alex Nemeth's avatar
Alex Nemeth committed
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
void BlurEffect::generateNoiseTexture()
{
    if (m_noiseStrength == 0) {
        return;
    }

    // Init randomness based on time
    qsrand((uint)QTime::currentTime().msec());

    QImage noiseImage(QSize(256, 256), QImage::Format_Grayscale8);

    for (int y = 0; y < noiseImage.height(); y++) {
        uint8_t *noiseImageLine = (uint8_t *) noiseImage.scanLine(y);

        for (int x = 0; x < noiseImage.width(); x++) {
            noiseImageLine[x] = qrand() % m_noiseStrength + (128 - m_noiseStrength / 2);
        }
    }

    // The noise texture looks distorted when not scaled with integer
    noiseImage = noiseImage.scaled(noiseImage.size() * m_scalingFactor);

    m_noiseTexture = GLTexture(noiseImage);
    m_noiseTexture.setFilter(GL_NEAREST);
    m_noiseTexture.setWrapMode(GL_REPEAT);
}

void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock, QRect windowRect)
637
{
Alex Nemeth's avatar
Alex Nemeth committed
638
639
640
641
642
643
    // Blur would not render correctly on a secondary monitor because of wrong coordinates
    // BUG: 393723
    const int xTranslate = -screen.x();
    const int yTranslate = effects->virtualScreenSize().height() - screen.height() - screen.y();

    const QRegion expandedBlurRegion = expand(shape) & expand(screen);
644

645
    const bool useSRGB = m_renderTextures.first().internalFormat() == GL_SRGB8_ALPHA8;
646

647
    // Upload geometry for the down and upsample iterations
648
    GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
649
    vbo->reset();
Alex Nemeth's avatar
Alex Nemeth committed
650
651

    uploadGeometry(vbo, expandedBlurRegion.translated(xTranslate, yTranslate), shape);
652
653
    vbo->bindArrays();

Alex Nemeth's avatar
Alex Nemeth committed
654
655
656
657
658
659
    const QRect sourceRect = expandedBlurRegion.boundingRect() & screen;
    const QRect destRect = sourceRect.translated(xTranslate, yTranslate);

    GLRenderTarget::pushRenderTargets(m_renderTargetStack);
    int blurRectCount = expandedBlurRegion.rectCount() * 6;

660
661
662
663
664
665
666
667
    /*
     * If the window is a dock or panel we avoid the "extended blur" effect.
     * Extended blur is when windows that are not under the blurred area affect
     * the final blur result.
     * We want to avoid this on panels, because it looks really weird and ugly
     * when maximized windows or windows near the panel affect the dock blur.
     */
    if (isDock) {
Alex Nemeth's avatar
Alex Nemeth committed
668
        m_renderTargets.last()->blitFromFramebuffer(sourceRect, destRect);
669

670
        if (useSRGB) {
671
672
673
            glEnable(GL_FRAMEBUFFER_SRGB);
        }

Alex Nemeth's avatar
Alex Nemeth committed
674
        copyScreenSampleTexture(vbo, blurRectCount, shape.translated(xTranslate, yTranslate), screenProjection);
675
    } else {
Alex Nemeth's avatar
Alex Nemeth committed
676
677
        m_renderTargets.first()->blitFromFramebuffer(sourceRect, destRect);

678
        if (useSRGB) {
679
680
681
            glEnable(GL_FRAMEBUFFER_SRGB);
        }

682
683
684
        // Remove the m_renderTargets[0] from the top of the stack that we will not use
        GLRenderTarget::popRenderTarget();
    }
685

686
687
    downSampleTexture(vbo, blurRectCount);
    upSampleTexture(vbo, blurRectCount);
688
689
690
691

    // Modulate the blurred texture with the window opacity if the window isn't opaque
    if (opacity < 1.0) {
        glEnable(GL_BLEND);
692
693
694
695
696
697
698
699
#if 1 // bow shape, always above y = x
        float o = 1.0f-opacity;
        o = 1.0f - o*o;
#else // sigmoid shape, above y = x for x > 0.5, below y = x for x < 0.5
        float o = 2.0f*opacity - 1.0f;
        o = 0.5f + o / (1.0f + qAbs(o));
#endif
        glBlendColor(0, 0, 0, o);
700
701
702
        glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
    }

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
703
    upscaleRenderToScreen(vbo, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6, screenProjection, windowRect.topLeft());
704

705
    if (useSRGB) {
706
707
708
        glDisable(GL_FRAMEBUFFER_SRGB);
    }

Alex Nemeth's avatar
Alex Nemeth committed
709
710
711
    if (opacity < 1.0) {
        glDisable(GL_BLEND);
    }
712

Alex Nemeth's avatar
Alex Nemeth committed
713
714
715
    vbo->unbindArrays();
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
716
void BlurEffect::upscaleRenderToScreen(GLVertexBuffer *vbo, int vboStart, int blurRectCount, QMatrix4x4 screenProjection, QPoint windowPosition)
Alex Nemeth's avatar
Alex Nemeth committed
717
718
{
    glActiveTexture(GL_TEXTURE0);
719
720
    m_renderTextures[1].bind();

Alex Nemeth's avatar
Alex Nemeth committed
721
722
    if (m_noiseStrength > 0) {
        m_shader->bind(BlurShader::NoiseSampleType);
723
724
725
        m_shader->setTargetTextureSize(m_renderTextures[0].size() * GLRenderTarget::virtualScreenScale());
        m_shader->setNoiseTextureSize(m_noiseTexture.size() * GLRenderTarget::virtualScreenScale());
        m_shader->setTexturePosition(windowPosition * GLRenderTarget::virtualScreenScale());
726

Alex Nemeth's avatar
Alex Nemeth committed
727
728
729
730
        glActiveTexture(GL_TEXTURE1);
        m_noiseTexture.bind();
    } else {
        m_shader->bind(BlurShader::UpSampleType);
731
        m_shader->setTargetTextureSize(m_renderTextures[0].size() * GLRenderTarget::virtualScreenScale());
Martin Flöser's avatar
Martin Flöser committed
732
    }
733

Alex Nemeth's avatar
Alex Nemeth committed
734
735
736
737
738
739
740
    m_shader->setOffset(m_offset);
    m_shader->setModelViewProjectionMatrix(screenProjection);

    //Render to the screen
    vbo->draw(GL_TRIANGLES, vboStart, blurRectCount);

    glActiveTexture(GL_TEXTURE0);
741
    m_shader->unbind();
742
743
}

744
void BlurEffect::downSampleTexture(GLVertexBuffer *vbo, int blurRectCount)
745
746
747
{
    QMatrix4x4 modelViewProjectionMatrix;

748
749
    m_shader->bind(BlurShader::DownSampleType);
    m_shader->setOffset(m_offset);
750

751
752
753
    for (int i = 1; i <= m_downSampleIterations; i++) {
        modelViewProjectionMatrix.setToIdentity();
        modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535);
754

755
        m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix);
Alex Nemeth's avatar
Alex Nemeth committed
756
        m_shader->setTargetTextureSize(m_renderTextures[i].size());
757

758
759
        //Copy the image from this texture
        m_renderTextures[i - 1].bind();
760

761
762
763
        vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount);
        GLRenderTarget::popRenderTarget();
    }
764

765
766
    m_shader->unbind();
}
767

768
769
770
void BlurEffect::upSampleTexture(GLVertexBuffer *vbo, int blurRectCount)
{
    QMatrix4x4 modelViewProjectionMatrix;
771

772
773
    m_shader->bind(BlurShader::UpSampleType);
    m_shader->setOffset(m_offset);
774

Alex Nemeth's avatar
Alex Nemeth committed
775
    for (int i = m_downSampleIterations - 1; i >= 1; i--) {
776
777
        modelViewProjectionMatrix.setToIdentity();
        modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535);
778

779
        m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix);
Alex Nemeth's avatar
Alex Nemeth committed
780
        m_shader->setTargetTextureSize(m_renderTextures[i].size());
781

782
783
        //Copy the image from this texture
        m_renderTextures[i + 1].bind();
784

785
        vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount);
786
787
788
        GLRenderTarget::popRenderTarget();
    }

789
790
    m_shader->unbind();
}
791

Alex Nemeth's avatar
Alex Nemeth committed
792
void BlurEffect::copyScreenSampleTexture(GLVertexBuffer *vbo, int blurRectCount, QRegion blurShape, QMatrix4x4 screenProjection)
793
794
{
    m_shader->bind(BlurShader::CopySampleType);
795

796
    m_shader->setModelViewProjectionMatrix(screenProjection);
Alex Nemeth's avatar
Alex Nemeth committed
797
    m_shader->setTargetTextureSize(effects->virtualScreenSize());
798

799
800
801
802
    /*
     * This '1' sized adjustment is necessary do avoid windows affecting the blur that are
     * right next to this window.
     */
Alex Nemeth's avatar
Alex Nemeth committed
803
804
    m_shader->setBlurRect(blurShape.boundingRect().adjusted(1, 1, -1, -1), effects->virtualScreenSize());
    m_renderTextures.last().bind();
805

806
807
    vbo->draw(GL_TRIANGLES, 0, blurRectCount);
    GLRenderTarget::popRenderTarget();
808

809
    m_shader->unbind();
810
811
}

812
813
814
815
816
bool BlurEffect::isActive() const
{
    return !effects->isScreenLocked();
}

817
818
} // namespace KWin