File indexing completed on 2024-11-10 04:57:08

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 // own
0010 #include "screentransform.h"
0011 #include "core/outputconfiguration.h"
0012 #include "core/renderlayer.h"
0013 #include "core/rendertarget.h"
0014 #include "core/renderviewport.h"
0015 #include "effect/effecthandler.h"
0016 #include "opengl/glutils.h"
0017 #include "scene/workspacescene.h"
0018 
0019 #include <QDebug>
0020 
0021 static void ensureResources()
0022 {
0023     // Must initialize resources manually because the effect is a static lib.
0024     Q_INIT_RESOURCE(screentransform);
0025 }
0026 
0027 namespace KWin
0028 {
0029 
0030 ScreenTransformEffect::ScreenTransformEffect()
0031     : Effect()
0032 {
0033     // Make sure that shaders in /effects/screentransform/shaders/* are loaded.
0034     ensureResources();
0035 
0036     m_shader = ShaderManager::instance()->generateShaderFromFile(
0037         ShaderTrait::MapTexture,
0038         QStringLiteral(":/effects/screentransform/shaders/crossfade.vert"),
0039         QStringLiteral(":/effects/screentransform/shaders/crossfade.frag"));
0040 
0041     m_modelViewProjectioMatrixLocation = m_shader->uniformLocation("modelViewProjectionMatrix");
0042     m_blendFactorLocation = m_shader->uniformLocation("blendFactor");
0043     m_previousTextureLocation = m_shader->uniformLocation("previousTexture");
0044     m_currentTextureLocation = m_shader->uniformLocation("currentTexture");
0045 
0046     const QList<Output *> screens = effects->screens();
0047     for (auto screen : screens) {
0048         addScreen(screen);
0049     }
0050     connect(effects, &EffectsHandler::screenAdded, this, &ScreenTransformEffect::addScreen);
0051     connect(effects, &EffectsHandler::screenRemoved, this, &ScreenTransformEffect::removeScreen);
0052 }
0053 
0054 ScreenTransformEffect::~ScreenTransformEffect() = default;
0055 
0056 bool ScreenTransformEffect::supported()
0057 {
0058     return effects->compositingType() == OpenGLCompositing && effects->waylandDisplay() && effects->animationsSupported();
0059 }
0060 
0061 qreal transformAngle(OutputTransform current, OutputTransform old)
0062 {
0063     auto ensureShort = [](int angle) {
0064         return angle > 180 ? angle - 360 : angle < -180 ? angle + 360
0065                                                         : angle;
0066     };
0067     // % 4 to ignore flipped cases (for now)
0068     return ensureShort((int(current.kind()) % 4 - int(old.kind()) % 4) * 90);
0069 }
0070 
0071 void ScreenTransformEffect::addScreen(Output *screen)
0072 {
0073     connect(screen, &Output::aboutToChange, this, [this, screen](OutputChangeSet *changeSet) {
0074         const OutputTransform transform = changeSet->transform.value_or(screen->transform());
0075         if (screen->transform() == transform) {
0076             return;
0077         }
0078 
0079         Scene *scene = effects->scene();
0080         RenderLayer layer(screen->renderLoop());
0081         SceneDelegate delegate(scene, screen);
0082         delegate.setLayer(&layer);
0083 
0084         // Avoid including this effect while capturing previous screen state.
0085         m_capturing = true;
0086         auto resetCapturing = qScopeGuard([this]() {
0087             m_capturing = false;
0088         });
0089 
0090         scene->prePaint(&delegate);
0091 
0092         effects->makeOpenGLContextCurrent();
0093         if (auto texture = GLTexture::allocate(GL_RGBA8, screen->pixelSize())) {
0094             auto &state = m_states[screen];
0095             state.m_oldTransform = screen->transform();
0096             state.m_oldGeometry = screen->geometry();
0097             state.m_timeLine.setDuration(std::chrono::milliseconds(long(animationTime(250))));
0098             state.m_timeLine.setEasingCurve(QEasingCurve::InOutCubic);
0099             state.m_angle = transformAngle(changeSet->transform.value(), state.m_oldTransform);
0100             state.m_prev.texture = std::move(texture);
0101             state.m_prev.framebuffer = std::make_unique<GLFramebuffer>(state.m_prev.texture.get());
0102 
0103             RenderTarget renderTarget(state.m_prev.framebuffer.get());
0104             scene->paint(renderTarget, screen->geometry());
0105         } else {
0106             m_states.remove(screen);
0107         }
0108 
0109         scene->postPaint();
0110     });
0111 }
0112 
0113 void ScreenTransformEffect::removeScreen(Output *screen)
0114 {
0115     screen->disconnect(this);
0116     if (auto it = m_states.find(screen); it != m_states.end()) {
0117         effects->makeOpenGLContextCurrent();
0118         m_states.erase(it);
0119     }
0120 }
0121 
0122 void ScreenTransformEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0123 {
0124     auto it = m_states.find(data.screen);
0125     if (it != m_states.end()) {
0126         it->m_timeLine.advance(presentTime);
0127         if (it->m_timeLine.done()) {
0128             m_states.remove(data.screen);
0129         }
0130     }
0131 
0132     effects->prePaintScreen(data, presentTime);
0133 }
0134 
0135 static GLVertexBuffer *texturedRectVbo(const QRectF &geometry, qreal scale)
0136 {
0137     GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0138     vbo->reset();
0139     vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D));
0140 
0141     const auto opt = vbo->map<GLVertex2D>(6);
0142     if (!opt) {
0143         return nullptr;
0144     }
0145     const auto map = *opt;
0146 
0147     auto deviceGeometry = scaledRect(geometry, scale);
0148 
0149     // first triangle
0150     map[0] = GLVertex2D{
0151         .position = QVector2D(deviceGeometry.left(), deviceGeometry.top()),
0152         .texcoord = QVector2D(0.0, 1.0),
0153     };
0154     map[1] = GLVertex2D{
0155         .position = QVector2D(deviceGeometry.right(), deviceGeometry.bottom()),
0156         .texcoord = QVector2D(1.0, 0.0),
0157     };
0158     map[2] = GLVertex2D{
0159         .position = QVector2D(deviceGeometry.left(), deviceGeometry.bottom()),
0160         .texcoord = QVector2D(0.0, 0.0),
0161     };
0162 
0163     // second triangle
0164     map[3] = GLVertex2D{
0165         .position = QVector2D(deviceGeometry.left(), deviceGeometry.top()),
0166         .texcoord = QVector2D(0.0, 1.0),
0167     };
0168     map[4] = GLVertex2D{
0169         .position = QVector2D(deviceGeometry.right(), deviceGeometry.top()),
0170         .texcoord = QVector2D(1.0, 1.0),
0171     };
0172     map[5] = GLVertex2D{
0173         .position = QVector2D(deviceGeometry.right(), deviceGeometry.bottom()),
0174         .texcoord = QVector2D(1.0, 0.0),
0175     };
0176 
0177     vbo->unmap();
0178     return vbo;
0179 }
0180 
0181 static qreal lerp(qreal a, qreal b, qreal t)
0182 {
0183     return (1 - t) * a + t * b;
0184 }
0185 
0186 static QRectF lerp(const QRectF &a, const QRectF &b, qreal t)
0187 {
0188     QRectF ret;
0189     ret.setWidth(lerp(a.width(), b.width(), t));
0190     ret.setHeight(lerp(a.height(), b.height(), t));
0191     ret.moveCenter(b.center());
0192     return ret;
0193 }
0194 
0195 void ScreenTransformEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, KWin::Output *screen)
0196 {
0197     auto it = m_states.find(screen);
0198     if (it == m_states.end()) {
0199         effects->paintScreen(renderTarget, viewport, mask, region, screen);
0200         return;
0201     }
0202 
0203     // Render the screen in an offscreen texture.
0204     const QSize nativeSize = screen->geometry().size() * screen->scale();
0205     if (!it->m_current.texture || it->m_current.texture->size() != nativeSize) {
0206         it->m_current.texture = GLTexture::allocate(GL_RGBA8, nativeSize);
0207         if (!it->m_current.texture) {
0208             m_states.remove(screen);
0209             return;
0210         }
0211         it->m_current.framebuffer = std::make_unique<GLFramebuffer>(it->m_current.texture.get());
0212     }
0213 
0214     RenderTarget fboRenderTarget(it->m_current.framebuffer.get());
0215     RenderViewport fboViewport(viewport.renderRect(), viewport.scale(), fboRenderTarget);
0216 
0217     GLFramebuffer::pushFramebuffer(it->m_current.framebuffer.get());
0218     effects->paintScreen(fboRenderTarget, fboViewport, mask, region, screen);
0219     GLFramebuffer::popFramebuffer();
0220 
0221     const qreal blendFactor = it->m_timeLine.value();
0222     const QRectF screenRect = screen->geometry();
0223     const qreal angle = it->m_angle * (1 - blendFactor);
0224 
0225     const auto scale = viewport.scale();
0226 
0227     // Projection matrix + rotate transform.
0228     const QVector3D transformOrigin(screenRect.center());
0229     QMatrix4x4 modelViewProjectionMatrix(viewport.projectionMatrix());
0230     modelViewProjectionMatrix.translate(transformOrigin * scale);
0231     modelViewProjectionMatrix.rotate(angle, 0, 0, 1);
0232     modelViewProjectionMatrix.translate(-transformOrigin * scale);
0233 
0234     glActiveTexture(GL_TEXTURE1);
0235     it->m_prev.texture->bind();
0236     glActiveTexture(GL_TEXTURE0);
0237     it->m_current.texture->bind();
0238 
0239     // Clear the background.
0240     glClearColor(0, 0, 0, 0);
0241     glClear(GL_COLOR_BUFFER_BIT);
0242 
0243     GLVertexBuffer *vbo = texturedRectVbo(lerp(it->m_oldGeometry, screenRect, blendFactor), scale);
0244     if (!vbo) {
0245         return;
0246     }
0247 
0248     ShaderManager *sm = ShaderManager::instance();
0249     sm->pushShader(m_shader.get());
0250     m_shader->setUniform(m_modelViewProjectioMatrixLocation, modelViewProjectionMatrix);
0251     m_shader->setUniform(m_blendFactorLocation, float(blendFactor));
0252     m_shader->setUniform(m_currentTextureLocation, 0);
0253     m_shader->setUniform(m_previousTextureLocation, 1);
0254 
0255     vbo->bindArrays();
0256     vbo->draw(GL_TRIANGLES, 0, 6);
0257     vbo->unbindArrays();
0258     sm->popShader();
0259 
0260     glActiveTexture(GL_TEXTURE1);
0261     it->m_prev.texture->unbind();
0262     glActiveTexture(GL_TEXTURE0);
0263     it->m_current.texture->unbind();
0264 
0265     effects->addRepaintFull();
0266 }
0267 
0268 bool ScreenTransformEffect::isActive() const
0269 {
0270     return !m_states.isEmpty() && !m_capturing;
0271 }
0272 
0273 } // namespace KWin
0274 
0275 #include "moc_screentransform.cpp"