2
* Copyright © 2010 Fredrik Höglund <fredrik@kde.org>
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.
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.
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.
21
#include "blurshader.h"
23
#include <X11/Xatom.h>
26
#include <QLinkedList>
27
#include <KConfigGroup>
33
KWIN_EFFECT(blur, BlurEffect)
34
KWIN_EFFECT_SUPPORTED(blur, BlurEffect::supported())
35
KWIN_EFFECT_ENABLEDBYDEFAULT(blur, BlurEffect::enabledByDefault())
38
BlurEffect::BlurEffect()
40
shader = BlurShader::create();
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);
48
target = new GLRenderTarget(tex);
50
net_wm_blur_region = XInternAtom(display(), "_KDE_NET_WM_BLUR_BEHIND_REGION", False);
51
effects->registerPropertyType(net_wm_blur_region, true);
53
reconfigure(ReconfigureAll);
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);
61
XDeleteProperty(display(), rootWindow(), net_wm_blur_region);
63
connect(effects, SIGNAL(windowAdded(EffectWindow*)), this, SLOT(slotWindowAdded(EffectWindow*)));
64
connect(effects, SIGNAL(propertyNotify(EffectWindow*,long)), this, SLOT(slotPropertyNotify(EffectWindow*,long)));
67
BlurEffect::~BlurEffect()
69
effects->registerPropertyType(net_wm_blur_region, false);
70
XDeleteProperty(display(), rootWindow(), net_wm_blur_region);
77
void BlurEffect::reconfigure(ReconfigureFlags flags)
81
KConfigGroup cg = EffectsHandler::effectConfig("Blur");
82
int radius = qBound(2, cg.readEntry("BlurRadius", 12), 14);
83
shader->setRadius(radius);
85
if (!shader->isValid())
86
XDeleteProperty(display(), rootWindow(), net_wm_blur_region);
89
void BlurEffect::updateBlurRegion(EffectWindow *w) const
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);
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);
111
w->setData(WindowBlurBehindRole, region);
114
void BlurEffect::slotWindowAdded(EffectWindow *w)
119
void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom)
121
if (w && atom == net_wm_blur_region)
125
bool BlurEffect::enabledByDefault()
127
GLPlatform *gl = GLPlatform::instance();
135
bool BlurEffect::supported()
137
bool supported = GLRenderTarget::supported() && GLTexture::NPOTTextureSupported() &&
138
(GLSLBlurShader::supported() || ARBBlurShader::supported());
142
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
144
if (displayWidth() > maxTexSize || displayHeight() > maxTexSize)
150
QRect BlurEffect::expand(const QRect &rect) const
152
const int radius = shader->radius();
153
return rect.adjusted(-radius, -radius, radius, radius);
156
QRegion BlurEffect::expand(const QRegion ®ion) const
160
if (region.rectCount() < 20) {
161
foreach (const QRect & rect, region.rects())
162
expanded += expand(rect);
164
expanded += expand(region.boundingRect());
169
QRegion BlurEffect::blurRegion(const EffectWindow *w) const
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()) {
179
region -= w->decorationInnerRect();
180
region |= appRegion.translated(w->contentsRect().topLeft()) &
183
region = appRegion & w->contentsRect();
185
// An empty region means that the blur effect should be enabled
186
// for the whole window.
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.
193
region -= w->decorationInnerRect();
199
void BlurEffect::drawRegion(const QRegion ®ion)
201
const int vertexCount = region.rectCount() * 6;
202
if (vertices.size() < vertexCount)
203
vertices.resize(vertexCount);
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());
214
GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
216
vbo->setData(vertexCount, 2, (float*)vertices.constData(), (float*)vertices.constData());
217
vbo->render(GL_TRIANGLES);
220
void BlurEffect::prePaintScreen(ScreenPrePaintData &data, int time)
222
EffectWindowList windows = effects->stackingOrder();
223
QLinkedList<QRegion> blurRegions;
224
bool checkDecos = effects->decorationsHaveAlpha() && effects->decorationSupportsBlurBehind();
225
bool clipChanged = false;
227
effects->prePaintScreen(data, time);
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))
234
if (effects->activeFullScreenEffect())
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())
245
if (!window->hasAlpha()) {
246
paint -= window->contentsRect().translated(window->pos());
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);
258
r = expand(r.translated(window->pos()));
259
if (r.intersects(paint))
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())
273
if (!window->hasAlpha() && !(checkDecos && window->hasDecoration()))
276
QRegion r = blurRegion(window);
280
r = expand(r.translated(window->pos()));
282
// We can't do a partial repaint of a blurred region
283
if (r.intersects(data.paint)) {
287
blurRegions.append(r);
290
while (clipChanged) {
292
QMutableLinkedListIterator<QRegion> i(blurRegions);
293
while (i.hasNext()) {
294
const QRegion r = i.next();
295
if (!r.intersects(data.paint))
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;
308
bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const
310
if (!target->valid() || !shader->isValid())
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)))
319
if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool())
325
bool scaled = !qFuzzyCompare(data.xScale, 1.0) && !qFuzzyCompare(data.yScale, 1.0);
326
bool translated = data.xTranslate || data.yTranslate;
328
if (scaled || translated || (mask & PAINT_WINDOW_TRANSFORMED))
331
bool blurBehindDecos = effects->decorationsHaveAlpha() &&
332
effects->decorationSupportsBlurBehind();
334
if (!w->hasAlpha() && !(blurBehindDecos && w->hasDecoration()))
340
void BlurEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
342
if (shouldBlur(w, mask, data)) {
343
const QRect screen(0, 0, displayWidth(), displayHeight());
344
const QRegion shape = blurRegion(w).translated(w->pos()) & screen;
346
if (!shape.isEmpty() && region.intersects(shape.boundingRect()))
347
doBlur(shape, screen, data.opacity * data.contents_opacity);
350
// Draw the window over the blurred area
351
effects->drawWindow(w, mask, region, data);
354
void BlurEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity)
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);
362
effects->paintEffectFrame(frame, region, opacity, frameOpacity);
365
void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity)
367
const QRegion expanded = expand(shape) & screen;
368
const QRect r = expanded.boundingRect();
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);
377
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r.x(), displayHeight() - r.y() - r.height(),
378
r.width(), r.height());
380
// Draw the texture on the offscreen framebuffer object, while blurring it horizontally
381
GLRenderTarget::pushRenderTarget(target);
384
shader->setDirection(Qt::Horizontal);
385
shader->setPixelDistance(1.0 / r.width());
387
// Set up the texture matrix to transform from screen coordinates
388
// to texture coordinates.
389
#ifndef KWIN_HAVE_OPENGLES
390
glMatrixMode(GL_TEXTURE);
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);
399
drawRegion(expanded);
401
GLRenderTarget::popRenderTarget();
405
// Now draw the horizontally blurred area back to the backbuffer, while
406
// blurring it vertically and clipping it to the window shape.
409
shader->setDirection(Qt::Vertical);
410
shader->setPixelDistance(1.0 / tex->height());
412
// Modulate the blurred texture with the window opacity if the window isn't opaque
414
#ifndef KWIN_HAVE_OPENGLES
415
glPushAttrib(GL_COLOR_BUFFER_BIT);
418
glBlendColor(0, 0, 0, opacity);
419
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
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);
433
#ifndef KWIN_HAVE_OPENGLES
434
glMatrixMode(GL_MODELVIEW);
439
#ifndef KWIN_HAVE_OPENGLES