/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2007 Lubos Lunak Copyright (C) 2008 Lucas Murray Copyright (C) 2008 Martin Gräßlin 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 "shadow.h" #include "shadow_helper.h" #include #include #include #include #include #include #include namespace KWin { KWIN_EFFECT( shadow, ShadowEffect ) ShadowEffect::ShadowEffect() : shadowSize( 0 ) { reconfigure( ReconfigureAll ); connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), this, SLOT(updateShadowColor())); } ShadowEffect::~ShadowEffect() { #ifdef KWIN_HAVE_OPENGL_COMPOSITING for( int i = 0; i < mShadowTextures.size(); i++ ) for( int j = 0; j < mShadowTextures.at( i ).size(); j++ ) delete mShadowTextures.at( i ).at( j ); for( int i = 0; i < mDefaultShadowTextures.size(); i++ ) delete mDefaultShadowTextures.at( i ); #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING for( int i = 0; i < mShadowPics.size(); i++ ) for( int j = 0; j < mShadowPics.at( i ).size(); j++ ) delete mShadowPics.at( i ).at( j ); for( int i = 0; i < mDefaultShadowPics.size(); i++ ) delete mDefaultShadowPics.at( i ); #endif } void ShadowEffect::reconfigure( ReconfigureFlags ) { KConfigGroup conf = effects->effectConfig("Shadow"); shadowXOffset = conf.readEntry( "XOffset", 0 ); shadowYOffset = conf.readEntry( "YOffset", 3 ); shadowOpacity = conf.readEntry( "Opacity", 0.25 ); shadowFuzzyness = conf.readEntry( "Fuzzyness", 10 ); shadowSize = conf.readEntry( "Size", 5 ); intensifyActiveShadow = conf.readEntry( "IntensifyActiveShadow", true ); updateShadowColor(); forceDecorated = conf.readEntry( "forceDecoratedToDefault", false ); forceUndecorated = conf.readEntry( "forceUndecoratedToDefault", false ); forceOther = conf.readEntry( "forceOtherToDefault", false ); // Load decoration shadow related things bool reconfiguring = false; if( mShadowQuadTypes.count() ) reconfiguring = true; mShadowQuadTypes.clear(); // Changed decoration? TODO: Unregister? #ifdef KWIN_HAVE_OPENGL_COMPOSITING if( effects->compositingType() == OpenGLCompositing ) { // Delete any other textures in memory for( int i = 0; i < mShadowTextures.size(); i++ ) for( int j = 0; j < mShadowTextures.at( i ).size(); j++ ) delete mShadowTextures.at( i ).at( j ); mShadowTextures.clear(); for( int i = 0; i < mDefaultShadowTextures.size(); i++ ) delete mDefaultShadowTextures.at( i ); mDefaultShadowTextures.clear(); // Create decoration shadows if( effects->hasDecorationShadows() ) { QList< QList > shadowImages = effects->shadowTextures(); for( int i = 0; i < shadowImages.size(); i++ ) { mShadowQuadTypes.append( effects->newWindowQuadType() ); QList textures; for( int j = 0; j < shadowImages.at( i ).size(); j++ ) textures.append( new GLTexture( shadowImages.at( i ).at( j ))); mShadowTextures.append( textures ); } } // Create default textures mDefaultShadowQuadType = effects->newWindowQuadType(); // TODO: Unregister? QImage shadowImage( KGlobal::dirs()->findResource( "data", "kwin/shadow-texture.png" )); int hw = shadowImage.width() / 2; int hh = shadowImage.height() / 2; mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( 0, 0, hw, hh ))); mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, 0, 1, hh ))); mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, 0, hw, hh ))); mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( 0, hh, hw, 1 ))); mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, hh, 1, 1 ))); mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, hh, hw, 1 ))); mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( 0, hh, hw, hh ))); mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, hh, 1, hh ))); mDefaultShadowTextures.append( new GLTexture( shadowImage.copy( hw, hh, hw, hh ))); } #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING if( effects->compositingType() == XRenderCompositing ) { // Delete any other pictures in memory for( int i = 0; i < mShadowPics.size(); i++ ) for( int j = 0; j < mShadowPics.at( i ).size(); j++ ) delete mShadowPics.at( i ).at( j ); mShadowPics.clear(); for( int i = 0; i < mDefaultShadowPics.size(); i++ ) delete mDefaultShadowPics.at( i ); mDefaultShadowPics.clear(); // Create decoration pictures if( effects->hasDecorationShadows() ) { QList< QList > shadowImages = effects->shadowTextures(); for( int i = 0; i < shadowImages.size(); i++ ) { mShadowQuadTypes.append( effects->newWindowQuadType() ); QList pictures; for( int j = 0; j < shadowImages.at( i ).size(); j++ ) pictures.append( new XRenderPicture( QPixmap::fromImage( shadowImages.at( i ).at( j )))); mShadowPics.append( pictures ); } } // Create default pictures mDefaultShadowQuadType = effects->newWindowQuadType(); // TODO: Unregister? QPixmap shadowPixmap( KGlobal::dirs()->findResource( "data", "kwin/shadow-texture.png" )); shadowPixmap = shadowPixmap.scaled( QSize( shadowFuzzyness * 4, shadowFuzzyness * 4 ), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); int hw = shadowPixmap.width() / 2; int hh = shadowPixmap.height() / 2; mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( 0, 0, hw, hh ))); mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, 0, 1, hh ))); mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, 0, hw, hh ))); mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( 0, hh, hw, 1 ))); mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, hh, 1, 1 ))); mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, hh, hw, 1 ))); mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( 0, hh, hw, hh ))); mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, hh, 1, hh ))); mDefaultShadowPics.append( new XRenderPicture( shadowPixmap.copy( hw, hh, hw, hh ))); // Apply repeat attribute to all pictures XRenderPictureAttributes pa; pa.repeat = true; for( int i = 0; i < mShadowPics.size(); i++ ) for( int j = 0; j < mShadowPics.at( i ).size(); j++ ) XRenderChangePicture( display(), *mShadowPics.at( i ).at( j ), CPRepeat, &pa ); for( int i = 0; i < mDefaultShadowPics.size(); i++ ) XRenderChangePicture( display(), *mDefaultShadowPics.at( i ), CPRepeat, &pa ); } #endif if( reconfiguring ) { // Force rebuild of all quads to clear their caches foreach( EffectWindow *w, effects->stackingOrder() ) w->buildQuads( true ); } } void ShadowEffect::updateShadowColor() { KConfigGroup conf = effects->effectConfig("Shadow"); shadowColor = conf.readEntry( "Color", schemeShadowColor() ); } QRect ShadowEffect::shadowRectangle( EffectWindow* w, const QRect& windowRectangle ) const { QRectF shadowRect; bool shadowDefined = false; if( effects->hasDecorationShadows() ) { // TODO: This function is called for EVERY damage, we need to cache this // (But how? Decoration shadow can change shape if it wanted to) if( w->hasDecoration() && !forceDecorated ) { // Decorated windows must be normal windows foreach( const QRect &r, w->shadowQuads( ShadowBorderedActive )) { shadowDefined = true; shadowRect |= r; } } else if( w->isNormalWindow() && !forceUndecorated ) { // No decoration on a normal window foreach( const QRect &r, w->shadowQuads( ShadowBorderlessActive )) { shadowDefined = true; shadowRect |= r; } } else if( !forceOther ) { // All other undecorated windows foreach( const QRect &r, w->shadowQuads( ShadowOther )) { shadowDefined = true; shadowRect |= r; } } } if( !shadowDefined ) { int shadowGrow = shadowFuzzyness + shadowSize; return windowRectangle.adjusted( shadowXOffset - shadowGrow, shadowYOffset - shadowGrow, shadowXOffset + shadowGrow, shadowYOffset + shadowGrow); } return windowRectangle.adjusted( qMin( shadowRect.x(), qreal(0.0) ), qMin( shadowRect.y(), qreal(0.0) ), qMax( shadowRect.x() + shadowRect.width() - w->width(), qreal(0.0) ), qMax( shadowRect.y() + shadowRect.height() - w->height(), qreal(0.0) ) ); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING static ScreenPaintData gScreenData; #endif void ShadowEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data ) { shadowDatas.clear(); #ifdef KWIN_HAVE_XRENDER_COMPOSITING if ((mask & PAINT_SCREEN_TRANSFORMED) && (effects->compositingType() == XRenderCompositing)) // TODO: copy constructor? { gScreenData.xTranslate = data.xTranslate; gScreenData.yTranslate = data.yTranslate; gScreenData.xScale = data.xScale; gScreenData.yScale = data.yScale; } #endif // Draw windows effects->paintScreen( mask, region, data ); // Draw shadows drawQueuedShadows( 0 ); } void ShadowEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time ) { if( useShadow( w )) data.paint |= shadowRectangle( w, data.paint.boundingRect() ); effects->prePaintWindow( w, data, time ); } void ShadowEffect::drawWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) { // Whether the shadow drawing can be delayed or not. bool optimize = !( mask & ( PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS | PAINT_WINDOW_TRANSLUCENT )); if( !optimize ) { // Transformed or translucent windows are drawn bottom-to-top, so // first we need to draw all queued shadows. drawQueuedShadows( w ); } if( useShadow( w )) { if( !optimize ) { // For translucent windows, shadow needs to be drawn before the // window itself. drawShadow( w, mask, region, data ); } else { // For opaque windows, just schedule the shadow to be drawn later ShadowData d(w, data); d.clip = w->shape().translated( w->x(), w->y()); if( !shadowDatas.isEmpty()) d.clip |= shadowDatas.last().clip; d.mask = mask; foreach( const QRect &r, region.rects() ) d.region |= shadowRectangle( w, r ); d.region &= region; shadowDatas.append(d); } } effects->drawWindow( w, mask, region, data ); } void ShadowEffect::buildQuads( EffectWindow* w, WindowQuadList& quadList ) { bool shadowDefined = false; if( effects->hasDecorationShadows() ) { // TODO: shadowQuads() is allowed to return different quads for // active and inactive shadows. Is implementing it worth // the performance drop? int id = 0; if( w->hasDecoration() && !forceDecorated ) { // Decorated windows must be normal windows foreach( const QRect &r, w->shadowQuads( ShadowBorderedActive )) { shadowDefined = true; WindowQuad quad( mShadowQuadTypes.at( effects->shadowTextureList( ShadowBorderedActive )), id++ ); quad[ 0 ] = WindowVertex( r.x(), r.y(), 0, 1 ); quad[ 1 ] = WindowVertex( r.x() + r.width(), r.y(), 1, 1 ); quad[ 2 ] = WindowVertex( r.x() + r.width(), r.y() + r.height(), 1, 0 ); quad[ 3 ] = WindowVertex( r.x(), r.y() + r.height(), 0, 0 ); quadList.append( quad ); } } else if( w->isNormalWindow() && !forceUndecorated ) { // No decoration on a normal window foreach( const QRect &r, w->shadowQuads( ShadowBorderlessActive )) { shadowDefined = true; WindowQuad quad( mShadowQuadTypes.at( effects->shadowTextureList( ShadowBorderlessActive )), id++ ); quad[ 0 ] = WindowVertex( r.x(), r.y(), 0, 1 ); quad[ 1 ] = WindowVertex( r.x() + r.width(), r.y(), 1, 1 ); quad[ 2 ] = WindowVertex( r.x() + r.width(), r.y() + r.height(), 1, 0 ); quad[ 3 ] = WindowVertex( r.x(), r.y() + r.height(), 0, 0 ); quadList.append( quad ); } } else if( !forceOther ) { // All other undecorated windows foreach( const QRect &r, w->shadowQuads( ShadowOther )) { shadowDefined = true; WindowQuad quad( mShadowQuadTypes.at( effects->shadowTextureList( ShadowOther )), id++ ); quad[ 0 ] = WindowVertex( r.x(), r.y(), 0, 1 ); quad[ 1 ] = WindowVertex( r.x() + r.width(), r.y(), 1, 1 ); quad[ 2 ] = WindowVertex( r.x() + r.width(), r.y() + r.height(), 1, 0 ); quad[ 3 ] = WindowVertex( r.x(), r.y() + r.height(), 0, 0 ); quadList.append( quad ); } } } if( !shadowDefined ) { //TODO: add config option to not have shadows for menus, etc. // Make our own shadow as the decoration doesn't support it int fuzzy = shadowFuzzyness; // Shadow's size must be a least 2*fuzzy in both directions (or the corners will be broken) int width = qMax( fuzzy * 2, w->width() + 2 * shadowSize ); int height = qMax( fuzzy * 2, w->height() + 2 * shadowSize ); double x1, y1, x2, y2; int id = 0; // top-left x1 = shadowXOffset - shadowSize + 0 - fuzzy; y1 = shadowYOffset - shadowSize + 0 - fuzzy; x2 = shadowXOffset - shadowSize + 0 + fuzzy; y2 = shadowYOffset - shadowSize + 0 + fuzzy; WindowQuad topLeftQuad( mDefaultShadowQuadType, id++ ); topLeftQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); topLeftQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); topLeftQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); topLeftQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( topLeftQuad ); // top x1 = shadowXOffset - shadowSize + 0 + fuzzy; y1 = shadowYOffset - shadowSize + 0 - fuzzy; x2 = shadowXOffset - shadowSize + width - fuzzy; y2 = shadowYOffset - shadowSize + 0 + fuzzy; WindowQuad topQuad( mDefaultShadowQuadType, id++ ); topQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); topQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); topQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); topQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( topQuad ); // top-right x1 = shadowXOffset - shadowSize + width - fuzzy; y1 = shadowYOffset - shadowSize + 0 - fuzzy; x2 = shadowXOffset - shadowSize + width + fuzzy; y2 = shadowYOffset - shadowSize + 0 + fuzzy; WindowQuad topRightQuad( mDefaultShadowQuadType, id++ ); topRightQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); topRightQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); topRightQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); topRightQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( topRightQuad ); // left x1 = shadowXOffset - shadowSize + 0 - fuzzy; y1 = shadowYOffset - shadowSize + 0 + fuzzy; x2 = shadowXOffset - shadowSize + 0 + fuzzy; y2 = shadowYOffset - shadowSize + height - fuzzy; WindowQuad leftQuad( mDefaultShadowQuadType, id++ ); leftQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); leftQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); leftQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); leftQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( leftQuad ); // center x1 = shadowXOffset - shadowSize + 0 + fuzzy; y1 = shadowYOffset - shadowSize + 0 + fuzzy; x2 = shadowXOffset - shadowSize + width - fuzzy; y2 = shadowYOffset - shadowSize + height - fuzzy; WindowQuad contentsQuad( mDefaultShadowQuadType, id++ ); contentsQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); contentsQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); contentsQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); contentsQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( contentsQuad ); // right x1 = shadowXOffset - shadowSize + width - fuzzy; y1 = shadowYOffset - shadowSize + 0 + fuzzy; x2 = shadowXOffset - shadowSize + width + fuzzy; y2 = shadowYOffset - shadowSize + height - fuzzy; WindowQuad rightQuad( mDefaultShadowQuadType, id++ ); rightQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); rightQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); rightQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); rightQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( rightQuad ); // bottom-left x1 = shadowXOffset - shadowSize + 0 - fuzzy; y1 = shadowYOffset - shadowSize + height - fuzzy; x2 = shadowXOffset - shadowSize + 0 + fuzzy; y2 = shadowYOffset - shadowSize + height + fuzzy; WindowQuad bottomLeftQuad( mDefaultShadowQuadType, id++ ); bottomLeftQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); bottomLeftQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); bottomLeftQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); bottomLeftQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( bottomLeftQuad ); // bottom x1 = shadowXOffset - shadowSize + 0 + fuzzy; y1 = shadowYOffset - shadowSize + height - fuzzy; x2 = shadowXOffset - shadowSize + width - fuzzy; y2 = shadowYOffset - shadowSize + height + fuzzy; WindowQuad bottomQuad( mDefaultShadowQuadType, id++ ); bottomQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); bottomQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); bottomQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); bottomQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( bottomQuad ); // bottom-right x1 = shadowXOffset - shadowSize + width - fuzzy; y1 = shadowYOffset - shadowSize + height - fuzzy; x2 = shadowXOffset - shadowSize + width + fuzzy; y2 = shadowYOffset - shadowSize + height + fuzzy; WindowQuad bottomRightQuad( mDefaultShadowQuadType, id++ ); bottomRightQuad[ 0 ] = WindowVertex( x1, y1, 0, 1 ); bottomRightQuad[ 1 ] = WindowVertex( x2, y1, 1, 1 ); bottomRightQuad[ 2 ] = WindowVertex( x2, y2, 1, 0 ); bottomRightQuad[ 3 ] = WindowVertex( x1, y2, 0, 0 ); quadList.append( bottomRightQuad ); } // This is called for menus, tooltips, windows where the user has disabled borders and shaped windows effects->buildQuads( w, quadList ); } QRect ShadowEffect::transformWindowDamage( EffectWindow* w, const QRect& r ) { if( !useShadow( w )) return effects->transformWindowDamage( w, r ); QRect r2 = r | shadowRectangle( w, r ); return effects->transformWindowDamage( w, r2 ); } void ShadowEffect::windowClosed( EffectWindow* c ) { effects->addRepaint( shadowRectangle( c, c->geometry() )); } bool ShadowEffect::useShadow( EffectWindow* w ) const { return !w->isDeleted() && !w->isDesktop() && !w->isDock() // popups may have shadow even if shaped, their shape is almost rectangular && ( !w->hasOwnShape() || w->isDropdownMenu() || w->isPopupMenu() || w->isComboBox()); } void ShadowEffect::addQuadVertices(QVector& verts, float x1, float y1, float x2, float y2) const { verts << x1 << y1; verts << x1 << y2; verts << x2 << y2; verts << x2 << y1; } void ShadowEffect::drawQueuedShadows( EffectWindow* behindWindow ) { QList newShadowDatas; QList thisTime; EffectWindowList stack = effects->stackingOrder(); foreach( const ShadowData &d, shadowDatas ) { // If behindWindow is given then only render shadows of the windows // that are behind that window. if( !behindWindow || stack.indexOf(d.w) < stack.indexOf(behindWindow)) thisTime.append(d); else newShadowDatas.append(d); } if( thisTime.count() ) { // Render them in stacking order foreach( EffectWindow *w, stack ) for( int i = 0; i < thisTime.size(); i++ ) { // Cannot use foreach() due to thisTime.removeOne() const ShadowData d = thisTime.at(i); if( d.w == w ) { drawShadow( d.w, d.mask, d.region.subtracted( d.clip ), d.data ); thisTime.removeAt( i ); break; } } } // Render the rest on the top (For menus, etc.) foreach( const ShadowData &d, thisTime ) drawShadow( d.w, d.mask, d.region.subtracted( d.clip ), d.data ); shadowDatas = newShadowDatas; } // Modified version of SceneOpenGL::Window::prepareRenderStates() from scene_opengl.cpp void ShadowEffect::prepareRenderStates( GLTexture *texture, double opacity, double brightness, double saturation ) { #ifdef KWIN_HAVE_OPENGL_COMPOSITING // setup blending of transparent windows glPushAttrib( GL_ENABLE_BIT ); /*if( saturation != 1.0 && texture->saturationSupported() ) { // First we need to get the color from [0; 1] range to [0.5; 1] range glActiveTexture( GL_TEXTURE0 ); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA ); const float scale_constant[] = { 1.0, 1.0, 1.0, 0.5 }; glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, scale_constant ); texture->bind(); // Then we take dot product of the result of previous pass and // saturation_constant. This gives us completely unsaturated // (greyscale) image // Note that both operands have to be in range [0.5; 1] since opengl // automatically substracts 0.5 from them glActiveTexture( GL_TEXTURE1 ); float saturation_constant[] = { 0.5 + 0.5*0.30, 0.5 + 0.5*0.59, 0.5 + 0.5*0.11, saturation }; glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR ); glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, saturation_constant ); texture->bind(); // Finally we need to interpolate between the original image and the // greyscale image to get wanted level of saturation glActiveTexture( GL_TEXTURE2 ); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE0 ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA ); glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, saturation_constant ); // Also replace alpha by primary color's alpha here glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PRIMARY_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA ); // And make primary color contain the wanted opacity glColor4f( opacity, opacity, opacity, opacity ); texture->bind(); if( brightness != 1.0 ) { glActiveTexture( GL_TEXTURE3 ); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE ); glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR ); // The color has to be multiplied by both opacity and brightness float opacityByBrightness = opacity * brightness; glColor4f( opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity ); glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE ); glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS ); glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA ); texture->bind(); } glActiveTexture(GL_TEXTURE0 ); } else*/ if( opacity != 1.0 || brightness != 1.0 ) { // the window is additionally configured to have its opacity adjusted, // do it float opacityByBrightness = opacity * brightness; glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); glColor4f( opacityByBrightness, opacityByBrightness, opacityByBrightness, opacity); } #endif } // Modified version of SceneOpenGL::Window::restoreRenderStates() from scene_opengl.cpp void ShadowEffect::restoreRenderStates( GLTexture *texture, double opacity, double brightness, double saturation ) { #ifdef KWIN_HAVE_OPENGL_COMPOSITING if( opacity != 1.0 || saturation != 1.0 || brightness != 1.0 ) { /*if( saturation != 1.0 && texture->saturationSupported()) { glActiveTexture(GL_TEXTURE3); glDisable( texture->target() ); glActiveTexture(GL_TEXTURE2); glDisable( texture->target() ); glActiveTexture(GL_TEXTURE1); glDisable( texture->target() ); glActiveTexture(GL_TEXTURE0); }*/ glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); glColor4f( 0, 0, 0, 0 ); } glPopAttrib(); // ENABLE_BIT #endif } void ShadowEffect::drawShadowQuadOpenGL( GLTexture *texture, QVector verts, QVector texCoords, QColor color, QRegion region, float opacity, float brightness, float saturation ) { #ifdef KWIN_HAVE_OPENGL_COMPOSITING if( color.isValid() ) glColor4f( color.redF(), color.greenF(), color.blueF(), 1.0 ); else glColor4f( 1.0, 1.0, 1.0, 1.0 ); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); prepareRenderStates( texture, opacity, brightness, saturation ); texture->bind(); texture->enableNormalizedTexCoords(); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); renderGLGeometry( region, 4, verts.data(), texCoords.data() ); texture->disableNormalizedTexCoords(); texture->unbind(); restoreRenderStates( texture, opacity, brightness, saturation ); #endif } void ShadowEffect::drawShadowQuadXRender( XRenderPicture *picture, QRect rect, float xScale, float yScale, QColor color, float opacity, float brightness, float saturation ) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING XRenderColor xc; if( color.isValid() ) xc = preMultiply( color, opacity ); else xc = preMultiply( QColor( 255, 255, 255 ), opacity ); XRenderPicture fill = xRenderFill( &xc ); // Scale if required if( xScale != 1.0 || yScale != 1.0 ) { XTransform xform = {{ { XDoubleToFixed( 1.0 / xScale ), XDoubleToFixed( 0.0 ), XDoubleToFixed( 0.0 ) }, { XDoubleToFixed( 0.0 ), XDoubleToFixed( 1.0 / yScale ), XDoubleToFixed( 0.0 ) }, { XDoubleToFixed( 0.0 ), XDoubleToFixed( 0.0 ), XDoubleToFixed( 1.0 ) } }}; XRenderSetPictureTransform( display(), *picture, &xform ); } // Render it // TODO: This always uses the fast filter, detect when to use smooth instead if( color.isValid() ) XRenderComposite( display(), PictOpOver, fill, *picture, effects->xrenderBufferPicture(), 0, 0, 0, 0, rect.x(), rect.y(), rect.width(), rect.height() ); else XRenderComposite( display(), PictOpOver, *picture, fill, effects->xrenderBufferPicture(), 0, 0, 0, 0, rect.x(), rect.y(), rect.width(), rect.height() ); // Fake brightness by overlaying black // Cannot use XRenderFillRectangle() due to ARGB XRenderColor col = { 0, 0, 0, 0xffff * ( 1 - brightness ) * opacity }; fill = xRenderFill( &col ); XRenderComposite( display(), PictOpOver, fill, *picture, effects->xrenderBufferPicture(), 0, 0, 0, 0, rect.x(), rect.y(), rect.width(), rect.height() ); // Return to scale to 1.0 if( xScale != 1.0 || yScale != 1.0 ) { XTransform xform = {{ { XDoubleToFixed( 1.0 ), XDoubleToFixed( 0.0 ), XDoubleToFixed( 0.0 ) }, { XDoubleToFixed( 0.0 ), XDoubleToFixed( 1.0 ), XDoubleToFixed( 0.0 ) }, { XDoubleToFixed( 0.0 ), XDoubleToFixed( 0.0 ), XDoubleToFixed( 1.0 ) } }}; XRenderSetPictureTransform( display(), *picture, &xform ); } #endif } void ShadowEffect::drawShadow( EffectWindow* window, int mask, QRegion region, const WindowPaintData& data ) { // Don't allow windows to cast shadows on other displays QRegion clipperGeom; for( int screen = 0; screen < effects->numScreens(); screen++ ) { QRect screenGeom = effects->clientArea( ScreenArea, screen, 0 ); if( !( window->geometry() & screenGeom ).isNull() ) clipperGeom |= screenGeom; } PaintClipper pc( clipperGeom ); #ifdef KWIN_HAVE_OPENGL_COMPOSITING if( effects->compositingType() == OpenGLCompositing ) { glPushAttrib( GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT ); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); foreach( const WindowQuad &quad, data.quads ) { if( !mShadowQuadTypes.contains( quad.type() ) && quad.type() != mDefaultShadowQuadType ) continue; // Not a shadow quad glPushMatrix(); // Use the window's top-left as the origin glTranslatef( window->x(), window->y(), 0 ); if( mask & PAINT_WINDOW_TRANSFORMED ) glTranslatef( data.xTranslate, data.yTranslate, data.zTranslate ); if(( mask & PAINT_WINDOW_TRANSFORMED ) && ( data.xScale != 1 || data.yScale != 1 )) glScalef( data.xScale, data.yScale, data.zScale ); if(( mask & PAINT_WINDOW_TRANSFORMED ) && data.rotation ) { glTranslatef( data.rotation->xRotationPoint, data.rotation->yRotationPoint, data.rotation->zRotationPoint ); float xAxis = 0.0; float yAxis = 0.0; float zAxis = 0.0; switch( data.rotation->axis ) { case RotationData::XAxis: xAxis = 1.0; break; case RotationData::YAxis: yAxis = 1.0; break; case RotationData::ZAxis: zAxis = 1.0; break; } glRotatef( data.rotation->angle, xAxis, yAxis, zAxis ); glTranslatef( -data.rotation->xRotationPoint, -data.rotation->yRotationPoint, -data.rotation->zRotationPoint ); } // Create our polygon QVector verts, texcoords; verts.reserve(8); texcoords.reserve(8); verts << quad[0].x() << quad[0].y(); verts << quad[1].x() << quad[1].y(); verts << quad[2].x() << quad[2].y(); verts << quad[3].x() << quad[3].y(); texcoords << quad[0].textureX() << quad[0].textureY(); texcoords << quad[1].textureX() << quad[1].textureY(); texcoords << quad[2].textureX() << quad[2].textureY(); texcoords << quad[3].textureX() << quad[3].textureY(); // Work out which texture to use int texture = mShadowQuadTypes.indexOf( quad.type() ); if( texture != -1 && texture < mShadowTextures.size() ) // TODO: Needed? { // Render it! // Cheat a little, assume the active and inactive shadows have identical quads if( effects->hasDecorationShadows() ) { if( window->hasDecoration() && effects->shadowTextureList( ShadowBorderedActive ) == texture ) { // Decorated windows // Active shadow drawShadowQuadOpenGL( mShadowTextures.at( texture ).at( quad.id() ), verts, texcoords, QColor(), region, data.opacity * window->shadowOpacity( ShadowBorderedActive ), data.brightness * window->shadowBrightness( ShadowBorderedActive ), data.saturation * window->shadowSaturation( ShadowBorderedActive )); // Inactive shadow texture = effects->shadowTextureList( ShadowBorderedInactive ); drawShadowQuadOpenGL( mShadowTextures.at( texture ).at( quad.id() ), verts, texcoords, QColor(), region, data.opacity * window->shadowOpacity( ShadowBorderedInactive ), data.brightness * window->shadowBrightness( ShadowBorderedInactive ), data.saturation * window->shadowSaturation( ShadowBorderedInactive )); } else if( effects->shadowTextureList( ShadowBorderlessActive ) == texture ) { // Decoration-less normal windows if( effects->activeWindow() == window ) { drawShadowQuadOpenGL( mShadowTextures.at( texture ).at( quad.id() ), verts, texcoords, QColor(), region, data.opacity * window->shadowOpacity( ShadowBorderlessActive ), data.brightness * window->shadowBrightness( ShadowBorderlessActive ), data.saturation * window->shadowSaturation( ShadowBorderlessActive )); } else { texture = effects->shadowTextureList( ShadowBorderlessInactive ); drawShadowQuadOpenGL( mShadowTextures.at( texture ).at( quad.id() ), verts, texcoords, QColor(), region, data.opacity * window->shadowOpacity( ShadowBorderlessInactive ), data.brightness * window->shadowBrightness( ShadowBorderlessInactive ), data.saturation * window->shadowSaturation( ShadowBorderlessInactive )); } } else { // Other windows drawShadowQuadOpenGL( mShadowTextures.at( texture ).at( quad.id() ), verts, texcoords, QColor(), region, data.opacity * window->shadowOpacity( ShadowOther ), data.brightness * window->shadowBrightness( ShadowOther ), data.saturation * window->shadowSaturation( ShadowOther )); } } } if( quad.type() == mDefaultShadowQuadType ) { // Default shadow float opacity = shadowOpacity; if( intensifyActiveShadow && window == effects->activeWindow() ) opacity = 1 - ( 1 - shadowOpacity ) * ( 1 - shadowOpacity ); drawShadowQuadOpenGL( mDefaultShadowTextures.at( quad.id() ), verts, texcoords, shadowColor, region, data.opacity * opacity, data.brightness, data.saturation ); } glPopMatrix(); } glPopAttrib(); } #endif #ifdef KWIN_HAVE_XRENDER_COMPOSITING if( effects->compositingType() == XRenderCompositing ) { XRenderSetPictureClipRegion( display(), effects->xrenderBufferPicture(), region.handle() ); foreach( const WindowQuad &quad, data.quads ) { if( !mShadowQuadTypes.contains( quad.type() ) && quad.type() != mDefaultShadowQuadType ) continue; // Not a shadow quad // Determine transformed quad position QRect windowRect = window->geometry(); float xScale = 1.0; float yScale = 1.0; float xTranslate = 0.0; float yTranslate = 0.0; if( mask & PAINT_SCREEN_TRANSFORMED) { xScale = gScreenData.xScale; yScale = gScreenData.yScale; xTranslate += ( xScale - 1.0 ) * windowRect.x() + gScreenData.xTranslate; yTranslate += ( yScale - 1.0 ) * windowRect.y() + gScreenData.yTranslate; } if( mask & PAINT_WINDOW_TRANSFORMED) { xTranslate += xScale * data.xTranslate; yTranslate += yScale * data.yTranslate; xScale *= data.xScale; yScale *= data.yScale; } QRect quadRect( window->x() + quad[0].x() * xScale + xTranslate, window->y() + quad[0].y() * yScale + yTranslate, ( quad[2].x() - quad[0].x() ) * xScale, ( quad[2].y() - quad[0].y() ) * yScale ); // Work out which texture to use int texture = mShadowQuadTypes.indexOf( quad.type() ); if( texture != -1 ) { // Render it! // Cheat a little, assume the active and inactive shadows have identical quads if( effects->hasDecorationShadows() ) { if( window->hasDecoration() && effects->shadowTextureList( ShadowBorderedActive ) == texture ) { // Decorated windows // Active shadow drawShadowQuadXRender( mShadowPics.at( texture ).at( quad.id() ), quadRect, xScale, yScale, QColor(), data.opacity * window->shadowOpacity( ShadowBorderedActive ), data.brightness * window->shadowBrightness( ShadowBorderedActive ), data.saturation * window->shadowSaturation( ShadowBorderedActive )); // Inactive shadow texture = effects->shadowTextureList( ShadowBorderedInactive ); drawShadowQuadXRender( mShadowPics.at( texture ).at( quad.id() ), quadRect, xScale, yScale, QColor(), data.opacity * window->shadowOpacity( ShadowBorderedInactive ), data.brightness * window->shadowBrightness( ShadowBorderedInactive ), data.saturation * window->shadowSaturation( ShadowBorderedInactive )); } else if( effects->shadowTextureList( ShadowBorderlessActive ) == texture ) { // Decoration-less normal windows if( effects->activeWindow() == window ) { // Active shadow drawShadowQuadXRender( mShadowPics.at( texture ).at( quad.id() ), quadRect, xScale, yScale, QColor(), data.opacity * window->shadowOpacity( ShadowBorderlessActive ), data.brightness * window->shadowBrightness( ShadowBorderlessActive ), data.saturation * window->shadowSaturation( ShadowBorderlessActive )); } else { // Inactive shadow texture = effects->shadowTextureList( ShadowBorderedInactive ); drawShadowQuadXRender( mShadowPics.at( texture ).at( quad.id() ), quadRect, xScale, yScale, QColor(), data.opacity * window->shadowOpacity( ShadowBorderlessInactive ), data.brightness * window->shadowBrightness( ShadowBorderlessInactive ), data.saturation * window->shadowSaturation( ShadowBorderlessInactive )); } } else { // Other windows drawShadowQuadXRender( mShadowPics.at( texture ).at( quad.id() ), quadRect, xScale, yScale, QColor(), data.opacity * window->shadowOpacity( ShadowOther ), data.brightness * window->shadowBrightness( ShadowOther ), data.saturation * window->shadowSaturation( ShadowOther )); } } } if( quad.type() == mDefaultShadowQuadType ) { // Default shadow float opacity = shadowOpacity; if( intensifyActiveShadow && window == effects->activeWindow() ) opacity = 1 - ( 1 - shadowOpacity ) * ( 1 - shadowOpacity ); drawShadowQuadXRender( mDefaultShadowPics.at( quad.id() ), quadRect, xScale, yScale, shadowColor, opacity * data.opacity, data.brightness, data.saturation ); } } } #endif } } // namespace #include "shadow.h"