File indexing completed on 2025-03-23 11:13:59
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 ®ion, 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 ®ion, 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 ®ion, 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 ®ion, 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