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