/***************************************************************** 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. Based on glcompmgr code by Felix Bellaby. ******************************************************************/ #include "scene_opengl.h" #include "utils.h" #include "client.h" #include #include 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 ); 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_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 }; 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 ); 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 ) { if( toplevel->hasAlpha()) { glEnable( GL_BLEND ); 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, 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