diff --git a/composite.cpp b/composite.cpp index 4449bf2509..5ffdc6a1a5 100644 --- a/composite.cpp +++ b/composite.cpp @@ -57,6 +57,7 @@ along with this program. If not, see . #include #include +#include #include #include #include @@ -197,12 +198,9 @@ void Workspace::setupCompositing() delete cm_selection; return; } - int rate = xrrRefreshRate = KWin::currentRefreshRate(); - compositeRate = 1000 / rate; - lastCompositePaint.start(); - // fake a previous paint, so that the next starts right now - nextPaintReference = QTime::currentTime().addMSecs( -compositeRate ); - compositeTimer.setSingleShot( true ); + xrrRefreshRate = KWin::currentRefreshRate(); + // invalidate timer -> bounds delay to 0 and the update happens instantly + nextPaintReference = QTime::currentTime().addMSecs( -1000 ); checkCompositeTimer(); composite_paint_times.clear(); XCompositeRedirectSubwindows( display(), rootWindow(), CompositeRedirectManual ); @@ -253,7 +251,9 @@ void Workspace::finishCompositing() effects = NULL; delete scene; scene = NULL; - compositeTimer.stop(); + if (compositeTimer) + killTimer(compositeTimer); + compositeTimer = 0; mousePollingTimer.stop(); repaints_region = QRegion(); for( ClientList::ConstIterator it = clients.constBegin(); @@ -370,24 +370,27 @@ void Workspace::addRepaintFull() checkCompositeTimer(); } +void Workspace::timerEvent( QTimerEvent *te ) +{ + if ( te->timerId() == compositeTimer ) + { + killTimer( compositeTimer ); + compositeTimer = 0; + performCompositing(); + } + else + QObject::timerEvent( te ); +} + void Workspace::performCompositing() { #ifdef KWIN_HAVE_COMPOSITING - // The event loop apparently tries to fire a QTimer as often as possible, even - // at the expense of not processing many X events. This means that the composite - // repaints can seriously impact performance of everything else, therefore throttle - // them - leave at least 1msec time after one repaint is finished and next one - // is started. - if( lastCompositePaint.elapsed() < 1 ) - { - compositeTimer.start( 1 ); - return; - } - if( !scene->waitSyncAvailable()) - nextPaintReference = QTime::currentTime(); if((( repaints_region.isEmpty() && !windowRepaintsPending()) // no damage || !overlay_visible )) // nothing is visible anyway { + // invalidate timer, we're idle, thus have already waited maxFps don't want to + // wait more when we woke up + nextPaintReference = QTime::currentTime().addMSecs( -1000 ); scene->idle(); // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not @@ -395,6 +398,8 @@ void Workspace::performCompositing() // Otherwise the window would not be painted normally anyway. return; } + // we paint now, how much time ever it takes, we wanna show up in maxfps from now + nextPaintReference = QTime::currentTime(); // create a list of all windows in the stacking order ToplevelList windows = xStackingOrder(); foreach( EffectWindow* c, static_cast< EffectsHandlerImpl* >( effects )->elevatedWindows()) @@ -403,19 +408,16 @@ void Workspace::performCompositing() windows.removeAll( t ); windows.append( t ); } +#if 0 // skip windows that are not yet ready for being painted ToplevelList tmp = windows; windows.clear(); -#if 0 // There is a bug somewhere that prevents this from working properly (#160393), but additionally // this cannot be used so carelessly - needs protections against broken clients, the window // should not get focus before it's displayed, handle unredirected windows properly and so on. foreach( Toplevel* c, tmp ) if( c->readyForPainting()) windows.append( c ); -#else - foreach( Toplevel* c, tmp ) - windows.append( c ); #endif foreach( Toplevel* c, windows ) { // This could be possibly optimized WRT obscuring, but that'd need being already @@ -423,7 +425,7 @@ void Workspace::performCompositing() // TODO I think effects->transformWindowDamage() doesn't need to be called here, // pre-paint will extend painted window areas as necessary. repaints_region |= c->repaints().translated( c->pos()); - repaints_region |= c->decorationPendingRegion(); + repaints_region |= c->decorationPendingRegion(); c->resetRepaints( c->decorationRect()); } QRegion repaints = repaints_region; @@ -431,23 +433,12 @@ void Workspace::performCompositing() repaints_region = QRegion(); QTime t = QTime::currentTime(); scene->paint( repaints, windows ); - if( scene->waitSyncAvailable()) - { - // If vsync is used, schedule the next repaint slightly in advance of the next sync, - // so that there is still time for the drawing to take place. We have just synced, and - // nextPaintReference is time from which multiples of compositeRate should be added, - // so set it 10ms back (meaning next paint will be in 'compositeRate - 10'). - // However, make sure the reserve is smaller than the composite rate. - int reserve = compositeRate <= 10 ? compositeRate - 1 : 10; - nextPaintReference = QTime::currentTime().addMSecs( -reserve ); - } // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and // checkCompositeTime() would restart it again somewhen later, called from functions that // would again add something pending. checkCompositeTimer(); checkCompositePaintTime( t.elapsed()); - lastCompositePaint.start(); #endif } @@ -477,9 +468,11 @@ void Workspace::setCompositeTimer() { if( !compositing()) // should not really happen, but there may be e.g. some damage events still pending return; - // The last paint set nextPaintReference as a reference time to which multiples of compositeRate // should be added for the next paint. qBound() for protection; system time can change without notice. - compositeTimer.start( qBound( 0, nextPaintReference.msecsTo( QTime::currentTime() ), 250 ) % compositeRate ); + if ( compositeTimer ) + killTimer( compositeTimer ); + int delay = options->maxFpsInterval - (qBound( 0, nextPaintReference.msecsTo( QTime::currentTime() ), 250 ) % options->maxFpsInterval); + compositeTimer = startTimer( delay ); } void Workspace::startMousePolling() @@ -567,7 +560,9 @@ void Workspace::checkCompositePaintTime( int msec ) "If this was only a temporary problem, you can resume using the '%1' shortcut.\n" "You can disable functionality checks in System Settings (on the Advanced tab in Desktop Effects).", shortcut ); Notify::raise( Notify::CompositingSlow, message ); - compositeTimer.start( 1000 ); // so that it doesn't trigger sooner than suspendCompositing() + if ( compositeTimer ) + killTimer( compositeTimer ); + compositeTimer = startTimer( 1000 ); // so that it doesn't trigger sooner than suspendCompositing() } } diff --git a/options.cpp b/options.cpp index 1fc9eaa37a..687aece7a4 100644 --- a/options.cpp +++ b/options.cpp @@ -66,9 +66,11 @@ int currentRefreshRate() { QString reply = QString::fromLocal8Bit( nvidia_settings.readAllStandardOutput() ); bool ok; - rate = reply.split(' ').first().split(KGlobal::locale()->decimalSymbol()).first().toUInt( &ok ); + const float frate = reply.split(' ').first().toFloat( &ok ); if ( !ok ) rate = -1; + else + rate = qRound(frate); } } #ifdef HAVE_XRANDR @@ -247,6 +249,7 @@ unsigned long Options::updateSettings() CmdAllWheel = mouseWheelCommand(config.readEntry("CommandAllWheel","Nothing")); config=KConfigGroup(_config,"Compositing"); + maxFpsInterval = qRound(1000.0/config.readEntry( "MaxFPS", 35 )); refreshRate = config.readEntry( "RefreshRate", 0 ); // Read button tooltip animation effect from kdeglobals diff --git a/options.h b/options.h index 9fdc3ba686..2fdc7527af 100644 --- a/options.h +++ b/options.h @@ -365,6 +365,7 @@ class Options : public KDecorationOptions // XRender bool xrenderSmoothScale; + uint maxFpsInterval; // Settings that should be auto-detected uint refreshRate; bool glDirect; diff --git a/scene_opengl.cpp b/scene_opengl.cpp index 1efe3b000e..3b89f3cce2 100644 --- a/scene_opengl.cpp +++ b/scene_opengl.cpp @@ -91,6 +91,8 @@ Sources and other compositing managers: namespace KWin { +extern int currentRefreshRate(); + //**************************************** // SceneOpenGL //**************************************** @@ -109,6 +111,9 @@ GLXDrawable SceneOpenGL::last_pixmap = None; bool SceneOpenGL::tfp_mode; // using glXBindTexImageEXT (texture_from_pixmap) bool SceneOpenGL::db; // destination drawable is double-buffered bool SceneOpenGL::shm_mode; +uint SceneOpenGL::vBlankInterval; +uint SceneOpenGL::estimatedRenderTime = 0xfffffff; // Looooong - to ensure we wait on the first frame +QTime SceneOpenGL::lastVBlank; #ifdef HAVE_XSHM XShmSegmentInfo SceneOpenGL::shm; #endif @@ -124,6 +129,8 @@ SceneOpenGL::SceneOpenGL( Workspace* ws ) kDebug( 1212 ) << "No glx extensions available"; return; // error } + vBlankInterval = (1000<<10) / KWin::currentRefreshRate(); + lastVBlank = QTime::currentTime(); initGLX(); // check for FBConfig support if( !hasGLExtension( "GLX_SGIX_fbconfig" ) || !glXGetFBConfigAttrib || !glXGetFBConfigs || @@ -796,22 +803,30 @@ void SceneOpenGL::waitSync() { // NOTE that vsync has no effect with indirect rendering if( waitSyncAvailable()) { - unsigned int sync; - - glFlush(); - glXGetVideoSync( &sync ); - glXWaitVideoSync( 2, ( sync + 1 ) % 2, &sync ); + // hackalert - we abuse "sync" as "remaining time before next estimated vblank" + // reason: we do not "just wait" for the next vsync if we estimate that we can paint the + // entire frame before this event, what could effectively mean "usleep(10000)", as it used to + uint sync = ((lastVBlank.msecsTo( QTime::currentTime() )<<10) % vBlankInterval); + if ( sync < (estimatedRenderTime+1)<<10 ) + { + glFlush(); + glXGetVideoSync( &sync ); + glXWaitVideoSync( 2, ( sync + 1 ) % 2, &sync ); + lastVBlank = QTime::currentTime(); + } } } // actually paint to the screen (double-buffer swap or copy from pixmap buffer) void SceneOpenGL::flushBuffer( int mask, QRegion damage ) { + QTime t; if( db ) { if( mask & PAINT_SCREEN_REGION ) { waitSync(); + t = QTime::currentTime(); if( glXCopySubBuffer ) { foreach( const QRect &r, damage.rects()) @@ -849,22 +864,28 @@ void SceneOpenGL::flushBuffer( int mask, QRegion damage ) else { waitSync(); + t = QTime::currentTime(); glXSwapBuffers( display(), glxbuffer ); } glXWaitGL(); XFlush( display()); + estimatedRenderTime = t.elapsed(); } else { + t = QTime::currentTime(); glFlush(); glXWaitGL(); + estimatedRenderTime = t.elapsed(); waitSync(); + t = QTime::currentTime(); if( mask & PAINT_SCREEN_REGION ) foreach( const QRect &r, damage.rects()) XCopyArea( display(), buffer, rootWindow(), gcroot, r.x(), r.y(), r.width(), r.height(), r.x(), r.y()); else XCopyArea( display(), buffer, rootWindow(), gcroot, 0, 0, displayWidth(), displayHeight(), 0, 0 ); XFlush( display()); + estimatedRenderTime += t.elapsed(); } } diff --git a/scene_opengl.h b/scene_opengl.h index 34693b4d8e..44e367d508 100644 --- a/scene_opengl.h +++ b/scene_opengl.h @@ -90,6 +90,8 @@ class SceneOpenGL static GLXDrawable last_pixmap; // for a workaround in bindTexture() static bool tfp_mode; static bool shm_mode; + static uint vBlankInterval, estimatedRenderTime; + static QTime lastVBlank; QHash< Toplevel*, Window* > windows; #ifdef HAVE_XSHM static XShmSegmentInfo shm; diff --git a/workspace.cpp b/workspace.cpp index 0bd618eccf..b63ecf3053 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -151,7 +151,8 @@ Workspace::Workspace( bool restore ) , forced_global_mouse_grab( false ) , cm_selection( NULL ) , compositingSuspended( false ) - , compositeRate( 0 ) + , compositeTimer( 0 ) + , vBlankInterval( 0 ) , xrrRefreshRate( 0 ) , overlay( None ) , overlay_visible( true ) @@ -384,7 +385,6 @@ void Workspace::init() connect( &reconfigureTimer, SIGNAL( timeout() ), this, SLOT( slotReconfigure() )); connect( &updateToolWindowsTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateToolWindows() )); - connect( &compositeTimer, SIGNAL( timeout() ), SLOT( performCompositing() )); connect( &mousePollingTimer, SIGNAL( timeout() ), SLOT( performMousePoll() )); connect( KGlobalSettings::self(), SIGNAL( appearanceChanged() ), this, SLOT( reconfigure() )); diff --git a/workspace.h b/workspace.h index eec1b2e904..9b5622ea5f 100644 --- a/workspace.h +++ b/workspace.h @@ -774,6 +774,7 @@ class Workspace : public QObject, public KDecorationDefines protected: bool keyPressMouseEmulation( XKeyEvent& ev ); + void timerEvent( QTimerEvent *te ); Q_SIGNALS: Q_SCRIPTABLE void compositingToggled( bool active ); @@ -1058,11 +1059,10 @@ class Workspace : public QObject, public KDecorationDefines KSelectionOwner* cm_selection; bool compositingSuspended; - QTimer compositeTimer; - QTime lastCompositePaint; + int compositeTimer; QTime nextPaintReference; QTimer mousePollingTimer; - int compositeRate; + uint vBlankInterval; int xrrRefreshRate; // used only for compositing QRegion repaints_region; Window overlay; // XComposite overlay window @@ -1348,7 +1348,7 @@ inline bool Workspace::hasClient( const Client* c ) inline void Workspace::checkCompositeTimer() { - if( !compositeTimer.isActive() ) + if( !compositeTimer ) setCompositeTimer(); }