[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [kde-workspace/KDE/4.11] kwin: kwin: Add support for EXT_buffer_age
From:       Fredrik_Höglund <fredrik () kde ! org>
Date:       2013-12-12 0:39:52
Message-ID: E1VquJk-0005Bp-SC () scm ! kde ! org
[Download RAW message or body]

Git commit 64c603ca90699a683a604cf2db0d5b01bab8c3c5 by Fredrik Höglund.
Committed on 21/11/2013 at 09:44.
Pushed by fredrik into branch 'KDE/4.11'.

kwin: Add support for EXT_buffer_age

This patch adds support for GLX_EXT_buffer_age, and
EGL_EXT_buffer_age on X11.

M  +50   -2    kwin/eglonxbackend.cpp
M  +1    -0    kwin/eglonxbackend.h
M  +51   -1    kwin/glxbackend.cpp
M  +1    -0    kwin/glxbackend.h
M  +7    -0    kwin/libkwineffects/kwinglutils_funcs.h
M  +30   -4    kwin/scene.cpp
M  +6    -1    kwin/scene.h
M  +40   -5    kwin/scene_opengl.cpp
M  +28   -0    kwin/scene_opengl.h
M  +3    -3    kwin/scene_xrender.cpp

http://commits.kde.org/kde-workspace/64c603ca90699a683a604cf2db0d5b01bab8c3c5

diff --git a/kwin/eglonxbackend.cpp b/kwin/eglonxbackend.cpp
index 08762c5..dd41da5 100644
--- a/kwin/eglonxbackend.cpp
+++ b/kwin/eglonxbackend.cpp
@@ -36,6 +36,7 @@ EglOnXBackend::EglOnXBackend()
     : OpenGLBackend()
     , ctx(EGL_NO_CONTEXT)
     , surfaceHasSubPost(0)
+    , m_bufferAge(0)
 {
     init();
     // Egl is always direct rendering
@@ -98,6 +99,16 @@ void EglOnXBackend::init()
             }
         }
     }
+
+    setSupportsBufferAge(false);
+
+    if (hasGLExtension("EGL_EXT_buffer_age")) {
+        const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE");
+
+        if (useBufferAge != "0")
+            setSupportsBufferAge(true);
+    }
+
     setSyncsToVBlank(false);
     setBlocksForRetrace(false);
     gs_tripleBufferNeedsDetection = false;
@@ -267,6 +278,13 @@ void EglOnXBackend::present()
     if (lastDamage().isEmpty())
         return;
 
+    if (supportsBufferAge()) {
+        eglSwapBuffers(dpy, surface);
+        eglQuerySurface(dpy, surface, EGL_BUFFER_AGE_EXT, &m_bufferAge);
+        setLastDamage(QRegion());
+        return;
+    }
+
     const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
     const bool fullRepaint = (lastDamage() == displayRegion);
 
@@ -311,8 +329,11 @@ void EglOnXBackend::present()
 void EglOnXBackend::screenGeometryChanged(const QSize &size)
 {
     Q_UNUSED(size)
-    // no backend specific code needed
+
     // TODO: base implementation in OpenGLBackend
+
+    // The back buffer contents are now undefined
+    m_bufferAge = 0;
 }
 
 SceneOpenGL::TexturePrivate *EglOnXBackend::createBackendTexture(SceneOpenGL::Texture *texture)
@@ -322,6 +343,8 @@ SceneOpenGL::TexturePrivate *EglOnXBackend::createBackendTexture(SceneOpenGL::Te
 
 QRegion EglOnXBackend::prepareRenderingFrame()
 {
+    QRegion repaint;
+
     if (gs_tripleBufferNeedsDetection) {
         // the composite timer floors the repaint frequency. This can pollute our triple buffering
         // detection because the glXSwapBuffers call for the new frame has to wait until the pending
@@ -332,14 +355,35 @@ QRegion EglOnXBackend::prepareRenderingFrame()
     }
 
     present();
+
+    if (supportsBufferAge())
+        repaint = accumulatedDamageHistory(m_bufferAge);
+
     startRenderTimer();
     eglWaitNative(EGL_CORE_NATIVE_ENGINE);
 
-    return QRegion();
+    return repaint;
 }
 
 void EglOnXBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
 {
+    if (damagedRegion.isEmpty()) {
+        setLastDamage(QRegion());
+
+        // If the damaged region of a window is fully occluded, the only
+        // rendering done, if any, will have been to repair a reused back
+        // buffer, making it identical to the front buffer.
+        //
+        // In this case we won't post the back buffer. Instead we'll just
+        // set the buffer age to 1, so the repaired regions won't be
+        // rendered again in the next frame.
+        if (!renderedRegion.isEmpty())
+            glFlush();
+
+        m_bufferAge = 1;
+        return;
+    }
+
     setLastDamage(renderedRegion);
 
     if (!blocksForRetrace()) {
@@ -354,6 +398,10 @@ void EglOnXBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegi
 
     if (overlayWindow()->window())  // show the window only after the first pass,
         overlayWindow()->show();   // since that pass may take long
+
+    // Save the damaged region to history
+    if (supportsBufferAge())
+        addToDamageHistory(damagedRegion);
 }
 
 /************************************************
diff --git a/kwin/eglonxbackend.h b/kwin/eglonxbackend.h
index 60ac553..c2ca2ff 100644
--- a/kwin/eglonxbackend.h
+++ b/kwin/eglonxbackend.h
@@ -49,6 +49,7 @@ private:
     EGLSurface surface;
     EGLContext ctx;
     int surfaceHasSubPost;
+    int m_bufferAge;
     friend class EglTexture;
 };
 
diff --git a/kwin/glxbackend.cpp b/kwin/glxbackend.cpp
index 96f6817..73f463e 100644
--- a/kwin/glxbackend.cpp
+++ b/kwin/glxbackend.cpp
@@ -46,6 +46,7 @@ GlxBackend::GlxBackend()
     , fbconfig(NULL)
     , glxWindow(None)
     , ctx(None)
+    , m_bufferAge(0)
     , haveSwapInterval(false)
 {
     init();
@@ -104,8 +105,19 @@ void GlxBackend::init()
         options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen
     glPlatform->printResults();
     initGL(GlxPlatformInterface);
+
     // Check whether certain features are supported
     haveSwapInterval = glXSwapIntervalMESA || glXSwapIntervalEXT || glXSwapIntervalSGI;
+
+    setSupportsBufferAge(false);
+
+    if (hasGLExtension("GLX_EXT_buffer_age")) {
+        const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE");
+
+        if (useBufferAge != "0")
+            setSupportsBufferAge(true);
+    }
+
     setSyncsToVBlank(false);
     setBlocksForRetrace(false);
     haveWaitSync = false;
@@ -426,6 +438,13 @@ void GlxBackend::present()
     if (lastDamage().isEmpty())
         return;
 
+    if (supportsBufferAge()) {
+        glXSwapBuffers(display(), glxWindow);
+        glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge);
+        setLastDamage(QRegion());
+        return;
+    }
+
     const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
     const bool fullRepaint = (lastDamage() == displayRegion);
 
@@ -486,6 +505,9 @@ void GlxBackend::screenGeometryChanged(const QSize &size)
 
     glXMakeCurrent(display(), glxWindow, ctx);
     glViewport(0, 0, size.width(), size.height());
+
+    // The back buffer contents are now undefined
+    m_bufferAge = 0;
 }
 
 SceneOpenGL::TexturePrivate *GlxBackend::createBackendTexture(SceneOpenGL::Texture *texture)
@@ -495,6 +517,8 @@ SceneOpenGL::TexturePrivate *GlxBackend::createBackendTexture(SceneOpenGL::Textu
 
 QRegion GlxBackend::prepareRenderingFrame()
 {
+    QRegion repaint;
+
     if (gs_tripleBufferNeedsDetection) {
         // the composite timer floors the repaint frequency. This can pollute our triple buffering
         // detection because the glXSwapBuffers call for the new frame has to wait until the pending
@@ -503,15 +527,37 @@ QRegion GlxBackend::prepareRenderingFrame()
         // fllush the buffer queue
         usleep(1000);
     }
+
     present();
+
+    if (supportsBufferAge())
+        repaint = accumulatedDamageHistory(m_bufferAge);
+
     startRenderTimer();
     glXWaitX();
 
-    return QRegion();
+    return repaint;
 }
 
 void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
 {
+    if (damagedRegion.isEmpty()) {
+        setLastDamage(QRegion());
+
+        // If the damaged region of a window is fully occluded, the only
+        // rendering done, if any, will have been to repair a reused back
+        // buffer, making it identical to the front buffer.
+        //
+        // In this case we won't post the back buffer. Instead we'll just
+        // set the buffer age to 1, so the repaired regions won't be
+        // rendered again in the next frame.
+        if (!renderedRegion.isEmpty())
+            glFlush();
+
+        m_bufferAge = 1;
+        return;
+    }
+
     setLastDamage(renderedRegion);
 
     if (!blocksForRetrace()) {
@@ -526,6 +572,10 @@ void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion
 
     if (overlayWindow()->window())  // show the window only after the first pass,
         overlayWindow()->show();   // since that pass may take long
+
+    // Save the damaged region to history
+    if (supportsBufferAge())
+        addToDamageHistory(damagedRegion);
 }
 
 
diff --git a/kwin/glxbackend.h b/kwin/glxbackend.h
index ce3b3f4..98538c3 100644
--- a/kwin/glxbackend.h
+++ b/kwin/glxbackend.h
@@ -64,6 +64,7 @@ private:
     GLXFBConfig fbconfig;
     GLXWindow glxWindow;
     GLXContext ctx;
+    int m_bufferAge;
     bool haveSwapInterval, haveWaitSync;
     friend class GlxTexture;
 };
diff --git a/kwin/libkwineffects/kwinglutils_funcs.h b/kwin/libkwineffects/kwinglutils_funcs.h
index d2248e5..09faa26 100644
--- a/kwin/libkwineffects/kwinglutils_funcs.h
+++ b/kwin/libkwineffects/kwinglutils_funcs.h
@@ -77,6 +77,9 @@ void KWIN_EXPORT glResolveFunctions(OpenGLPlatformInterface platformInterface);
 #define GL_READ_FRAMEBUFFER               0x8CA8
 #endif
 
+#ifndef GLX_BACK_BUFFER_AGE_EXT
+#define GLX_BACK_BUFFER_AGE_EXT           0x20F4
+#endif
 
 #include <fixx11h.h>
 
@@ -503,6 +506,10 @@ extern KWIN_EXPORT glCopyBufferSubData_func glCopyBufferSubData;
 #define EGL_POST_SUB_BUFFER_SUPPORTED_NV 0x30BE
 #endif
 
+#ifndef EGL_BUFFER_AGE_EXT
+#define EGL_BUFFER_AGE_EXT 0x313D
+#endif
+
 #ifndef GL_UNPACK_ROW_LENGTH
 #define GL_UNPACK_ROW_LENGTH 0x0CF2
 #endif
diff --git a/kwin/scene.cpp b/kwin/scene.cpp
index d5a3a83..0fc30f8 100644
--- a/kwin/scene.cpp
+++ b/kwin/scene.cpp
@@ -104,7 +104,8 @@ Scene::~Scene()
 }
 
 // returns mask and possibly modified region
-void Scene::paintScreen(int* mask, const QRegion &damage, QRegion *validRegion)
+void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint,
+                        QRegion *updateRegion, QRegion *validRegion)
 {
     const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
     *mask = (damage == displayRegion) ? 0 : PAINT_SCREEN_REGION;
@@ -137,6 +138,7 @@ void Scene::paintScreen(int* mask, const QRegion &damage, QRegion *validRegion)
     }
 
     painted_region = region;
+    repaint_region = repaint;
 
     if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) {
         paintBackground(region);
@@ -152,8 +154,12 @@ void Scene::paintScreen(int* mask, const QRegion &damage, QRegion *validRegion)
     effects->postPaintScreen();
 
     // make sure not to go outside of the screen area
+    *updateRegion = damaged_region;
     *validRegion = (region | painted_region) & displayRegion;
 
+    repaint_region = QRegion();
+    damaged_region = QRegion();
+
     // make sure all clipping is restored
     Q_ASSERT(!PaintClipper::clip());
 }
@@ -233,6 +239,8 @@ void Scene::paintGenericScreen(int orig_mask, ScreenPaintData)
     foreach (const Phase2Data & d, phase2) {
         paintWindow(d.window, d.mask, d.region, d.quads);
     }
+
+    damaged_region = QRegion(0, 0, displayWidth(), displayHeight());
 }
 
 // The optimized case without any transformations at all.
@@ -309,8 +317,13 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region)
         w->suspendUnredirect(data.mask & PAINT_WINDOW_TRANSLUCENT);
     }
 
-    const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
+    // Save the part of the repaint region that's exclusively rendered to
+    // bring a reused back buffer up to date. Then union the dirty region
+    // with the repaint region.
+    const QRegion repaintClip = repaint_region - dirtyArea;
+    dirtyArea |= repaint_region;
 
+    const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
     bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations
     if (!fullRepaint) {
         extendPaintRegion(dirtyArea, opaqueFullscreen);
@@ -318,6 +331,8 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region)
     }
 
     QRegion allclips, upperTranslucentDamage;
+    upperTranslucentDamage = repaint_region;
+
     // This is the occlusion culling pass
     for (int i = phase2data.count() - 1; i >= 0; --i) {
         QPair< Window*, Phase2Data > *entry = &phase2data[i];
@@ -362,10 +377,21 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region)
 
         paintWindow(data->window, data->mask, data->region, data->quads);
     }
-    if (fullRepaint)
+
+    if (fullRepaint) {
         painted_region = displayRegion;
-    else
+        damaged_region = displayRegion;
+    } else {
         painted_region |= paintedArea;
+
+        // Clip the repainted region from the damaged region.
+        // It's important that we don't add the union of the damaged region
+        // and the repainted region to the damage history. Otherwise the
+        // repaint region will grow with every frame until it eventually
+        // covers the whole back buffer, at which point we're always doing
+        // full repaints.
+        damaged_region = paintedArea - repaintClip;
+    }
 }
 
 static Scene::Window *s_recursionCheck = NULL;
diff --git a/kwin/scene.h b/kwin/scene.h
index 927dac0..b988483 100644
--- a/kwin/scene.h
+++ b/kwin/scene.h
@@ -115,7 +115,8 @@ public Q_SLOTS:
     virtual void windowClosed(KWin::Toplevel* c, KWin::Deleted* deleted) = 0;
 protected:
     // shared implementation, starts painting the screen
-    void paintScreen(int* mask, const QRegion &damage, QRegion *validRegion);
+    void paintScreen(int *mask, const QRegion &damage, const QRegion &repaint,
+                     QRegion *updateRegion, QRegion *validRegion);
     friend class EffectsHandlerImpl;
     // called after all effects had their paintScreen() called
     void finalPaintScreen(int mask, QRegion region, ScreenPaintData& data);
@@ -160,6 +161,10 @@ protected:
     // up all the way from paintSimpleScreen() up to paintScreen(), so save them here rather
     // than propagate them up in arguments.
     QRegion painted_region;
+    // Additional damage that needs to be repaired to bring a reused back buffer up to date
+    QRegion repaint_region;
+    // The dirty region before it was unioned with repaint_region
+    QRegion damaged_region;
     // time since last repaint
     int time_diff;
     QElapsedTimer last_time;
diff --git a/kwin/scene_opengl.cpp b/kwin/scene_opengl.cpp
index 686b720..961e81f 100644
--- a/kwin/scene_opengl.cpp
+++ b/kwin/scene_opengl.cpp
@@ -87,6 +87,7 @@ OpenGLBackend::OpenGLBackend()
     , m_syncsToVBlank(false)
     , m_blocksForRetrace(false)
     , m_directRendering(false)
+    , m_haveBufferAge(false)
     , m_failed(false)
 {
 }
@@ -111,6 +112,29 @@ void OpenGLBackend::idle()
         present();
 }
 
+void OpenGLBackend::addToDamageHistory(const QRegion &region)
+{
+    if (m_damageHistory.count() > 10)
+        m_damageHistory.removeLast();
+
+    m_damageHistory.prepend(region);
+}
+
+QRegion OpenGLBackend::accumulatedDamageHistory(int bufferAge) const
+{
+    QRegion region;
+
+    // Note: An age of zero means the buffer contents are undefined
+    if (bufferAge > 0 && bufferAge <= m_damageHistory.count()) {
+        for (int i = 0; i < bufferAge - 1; i++)
+            region |= m_damageHistory[i];
+    } else {
+        region = QRegion(0, 0, displayWidth(), displayHeight());
+    }
+
+    return region;
+}
+
 /************************************************
  * SceneOpenGL
  ***********************************************/
@@ -344,7 +368,7 @@ qint64 SceneOpenGL::paint(QRegion damage, ToplevelList toplevels)
         stacking_order.append(windows[ c ]);
     }
 
-    m_backend->prepareRenderingFrame();
+    QRegion repaint = m_backend->prepareRenderingFrame();
 
     const GLenum status = glGetGraphicsResetStatus();
     if (status != GL_NO_ERROR) {
@@ -357,25 +381,33 @@ qint64 SceneOpenGL::paint(QRegion damage, ToplevelList toplevels)
     checkGLError("Paint1");
 #endif
 
-    QRegion validRegion;
-    paintScreen(&mask, damage, &validRegion);   // call generic implementation
+    // After this call, updateRegion will contain the damaged region in the
+    // back buffer. This is the region that needs to be posted to repair
+    // the front buffer. It doesn't include the additional damage returned
+    // by prepareRenderingFrame(). validRegion is the region that has been
+    // repainted, and may be larger than updateRegion.
+    QRegion updateRegion, validRegion;
+    paintScreen(&mask, damage, repaint, &updateRegion, &validRegion);   // call generic implementation
 
 #ifndef KWIN_HAVE_OPENGLES
     const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
 
     // copy dirty parts from front to backbuffer
-    if (options->glPreferBufferSwap() == Options::CopyFrontBuffer && validRegion != displayRegion) {
+    if (!m_backend->supportsBufferAge() &&
+        options->glPreferBufferSwap() == Options::CopyFrontBuffer &&
+        validRegion != displayRegion) {
         glReadBuffer(GL_FRONT);
         copyPixels(displayRegion - validRegion);
         glReadBuffer(GL_BACK);
         damage = displayRegion;
     }
 #endif
+
 #ifdef CHECK_GL_ERROR
     checkGLError("Paint2");
 #endif
 
-    m_backend->endRenderingFrame(validRegion, validRegion);
+    m_backend->endRenderingFrame(validRegion, updateRegion);
 
     // do cleanup
     stacking_order.clear();
@@ -431,6 +463,9 @@ void SceneOpenGL::paintBackground(QRegion region)
 
 void SceneOpenGL::extendPaintRegion(QRegion &region, bool opaqueFullscreen)
 {
+    if (m_backend->supportsBufferAge())
+        return;
+
     if (options->glPreferBufferSwap() == Options::ExtendDamage) { // only Extend "large" repaints
         const QRegion displayRegion(0, 0, displayWidth(), displayHeight());
         uint damagedPixels = 0;
diff --git a/kwin/scene_opengl.h b/kwin/scene_opengl.h
index baad5dd..753d1ca 100644
--- a/kwin/scene_opengl.h
+++ b/kwin/scene_opengl.h
@@ -543,6 +543,21 @@ public:
     bool isDirectRendering() const {
         return m_directRendering;
     }
+
+    bool supportsBufferAge() const {
+        return m_haveBufferAge;
+    }
+
+    /**
+     * Returns the damage that has accumulated since a buffer of the given age was presented.
+     */
+    QRegion accumulatedDamageHistory(int bufferAge) const;
+
+    /**
+     * Saves the given region to damage history.
+     */
+    void addToDamageHistory(const QRegion &region);
+
 protected:
     /**
      * @brief Backend specific flushing of frame to screen.
@@ -589,6 +604,11 @@ protected:
     void setIsDirectRendering(bool direct) {
         m_directRendering = direct;
     }
+
+    void setSupportsBufferAge(bool value) {
+        m_haveBufferAge = value;
+    }
+
     /**
      * @return const QRegion& Damage of previously rendered frame
      **/
@@ -627,6 +647,10 @@ private:
      **/
     bool m_directRendering;
     /**
+     * @brief Whether the backend supports GLX_EXT_buffer_age / EGL_EXT_buffer_age.
+     */
+    bool m_haveBufferAge;
+    /**
      * @brief Whether the initialization failed, of course default to @c false.
      **/
     bool m_failed;
@@ -635,6 +659,10 @@ private:
      **/
     QRegion m_lastDamage;
     /**
+     * @brief The damage history for the past 10 frames.
+     */
+    QList<QRegion> m_damageHistory;
+    /**
      * @brief Timer to measure how long a frame renders.
      **/
     QElapsedTimer m_renderTimer;
diff --git a/kwin/scene_xrender.cpp b/kwin/scene_xrender.cpp
index cd6a6d9..fb53288 100644
--- a/kwin/scene_xrender.cpp
+++ b/kwin/scene_xrender.cpp
@@ -190,13 +190,13 @@ qint64 SceneXrender::paint(QRegion damage, ToplevelList toplevels)
     }
 
     int mask = 0;
-    QRegion validRegion;
-    paintScreen(&mask, damage, &validRegion);
+    QRegion updateRegion, validRegion;
+    paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion);
 
     if (m_overlayWindow->window())  // show the window only after the first pass, since
         m_overlayWindow->show();   // that pass may take long
 
-    present(mask, damage);
+    present(mask, updateRegion);
     // do cleanup
     stacking_order.clear();
 
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic