Finally make the bloody blur effect work properly.

This fixes the artefacts appearing when only part of the screen is updated.
This version also brings ton of optimizations which might well increase performance
  2 or 3 times on slower cards:
- Windows are not drawn twice anymore. Now they're drawn only to render target and
  later changed parts of the render target are copied back onto screen.
- Shaders have been optimized. Some calculations moved from pixel shader to vertex shader.
- For ARGB windows, if window's opacity is 0 then it will stay transparent instead of being
  replaced by blurred background.
- Blur effect should now play nicer with other effects, e.g. shadows.

svn path=/trunk/KDE/kdebase/workspace/; revision=748502
icc-effect-5.14.5
Rivo Laks 2007-12-14 16:57:38 +00:00
parent eba221d0a3
commit 213833fc7f
7 changed files with 190 additions and 89 deletions

View File

@ -47,12 +47,17 @@ BlurEffect::BlurEffect() : Effect()
mWindowShader = 0;
mBlurRadius = 4;
mTime = 0;
mValid = loadData();
if( !mValid )
{
kWarning() << "Loading failed";
}
effects->addRepaintFull();
}
BlurEffect::~BlurEffect()
{
{
effects->addRepaintFull();
delete mSceneTexture;
delete mTmpTexture;
delete mBlurTexture;
@ -61,7 +66,7 @@ BlurEffect::~BlurEffect()
delete mBlurTarget;
delete mBlurShader;
delete mWindowShader;
}
}
bool BlurEffect::loadData()
@ -140,44 +145,123 @@ bool BlurEffect::supported()
(effects->compositingType() == OpenGLCompositing);
}
QRegion BlurEffect::expandedRegion( const QRegion& region ) const
{
QRegion expandedregion;
foreach( QRect r, region.rects() )
{
r.adjust( -mBlurRadius, -mBlurRadius, mBlurRadius, mBlurRadius );
expandedregion += r;
}
return expandedregion;
}
void BlurEffect::prePaintScreen( ScreenPrePaintData& data, int time )
{
mTransparentWindows = 0;
mTime += time;
mScreenDirty = QRegion();
mBlurDirty = QRegion();
mBlurMask = QRegion();
effects->prePaintScreen(data, time);
}
void BlurEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time )
{
{
// Expand the painted area
mBlurMask |= expandedRegion( data.paint );
data.paint |= expandedRegion( mBlurMask );
effects->prePaintWindow( w, data, time );
if( w->isPaintingEnabled() && ( data.mask & PAINT_WINDOW_TRANSLUCENT ))
mTransparentWindows++;
}
data.setTranslucent();
}
void BlurEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
{
if( mValid && mTransparentWindows )
void BlurEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data )
{
if( mask & PAINT_WINDOW_TRANSLUCENT )
// TODO: prePaintWindow() gets called _after_ paintScreen(), so we have no
// way of knowing here whether there will be any translucent windows or
// not. If we'd know that there's no translucent windows then we could
// render straight onto screen, saving some time.
if( mValid /*&& mTransparentWindows*/ )
{
// rendering everything onto render target
effects->pushRenderTarget(mSceneTarget);
effects->paintScreen( mask, region, data );
effects->popRenderTarget();
// Copy changed areas back onto screen
mScreenDirty &= mBlurMask;
if( !mScreenDirty.isEmpty() )
{
if( mask & PAINT_SCREEN_TRANSFORMED )
{
// We don't want any transformations when working with our own
// textures, so load an identity matrix
glMatrixMode( GL_MODELVIEW );
glPushMatrix();
glLoadIdentity();
}
GLTexture* tex = mSceneTexture;
int pixels = 0;
tex->bind();
tex->enableUnnormalizedTexCoords();
foreach( QRect r, mScreenDirty.rects() )
{
r.adjust(0, -1, 0, -1);
int rx2 = r.x() + r.width();
int ry2 = r.y() + r.height();
glBegin(GL_QUADS);
glTexCoord2f( r.x(), ry2 ); glVertex2f( r.x(), ry2 );
glTexCoord2f( rx2 , ry2 ); glVertex2f( rx2 , ry2 );
glTexCoord2f( rx2 , r.y() ); glVertex2f( rx2 , r.y() );
glTexCoord2f( r.x(), r.y() ); glVertex2f( r.x(), r.y() );
glEnd();
pixels += r.width()*r.height();
}
tex->disableUnnormalizedTexCoords();
tex->unbind();
if( mask & PAINT_SCREEN_TRANSFORMED )
{
// Restore the original matrix
glPopMatrix();
}
// kDebug() << "Copied" << mScreenDirty.rects().count() << "rects, pixels:" << pixels;
}
}
else
{
effects->paintScreen( mask, region, data );
}
}
void BlurEffect::drawWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
{
if( mValid /*&& mTransparentWindows*/ )
{
if( mask & PAINT_WINDOW_TRANSLUCENT &&
(data.opacity != 1.0 || data.contents_opacity != 1.0 || data.decoration_opacity != 1.0 ))
{
// Make sure the blur texture is up to date
if( mask & PAINT_SCREEN_TRANSFORMED )
{
{
// We don't want any transformations when working with our own
// textures, so load an identity matrix
glPushMatrix();
glLoadIdentity();
}
}
// If we're having transformations, we don't know the window's
// transformed position on the screen and thus have to update the
// entire screen
if( mask & ( PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS ) )
updateBlurTexture( QRegion(0, 0, displayWidth(), displayHeight()) );
else
updateBlurTexture(region);
updateBlurTexture( mBlurDirty );
mBlurDirty = QRegion();
if( mask & PAINT_SCREEN_TRANSFORMED )
// Restore the original matrix
glPopMatrix();
@ -191,97 +275,87 @@ void BlurEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowP
glActiveTexture(GL_TEXTURE0);
// Paint
effects->paintWindow( w, mask, region, data );
if(mTransparentWindows > 1)
{
// If we have multiple translucent windows on top of each
// other, we need to paint those onto the scene rendertarget
// as well
effects->pushRenderTarget(mSceneTarget);
effects->paintWindow( w, mask, region, data );
effects->popRenderTarget();
}
effects->drawWindow( w, mask, region, data );
// Disable blur texture and shader
glActiveTexture(GL_TEXTURE4);
mBlurTexture->unbind();
glActiveTexture(GL_TEXTURE0);
mWindowShader->unbind();
}
}
else
{
{
// Opaque window
// Paint to the screen...
effects->paintWindow( w, mask, region, data );
// ...and to the rendertarget as well
effects->pushRenderTarget(mSceneTarget);
effects->paintWindow( w, mask, region, data );
effects->popRenderTarget();
// Paint to the rendertarget (which is already being used)
effects->drawWindow( w, mask, region, data );
}
// Mark the window's region as dirty
mScreenDirty += region;
mBlurDirty += region & mBlurMask;
}
}
else
// If there are no translucent windows then paint as usual
effects->paintWindow( w, mask, region, data );
}
effects->drawWindow( w, mask, region, data );
}
void BlurEffect::updateBlurTexture(const QRegion& region)
{
{
QRect bounding = region.boundingRect();
QVector<QRect> rects = region.rects();
int totalarea = 0;
foreach( QRect r, rects )
totalarea += r.width() * r.height();
if( (int)(totalarea * 1.33 + 100 ) < bounding.width() * bounding.height() )
{
{
// Use small rects
updateBlurTexture(rects);
}
}
else
{
{
// Bounding rect is probably cheaper
QVector<QRect> tmp( 1, bounding );
updateBlurTexture( tmp );
}
}
}
void BlurEffect::updateBlurTexture(const QVector<QRect>& rects)
{
{
// Blur
// First pass (vertical)
effects->pushRenderTarget(mTmpTarget);
mBlurShader->bind();
mSceneTexture->bind();
effects->pushRenderTarget(mTmpTarget);
mBlurShader->setAttribute("xBlur", 0.0f);
mBlurShader->setAttribute("yBlur", 1.0f);
mBlurShader->setAttribute("xBlur", 0);
mBlurShader->setAttribute("yBlur", 1);
mSceneTexture->bind();
foreach( QRect r, rects )
{
r.adjust(-mBlurRadius, -mBlurRadius, mBlurRadius, mBlurRadius);
// We change x coordinates here because horizontal blur pass (which
// comes after this one) also uses pixels that are horizontally edging
// the blurred area. Thus we need to make sure that those pixels are
// also updated.
glBegin(GL_QUADS);
glVertex2f( r.x() , r.y() + r.height() );
glVertex2f( r.x() + r.width(), r.y() + r.height() );
glVertex2f( r.x() + r.width(), r.y() );
glVertex2f( r.x() , r.y() );
glVertex2f( r.x()-mBlurRadius , r.y() + r.height() );
glVertex2f( r.x() + r.width()+mBlurRadius, r.y() + r.height() );
glVertex2f( r.x() + r.width()+mBlurRadius, r.y() );
glVertex2f( r.x()-mBlurRadius , r.y() );
glEnd();
}
mSceneTexture->unbind();
mBlurShader->unbind();
effects->popRenderTarget();
// Second pass (horizontal)
effects->pushRenderTarget(mBlurTarget);
mBlurShader->bind();
mTmpTexture->bind();
mBlurShader->setAttribute("xBlur", 1.0f);
mBlurShader->setAttribute("yBlur", 0.0f);
mBlurShader->setAttribute("xBlur", 1);
mBlurShader->setAttribute("yBlur", 0);
mTmpTexture->bind();
foreach( QRect r, rects )
{
r.adjust(-mBlurRadius, -mBlurRadius, mBlurRadius, mBlurRadius);
glBegin(GL_QUADS);
glVertex2f( r.x() , r.y() + r.height() );
glVertex2f( r.x() + r.width(), r.y() + r.height() );
@ -292,9 +366,9 @@ void BlurEffect::updateBlurTexture(const QVector<QRect>& rects)
mTmpTexture->unbind();
mBlurShader->unbind();
effects->popRenderTarget();
}
mBlurShader->unbind();
}
} // namespace

View File

@ -75,9 +75,10 @@ X-KDE-ServiceTypes=KWin/Effect
X-KDE-PluginInfo-Author=Rivo Laks
X-KDE-PluginInfo-Email=rivolaks@hot.ee
X-KDE-PluginInfo-Name=kwin4_effect_blur
X-KDE-PluginInfo-Version=0.1.0
X-KDE-PluginInfo-Version=0.2.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-Ordering=85

View File

@ -24,6 +24,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Include with base class for effects.
#include <kwineffects.h>
#include <QRegion>
template< class T > class QVector;
@ -44,9 +46,10 @@ class BlurEffect : public Effect
~BlurEffect();
virtual void prePaintScreen( ScreenPrePaintData& data, int time );
virtual void paintScreen( int mask, QRegion region, ScreenPaintData& data );
virtual void prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time );
virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data );
virtual void drawWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data );
static bool supported();
@ -56,6 +59,8 @@ class BlurEffect : public Effect
void updateBlurTexture(const QVector<QRect>& rects);
void updateBlurTexture(const QRegion& region);
QRegion expandedRegion( const QRegion& r ) const;
private:
GLTexture* mSceneTexture;
GLTexture* mTmpTexture;
@ -69,7 +74,11 @@ class BlurEffect : public Effect
int mBlurRadius;
int mTransparentWindows;
int mTime;
QRegion mBlurDirty;
QRegion mScreenDirty;
QRegion mBlurMask;
};
} // namespace

View File

@ -14,7 +14,7 @@ vec2 pix2tex(vec2 pix)
}
// Returns color of the window at given texture coordinate, taking into
// account opacity, brightness and saturation
// account brightness and saturation, but not opacity
vec4 windowColor(vec2 texcoord)
{
vec4 color = texture2D(windowTex, texcoord);
@ -23,21 +23,18 @@ vec4 windowColor(vec2 texcoord)
color.rgb = mix(vec3(grayscale), color.rgb, saturation);
// Apply brightness
color.rgb = color.rgb * brightness;
// Apply opacity
color.a = color.a * opacity;
// and return
return color;
}
void main()
{
vec2 texcoord = (gl_TexCoord[0] * gl_TextureMatrix[0]).xy;
vec2 blurtexcoord = pix2tex(gl_FragCoord.xy); //(gl_FragCoord * gl_TextureMatrix[4]).xy;
vec2 blurtexcoord = pix2tex(gl_FragCoord.xy);
vec4 winColor = windowColor(texcoord);
vec4 winColor = windowColor(gl_TexCoord[0].xy);
vec3 tex = mix(texture2D(backgroundTex, blurtexcoord).rgb,
winColor.rgb, winColor.a * opacity);
gl_FragColor = vec4(tex, 1.0);
gl_FragColor = vec4(tex, pow(winColor.a, 0.2));
}

View File

@ -1,5 +1,5 @@
void main()
{
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[0] = gl_MultiTexCoord0 * gl_TextureMatrix[0];
gl_Position = ftransform();
}

View File

@ -1,20 +1,19 @@
uniform sampler2D inputTex;
uniform float textureWidth;
uniform float textureHeight;
varying vec2 pos;
varying vec2 blurDirection;
varying vec2 samplePos1;
varying vec2 samplePos2;
varying vec2 samplePos3;
varying vec2 samplePos4;
varying vec2 samplePos5;
// Converts pixel coordinates to texture coordinates
vec2 pix2tex(vec2 pix)
// If defined, use five samples (blur radius = 5), otherwise 3 samples (radius = 3)
#define FIVE_SAMPLES
vec3 blurTex(vec2 pos, float strength)
{
return vec2(pix.x / textureWidth, 1.0 - pix.y / textureHeight);
}
vec3 blurTex(float offset, float strength)
{
return texture2D(inputTex, pix2tex(pos + blurDirection * offset)).rgb * strength;
return texture2D(inputTex, pos).rgb * strength;
}
void main()
@ -23,11 +22,17 @@ void main()
// This blur actually has a radius of 4, but we take advantage of gpu's
// linear texture filtering, so e.g. 1.5 actually gives us both texels
// 1 and 2
vec3 tex = blurTex(0.0, 0.20);
tex += blurTex(-1.5, 0.30);
tex += blurTex( 1.5, 0.30);
tex += blurTex(-3.5, 0.10);
tex += blurTex( 3.5, 0.10);
#ifdef FIVE_SAMPLES
vec3 tex = blurTex(samplePos1, 0.30);
tex += blurTex(samplePos2, 0.25);
tex += blurTex(samplePos3, 0.25);
tex += blurTex(samplePos4, 0.1);
tex += blurTex(samplePos5, 0.1);
#else
vec3 tex = blurTex(samplePos1, 0.40);
tex += blurTex(samplePos2, 0.30);
tex += blurTex(samplePos3, 0.30);
#endif
gl_FragColor = vec4(tex, 1.0);
}

View File

@ -1,13 +1,28 @@
varying vec2 pos;
varying vec2 blurDirection;
varying vec2 samplePos1;
varying vec2 samplePos2;
varying vec2 samplePos3;
varying vec2 samplePos4;
varying vec2 samplePos5;
uniform float textureWidth;
uniform float textureHeight;
attribute float xBlur;
attribute float yBlur;
vec2 mkSamplePos(vec2 origin, float offset)
{
vec2 foo = origin + vec2(xBlur, yBlur) * offset;
return vec2(foo.x / textureWidth, 1.0 - foo.y / textureHeight);
}
void main()
{
blurDirection = vec2(xBlur, yBlur);
pos = gl_Vertex.xy;
samplePos1 = mkSamplePos(gl_Vertex.xy, 0.0);
samplePos2 = mkSamplePos(gl_Vertex.xy, -1.5);
samplePos3 = mkSamplePos(gl_Vertex.xy, 1.5);
samplePos4 = mkSamplePos(gl_Vertex.xy, 3.5);
samplePos5 = mkSamplePos(gl_Vertex.xy, -3.5);
gl_Position = ftransform();
}