From 9aef5b85a0f8e43b6c7641d12ee003f74861d2d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20L=C3=BCbking?= Date: Mon, 18 Feb 2013 23:17:46 +0100 Subject: [PATCH] support a permanent glSwapBuffer either by - forcing fullrepaints unconditionally - turning a repaint to a full one beyond a threshhold - completing the the backbuffer from the frontbuffer after the paint BUG: 307965 FIXED-IN: 4.10 REVIEW: 107198 --- composite.cpp | 12 +---- eglonxbackend.cpp | 11 +++-- glxbackend.cpp | 117 ++++++++++++++++++++++++---------------------- options.cpp | 29 ++++++++++++ options.h | 13 ++++++ scene.cpp | 45 ++++++++++++++---- scene.h | 3 ++ scene_opengl.cpp | 77 +++++++++++++++++++++++++++++- scene_opengl.h | 1 + 9 files changed, 224 insertions(+), 84 deletions(-) diff --git a/composite.cpp b/composite.cpp index 5702ec01d2..1e77b5ce5c 100644 --- a/composite.cpp +++ b/composite.cpp @@ -501,8 +501,6 @@ void Compositor::timerEvent(QTimerEvent *te) QObject::timerEvent(te); } -static int s_pendingFlushes = 0; - void Compositor::performCompositing() { if (!isOverlayWindowVisible()) @@ -543,15 +541,7 @@ void Compositor::performCompositing() win->getDamageRegionReply(); } - bool pending = !repaints_region.isEmpty() || windowRepaintsPending(); - if (pending) - s_pendingFlushes = 3; - else if (m_scene->hasPendingFlush()) - --s_pendingFlushes; - else - s_pendingFlushes = 0; - if (s_pendingFlushes < 1) { - s_pendingFlushes = 0; + if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not diff --git a/eglonxbackend.cpp b/eglonxbackend.cpp index dcb70d82fd..a34685aea4 100644 --- a/eglonxbackend.cpp +++ b/eglonxbackend.cpp @@ -194,12 +194,13 @@ bool EglOnXBackend::initBufferConfigs() void EglOnXBackend::present() { - if (lastMask() & Scene::PAINT_SCREEN_REGION && surfaceHasSubPost && eglPostSubBufferNV) { - const QRect damageRect = lastDamage().boundingRect(); - - eglPostSubBufferNV(dpy, surface, damageRect.left(), displayHeight() - damageRect.bottom() - 1, damageRect.width(), damageRect.height()); - } else { + const bool swap = (options->glPreferBufferSwap() && options->glPreferBufferSwap() != Options::ExtendDamage) || + !(lastMask() & Scene::PAINT_SCREEN_REGION && surfaceHasSubPost && eglPostSubBufferNV); + if (swap) { eglSwapBuffers(dpy, surface); + } else { + const QRect damageRect = lastDamage().boundingRect(); + eglPostSubBufferNV(dpy, surface, damageRect.left(), displayHeight() - damageRect.bottom() - 1, damageRect.width(), damageRect.height()); } eglWaitGL(); diff --git a/glxbackend.cpp b/glxbackend.cpp index be1149780c..17d01e7592 100644 --- a/glxbackend.cpp +++ b/glxbackend.cpp @@ -115,7 +115,7 @@ void GlxBackend::init() // However mesa/dri will return a range error (6) because deactivating the // swapinterval (as of today) seems completely unsupported setHasWaitSync(true); - setSwapInterval(0); + setSwapInterval(1); } else qWarning() << "NO VSYNC! glXWaitVideoSync(1,0,&uint) isn't 0 but" << glXWaitVideoSync(1, 0, &sync); @@ -473,77 +473,80 @@ void GlxBackend::waitSync() void GlxBackend::present() { + QRegion displayRegion(0, 0, displayWidth(), displayHeight()); + const bool fullRepaint = (lastDamage() == displayRegion); + if (isDoubleBuffer()) { - if (lastMask() & Scene::PAINT_SCREEN_REGION) { - waitSync(); - if (glXCopySubBuffer) { - foreach (const QRect & r, lastDamage().rects()) { - // convert to OpenGL coordinates - int y = displayHeight() - r.y() - r.height(); - glXCopySubBuffer(display(), glxbuffer, r.x(), y, r.width(), r.height()); - } - } else { - // if a shader is bound or the texture unit is enabled, copy pixels results in a black screen - // therefore unbind the shader and restore after copying the pixels - GLint shader = 0; - if (ShaderManager::instance()->isShaderBound()) { - glGetIntegerv(GL_CURRENT_PROGRAM, &shader); - glUseProgram(0); - } - bool reenableTexUnit = false; - if (glIsEnabled(GL_TEXTURE_2D)) { - glDisable(GL_TEXTURE_2D); - reenableTexUnit = true; - } - // no idea why glScissor() is used, but Compiz has it and it doesn't seem to hurt - glEnable(GL_SCISSOR_TEST); - glDrawBuffer(GL_FRONT); - int xpos = 0; - int ypos = 0; - foreach (const QRect & r, lastDamage().rects()) { - // convert to OpenGL coordinates - int y = displayHeight() - r.y() - r.height(); - // Move raster position relatively using glBitmap() rather - // than using glRasterPos2f() - the latter causes drawing - // artefacts at the bottom screen edge with some gfx cards -// glRasterPos2f( r.x(), r.y() + r.height()); - glBitmap(0, 0, 0, 0, r.x() - xpos, y - ypos, NULL); - xpos = r.x(); - ypos = y; - glScissor(r.x(), y, r.width(), r.height()); - glCopyPixels(r.x(), y, r.width(), r.height(), GL_COLOR); - } - glBitmap(0, 0, 0, 0, -xpos, -ypos, NULL); // move position back to 0,0 - glDrawBuffer(GL_BACK); - glDisable(GL_SCISSOR_TEST); - if (reenableTexUnit) { - glEnable(GL_TEXTURE_2D); - } - // rebind previously bound shader - if (ShaderManager::instance()->isShaderBound()) { - glUseProgram(shader); - } - } - } else { + + if (fullRepaint) { if (haveSwapInterval) { - setSwapInterval(options->isGlVSync() ? 1 : 0); glXSwapBuffers(display(), glxbuffer); - setSwapInterval(0); - startRenderTimer(); // this is important so we don't assume to be loosing frames in the compositor timing calculation + startRenderTimer(); } else { - waitSync(); + waitSync(); // calls startRenderTimer(); glXSwapBuffers(display(), glxbuffer); } + } else if (glXCopySubBuffer) { + waitSync(); + foreach (const QRect & r, lastDamage().rects()) { + // convert to OpenGL coordinates + int y = displayHeight() - r.y() - r.height(); + glXCopySubBuffer(display(), glxbuffer, r.x(), y, r.width(), r.height()); + } + } else { // Copy Pixels + // if a shader is bound or the texture unit is enabled, copy pixels results in a black screen + // therefore unbind the shader and restore after copying the pixels + GLint shader = 0; + if (ShaderManager::instance()->isShaderBound()) { + glGetIntegerv(GL_CURRENT_PROGRAM, &shader); + glUseProgram(0); + } + bool reenableTexUnit = false; + if (glIsEnabled(GL_TEXTURE_2D)) { + glDisable(GL_TEXTURE_2D); + reenableTexUnit = true; + } + // no idea why glScissor() is used, but Compiz has it and it doesn't seem to hurt + glEnable(GL_SCISSOR_TEST); + glDrawBuffer(GL_FRONT); + waitSync(); + int xpos = 0; + int ypos = 0; + foreach (const QRect & r, lastDamage().rects()) { + // convert to OpenGL coordinates + int y = displayHeight() - r.y() - r.height(); + // Move raster position relatively using glBitmap() rather + // than using glRasterPos2f() - the latter causes drawing + // artefacts at the bottom screen edge with some gfx cards +// glRasterPos2f( r.x(), r.y() + r.height()); + glBitmap(0, 0, 0, 0, r.x() - xpos, y - ypos, NULL); + xpos = r.x(); + ypos = y; + glScissor(r.x(), y, r.width(), r.height()); + glCopyPixels(r.x(), y, r.width(), r.height(), GL_COLOR); + } + glBitmap(0, 0, 0, 0, -xpos, -ypos, NULL); // move position back to 0,0 + glDrawBuffer(GL_BACK); + glDisable(GL_SCISSOR_TEST); + if (reenableTexUnit) { + glEnable(GL_TEXTURE_2D); + } + // rebind previously bound shader + if (ShaderManager::instance()->isShaderBound()) { + glUseProgram(shader); + } } + glXWaitGL(); } else { glXWaitGL(); - if (lastMask() & Scene::PAINT_SCREEN_REGION) + if (!fullRepaint) foreach (const QRect & r, lastDamage().rects()) XCopyArea(display(), buffer, rootWindow(), gcroot, r.x(), r.y(), r.width(), r.height(), r.x(), r.y()); else XCopyArea(display(), buffer, rootWindow(), gcroot, 0, 0, displayWidth(), displayHeight(), 0, 0); } + setLastDamage(QRegion()); XFlush(display()); } diff --git a/options.cpp b/options.cpp index c0150a23b3..46a939667e 100644 --- a/options.cpp +++ b/options.cpp @@ -161,6 +161,7 @@ Options::Options(QObject *parent) , m_glStrictBinding(Options::defaultGlStrictBinding()) , m_glStrictBindingFollowsDriver(Options::defaultGlStrictBindingFollowsDriver()) , m_glLegacy(Options::defaultGlLegacy()) + , m_glPreferBufferSwap(Options::defaultGlPreferBufferSwap()) , OpTitlebarDblClick(Options::defaultOperationTitlebarDblClick()) , CmdActiveTitlebar1(Options::defaultCommandActiveTitlebar1()) , CmdActiveTitlebar2(Options::defaultCommandActiveTitlebar2()) @@ -768,6 +769,24 @@ void Options::setGlLegacy(bool glLegacy) emit glLegacyChanged(); } +void Options::setGlPreferBufferSwap(char glPreferBufferSwap) +{ + if (glPreferBufferSwap == 'a') { + // buffer cpying is very fast with the nvidia blob + // but due to restrictions in DRI2 *incredibly* slow for all MESA drivers + // see http://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt, item 2.5 + if (GLPlatform::instance()->driver() == Driver_NVidia) + glPreferBufferSwap = CopyFrontBuffer; + else + glPreferBufferSwap = ExtendDamage; + } + if (m_glPreferBufferSwap == (GlSwapStrategy)glPreferBufferSwap) { + return; + } + m_glPreferBufferSwap = (GlSwapStrategy)glPreferBufferSwap; + emit glPreferBufferSwapChanged(); +} + void Options::reparseConfiguration() { KGlobal::config()->reparseConfiguration(); @@ -957,6 +976,16 @@ void Options::reloadCompositingSettings(bool force) } setGlLegacy(config.readEntry("GLLegacy", Options::defaultGlLegacy())); + char c = 0; + if (isGlVSync()) { // buffer swap enforcement makes little sense without + const QString s = config.readEntry("GLPreferBufferSwap", QString(Options::defaultGlPreferBufferSwap())); + if (!s.isEmpty()) + c = s.at(0).toAscii(); + if (c != 'a' && c != 'c' && c != 'p' && c != 'e') + c = 0; + } + setGlPreferBufferSwap(c); + setColorCorrected(config.readEntry("GLColorCorrection", Options::defaultColorCorrected())); m_xrenderSmoothScale = config.readEntry("XRenderSmoothScale", false); diff --git a/options.h b/options.h index c76dbae8c7..e816d4aca1 100644 --- a/options.h +++ b/options.h @@ -42,6 +42,7 @@ class Options : public QObject, public KDecorationOptions { Q_OBJECT Q_ENUMS(FocusPolicy) + Q_ENUMS(GlSwapStrategy) Q_ENUMS(MouseCommand) Q_ENUMS(MouseWheelCommand) @@ -187,6 +188,7 @@ class Options : public QObject, public KDecorationOptions * Whether legacy OpenGL should be used or OpenGL (ES) 2 **/ Q_PROPERTY(bool glLegacy READ isGlLegacy WRITE setGlLegacy NOTIFY glLegacyChanged) + Q_PROPERTY(char glPreferBufferSwap READ glPreferBufferSwap WRITE setGlPreferBufferSwap NOTIFY glPreferBufferSwapChanged) public: explicit Options(QObject *parent = NULL); @@ -549,6 +551,11 @@ public: return m_glLegacy; } + enum GlSwapStrategy { NoSwapEncourage = 0, CopyFrontBuffer = 'c', PaintFullScreen = 'p', ExtendDamage = 'e', AutoSwapStrategy = 'a' }; + GlSwapStrategy glPreferBufferSwap() const { + return m_glPreferBufferSwap; + } + // setters void setFocusPolicy(FocusPolicy focusPolicy); void setNextFocusPrefersMouse(bool nextFocusPrefersMouse); @@ -610,6 +617,7 @@ public: void setGlStrictBinding(bool glStrictBinding); void setGlStrictBindingFollowsDriver(bool glStrictBindingFollowsDriver); void setGlLegacy(bool glLegacy); + void setGlPreferBufferSwap(char glPreferBufferSwap); // default values static WindowOperation defaultOperationTitlebarDblClick() { @@ -717,6 +725,9 @@ public: static bool defaultGlLegacy() { return false; } + static GlSwapStrategy defaultGlPreferBufferSwap() { + return AutoSwapStrategy; + } static int defaultAnimationSpeed() { return 3; } @@ -797,6 +808,7 @@ Q_SIGNALS: void glStrictBindingChanged(); void glStrictBindingFollowsDriverChanged(); void glLegacyChanged(); + void glPreferBufferSwapChanged(); public Q_SLOTS: void setColorCorrected(bool colorCorrected = false); @@ -847,6 +859,7 @@ private: bool m_glStrictBinding; bool m_glStrictBindingFollowsDriver; bool m_glLegacy; + GlSwapStrategy m_glPreferBufferSwap; WindowOperation OpTitlebarDblClick; diff --git a/scene.cpp b/scene.cpp index 685254b4c3..939f000f0c 100644 --- a/scene.cpp +++ b/scene.cpp @@ -103,8 +103,9 @@ Scene::~Scene() // returns mask and possibly modified region void Scene::paintScreen(int* mask, QRegion* region) { - *mask = (*region == QRegion(0, 0, displayWidth(), displayHeight())) - ? 0 : PAINT_SCREEN_REGION; + const QRegion displayRegion(0, 0, displayWidth(), displayHeight()); + *mask = (*region == displayRegion) ? 0 : PAINT_SCREEN_REGION; + updateTimeDiff(); // preparation step static_cast(effects)->startPaint(); @@ -124,10 +125,10 @@ void Scene::paintScreen(int* mask, QRegion* region) *region = infiniteRegion(); } else if (*mask & PAINT_SCREEN_REGION) { // make sure not to go outside visible screen - *region &= QRegion(0, 0, displayWidth(), displayHeight()); + *region &= displayRegion; } else { // whole screen, not transformed, force region to be full - *region = QRegion(0, 0, displayWidth(), displayHeight()); + *region = displayRegion; } painted_region = *region; if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) { @@ -141,7 +142,7 @@ void Scene::paintScreen(int* mask, QRegion* region) effects->postPaintScreen(); *region |= painted_region; // make sure not to go outside of the screen area - *region &= QRegion(0, 0, displayWidth(), displayHeight()); + *region &= displayRegion; // make sure all clipping is restored Q_ASSERT(!PaintClipper::clip()); } @@ -236,6 +237,7 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region) QList< QPair< Window*, Phase2Data > > phase2data; QRegion dirtyArea = region; + bool opaqueFullscreen(false); for (int i = 0; // do prePaintWindow bottom to top i < stacking_order.count(); ++i) { @@ -254,10 +256,12 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region) topw->resetRepaints(); // Clip out the decoration for opaque windows; the decoration is drawn in the second pass + opaqueFullscreen = false; // TODO: do we care about unmanged windows here (maybe input windows?) if (w->isOpaque()) { Client *c = NULL; if (topw->isClient()) { c = static_cast(topw); + opaqueFullscreen = c->isFullScreen(); } // the window is fully opaque if (c && c->decorationHasAlpha()) { @@ -297,13 +301,24 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region) w->suspendUnredirect(data.mask & PAINT_WINDOW_TRANSLUCENT); } - // This is the occlusion culling pass + const QRegion displayRegion(0, 0, displayWidth(), displayHeight()); + + bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations + if (!fullRepaint) { + extendPaintRegion(dirtyArea, opaqueFullscreen); + fullRepaint = (dirtyArea == displayRegion); + } + QRegion allclips, upperTranslucentDamage; + // This is the occlusion culling pass for (int i = phase2data.count() - 1; i >= 0; --i) { QPair< Window*, Phase2Data > *entry = &phase2data[i]; Phase2Data *data = &entry->second; - data->region |= upperTranslucentDamage; + if (fullRepaint) + data->region = displayRegion; + else + data->region |= upperTranslucentDamage; // subtract the parts which will possibly been drawn as part of // a higher opaque window @@ -315,8 +330,9 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region) // clip away the opaque regions for all windows below this one allclips |= data->clip; // extend the translucent damage for windows below this by remaining (translucent) regions - upperTranslucentDamage |= data->region - data->clip; - } else { + if (!fullRepaint) + upperTranslucentDamage |= data->region - data->clip; + } else if (!fullRepaint) { upperTranslucentDamage |= data->region; } } @@ -338,7 +354,10 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region) paintWindow(data->window, data->mask, data->region, data->quads); } - painted_region |= paintedArea; + if (fullRepaint) + painted_region = displayRegion; + else + painted_region |= paintedArea; } void Scene::paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads) @@ -452,6 +471,12 @@ void Scene::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, Windo w->sceneWindow()->performPaint(mask, region, data); } +void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) +{ + Q_UNUSED(region); + Q_UNUSED(opaqueFullscreen); +} + bool Scene::waitSyncAvailable() const { return false; diff --git a/scene.h b/scene.h index f06d150a63..5d49c40abd 100644 --- a/scene.h +++ b/scene.h @@ -125,6 +125,9 @@ protected: virtual void paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads); // called after all effects had their drawWindow() called virtual void finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); + // let the scene decide whether it's better to paint more of the screen, eg. in order to allow a buffer swap + // the default is NOOP + virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); // compute time since the last repaint void updateTimeDiff(); // saved data for 2nd pass of optimized screen painting diff --git a/scene_opengl.cpp b/scene_opengl.cpp index 3185c9eca7..a9c5d84683 100644 --- a/scene_opengl.cpp +++ b/scene_opengl.cpp @@ -98,7 +98,8 @@ void OpenGLBackend::setFailed(const QString &reason) void OpenGLBackend::idle() { - present(); + if (hasPendingFlush()) + present(); } /************************************************ @@ -269,7 +270,52 @@ int SceneOpenGL::paint(QRegion damage, ToplevelList toplevels) #ifdef CHECK_GL_ERROR checkGLError("Paint1"); #endif + + const QRegion displayRegion(0, 0, displayWidth(), displayHeight()); paintScreen(&mask, &damage); // call generic implementation +#ifndef KWIN_HAVE_OPENGLES + // copy dirty parts from front to backbuffer + if (options->glPreferBufferSwap() == Options::CopyFrontBuffer && damage != displayRegion) { + GLint shader = 0; + if (ShaderManager::instance()->isShaderBound()) { + glGetIntegerv(GL_CURRENT_PROGRAM, &shader); + glUseProgram(0); + } + bool reenableTexUnit = false; + if (glIsEnabled(GL_TEXTURE_2D)) { + glDisable(GL_TEXTURE_2D); + reenableTexUnit = true; + } + // no idea why glScissor() is used, but Compiz has it and it doesn't seem to hurt + glEnable(GL_SCISSOR_TEST); + glReadBuffer(GL_FRONT); + + int xpos = 0; + int ypos = 0; + const QRegion dirty = displayRegion - damage; + foreach (const QRect &r, dirty.rects()) { + // convert to OpenGL coordinates + int y = displayHeight() - r.y() - r.height(); + glBitmap(0, 0, 0, 0, r.x() - xpos, y - ypos, NULL); // not glRasterPos2f, see glxbackend.cpp + xpos = r.x(); + ypos = y; + glScissor(r.x(), y, r.width(), r.height()); + glCopyPixels(r.x(), y, r.width(), r.height(), GL_COLOR); + } + + glBitmap(0, 0, 0, 0, -xpos, -ypos, NULL); // move position back to 0,0 + glReadBuffer(GL_BACK); + glDisable(GL_SCISSOR_TEST); + if (reenableTexUnit) { + glEnable(GL_TEXTURE_2D); + } + // rebind previously bound shader + if (ShaderManager::instance()->isShaderBound()) { + glUseProgram(shader); + } + damage = displayRegion; + } +#endif #ifdef CHECK_GL_ERROR checkGLError("Paint2"); #endif @@ -328,6 +374,35 @@ void SceneOpenGL::paintBackground(QRegion region) doPaintBackground(verts); } +void SceneOpenGL::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) +{ +#ifndef KWIN_HAVE_OPENGLES + if (options->glPreferBufferSwap() == Options::ExtendDamage) { // only Extend "large" repaints + const QRegion displayRegion(0, 0, displayWidth(), displayHeight()); + uint damagedPixels = 0; + const uint fullRepaintLimit = (opaqueFullscreen?0.49f:0.748f)*displayWidth()*displayHeight(); + // 16:9 is 75% of 4:3 and 2.55:1 is 49.01% of 5:4 + // (5:4 is the most square format and 2.55:1 is Cinemascope55 - the widest ever shot + // movie aspect - two times ;-) It's a Fox format, though, so maybe we want to restrict + // to 2.20:1 - Panavision - which has actually been used for interesting movies ...) + // would be 57% of 5/4 + foreach (const QRect &r, region.rects()) { +// damagedPixels += r.width() * r.height(); // combined window damage test + damagedPixels = r.width() * r.height(); // experimental single window damage testing + if (damagedPixels > fullRepaintLimit) { + region = displayRegion; + return; + } + } + } else if (options->glPreferBufferSwap() == Options::PaintFullScreen) { // forced full rePaint + region = QRegion(0, 0, displayWidth(), displayHeight()); + } +#else + Q_UNUSED(region); + Q_UNUSED(opaqueFullscreen); +#endif +} + void SceneOpenGL::windowAdded(Toplevel* c) { assert(!windows.contains(c)); diff --git a/scene_opengl.h b/scene_opengl.h index 7971c8314f..cd3dd86952 100644 --- a/scene_opengl.h +++ b/scene_opengl.h @@ -68,6 +68,7 @@ public: protected: SceneOpenGL(Workspace* ws, OpenGLBackend *backend); virtual void paintBackground(QRegion region); + virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); QMatrix4x4 transformation(int mask, const ScreenPaintData &data) const; virtual void doPaintBackground(const QVector &vertices) = 0;