kwin: Add support for EXT_buffer_age

This patch adds support for GLX_EXT_buffer_age, and
EGL_EXT_buffer_age on X11.
icc-effect-5.14.5
Fredrik Höglund 2013-11-21 10:44:06 +01:00
parent a9e49e218f
commit eeb309c149
10 changed files with 217 additions and 16 deletions

View File

@ -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);
}
/************************************************

View File

@ -49,6 +49,7 @@ private:
EGLSurface surface;
EGLContext ctx;
int surfaceHasSubPost;
int m_bufferAge;
friend class EglTexture;
};

View File

@ -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);
}

View File

@ -64,6 +64,7 @@ private:
GLXFBConfig fbconfig;
GLXWindow glxWindow;
GLXContext ctx;
int m_bufferAge;
bool haveSwapInterval, haveWaitSync;
friend class GlxTexture;
};

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
**/
@ -626,6 +646,10 @@ private:
* @brief Whether direct rendering is used, defaults to @c false.
**/
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.
**/
@ -634,6 +658,10 @@ private:
* @brief Damaged region of previously rendered frame.
**/
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.
**/

View File

@ -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();