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