From 9de9b93325aa4df8fc75237c4911551aa9ff0f45 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Fri, 25 Feb 2022 21:56:38 +0000 Subject: [PATCH] Add new blend effect When changing global settings like switching between light and dark themes the result is currently quite messy. Apps update in a scattered order and a jarring change. The solution is heavily inspired from Gnome, we cache the window for X milliseconds, then perform a crossfade between the cache and current contents. It does mean any video is paused for half a second, but pragmatically it's not really bothersome. On receipt of an explicit DBus method call we create a cache and start animating. The explicit method call is needed so that we can guarantee kwin has a fully copy before a client might change. Use of an offscreen texture is used so that we include any window decorations. The DeformEffect effect is repurposed as it has most the relevnat logic if we remove the live updating. --- src/effects/CMakeLists.txt | 1 + src/effects/blendchanges/CMakeLists.txt | 14 +++ src/effects/blendchanges/blendchanges.cpp | 115 ++++++++++++++++++++++ src/effects/blendchanges/blendchanges.h | 53 ++++++++++ src/effects/blendchanges/main.cpp | 18 ++++ src/effects/blendchanges/metadata.json | 13 +++ src/libkwineffects/kwindeformeffect.cpp | 18 +++- src/libkwineffects/kwindeformeffect.h | 10 +- 8 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 src/effects/blendchanges/CMakeLists.txt create mode 100644 src/effects/blendchanges/blendchanges.cpp create mode 100644 src/effects/blendchanges/blendchanges.h create mode 100644 src/effects/blendchanges/main.cpp create mode 100644 src/effects/blendchanges/metadata.json diff --git a/src/effects/CMakeLists.txt b/src/effects/CMakeLists.txt index f8a2c141fa..835f39e418 100644 --- a/src/effects/CMakeLists.txt +++ b/src/effects/CMakeLists.txt @@ -86,6 +86,7 @@ add_subdirectory(touchpoints) add_subdirectory(zoom) # OpenGL-specific effects +add_subdirectory(blendchanges) add_subdirectory(blur) add_subdirectory(backgroundcontrast) add_subdirectory(glide) diff --git a/src/effects/blendchanges/CMakeLists.txt b/src/effects/blendchanges/CMakeLists.txt new file mode 100644 index 0000000000..395e4a61ad --- /dev/null +++ b/src/effects/blendchanges/CMakeLists.txt @@ -0,0 +1,14 @@ +####################################### +# Effect + +set(blendchanges_SOURCES + main.cpp + blendchanges.cpp +) + +kwin4_add_effect_module(kwin4_effect_blend ${blendchanges_SOURCES}) +target_link_libraries(kwin4_effect_blend PRIVATE + kwineffects + kwinglutils + Qt::DBus +) diff --git a/src/effects/blendchanges/blendchanges.cpp b/src/effects/blendchanges/blendchanges.cpp new file mode 100644 index 0000000000..1d45aaeabd --- /dev/null +++ b/src/effects/blendchanges/blendchanges.cpp @@ -0,0 +1,115 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 David Edmundson + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +// own +#include "blendchanges.h" +#include "kwinglutils.h" + +#include +#include + +namespace KWin +{ +BlendChanges::BlendChanges() + : DeformEffect() +{ + QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/BlendChanges"), + QStringLiteral("org.kde.KWin.BlendChanges"), + this, + QDBusConnection::ExportAllSlots); + + setLive(false); + m_timeline.setEasingCurve(QEasingCurve::InOutCubic); +} + +BlendChanges::~BlendChanges() = default; + +bool BlendChanges::supported() +{ + return effects->compositingType() == OpenGLCompositing && effects->animationsSupported(); +} + +void KWin::BlendChanges::start(int delay) +{ + int animationDuration = animationTime(400); + + if (!supported() || m_state != Off) { + return; + } + const EffectWindowList allWindows = effects->stackingOrder(); + for (auto window : allWindows) { + redirect(window); + } + + QTimer::singleShot(delay, this, [this, animationDuration]() { + m_timeline.setDuration(std::chrono::milliseconds(animationDuration)); + effects->addRepaintFull(); + m_state = Blending; + }); + + 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) { + DeformEffect::drawWindow(window, mask, region, data); + } +} + +void BlendChanges::deform(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads) +{ + Q_UNUSED(window) + Q_UNUSED(mask) + Q_UNUSED(quads) + data.setOpacity(1.0 - m_timeline.value()); +} + +bool BlendChanges::isActive() const +{ + return m_state != Off; +} + +void BlendChanges::postPaintScreen() +{ + if (m_timeline.done()) { + m_lastPresentTime = std::chrono::milliseconds::zero(); + m_timeline.reset(); + m_state = Off; + + const EffectWindowList allWindows = effects->stackingOrder(); + for (auto window : allWindows) { + unredirect(window); + } + } + effects->addRepaintFull(); +} + +void BlendChanges::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) +{ + if (m_state == Off) { + return; + } + if (m_state == Blending) { + std::chrono::milliseconds delta(0); + if (m_lastPresentTime.count()) { + delta = presentTime - m_lastPresentTime; + } + m_lastPresentTime = presentTime; + m_timeline.update(delta); + } + + effects->prePaintScreen(data, presentTime); +} + +} // namespace KWin diff --git a/src/effects/blendchanges/blendchanges.h b/src/effects/blendchanges/blendchanges.h new file mode 100644 index 0000000000..51653eb7ae --- /dev/null +++ b/src/effects/blendchanges/blendchanges.h @@ -0,0 +1,53 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 David Edmundson + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once +#include +#include +#include + +namespace KWin +{ + +class BlendChanges : public DeformEffect +{ + Q_OBJECT + +public: + BlendChanges(); + ~BlendChanges() override; + + static bool supported(); + + // 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 deform(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads) override; + bool isActive() const override; + +public Q_SLOTS: + /** + * Called from DBus, this should be called before triggering any changes + * delay (ms) refers to how long to keep the current frame before starting a crossfade + * We should expect all clients to have repainted by the time this expires + */ + void start(int delay = 300); + +private: + TimeLine m_timeline; + std::chrono::milliseconds m_lastPresentTime = std::chrono::milliseconds::zero(); + enum State { + Off, + ShowingCache, + Blending + }; + State m_state = Off; +}; + +} // namespace KWin diff --git a/src/effects/blendchanges/main.cpp b/src/effects/blendchanges/main.cpp new file mode 100644 index 0000000000..9bf8e36fb0 --- /dev/null +++ b/src/effects/blendchanges/main.cpp @@ -0,0 +1,18 @@ +/* + SPDX-FileCopyrightText: 2022 David Edmundson + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "blendchanges.h" + +namespace KWin +{ + +KWIN_EFFECT_FACTORY_SUPPORTED(BlendChanges, + "metadata.json.stripped", + return BlendChanges::supported();) + +} // namespace KWin + +#include "main.moc" diff --git a/src/effects/blendchanges/metadata.json b/src/effects/blendchanges/metadata.json new file mode 100644 index 0000000000..33ef3158dc --- /dev/null +++ b/src/effects/blendchanges/metadata.json @@ -0,0 +1,13 @@ +{ + "KPlugin": { + "Name": "Blend Changes", + "Description": "Animates system style changes", + "Category": "Appearance", + "EnabledByDefault": true, + "Id": "blendchanges", + "License": "GPL" + }, + "org.kde.kwin.effect": { + "internal": true + } +} diff --git a/src/libkwineffects/kwindeformeffect.cpp b/src/libkwineffects/kwindeformeffect.cpp index 32a7ed2895..0b01735b89 100644 --- a/src/libkwineffects/kwindeformeffect.cpp +++ b/src/libkwineffects/kwindeformeffect.cpp @@ -29,6 +29,7 @@ public: const WindowPaintData &data, const WindowQuadList &quads); GLTexture *maybeRender(EffectWindow *window, DeformOffscreenData *offscreenData); + bool live = true; }; DeformEffect::DeformEffect(QObject *parent) @@ -47,6 +48,12 @@ bool DeformEffect::supported() return effects->isOpenGLCompositing(); } +void DeformEffect::setLive(bool live) +{ + Q_ASSERT(d->windows.isEmpty()); + d->live = live; +} + void DeformEffect::redirect(EffectWindow *window) { DeformOffscreenData *&offscreenData = d->windows[window]; @@ -58,6 +65,11 @@ void DeformEffect::redirect(EffectWindow *window) if (d->windows.count() == 1) { setupConnections(); } + + if (!d->live) { + effects->makeOpenGLContextCurrent(); + d->maybeRender(window, offscreenData); + } } void DeformEffect::unredirect(EffectWindow *window) @@ -205,8 +217,10 @@ void DeformEffect::handleWindowDeleted(EffectWindow *window) void DeformEffect::setupConnections() { - d->windowDamagedConnection = - connect(effects, &EffectsHandler::windowDamaged, this, &DeformEffect::handleWindowDamaged); + if (d->live) { + d->windowDamagedConnection = + connect(effects, &EffectsHandler::windowDamaged, this, &DeformEffect::handleWindowDamaged); + } d->windowDeletedConnection = connect(effects, &EffectsHandler::windowDeleted, this, &DeformEffect::handleWindowDeleted); } diff --git a/src/libkwineffects/kwindeformeffect.h b/src/libkwineffects/kwindeformeffect.h index a228668e0a..720b56d328 100644 --- a/src/libkwineffects/kwindeformeffect.h +++ b/src/libkwineffects/kwindeformeffect.h @@ -37,10 +37,16 @@ public: static bool supported(); -private: - void drawWindow(EffectWindow *window, int mask, const QRegion ®ion, WindowPaintData &data) override; + /** + * 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; + /** * This function must be called when the effect wants to animate the specified * @a window.