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
This commit is contained in:
Martin Gräßlin 2013-05-13 08:17:28 +02:00
parent e7ab3adafd
commit 941c02a60f
12 changed files with 216 additions and 22 deletions

View file

@ -1807,6 +1807,20 @@ void EffectWindowImpl::desktopThumbnailDestroyed(QObject *object)
m_desktopThumbnails.removeAll(static_cast<DesktopThumbnailItem*>(object));
}
void EffectWindowImpl::referencePreviousWindowPixmap()
{
if (sw) {
sw->referencePreviousPixmap();
}
}
void EffectWindowImpl::unreferencePreviousWindowPixmap()
{
if (sw) {
sw->unreferencePreviousPixmap();
}
}
//****************************************
// EffectWindowGroupImpl
//****************************************

View file

@ -295,6 +295,9 @@ public:
virtual WindowQuadList buildQuads(bool force = false) const;
virtual void referencePreviousWindowPixmap();
virtual void unreferencePreviousWindowPixmap();
const Toplevel* window() const;
Toplevel* window();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

31
scene.h
View file

@ -253,6 +253,7 @@ protected:
* @return The WindowPixmap casted to T* or @c NULL if there is no valid window pixmap.
*/
template<typename T> T *windowPixmap();
template<typename T> 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 <typename T>
inline
T* Scene::Window::previousWindowPixmap()
{
return static_cast<T*>(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

View file

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

View file

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

View file

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