[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 ®ion)
+{
+ 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 ®ion, 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 ®ion);
+
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