diff --git a/composite.cpp b/composite.cpp index 3850f3f1b5..2f18dbae7f 100644 --- a/composite.cpp +++ b/composite.cpp @@ -235,7 +235,7 @@ void Compositor::slotCompositingOptionsInitialized() } m_xrrRefreshRate = KWin::currentRefreshRate(); fpsInterval = (options->maxFpsInterval() << 10); - if (m_scene->waitSyncAvailable()) { // if we do vsync, set the fps to the next multiple of the vblank rate + if (m_scene->syncsToVBlank()) { // if we do vsync, set the fps to the next multiple of the vblank rate vBlankInterval = (1000 << 10) / m_xrrRefreshRate; fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); } else @@ -648,7 +648,7 @@ void Compositor::setCompositeTimer() uint padding = m_timeSinceLastVBlank << 10; - if (m_scene->waitSyncAvailable()) { + if (m_scene->blocksForRetrace()) { // TODO: make vBlankTime dynamic?! // It's required because glXWaitVideoSync will *likely* block a full frame if one enters diff --git a/eglonxbackend.cpp b/eglonxbackend.cpp index 0d3ef17260..5d66f950d0 100644 --- a/eglonxbackend.cpp +++ b/eglonxbackend.cpp @@ -53,6 +53,9 @@ EglOnXBackend::~EglOnXBackend() } } +static bool gs_tripleBufferUndetected = true; +static bool gs_tripleBufferNeedsDetection = false; + void EglOnXBackend::init() { if (!initRenderingContext()) { @@ -88,6 +91,10 @@ void EglOnXBackend::init() } } } + setSyncsToVBlank(false); + setBlocksForRetrace(false); + gs_tripleBufferNeedsDetection = false; + m_swapProfiler.init(); if (surfaceHasSubPost) { kDebug(1212) << "EGL implementation and surface support eglPostSubBufferNV, let's use it"; @@ -98,7 +105,13 @@ void EglOnXBackend::init() if (val >= 1) { if (eglSwapInterval(dpy, 1)) { kDebug(1212) << "Enabled v-sync"; - setHasWaitSync(true); + setSyncsToVBlank(true); + const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); + if (!tripleBuffer.isEmpty()) { + setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); + gs_tripleBufferUndetected = false; + } + gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; } } else { kWarning(1212) << "Cannot enable v-sync as max. swap interval is" << val; @@ -249,8 +262,19 @@ void EglOnXBackend::present() const bool fullRepaint = (lastDamage() == displayRegion); if (fullRepaint || !surfaceHasSubPost) { + if (gs_tripleBufferNeedsDetection) { + eglWaitGL(); + m_swapProfiler.begin(); + } // the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation) eglSwapBuffers(dpy, surface); + if (gs_tripleBufferNeedsDetection) { + eglWaitGL(); + if (char result = m_swapProfiler.end()) { + gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; + setBlocksForRetrace(result == 'd'); + } + } } else { // a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area foreach (const QRect & r, lastDamage().rects()) { diff --git a/glxbackend.cpp b/glxbackend.cpp index cf28a9acb5..8518b949f8 100644 --- a/glxbackend.cpp +++ b/glxbackend.cpp @@ -69,6 +69,9 @@ GlxBackend::~GlxBackend() checkGLError("Cleanup"); } +static bool gs_tripleBufferUndetected = true; +static bool gs_tripleBufferNeedsDetection = false; + void GlxBackend::init() { initGLX(); @@ -96,26 +99,32 @@ void GlxBackend::init() initGL(GlxPlatformInterface); // Check whether certain features are supported haveSwapInterval = glXSwapIntervalMESA || glXSwapIntervalEXT || glXSwapIntervalSGI; + setSyncsToVBlank(false); + setBlocksForRetrace(false); + haveWaitSync = false; + gs_tripleBufferNeedsDetection = false; + m_swapProfiler.init(); const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; - if (wantSync) { - if (glXGetVideoSync && haveSwapInterval && glXIsDirect(display(), ctx)) { + if (wantSync && glXIsDirect(display(), ctx)) { + if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable + setSwapInterval(1); + setSyncsToVBlank(true); + const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); + if (!tripleBuffer.isEmpty()) { + setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); + gs_tripleBufferUndetected = false; + } + gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; + } else if (glXGetVideoSync) { unsigned int sync; - if (glXGetVideoSync(&sync) == 0) { - if (glXWaitVideoSync(1, 0, &sync) == 0) { - // NOTICE at this time we should actually check whether we can successfully - // deactivate the swapInterval "glXSwapInterval(0) == 0" - // (because we don't actually want it active unless we explicitly run a glXSwapBuffers) - // However mesa/dri will return a range error (6) because deactivating the - // swapinterval (as of today) seems completely unsupported - setHasWaitSync(true); - setSwapInterval(1); - } else - qWarning() << "NO VSYNC! glXWaitVideoSync(1,0,&uint) isn't 0 but" << glXWaitVideoSync(1, 0, &sync); + if (glXGetVideoSync(&sync) == 0 && glXWaitVideoSync(1, 0, &sync) == 0) { + setSyncsToVBlank(true); + setBlocksForRetrace(true); + haveWaitSync = true; } else - qWarning() << "NO VSYNC! glXGetVideoSync(&uint) isn't 0 but" << glXGetVideoSync(&sync); + qWarning() << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken"; } else - qWarning() << "NO VSYNC! glXGetVideoSync, haveSwapInterval, glXIsDirect" << - bool(glXGetVideoSync) << haveSwapInterval << glXIsDirect(display(), ctx); + qWarning() << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported"; } else { // disable v-sync (if possible) setSwapInterval(0); @@ -364,7 +373,7 @@ void GlxBackend::setSwapInterval(int interval) void GlxBackend::waitSync() { // NOTE that vsync has no effect with indirect rendering - if (waitSyncAvailable()) { + if (haveWaitSync) { #if VSYNC_DEBUG startRenderTimer(); #endif @@ -403,8 +412,19 @@ void GlxBackend::present() if (fullRepaint) { if (haveSwapInterval) { + if (gs_tripleBufferNeedsDetection) { + glXWaitGL(); + m_swapProfiler.begin(); + } glXSwapBuffers(display(), glxWindow); startRenderTimer(); + if (gs_tripleBufferNeedsDetection) { + glXWaitGL(); + if (char result = m_swapProfiler.end()) { + gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; + setBlocksForRetrace(result == 'd'); + } + } } else { waitSync(); // calls startRenderTimer(); glXSwapBuffers(display(), glxWindow); diff --git a/glxbackend.h b/glxbackend.h index 49b897e71b..11b907966f 100644 --- a/glxbackend.h +++ b/glxbackend.h @@ -64,7 +64,7 @@ private: GLXFBConfig fbconfig; GLXWindow glxWindow; GLXContext ctx; - bool haveSwapInterval; + bool haveSwapInterval, haveWaitSync; friend class GlxTexture; }; diff --git a/scene.cpp b/scene.cpp index cb7d04b74d..71ebaaea60 100644 --- a/scene.cpp +++ b/scene.cpp @@ -561,7 +561,12 @@ void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) Q_UNUSED(opaqueFullscreen); } -bool Scene::waitSyncAvailable() const +bool Scene::blocksForRetrace() const +{ + return false; +} + +bool Scene::syncsToVBlank() const { return false; } diff --git a/scene.h b/scene.h index 4dc4f47d7c..2cfc5658b1 100644 --- a/scene.h +++ b/scene.h @@ -100,7 +100,8 @@ public: enum ImageFilterType { ImageFilterFast, ImageFilterGood }; // there's nothing to paint (adjust time_diff later) virtual void idle(); - virtual bool waitSyncAvailable() const; + virtual bool blocksForRetrace() const; + virtual bool syncsToVBlank() const; virtual OverlayWindow* overlayWindow() = 0; public Q_SLOTS: // a window has been destroyed diff --git a/scene_opengl.cpp b/scene_opengl.cpp index 062d391603..6b90576e1f 100644 --- a/scene_opengl.cpp +++ b/scene_opengl.cpp @@ -76,7 +76,8 @@ extern int currentRefreshRate(); //**************************************** OpenGLBackend::OpenGLBackend() : m_overlayWindow(new OverlayWindow()) // TODO: maybe create only if needed? - , m_waitSync(false) + , m_syncsToVBlank(false) + , m_blocksForRetrace(false) , m_directRendering(false) , m_failed(false) { @@ -239,9 +240,14 @@ OverlayWindow *SceneOpenGL::overlayWindow() return m_backend->overlayWindow(); } -bool SceneOpenGL::waitSyncAvailable() const +bool SceneOpenGL::syncsToVBlank() const { - return m_backend->waitSyncAvailable(); + return m_backend->syncsToVBlank(); +} + +bool SceneOpenGL::blocksForRetrace() const +{ + return m_backend->blocksForRetrace(); } void SceneOpenGL::idle() @@ -2220,4 +2226,34 @@ bool SceneOpenGLShadow::prepareBackend() return true; } +SwapProfiler::SwapProfiler() +{ + init(); +} + +void SwapProfiler::init() +{ + m_time = 2 * 1000*1000; // we start with a long time mean of 2ms ... + m_counter = 0; +} + +void SwapProfiler::begin() +{ + m_timer.start(); +} + +char SwapProfiler::end() +{ + // .. and blend in actual values. + // this way we prevent extremes from killing our long time mean + m_time = (10*m_time + m_timer.nsecsElapsed())/11; + if (++m_counter > 500) { + const bool blocks = m_time > 1000 * 1000; // 1ms, i get ~250µs and ~7ms w/o triple buffering... + kDebug(1212) << "Triple buffering detection:" << QString(blocks ? "NOT available" : "Available") << + " - Mean block time:" << m_time/(1000.0*1000.0) << "ms"; + return blocks ? 'd' : 't'; + } + return 0; +} + } // namespace diff --git a/scene_opengl.h b/scene_opengl.h index cb01664ea4..d6a658003e 100644 --- a/scene_opengl.h +++ b/scene_opengl.h @@ -51,7 +51,8 @@ public: virtual void windowDeleted(Deleted*); virtual void screenGeometryChanged(const QSize &size); virtual OverlayWindow *overlayWindow(); - virtual bool waitSyncAvailable() const; + virtual bool blocksForRetrace() const; + virtual bool syncsToVBlank() const; void idle(); @@ -382,6 +383,27 @@ private: GLTexture *m_texture; }; +/** + * @short Profiler to detect whether we have triple buffering + * The strategy is to start setBlocksForRetrace(false) but assume blocking and have the system prove that assumption wrong + **/ +class SwapProfiler +{ +public: + SwapProfiler(); + void init(); + void begin(); + /** + * @return char being 'd' for double, 't' for triple (or more - but non-blocking) buffering and + * 0 (NOT '0') otherwise, so you can act on "if (char result = SwapProfiler::end()) { fooBar(); } + **/ + char end(); +private: + QElapsedTimer m_timer; + qint64 m_time; + int m_counter; +}; + /** * @brief The OpenGLBackend creates and holds the OpenGL context and is responsible for Texture from Pixmap. * @@ -462,8 +484,17 @@ public: * * @return bool @c true if VSync support is available, @c false otherwise **/ - bool waitSyncAvailable() const { - return m_waitSync; + bool syncsToVBlank() const { + return m_syncsToVBlank; + } + /** + * @brief Whether VSync blocks execution until the screen is in the retrace + * + * Case for waitVideoSync and non triple buffering buffer swaps + * + **/ + bool blocksForRetrace() const { + return m_blocksForRetrace; } /** * @brief Whether the backend uses direct rendering. @@ -497,8 +528,18 @@ protected: * If the subclass does not call this method, the backend defaults to @c false. * @param enabled @c true if VSync support available, @c false otherwise. **/ - void setHasWaitSync(bool enabled) { - m_waitSync = enabled; + void setSyncsToVBlank(bool enabled) { + m_syncsToVBlank = enabled; + } + /** + * @brief Sets whether the VSync iplementation blocks + * + * Should be called by the concrete subclass once it is determined how VSync works. + * If the subclass does not call this method, the backend defaults to @c false. + * @param enabled @c true if VSync blocks, @c false otherwise. + **/ + void setBlocksForRetrace(bool enabled) { + m_blocksForRetrace = enabled; } /** * @brief Sets whether the OpenGL context is direct. @@ -530,15 +571,21 @@ protected: m_renderTimer.start(); } + SwapProfiler m_swapProfiler; + private: /** * @brief The OverlayWindow used by this Backend. **/ OverlayWindow *m_overlayWindow; /** - * @brief Whether VSync is available, defaults to @c false. + * @brief Whether VSync is available and used, defaults to @c false. **/ - bool m_waitSync; + bool m_syncsToVBlank; + /** + * @brief Whether present() will block execution until the next vertical retrace @c false. + **/ + bool m_blocksForRetrace; /** * @brief Whether direct rendering is used, defaults to @c false. **/