diff --git a/src/effects/CMakeLists.txt b/src/effects/CMakeLists.txt index 28f18e124f..f15f6cb4a4 100644 --- a/src/effects/CMakeLists.txt +++ b/src/effects/CMakeLists.txt @@ -171,6 +171,7 @@ add_subdirectory(diminactive) include(fallapart/CMakeLists.txt) include(highlightwindow/CMakeLists.txt) include(kscreen/CMakeLists.txt) +include(screentransform/CMakeLists.txt) add_subdirectory(magiclamp) add_subdirectory(overview) add_subdirectory(presentwindows) diff --git a/src/effects/effect_builtins.cpp b/src/effects/effect_builtins.cpp index 08932c8963..8ab05deb96 100644 --- a/src/effects/effect_builtins.cpp +++ b/src/effects/effect_builtins.cpp @@ -17,6 +17,7 @@ #include "presentwindows/presentwindows.h" #include "screenedge/screenedgeeffect.h" #include "screenshot/screenshot.h" +#include "screentransform/screentransform.h" #include "slidingpopups/slidingpopups.h" // Common effects only relevant to desktop #include "desktopgrid/desktopgrid.h" @@ -404,6 +405,21 @@ EFFECT_FALLBACK #endif EFFECT_FALLBACK QString() + }, { + QStringLiteral("screentransform"), + i18ndc("kwin_effects", "Name of a KWin Effect", "Transform"), + i18ndc("kwin_effects", "Comment describing the KWin Effect", "Animates display transformations"), + QStringLiteral("Appearance"), + QString(), + QUrl(), + true, + true, +#ifdef EFFECT_BUILTINS + &createHelper, + &ScreenTransformEffect::supported, + nullptr, +#endif + EFFECT_FALLBACK QString() }, { QStringLiteral("sheet"), i18ndc("kwin_effects", "Name of a KWin Effect", "Sheet"), diff --git a/src/effects/effect_builtins.h b/src/effects/effect_builtins.h index ebf77e0549..d86a0e3657 100644 --- a/src/effects/effect_builtins.h +++ b/src/effects/effect_builtins.h @@ -43,6 +43,7 @@ enum class BuiltInEffect Resize, ScreenEdge, ScreenShot, + ScreenTransform, Sheet, ShowFps, ShowPaint, diff --git a/src/effects/screentransform/CMakeLists.txt b/src/effects/screentransform/CMakeLists.txt new file mode 100644 index 0000000000..60a306eae4 --- /dev/null +++ b/src/effects/screentransform/CMakeLists.txt @@ -0,0 +1,7 @@ +####################################### +# Effect + +set(kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} + screentransform/screentransform.cpp +) + diff --git a/src/effects/screentransform/screentransform.cpp b/src/effects/screentransform/screentransform.cpp new file mode 100644 index 0000000000..6a93721f32 --- /dev/null +++ b/src/effects/screentransform/screentransform.cpp @@ -0,0 +1,205 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +// own +#include "screentransform.h" +#include "kscreenconfig.h" +#include "kwinglutils.h" +#include + +namespace KWin +{ +ScreenTransformEffect::ScreenTransformEffect() + : Effect() +{ + initConfig(); + reconfigure(ReconfigureAll); + + const QList screens = effects->screens(); + for (auto screen : screens) { + addScreen(screen); + } + connect(effects, &EffectsHandler::screenAdded, this, &ScreenTransformEffect::addScreen); + connect(effects, &EffectsHandler::screenRemoved, this, &ScreenTransformEffect::removeScreen); +} + +ScreenTransformEffect::~ScreenTransformEffect() = default; + +bool ScreenTransformEffect::supported() +{ + return effects->compositingType() == OpenGLCompositing && effects->waylandDisplay() && effects->animationsSupported(); +} + +qreal transformAngle(EffectScreen::Transform current, EffectScreen::Transform old) +{ + auto ensureShort = [](int angle) { + return angle > 180 ? angle - 360 : angle < -180 ? angle + 360 : angle; + }; + // % 4 to ignore flipped cases (for now) + return ensureShort((int(current) % 4 - int(old) % 4) * 90); +} + +void ScreenTransformEffect::addScreen(EffectScreen *screen) +{ + connect(screen, &EffectScreen::changed, this, [this, screen] { + auto &state = m_states[screen]; + if (screen->transform() == state.m_oldTransform) { + effects->makeOpenGLContextCurrent(); + m_states.remove(screen); + return; + } + state.m_timeLine.setDuration(std::chrono::milliseconds(animationTime(250))); + state.m_timeLine.setEasingCurve(QEasingCurve::OutCirc); + state.m_lastPresentTime = std::chrono::milliseconds::zero(); + state.m_angle = transformAngle(screen->transform(), state.m_oldTransform); + Q_ASSERT(state.m_angle != 0); + effects->addRepaintFull(); + }); + connect(screen, &EffectScreen::aboutToChange, this, [this, screen] { + effects->makeOpenGLContextCurrent(); + auto &state = m_states[screen]; + state.m_oldTransform = screen->transform(); + state.m_texture.reset(new GLTexture(GL_RGBA8, screen->geometry().size() * screen->devicePixelRatio())); + + // Rendering the current scene into a texture + const bool c = state.m_texture->create(); + Q_ASSERT(c); + GLRenderTarget renderTarget(*state.m_texture); + GLRenderTarget::pushRenderTarget(&renderTarget); + + GLVertexBuffer::setVirtualScreenGeometry(screen->geometry()); + GLRenderTarget::setVirtualScreenGeometry(screen->geometry()); + GLVertexBuffer::setVirtualScreenScale(screen->devicePixelRatio()); + GLRenderTarget::setVirtualScreenScale(screen->devicePixelRatio()); + + effects->renderScreen(screen); + state.m_captured = true; + GLRenderTarget::popRenderTarget(); + }); +} + +void ScreenTransformEffect::removeScreen(EffectScreen *screen) +{ + effects->makeOpenGLContextCurrent(); + m_states.remove(screen); + effects->doneOpenGLContextCurrent(); +} + +void ScreenTransformEffect::reconfigure(ReconfigureFlags flags) +{ + Q_UNUSED(flags) + KscreenConfig::self()->read(); +} + +void ScreenTransformEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) +{ + if (isScreenTransforming(data.screen)) { + std::chrono::milliseconds delta = std::chrono::milliseconds::zero(); + auto &state = m_states[data.screen]; + if (state.m_lastPresentTime.count()) { + delta = presentTime - state.m_lastPresentTime; + } + state.m_lastPresentTime = presentTime; + if (state.isSecondHalf()) { + data.mask |= PAINT_SCREEN_TRANSFORMED; + } + + state.m_timeLine.update(delta); + if (state.m_timeLine.done()) { + m_states.remove(data.screen); + } + } + + effects->prePaintScreen(data, presentTime); +} + +void ScreenTransformEffect::paintScreen(int mask, const QRegion ®ion, KWin::ScreenPaintData &data) +{ + auto screen = data.screen(); + if (isScreenTransforming(screen)) { + auto &state = m_states[screen]; + if (state.isSecondHalf()) { + data.setRotationAngle(state.m_angle / 2 * (1 - state.m_timeLine.value())); + auto center = screen->geometry().center(); + data.setRotationOrigin(QVector3D(center.x(), center.y(), 0)); + effects->addRepaintFull(); + } + } + + effects->paintScreen(mask, region, data); + if (isScreenTransforming(screen)) { + auto &state = m_states[screen]; + + if (!state.isSecondHalf()) { + Q_ASSERT(state.m_texture); + + ShaderBinder binder(ShaderTrait::MapTexture); + GLShader *shader(binder.shader()); + if (!shader) { + return; + } + const QRect screenGeometry = screen->geometry(); + const QRect textureRect = {0, 0, state.m_texture->width(), state.m_texture->height()}; + + QMatrix4x4 matrix(data.projectionMatrix()); + // Go to the former texture centre + matrix.translate(screenGeometry.width() / 2, screenGeometry.height() / 2); + // Invert the transformation + matrix.rotate(state.m_angle / 2 * (1 + 1 - state.m_timeLine.value()), 0, 0, 1); + // Go to the screen centre + matrix.translate(-textureRect.width() / 2, -textureRect.height() / 2); + shader->setUniform(GLShader::ModelViewProjectionMatrix, matrix); + + state.m_texture->bind(); + state.m_texture->render(screen->geometry(), textureRect); + state.m_texture->unbind(); + } + effects->addRepaintFull(); + } +} + +void ScreenTransformEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) +{ + auto screen = effects->findScreen(w->screen()); + if (isScreenTransforming(screen)) { + auto &state = m_states[screen]; + if (!state.isSecondHalf()) { + data.setTranslucent(); + } + } + effects->prePaintWindow(w, data, presentTime); +} + +void ScreenTransformEffect::paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) +{ + auto screen = effects->findScreen(w->screen()); + if (isScreenTransforming(screen)) { + auto &state = m_states[screen]; + if (!state.isSecondHalf()) { + // During the first half we want all on 0 because we'll be rendering our texture + data.multiplyOpacity(0.0); + data.multiplyBrightness(0.0); + } + } + effects->paintWindow(w, mask, region, data); +} + +bool ScreenTransformEffect::isActive() const +{ + return !m_states.isEmpty(); +} + +bool ScreenTransformEffect::isScreenTransforming(EffectScreen *screen) const +{ + auto it = m_states.constFind(screen); + return it != m_states.constEnd() && it->m_captured; +} + +ScreenTransformEffect::ScreenState::~ScreenState() = default; + +} // namespace KWin diff --git a/src/effects/screentransform/screentransform.h b/src/effects/screentransform/screentransform.h new file mode 100644 index 0000000000..07098a5259 --- /dev/null +++ b/src/effects/screentransform/screentransform.h @@ -0,0 +1,65 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef KWIN_SCREENTRANSFORM_H +#define KWIN_SCREENTRANSFORM_H + +#include + +namespace KWin +{ +class GLRenderTarget; +class GLTexture; + +class ScreenTransformEffect : public Effect +{ + Q_OBJECT + +public: + ScreenTransformEffect(); + ~ScreenTransformEffect() override; + + void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override; + void paintScreen(int mask, const QRegion ®ion, KWin::ScreenPaintData &data) override; + void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) override; + void paintWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) override; + + void reconfigure(ReconfigureFlags flags) override; + bool isActive() const override; + + int requestedEffectChainPosition() const override + { + return 99; + } + static bool supported(); + +private: + struct ScreenState { + ~ScreenState(); + bool isSecondHalf() const + { + return m_timeLine.progress() > 0.5; + } + + TimeLine m_timeLine; + QSharedPointer m_texture; + std::chrono::milliseconds m_lastPresentTime = std::chrono::milliseconds::zero(); + EffectScreen::Transform m_oldTransform; + qreal m_angle = 0; + bool m_captured = false; + }; + + void addScreen(EffectScreen *screen); + void removeScreen(EffectScreen *screen); + bool isScreenTransforming(EffectScreen *screen) const; + + QHash m_states; +}; + +} // namespace KWin +#endif // KWIN_SCREENTRANSFORM_H diff --git a/src/scene.cpp b/src/scene.cpp index 4b441f8735..b60579c3fe 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -74,7 +74,7 @@ #include "shadow.h" #include "wayland_server.h" #include "composite.h" -#include +#include namespace KWin {