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 ®ion, 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"