From 31d1f885ce184b54690d44c5fcd4bb7384f6f0a3 Mon Sep 17 00:00:00 2001 From: Marco Martin Date: Wed, 7 Sep 2022 10:16:36 +0000 Subject: [PATCH] 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 --- .../effects/maximize_animation_test.cpp | 1 + src/effects.cpp | 22 ++ src/effects.h | 1 + src/effects/blendchanges/blendchanges.cpp | 32 +-- src/effects/blendchanges/blendchanges.h | 6 +- .../package/contents/code/maximize.js | 37 +++- .../package/contents/code/morphingpopups.js | 40 ++-- src/events.cpp | 2 + src/internalwindow.cpp | 2 + src/libkwineffects/kwinanimationeffect.cpp | 17 +- src/libkwineffects/kwinanimationeffect.h | 2 +- src/libkwineffects/kwineffects.h | 29 +++ src/libkwineffects/kwinoffscreeneffect.cpp | 209 +++++++++++++----- src/libkwineffects/kwinoffscreeneffect.h | 59 +++-- src/window.cpp | 1 + src/window.h | 6 + src/x11window.cpp | 1 + src/xdgshellwindow.cpp | 2 + 18 files changed, 344 insertions(+), 125 deletions(-) diff --git a/autotests/integration/effects/maximize_animation_test.cpp b/autotests/integration/effects/maximize_animation_test.cpp index b8daa4990e..073035e901 100644 --- a/autotests/integration/effects/maximize_animation_test.cpp +++ b/autotests/integration/effects/maximize_animation_test.cpp @@ -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(); diff --git a/src/effects.cpp b/src/effects.cpp index 637be1f991..5a0515595b 100644 --- a/src/effects.cpp +++ b/src/effects.cpp @@ -293,6 +293,18 @@ void EffectsHandlerImpl::setupWindowConnections(Window *window) connect(window, &Window::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed); connect(window, static_cast(&Window::clientMaximizedStateChanged), this, &EffectsHandlerImpl::slotClientMaximized); + connect(window, static_cast(&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 ®ion, WindowPaintData &data) +{ + m_scene->finalDrawWindow(static_cast(w), mask, region, data); +} + bool EffectsHandlerImpl::hasDecorationShadows() const { return false; diff --git a/src/effects.h b/src/effects.h index b50f6065f9..69b5628c67 100644 --- a/src/effects.h +++ b/src/effects.h @@ -65,6 +65,7 @@ public: Effect *provides(Effect::Feature ef); void drawWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override; + void renderWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override; void activateWindow(EffectWindow *c) override; EffectWindow *activeWindow() const override; diff --git a/src/effects/blendchanges/blendchanges.cpp b/src/effects/blendchanges/blendchanges.cpp index adb9069c9d..be075c0e3f 100644 --- a/src/effects/blendchanges/blendchanges.cpp +++ b/src/effects/blendchanges/blendchanges.cpp @@ -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 ®ion, 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) { diff --git a/src/effects/blendchanges/blendchanges.h b/src/effects/blendchanges/blendchanges.h index a55c1ac7db..f0e2039f77 100644 --- a/src/effects/blendchanges/blendchanges.h +++ b/src/effects/blendchanges/blendchanges.h @@ -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 ®ion, 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: diff --git a/src/effects/maximize/package/contents/code/maximize.js b/src/effects/maximize/package/contents/code/maximize.js index 53e52100cc..ebb5163d03 100644 --- a/src/effects/maximize/package/contents/code/maximize.js +++ b/src/effects/maximize/package/contents/code/maximize.js @@ -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) { diff --git a/src/effects/morphingpopups/package/contents/code/morphingpopups.js b/src/effects/morphingpopups/package/contents/code/morphingpopups.js index 333b7ecf64..0de3d2bb5d 100644 --- a/src/effects/morphingpopups/package/contents/code/morphingpopups.js +++ b/src/effects/morphingpopups/package/contents/code/morphingpopups.js @@ -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); } }; diff --git a/src/events.cpp b/src/events.cpp index 1b64c67f20..495193329d 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -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; diff --git a/src/internalwindow.cpp b/src/internalwindow.cpp index cc756f4033..555156fab8 100644 --- a/src/internalwindow.cpp +++ b/src/internalwindow.cpp @@ -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; diff --git a/src/libkwineffects/kwinanimationeffect.cpp b/src/libkwineffects/kwinanimationeffect.cpp index 409740a959..6c78ea13b9 100644 --- a/src/libkwineffects/kwinanimationeffect.cpp +++ b/src/libkwineffects/kwinanimationeffect.cpp @@ -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::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" diff --git a/src/libkwineffects/kwinanimationeffect.h b/src/libkwineffects/kwinanimationeffect.h index 4fb552f486..97c9e6b5ae 100644 --- a/src/libkwineffects/kwinanimationeffect.h +++ b/src/libkwineffects/kwinanimationeffect.h @@ -191,7 +191,7 @@ class AnimationEffectPrivate; * * @since 4.8 */ -class KWINEFFECTS_EXPORT AnimationEffect : public OffscreenEffect +class KWINEFFECTS_EXPORT AnimationEffect : public CrossFadeEffect { Q_OBJECT diff --git a/src/libkwineffects/kwineffects.h b/src/libkwineffects/kwineffects.h index 8af8a93529..ec93e309bc 100644 --- a/src/libkwineffects/kwineffects.h +++ b/src/libkwineffects/kwineffects.h @@ -829,6 +829,7 @@ public: virtual void paintWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) = 0; virtual void postPaintWindow(EffectWindow *w) = 0; virtual void drawWindow(EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) = 0; + virtual void renderWindow(EffectWindow *w, int mask, const QRegion ®ion, 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. diff --git a/src/libkwineffects/kwinoffscreeneffect.cpp b/src/libkwineffects/kwinoffscreeneffect.cpp index 412b287767..6ee3092dc0 100644 --- a/src/libkwineffects/kwinoffscreeneffect.cpp +++ b/src/libkwineffects/kwinoffscreeneffect.cpp @@ -13,10 +13,21 @@ namespace KWin struct OffscreenData { - std::unique_ptr texture; - std::unique_ptr fbo; - bool isDirty = true; - GLShader *shader = nullptr; +public: + virtual ~OffscreenData(); + void setDirty(); + void setShader(GLShader *newShader); + + void paint(EffectWindow *window, const QRegion ®ion, + const WindowPaintData &data, const WindowQuadList &quads); + + void maybeRender(EffectWindow *window); + +private: + std::unique_ptr m_texture; + std::unique_ptr m_fbo; + bool m_isDirty = true; + GLShader *m_shader = nullptr; }; class OffscreenEffectPrivate @@ -25,12 +36,6 @@ public: QHash windows; QMetaObject::Connection windowDamagedConnection; QMetaObject::Connection windowDeletedConnection; - - void paint(EffectWindow *window, GLTexture *texture, const QRegion ®ion, - 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; } - - return offscreenData->texture.get(); } -void OffscreenEffectPrivate::paint(EffectWindow *window, GLTexture *texture, const QRegion ®ion, - const WindowPaintData &data, const WindowQuadList &quads, GLShader *offscreenShader) +OffscreenData::~OffscreenData() { - GLShader *shader = offscreenShader ? offscreenShader : ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation); +} + +void OffscreenData::setDirty() +{ + m_isDirty = true; +} + +void OffscreenData::setShader(GLShader *newShader) +{ + m_shader = newShader; +} + +void OffscreenData::paint(EffectWindow *window, const QRegion ®ion, + const WindowPaintData &data, const WindowQuadList &quads) +{ + 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(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(); } } @@ -230,10 +236,9 @@ void OffscreenEffect::handleWindowDeleted(EffectWindow *window) void OffscreenEffect::setupConnections() { - if (d->live) { - d->windowDamagedConnection = - connect(effects, &EffectsHandler::windowDamaged, this, &OffscreenEffect::handleWindowDamaged); - } + d->windowDamagedConnection = + connect(effects, &EffectsHandler::windowDamaged, this, &OffscreenEffect::handleWindowDamaged); + d->windowDeletedConnection = connect(effects, &EffectsHandler::windowDeleted, this, &OffscreenEffect::handleWindowDeleted); } @@ -247,11 +252,111 @@ void OffscreenEffect::destroyConnections() d->windowDeletedConnection = {}; } -void OffscreenEffect::setShader(EffectWindow *window, GLShader *shader) +class CrossFadeWindowData : public OffscreenData { - OffscreenData *offscreenData = d->windows.value(window); +public: + QRectF frameGeometryAtCapture; +}; + +class CrossFadeEffectPrivate +{ +public: + QHash windows; + qreal progress; +}; + +CrossFadeEffect::CrossFadeEffect(QObject *parent) + : Effect(parent) + , d(new CrossFadeEffectPrivate) +{ +} + +CrossFadeEffect::~CrossFadeEffect() +{ + qDeleteAll(d->windows); +} + +void CrossFadeEffect::drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) +{ + Q_UNUSED(mask) + + // paint the new window (if applicable) underneath + Effect::drawWindow(window, mask, region, data); + + // paint old snapshot on top + WindowPaintData previousWindowData = data; + previousWindowData.setOpacity((1.0 - data.crossFadeProgress()) * data.opacity()); + CrossFadeWindowData *offscreenData = d->windows[window]; + if (!offscreenData) { + return; + } + const QRectF expandedGeometry = window->expandedGeometry(); + const QRectF frameGeometry = window->frameGeometry(); + + // This is for the case of *non* live effect, when the window buffer we saved has a different size + // compared to the size the window has now. The "old" window will be rendered scaled to the current + // window geometry, but everything will be scaled, also the shadow if there is any, making the window + // frame not line up anymore with window->frameGeometry() + // to fix that, we consider how much the shadow will have scaled, and use that as margins to the + // current frame geometry. this causes the scaled window to visually line up perfectly with frameGeometry, + // having the scaled shadow all outside of it. + const qreal widthRatio = offscreenData->frameGeometryAtCapture.width() / frameGeometry.width(); + const qreal heightRatio = offscreenData->frameGeometryAtCapture.height() / frameGeometry.height(); + + const QMarginsF margins( + (expandedGeometry.x() - frameGeometry.x()) / widthRatio, + (expandedGeometry.y() - frameGeometry.y()) / heightRatio, + (frameGeometry.right() - expandedGeometry.right()) / widthRatio, + (frameGeometry.bottom() - expandedGeometry.bottom()) / heightRatio); + + QRectF visibleRect = QRectF(QPointF(0, 0), frameGeometry.size()) - margins; + + WindowQuad quad; + quad[0] = WindowVertex(visibleRect.topLeft(), QPointF(0, 0)); + quad[1] = WindowVertex(visibleRect.topRight(), QPointF(1, 0)); + quad[2] = WindowVertex(visibleRect.bottomRight(), QPointF(1, 1)); + quad[3] = WindowVertex(visibleRect.bottomLeft(), QPointF(0, 1)); + + WindowQuadList quads; + quads.append(quad); + offscreenData->paint(window, region, previousWindowData, quads); +} + +void CrossFadeEffect::redirect(EffectWindow *window) +{ + if (d->windows.isEmpty()) { + connect(effects, &EffectsHandler::windowDeleted, this, &CrossFadeEffect::handleWindowDeleted); + } + + CrossFadeWindowData *&offscreenData = d->windows[window]; if (offscreenData) { - offscreenData->shader = shader; + return; + } + offscreenData = new CrossFadeWindowData; + + effects->makeOpenGLContextCurrent(); + offscreenData->maybeRender(window); + offscreenData->frameGeometryAtCapture = window->frameGeometry(); +} + +void CrossFadeEffect::unredirect(EffectWindow *window) +{ + delete d->windows.take(window); + if (d->windows.isEmpty()) { + disconnect(effects, &EffectsHandler::windowDeleted, this, &CrossFadeEffect::handleWindowDeleted); + } +} + +void CrossFadeEffect::handleWindowDeleted(EffectWindow *window) +{ + unredirect(window); +} + +void CrossFadeEffect::setShader(EffectWindow *window, GLShader *shader) +{ + CrossFadeWindowData *offscreenData = d->windows.value(window); + if (offscreenData) { + offscreenData->setShader(shader); } } diff --git a/src/libkwineffects/kwinoffscreeneffect.h b/src/libkwineffects/kwinoffscreeneffect.h index ebf5c29b5f..fba5b787d5 100644 --- a/src/libkwineffects/kwinoffscreeneffect.h +++ b/src/libkwineffects/kwinoffscreeneffect.h @@ -12,6 +12,7 @@ namespace KWin { class OffscreenEffectPrivate; +class CrossFadeEffectPrivate; /** * The OffscreenEffect class is the base class for effects that paint deformed windows. @@ -36,13 +37,6 @@ public: static bool supported(); - /** - * If set our offscreen texture will be updated with the latest contents - * It should be set before redirecting windows - * The default is true - */ - void setLive(bool live); - protected: void drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) override; @@ -62,12 +56,6 @@ protected: */ virtual void apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads); - /** - * Allows to specify a @p shader to draw the redirected texture for @p window. - * Can only be called once the window is redirected. - * @since 5.25 - **/ - void setShader(EffectWindow *window, GLShader *shader); private Q_SLOTS: void handleWindowDamaged(EffectWindow *window); @@ -80,4 +68,49 @@ private: std::unique_ptr d; }; +/** + * The CrossFadeEffect class is the base class for effects that paints crossfades + * + * Windows are snapshotted at the time we want to start crossfading from. Hereafter we draw both the new contents + * and the old pixmap at the ratio defined by WindowPaintData::crossFadeProgress + * + * Subclasses are responsible for driving the animation and calling unredirect after animation completes. + * + * If window geometry changes shape after this point our "old" pixmap is resized to fit approximately matching + * frame geometry + */ +class KWINEFFECTS_EXPORT CrossFadeEffect : public Effect +{ + Q_OBJECT +public: + explicit CrossFadeEffect(QObject *parent = nullptr); + ~CrossFadeEffect() override; + + void drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) override; + + /** + * This function must be called when the effect wants to animate the specified + * @a window. + */ + void redirect(EffectWindow *window); + /** + * This function must be called when the effect is done animating the specified + * @a window. The window will be automatically unredirected if it's deleted. + */ + void unredirect(EffectWindow *window); + + /** + * Allows to specify a @p shader to draw the redirected texture for @p window. + * Can only be called once the window is redirected. + * @since 5.25 + **/ + void setShader(EffectWindow *window, GLShader *shader); + + static bool supported(); + +private: + void handleWindowDeleted(EffectWindow *window); + std::unique_ptr d; +}; + } // namespace KWin diff --git a/src/window.cpp b/src/window.cpp index 3d6cc1ba82..79791d3a2b 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1588,6 +1588,7 @@ void Window::maximize(MaximizeMode m) void Window::setMaximize(bool vertically, bool horizontally) { // changeMaximize() flips the state, so change from set->flip + Q_EMIT clientMaximizedStateAboutToChange(this, MaximizeMode((vertically ? MaximizeVertical : 0) | (horizontally ? MaximizeHorizontal : 0))); const MaximizeMode oldMode = requestedMaximizeMode(); changeMaximize( oldMode & MaximizeHorizontal ? !horizontally : horizontally, diff --git a/src/window.h b/src/window.h index f8bd1ccdc3..574f196587 100644 --- a/src/window.h +++ b/src/window.h @@ -1498,6 +1498,11 @@ Q_SIGNALS: */ void clientGeometryChanged(KWin::Window *window, const QRectF &oldGeometry); + /** + * This signal is emitted when the frame geometry is about to change. the new geometry is not known yet + */ + void frameGeometryAboutToChange(KWin::Window *window); + /** * This signal is emitted when the visible geometry has changed. */ @@ -1525,6 +1530,7 @@ Q_SIGNALS: void paletteChanged(const QPalette &p); void colorSchemeChanged(); void captionChanged(); + void clientMaximizedStateAboutToChange(KWin::Window *, MaximizeMode); void clientMaximizedStateChanged(KWin::Window *, MaximizeMode); void clientMaximizedStateChanged(KWin::Window *c, bool h, bool v); void transientChanged(); diff --git a/src/x11window.cpp b/src/x11window.cpp index b0e462343a..160a5c1e59 100644 --- a/src/x11window.cpp +++ b/src/x11window.cpp @@ -4205,6 +4205,7 @@ void X11Window::moveResizeInternal(const QRectF &rect, MoveResizeMode mode) return; } + Q_EMIT frameGeometryAboutToChange(this); const QRectF oldBufferGeometry = m_lastBufferGeometry; const QRectF oldFrameGeometry = m_lastFrameGeometry; const QRectF oldClientGeometry = m_lastClientGeometry; diff --git a/src/xdgshellwindow.cpp b/src/xdgshellwindow.cpp index 3954de6dcf..ca98b3350e 100644 --- a/src/xdgshellwindow.cpp +++ b/src/xdgshellwindow.cpp @@ -296,6 +296,8 @@ void XdgSurfaceWindow::moveResizeInternal(const QRectF &rect, MoveResizeMode mod return; } + Q_EMIT frameGeometryAboutToChange(this); + if (mode != MoveResizeMode::Move) { const QSizeF requestedClientSize = frameSizeToClientSize(rect.size()); if (requestedClientSize == clientSize()) {