kwin/scene_xrender.cpp

448 lines
15 KiB
C++

/*****************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2006 Lubos Lunak <l.lunak@kde.org>
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