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

0001 /*
0002     SPDX-FileCopyrightText: 2010 Fredrik Höglund <fredrik@kde.org>
0003     SPDX-FileCopyrightText: 2011 Philipp Knechtges <philipp-dev@knechtges.com>
0004     SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "contrast.h"
0010 #include "contrastshader.h"
0011 // KConfigSkeleton
0012 
0013 #include "core/rendertarget.h"
0014 #include "core/renderviewport.h"
0015 #include "effect/effecthandler.h"
0016 #include "utils/xcbutils.h"
0017 #include "wayland/contrast.h"
0018 #include "wayland/display.h"
0019 #include "wayland/surface.h"
0020 
0021 #include <QCoreApplication>
0022 #include <QMatrix4x4>
0023 #include <QTimer>
0024 #include <QWindow>
0025 #include <cmath> // for ceil()
0026 
0027 namespace KWin
0028 {
0029 
0030 static const QByteArray s_contrastAtomName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
0031 
0032 ContrastManagerInterface *ContrastEffect::s_contrastManager = nullptr;
0033 QTimer *ContrastEffect::s_contrastManagerRemoveTimer = nullptr;
0034 
0035 ContrastEffect::ContrastEffect()
0036 {
0037     m_shader = std::make_unique<ContrastShader>();
0038     m_shader->init();
0039 
0040     // ### Hackish way to announce support.
0041     //     Should be included in _NET_SUPPORTED instead.
0042     if (m_shader && m_shader->isValid()) {
0043         if (effects->xcbConnection()) {
0044             m_net_wm_contrast_region = effects->announceSupportProperty(s_contrastAtomName, this);
0045         }
0046         if (effects->waylandDisplay()) {
0047             if (!s_contrastManagerRemoveTimer) {
0048                 s_contrastManagerRemoveTimer = new QTimer(QCoreApplication::instance());
0049                 s_contrastManagerRemoveTimer->setSingleShot(true);
0050                 s_contrastManagerRemoveTimer->callOnTimeout([]() {
0051                     s_contrastManager->remove();
0052                     s_contrastManager = nullptr;
0053                 });
0054             }
0055             s_contrastManagerRemoveTimer->stop();
0056             if (!s_contrastManager) {
0057                 s_contrastManager = new ContrastManagerInterface(effects->waylandDisplay(), s_contrastManagerRemoveTimer);
0058             }
0059         }
0060     }
0061 
0062     connect(effects, &EffectsHandler::windowAdded, this, &ContrastEffect::slotWindowAdded);
0063     connect(effects, &EffectsHandler::windowDeleted, this, &ContrastEffect::slotWindowDeleted);
0064     connect(effects, &EffectsHandler::propertyNotify, this, &ContrastEffect::slotPropertyNotify);
0065     connect(effects, &EffectsHandler::virtualScreenGeometryChanged, this, &ContrastEffect::slotScreenGeometryChanged);
0066     connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() {
0067         if (m_shader && m_shader->isValid()) {
0068             m_net_wm_contrast_region = effects->announceSupportProperty(s_contrastAtomName, this);
0069         }
0070     });
0071 
0072     // Fetch the contrast regions for all windows
0073     const QList<EffectWindow *> windowList = effects->stackingOrder();
0074     for (EffectWindow *window : windowList) {
0075         slotWindowAdded(window);
0076     }
0077 }
0078 
0079 ContrastEffect::~ContrastEffect()
0080 {
0081     // When compositing is restarted, avoid removing the manager immediately.
0082     if (s_contrastManager) {
0083         s_contrastManagerRemoveTimer->start(1000);
0084     }
0085 }
0086 
0087 void ContrastEffect::slotScreenGeometryChanged()
0088 {
0089     effects->makeOpenGLContextCurrent();
0090     if (!supported()) {
0091         effects->reloadEffect(this);
0092         return;
0093     }
0094 
0095     const QList<EffectWindow *> windowList = effects->stackingOrder();
0096     for (EffectWindow *window : windowList) {
0097         updateContrastRegion(window);
0098     }
0099 }
0100 
0101 void ContrastEffect::updateContrastRegion(EffectWindow *w)
0102 {
0103     QRegion region;
0104     QMatrix4x4 matrix;
0105     float colorTransform[16];
0106     bool valid = false;
0107 
0108     if (m_net_wm_contrast_region != XCB_ATOM_NONE) {
0109         const QByteArray value = w->readProperty(m_net_wm_contrast_region, m_net_wm_contrast_region, 32);
0110 
0111         if (value.size() > 0 && !((value.size() - (16 * sizeof(uint32_t))) % ((4 * sizeof(uint32_t))))) {
0112             const uint32_t *cardinals = reinterpret_cast<const uint32_t *>(value.constData());
0113             const float *floatCardinals = reinterpret_cast<const float *>(value.constData());
0114             unsigned int i = 0;
0115             for (; i < ((value.size() - (16 * sizeof(uint32_t)))) / sizeof(uint32_t);) {
0116                 int x = cardinals[i++];
0117                 int y = cardinals[i++];
0118                 int w = cardinals[i++];
0119                 int h = cardinals[i++];
0120                 region += Xcb::fromXNative(QRect(x, y, w, h)).toRect();
0121             }
0122 
0123             for (unsigned int j = 0; j < 16; ++j) {
0124                 colorTransform[j] = floatCardinals[i + j];
0125             }
0126 
0127             matrix = QMatrix4x4(colorTransform);
0128         }
0129 
0130         valid = !value.isNull();
0131     }
0132 
0133     SurfaceInterface *surf = w->surface();
0134 
0135     if (surf && surf->contrast()) {
0136         region = surf->contrast()->region();
0137         matrix = colorMatrix(surf->contrast()->contrast(), surf->contrast()->intensity(), surf->contrast()->saturation());
0138         valid = true;
0139     }
0140 
0141     if (auto internal = w->internalWindow()) {
0142         const auto property = internal->property("kwin_background_region");
0143         if (property.isValid()) {
0144             region = property.value<QRegion>();
0145             bool ok = false;
0146             qreal contrast = internal->property("kwin_background_contrast").toReal(&ok);
0147             if (!ok) {
0148                 contrast = 1.0;
0149             }
0150             qreal intensity = internal->property("kwin_background_intensity").toReal(&ok);
0151             if (!ok) {
0152                 intensity = 1.0;
0153             }
0154             qreal saturation = internal->property("kwin_background_saturation").toReal(&ok);
0155             if (!ok) {
0156                 saturation = 1.0;
0157             }
0158             matrix = colorMatrix(contrast, intensity, saturation);
0159             valid = true;
0160         }
0161     }
0162 
0163     if (valid) {
0164         Data &data = m_windowData[w];
0165         data.colorMatrix = matrix;
0166         data.contrastRegion = region;
0167     } else {
0168         if (auto it = m_windowData.find(w); it != m_windowData.end()) {
0169             effects->makeOpenGLContextCurrent();
0170             m_windowData.erase(it);
0171         }
0172     }
0173 }
0174 
0175 void ContrastEffect::slotWindowAdded(EffectWindow *w)
0176 {
0177     SurfaceInterface *surf = w->surface();
0178 
0179     if (surf) {
0180         m_contrastChangedConnections[w] = connect(surf, &SurfaceInterface::contrastChanged, this, [this, w]() {
0181             if (w) {
0182                 updateContrastRegion(w);
0183             }
0184         });
0185     }
0186 
0187     if (auto internal = w->internalWindow()) {
0188         internal->installEventFilter(this);
0189     }
0190 
0191     updateContrastRegion(w);
0192 }
0193 
0194 bool ContrastEffect::eventFilter(QObject *watched, QEvent *event)
0195 {
0196     auto internal = qobject_cast<QWindow *>(watched);
0197     if (internal && event->type() == QEvent::DynamicPropertyChange) {
0198         QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent *>(event);
0199         if (pe->propertyName() == "kwin_background_region" || pe->propertyName() == "kwin_background_contrast" || pe->propertyName() == "kwin_background_intensity" || pe->propertyName() == "kwin_background_saturation") {
0200             if (auto w = effects->findWindow(internal)) {
0201                 updateContrastRegion(w);
0202             }
0203         }
0204     }
0205     return false;
0206 }
0207 
0208 void ContrastEffect::slotWindowDeleted(EffectWindow *w)
0209 {
0210     if (m_contrastChangedConnections.contains(w)) {
0211         disconnect(m_contrastChangedConnections[w]);
0212         m_contrastChangedConnections.remove(w);
0213     }
0214     if (auto it = m_windowData.find(w); it != m_windowData.end()) {
0215         effects->makeOpenGLContextCurrent();
0216         m_windowData.erase(it);
0217     }
0218 }
0219 
0220 void ContrastEffect::slotPropertyNotify(EffectWindow *w, long atom)
0221 {
0222     if (w && atom == m_net_wm_contrast_region && m_net_wm_contrast_region != XCB_ATOM_NONE) {
0223         updateContrastRegion(w);
0224     }
0225 }
0226 
0227 QMatrix4x4 ContrastEffect::colorMatrix(qreal contrast, qreal intensity, qreal saturation)
0228 {
0229     QMatrix4x4 satMatrix; // saturation
0230     QMatrix4x4 intMatrix; // intensity
0231     QMatrix4x4 contMatrix; // contrast
0232 
0233     // Saturation matrix
0234     if (!qFuzzyCompare(saturation, 1.0)) {
0235         const qreal rval = (1.0 - saturation) * .2126;
0236         const qreal gval = (1.0 - saturation) * .7152;
0237         const qreal bval = (1.0 - saturation) * .0722;
0238 
0239         satMatrix = QMatrix4x4(rval + saturation, rval, rval, 0.0,
0240                                gval, gval + saturation, gval, 0.0,
0241                                bval, bval, bval + saturation, 0.0,
0242                                0, 0, 0, 1.0);
0243     }
0244 
0245     // IntensityMatrix
0246     if (!qFuzzyCompare(intensity, 1.0)) {
0247         intMatrix.scale(intensity, intensity, intensity);
0248     }
0249 
0250     // Contrast Matrix
0251     if (!qFuzzyCompare(contrast, 1.0)) {
0252         const float transl = (1.0 - contrast) / 2.0;
0253 
0254         contMatrix = QMatrix4x4(contrast, 0, 0, 0.0,
0255                                 0, contrast, 0, 0.0,
0256                                 0, 0, contrast, 0.0,
0257                                 transl, transl, transl, 1.0);
0258     }
0259 
0260     QMatrix4x4 colorMatrix = contMatrix * satMatrix * intMatrix;
0261     // colorMatrix = colorMatrix.transposed();
0262 
0263     return colorMatrix;
0264 }
0265 
0266 bool ContrastEffect::enabledByDefault()
0267 {
0268     GLPlatform *gl = GLPlatform::instance();
0269 
0270     if (gl->isIntel() && gl->chipClass() < SandyBridge) {
0271         return false;
0272     }
0273     if (gl->isPanfrost() && gl->chipClass() <= MaliT8XX) {
0274         return false;
0275     }
0276     if (gl->isLima() || gl->isVideoCore4() || gl->isVideoCore3D()) {
0277         return false;
0278     }
0279     if (gl->isSoftwareEmulation()) {
0280         return false;
0281     }
0282 
0283     return true;
0284 }
0285 
0286 bool ContrastEffect::supported()
0287 {
0288     bool supported = effects->isOpenGLCompositing() && GLFramebuffer::supported();
0289 
0290     if (supported) {
0291         int maxTexSize;
0292         glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
0293 
0294         const QSize screenSize = effects->virtualScreenSize();
0295         if (screenSize.width() > maxTexSize || screenSize.height() > maxTexSize) {
0296             supported = false;
0297         }
0298     }
0299     return supported;
0300 }
0301 
0302 QRegion ContrastEffect::contrastRegion(const EffectWindow *w) const
0303 {
0304     QRegion region;
0305     if (const auto it = m_windowData.find(w); it != m_windowData.end()) {
0306         const QRegion &appRegion = it->second.contrastRegion;
0307         if (!appRegion.isEmpty()) {
0308             region += appRegion.translated(w->contentsRect().topLeft().toPoint()) & w->decorationInnerRect().toRect();
0309         } else {
0310             // An empty region means that the blur effect should be enabled
0311             // for the whole window.
0312             region = w->decorationInnerRect().toRect();
0313         }
0314     }
0315 
0316     return region;
0317 }
0318 
0319 void ContrastEffect::uploadRegion(std::span<QVector2D> map, const QRegion &region, qreal scale)
0320 {
0321     size_t index = 0;
0322     for (const QRect &r : region) {
0323         const auto deviceRect = scaledRect(r, scale);
0324         const QVector2D topLeft = roundVector(QVector2D(deviceRect.x(), deviceRect.y()));
0325         const QVector2D topRight = roundVector(QVector2D(deviceRect.x() + deviceRect.width(), deviceRect.y()));
0326         const QVector2D bottomLeft = roundVector(QVector2D(deviceRect.x(), deviceRect.y() + deviceRect.height()));
0327         const QVector2D bottomRight = roundVector(QVector2D(deviceRect.x() + deviceRect.width(), deviceRect.y() + deviceRect.height()));
0328 
0329         // First triangle
0330         map[index++] = topRight;
0331         map[index++] = topLeft;
0332         map[index++] = bottomLeft;
0333 
0334         // Second triangle
0335         map[index++] = bottomLeft;
0336         map[index++] = bottomRight;
0337         map[index++] = topRight;
0338     }
0339 }
0340 
0341 bool ContrastEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &region, qreal scale)
0342 {
0343     const int vertexCount = region.rectCount() * 6;
0344     if (!vertexCount) {
0345         return false;
0346     }
0347 
0348     const auto map = vbo->map<QVector2D>(vertexCount);
0349     if (!map) {
0350         return false;
0351     }
0352     uploadRegion(*map, region, scale);
0353     vbo->unmap();
0354 
0355     constexpr std::array layout{
0356         GLVertexAttrib{
0357             .attributeIndex = VA_Position,
0358             .componentCount = 2,
0359             .type = GL_FLOAT,
0360             .relativeOffset = 0,
0361         },
0362         GLVertexAttrib{
0363             .attributeIndex = VA_TexCoord,
0364             .componentCount = 2,
0365             .type = GL_FLOAT,
0366             .relativeOffset = 0,
0367         },
0368     };
0369     vbo->setAttribLayout(std::span(layout), sizeof(QVector2D));
0370     return true;
0371 }
0372 
0373 bool ContrastEffect::shouldContrast(const EffectWindow *w, int mask, const WindowPaintData &data) const
0374 {
0375     if (!m_shader || !m_shader->isValid()) {
0376         return false;
0377     }
0378 
0379     if (effects->activeFullScreenEffect() && !w->data(WindowForceBackgroundContrastRole).toBool()) {
0380         return false;
0381     }
0382 
0383     if (w->isDesktop()) {
0384         return false;
0385     }
0386 
0387     bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0);
0388     bool translated = data.xTranslation() || data.yTranslation();
0389 
0390     if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBackgroundContrastRole).toBool()) {
0391         return false;
0392     }
0393 
0394     return true;
0395 }
0396 
0397 void ContrastEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
0398 {
0399     if (shouldContrast(w, mask, data)) {
0400         const QRect screen = viewport.renderRect().toRect();
0401         QRegion shape = region & contrastRegion(w).translated(w->pos().toPoint()) & screen;
0402 
0403         // let's do the evil parts - someone wants to contrast behind a transformed window
0404         const bool translated = data.xTranslation() || data.yTranslation();
0405         const bool scaled = data.xScale() != 1 || data.yScale() != 1;
0406         if (scaled) {
0407             QPoint pt = shape.boundingRect().topLeft();
0408             QRegion scaledShape;
0409             for (QRect r : shape) {
0410                 const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(),
0411                                       pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation());
0412                 const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1,
0413                                          std::floor(topLeft.y() + r.height() * data.yScale()) - 1);
0414                 scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight);
0415             }
0416             shape = scaledShape & region;
0417 
0418             // Only translated, not scaled
0419         } else if (translated) {
0420             QRegion translated;
0421             for (QRect r : shape) {
0422                 const QRectF t = QRectF(r).translated(data.xTranslation(), data.yTranslation());
0423                 const QPoint topLeft(std::ceil(t.x()), std::ceil(t.y()));
0424                 const QPoint bottomRight(std::floor(t.x() + t.width() - 1), std::floor(t.y() + t.height() - 1));
0425                 translated += QRect(topLeft, bottomRight);
0426             }
0427             shape = translated & region;
0428         }
0429 
0430         if (!shape.isEmpty()) {
0431             doContrast(renderTarget, viewport, w, shape, screen, w->opacity() * data.opacity(), data.projectionMatrix());
0432         }
0433     }
0434 
0435     // Draw the window over the contrast area
0436     effects->drawWindow(renderTarget, viewport, w, mask, region, data);
0437 }
0438 
0439 void ContrastEffect::doContrast(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, const QRegion &shape, const QRect &screen, const float opacity, const QMatrix4x4 &screenProjection)
0440 {
0441     const qreal scale = viewport.scale();
0442     const QRegion actualShape = shape & screen;
0443     const QRectF r = viewport.mapToRenderTarget(actualShape.boundingRect());
0444 
0445     // Upload geometry for the horizontal and vertical passes
0446     GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0447     vbo->reset();
0448     if (!uploadGeometry(vbo, actualShape, scale)) {
0449         return;
0450     }
0451     vbo->bindArrays();
0452 
0453     Q_ASSERT(m_windowData.contains(w));
0454     auto &windowData = m_windowData[w];
0455     if (!windowData.texture || (renderTarget.texture() && windowData.texture->internalFormat() != renderTarget.texture()->internalFormat()) || windowData.texture->size() != r.size()) {
0456         windowData.texture = GLTexture::allocate(renderTarget.texture() ? renderTarget.texture()->internalFormat() : GL_RGBA8, r.size().toSize());
0457         if (!windowData.texture) {
0458             return;
0459         }
0460         windowData.fbo = std::make_unique<GLFramebuffer>(windowData.texture.get());
0461         windowData.texture->setFilter(GL_LINEAR);
0462         windowData.texture->setWrapMode(GL_CLAMP_TO_EDGE);
0463     }
0464     GLTexture *contrastTexture = windowData.texture.get();
0465     contrastTexture->bind();
0466 
0467     windowData.fbo->blitFromFramebuffer(r.toRect(), QRect(QPoint(), contrastTexture->size()));
0468 
0469     m_shader->setColorMatrix(m_windowData[w].colorMatrix);
0470     m_shader->bind();
0471 
0472     m_shader->setOpacity(opacity);
0473     // Set up the texture matrix to transform from screen coordinates
0474     // to texture coordinates.
0475     const QRectF boundingRect = actualShape.boundingRect();
0476     QMatrix4x4 textureMatrix;
0477     textureMatrix.scale(1, -1);
0478     textureMatrix.translate(0, -1);
0479     // apply texture->buffer transformation
0480     textureMatrix.translate(0.5, 0.5);
0481     textureMatrix *= renderTarget.transform().toMatrix();
0482     textureMatrix.translate(-0.5, -0.5);
0483     // scaled logical to texture coordinates
0484     textureMatrix.scale(1.0 / boundingRect.width(), 1.0 / boundingRect.height(), 1);
0485     textureMatrix.translate(-boundingRect.x(), -boundingRect.y(), 0);
0486     textureMatrix.scale(1.0 / viewport.scale(), 1.0 / viewport.scale());
0487 
0488     m_shader->setTextureMatrix(textureMatrix);
0489     m_shader->setModelViewProjectionMatrix(screenProjection);
0490 
0491     vbo->draw(GL_TRIANGLES, 0, actualShape.rectCount() * 6);
0492 
0493     contrastTexture->unbind();
0494 
0495     vbo->unbindArrays();
0496 
0497     if (opacity < 1.0) {
0498         glDisable(GL_BLEND);
0499     }
0500 
0501     m_shader->unbind();
0502 }
0503 
0504 bool ContrastEffect::isActive() const
0505 {
0506     return !effects->isScreenLocked();
0507 }
0508 
0509 bool ContrastEffect::blocksDirectScanout() const
0510 {
0511     return false;
0512 }
0513 
0514 } // namespace KWin
0515 
0516 #include "moc_contrast.cpp"