dac02f69c6
svn path=/branches/work/kwin_composite/; revision=593722
577 lines
18 KiB
C++
577 lines
18 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.
|
|
|
|
Based on glcompmgr code by Felix Bellaby.
|
|
******************************************************************/
|
|
|
|
|
|
|
|
#include "scene_opengl.h"
|
|
|
|
#include "utils.h"
|
|
#include "client.h"
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#include <X11/extensions/shape.h>
|
|
|
|
namespace KWinInternal
|
|
{
|
|
|
|
//****************************************
|
|
// SceneOpenGL
|
|
//****************************************
|
|
|
|
GLXFBConfig SceneOpenGL::fbcdrawable;
|
|
GLXContext SceneOpenGL::context;
|
|
GLXPixmap SceneOpenGL::glxroot;
|
|
bool SceneOpenGL::tfp_mode; // using glXBindTexImageEXT (texture_from_pixmap)
|
|
|
|
typedef void (*glXBindTexImageEXT_func)( Display* dpy, GLXDrawable drawable,
|
|
int buffer, const int* attrib_list );
|
|
typedef void (*glXReleaseTexImageEXT_func)( Display* dpy, GLXDrawable drawable, int buffer );
|
|
glXBindTexImageEXT_func glXBindTexImageEXT;
|
|
glXReleaseTexImageEXT_func glXReleaseTexImageEXT;
|
|
|
|
static void checkGLError( const char* txt )
|
|
{
|
|
GLenum err = glGetError();
|
|
if( err != GL_NO_ERROR )
|
|
kWarning() << "GL error (" << txt << "): 0x" << QString::number( err, 16 ) << endl;
|
|
}
|
|
|
|
const int root_db_attrs[] =
|
|
{
|
|
GLX_DOUBLEBUFFER, True,
|
|
GLX_DEPTH_SIZE, 16,
|
|
GLX_RED_SIZE, 1,
|
|
GLX_GREEN_SIZE, 1,
|
|
GLX_BLUE_SIZE, 1,
|
|
GLX_ALPHA_SIZE, 1,
|
|
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
|
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
|
|
None
|
|
};
|
|
|
|
static const int root_buffer_attrs[] =
|
|
{
|
|
GLX_DOUBLEBUFFER, False,
|
|
GLX_DEPTH_SIZE, 16,
|
|
GLX_RED_SIZE, 1,
|
|
GLX_GREEN_SIZE, 1,
|
|
GLX_BLUE_SIZE, 1,
|
|
GLX_ALPHA_SIZE, 1,
|
|
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
|
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
|
|
None
|
|
};
|
|
|
|
const int drawable_attrs[] =
|
|
{
|
|
GLX_DOUBLEBUFFER, False,
|
|
GLX_DEPTH_SIZE, 0,
|
|
GLX_RED_SIZE, 1,
|
|
GLX_GREEN_SIZE, 1,
|
|
GLX_BLUE_SIZE, 1,
|
|
GLX_ALPHA_SIZE, 1,
|
|
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
|
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
|
|
None
|
|
};
|
|
|
|
const int drawable_tfp_attrs[] =
|
|
{
|
|
GLX_DOUBLEBUFFER, False,
|
|
GLX_DEPTH_SIZE, 0,
|
|
GLX_RED_SIZE, 1,
|
|
GLX_GREEN_SIZE, 1,
|
|
GLX_BLUE_SIZE, 1,
|
|
GLX_ALPHA_SIZE, 1,
|
|
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
|
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
|
|
GLX_BIND_TO_TEXTURE_RGBA_EXT, True, // additional for tfp
|
|
None
|
|
};
|
|
|
|
SceneOpenGL::SceneOpenGL( Workspace* ws )
|
|
: Scene( ws )
|
|
{
|
|
// TODO add checks where needed
|
|
int dummy;
|
|
if( !glXQueryExtension( display(), &dummy, &dummy ))
|
|
return;
|
|
glXBindTexImageEXT = (glXBindTexImageEXT_func)
|
|
dlsym( RTLD_DEFAULT, "glXBindTexImageEXT" );
|
|
glXReleaseTexImageEXT = (glXReleaseTexImageEXT_func)
|
|
dlsym( RTLD_DEFAULT, "glXReleaseTexImageEXT" );
|
|
tfp_mode = ( glXBindTexImageEXT != NULL && glXReleaseTexImageEXT != NULL );
|
|
initBuffer();
|
|
if( tfp_mode )
|
|
{
|
|
if( !findConfig( drawable_tfp_attrs, fbcdrawable ))
|
|
{
|
|
tfp_mode = false;
|
|
if( !findConfig( drawable_attrs, fbcdrawable ))
|
|
assert( false );
|
|
}
|
|
}
|
|
else
|
|
if( !findConfig( drawable_attrs, fbcdrawable ))
|
|
assert( false );
|
|
context = glXCreateNewContext( display(), fbcroot, GLX_RGBA_TYPE, NULL, GL_FALSE );
|
|
glXMakeContextCurrent( display(), glxroot, glxroot, context );
|
|
glMatrixMode( GL_PROJECTION );
|
|
glLoadIdentity();
|
|
glOrtho( 0, displayWidth(), 0, displayHeight(), 0, 65535 );
|
|
glEnable( GL_DEPTH_TEST );
|
|
checkGLError( "Init" );
|
|
kDebug() << "Root DB:" << root_db << ", TFP:" << tfp_mode << endl;
|
|
}
|
|
|
|
SceneOpenGL::~SceneOpenGL()
|
|
{
|
|
for( QMap< Toplevel*, Window >::Iterator it = windows.begin();
|
|
it != windows.end();
|
|
++it )
|
|
(*it).free();
|
|
if( root_db )
|
|
glXDestroyWindow( display(), glxroot );
|
|
else
|
|
{
|
|
glXDestroyPixmap( display(), glxroot );
|
|
XFreeGC( display(), gcroot );
|
|
XFreePixmap( display(), buffer );
|
|
}
|
|
glXDestroyContext( display(), context );
|
|
checkGLError( "Cleanup" );
|
|
}
|
|
|
|
void SceneOpenGL::initBuffer()
|
|
{
|
|
XWindowAttributes attrs;
|
|
XGetWindowAttributes( display(), rootWindow(), &attrs );
|
|
if( findConfig( root_db_attrs, fbcroot, XVisualIDFromVisual( attrs.visual )))
|
|
root_db = true;
|
|
else
|
|
{
|
|
if( findConfig( root_buffer_attrs, fbcroot ))
|
|
root_db = false;
|
|
else
|
|
assert( false );
|
|
}
|
|
if( root_db )
|
|
{
|
|
buffer = rootWindow();
|
|
glxroot = glXCreateWindow( display(), fbcroot, buffer, NULL );
|
|
}
|
|
else
|
|
{
|
|
XGCValues gcattr;
|
|
gcattr.subwindow_mode = IncludeInferiors;
|
|
gcroot = XCreateGC( display(), rootWindow(), GCSubwindowMode, &gcattr );
|
|
buffer = XCreatePixmap( display(), rootWindow(), displayWidth(), displayHeight(),
|
|
QX11Info::appDepth());
|
|
glxroot = glXCreatePixmap( display(), fbcroot, buffer, NULL );
|
|
}
|
|
}
|
|
|
|
static void debugFBConfig( GLXFBConfig* fbconfigs, int i, const int* attrs )
|
|
{
|
|
int pos = 0;
|
|
while( attrs[ pos ] != (int)None )
|
|
{
|
|
int value;
|
|
if( glXGetFBConfigAttrib( display(), fbconfigs[ i ], attrs[ pos ], &value )
|
|
== Success )
|
|
kDebug() << "ATTR: 0x" << QString::number( attrs[ pos ], 16 )
|
|
<< ": 0x" << QString::number( attrs[ pos + 1 ], 16 )
|
|
<< ": 0x" << QString::number( value, 16 ) << endl;
|
|
else
|
|
kDebug() << "ATTR FAIL: 0x" << QString::number( attrs[ pos ], 16 ) << endl;
|
|
pos += 2;
|
|
}
|
|
}
|
|
|
|
bool SceneOpenGL::findConfig( const int* attrs, GLXFBConfig& config, VisualID visual )
|
|
{
|
|
int cnt;
|
|
GLXFBConfig* fbconfigs = glXChooseFBConfig( display(), DefaultScreen( display()),
|
|
attrs, &cnt );
|
|
if( fbconfigs != NULL )
|
|
{
|
|
if( visual == None )
|
|
{
|
|
config = fbconfigs[ 0 ];
|
|
kDebug() << "Found FBConfig" << endl;
|
|
debugFBConfig( fbconfigs, 0, attrs );
|
|
XFree( fbconfigs );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
for( int i = 0;
|
|
i < cnt;
|
|
++i )
|
|
{
|
|
int value;
|
|
glXGetFBConfigAttrib( display(), fbconfigs[ i ], GLX_VISUAL_ID, &value );
|
|
if( value == (int)visual )
|
|
{
|
|
kDebug() << "Found FBConfig" << endl;
|
|
config = fbconfigs[ i ];
|
|
debugFBConfig( fbconfigs, i, attrs );
|
|
XFree( fbconfigs );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if 0 // for debug
|
|
fbconfigs = glXGetFBConfigs( display(), DefaultScreen( display()), &cnt );
|
|
for( int i = 0;
|
|
i < cnt;
|
|
++i )
|
|
{
|
|
kDebug() << "Listing FBConfig:" << i << endl;
|
|
debugFBConfig( fbconfigs, i, attrs );
|
|
}
|
|
if( fbconfigs != NULL )
|
|
XFree( fbconfigs );
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void SceneOpenGL::paint( QRegion, ToplevelList windows )
|
|
{
|
|
grabXServer();
|
|
glXWaitX();
|
|
glClearColor( 0, 0, 0, 1 );
|
|
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
|
|
int depth = 0;
|
|
QList< Window* > phase2;
|
|
for( int i = windows.count() - 1; // top to bottom
|
|
i >= 0;
|
|
--i )
|
|
{
|
|
Toplevel* c = windows[ i ];
|
|
assert( this->windows.contains( c ));
|
|
Window& w = this->windows[ c ];
|
|
w.setDepth( --depth );
|
|
if( !w.isVisible())
|
|
continue;
|
|
if( !w.isOpaque())
|
|
{
|
|
phase2.prepend( &w );
|
|
continue;
|
|
}
|
|
w.bindTexture();
|
|
w.draw();
|
|
}
|
|
foreach( Window* w2, phase2 )
|
|
{
|
|
Window& w = *w2;
|
|
w.bindTexture();
|
|
glEnable( GL_BLEND );
|
|
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
w.draw();
|
|
glDisable( GL_BLEND );
|
|
}
|
|
if( root_db )
|
|
glXSwapBuffers( display(), glxroot );
|
|
else
|
|
{
|
|
glFlush();
|
|
glXWaitGL();
|
|
XCopyArea( display(), buffer, rootWindow(), gcroot, 0, 0, displayWidth(), displayHeight(), 0, 0 );
|
|
XFlush( display());
|
|
}
|
|
ungrabXServer();
|
|
checkGLError( "PostPaint" );
|
|
}
|
|
|
|
void SceneOpenGL::windowAdded( Toplevel* c )
|
|
{
|
|
assert( !windows.contains( c ));
|
|
windows[ c ] = Window( c );
|
|
}
|
|
|
|
void SceneOpenGL::windowDeleted( Toplevel* c )
|
|
{
|
|
assert( windows.contains( c ));
|
|
windows[ c ].free();
|
|
windows.remove( c );
|
|
}
|
|
|
|
void SceneOpenGL::windowGeometryShapeChanged( Toplevel* c )
|
|
{
|
|
if( !windows.contains( c )) // this is ok, shape is not valid
|
|
return; // by default
|
|
Window& w = windows[ c ];
|
|
w.discardShape();
|
|
w.discardTexture();
|
|
}
|
|
|
|
void SceneOpenGL::windowOpacityChanged( Toplevel* )
|
|
{
|
|
#if 0 // not really needed, windows are painted on every repaint
|
|
// and opacity is used when applying texture, not when
|
|
// creating it
|
|
if( !windows.contains( c )) // this is ok, texture is created
|
|
return; // on demand
|
|
Window& w = windows[ c ];
|
|
w.discardTexture();
|
|
#endif
|
|
}
|
|
|
|
SceneOpenGL::Window::Window( Toplevel* c )
|
|
: toplevel( c )
|
|
, texture( 0 )
|
|
, texture_y_inverted( false )
|
|
, bound_pixmap( None )
|
|
, bound_glxpixmap( None )
|
|
, shape_valid( false )
|
|
, depth( 0 )
|
|
{
|
|
}
|
|
|
|
SceneOpenGL::Window::~Window()
|
|
{
|
|
}
|
|
|
|
void SceneOpenGL::Window::free()
|
|
{
|
|
discardTexture();
|
|
}
|
|
|
|
// for relative window positioning
|
|
void SceneOpenGL::Window::setDepth( int d )
|
|
{
|
|
depth = d;
|
|
}
|
|
|
|
void SceneOpenGL::Window::bindTexture()
|
|
{
|
|
if( texture != 0 && toplevel->damage().isEmpty()
|
|
&& !tfp_mode ) // interestingly this makes tfp slower
|
|
{
|
|
// texture doesn't need updating, just bind it
|
|
glBindTexture( GL_TEXTURE_RECTANGLE_ARB, texture );
|
|
return;
|
|
}
|
|
// TODO cache pixmaps here if possible
|
|
Pixmap window_pix = toplevel->createWindowPixmap();
|
|
Pixmap pix = window_pix;
|
|
// HACK
|
|
// When a window uses ARGB visual and has a decoration, the decoration
|
|
// does use ARGB visual. When converting such window to a texture
|
|
// the alpha for the decoration part is broken for some reason (undefined?).
|
|
// I wasn't lucky converting KWin to use ARGB visuals for decorations,
|
|
// so instead simply set alpha in those parts to opaque.
|
|
// Without ALPHA_CLEAR_COPY the setting is done directly in the window
|
|
// pixmap, which seems to be ok, but let's not risk trouble right now.
|
|
// TODO check if this isn't a performance problem and how it can be done better
|
|
Client* c = dynamic_cast< Client* >( toplevel );
|
|
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 );
|
|
glXWaitX();
|
|
}
|
|
if( tfp_mode )
|
|
{
|
|
if( texture == None )
|
|
glGenTextures( 1, &texture );
|
|
if( bound_pixmap != None )
|
|
{
|
|
glXReleaseTexImageEXT( display(), bound_glxpixmap, GLX_FRONT_LEFT_EXT );
|
|
glXDestroyGLXPixmap( display(), bound_glxpixmap );
|
|
XFreePixmap( display(), bound_pixmap );
|
|
}
|
|
static const int attrs[] =
|
|
{
|
|
GLX_TEXTURE_FORMAT_EXT, GLX_TEXTURE_FORMAT_RGBA_EXT,
|
|
None
|
|
};
|
|
bound_pixmap = pix;
|
|
bound_glxpixmap = glXCreatePixmap( display(), fbcdrawable, pix, attrs );
|
|
int value;
|
|
glXGetFBConfigAttrib( display(), fbcdrawable, GLX_Y_INVERTED_EXT, &value );
|
|
texture_y_inverted = value ? true : false;
|
|
glBindTexture( GL_TEXTURE_RECTANGLE_ARB, texture );
|
|
glXBindTexImageEXT( display(), bound_glxpixmap, GLX_FRONT_LEFT_EXT, NULL );
|
|
}
|
|
else
|
|
{
|
|
GLXDrawable pixmap = glXCreatePixmap( display(), fbcdrawable, pix, NULL );
|
|
glXMakeContextCurrent( display(), pixmap, pixmap, context );
|
|
glReadBuffer( GL_FRONT );
|
|
glDrawBuffer( GL_FRONT );
|
|
if( texture == None )
|
|
{
|
|
glGenTextures( 1, &texture );
|
|
glBindTexture( GL_TEXTURE_RECTANGLE_ARB, texture );
|
|
glCopyTexImage2D( GL_TEXTURE_RECTANGLE_ARB, 0,
|
|
toplevel->hasAlpha() ? GL_RGBA : GL_RGB,
|
|
0, 0, toplevel->width(), toplevel->height(), 0 );
|
|
}
|
|
else
|
|
{
|
|
glBindTexture( GL_TEXTURE_RECTANGLE_ARB, texture );
|
|
if( !toplevel->damage().isEmpty())
|
|
{
|
|
foreach( QRect r, toplevel->damage().rects())
|
|
{
|
|
int gly = height() - r.y() - r.height(); // to opengl coords
|
|
glCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_ARB, 0,
|
|
r.x(), gly, r.x(), gly, r.width(), r.height());
|
|
}
|
|
}
|
|
}
|
|
// the pixmap is no longer needed, the texture will be updated
|
|
// only when the window changes anyway, so no need to cache
|
|
// the pixmap
|
|
glXDestroyPixmap( display(), pixmap );
|
|
XFreePixmap( display(), pix );
|
|
}
|
|
#ifdef ALPHA_CLEAR_COPY
|
|
if( alpha_clear )
|
|
XFreePixmap( display(), window_pix );
|
|
#endif
|
|
}
|
|
|
|
void SceneOpenGL::Window::discardTexture()
|
|
{
|
|
if( texture != 0 )
|
|
{
|
|
if( tfp_mode )
|
|
{
|
|
glXReleaseTexImageEXT( display(), bound_glxpixmap, GLX_FRONT_LEFT_EXT );
|
|
glXDestroyGLXPixmap( display(), bound_glxpixmap );
|
|
XFreePixmap( display(), bound_pixmap );
|
|
bound_pixmap = None;
|
|
bound_glxpixmap = None;
|
|
}
|
|
glDeleteTextures( 1, &texture );
|
|
}
|
|
texture = 0;
|
|
}
|
|
|
|
|
|
void SceneOpenGL::Window::discardShape()
|
|
{
|
|
shape_valid = false;
|
|
}
|
|
|
|
QRegion SceneOpenGL::Window::shape() const
|
|
{
|
|
if( !shape_valid )
|
|
{
|
|
Client* c = dynamic_cast< Client* >( toplevel );
|
|
if( toplevel->shape() || ( c != NULL && !c->mask().isEmpty()))
|
|
{
|
|
int count, order;
|
|
XRectangle* rects = XShapeGetRectangles( display(), toplevel->handle(),
|
|
ShapeBounding, &count, &order );
|
|
if(rects)
|
|
{
|
|
shape_region = QRegion();
|
|
for( int i = 0;
|
|
i < count;
|
|
++i )
|
|
shape_region += QRegion( rects[ i ].x, rects[ i ].y,
|
|
rects[ i ].width, rects[ i ].height );
|
|
XFree(rects);
|
|
}
|
|
else
|
|
shape_region = QRegion( 0, 0, width(), height());
|
|
}
|
|
else
|
|
shape_region = QRegion( 0, 0, width(), height());
|
|
shape_valid = true;
|
|
}
|
|
return shape_region;
|
|
}
|
|
|
|
static void quadDraw( int x1, int y1, int x2, int y2, bool invert_y )
|
|
{
|
|
glTexCoord2i( x1, invert_y ? y2 : y1 );
|
|
glVertex2i( x1, y1 );
|
|
glTexCoord2i( x2, invert_y ? y2 : y1 );
|
|
glVertex2i( x2, y1 );
|
|
glTexCoord2i( x2, invert_y ? y1 : y2 );
|
|
glVertex2i( x2, y2 );
|
|
glTexCoord2i( x1, invert_y ? y1 : y2 );
|
|
glVertex2i( x1, y2 );
|
|
}
|
|
|
|
void SceneOpenGL::Window::draw()
|
|
{
|
|
// TODO for double-buffered root glDrawBuffer( GL_BACK );
|
|
glXMakeContextCurrent( display(), glxroot, glxroot, context );
|
|
glPushMatrix();
|
|
glTranslatef( glX(), glY(), depth );
|
|
if( toplevel->opacity() != 1.0 )
|
|
{
|
|
glEnable( GL_BLEND );
|
|
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
glColor4f( toplevel->opacity(), toplevel->opacity(), toplevel->opacity(),
|
|
toplevel->opacity());
|
|
}
|
|
glEnable( GL_TEXTURE_RECTANGLE_ARB );
|
|
glBegin( GL_QUADS );
|
|
foreach( QRect r, shape().rects())
|
|
quadDraw( r.x(), height() - r.y() - r.height(),
|
|
r.x() + r.width(), height() - r.y(), texture_y_inverted );
|
|
glEnd();
|
|
glPopMatrix();
|
|
if( toplevel->opacity() != 1.0 )
|
|
{
|
|
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
|
|
glColor4f( 0, 0, 0, 0 );
|
|
glDisable( GL_BLEND );
|
|
}
|
|
glDisable( GL_TEXTURE_RECTANGLE_ARB );
|
|
glBindTexture( GL_TEXTURE_RECTANGLE_ARB, 0 );
|
|
}
|
|
|
|
bool SceneOpenGL::Window::isVisible() const
|
|
{
|
|
// TODO mapping state?
|
|
return !toplevel->geometry()
|
|
.intersect( QRect( 0, 0, displayWidth(), displayHeight()))
|
|
.isEmpty();
|
|
}
|
|
|
|
bool SceneOpenGL::Window::isOpaque() const
|
|
{
|
|
return toplevel->opacity() == 1.0 && !toplevel->hasAlpha();
|
|
}
|
|
|
|
} // namespace
|