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
parent
a9e49e218f
commit
eeb309c149
|
@ -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);
|
||||
}
|
||||
|
||||
/************************************************
|
||||
|
|
|
@ -49,6 +49,7 @@ private:
|
|||
EGLSurface surface;
|
||||
EGLContext ctx;
|
||||
int surfaceHasSubPost;
|
||||
int m_bufferAge;
|
||||
friend class EglTexture;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ private:
|
|||
GLXFBConfig fbconfig;
|
||||
GLXWindow glxWindow;
|
||||
GLXContext ctx;
|
||||
int m_bufferAge;
|
||||
bool haveSwapInterval, haveWaitSync;
|
||||
friend class GlxTexture;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
34
scene.cpp
34
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;
|
||||
|
|
7
scene.h
7
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
**/
|
||||
|
@ -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.
|
||||
**/
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue