/***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #include "scene_xrender.h" #ifdef HAVE_XRENDER #include "toplevel.h" #include "client.h" #include "effects.h" namespace KWinInternal { //**************************************** // SceneXrender //**************************************** struct RegionDebug { RegionDebug( XserverRegion r ) : rr( r ) {} XserverRegion rr; }; #ifdef NDEBUG inline kndbgstream& operator<<( kndbgstream& stream, RegionDebug ) { return stream; } #else kdbgstream& operator<<( kdbgstream& stream, RegionDebug r ) { if( r.rr == None ) return stream << "EMPTY"; int num; XRectangle* rects = XFixesFetchRegion( display(), r.rr, &num ); if( rects == NULL || num == 0 ) return stream << "EMPTY"; for( int i = 0; i < num; ++i ) stream << "[" << rects[ i ].x << "+" << rects[ i ].y << " " << rects[ i ].width << "x" << rects[ i ].height << "]"; return stream; } #endif SceneXrender::SceneXrender( Workspace* ws ) : Scene( ws ) { format = XRenderFindVisualFormat( display(), DefaultVisual( display(), DefaultScreen( display()))); XRenderPictureAttributes pa; pa.subwindow_mode = IncludeInferiors; front = XRenderCreatePicture( display(), rootWindow(), format, CPSubwindowMode, &pa ); createBuffer(); } SceneXrender::~SceneXrender() { XRenderFreePicture( display(), front ); XRenderFreePicture( display(), buffer ); for( QMap< Toplevel*, WindowData >::Iterator it = window_data.begin(); it != window_data.end(); ++it ) (*it).free(); } void SceneXrender::paint( QRegion dam, ToplevelList windows ) { #if 1 dam = QRegion( 0, 0, displayWidth(), displayHeight()); #endif QVector< QRect > rects = dam.rects(); XRectangle* xr = new XRectangle[ rects.count() ]; for( int i = 0; i < rects.count(); ++i ) { xr[ i ].x = rects[ i ].x(); xr[ i ].y = rects[ i ].y(); xr[ i ].width = rects[ i ].width(); xr[ i ].height = rects[ i ].height(); } XserverRegion damage = XFixesCreateRegion( display(), xr, rects.count()); delete[] xr; // Use the damage region as the clip region for the root window XFixesSetPictureClipRegion( display(), front, 0, 0, damage ); // Prepare pass for windows // Go top to bottom so that clipping is computed properly for phase1 for( int i = windows.count() - 1; i >= 0; --i ) { Toplevel* c = windows[ i ]; resetWindowData( c ); WindowData& data = window_data[ c ]; Picture picture = data.picture(); if( picture == None ) // The render format can be null for GL and/or Xv visuals { windows.removeAt( i ); continue; } effects->transformWindow( c, data.matrix, data.effect ); // TODO remove, instead add initWindow() to effects effects->transformWorkspace( data.matrix, data.effect ); data.saveClipRegion( damage ); if( data.simpleTransformation() && data.isOpaque()) { // is opaque, has simple shape, can be clipped, will be painted using simpler faster method // Subtract the clients shape from the damage region XserverRegion shape = data.shape(); assert( shape != None ); XFixesSubtractRegion( display(), damage, damage, shape ); data.phase = 1; } else data.phase = 2; // will be painted later bottom to top } // Fill any areas of the root window not covered by windows XFixesSetPictureClipRegion( display(), buffer, 0, 0, damage ); XRenderColor col = { 0xffff, 0xffff, 0xffff, 0xffff }; XRenderFillRectangle( display(), PictOpSrc, buffer, &col, 0, 0, displayWidth(), displayHeight()); // Draw each opaque window top to bottom, subtracting the bounding rect of // each window from the clip region after it's been drawn. for( int i = windows.count() - 1; i >= 0; --i ) { Toplevel* c = windows[ i ]; WindowData& data = window_data[ c ]; if( data.phase != 1 ) continue; XFixesSetPictureClipRegion( display(), buffer, 0, 0, data.savedClipRegion()); Picture picture = data.picture(); XRenderComposite( display(), PictOpSrc, picture, None, buffer, 0, 0, 0, 0, c->x() + int( data.matrix.xTranslate()), c->y() + int( data.matrix.yTranslate()), c->width(), c->height()); } // Now walk the list bottom to top, drawing translucent and complicated windows. // That we draw bottom to top is important now since we're drawing translucent objects // and also are clipping only by opaque windows. for( int i = 0; i < windows.count(); ++i ) { Toplevel* c = windows[ i ]; WindowData& data = window_data[ c ]; if( data.phase != 2 ) continue; XFixesSetPictureClipRegion( display(), buffer, 0, 0, data.savedClipRegion()); Picture picture = data.picture(); Picture alpha = data.alphaMask(); if( data.simpleTransformation()) { XRenderComposite( display(), PictOpOver, picture, alpha, buffer, 0, 0, 0, 0, c->x() + int( data.matrix.xTranslate()), c->y() + int( data.matrix.yTranslate()), c->width(), c->height()); } else { // TODO Okay, I'm at loss here. Whoever wants advanced transformations can implement // it themselves. If not, they actually don't want it that badly *shrug*. // setPictureMatrix( picture, data.matrix ); XRenderComposite( display(), PictOpSrc, picture, alpha, buffer, 0, 0, 0, 0, c->x(), c->y(), c->width(), c->height()); // setPictureMatrix( picture, Matrix()); } } // cleanup for( int i = 0; i < windows.count(); ++i ) { Toplevel* c = windows[ i ]; WindowData& data = window_data[ c ]; data.cleanup(); } // copy composed buffer to the root window XFixesSetPictureClipRegion( display(), buffer, 0, 0, None ); XRenderComposite( display(), PictOpSrc, buffer, None, front, 0, 0, 0, 0, 0, 0, displayWidth(), displayHeight()); XFlush( display()); XFixesDestroyRegion( display(), damage ); } void SceneXrender::resetWindowData( Toplevel* c ) { if( !window_data.contains( c )) window_data[ c ] = WindowData( c, XRenderFindVisualFormat( display(), c->visual())); WindowData& data = window_data[ c ]; data.matrix = Matrix(); data.effect.opacity = c->opacity(); } void SceneXrender::windowGeometryShapeChanged( Toplevel* c ) { if( !window_data.contains( c )) return; window_data[ c ].geometryShapeChanged(); } void SceneXrender::windowOpacityChanged( Toplevel* c ) { if( !window_data.contains( c )) return; window_data[ c ].opacityChanged(); } void SceneXrender::windowDeleted( Toplevel* c ) { assert( window_data.contains( c )); window_data[ c ].free(); window_data.remove( c ); } void SceneXrender::windowAdded( Toplevel* c ) { assert( !window_data.contains( c )); resetWindowData( c ); } // TODO handle xrandr changes void SceneXrender::createBuffer() { if( buffer != None ) XRenderFreePicture( display(), buffer ); Pixmap pixmap = XCreatePixmap( display(), rootWindow(), displayWidth(), displayHeight(), QX11Info::appDepth()); buffer = XRenderCreatePicture( display(), pixmap, format, 0, 0 ); XFreePixmap( display(), pixmap ); // The picture owns the pixmap now } void SceneXrender::setPictureMatrix( Picture pic, const Matrix& ) { if( pic == None ) return; #if 0 XTransform t; // ignore z axis t.matrix[ 0 ][ 0 ] = XDoubleToFixed( 1 / m.m[ 0 ][ 0 ] ); t.matrix[ 0 ][ 1 ] = XDoubleToFixed( m.m[ 0 ][ 1 ] ); t.matrix[ 0 ][ 2 ] = -XDoubleToFixed( m.m[ 0 ][ 3 ] ); // translation seems to be inverted t.matrix[ 1 ][ 0 ] = XDoubleToFixed( m.m[ 1 ][ 0 ] ); t.matrix[ 1 ][ 1 ] = XDoubleToFixed( 1 / m.m[ 1 ][ 1 ] ); t.matrix[ 1 ][ 2 ] = -XDoubleToFixed( m.m[ 1 ][ 3 ] ); t.matrix[ 2 ][ 0 ] = XDoubleToFixed( m.m[ 3 ][ 0 ] ); t.matrix[ 2 ][ 1 ] = XDoubleToFixed( m.m[ 3 ][ 1 ] ); t.matrix[ 2 ][ 2 ] = XDoubleToFixed( m.m[ 3 ][ 3 ] ); // and scaling seems to be wrong too // or maybe I just don't get it, but anyway, for now if( m.m[ 3 ][ 3 ] != 1 ) { t.matrix[ 0 ][ 0 ] = XDoubleToFixed( 1 / m.m[ 0 ][ 0 ] * m.m[ 3 ][ 3 ] ); t.matrix[ 1 ][ 1 ] = XDoubleToFixed( 1 / m.m[ 1 ][ 1 ] * m.m[ 3 ][ 3 ] ); t.matrix[ 2 ][ 2 ] = XDoubleToFixed( 1 ); } XRenderSetPictureTransform( display(), pic, &t ); if( t.matrix[ 0 ][ 0 ] != XDoubleToFixed( 1 ) || t.matrix[ 1 ][ 1 ] != XDoubleToFixed( 1 ) || t.matrix[ 2 ][ 2 ] != XDoubleToFixed( 1 ) || t.matrix[ 0 ][ 1 ] != XDoubleToFixed( 0 ) || t.matrix[ 1 ][ 0 ] != XDoubleToFixed( 0 )) { XRenderSetPictureFilter( display(), pic, const_cast< char* >( FilterGood ), 0, 0 ); } else // fast filter for identity or translation { XRenderSetPictureFilter( display(), pic, const_cast< char* >( FilterFast ), 0, 0 ); } #endif } SceneXrender::WindowData::WindowData( Toplevel* c, XRenderPictFormat* f ) : window( c ) , _picture( None ) , format( f ) , saved_clip_region( None ) , alpha( None ) , _shape( None ) { } void SceneXrender::WindowData::free() { if( _picture != None ) XRenderFreePicture( display(), _picture ); if( alpha != None ) XRenderFreePicture( display(), alpha ); if( _shape != None ) XRenderFreePicture( display(), _shape ); } bool SceneXrender::WindowData::simpleTransformation() const { return ( matrix.isIdentity() || matrix.isOnlyTranslate()); } Picture SceneXrender::WindowData::picture() { if( !window->damage().isEmpty() && _picture != None ) { XRenderFreePicture( display(), _picture ); _picture = None; } if( _picture == None && format != NULL ) { Pixmap window_pix = window->createWindowPixmap(); Pixmap pix = window_pix; // HACK the same like with opengl Client* c = dynamic_cast< Client* >( window ); bool alpha_clear = c != NULL && c->hasAlpha() && !c->noBorder(); #define ALPHA_CLEAR_COPY #ifdef ALPHA_CLEAR_COPY if( alpha_clear ) { Pixmap p2 = XCreatePixmap( display(), pix, c->width(), c->height(), 32 ); GC gc = XCreateGC( display(), pix, 0, NULL ); XCopyArea( display(), pix, p2, gc, 0, 0, c->width(), c->height(), 0, 0 ); pix = p2; XFreeGC( display(), gc ); } #endif if( alpha_clear ) { XGCValues gcv; gcv.foreground = 0xff000000; gcv.plane_mask = 0xff000000; GC gc = XCreateGC( display(), pix, GCPlaneMask | GCForeground, &gcv ); XFillRectangle( display(), pix, gc, 0, 0, c->width(), c->clientPos().y()); XFillRectangle( display(), pix, gc, 0, 0, c->clientPos().x(), c->height()); int tw = c->clientPos().x() + c->clientSize().width(); int th = c->clientPos().y() + c->clientSize().height(); XFillRectangle( display(), pix, gc, 0, th, c->width(), c->height() - th ); XFillRectangle( display(), pix, gc, tw, 0, c->width() - tw, c->height()); XFreeGC( display(), gc ); } _picture = XRenderCreatePicture( display(), pix, format, 0, 0 ); XFreePixmap( display(), pix ); // the picture owns the pixmap #ifdef ALPHA_CLEAR_COPY if( alpha_clear ) XFreePixmap( display(), window_pix ); #endif } return _picture; } void SceneXrender::WindowData::saveClipRegion( XserverRegion r ) { saved_clip_region = XFixesCreateRegion( display(), NULL, 0 ); XFixesCopyRegion( display(), saved_clip_region, r ); } XserverRegion SceneXrender::WindowData::savedClipRegion() { return saved_clip_region; } void SceneXrender::WindowData::cleanup() { XFixesDestroyRegion( display(), saved_clip_region ); saved_clip_region = None; } bool SceneXrender::WindowData::isOpaque() const { if( format->type == PictTypeDirect && format->direct.alphaMask ) return false; if( effect.opacity != 1.0 ) return false; return true; } Picture SceneXrender::WindowData::alphaMask() { if( isOpaque()) return None; if( alpha != None && alpha_cached_opacity != effect.opacity ) { if( alpha != None ) XRenderFreePicture( display(), alpha ); alpha = None; } if( alpha != None ) return alpha; if( effect.opacity == 1.0 ) { // no need to create alpha mask alpha_cached_opacity = 1.0; return None; } Pixmap pixmap = XCreatePixmap( display(), rootWindow(), 1, 1, 8 ); XRenderPictFormat* format = XRenderFindStandardFormat( display(), PictStandardA8 ); XRenderPictureAttributes pa; pa.repeat = True; alpha = XRenderCreatePicture( display(), pixmap, format, CPRepeat, &pa ); XFreePixmap( display(), pixmap ); XRenderColor col; col.alpha = int( effect.opacity * 0xffff ); alpha_cached_opacity = effect.opacity; XRenderFillRectangle( display(), PictOpSrc, alpha, &col, 0, 0, 1, 1 ); return alpha; } XserverRegion SceneXrender::WindowData::shape() { #if 0 // it probably doesn't make sense to cache this, and perhaps some others - they aren't roundtrips if( shape == None ) { shape = XFixesCreateRegionFromWindow( display(), window->handle(), WindowRegionBounding ); XFixesTranslateRegion( display(), shape, window->x(), window->y()); } return shape; #else if( !simpleTransformation()) { // The region here should be translated using the matrix, but that's not possible // (well, maybe fetch the region and transform manually - TODO check). return None; } XserverRegion shape = XFixesCreateRegionFromWindow( display(), window->handle(), WindowRegionBounding ); XFixesTranslateRegion( display(), shape, window->x() + int( matrix.xTranslate()), window->y() + int( matrix.yTranslate())); return shape; #endif } void SceneXrender::WindowData::geometryShapeChanged() { if( _picture != None ) XRenderFreePicture( display(), _picture ); _picture = None; if( alpha != None ) XRenderFreePicture( display(), alpha ); alpha = None; if( _shape != None ) XFixesDestroyRegion( display(), _shape ); _shape = None; } void SceneXrender::WindowData::opacityChanged() { if( alpha != None ) XRenderFreePicture( display(), alpha ); alpha = None; } } // namespace #endif