kwin/lanczosfilter.cpp
Fredrik Höglund 119c06e403 Make the lanczos shader use a fixed number of iterations in the loop.
This makes it possible for the GLSL compiler to unroll it, which also
avoids the need to use relative addressing. With this change the shader
should hopefully work with the R300G driver.

The unused kernel weights are set to zero so they don't contribute
to the end result.

Thanks to Tom Stellard and Marek Olšák for their suggestions on how
to solve this problem.

CCBUG: 243191

svn path=/trunk/KDE/kdebase/workspace/; revision=1175021
2010-09-13 22:03:21 +00:00

323 lines
10 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2010 by 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. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "lanczosfilter.h"
#include "effects.h"
#ifdef KWIN_HAVE_OPENGL_COMPOSITING
#include <kwinglutils.h>
#endif
#include <kwineffects.h>
#include <KDE/KGlobalSettings>
#include <qmath.h>
#include <cmath>
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<float> 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