Commit 31d1f885 authored by Marco Martin's avatar Marco Martin
Browse files

Restore the crossfade effect

This enables again the crossfade between the old window picture and the new one in the maximize and morphingpopup effects.
It does that with the OffScreenEffect redirect() feature.

BUG:439689
BUG:435423
parent b7f950a5
Pipeline #228374 passed with stage
in 26 minutes and 16 seconds
......@@ -55,6 +55,7 @@ void MaximizeAnimationTest::initTestCase()
config->sync();
kwinApp()->setConfig(config);
qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2"));
qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1"));
kwinApp()->start();
......
......@@ -293,6 +293,18 @@ void EffectsHandlerImpl::setupWindowConnections(Window *window)
connect(window, &Window::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed);
connect(window, static_cast<void (Window::*)(KWin::Window *, MaximizeMode)>(&Window::clientMaximizedStateChanged),
this, &EffectsHandlerImpl::slotClientMaximized);
connect(window, static_cast<void (Window::*)(KWin::Window *, MaximizeMode)>(&Window::clientMaximizedStateAboutToChange),
this, [this](KWin::Window *window, MaximizeMode m) {
if (EffectWindowImpl *w = window->effectWindow()) {
Q_EMIT windowMaximizedStateAboutToChange(w, m & MaximizeHorizontal, m & MaximizeVertical);
}
});
connect(window, &Window::frameGeometryAboutToChange,
this, [this](KWin::Window *window) {
if (EffectWindowImpl *w = window->effectWindow()) {
Q_EMIT windowFrameGeometryAboutToChange(w);
}
});
connect(window, &Window::clientStartUserMovedResized, this, [this](Window *window) {
Q_EMIT windowStartUserMovedResized(window->effectWindow());
});
......@@ -357,6 +369,11 @@ void EffectsHandlerImpl::setupUnmanagedConnections(Unmanaged *u)
connect(u, &Unmanaged::visibleGeometryChanged, this, [this, u]() {
Q_EMIT windowExpandedGeometryChanged(u->effectWindow());
});
connect(u, &Unmanaged::frameGeometryAboutToChange, this, [this](Window *window) {
if (EffectWindowImpl *w = window->effectWindow()) {
Q_EMIT windowFrameGeometryAboutToChange(w);
}
});
}
void EffectsHandlerImpl::reconfigure()
......@@ -441,6 +458,11 @@ void EffectsHandlerImpl::drawWindow(EffectWindow *w, int mask, const QRegion &re
}
}
void EffectsHandlerImpl::renderWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
{
m_scene->finalDrawWindow(static_cast<EffectWindowImpl *>(w), mask, region, data);
}
bool EffectsHandlerImpl::hasDecorationShadows() const
{
return false;
......
......@@ -65,6 +65,7 @@ public:
Effect *provides(Effect::Feature ef);
void drawWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) override;
void renderWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) override;
void activateWindow(EffectWindow *c) override;
EffectWindow *activeWindow() const override;
......
......@@ -16,14 +16,13 @@
namespace KWin
{
BlendChanges::BlendChanges()
: OffscreenEffect()
: CrossFadeEffect()
{
QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/BlendChanges"),
QStringLiteral("org.kde.KWin.BlendChanges"),
this,
QDBusConnection::ExportAllSlots);
setLive(false);
m_timeline.setEasingCurve(QEasingCurve::InOutCubic);
}
......@@ -59,26 +58,6 @@ void KWin::BlendChanges::start(int delay)
m_state = ShowingCache;
}
void BlendChanges::drawWindow(EffectWindow *window, int mask, const QRegion &region, WindowPaintData &data)
{
// draw the new picture underneath at full opacity
if (m_state != ShowingCache) {
Effect::drawWindow(window, mask, region, data);
}
// then the old on top, it works better than changing both alphas with the current blend mode
if (m_state != Off) {
OffscreenEffect::drawWindow(window, mask, region, data);
}
}
void BlendChanges::apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads)
{
Q_UNUSED(window)
Q_UNUSED(mask)
Q_UNUSED(quads)
data.setOpacity((1.0 - m_timeline.value()) * data.opacity());
}
bool BlendChanges::isActive() const
{
return m_state != Off;
......@@ -98,6 +77,15 @@ void BlendChanges::postPaintScreen()
effects->addRepaintFull();
}
void BlendChanges::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
{
Q_UNUSED(w)
Q_UNUSED(mask)
Q_UNUSED(region)
data.setCrossFadeProgress(m_timeline.value());
effects->paintWindow(w, mask, region, data);
}
void BlendChanges::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
{
if (m_state == Off) {
......
......@@ -14,7 +14,7 @@
namespace KWin
{
class BlendChanges : public OffscreenEffect
class BlendChanges : public CrossFadeEffect
{
Q_OBJECT
......@@ -27,8 +27,8 @@ public:
// Effect interface
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override;
void postPaintScreen() override;
void drawWindow(EffectWindow *window, int mask, const QRegion &region, WindowPaintData &data) override;
void apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads) override;
void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override;
bool isActive() const override;
public Q_SLOTS:
......
......@@ -16,6 +16,8 @@ class MaximizeEffect {
effects.windowMaximizedStateChanged.connect(
this.onWindowMaximizedStateChanged.bind(this));
effect.animationEnded.connect(this.restoreForceBlurState.bind(this));
effects.windowMaximizedStateAboutToChange.connect(
this.onWindowMaximizedStateAboutToChange.bind(this));
this.loadConfig();
}
......@@ -24,6 +26,29 @@ class MaximizeEffect {
this.duration = animationTime(250);
}
onWindowMaximizedStateAboutToChange(window) {
if (window.maximizeAnimation1) {
cancel(window.maximizeAnimation1);
delete window.maximizeAnimation1;
}
let couldRetarget = false;
if (window.maximizeAnimation2) {
couldRetarget = retarget(window.maximizeAnimation2, 1.0, this.duration);
}
if (!couldRetarget) {
window.maximizeAnimation2 = animate({
window: window,
duration: this.duration,
animations: [{
type: Effect.CrossFadePrevious,
to: 1.0,
from: 0.0,
curve: QEasingCurve.OutCubic
}]
});
}
}
onWindowMaximizedStateChanged(window) {
if (!window.oldGeometry) {
return;
......@@ -62,18 +87,6 @@ class MaximizeEffect {
curve: QEasingCurve.OutCubic
}]
});
if (!window.resize) {
window.maximizeAnimation2 =animate({
window: window,
duration: this.duration,
animations: [{
type: Effect.CrossFadePrevious,
to: 1.0,
from: 0.0,
curve: QEasingCurve.OutCubic
}]
});
}
}
restoreForceBlurState(window) {
......
......@@ -15,6 +15,28 @@ var morphingEffect = {
morphingEffect.duration = animationTime(150);
},
handleFrameGeometryAboutToChange: function (window) {
//only tooltips and notifications
if (!window.tooltip && !window.notification && !window.criticalNotification) {
return;
}
var couldRetarget = false;
if (window.fadeAnimation) {
couldRetarget = retarget(window.fadeAnimation[0], 1.0, morphingEffect.duration);
}
if (!couldRetarget) {
window.fadeAnimation = animate({
window: window,
duration: morphingEffect.duration,
animations: [{
type: Effect.CrossFadePrevious,
to: 1.0,
from: 0.0
}]
});
}
},
handleFrameGeometryChanged: function (window, oldGeometry) {
//only tooltips and notifications
if (!window.tooltip && !window.notification && !window.criticalNotification) {
......@@ -98,27 +120,11 @@ var morphingEffect = {
});
}
couldRetarget = false;
if (window.fadeAnimation) {
couldRetarget = retarget(window.fadeAnimation[0], 1.0, morphingEffect.duration);
}
if (!couldRetarget) {
window.fadeAnimation = animate({
window: window,
duration: morphingEffect.duration,
animations: [{
type: Effect.CrossFadePrevious,
to: 1.0,
from: 0.0
}]
});
}
},
init: function () {
effect.configChanged.connect(morphingEffect.loadConfig);
effects.windowFrameGeometryAboutToChange.connect(morphingEffect.handleFrameGeometryAboutToChange);
effects.windowFrameGeometryChanged.connect(morphingEffect.handleFrameGeometryChanged);
}
};
......
......@@ -1312,6 +1312,8 @@ void Unmanaged::configureNotifyEvent(xcb_configure_notify_event_t *e)
}
QRectF newgeom(Xcb::fromXNative(e->x), Xcb::fromXNative(e->y), Xcb::fromXNative(e->width), Xcb::fromXNative(e->height));
if (newgeom != m_frameGeometry) {
Q_EMIT frameGeometryAboutToChange(this);
QRectF old = m_frameGeometry;
m_clientGeometry = newgeom;
m_frameGeometry = newgeom;
......
......@@ -486,6 +486,8 @@ void InternalWindow::commitGeometry(const QRectF &rect)
const QRectF oldFrameGeometry = m_frameGeometry;
const Output *oldOutput = m_output;
Q_EMIT frameGeometryAboutToChange(this);
m_clientGeometry = frameRectToClientRect(rect);
m_frameGeometry = rect;
m_bufferGeometry = m_clientGeometry;
......
......@@ -46,7 +46,8 @@ public:
quint64 AnimationEffectPrivate::m_animCounter = 0;
AnimationEffect::AnimationEffect()
: d_ptr(new AnimationEffectPrivate())
: CrossFadeEffect()
, d_ptr(new AnimationEffectPrivate())
{
if (!s_clock.isValid()) {
s_clock.start();
......@@ -236,7 +237,7 @@ quint64 AnimationEffect::p_animate(EffectWindow *w, Attribute a, uint meta, int
PreviousWindowPixmapLockPtr previousPixmap;
if (a == CrossFadePrevious) {
previousPixmap = PreviousWindowPixmapLockPtr::create(w);
CrossFadeEffect::redirect(w);
}
it->first.append(AniData(
......@@ -282,7 +283,7 @@ quint64 AnimationEffect::p_animate(EffectWindow *w, Attribute a, uint meta, int
triggerRepaint();
}
if (shader) {
OffscreenEffect::redirect(w);
CrossFadeEffect::redirect(w);
}
return ret_id;
}
......@@ -308,6 +309,9 @@ bool AnimationEffect::retarget(quint64 animationId, FPx2 newTarget, int newRemai
anim->timeLine.setDuration(std::chrono::milliseconds(newRemainingTime));
anim->timeLine.reset();
if (anim->attribute == CrossFadePrevious) {
CrossFadeEffect::redirect(entry.key());
}
return true;
}
}
......@@ -343,7 +347,6 @@ bool AnimationEffect::freezeInTime(quint64 animationId, qint64 frozenTime)
bool AnimationEffect::redirect(quint64 animationId, Direction direction, TerminationFlags terminationFlags)
{
Q_D(AnimationEffect);
if (animationId == d->m_justEndedAnimation) {
return false;
}
......@@ -393,6 +396,7 @@ bool AnimationEffect::complete(quint64 animationId)
}
animIt->timeLine.setElapsed(animIt->timeLine.duration());
unredirect(entryIt.key());
return true;
}
......@@ -532,6 +536,8 @@ void AnimationEffect::paintWindow(EffectWindow *w, int mask, QRegion region, Win
{
Q_D(AnimationEffect);
AniMap::const_iterator entry = d->m_animations.constFind(w);
auto finalRegion = region;
if (entry != d->m_animations.constEnd()) {
for (QList<AniData>::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim) {
......@@ -571,7 +577,7 @@ void AnimationEffect::paintWindow(EffectWindow *w, int mask, QRegion region, Win
break;
}
case Clip:
region = clipRect(w->expandedGeometry().toAlignedRect(), *anim);
finalRegion = clipRect(w->expandedGeometry().toAlignedRect(), *anim);
break;
case Translation:
data += QPointF(interpolated(*anim, 0), interpolated(*anim, 1));
......@@ -676,6 +682,7 @@ void AnimationEffect::postPaintScreen()
if (anim->shader && std::none_of(entry->first.begin(), entry->first.end(), [anim] (const auto &other) { return anim->id != other.id && other.shader; })) {
unredirect(window);
}
unredirect(window);
animationEnded(window, anim->attribute, anim->meta);
d->m_justEndedAnimation = 0;
// NOTICE animationEnded is an external call and might have called "::animate"
......
......@@ -191,7 +191,7 @@ class AnimationEffectPrivate;
*
* @since 4.8
*/
class KWINEFFECTS_EXPORT AnimationEffect : public OffscreenEffect
class KWINEFFECTS_EXPORT AnimationEffect : public CrossFadeEffect
{
Q_OBJECT
......
......@@ -829,6 +829,7 @@ public:
virtual void paintWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) = 0;
virtual void postPaintWindow(EffectWindow *w) = 0;
virtual void drawWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) = 0;
virtual void renderWindow(EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) = 0;
virtual QVariant kwinOption(KWinOption kwopt) = 0;
/**
* Sets the cursor while the mouse is intercepted.
......@@ -1594,6 +1595,24 @@ Q_SIGNALS:
* @since 4.7
*/
void windowMaximizedStateChanged(KWin::EffectWindow *w, bool horizontal, bool vertical);
/**
* Signal emitted when the maximized state of the window @p w is about to change,
* but before windowMaximizedStateChanged is emitted or any geometry change.
* Useful for OffscreenEffect to grab a window image before any actual change happens
*
* A window can be in one of four states:
* @li restored: both @p horizontal and @p vertical are @c false
* @li horizontally maximized: @p horizontal is @c true and @p vertical is @c false
* @li vertically maximized: @p horizontal is @c false and @p vertical is @c true
* @li completely maximized: both @p horizontal and @p vertical are @c true
* @param w The window whose maximized state changed
* @param horizontal If @c true maximized horizontally
* @param vertical If @c true maximized vertically
* @since 5.26
*/
void windowMaximizedStateAboutToChange(KWin::EffectWindow *w, bool horizontal, bool vertical);
/**
* Signal emitted when the geometry or shape of a window changed.
* This is caused if the window changes geometry without user interaction.
......@@ -1612,6 +1631,16 @@ Q_SIGNALS:
* @since 5.19
*/
void windowFrameGeometryChanged(KWin::EffectWindow *window, const QRectF &oldGeometry);
/**
* This signal is emitted when the frame geometry is about to change, the new one is not known yet.
* Useful for OffscreenEffect to grab a window image before any actual change happens.
*
* @param window The window whose geometry is about to change
* @since 5.26
*/
void windowFrameGeometryAboutToChange(KWin::EffectWindow *window);
/**
* Signal emitted when the windows opacity is changed.
* @param w The window whose opacity level is changed.
......
......@@ -13,10 +13,21 @@ namespace KWin
struct OffscreenData
{
std::unique_ptr<GLTexture> texture;
std::unique_ptr<GLFramebuffer> fbo;
bool isDirty = true;
GLShader *shader = nullptr;
public:
virtual ~OffscreenData();
void setDirty();
void setShader(GLShader *newShader);
void paint(EffectWindow *window, const QRegion &region,
const WindowPaintData &data, const WindowQuadList &quads);
void maybeRender(EffectWindow *window);
private:
std::unique_ptr<GLTexture> m_texture;
std::unique_ptr<GLFramebuffer> m_fbo;
bool m_isDirty = true;
GLShader *m_shader = nullptr;
};
class OffscreenEffectPrivate
......@@ -25,12 +36,6 @@ public:
QHash<EffectWindow *, OffscreenData *> windows;
QMetaObject::Connection windowDamagedConnection;
QMetaObject::Connection windowDeletedConnection;
void paint(EffectWindow *window, GLTexture *texture, const QRegion &region,
const WindowPaintData &data, const WindowQuadList &quads, GLShader *offscreenShader);
GLTexture *maybeRender(EffectWindow *window, OffscreenData *offscreenData);
bool live = true;
};
OffscreenEffect::OffscreenEffect(QObject *parent)
......@@ -49,12 +54,6 @@ bool OffscreenEffect::supported()
return effects->isOpenGLCompositing();
}
void OffscreenEffect::setLive(bool live)
{
Q_ASSERT(d->windows.isEmpty());
d->live = live;
}
void OffscreenEffect::redirect(EffectWindow *window)
{
OffscreenData *&offscreenData = d->windows[window];
......@@ -66,11 +65,6 @@ void OffscreenEffect::redirect(EffectWindow *window)
if (d->windows.count() == 1) {
setupConnections();
}
if (!d->live) {
effects->makeOpenGLContextCurrent();
d->maybeRender(window, offscreenData);
}
}
void OffscreenEffect::unredirect(EffectWindow *window)
......@@ -89,7 +83,7 @@ void OffscreenEffect::apply(EffectWindow *window, int mask, WindowPaintData &dat
Q_UNUSED(quads)
}
GLTexture *OffscreenEffectPrivate::maybeRender(EffectWindow *window, OffscreenData *offscreenData)
void OffscreenData::maybeRender(EffectWindow *window)
{
const QRect geometry = window->expandedGeometry().toAlignedRect();
QSize textureSize = geometry.size();
......@@ -98,16 +92,16 @@ GLTexture *OffscreenEffectPrivate::maybeRender(EffectWindow *window, OffscreenDa
textureSize *= screen->devicePixelRatio();
}
if (!offscreenData->texture || offscreenData->texture->size() != textureSize) {
offscreenData->texture.reset(new GLTexture(GL_RGBA8, textureSize));
offscreenData->texture->setFilter(GL_LINEAR);
offscreenData->texture->setWrapMode(GL_CLAMP_TO_EDGE);
offscreenData->fbo.reset(new GLFramebuffer(offscreenData->texture.get()));
offscreenData->isDirty = true;
if (!m_texture || m_texture->size() != textureSize) {
m_texture.reset(new GLTexture(GL_RGBA8, textureSize));
m_texture->setFilter(GL_LINEAR);
m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
m_fbo.reset(new GLFramebuffer(m_texture.get()));
m_isDirty = true;
}
if (offscreenData->isDirty) {
GLFramebuffer::pushFramebuffer(offscreenData->fbo.get());
if (m_isDirty) {
GLFramebuffer::pushFramebuffer(m_fbo.get());
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
......@@ -121,19 +115,31 @@ GLTexture *OffscreenEffectPrivate::maybeRender(EffectWindow *window, OffscreenDa
data.setProjectionMatrix(projectionMatrix);
const int mask = Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_WINDOW_TRANSLUCENT;
effects->drawWindow(window, mask, infiniteRegion(), data);
effects->renderWindow(window, mask, infiniteRegion(), data);
GLFramebuffer::popFramebuffer();
offscreenData->isDirty = false;
m_isDirty = false;
}
}
OffscreenData::~OffscreenData()
{
}
void OffscreenData::setDirty()
{
m_isDirty = true;
}
return offscreenData->texture.get();
void OffscreenData::setShader(GLShader *newShader)
{
m_shader = newShader;
}
void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, const QRegion &region,
const WindowPaintData &data, const WindowQuadList &quads, GLShader *offscreenShader)
void OffscreenData::paint(EffectWindow *window, const QRegion &region,
const WindowPaintData &data, const WindowQuadList &quads)
{
GLShader *shader = offscreenShader ? offscreenShader : ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation);
GLShader *shader = m_shader ? m_shader : ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation);
ShaderBinder binder(shader);
const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads();
......@@ -151,7 +157,7 @@ void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, con
const size_t size = verticesPerQuad * quads.count() * sizeof(GLVertex2D);
GLVertex2D *map = static_cast<GLVertex2D *>(vbo->map(size));
quads.makeInterleavedArrays(primitiveType, map, texture->matrix(NormalizedCoordinates));
quads.makeInterleavedArrays(primitiveType, map, m_texture->matrix(NormalizedCoordinates));
vbo->unmap();
vbo->bindArrays();
......@@ -164,8 +170,8 @@ void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, con
shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * data.toMatrix());
shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a));
shader->setUniform(GLShader::Saturation, data.saturation());
shader->setUniform(GLShader::TextureWidth, texture->width());
shader->setUniform(GLShader::TextureHeight, texture->height());
shader->setUniform(GLShader::TextureWidth, m_texture->width());
shader->setUniform(GLShader::TextureHeight, m_texture->height());
const bool clipping = region != infiniteRegion();
const QRegion clipRegion = clipping ? effects->mapToRenderTarget(region) : infiniteRegion();
......@@ -177,9 +183,9 @@ void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, con
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
texture->bind();
m_texture->bind();
vbo->draw(clipRegion, primitiveType, 0, verticesPerQuad * quads.count(), clipping);
texture->unbind();
m_texture->unbind();
glDisable(GL_BLEND);
if (clipping) {
......@@ -211,15 +217,15 @@ void OffscreenEffect::drawWindow(EffectWindow *window, int mask, const QRegion &
quads.append(quad);
apply(window, mask, data, quads);
GLTexture *texture = d->maybeRender(window, offscreenData);
d->paint(window, texture, region, data, quads, offscreenData->shader);
offscreenData->maybeRender(window);
offscreenData->paint(window, region, data, quads);
}
void OffscreenEffect::handleWindowDamaged(EffectWindow *window)
{
OffscreenData *offscreenData = d->windows.value(window);
if (offscreenData) {
offscreenData->isDirty = true;
offscreenData->setDirty();
}
}