From 213833fc7fd88e690fe35d257b35d36d031951cf Mon Sep 17 00:00:00 2001 From: Rivo Laks Date: Fri, 14 Dec 2007 16:57:38 +0000 Subject: [PATCH] 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 --- effects/blur.cpp | 188 +++++++++++++++++++++++----------- effects/blur.desktop | 3 +- effects/blur.h | 13 ++- effects/data/blur-render.frag | 11 +- effects/data/blur-render.vert | 2 +- effects/data/blur.frag | 39 ++++--- effects/data/blur.vert | 23 ++++- 7 files changed, 190 insertions(+), 89 deletions(-) diff --git a/effects/blur.cpp b/effects/blur.cpp index 7456c05390..9db4d35298 100644 --- a/effects/blur.cpp +++ b/effects/blur.cpp @@ -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 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 tmp( 1, bounding ); updateBlurTexture( tmp ); + } } -} void BlurEffect::updateBlurTexture(const QVector& 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& rects) mTmpTexture->unbind(); - mBlurShader->unbind(); effects->popRenderTarget(); -} + mBlurShader->unbind(); + } } // namespace diff --git a/effects/blur.desktop b/effects/blur.desktop index 413b8b2d82..48aee9c8d0 100644 --- a/effects/blur.desktop +++ b/effects/blur.desktop @@ -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 diff --git a/effects/blur.h b/effects/blur.h index 69c92ee00a..91f79902a5 100644 --- a/effects/blur.h +++ b/effects/blur.h @@ -24,6 +24,8 @@ along with this program. If not, see . // Include with base class for effects. #include +#include + 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& 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 diff --git a/effects/data/blur-render.frag b/effects/data/blur-render.frag index 7f8650c82e..740f48149f 100644 --- a/effects/data/blur-render.frag +++ b/effects/data/blur-render.frag @@ -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)); } diff --git a/effects/data/blur-render.vert b/effects/data/blur-render.vert index f1bef4e5ed..cbe3c17bd6 100644 --- a/effects/data/blur-render.vert +++ b/effects/data/blur-render.vert @@ -1,5 +1,5 @@ void main() { - gl_TexCoord[0] = gl_MultiTexCoord0; + gl_TexCoord[0] = gl_MultiTexCoord0 * gl_TextureMatrix[0]; gl_Position = ftransform(); } diff --git a/effects/data/blur.frag b/effects/data/blur.frag index a37521936d..72e867a4df 100644 --- a/effects/data/blur.frag +++ b/effects/data/blur.frag @@ -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); } diff --git a/effects/data/blur.vert b/effects/data/blur.vert index b6fe605e8d..4eb1e0d0ed 100644 --- a/effects/data/blur.vert +++ b/effects/data/blur.vert @@ -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(); }