kwin/scene_opengl.cpp

681 lines
21 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)
bool SceneOpenGL::root_db; // destination drawable is double-buffered
typedef void (*glXBindTexImageEXT_func)( Display* dpy, GLXDrawable drawable,
int buffer, const int* attrib_list );
typedef void (*glXReleaseTexImageEXT_func)( Display* dpy, GLXDrawable drawable, int buffer );
typedef void (*glXFuncPtr)();
typedef glXFuncPtr (*glXGetProcAddress_func)( const GLubyte* );
glXBindTexImageEXT_func glXBindTexImageEXT;
glXReleaseTexImageEXT_func glXReleaseTexImageEXT;
glXGetProcAddress_func glXGetProcAddress;
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_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_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
};
static glXFuncPtr getProcAddress( const char* name )
{
glXFuncPtr ret = NULL;
if( glXGetProcAddress != NULL )
ret = glXGetProcAddress( ( const GLubyte* ) name );
if( ret == NULL )
ret = ( glXFuncPtr ) dlsym( RTLD_DEFAULT, name );
return ret;
}
SceneOpenGL::SceneOpenGL( Workspace* ws )
: Scene( ws )
{
// TODO add checks where needed
int dummy;
if( !glXQueryExtension( display(), &dummy, &dummy ))
return;
glXGetProcAddress = (glXGetProcAddress_func) getProcAddress( "glxGetProcAddress" );
if( glXGetProcAddress == NULL )
glXGetProcAddress = (glXGetProcAddress_func) getProcAddress( "glxGetProcAddressARB" );
glXBindTexImageEXT = (glXBindTexImageEXT_func) getProcAddress( "glXBindTexImageEXT" );
glXReleaseTexImageEXT = (glXReleaseTexImageEXT_func) getProcAddress( "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 );
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 );
glDrawBuffer( GL_BACK );
}
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 damage, ToplevelList windows )
{
grabXServer();
glXWaitX();
if( /*generic case*/false )
paintGenericScreen( windows );
else
paintSimpleScreen( damage, windows );
ungrabXServer();
checkGLError( "PostPaint" );
}
// the generic painting code that should eventually handle even
// transformations
void SceneOpenGL::paintGenericScreen( ToplevelList windows )
{
glPushMatrix();
glClearColor( 0, 0, 0, 1 );
glClear( GL_COLOR_BUFFER_BIT );
glScalef( 1, -1, 1 );
glTranslatef( 0, -displayHeight(), 0 );
paintBackground( infiniteRegion());
foreach( Toplevel* c, windows ) // bottom to top
{
assert( this->windows.contains( c ));
Window& w = this->windows[ c ];
if( !w.isVisible())
continue;
w.paint( infiniteRegion(), PAINT_OPAQUE | PAINT_TRANSLUCENT );
}
glPopMatrix();
if( root_db )
glXSwapBuffers( display(), glxroot );
else
{
glFlush();
glXWaitGL();
XCopyArea( display(), buffer, rootWindow(), gcroot, 0, 0, displayWidth(), displayHeight(), 0, 0 );
XFlush( display());
}
}
// the optimized case without any transformations at all
void SceneOpenGL::paintSimpleScreen( QRegion damage, ToplevelList windows )
{
glPushMatrix();
glClearColor( 0, 0, 0, 1 );
glClear( GL_COLOR_BUFFER_BIT );
glScalef( 1, -1, 1 );
glTranslatef( 0, -displayHeight(), 0 );
QList< Phase2Data > phase2;
QRegion region = damage;
// TODO repaint only damaged areas (means also don't do glXSwapBuffers and similar)
region = QRegion( 0, 0, displayWidth(), displayHeight());
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 ];
if( !w.isVisible())
continue;
if( region.isEmpty()) // completely clipped
continue;
if( !w.isOpaque())
{
phase2.prepend( Phase2Data( &w, region ));
continue;
}
w.paint( region, PAINT_OPAQUE );
// window is opaque, clip windows below
region -= w.shape().translated( w.x(), w.y());
}
paintBackground( region );
foreach( Phase2Data d, phase2 )
{
Window& w = *d.window;
w.paint( d.region, PAINT_TRANSLUCENT );
}
glPopMatrix();
if( root_db )
glXSwapBuffers( display(), glxroot );
else
{
glFlush();
glXWaitGL();
XCopyArea( display(), buffer, rootWindow(), gcroot, 0, 0, displayWidth(), displayHeight(), 0, 0 );
XFlush( display());
}
}
void SceneOpenGL::paintBackground( QRegion )
{
// TODO?
}
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 )
{
}
SceneOpenGL::Window::~Window()
{
}
void SceneOpenGL::Window::free()
{
discardTexture();
}
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 );
// this is swapped in order to get a conversion of OpenGL coordinates
// (binding to a texture is not affected by transforming the OpenGL scene)
texture_y_inverted = value ? false : true;
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 );
texture_y_inverted = true; // conversion to OpenGL coordinates
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())
{
// convert to OpenGL coordinates (this is mapping
// the pixmap to a texture, this is not affected
// by transforming the OpenGL scene)
int gly = toplevel->height() - r.y() - r.height();
texture_y_inverted = true;
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 );
if( root_db )
glDrawBuffer( GL_BACK );
glXMakeContextCurrent( display(), glxroot, glxroot, context );
}
#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 quadPaint( 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::paint( QRegion region, int mask )
{
if( mask & ( PAINT_OPAQUE | PAINT_TRANSLUCENT ))
{}
else if( mask & PAINT_OPAQUE )
{
if( !isOpaque())
return;
}
else if( mask & PAINT_TRANSLUCENT )
{
if( isOpaque())
return;
}
// paint only requested areas
if( region != infiniteRegion()) // avoid integer overflow
region.translate( -x(), -y());
region &= shape();
if( region.isEmpty())
return;
bindTexture();
glPushMatrix();
glTranslatef( x(), y(), 0 );
bool was_blend = glIsEnabled( GL_BLEND );
if( !isOpaque())
{
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}
if( toplevel->opacity() != 1.0 )
{
if( toplevel->hasAlpha())
{
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glColor4f( toplevel->opacity(), toplevel->opacity(), toplevel->opacity(),
toplevel->opacity());
}
else
{
float constant_alpha[] = { 0, 0, 0, toplevel->opacity() };
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE );
glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE );
glTexEnvi( GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE );
glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE );
glTexEnvi( GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT );
glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, constant_alpha );
}
}
glEnable( GL_TEXTURE_RECTANGLE_ARB );
glBegin( GL_QUADS );
foreach( QRect r, region.rects())
{
quadPaint( r.x(), r.y(), r.x() + r.width(), r.y() + r.height(),
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 );
}
if( !was_blend )
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