diff --git a/effects/blur/CMakeLists.txt b/effects/blur/CMakeLists.txt new file mode 100644 index 0000000000..c0cf73bfe1 --- /dev/null +++ b/effects/blur/CMakeLists.txt @@ -0,0 +1,14 @@ +####################################### +# Effect + +# Source files +set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} + blur/blur.cpp + blur/blurshader.cpp + ) + +# .desktop files +install( FILES + blur/blur.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) + diff --git a/effects/blur/blur.cpp b/effects/blur/blur.cpp new file mode 100644 index 0000000000..befe15dc52 --- /dev/null +++ b/effects/blur/blur.cpp @@ -0,0 +1,200 @@ +/* + * Copyright © 2010 Fredrik Höglund + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. if not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "blur.h" +#include "blurshader.h" + + +namespace KWin +{ + +KWIN_EFFECT(blur, BlurEffect) +KWIN_EFFECT_SUPPORTED(blur, BlurEffect::supported()) + + +BlurEffect::BlurEffect() + : radius(12) +{ + shader = new BlurShader; + shader->setRadius(radius); + + // Offscreen texture that's used as the target for the horizontal blur pass + // and the source for the vertical pass. + tex = new GLTexture(displayWidth(), displayHeight()); + tex->setFilter(GL_LINEAR); + tex->setWrapMode(GL_CLAMP_TO_EDGE); + + target = new GLRenderTarget(tex); +} + +BlurEffect::~BlurEffect() +{ + delete shader; + delete target; + delete tex; +} + +bool BlurEffect::supported() +{ + return GLRenderTarget::supported() && GLTexture::NPOTTextureSupported() && + hasGLExtension("GL_ARB_fragment_program"); +} + +QRect BlurEffect::expand(const QRect &rect) const +{ + return rect.adjusted(-radius, -radius, radius, radius); +} + +QRegion BlurEffect::expand(const QRegion ®ion) const +{ + QRegion expanded; + + if (region.rectCount() < 10) { + foreach (const QRect &rect, region.rects()) + expanded += expand(rect); + } else + expanded += expand(region.boundingRect()); + + return expanded; +} + +void BlurEffect::paintScreen(int mask, QRegion region, ScreenPaintData &data) +{ + // Force the scene to call paintGenericScreen() so the windows are painted bottom -> top + if (!effects->activeFullScreenEffect()) + mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + + effects->paintScreen(mask, region, data); +} + +void BlurEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) +{ + bool scaled = !qFuzzyCompare(data.xScale, 1.0) && !qFuzzyCompare(data.yScale, 1.0); + bool translated = data.xTranslate || data.yTranslate; + bool hasAlpha = w->hasAlpha() || (w->hasDecoration() && effects->decorationsHaveAlpha()); + + if (!effects->activeFullScreenEffect() && hasAlpha && !w->isDesktop() && + !scaled && !translated /* && region.intersects(w->geometry())*/) + { + const QRect screen(0, 0, displayWidth(), displayHeight()); + const QRegion shape = w->shape().translated(w->geometry().topLeft()) & screen; + const QRect r = expand(shape.boundingRect()) & screen; + const QPoint offset = -shape.boundingRect().topLeft() + + (shape.boundingRect().topLeft() - r.topLeft()); + + // Create a scratch texture and copy the area in the back buffer that we're + // going to blur into it + GLTexture scratch(r.width(), r.height()); + scratch.setFilter(GL_LINEAR); + scratch.setWrapMode(GL_CLAMP_TO_EDGE); + scratch.bind(); + + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r.x(), displayHeight() - r.y() - r.height(), + r.width(), r.height()); + + // Draw the texture on the offscreen framebuffer object, while blurring it horizontally + effects->pushRenderTarget(target); + + shader->bind(); + shader->setDirection(Qt::Horizontal); + shader->setPixelDistance(1.0 / r.width()); + shader->setOpacity(1.0); + + glBegin(GL_QUADS); + glTexCoord2f(0, 1); glVertex2i(0, 0); + glTexCoord2f(1, 1); glVertex2i(r.width(), 0); + glTexCoord2f(1, 0); glVertex2i(r.width(), r.height()); + glTexCoord2f(0, 0); glVertex2i(0, r.height()); + glEnd(); + + effects->popRenderTarget(); + scratch.unbind(); + scratch.discard(); + + // Now draw the horizontally blurred area back to the backbuffer, while + // blurring it vertically and clipping it to the window shape. + tex->bind(); + + shader->setDirection(Qt::Vertical); + shader->setPixelDistance(1.0 / tex->height()); + + // Modulate the blurred texture with the window opacity if the window isn't opaque + const float opacity = data.opacity * data.contents_opacity; + if (opacity < 1.0) { + shader->setOpacity(opacity); + + glPushAttrib(GL_COLOR_BUFFER_BIT); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } + + const float tw = tex->width(); + const float th = tex->height(); + int vertexCount = shape.rectCount() * 4; + + if (vertices.size() < vertexCount) { + vertices.resize(vertexCount); + texCoords.resize(vertexCount); + } + + int i = 0; + foreach (const QRect &r, shape.rects()) { + vertices[i + 0] = QVector2D(r.x(), r.y()); + vertices[i + 1] = QVector2D(r.x() + r.width(), r.y()); + vertices[i + 2] = QVector2D(r.x() + r.width(), r.y() + r.height()); + vertices[i + 3] = QVector2D(r.x(), r.y() + r.height()); + + const QRect sr = r.translated(offset); + texCoords[i + 0] = QVector2D(sr.x() / tw, 1 - sr.y() / th); + texCoords[i + 1] = QVector2D((sr.x() + sr.width()) / tw, 1 - sr.y() / th); + texCoords[i + 2] = QVector2D((sr.x() + sr.width()) / tw, 1 - (sr.y() + sr.height()) / th); + texCoords[i + 3] = QVector2D(sr.x() / tw, 1 - (sr.y() + sr.height()) / th); + i += 4; + } + + if (vertexCount > 1000) { + glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 0, (float*)texCoords.constData()); + glVertexPointer(2, GL_FLOAT, 0, (float*)vertices.constData()); + glDrawArrays(GL_QUADS, 0, vertexCount); + glPopClientAttrib(); + } else { + glBegin(GL_QUADS); + for (int i = 0; i < vertexCount; i++) { + glTexCoord2fv((const float*)&texCoords[i]); + glVertex2fv((const float*)&vertices[i]); + } + glEnd(); + } + + if (opacity < 1.0) + glPopAttrib(); + + tex->unbind(); + shader->unbind(); + } + + // Draw the window over the blurred area + effects->drawWindow(w, mask, region, data); +} + +} // namespace KWin + diff --git a/effects/blur/blur.desktop b/effects/blur/blur.desktop new file mode 100644 index 0000000000..7360dcb2bb --- /dev/null +++ b/effects/blur/blur.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Blur +Icon=preferences-system-windows-effect-blur +Comment=Blurs the background behind semi-transparent windows + +Type=Service +X-KDE-ServiceTypes=KWin/Effect +X-KDE-PluginInfo-Author=Fredrik Höglund +X-KDE-PluginInfo-Email=fredrik@kde.org +X-KDE-PluginInfo-Name=kwin4_effect_blur +X-KDE-PluginInfo-Version=0.1.0 +X-KDE-PluginInfo-Category=Appearance +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +X-KDE-Library=kwin4_effect_builtins +X-KDE-Ordering=75 + diff --git a/effects/blur/blur.h b/effects/blur/blur.h new file mode 100644 index 0000000000..dfb6df414e --- /dev/null +++ b/effects/blur/blur.h @@ -0,0 +1,61 @@ +/* + * Copyright © 2010 Fredrik Höglund + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. if not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef BLUR_H +#define BLUR_H + +#include +#include + +#include +#include + +namespace KWin +{ + +class BlurShader; + +class BlurEffect : public KWin::Effect +{ +public: + BlurEffect(); + ~BlurEffect(); + + static bool supported(); + + void paintScreen(int mask, QRegion region, ScreenPaintData &data); + void drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data); + +private: + QRect expand(const QRect &rect) const; + QRegion expand(const QRegion ®ion) const; + +private: + BlurShader *shader; + QVector vertices; + QVector texCoords; + GLRenderTarget *target; + GLTexture *tex; + int radius; +}; + +} // namespace KWin + +#endif + diff --git a/effects/blur/blurshader.cpp b/effects/blur/blurshader.cpp new file mode 100644 index 0000000000..2aa2e2425d --- /dev/null +++ b/effects/blur/blurshader.cpp @@ -0,0 +1,188 @@ +/* + * Copyright © 2010 Fredrik Höglund + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. if not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "blurshader.h" + +#include +#include +#include + +#include + +using namespace KWin; + + +BlurShader::BlurShader() + : program(0), radius(12) +{ +} + +BlurShader::~BlurShader() +{ + if (program) { + glDeleteProgramsARB(1, &program); + program = 0; + } +} + +void BlurShader::setRadius(int _radius) +{ + int r = qMax(_radius, 2); + + if (radius != r) { + radius = r; + + if (program) { + glDeleteProgramsARB(1, &program); + program = 0; + } + } +} + +void BlurShader::setDirection(Qt::Orientation orientation) +{ + direction = orientation; +} + +void BlurShader::setPixelDistance(float val) +{ + float firstStep = val * 1.5; + float nextStep = val * 2.0; + + if (direction == Qt::Horizontal) { + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, firstStep, 0, 0, 0); + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, nextStep, 0, 0, 0); + } else { + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0, firstStep, 0, 0); + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 0, nextStep, 0, 0); + } +} + +void BlurShader::setOpacity(float val) +{ + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 2, val, val, val, val); +} + +void BlurShader::bind() +{ + if (!program) + init(); + + glEnable(GL_FRAGMENT_PROGRAM_ARB); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program); +} + +void BlurShader::unbind() +{ + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); + glDisable(GL_FRAGMENT_PROGRAM_ARB); +} + +float BlurShader::gaussian(float x, float sigma) const +{ + return (1.0 / std::sqrt(2.0 * M_PI) * sigma) + * std::exp(-((x * x) / (2.0 * sigma * sigma))); +} + +QVector BlurShader::gaussianKernel() const +{ + const int size = radius | 1; + QVector kernel(size); + const qreal sigma = radius / 2.5; + const int center = size / 2; + + // Generate the gaussian kernel + kernel[center] = gaussian(0, sigma) * .5; + for (int i = 1; i <= center; i++) { + const float val = gaussian(1.5 + (i - 1) * 2.0, sigma); + kernel[center + i] = val; + kernel[center - i] = val; + } + + // Normalize the kernel + qreal total = 0; + for (int i = 0; i < size; i++) + total += kernel[i]; + + for (int i = 0; i < size; i++) + kernel[i] /= total; + + return kernel; +} + +void BlurShader::init() +{ + QVector kernel = gaussianKernel(); + const int size = kernel.size(); + const int center = size / 2; + + QByteArray text; + QTextStream stream(&text); + + stream << "!!ARBfp1.0\n"; + + // The kernel values are hardcoded into the program + for (int i = 0; i <= center; i++) + stream << "PARAM kernel" << i << " = " << kernel[i] << ";\n"; + + stream << "PARAM firstSample = program.local[0];\n"; // Distance from gl_TexCoord[0] to the next sample + stream << "PARAM nextSample = program.local[1];\n"; // Distance to the subsequent sample + stream << "PARAM opacity = program.local[2];\n"; // The opacity with which to modulate the pixels + + stream << "TEMP coord;\n"; // The coordinate we'll be sampling + stream << "TEMP sample;\n"; // The sampled value + stream << "TEMP sum;\n"; // The sum of the weighted samples + + // Start by sampling the center coordinate + stream << "TEX sample, fragment.texcoord[0], texture[0], 2D;\n"; // sample = texture2D(tex, gl_TexCoord[0]) + stream << "MUL sum, sample, kernel" << center << ";\n"; // sum = sample * kernel[center] + + for (int i = 1; i <= center; i++) { + if (i == 1) + stream << "SUB coord, fragment.texcoord[0], firstSample;\n"; // coord = gl_TexCoord[0] - firstSample + else + stream << "SUB coord, coord, nextSample;\n"; // coord -= nextSample + stream << "TEX sample, coord, texture[0], 2D;\n"; // sample = texture2D(tex, coord) + stream << "MAD sum, sample, kernel" << center - i << ", sum;\n"; // sum += sample * kernel[center - i] + } + + for (int i = 1; i <= center; i++) { + if (i == 1) + stream << "ADD coord, fragment.texcoord[0], firstSample;\n"; // coord = gl_TexCoord[0] + firstSample + else + stream << "ADD coord, coord, nextSample;\n"; // coord += nextSample + stream << "TEX sample, coord, texture[0], 2D;\n"; // sample = texture2D(tex, coord) + stream << "MAD sum, sample, kernel" << center - i << ", sum;\n"; // sum += sample * kernel[center - i] + } + stream << "MUL result.color, sum, opacity;\n"; // gl_FragColor = sum * opacity + stream << "END\n"; + stream.flush(); + + glGenProgramsARB(1, &program); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program); + glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, text.length(), text.constData()); + + if (glGetError()) { + const char *error = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); + kError() << "Error when compiling fragment program:" << error; + } + + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); +} + diff --git a/effects/blur/blurshader.h b/effects/blur/blurshader.h new file mode 100644 index 0000000000..6a0cdf223c --- /dev/null +++ b/effects/blur/blurshader.h @@ -0,0 +1,63 @@ +/* + * Copyright © 2010 Fredrik Höglund + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. if not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef BLURSHADER_H +#define BLURSHADER_H + +#include + +namespace KWin +{ + +class BlurShader +{ +public: + BlurShader(); + ~BlurShader(); + + // Sets the radius in pixels + void setRadius(int radius); + + // Sets the blur direction + void setDirection(Qt::Orientation orientation); + + // Sets the distance between two pixels + void setPixelDistance(float val); + + // The opacity of the resulting pixels is multiplied by this value + void setOpacity(float val); + + void bind(); + void unbind(); + +private: + float gaussian(float x, float sigma) const; + QVector gaussianKernel() const; + void init(); + +private: + GLuint program; + int radius; + Qt::Orientation direction; +}; + +} // namespace KWin + +#endif +