From 941c02a60f3a6dd21c522df06811f17d79a7ec8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Mon, 13 May 2013 08:17:28 +0200 Subject: [PATCH] Introduce cross-fading with previous pixmap Cross fading with previous pixmap is achieved by referencing the old window pixmap. WindowPaintData has a cross-fade-factor which interpolates between 0.0 (completely old pixmap) to 1.0 (completely new pixmap). If a cross fading factor is set and a previous pixmap is valid this one is rendered on top of the current pixmap with opacity adjusted. This results in a smoother fading. To simplify the setup the AnimationEffect is extended and also takes care about correctly (un)referencing the previous window pixmap. The maximize effect is adjusted to make use of this new capabilities. Unfortunately this setup has a huge problem with the case that the window decoration gets smaller (e.g. from normal to maximized state). In this situation it can happen that the old window is rendered with parts outside the content resulting in video garbage being shown. To prevent this a set of new WindowQuads is generated with normalized texture coordinates in the safe area which contains real content. For OpenGL2Window a PreviousContentLeaf is added which is only set up in case the crass fading factor is set. REVIEW: 110578 --- effects.cpp | 14 +++ effects.h | 3 + .../package/contents/code/maximize.js | 4 + libkwineffects/kwinanimationeffect.cpp | 17 ++- libkwineffects/kwinanimationeffect.h | 2 +- libkwineffects/kwineffects.cpp | 13 +++ libkwineffects/kwineffects.h | 44 ++++++++ scene.cpp | 1 + scene.h | 31 ++++++ scene_opengl.cpp | 102 ++++++++++++++---- scene_opengl.h | 5 +- tests/test_window_paint_data.cpp | 2 + 12 files changed, 216 insertions(+), 22 deletions(-) diff --git a/effects.cpp b/effects.cpp index 6d6510da10..1cd7a0354c 100644 --- a/effects.cpp +++ b/effects.cpp @@ -1807,6 +1807,20 @@ void EffectWindowImpl::desktopThumbnailDestroyed(QObject *object) m_desktopThumbnails.removeAll(static_cast(object)); } +void EffectWindowImpl::referencePreviousWindowPixmap() +{ + if (sw) { + sw->referencePreviousPixmap(); + } +} + +void EffectWindowImpl::unreferencePreviousWindowPixmap() +{ + if (sw) { + sw->unreferencePreviousPixmap(); + } +} + //**************************************** // EffectWindowGroupImpl //**************************************** diff --git a/effects.h b/effects.h index 4115433ee5..1cb4bc230d 100644 --- a/effects.h +++ b/effects.h @@ -295,6 +295,9 @@ public: virtual WindowQuadList buildQuads(bool force = false) const; + virtual void referencePreviousWindowPixmap(); + virtual void unreferencePreviousWindowPixmap(); + const Toplevel* window() const; Toplevel* window(); diff --git a/effects/maximize/package/contents/code/maximize.js b/effects/maximize/package/contents/code/maximize.js index 5013c84a32..f85fae8334 100644 --- a/effects/maximize/package/contents/code/maximize.js +++ b/effects/maximize/package/contents/code/maximize.js @@ -54,6 +54,10 @@ var maximizeEffect = { value1: oldGeometry.x - newGeometry.x - (newGeometry.width / 2 - oldGeometry.width / 2), value2: oldGeometry.y - newGeometry.y - (newGeometry.height / 2 - oldGeometry.height / 2) } + }, { + type: Effect.CrossFadePrevious, + to: 1.0, + from: 0.0 }] }); }, diff --git a/libkwineffects/kwinanimationeffect.cpp b/libkwineffects/kwinanimationeffect.cpp index 17973870ef..8ae606d320 100644 --- a/libkwineffects/kwinanimationeffect.cpp +++ b/libkwineffects/kwinanimationeffect.cpp @@ -177,6 +177,14 @@ quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int to.set(1.0,1.0); setMetaData( TargetAnchor, metaData(SourceAnchor, meta), meta ); } + } else if (a == CrossFadePrevious) { + if (!from.isValid()) { + from.set(0.0); + } + if (!to.isValid()) { + to.set(1.0); + } + w->referencePreviousWindowPixmap(); } Q_D(AnimationEffect); @@ -268,6 +276,9 @@ void AnimationEffect::prePaintScreen( ScreenPrePaintData& data, int time ) } else { EffectWindow *oldW = entry.key(); AniData *aData = &(*anim); + if (aData->attribute == KWin::AnimationEffect::CrossFadePrevious) { + oldW->unreferencePreviousWindowPixmap(); + } animationEnded(oldW, anim->attribute, anim->meta); // NOTICE animationEnded is an external call and might have called "::animate" // as a result our iterators could now point random junk on the heap @@ -423,7 +434,7 @@ void AnimationEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, continue; isUsed = true; - if (anim->attribute == Opacity) + if (anim->attribute == Opacity || anim->attribute == CrossFadePrevious) data.setTranslucent(); else if (!(anim->attribute == Brightness || anim->attribute == Saturation)) { data.setTransformed(); @@ -554,6 +565,9 @@ void AnimationEffect::paintWindow( EffectWindow* w, int mask, QRegion region, Wi case Generic: genericAnimation(w, data, progress(*anim), anim->meta); break; + case CrossFadePrevious: + data.setCrossFadeProgress(progress(*anim)); + break; default: break; } @@ -725,6 +739,7 @@ void AnimationEffect::updateLayerRepaints() case Opacity: case Brightness: case Saturation: + case CrossFadePrevious: createRegion = true; break; case Rotation: diff --git a/libkwineffects/kwinanimationeffect.h b/libkwineffects/kwinanimationeffect.h index 08c34934d3..528190fc5d 100644 --- a/libkwineffects/kwinanimationeffect.h +++ b/libkwineffects/kwinanimationeffect.h @@ -100,7 +100,7 @@ public: Horizontal = Left|Right, Vertical = Top|Bottom, Mouse = 1<<4 }; enum Attribute { Opacity = 0, Brightness, Saturation, Scale, Rotation, - Position, Size, Translation, Clip, Generic, + Position, Size, Translation, Clip, Generic, CrossFadePrevious, NonFloatBase = Position }; enum MetaType { SourceAnchor, TargetAnchor, diff --git a/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp index b0634b12c3..d29c6343d8 100644 --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -232,6 +232,7 @@ public: qreal saturation; qreal brightness; int screen; + qreal crossFadeProgress; }; WindowPaintData::WindowPaintData(EffectWindow* w) @@ -245,6 +246,7 @@ WindowPaintData::WindowPaintData(EffectWindow* w) setSaturation(1.0); setBrightness(1.0); setScreen(0); + setCrossFadeProgress(1.0); } WindowPaintData::WindowPaintData(const WindowPaintData &other) @@ -265,6 +267,7 @@ WindowPaintData::WindowPaintData(const WindowPaintData &other) setSaturation(other.saturation()); setBrightness(other.brightness()); setScreen(other.screen()); + setCrossFadeProgress(other.crossFadeProgress()); } WindowPaintData::~WindowPaintData() @@ -322,6 +325,16 @@ void WindowPaintData::setScreen(int screen) const d->screen = screen; } +qreal WindowPaintData::crossFadeProgress() const +{ + return d->crossFadeProgress; +} + +void WindowPaintData::setCrossFadeProgress(qreal factor) +{ + d->crossFadeProgress = qBound(0.0, factor, 1.0); +} + qreal WindowPaintData::multiplyDecorationOpacity(qreal factor) { d->decorationOpacity *= factor; diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h index ec74651a45..4f735ea01b 100644 --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -1652,6 +1652,35 @@ public: */ Q_SCRIPTABLE virtual void setData(int role, const QVariant &data) = 0; Q_SCRIPTABLE virtual QVariant data(int role) const = 0; + + /** + * @brief References the previous window pixmap to prevent discarding. + * + * This method allows to reference the previous window pixmap in case that a window changed + * its size, which requires a new window pixmap. By referencing the previous (and then outdated) + * window pixmap an effect can for example cross fade the current window pixmap with the previous + * one. This allows for smoother transitions for window geometry changes. + * + * If an effect calls this method on a window it also needs to call @link unreferencePreviousWindowPixmap + * once it does no longer need the previous window pixmap. + * + * Note: the window pixmap is not kept forever even when referenced. If the geometry changes again, so that + * a new window pixmap is created, the previous window pixmap will be exchanged with the current one. This + * means it's still possible to have rendering glitches. An effect is supposed to track for itself the changes + * to the window's geometry and decide how the transition should continue in such a situation. + * + * @see unreferencePreviousWindowPixmap + * @since 4.11 + */ + virtual void referencePreviousWindowPixmap() = 0; + /** + * @brief Unreferences the previous window pixmap. Only relevant after @link referencePreviousWindowPixmap had + * been called. + * + * @see referencePreviousWindowPixmap + * @since 4.11 + */ + virtual void unreferencePreviousWindowPixmap() = 0; }; class KWIN_EXPORT EffectWindowGroup @@ -2086,6 +2115,21 @@ public: * A value less than 0 will indicate that a default profile should be done. */ void setScreen(int screen) const; + /** + * @brief Sets the cross fading @p factor to fade over with previously sized window. + * If @c 1.0 only the current window is used, if @c 0.0 only the previous window is used. + * + * By default only the current window is used. This factor can only make any visual difference + * if the previous window get referenced. + * + * @param factor The cross fade factor between @c 0.0 (previous window) and @c 1.0 (current window) + * @see crossFadeProgress + */ + void setCrossFadeProgress(qreal factor); + /** + * @see setCrossFadeProgress + */ + qreal crossFadeProgress() const; WindowQuadList quads; /** * Shader to be used for rendering, if any. diff --git a/scene.cpp b/scene.cpp index 45564eb686..2231bcff7c 100644 --- a/scene.cpp +++ b/scene.cpp @@ -873,6 +873,7 @@ void WindowPixmap::create() } m_pixmap = pix; m_pixmapSize = QSize(toplevel()->width(), toplevel()->height()); + m_contentsRect = QRect(toplevel()->clientPos(), toplevel()->clientSize()); m_window->unreferencePreviousPixmap(); } diff --git a/scene.h b/scene.h index a6685ce489..462a604d9e 100644 --- a/scene.h +++ b/scene.h @@ -253,6 +253,7 @@ protected: * @return The WindowPixmap casted to T* or @c NULL if there is no valid window pixmap. */ template T *windowPixmap(); + template T *previousWindowPixmap(); /** * @brief Factory method to create a WindowPixmap. * @@ -327,6 +328,16 @@ public: * @see isDiscarded */ void markAsDiscarded(); + /** + * The size of the pixmap. + */ + const QSize &size() const; + /** + * The geometry of the Client's content inside the pixmap. In case of a decorated Client the + * pixmap also contains the decoration which is not rendered into this pixmap, though. This + * contentsRect tells where inside the complete pixmap the real content is. + */ + const QRect &contentsRect() const; protected: explicit WindowPixmap(Scene::Window *window); @@ -344,6 +355,7 @@ private: xcb_pixmap_t m_pixmap; QSize m_pixmapSize; bool m_discarded; + QRect m_contentsRect; }; class Scene::EffectFrame @@ -471,6 +483,13 @@ T* Scene::Window::windowPixmap() } } +template +inline +T* Scene::Window::previousWindowPixmap() +{ + return static_cast(m_previousPixmap.data()); +} + inline Toplevel* WindowPixmap::toplevel() { @@ -496,6 +515,18 @@ void WindowPixmap::markAsDiscarded() m_window->referencePreviousPixmap(); } +inline +const QRect &WindowPixmap::contentsRect() const +{ + return m_contentsRect; +} + +inline +const QSize &WindowPixmap::size() const +{ + return m_pixmapSize; +} + } // namespace #endif diff --git a/scene_opengl.cpp b/scene_opengl.cpp index 71908310e6..4201136d87 100644 --- a/scene_opengl.cpp +++ b/scene_opengl.cpp @@ -1343,6 +1343,14 @@ void SceneOpenGL2Window::setupLeafNodes(LeafNode *nodes, const WindowQuadList *q nodes[ContentLeaf].hasAlpha = !isOpaque(); nodes[ContentLeaf].opacity = data.opacity(); nodes[ContentLeaf].coordinateType = UnnormalizedCoordinates; + + if (data.crossFadeProgress() != 1.0) { + OpenGLWindowPixmap *previous = previousWindowPixmap(); + nodes[PreviousContentLeaf].texture = previous ? previous->texture() : NULL; + nodes[PreviousContentLeaf].hasAlpha = !isOpaque(); + nodes[PreviousContentLeaf].opacity = 1.0 - data.crossFadeProgress(); + nodes[PreviousContentLeaf].coordinateType = NormalizedCoordinates; + } } void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData data) @@ -1368,7 +1376,7 @@ void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData const GLenum filter = (mask & (Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_SCREEN_TRANSFORMED)) && options->glSmoothScale() != 0 ? GL_LINEAR : GL_NEAREST; - WindowQuadList quads[4]; + WindowQuadList quads[LeafCount]; // Split the quads into separate lists for each type foreach (const WindowQuad &quad, data.quads) { @@ -1401,20 +1409,44 @@ void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData } } + if (data.crossFadeProgress() != 1.0) { + OpenGLWindowPixmap *previous = previousWindowPixmap(); + if (previous) { + const QRect &oldGeometry = previous->contentsRect(); + Q_FOREACH (const WindowQuad &quad, quads[ContentLeaf]) { + // we need to create new window quads with normalize texture coordinates + // normal quads divide the x/y position by width/height. This would not work as the texture + // is larger than the visible content in case of a decorated Client resulting in garbage being shown. + // So we calculate the normalized texture coordinate in the Client's new content space and map it to + // the previous Client's content space. + WindowQuad newQuad(WindowQuadContents); + for (int i = 0; i < 4; ++i) { + const qreal xFactor = qreal(quad[i].textureX() - toplevel->clientPos().x())/qreal(toplevel->clientSize().width()); + const qreal yFactor = qreal(quad[i].textureY() - toplevel->clientPos().y())/qreal(toplevel->clientSize().height()); + WindowVertex vertex(quad[i].x(), quad[i].y(), + (xFactor * oldGeometry.width() + oldGeometry.x())/qreal(previous->size().width()), + (yFactor * oldGeometry.height() + oldGeometry.y())/qreal(previous->size().height())); + newQuad[i] = vertex; + } + quads[PreviousContentLeaf].append(newQuad); + } + } + } + const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads(); const GLenum primitiveType = indexedQuads ? GL_QUADS_KWIN : GL_TRIANGLES; const int verticesPerQuad = indexedQuads ? 4 : 6; const size_t size = verticesPerQuad * - (quads[0].count() + quads[1].count() + quads[2].count() + quads[3].count()) * sizeof(GLVertex2D); + (quads[0].count() + quads[1].count() + quads[2].count() + quads[3].count() + quads[4].count()) * sizeof(GLVertex2D); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); GLVertex2D *map = (GLVertex2D *) vbo->map(size); - LeafNode nodes[4]; + LeafNode nodes[LeafCount]; setupLeafNodes(nodes, quads, data); - for (int i = 0, v = 0; i < 4; i++) { + for (int i = 0, v = 0; i < LeafCount; i++) { if (quads[i].isEmpty() || !nodes[i].texture) continue; @@ -1435,7 +1467,7 @@ void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData float opacity = -1.0; - for (int i = 0; i < 4; i++) { + for (int i = 0; i < LeafCount; i++) { if (nodes[i].vertexCount == 0) continue; @@ -1544,21 +1576,33 @@ void SceneOpenGL1Window::performPaint(int mask, QRegion region, WindowPaintData paintDecorations(data, region); // paint the content - WindowQuadList contentQuads = data.quads.select(WindowQuadContents); - if (!contentQuads.empty()) { - s_frameTexture->bind(); - prepareStates(Content, data.opacity(), data.brightness(), data.saturation(), data.screen()); - renderQuads(mask, region, contentQuads, s_frameTexture, false); - restoreStates(Content, data.opacity(), data.brightness(), data.saturation()); - s_frameTexture->unbind(); - -#ifndef KWIN_HAVE_OPENGLES - if (m_scene && m_scene->debug()) { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - renderQuads(mask, region, contentQuads, s_frameTexture, false); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + OpenGLWindowPixmap *previous = previousWindowPixmap(); + const WindowQuadList contentQuads = data.quads.select(WindowQuadContents); + if (previous && data.crossFadeProgress() != 1.0) { + paintContent(s_frameTexture, region, mask, data.opacity(), data, contentQuads, false); + previous->texture()->setFilter(filter == Scene::ImageFilterGood ? GL_LINEAR : GL_NEAREST); + WindowQuadList oldContents; + const QRect &oldGeometry = previous->contentsRect(); + Q_FOREACH (const WindowQuad &quad, contentQuads) { + // we need to create new window quads with normalize texture coordinates + // normal quads divide the x/y position by width/height. This would not work as the texture + // is larger than the visible content in case of a decorated Client resulting in garbage being shown. + // So we calculate the normalized texture coordinate in the Client's new content space and map it to + // the previous Client's content space. + WindowQuad newQuad(WindowQuadContents); + for (int i = 0; i < 4; ++i) { + const qreal xFactor = qreal(quad[i].textureX() - toplevel->clientPos().x())/qreal(toplevel->clientSize().width()); + const qreal yFactor = qreal(quad[i].textureY() - toplevel->clientPos().y())/qreal(toplevel->clientSize().height()); + WindowVertex vertex(quad[i].x(), quad[i].y(), + (xFactor * oldGeometry.width() + oldGeometry.x())/qreal(previous->size().width()), + (yFactor * oldGeometry.height() + oldGeometry.y())/qreal(previous->size().height())); + newQuad[i] = vertex; + } + oldContents.append(newQuad); } -#endif + paintContent(previous->texture(), region, mask, 1.0 - data.crossFadeProgress(), data, oldContents, true); + } else { + paintContent(s_frameTexture, region, mask, data.opacity(), data, contentQuads, false); } popMatrix(); @@ -1566,6 +1610,26 @@ void SceneOpenGL1Window::performPaint(int mask, QRegion region, WindowPaintData endRenderWindow(); } +void SceneOpenGL1Window::paintContent(SceneOpenGL::Texture* content, const QRegion& region, int mask, + qreal opacity, const WindowPaintData& data, const WindowQuadList &contentQuads, bool normalized) +{ + if (contentQuads.isEmpty()) { + return; + } + content->bind(); + prepareStates(Content, opacity, data.brightness(), data.saturation(), data.screen()); + renderQuads(mask, region, contentQuads, content, normalized); + restoreStates(Content, opacity, data.brightness(), data.saturation()); + content->unbind(); +#ifndef KWIN_HAVE_OPENGLES + if (m_scene && m_scene->debug()) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + renderQuads(mask, region, contentQuads, content, normalized); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } +#endif +} + void SceneOpenGL1Window::prepareStates(TextureType type, qreal opacity, qreal brightness, qreal saturation, int screen) { Q_UNUSED(screen) diff --git a/scene_opengl.h b/scene_opengl.h index 55a684076e..043ef114ba 100644 --- a/scene_opengl.h +++ b/scene_opengl.h @@ -280,7 +280,7 @@ private: class SceneOpenGL2Window : public SceneOpenGL::Window { public: - enum Leaf { ShadowLeaf = 0, LeftRightLeaf, TopBottomLeaf, ContentLeaf }; + enum Leaf { ShadowLeaf = 0, LeftRightLeaf, TopBottomLeaf, ContentLeaf, PreviousContentLeaf, LeafCount }; struct LeafNode { @@ -331,6 +331,9 @@ protected: virtual void performPaint(int mask, QRegion region, WindowPaintData data); virtual void prepareStates(TextureType type, qreal opacity, qreal brightness, qreal saturation, int screen); virtual void restoreStates(TextureType type, qreal opacity, qreal brightness, qreal saturation); +private: + void paintContent(SceneOpenGL::Texture* content, const QRegion& region, int mask, qreal opacity, + const WindowPaintData& data, const WindowQuadList &contentQuads, bool normalized); }; #endif diff --git a/tests/test_window_paint_data.cpp b/tests/test_window_paint_data.cpp index 419b2405b4..81807e8153 100644 --- a/tests/test_window_paint_data.cpp +++ b/tests/test_window_paint_data.cpp @@ -70,6 +70,8 @@ public: virtual void unrefWindow(); virtual QRegion shape() const; virtual void setData(int role, const QVariant &data); + virtual void referencePreviousWindowPixmap() {} + virtual void unreferencePreviousWindowPixmap() {} }; MockEffectWindow::MockEffectWindow(QObject *parent)