diff --git a/effects/taskbarthumbnail/CMakeLists.txt b/effects/taskbarthumbnail/CMakeLists.txt index c502753907..36875f62ee 100644 --- a/effects/taskbarthumbnail/CMakeLists.txt +++ b/effects/taskbarthumbnail/CMakeLists.txt @@ -5,6 +5,9 @@ set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} taskbarthumbnail/taskbarthumbnail.cpp ) +qt4_add_resources( kwin4_effect_builtins_sources + taskbarthumbnail/taskbarthumbnail.qrc + ) # .desktop files install( FILES diff --git a/effects/taskbarthumbnail/fragment.glsl b/effects/taskbarthumbnail/fragment.glsl new file mode 100644 index 0000000000..639bf847b4 --- /dev/null +++ b/effects/taskbarthumbnail/fragment.glsl @@ -0,0 +1,15 @@ +uniform sampler2D texUnit; +uniform vec2 offsets[25]; +uniform vec4 kernel[25]; +uniform int kernelSize; + +void main(void) +{ + vec4 sum = texture2D(texUnit, gl_TexCoord[0].st) * kernel[0]; + for (int i = 1; i < kernelSize; i++) { + sum += texture2D(texUnit, gl_TexCoord[0].st - offsets[i]) * kernel[i]; + sum += texture2D(texUnit, gl_TexCoord[0].st + offsets[i]) * kernel[i]; + } + gl_FragColor = sum; +} + diff --git a/effects/taskbarthumbnail/taskbarthumbnail.cpp b/effects/taskbarthumbnail/taskbarthumbnail.cpp index 01f0f30f5d..0346dc61a4 100644 --- a/effects/taskbarthumbnail/taskbarthumbnail.cpp +++ b/effects/taskbarthumbnail/taskbarthumbnail.cpp @@ -27,6 +27,8 @@ along with this program. If not, see . #include #endif +#include +#include // This effect shows a preview inside a window that has a special property set // on it that says which window and where to render. It is used by the taskbar @@ -44,12 +46,42 @@ TaskbarThumbnailEffect::TaskbarThumbnailEffect() // TODO hackish way to announce support, make better after 4.0 unsigned char dummy = 0; XChangeProperty( display(), rootWindow(), atom, atom, 8, PropModeReplace, &dummy, 1 ); + + // With the NVIDIA driver the alpha values are set to 0 when drawing a window texture + // without an alpha channel on an FBO that has an alpha channel. + // This creates problems when the FBO is drawn on the backbuffer with blending enabled, + // so for now we only do high quality scaling with windows with alpha channels with + // the NVIDIA driver. + const QByteArray vendor = (const char *) glGetString( GL_VENDOR ); + alphaWindowsOnly = vendor.startsWith("NVIDIA"); + + if ( GLShader::fragmentShaderSupported() && + GLShader::vertexShaderSupported() && + GLRenderTarget::supported() ) + { + shader = new GLShader(":/taskbarthumbnail/vertex.glsl", ":/taskbarthumbnail/fragment.glsl"); + shader->bind(); + uTexUnit = shader->uniformLocation("texUnit"); + uKernelSize = shader->uniformLocation("kernelSize"); + uKernel = shader->uniformLocation("kernel"); + uOffsets = shader->uniformLocation("offsets"); + shader->unbind(); + } + else + { + shader = 0; + } + offscreenTex = 0; + offscreenTarget = 0; } TaskbarThumbnailEffect::~TaskbarThumbnailEffect() { XDeleteProperty( display(), rootWindow(), atom ); effects->registerPropertyType( atom, false ); + delete offscreenTarget; + delete offscreenTex; + delete shader; } void TaskbarThumbnailEffect::prePaintScreen( ScreenPrePaintData& data, int time ) @@ -66,6 +98,85 @@ void TaskbarThumbnailEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData effects->prePaintWindow( w, data, time ); } +void TaskbarThumbnailEffect::updateOffscreenSurfaces() + { + int w = displayWidth(); + int h = displayHeight(); + if ( !GLTexture::NPOTTextureSupported() ) + { + w = nearestPowerOfTwo( w ); + h = nearestPowerOfTwo( h ); + } + if ( !offscreenTex || offscreenTex->width() != w || offscreenTex->height() != h ) + { + if ( offscreenTex ) + { + delete offscreenTex; + delete offscreenTarget; + } + offscreenTex = new GLTexture( w, h ); + offscreenTex->setFilter( GL_LINEAR ); + offscreenTex->setWrapMode( GL_CLAMP_TO_EDGE ); + offscreenTarget = new GLRenderTarget( offscreenTex ); + } + } + +static float sinc( float x ) + { + return std::sin( x * M_PI ) / ( x * M_PI ); + } + +static float lanczos( float x, float a ) + { + if ( qFuzzyCompare( x + 1.0, 1.0 ) ) + return 1.0; + + if ( qAbs( x ) >= a ) + return 0.0; + + return sinc( x ) * sinc( x / a ); + } + +QVector TaskbarThumbnailEffect::createKernel( float delta ) + { + const float a = 2.0; + + // The two outermost samples always fall at points where the lanczos + // function returns 0, so we'll skip them. + const int sampleCount = qBound( 3, qCeil(delta * a) * 2 + 1 - 2, 49 ); + const int center = sampleCount / 2; + const int kernelSize = center + 1; + const float factor = 1.0 / delta; + + QVector values( kernelSize ); + QVector kernel( kernelSize ); + float sum = 0; + + for ( int i = 0; i < kernelSize; i++ ) { + const float val = lanczos( i * factor, a ); + sum += i > 0 ? val * 2 : val; + values[i] = val; + } + + // Normalize the kernel + for ( int i = 0; i < kernelSize; i++ ) { + const float val = values[i] / sum; + kernel[i] = QVector4D( val, val, val, val ); + } + + return kernel; + } + +QVector TaskbarThumbnailEffect::createOffsets( int count, float width, Qt::Orientation direction ) + { + QVector offsets( count ); + for ( int i = 0; i < count; i++ ) { + offsets[i] = ( direction == Qt::Horizontal ) ? + QVector2D( i / width, 0 ) : QVector2D( 0, i / width ); + } + return offsets; + } + void TaskbarThumbnailEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) { effects->paintWindow( w, mask, region, data ); // paint window first @@ -84,30 +195,124 @@ void TaskbarThumbnailEffect::paintWindow( EffectWindow* w, int mask, QRegion reg WindowPaintData thumbData( thumbw ); thumbData.opacity = data.opacity; QRect r; - setPositionTransformations( thumbData, r, - thumbw, thumb.rect.translated( w->pos()), Qt::KeepAspectRatio ); #ifdef KWIN_HAVE_OPENGL_COMPOSITING - if( effects->compositingType() == KWin::OpenGLCompositing && data.shader ) + if( effects->compositingType() == KWin::OpenGLCompositing ) { - // there is a shader - update texture width and height - int texw = thumbw->width(); - int texh = thumbw->height(); - if( !GLTexture::NPOTTextureSupported() ) + if ( shader && (!alphaWindowsOnly || thumbw->hasAlpha()) ) { - kWarning( 1212 ) << "NPOT textures not supported, wasting some memory" ; - texw = nearestPowerOfTwo( texw ); - texh = nearestPowerOfTwo( texh ); + int tx = thumb.rect.x() + w->x(); + int ty = thumb.rect.y() + w->y(); + int tw = thumb.rect.width(); + int th = thumb.rect.height(); + + int sw = thumbw->width(); + int sh = thumbw->height(); + + setPositionTransformations( thumbData, r, + thumbw, QRect(0, 0, sw, sh), Qt::KeepAspectRatio ); + + // Bind the offscreen FBO and draw the window on it unscaled + updateOffscreenSurfaces(); + effects->pushRenderTarget( offscreenTarget ); + + glClear( GL_COLOR_BUFFER_BIT ); + effects->drawWindow( thumbw, mask, r, thumbData ); + + // Create a scratch texture and copy the rendered window into it + GLTexture tex( sw, sh ); + tex.setFilter( GL_LINEAR ); + tex.setWrapMode( GL_CLAMP_TO_EDGE ); + tex.bind(); + + glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, offscreenTex->height() - sh, sw, sh ); + + // Set up the shader for horizontal scaling + float dx = sw / float(tw); + QVector kernel = createKernel( dx ); + QVector offsets = createOffsets( kernel.size(), sw, Qt::Horizontal ); + + shader->bind(); + glUniform1i( uTexUnit, 0 ); + glUniform1i( uKernelSize, kernel.size() ); + glUniform2fv( uOffsets, offsets.size(), (const GLfloat*)offsets.constData() ); + glUniform4fv( uKernel, kernel.size(), (const GLfloat*)kernel.constData() ); + + // Draw the window back into the FBO, this time scaled horizontally + glClear( GL_COLOR_BUFFER_BIT ); + + glBegin( GL_QUADS ); + glTexCoord2f( 0, 0 ); glVertex2i( 0, 0 ); // Top left + glTexCoord2f( 1, 0 ); glVertex2i( tw, 0 ); // Top right + glTexCoord2f( 1, 1 ); glVertex2i( tw, sh ); // Bottom right + glTexCoord2f( 0, 1 ); glVertex2i( 0, sh ); // Bottom left + glEnd(); + + // At this point we don't need the scratch texture anymore + tex.unbind(); + tex.discard(); + + // Unbind the FBO + effects->popRenderTarget(); + + // Set up the shader for vertical scaling + float dy = sh / float(th); + kernel = createKernel( dy ); + offsets = createOffsets( kernel.size(), offscreenTex->height(), Qt::Vertical ); + + glUniform1i( uKernelSize, kernel.size() ); + glUniform2fv( uOffsets, offsets.size(), (const GLfloat*)offsets.constData() ); + glUniform4fv( uKernel, kernel.size(), (const GLfloat*)kernel.constData() ); + + float sx2 = tw / float(offscreenTex->width()); + float sy2 = 1 - (sh / float(offscreenTex->height())); + + // Now draw the horizontally scaled window in the FBO at the right + // coordinates on the screen, while scaling it vertically and blending it. + offscreenTex->bind(); + + glPushAttrib( GL_COLOR_BUFFER_BIT ); + glEnable( GL_BLEND ); + glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA ); + + glBegin( GL_QUADS ); + glTexCoord2f( 0, sy2 ); glVertex2i( tx, ty ); // Top left + glTexCoord2f( sx2, sy2 ); glVertex2i( tx + tw, ty ); // Top right + glTexCoord2f( sx2, 1 ); glVertex2i( tx + tw, ty + th ); // Bottom right + glTexCoord2f( 0, 1 ); glVertex2i( tx, ty + th ); // Bottom left + glEnd(); + + glPopAttrib(); + offscreenTex->unbind(); + shader->unbind(); + + // Delete the offscreen surface after 5 seconds + timer.start( 5000, this ); + return; } - thumbData.shader = data.shader; - thumbData.shader->setTextureWidth( (float)texw ); - thumbData.shader->setTextureHeight( (float)texh ); - } + if ( data.shader ) + { + // there is a shader - update texture width and height + int texw = thumbw->width(); + int texh = thumbw->height(); + if( !GLTexture::NPOTTextureSupported() ) + { + kWarning( 1212 ) << "NPOT textures not supported, wasting some memory" ; + texw = nearestPowerOfTwo( texw ); + texh = nearestPowerOfTwo( texh ); + } + thumbData.shader = data.shader; + thumbData.shader->setTextureWidth( (float)texw ); + thumbData.shader->setTextureHeight( (float)texh ); + } + } // if ( effects->compositingType() == KWin::OpenGLCompositing ) #endif + setPositionTransformations( thumbData, r, + thumbw, thumb.rect.translated( w->pos()), Qt::KeepAspectRatio ); effects->drawWindow( thumbw, mask, r, thumbData ); } } - } + } // End of function void TaskbarThumbnailEffect::windowDamaged( EffectWindow* w, const QRect& damage ) { @@ -158,4 +363,18 @@ void TaskbarThumbnailEffect::propertyNotify( EffectWindow* w, long a ) } } + +void TaskbarThumbnailEffect::timerEvent( QTimerEvent *event ) + { + if (event->timerId() == timer.timerId()) + { + timer.stop(); + + delete offscreenTarget; + delete offscreenTex; + offscreenTarget = 0; + offscreenTex = 0; + } + } + } // namespace diff --git a/effects/taskbarthumbnail/taskbarthumbnail.h b/effects/taskbarthumbnail/taskbarthumbnail.h index aa419792a3..507e35285e 100644 --- a/effects/taskbarthumbnail/taskbarthumbnail.h +++ b/effects/taskbarthumbnail/taskbarthumbnail.h @@ -23,12 +23,17 @@ along with this program. If not, see . #define KWIN_TASKBARTHUMBNAIL_H #include +#include +#include +#include +#include +#include namespace KWin { class TaskbarThumbnailEffect - : public Effect + : public QObject, public Effect { public: TaskbarThumbnailEffect(); @@ -41,15 +46,28 @@ class TaskbarThumbnailEffect virtual void windowDeleted( EffectWindow* w ); virtual void propertyNotify( EffectWindow* w, long atom ); protected: + virtual void timerEvent(QTimerEvent*); private: + void updateOffscreenSurfaces(); + QVector createKernel(float delta); + QVector createOffsets(int count, float width, Qt::Orientation direction); struct Data { Window window; // thumbnail of this window QRect rect; }; long atom; + GLTexture *offscreenTex; + GLRenderTarget *offscreenTarget; + GLShader *shader; QMultiHash< EffectWindow*, Data > thumbnails; EffectWindowList damagedWindows; + QBasicTimer timer; + int uTexUnit; + int uOffsets; + int uKernel; + int uKernelSize; + bool alphaWindowsOnly; }; } // namespace diff --git a/effects/taskbarthumbnail/taskbarthumbnail.qrc b/effects/taskbarthumbnail/taskbarthumbnail.qrc new file mode 100644 index 0000000000..b439ad06c7 --- /dev/null +++ b/effects/taskbarthumbnail/taskbarthumbnail.qrc @@ -0,0 +1,6 @@ + + + vertex.glsl + fragment.glsl + + diff --git a/effects/taskbarthumbnail/vertex.glsl b/effects/taskbarthumbnail/vertex.glsl new file mode 100644 index 0000000000..f04c034d3e --- /dev/null +++ b/effects/taskbarthumbnail/vertex.glsl @@ -0,0 +1,6 @@ +void main(void) +{ + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; +} +