Initial commit of the blur effect rewrite.

svn path=/trunk/KDE/kdebase/workspace/; revision=1099619
This commit is contained in:
Fredrik Höglund 2010-03-05 20:42:10 +00:00
parent 48c3a09119
commit 53391ba944
6 changed files with 544 additions and 0 deletions

View file

@ -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 )

200
effects/blur/blur.cpp Normal file
View file

@ -0,0 +1,200 @@
/*
* Copyright © 2010 Fredrik Höglund <fredrik@kde.org>
*
* 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 &region) 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

18
effects/blur/blur.desktop Normal file
View file

@ -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

61
effects/blur/blur.h Normal file
View file

@ -0,0 +1,61 @@
/*
* Copyright © 2010 Fredrik Höglund <fredrik@kde.org>
*
* 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 <kwineffects.h>
#include <kwinglutils.h>
#include <QVector>
#include <QVector2D>
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 &region) const;
private:
BlurShader *shader;
QVector<QVector2D> vertices;
QVector<QVector2D> texCoords;
GLRenderTarget *target;
GLTexture *tex;
int radius;
};
} // namespace KWin
#endif

188
effects/blur/blurshader.cpp Normal file
View file

@ -0,0 +1,188 @@
/*
* Copyright © 2010 Fredrik Höglund <fredrik@kde.org>
*
* 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 <QByteArray>
#include <QTextStream>
#include <KDebug>
#include <cmath>
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<float> BlurShader::gaussianKernel() const
{
const int size = radius | 1;
QVector<float> 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<float> 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);
}

63
effects/blur/blurshader.h Normal file
View file

@ -0,0 +1,63 @@
/*
* Copyright © 2010 Fredrik Höglund <fredrik@kde.org>
*
* 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 <kwinglutils.h>
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<float> gaussianKernel() const;
void init();
private:
GLuint program;
int radius;
Qt::Orientation direction;
};
} // namespace KWin
#endif