/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 by 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. If not, see . *********************************************************************/ #include "lanczosfilter.h" #include "effects.h" #ifdef KWIN_HAVE_OPENGL_COMPOSITING #include #endif #include #include #include #include namespace KWin { LanczosFilter::LanczosFilter( QObject* parent ) : QObject( parent ) #ifdef KWIN_HAVE_OPENGL_COMPOSITING , m_offscreenTex( 0 ) , m_offscreenTarget( 0 ) , m_shader( 0 ) #endif , m_inited( false) { } LanczosFilter::~LanczosFilter() { #ifdef KWIN_HAVE_OPENGL_COMPOSITING delete m_offscreenTarget; delete m_offscreenTex; delete m_shader; #endif } void LanczosFilter::init() { if (m_inited) return; m_inited = true; #ifdef KWIN_HAVE_OPENGL_COMPOSITING // check the blacklist KSharedConfigPtr config = KSharedConfig::openConfig( "kwinrc" ); KConfigGroup blacklist = config->group( "Blacklist" ).group( "Lanczos" ); if( effects->checkDriverBlacklist( blacklist ) ) { kDebug() << "Lanczos Filter disabled by driver blacklist"; return; } if ( GLShader::fragmentShaderSupported() && GLShader::vertexShaderSupported() && GLRenderTarget::supported() ) { m_shader = new GLShader(":/resources/lanczos-vertex.glsl", ":/resources/lanczos-fragment.glsl"); if (m_shader->isValid()) { m_shader->bind(); m_uTexUnit = m_shader->uniformLocation("texUnit"); m_uKernel = m_shader->uniformLocation("kernel"); m_uOffsets = m_shader->uniformLocation("offsets"); m_shader->unbind(); } else { kDebug(1212) << "Shader is not valid"; m_shader = 0; } } #endif } void LanczosFilter::updateOffscreenSurfaces() { #ifdef KWIN_HAVE_OPENGL_COMPOSITING int w = displayWidth(); int h = displayHeight(); if ( !GLTexture::NPOTTextureSupported() ) { w = nearestPowerOfTwo( w ); h = nearestPowerOfTwo( h ); } if ( !m_offscreenTex || m_offscreenTex->width() != w || m_offscreenTex->height() != h ) { if ( m_offscreenTex ) { delete m_offscreenTex; delete m_offscreenTarget; } m_offscreenTex = new GLTexture( w, h ); m_offscreenTex->setFilter( GL_LINEAR ); m_offscreenTex->setWrapMode( GL_CLAMP_TO_EDGE ); m_offscreenTarget = new GLRenderTarget( m_offscreenTex ); } #endif } 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 ); } void LanczosFilter::createKernel( float delta, int *size ) { 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 ); 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; } memset(m_kernel, 0, 25 * sizeof(QVector4D)); // Normalize the kernel for ( int i = 0; i < kernelSize; i++ ) { const float val = values[i] / sum; m_kernel[i] = QVector4D( val, val, val, val ); } *size = kernelSize; } void LanczosFilter::createOffsets( int count, float width, Qt::Orientation direction ) { memset(m_offsets, 0, 25 * sizeof(QVector2D)); for ( int i = 0; i < count; i++ ) { m_offsets[i] = ( direction == Qt::Horizontal ) ? QVector2D( i / width, 0 ) : QVector2D( 0, i / width ); } } void LanczosFilter::performPaint( EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data ) { #ifdef KWIN_HAVE_OPENGL_COMPOSITING if( effects->compositingType() == KWin::OpenGLCompositing && KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects ) { if (!m_inited) init(); const QRect screenRect = Workspace::self()->clientArea( ScreenArea, w->screen(), w->desktop() ); // window geometry may not be bigger than screen geometry to fit into the FBO if ( m_shader && w->width() <= screenRect.width() && w->height() <= screenRect.height() ) { double left = 0; double top = 0; double right = w->width(); double bottom = w->height(); foreach( const WindowQuad& quad, data.quads ) { // we need this loop to include the decoration padding left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } double width = right - left; double height = bottom - top; if( width > screenRect.width() || height > screenRect.height() ) { // window with padding does not fit into the framebuffer // so cut of the shadow left = 0; top = 0; width = w->width(); height = w->height(); } int tx = data.xTranslate + w->x() + left*data.xScale; int ty = data.yTranslate + w->y() + top*data.yScale; int tw = width*data.xScale; int th = height*data.yScale; int sw = width; int sh = height; WindowPaintData thumbData = data; thumbData.xScale = 1.0; thumbData.yScale = 1.0; thumbData.xTranslate = -w->x() - left; thumbData.yTranslate = -w->y() - top; // Bind the offscreen FBO and draw the window on it unscaled updateOffscreenSurfaces(); effects->pushRenderTarget( m_offscreenTarget ); glClear( GL_COLOR_BUFFER_BIT ); w->sceneWindow()->performPaint( mask, QRegion(0, 0, sw, sh), 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, m_offscreenTex->height() - sh, sw, sh ); // Set up the shader for horizontal scaling float dx = sw / float(tw); int kernelSize; createKernel( dx, &kernelSize ); createOffsets( kernelSize, sw, Qt::Horizontal ); m_shader->bind(); glUniform1i( m_uTexUnit, 0 ); glUniform2fv( m_uOffsets, 25, (const GLfloat*)m_offsets ); glUniform4fv( m_uKernel, 25, (const GLfloat*)m_kernel ); // 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); createKernel( dy, &kernelSize ); createOffsets( kernelSize, m_offscreenTex->height(), Qt::Vertical ); glUniform2fv( m_uOffsets, 25, (const GLfloat*)m_offsets ); glUniform4fv( m_uKernel, 25, (const GLfloat*)m_kernel ); float sx2 = tw / float(m_offscreenTex->width()); float sy2 = 1 - (sh / float(m_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. m_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(); m_offscreenTex->unbind(); m_shader->unbind(); // Delete the offscreen surface after 5 seconds m_timer.start( 5000, this ); return; } } // if ( effects->compositingType() == KWin::OpenGLCompositing ) #endif w->sceneWindow()->performPaint( mask, region, data ); } // End of function void LanczosFilter::timerEvent( QTimerEvent *event ) { #ifdef KWIN_HAVE_OPENGL_COMPOSITING if (event->timerId() == m_timer.timerId()) { m_timer.stop(); delete m_offscreenTarget; delete m_offscreenTex; m_offscreenTarget = 0; m_offscreenTex = 0; } #endif } } // namespace