File indexing completed on 2024-05-19 16:34:34

0001 /*
0002     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kwinoffscreeneffect.h"
0008 #include "kwingltexture.h"
0009 #include "kwinglutils.h"
0010 
0011 namespace KWin
0012 {
0013 
0014 struct OffscreenData
0015 {
0016 public:
0017     virtual ~OffscreenData();
0018     void setDirty();
0019     void setShader(GLShader *newShader);
0020     void setVertexSnappingMode(RenderGeometry::VertexSnappingMode mode);
0021 
0022     void paint(EffectWindow *window, const QRegion &region,
0023                const WindowPaintData &data, const WindowQuadList &quads);
0024 
0025     void maybeRender(EffectWindow *window);
0026 
0027 private:
0028     std::unique_ptr<GLTexture> m_texture;
0029     std::unique_ptr<GLFramebuffer> m_fbo;
0030     bool m_isDirty = true;
0031     GLShader *m_shader = nullptr;
0032     RenderGeometry::VertexSnappingMode m_vertexSnappingMode = RenderGeometry::VertexSnappingMode::Round;
0033 };
0034 
0035 class OffscreenEffectPrivate
0036 {
0037 public:
0038     std::map<EffectWindow *, std::unique_ptr<OffscreenData>> windows;
0039     QMetaObject::Connection windowDamagedConnection;
0040     QMetaObject::Connection windowDeletedConnection;
0041     RenderGeometry::VertexSnappingMode vertexSnappingMode = RenderGeometry::VertexSnappingMode::Round;
0042 };
0043 
0044 OffscreenEffect::OffscreenEffect(QObject *parent)
0045     : Effect(parent)
0046     , d(std::make_unique<OffscreenEffectPrivate>())
0047 {
0048 }
0049 
0050 OffscreenEffect::~OffscreenEffect() = default;
0051 
0052 bool OffscreenEffect::supported()
0053 {
0054     return effects->isOpenGLCompositing();
0055 }
0056 
0057 void OffscreenEffect::redirect(EffectWindow *window)
0058 {
0059     std::unique_ptr<OffscreenData> &offscreenData = d->windows[window];
0060     if (offscreenData) {
0061         return;
0062     }
0063     offscreenData = std::make_unique<OffscreenData>();
0064     offscreenData->setVertexSnappingMode(d->vertexSnappingMode);
0065 
0066     if (d->windows.size() == 1) {
0067         setupConnections();
0068     }
0069 }
0070 
0071 void OffscreenEffect::unredirect(EffectWindow *window)
0072 {
0073     d->windows.erase(window);
0074     if (d->windows.empty()) {
0075         destroyConnections();
0076     }
0077 }
0078 
0079 void OffscreenEffect::setShader(EffectWindow *window, GLShader *shader)
0080 {
0081     if (const auto it = d->windows.find(window); it != d->windows.end()) {
0082         it->second->setShader(shader);
0083     }
0084 }
0085 
0086 void OffscreenEffect::apply(EffectWindow *window, int mask, WindowPaintData &data, WindowQuadList &quads)
0087 {
0088 }
0089 
0090 void OffscreenData::maybeRender(EffectWindow *window)
0091 {
0092     QRectF logicalGeometry = window->expandedGeometry();
0093     QRectF deviceGeometry = scaledRect(logicalGeometry, effects->renderTargetScale());
0094 
0095     QSize textureSize = deviceGeometry.toAlignedRect().size();
0096 
0097     if (!m_texture || m_texture->size() != textureSize) {
0098         m_texture.reset(new GLTexture(GL_RGBA8, textureSize));
0099         m_texture->setFilter(GL_LINEAR);
0100         m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
0101         m_fbo.reset(new GLFramebuffer(m_texture.get()));
0102         m_isDirty = true;
0103     }
0104 
0105     if (m_isDirty) {
0106         GLFramebuffer::pushFramebuffer(m_fbo.get());
0107         glClearColor(0.0, 0.0, 0.0, 0.0);
0108         glClear(GL_COLOR_BUFFER_BIT);
0109 
0110         QMatrix4x4 projectionMatrix;
0111         projectionMatrix.ortho(QRectF(0, 0, deviceGeometry.width(), deviceGeometry.height()));
0112 
0113         WindowPaintData data;
0114         data.setXTranslation(-logicalGeometry.x());
0115         data.setYTranslation(-logicalGeometry.y());
0116         data.setOpacity(1.0);
0117         data.setProjectionMatrix(projectionMatrix);
0118 
0119         const int mask = Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_WINDOW_TRANSLUCENT;
0120         effects->drawWindow(window, mask, infiniteRegion(), data);
0121 
0122         GLFramebuffer::popFramebuffer();
0123         m_isDirty = false;
0124     }
0125 }
0126 
0127 OffscreenData::~OffscreenData()
0128 {
0129 }
0130 
0131 void OffscreenData::setDirty()
0132 {
0133     m_isDirty = true;
0134 }
0135 
0136 void OffscreenData::setShader(GLShader *newShader)
0137 {
0138     m_shader = newShader;
0139 }
0140 
0141 void OffscreenData::setVertexSnappingMode(RenderGeometry::VertexSnappingMode mode)
0142 {
0143     m_vertexSnappingMode = mode;
0144 }
0145 
0146 void OffscreenData::paint(EffectWindow *window, const QRegion &region,
0147                           const WindowPaintData &data, const WindowQuadList &quads)
0148 {
0149     GLShader *shader = m_shader ? m_shader : ShaderManager::instance()->shader(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::AdjustSaturation);
0150     ShaderBinder binder(shader);
0151 
0152     const qreal scale = effects->renderTargetScale();
0153 
0154     GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0155     vbo->reset();
0156     vbo->setAttribLayout(GLVertexBuffer::GLVertex2DLayout, 2, sizeof(GLVertex2D));
0157 
0158     RenderGeometry geometry;
0159     geometry.setVertexSnappingMode(m_vertexSnappingMode);
0160     for (auto &quad : quads) {
0161         geometry.appendWindowQuad(quad, scale);
0162     }
0163     geometry.postProcessTextureCoordinates(m_texture->matrix(NormalizedCoordinates));
0164 
0165     GLVertex2D *map = static_cast<GLVertex2D *>(vbo->map(geometry.count() * sizeof(GLVertex2D)));
0166     geometry.copy(std::span(map, geometry.count()));
0167     vbo->unmap();
0168 
0169     vbo->bindArrays();
0170 
0171     const qreal rgb = data.brightness() * data.opacity();
0172     const qreal a = data.opacity();
0173 
0174     QMatrix4x4 mvp = data.projectionMatrix();
0175     mvp.translate(window->x() * scale, window->y() * scale);
0176 
0177     shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * data.toMatrix(effects->renderTargetScale()));
0178     shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a));
0179     shader->setUniform(GLShader::Saturation, data.saturation());
0180     shader->setUniform(GLShader::TextureWidth, m_texture->width());
0181     shader->setUniform(GLShader::TextureHeight, m_texture->height());
0182 
0183     const bool clipping = region != infiniteRegion();
0184     const QRegion clipRegion = clipping ? effects->mapToRenderTarget(region) : infiniteRegion();
0185 
0186     if (clipping) {
0187         glEnable(GL_SCISSOR_TEST);
0188     }
0189 
0190     glEnable(GL_BLEND);
0191     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
0192 
0193     m_texture->bind();
0194     vbo->draw(clipRegion, GL_TRIANGLES, 0, geometry.count(), clipping);
0195     m_texture->unbind();
0196 
0197     glDisable(GL_BLEND);
0198     if (clipping) {
0199         glDisable(GL_SCISSOR_TEST);
0200     }
0201     vbo->unbindArrays();
0202 }
0203 
0204 void OffscreenEffect::drawWindow(EffectWindow *window, int mask, const QRegion &region, WindowPaintData &data)
0205 {
0206     const auto it = d->windows.find(window);
0207     if (it == d->windows.end()) {
0208         effects->drawWindow(window, mask, region, data);
0209         return;
0210     }
0211     OffscreenData *offscreenData = it->second.get();
0212 
0213     const QRectF expandedGeometry = window->expandedGeometry();
0214     const QRectF frameGeometry = window->frameGeometry();
0215 
0216     QRectF visibleRect = expandedGeometry;
0217     visibleRect.moveTopLeft(expandedGeometry.topLeft() - frameGeometry.topLeft());
0218     WindowQuad quad;
0219     quad[0] = WindowVertex(visibleRect.topLeft(), QPointF(0, 0));
0220     quad[1] = WindowVertex(visibleRect.topRight(), QPointF(1, 0));
0221     quad[2] = WindowVertex(visibleRect.bottomRight(), QPointF(1, 1));
0222     quad[3] = WindowVertex(visibleRect.bottomLeft(), QPointF(0, 1));
0223 
0224     WindowQuadList quads;
0225     quads.append(quad);
0226     apply(window, mask, data, quads);
0227 
0228     offscreenData->maybeRender(window);
0229     offscreenData->paint(window, region, data, quads);
0230 }
0231 
0232 void OffscreenEffect::handleWindowDamaged(EffectWindow *window)
0233 {
0234     if (const auto it = d->windows.find(window); it != d->windows.end()) {
0235         it->second->setDirty();
0236     }
0237 }
0238 
0239 void OffscreenEffect::handleWindowDeleted(EffectWindow *window)
0240 {
0241     effects->makeOpenGLContextCurrent();
0242     unredirect(window);
0243 }
0244 
0245 void OffscreenEffect::setupConnections()
0246 {
0247     d->windowDamagedConnection =
0248         connect(effects, &EffectsHandler::windowDamaged, this, &OffscreenEffect::handleWindowDamaged);
0249 
0250     d->windowDeletedConnection =
0251         connect(effects, &EffectsHandler::windowDeleted, this, &OffscreenEffect::handleWindowDeleted);
0252 }
0253 
0254 void OffscreenEffect::destroyConnections()
0255 {
0256     disconnect(d->windowDamagedConnection);
0257     disconnect(d->windowDeletedConnection);
0258 
0259     d->windowDamagedConnection = {};
0260     d->windowDeletedConnection = {};
0261 }
0262 
0263 void OffscreenEffect::setVertexSnappingMode(RenderGeometry::VertexSnappingMode mode)
0264 {
0265     d->vertexSnappingMode = mode;
0266     for (auto &window : std::as_const(d->windows)) {
0267         window.second->setVertexSnappingMode(mode);
0268     }
0269 }
0270 
0271 class CrossFadeWindowData : public OffscreenData
0272 {
0273 public:
0274     QRectF frameGeometryAtCapture;
0275 };
0276 
0277 class CrossFadeEffectPrivate
0278 {
0279 public:
0280     std::map<EffectWindow *, std::unique_ptr<CrossFadeWindowData>> windows;
0281     qreal progress;
0282 };
0283 
0284 CrossFadeEffect::CrossFadeEffect(QObject *parent)
0285     : Effect(parent)
0286     , d(std::make_unique<CrossFadeEffectPrivate>())
0287 {
0288 }
0289 
0290 CrossFadeEffect::~CrossFadeEffect() = default;
0291 
0292 void CrossFadeEffect::drawWindow(EffectWindow *window, int mask, const QRegion &region, WindowPaintData &data)
0293 {
0294     const auto it = d->windows.find(window);
0295 
0296     // paint the new window (if applicable) underneath
0297     if (data.crossFadeProgress() > 0 || it == d->windows.end()) {
0298         Effect::drawWindow(window, mask, region, data);
0299     }
0300 
0301     if (it == d->windows.end()) {
0302         return;
0303     }
0304     CrossFadeWindowData *offscreenData = it->second.get();
0305 
0306     // paint old snapshot on top
0307     WindowPaintData previousWindowData = data;
0308     previousWindowData.setOpacity((1.0 - data.crossFadeProgress()) * data.opacity());
0309 
0310     const QRectF expandedGeometry = window->expandedGeometry();
0311     const QRectF frameGeometry = window->frameGeometry();
0312 
0313     // This is for the case of *non* live effect, when the window buffer we saved has a different size
0314     // compared to the size the window has now. The "old" window will be rendered scaled to the current
0315     // window geometry, but everything will be scaled, also the shadow if there is any, making the window
0316     // frame not line up anymore with window->frameGeometry()
0317     // to fix that, we consider how much the shadow will have scaled, and use that as margins to the
0318     // current frame geometry. this causes the scaled window to visually line up perfectly with frameGeometry,
0319     // having the scaled shadow all outside of it.
0320     const qreal widthRatio = offscreenData->frameGeometryAtCapture.width() / frameGeometry.width();
0321     const qreal heightRatio = offscreenData->frameGeometryAtCapture.height() / frameGeometry.height();
0322 
0323     const QMarginsF margins(
0324         (expandedGeometry.x() - frameGeometry.x()) / widthRatio,
0325         (expandedGeometry.y() - frameGeometry.y()) / heightRatio,
0326         (frameGeometry.right() - expandedGeometry.right()) / widthRatio,
0327         (frameGeometry.bottom() - expandedGeometry.bottom()) / heightRatio);
0328 
0329     QRectF visibleRect = QRectF(QPointF(0, 0), frameGeometry.size()) - margins;
0330 
0331     WindowQuad quad;
0332     quad[0] = WindowVertex(visibleRect.topLeft(), QPointF(0, 0));
0333     quad[1] = WindowVertex(visibleRect.topRight(), QPointF(1, 0));
0334     quad[2] = WindowVertex(visibleRect.bottomRight(), QPointF(1, 1));
0335     quad[3] = WindowVertex(visibleRect.bottomLeft(), QPointF(0, 1));
0336 
0337     WindowQuadList quads;
0338     quads.append(quad);
0339     offscreenData->paint(window, region, previousWindowData, quads);
0340 }
0341 
0342 void CrossFadeEffect::redirect(EffectWindow *window)
0343 {
0344     if (d->windows.empty()) {
0345         connect(effects, &EffectsHandler::windowDeleted, this, &CrossFadeEffect::handleWindowDeleted);
0346     }
0347 
0348     std::unique_ptr<CrossFadeWindowData> &offscreenData = d->windows[window];
0349     if (offscreenData) {
0350         return;
0351     }
0352     offscreenData = std::make_unique<CrossFadeWindowData>();
0353 
0354     // Avoid including blur and contrast effects. During a normal painting cycle they
0355     // won't be included, but since we call effects->drawWindow() outside usual compositing
0356     // cycle, we have to prevent backdrop effects kicking in.
0357     const QVariant blurRole = window->data(WindowForceBlurRole);
0358     window->setData(WindowForceBlurRole, QVariant());
0359     const QVariant contrastRole = window->data(WindowForceBackgroundContrastRole);
0360     window->setData(WindowForceBackgroundContrastRole, QVariant());
0361 
0362     effects->makeOpenGLContextCurrent();
0363     offscreenData->maybeRender(window);
0364     offscreenData->frameGeometryAtCapture = window->frameGeometry();
0365 
0366     window->setData(WindowForceBlurRole, blurRole);
0367     window->setData(WindowForceBackgroundContrastRole, contrastRole);
0368 }
0369 
0370 void CrossFadeEffect::unredirect(EffectWindow *window)
0371 {
0372     d->windows.erase(window);
0373     if (d->windows.empty()) {
0374         disconnect(effects, &EffectsHandler::windowDeleted, this, &CrossFadeEffect::handleWindowDeleted);
0375     }
0376 }
0377 
0378 void CrossFadeEffect::handleWindowDeleted(EffectWindow *window)
0379 {
0380     unredirect(window);
0381 }
0382 
0383 void CrossFadeEffect::setShader(EffectWindow *window, GLShader *shader)
0384 {
0385     if (const auto it = d->windows.find(window); it != d->windows.end()) {
0386         it->second->setShader(shader);
0387     }
0388 }
0389 
0390 } // namespace KWin