~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to kwin/effects/blur/blur.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *   Copyright © 2010 Fredrik Höglund <fredrik@kde.org>
 
3
 *
 
4
 *   This program is free software; you can redistribute it and/or modify
 
5
 *   it under the terms of the GNU General Public License as published by
 
6
 *   the Free Software Foundation; either version 2 of the License, or
 
7
 *   (at your option) any later version.
 
8
 *
 
9
 *   This program is distributed in the hope that it will be useful,
 
10
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
12
 *   General Public License for more details.
 
13
 *
 
14
 *   You should have received a copy of the GNU General Public License
 
15
 *   along with this program; see the file COPYING.  if not, write to
 
16
 *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
17
 *   Boston, MA 02110-1301, USA.
 
18
 */
 
19
 
 
20
#include "blur.h"
 
21
#include "blurshader.h"
 
22
 
 
23
#include <X11/Xatom.h>
 
24
 
 
25
#include <QMatrix4x4>
 
26
#include <QLinkedList>
 
27
#include <KConfigGroup>
 
28
#include <KDebug>
 
29
 
 
30
namespace KWin
 
31
{
 
32
 
 
33
KWIN_EFFECT(blur, BlurEffect)
 
34
KWIN_EFFECT_SUPPORTED(blur, BlurEffect::supported())
 
35
KWIN_EFFECT_ENABLEDBYDEFAULT(blur, BlurEffect::enabledByDefault())
 
36
 
 
37
 
 
38
BlurEffect::BlurEffect()
 
39
{
 
40
    shader = BlurShader::create();
 
41
 
 
42
    // Offscreen texture that's used as the target for the horizontal blur pass
 
43
    // and the source for the vertical pass.
 
44
    tex = new GLTexture(displayWidth(), displayHeight());
 
45
    tex->setFilter(GL_LINEAR);
 
46
    tex->setWrapMode(GL_CLAMP_TO_EDGE);
 
47
 
 
48
    target = new GLRenderTarget(tex);
 
49
 
 
50
    net_wm_blur_region = XInternAtom(display(), "_KDE_NET_WM_BLUR_BEHIND_REGION", False);
 
51
    effects->registerPropertyType(net_wm_blur_region, true);
 
52
 
 
53
    reconfigure(ReconfigureAll);
 
54
 
 
55
    // ### Hackish way to announce support.
 
56
    //     Should be included in _NET_SUPPORTED instead.
 
57
    if (shader->isValid() && target->valid()) {
 
58
        XChangeProperty(display(), rootWindow(), net_wm_blur_region, net_wm_blur_region,
 
59
                        32, PropModeReplace, 0, 0);
 
60
    } else {
 
61
        XDeleteProperty(display(), rootWindow(), net_wm_blur_region);
 
62
    }
 
63
    connect(effects, SIGNAL(windowAdded(EffectWindow*)), this, SLOT(slotWindowAdded(EffectWindow*)));
 
64
    connect(effects, SIGNAL(propertyNotify(EffectWindow*,long)), this, SLOT(slotPropertyNotify(EffectWindow*,long)));
 
65
}
 
66
 
 
67
BlurEffect::~BlurEffect()
 
68
{
 
69
    effects->registerPropertyType(net_wm_blur_region, false);
 
70
    XDeleteProperty(display(), rootWindow(), net_wm_blur_region);
 
71
 
 
72
    delete shader;
 
73
    delete target;
 
74
    delete tex;
 
75
}
 
76
 
 
77
void BlurEffect::reconfigure(ReconfigureFlags flags)
 
78
{
 
79
    Q_UNUSED(flags)
 
80
 
 
81
    KConfigGroup cg = EffectsHandler::effectConfig("Blur");
 
82
    int radius = qBound(2, cg.readEntry("BlurRadius", 12), 14);
 
83
    shader->setRadius(radius);
 
84
 
 
85
    if (!shader->isValid())
 
86
        XDeleteProperty(display(), rootWindow(), net_wm_blur_region);
 
87
}
 
88
 
 
89
void BlurEffect::updateBlurRegion(EffectWindow *w) const
 
90
{
 
91
    QRegion region;
 
92
 
 
93
    const QByteArray value = w->readProperty(net_wm_blur_region, XA_CARDINAL, 32);
 
94
    if (value.size() > 0 && !(value.size() % (4 * sizeof(unsigned long)))) {
 
95
        const unsigned long *cardinals = reinterpret_cast<const unsigned long*>(value.constData());
 
96
        for (unsigned int i = 0; i < value.size() / sizeof(unsigned long);) {
 
97
            int x = cardinals[i++];
 
98
            int y = cardinals[i++];
 
99
            int w = cardinals[i++];
 
100
            int h = cardinals[i++];
 
101
            region += QRect(x, y, w, h);
 
102
        }
 
103
    }
 
104
 
 
105
    if (region.isEmpty() && !value.isNull()) {
 
106
        // Set the data to a dummy value.
 
107
        // This is needed to be able to distinguish between the value not
 
108
        // being set, and being set to an empty region.
 
109
        w->setData(WindowBlurBehindRole, 1);
 
110
    } else
 
111
        w->setData(WindowBlurBehindRole, region);
 
112
}
 
113
 
 
114
void BlurEffect::slotWindowAdded(EffectWindow *w)
 
115
{
 
116
    updateBlurRegion(w);
 
117
}
 
118
 
 
119
void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom)
 
120
{
 
121
    if (w && atom == net_wm_blur_region)
 
122
        updateBlurRegion(w);
 
123
}
 
124
 
 
125
bool BlurEffect::enabledByDefault()
 
126
{
 
127
    GLPlatform *gl = GLPlatform::instance();
 
128
 
 
129
    if (gl->isIntel())
 
130
        return false;
 
131
 
 
132
    return true;
 
133
}
 
134
 
 
135
bool BlurEffect::supported()
 
136
{
 
137
    bool supported = GLRenderTarget::supported() && GLTexture::NPOTTextureSupported() &&
 
138
                     (GLSLBlurShader::supported() || ARBBlurShader::supported());
 
139
 
 
140
    if (supported) {
 
141
        int maxTexSize;
 
142
        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
 
143
 
 
144
        if (displayWidth() > maxTexSize || displayHeight() > maxTexSize)
 
145
            supported = false;
 
146
    }
 
147
    return supported;
 
148
}
 
149
 
 
150
QRect BlurEffect::expand(const QRect &rect) const
 
151
{
 
152
    const int radius = shader->radius();
 
153
    return rect.adjusted(-radius, -radius, radius, radius);
 
154
}
 
155
 
 
156
QRegion BlurEffect::expand(const QRegion &region) const
 
157
{
 
158
    QRegion expanded;
 
159
 
 
160
    if (region.rectCount() < 20) {
 
161
        foreach (const QRect & rect, region.rects())
 
162
        expanded += expand(rect);
 
163
    } else
 
164
        expanded += expand(region.boundingRect());
 
165
 
 
166
    return expanded;
 
167
}
 
168
 
 
169
QRegion BlurEffect::blurRegion(const EffectWindow *w) const
 
170
{
 
171
    QRegion region;
 
172
 
 
173
    const QVariant value = w->data(WindowBlurBehindRole);
 
174
    if (value.isValid()) {
 
175
        const QRegion appRegion = qvariant_cast<QRegion>(value);
 
176
        if (!appRegion.isEmpty()) {
 
177
            if (w->hasDecoration() && effects->decorationSupportsBlurBehind()) {
 
178
                region = w->shape();
 
179
                region -= w->decorationInnerRect();
 
180
                region |= appRegion.translated(w->contentsRect().topLeft()) &
 
181
                          w->contentsRect();
 
182
            } else
 
183
                region = appRegion & w->contentsRect();
 
184
        } else {
 
185
            // An empty region means that the blur effect should be enabled
 
186
            // for the whole window.
 
187
            region = w->shape();
 
188
        }
 
189
    } else if (w->hasDecoration() && effects->decorationSupportsBlurBehind()) {
 
190
        // If the client hasn't specified a blur region, we'll only enable
 
191
        // the effect behind the decoration.
 
192
        region = w->shape();
 
193
        region -= w->decorationInnerRect();
 
194
    }
 
195
 
 
196
    return region;
 
197
}
 
198
 
 
199
void BlurEffect::drawRegion(const QRegion &region)
 
200
{
 
201
    const int vertexCount = region.rectCount() * 6;
 
202
    if (vertices.size() < vertexCount)
 
203
        vertices.resize(vertexCount);
 
204
 
 
205
    int i = 0;
 
206
    foreach (const QRect & r, region.rects()) {
 
207
        vertices[i++] = QVector2D(r.x() + r.width(), r.y());
 
208
        vertices[i++] = QVector2D(r.x(),             r.y());
 
209
        vertices[i++] = QVector2D(r.x(),             r.y() + r.height());
 
210
        vertices[i++] = QVector2D(r.x(),             r.y() + r.height());
 
211
        vertices[i++] = QVector2D(r.x() + r.width(), r.y() + r.height());
 
212
        vertices[i++] = QVector2D(r.x() + r.width(), r.y());
 
213
    }
 
214
    GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
 
215
    vbo->reset();
 
216
    vbo->setData(vertexCount, 2, (float*)vertices.constData(), (float*)vertices.constData());
 
217
    vbo->render(GL_TRIANGLES);
 
218
}
 
219
 
 
220
void BlurEffect::prePaintScreen(ScreenPrePaintData &data, int time)
 
221
{
 
222
    EffectWindowList windows = effects->stackingOrder();
 
223
    QLinkedList<QRegion> blurRegions;
 
224
    bool checkDecos = effects->decorationsHaveAlpha() && effects->decorationSupportsBlurBehind();
 
225
    bool clipChanged = false;
 
226
 
 
227
    effects->prePaintScreen(data, time);
 
228
 
 
229
    // If the whole screen will be repainted anyway, there is no point in
 
230
    // adding to the paint region.
 
231
    if (data.mask & (PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS | PAINT_SCREEN_TRANSFORMED))
 
232
        return;
 
233
 
 
234
    if (effects->activeFullScreenEffect())
 
235
        return;
 
236
 
 
237
    // Walk the window list top->bottom and check if the paint region is fully
 
238
    // contained within opaque windows and doesn't intersect any blurred region.
 
239
    QRegion paint = data.paint;
 
240
    for (int i = windows.count() - 1; i >= 0; --i) {
 
241
        EffectWindow *window = windows.at(i);
 
242
        if (!window->isPaintingEnabled())
 
243
            continue;
 
244
 
 
245
        if (!window->hasAlpha()) {
 
246
            paint -= window->contentsRect().translated(window->pos());
 
247
            if (paint.isEmpty())
 
248
                break;
 
249
        }
 
250
 
 
251
        // The window decoration is treated as an object below the window
 
252
        // contents, so check it after the contents.
 
253
        if (window->hasAlpha() || (checkDecos && window->hasDecoration())) {
 
254
            QRegion r = blurRegion(window);
 
255
            if (r.isEmpty())
 
256
                continue;
 
257
 
 
258
            r = expand(r.translated(window->pos()));
 
259
            if (r.intersects(paint))
 
260
                break;
 
261
        }
 
262
    }
 
263
 
 
264
    if (paint.isEmpty())
 
265
        return;
 
266
 
 
267
    // Walk the list again bottom->top and expand the paint region when
 
268
    // it intersects a blurred region.
 
269
    foreach (EffectWindow *window, windows) {
 
270
        if (!window->isPaintingEnabled())
 
271
            continue;
 
272
 
 
273
        if (!window->hasAlpha() && !(checkDecos && window->hasDecoration()))
 
274
            continue;
 
275
 
 
276
        QRegion r = blurRegion(window);
 
277
        if (r.isEmpty())
 
278
            continue;
 
279
 
 
280
        r = expand(r.translated(window->pos()));
 
281
 
 
282
        // We can't do a partial repaint of a blurred region
 
283
        if (r.intersects(data.paint)) {
 
284
            data.paint += r;
 
285
            clipChanged = true;
 
286
        } else
 
287
            blurRegions.append(r);
 
288
    }
 
289
 
 
290
    while (clipChanged) {
 
291
        clipChanged = false;
 
292
        QMutableLinkedListIterator<QRegion> i(blurRegions);
 
293
        while (i.hasNext()) {
 
294
            const QRegion r = i.next();
 
295
            if (!r.intersects(data.paint))
 
296
                continue;
 
297
 
 
298
            data.paint += r;
 
299
            clipChanged = true;
 
300
            i.remove();
 
301
        }
 
302
    }
 
303
 
 
304
    // Force the scene to call paintGenericScreen() so the windows are painted bottom -> top
 
305
    data.mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_WITHOUT_FULL_REPAINTS;
 
306
}
 
307
 
 
308
bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const
 
309
{
 
310
    if (!target->valid() || !shader->isValid())
 
311
        return false;
 
312
 
 
313
    // Don't blur anything if we're painting top-to-bottom
 
314
    if (!(mask & (PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_WITHOUT_FULL_REPAINTS |
 
315
                  PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS |
 
316
                  PAINT_SCREEN_TRANSFORMED)))
 
317
        return false;
 
318
 
 
319
    if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool())
 
320
        return false;
 
321
 
 
322
    if (w->isDesktop())
 
323
        return false;
 
324
 
 
325
    bool scaled = !qFuzzyCompare(data.xScale, 1.0) && !qFuzzyCompare(data.yScale, 1.0);
 
326
    bool translated = data.xTranslate || data.yTranslate;
 
327
 
 
328
    if (scaled || translated || (mask & PAINT_WINDOW_TRANSFORMED))
 
329
        return false;
 
330
 
 
331
    bool blurBehindDecos = effects->decorationsHaveAlpha() &&
 
332
                effects->decorationSupportsBlurBehind();
 
333
 
 
334
    if (!w->hasAlpha() && !(blurBehindDecos && w->hasDecoration()))
 
335
        return false;
 
336
 
 
337
    return true;
 
338
}
 
339
 
 
340
void BlurEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
 
341
{
 
342
    if (shouldBlur(w, mask, data)) {
 
343
        const QRect screen(0, 0, displayWidth(), displayHeight());
 
344
        const QRegion shape = blurRegion(w).translated(w->pos()) & screen;
 
345
 
 
346
        if (!shape.isEmpty() && region.intersects(shape.boundingRect()))
 
347
            doBlur(shape, screen, data.opacity * data.contents_opacity);
 
348
    }
 
349
 
 
350
    // Draw the window over the blurred area
 
351
    effects->drawWindow(w, mask, region, data);
 
352
}
 
353
 
 
354
void BlurEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity)
 
355
{
 
356
    const QRect screen(0, 0, displayWidth(), displayHeight());
 
357
    bool valid = target->valid() && shader->isValid();
 
358
    QRegion shape = frame->geometry().adjusted(-5, -5, 5, 5) & screen;
 
359
    if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) {
 
360
        doBlur(shape, screen, opacity * frameOpacity);
 
361
    }
 
362
    effects->paintEffectFrame(frame, region, opacity, frameOpacity);
 
363
}
 
364
 
 
365
void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity)
 
366
{
 
367
    const QRegion expanded = expand(shape) & screen;
 
368
    const QRect r = expanded.boundingRect();
 
369
 
 
370
    // Create a scratch texture and copy the area in the back buffer that we're
 
371
    // going to blur into it
 
372
    GLTexture scratch(r.width(), r.height());
 
373
    scratch.setFilter(GL_LINEAR);
 
374
    scratch.setWrapMode(GL_CLAMP_TO_EDGE);
 
375
    scratch.bind();
 
376
 
 
377
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r.x(), displayHeight() - r.y() - r.height(),
 
378
                        r.width(), r.height());
 
379
 
 
380
    // Draw the texture on the offscreen framebuffer object, while blurring it horizontally
 
381
    GLRenderTarget::pushRenderTarget(target);
 
382
 
 
383
    shader->bind();
 
384
    shader->setDirection(Qt::Horizontal);
 
385
    shader->setPixelDistance(1.0 / r.width());
 
386
 
 
387
    // Set up the texture matrix to transform from screen coordinates
 
388
    // to texture coordinates.
 
389
#ifndef KWIN_HAVE_OPENGLES
 
390
    glMatrixMode(GL_TEXTURE);
 
391
#endif
 
392
    pushMatrix();
 
393
    QMatrix4x4 textureMatrix;
 
394
    textureMatrix.scale(1.0 / scratch.width(), -1.0 / scratch.height(), 1);
 
395
    textureMatrix.translate(-r.x(), -scratch.height() - r.y(), 0);
 
396
    loadMatrix(textureMatrix);
 
397
    shader->setTextureMatrix(textureMatrix);
 
398
 
 
399
    drawRegion(expanded);
 
400
 
 
401
    GLRenderTarget::popRenderTarget();
 
402
    scratch.unbind();
 
403
    scratch.discard();
 
404
 
 
405
    // Now draw the horizontally blurred area back to the backbuffer, while
 
406
    // blurring it vertically and clipping it to the window shape.
 
407
    tex->bind();
 
408
 
 
409
    shader->setDirection(Qt::Vertical);
 
410
    shader->setPixelDistance(1.0 / tex->height());
 
411
 
 
412
    // Modulate the blurred texture with the window opacity if the window isn't opaque
 
413
    if (opacity < 1.0) {
 
414
#ifndef KWIN_HAVE_OPENGLES
 
415
        glPushAttrib(GL_COLOR_BUFFER_BIT);
 
416
#endif
 
417
        glEnable(GL_BLEND);
 
418
        glBlendColor(0, 0, 0, opacity);
 
419
        glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
 
420
    }
 
421
 
 
422
    // Set the up the texture matrix to transform from screen coordinates
 
423
    // to texture coordinates.
 
424
    textureMatrix.setToIdentity();
 
425
    textureMatrix.scale(1.0 / tex->width(), -1.0 / tex->height(), 1);
 
426
    textureMatrix.translate(0, -tex->height(), 0);
 
427
    loadMatrix(textureMatrix);
 
428
    shader->setTextureMatrix(textureMatrix);
 
429
 
 
430
    drawRegion(shape);
 
431
 
 
432
    popMatrix();
 
433
#ifndef KWIN_HAVE_OPENGLES
 
434
    glMatrixMode(GL_MODELVIEW);
 
435
#endif
 
436
 
 
437
    if (opacity < 1.0) {
 
438
        glDisable(GL_BLEND);
 
439
#ifndef KWIN_HAVE_OPENGLES
 
440
        glPopAttrib();
 
441
#endif
 
442
    }
 
443
 
 
444
    tex->unbind();
 
445
    shader->unbind();
 
446
}
 
447
 
 
448
} // namespace KWin
 
449