File indexing completed on 2024-11-10 04:56:45

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