KWin
Loading...
Searching...
No Matches
contrast.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2010 Fredrik Höglund <fredrik@kde.org>
3 SPDX-FileCopyrightText: 2011 Philipp Knechtges <philipp-dev@knechtges.com>
4 SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "contrast.h"
10#include "contrastshader.h"
11// KConfigSkeleton
12
13#include "core/rendertarget.h"
14#include "core/renderviewport.h"
16#include "utils/xcbutils.h"
17#include "wayland/contrast.h"
18#include "wayland/display.h"
19#include "wayland/surface.h"
20
21#include <QCoreApplication>
22#include <QMatrix4x4>
23#include <QTimer>
24#include <QWindow>
25#include <cmath> // for ceil()
26
27namespace KWin
28{
29
30static const QByteArray s_contrastAtomName = QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
31
32ContrastManagerInterface *ContrastEffect::s_contrastManager = nullptr;
33QTimer *ContrastEffect::s_contrastManagerRemoveTimer = nullptr;
34
36{
37 m_shader = std::make_unique<ContrastShader>();
38 m_shader->init();
39
40 // ### Hackish way to announce support.
41 // Should be included in _NET_SUPPORTED instead.
42 if (m_shader && m_shader->isValid()) {
43 if (effects->xcbConnection()) {
44 m_net_wm_contrast_region = effects->announceSupportProperty(s_contrastAtomName, this);
45 }
46 if (effects->waylandDisplay()) {
47 if (!s_contrastManagerRemoveTimer) {
48 s_contrastManagerRemoveTimer = new QTimer(QCoreApplication::instance());
49 s_contrastManagerRemoveTimer->setSingleShot(true);
50 s_contrastManagerRemoveTimer->callOnTimeout([]() {
51 s_contrastManager->remove();
52 s_contrastManager = nullptr;
53 });
54 }
55 s_contrastManagerRemoveTimer->stop();
56 if (!s_contrastManager) {
57 s_contrastManager = new ContrastManagerInterface(effects->waylandDisplay(), s_contrastManagerRemoveTimer);
58 }
59 }
60 }
61
66 connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() {
67 if (m_shader && m_shader->isValid()) {
68 m_net_wm_contrast_region = effects->announceSupportProperty(s_contrastAtomName, this);
69 }
70 });
71
72 // Fetch the contrast regions for all windows
73 const QList<EffectWindow *> windowList = effects->stackingOrder();
74 for (EffectWindow *window : windowList) {
75 slotWindowAdded(window);
76 }
77}
78
80{
81 // When compositing is restarted, avoid removing the manager immediately.
82 if (s_contrastManager) {
83 s_contrastManagerRemoveTimer->start(1000);
84 }
85}
86
88{
90 if (!supported()) {
91 effects->reloadEffect(this);
92 return;
93 }
94
95 const QList<EffectWindow *> windowList = effects->stackingOrder();
96 for (EffectWindow *window : windowList) {
97 updateContrastRegion(window);
98 }
99}
100
101void ContrastEffect::updateContrastRegion(EffectWindow *w)
102{
103 QRegion region;
104 QMatrix4x4 matrix;
105 float colorTransform[16];
106 bool valid = false;
107
108 if (m_net_wm_contrast_region != XCB_ATOM_NONE) {
109 const QByteArray value = w->readProperty(m_net_wm_contrast_region, m_net_wm_contrast_region, 32);
110
111 if (value.size() > 0 && !((value.size() - (16 * sizeof(uint32_t))) % ((4 * sizeof(uint32_t))))) {
112 const uint32_t *cardinals = reinterpret_cast<const uint32_t *>(value.constData());
113 const float *floatCardinals = reinterpret_cast<const float *>(value.constData());
114 unsigned int i = 0;
115 for (; i < ((value.size() - (16 * sizeof(uint32_t)))) / sizeof(uint32_t);) {
116 int x = cardinals[i++];
117 int y = cardinals[i++];
118 int w = cardinals[i++];
119 int h = cardinals[i++];
120 region += Xcb::fromXNative(QRect(x, y, w, h)).toRect();
121 }
122
123 for (unsigned int j = 0; j < 16; ++j) {
124 colorTransform[j] = floatCardinals[i + j];
125 }
126
127 matrix = QMatrix4x4(colorTransform);
128 }
129
130 valid = !value.isNull();
131 }
132
133 SurfaceInterface *surf = w->surface();
134
135 if (surf && surf->contrast()) {
136 region = surf->contrast()->region();
137 matrix = colorMatrix(surf->contrast()->contrast(), surf->contrast()->intensity(), surf->contrast()->saturation());
138 valid = true;
139 }
140
141 if (auto internal = w->internalWindow()) {
142 const auto property = internal->property("kwin_background_region");
143 if (property.isValid()) {
144 region = property.value<QRegion>();
145 bool ok = false;
146 qreal contrast = internal->property("kwin_background_contrast").toReal(&ok);
147 if (!ok) {
148 contrast = 1.0;
149 }
150 qreal intensity = internal->property("kwin_background_intensity").toReal(&ok);
151 if (!ok) {
152 intensity = 1.0;
153 }
154 qreal saturation = internal->property("kwin_background_saturation").toReal(&ok);
155 if (!ok) {
156 saturation = 1.0;
157 }
158 matrix = colorMatrix(contrast, intensity, saturation);
159 valid = true;
160 }
161 }
162
163 if (valid) {
164 Data &data = m_windowData[w];
165 data.colorMatrix = matrix;
166 data.contrastRegion = region;
167 } else {
168 if (auto it = m_windowData.find(w); it != m_windowData.end()) {
170 m_windowData.erase(it);
171 }
172 }
173}
174
176{
177 SurfaceInterface *surf = w->surface();
178
179 if (surf) {
180 m_contrastChangedConnections[w] = connect(surf, &SurfaceInterface::contrastChanged, this, [this, w]() {
181 if (w) {
182 updateContrastRegion(w);
183 }
184 });
185 }
186
187 if (auto internal = w->internalWindow()) {
188 internal->installEventFilter(this);
189 }
190
191 updateContrastRegion(w);
192}
193
194bool ContrastEffect::eventFilter(QObject *watched, QEvent *event)
195{
196 auto internal = qobject_cast<QWindow *>(watched);
197 if (internal && event->type() == QEvent::DynamicPropertyChange) {
198 QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent *>(event);
199 if (pe->propertyName() == "kwin_background_region" || pe->propertyName() == "kwin_background_contrast" || pe->propertyName() == "kwin_background_intensity" || pe->propertyName() == "kwin_background_saturation") {
200 if (auto w = effects->findWindow(internal)) {
201 updateContrastRegion(w);
202 }
203 }
204 }
205 return false;
206}
207
209{
210 if (m_contrastChangedConnections.contains(w)) {
211 disconnect(m_contrastChangedConnections[w]);
212 m_contrastChangedConnections.remove(w);
213 }
214 if (auto it = m_windowData.find(w); it != m_windowData.end()) {
216 m_windowData.erase(it);
217 }
218}
219
221{
222 if (w && atom == m_net_wm_contrast_region && m_net_wm_contrast_region != XCB_ATOM_NONE) {
223 updateContrastRegion(w);
224 }
225}
226
227QMatrix4x4 ContrastEffect::colorMatrix(qreal contrast, qreal intensity, qreal saturation)
228{
229 QMatrix4x4 satMatrix; // saturation
230 QMatrix4x4 intMatrix; // intensity
231 QMatrix4x4 contMatrix; // contrast
232
233 // Saturation matrix
234 if (!qFuzzyCompare(saturation, 1.0)) {
235 const qreal rval = (1.0 - saturation) * .2126;
236 const qreal gval = (1.0 - saturation) * .7152;
237 const qreal bval = (1.0 - saturation) * .0722;
238
239 satMatrix = QMatrix4x4(rval + saturation, rval, rval, 0.0,
240 gval, gval + saturation, gval, 0.0,
241 bval, bval, bval + saturation, 0.0,
242 0, 0, 0, 1.0);
243 }
244
245 // IntensityMatrix
246 if (!qFuzzyCompare(intensity, 1.0)) {
247 intMatrix.scale(intensity, intensity, intensity);
248 }
249
250 // Contrast Matrix
251 if (!qFuzzyCompare(contrast, 1.0)) {
252 const float transl = (1.0 - contrast) / 2.0;
253
254 contMatrix = QMatrix4x4(contrast, 0, 0, 0.0,
255 0, contrast, 0, 0.0,
256 0, 0, contrast, 0.0,
257 transl, transl, transl, 1.0);
258 }
259
260 QMatrix4x4 colorMatrix = contMatrix * satMatrix * intMatrix;
261 // colorMatrix = colorMatrix.transposed();
262
263 return colorMatrix;
264}
265
267{
269
270 if (gl->isIntel() && gl->chipClass() < SandyBridge) {
271 return false;
272 }
273 if (gl->isPanfrost() && gl->chipClass() <= MaliT8XX) {
274 return false;
275 }
276 if (gl->isLima() || gl->isVideoCore4() || gl->isVideoCore3D()) {
277 return false;
278 }
279 if (gl->isSoftwareEmulation()) {
280 return false;
281 }
282
283 return true;
284}
285
287{
289
290 if (supported) {
291 int maxTexSize;
292 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
293
294 const QSize screenSize = effects->virtualScreenSize();
295 if (screenSize.width() > maxTexSize || screenSize.height() > maxTexSize) {
296 supported = false;
297 }
298 }
299 return supported;
300}
301
302QRegion ContrastEffect::contrastRegion(const EffectWindow *w) const
303{
304 QRegion region;
305 if (const auto it = m_windowData.find(w); it != m_windowData.end()) {
306 const QRegion &appRegion = it->second.contrastRegion;
307 if (!appRegion.isEmpty()) {
308 region += appRegion.translated(w->contentsRect().topLeft().toPoint()) & w->decorationInnerRect().toRect();
309 } else {
310 // An empty region means that the blur effect should be enabled
311 // for the whole window.
312 region = w->decorationInnerRect().toRect();
313 }
314 }
315
316 return region;
317}
318
319void ContrastEffect::uploadRegion(std::span<QVector2D> map, const QRegion &region, qreal scale)
320{
321 size_t index = 0;
322 for (const QRect &r : region) {
323 const auto deviceRect = scaledRect(r, scale);
324 const QVector2D topLeft = roundVector(QVector2D(deviceRect.x(), deviceRect.y()));
325 const QVector2D topRight = roundVector(QVector2D(deviceRect.x() + deviceRect.width(), deviceRect.y()));
326 const QVector2D bottomLeft = roundVector(QVector2D(deviceRect.x(), deviceRect.y() + deviceRect.height()));
327 const QVector2D bottomRight = roundVector(QVector2D(deviceRect.x() + deviceRect.width(), deviceRect.y() + deviceRect.height()));
328
329 // First triangle
330 map[index++] = topRight;
331 map[index++] = topLeft;
332 map[index++] = bottomLeft;
333
334 // Second triangle
335 map[index++] = bottomLeft;
336 map[index++] = bottomRight;
337 map[index++] = topRight;
338 }
339}
340
341bool ContrastEffect::uploadGeometry(GLVertexBuffer *vbo, const QRegion &region, qreal scale)
342{
343 const int vertexCount = region.rectCount() * 6;
344 if (!vertexCount) {
345 return false;
346 }
347
348 const auto map = vbo->map<QVector2D>(vertexCount);
349 if (!map) {
350 return false;
351 }
352 uploadRegion(*map, region, scale);
353 vbo->unmap();
354
355 constexpr std::array layout{
356 GLVertexAttrib{
357 .attributeIndex = VA_Position,
358 .componentCount = 2,
359 .type = GL_FLOAT,
360 .relativeOffset = 0,
361 },
362 GLVertexAttrib{
363 .attributeIndex = VA_TexCoord,
364 .componentCount = 2,
365 .type = GL_FLOAT,
366 .relativeOffset = 0,
367 },
368 };
369 vbo->setAttribLayout(std::span(layout), sizeof(QVector2D));
370 return true;
371}
372
373bool ContrastEffect::shouldContrast(const EffectWindow *w, int mask, const WindowPaintData &data) const
374{
375 if (!m_shader || !m_shader->isValid()) {
376 return false;
377 }
378
380 return false;
381 }
382
383 if (w->isDesktop()) {
384 return false;
385 }
386
387 bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0);
388 bool translated = data.xTranslation() || data.yTranslation();
389
390 if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBackgroundContrastRole).toBool()) {
391 return false;
392 }
393
394 return true;
395}
396
397void ContrastEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
398{
399 if (shouldContrast(w, mask, data)) {
400 const QRect screen = viewport.renderRect().toRect();
401 QRegion shape = region & contrastRegion(w).translated(w->pos().toPoint()) & screen;
402
403 // let's do the evil parts - someone wants to contrast behind a transformed window
404 const bool translated = data.xTranslation() || data.yTranslation();
405 const bool scaled = data.xScale() != 1 || data.yScale() != 1;
406 if (scaled) {
407 QPoint pt = shape.boundingRect().topLeft();
408 QRegion scaledShape;
409 for (QRect r : shape) {
410 const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(),
411 pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation());
412 const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1,
413 std::floor(topLeft.y() + r.height() * data.yScale()) - 1);
414 scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight);
415 }
416 shape = scaledShape & region;
417
418 // Only translated, not scaled
419 } else if (translated) {
420 QRegion translated;
421 for (QRect r : shape) {
422 const QRectF t = QRectF(r).translated(data.xTranslation(), data.yTranslation());
423 const QPoint topLeft(std::ceil(t.x()), std::ceil(t.y()));
424 const QPoint bottomRight(std::floor(t.x() + t.width() - 1), std::floor(t.y() + t.height() - 1));
425 translated += QRect(topLeft, bottomRight);
426 }
427 shape = translated & region;
428 }
429
430 if (!shape.isEmpty()) {
431 doContrast(renderTarget, viewport, w, shape, screen, w->opacity() * data.opacity(), data.projectionMatrix());
432 }
433 }
434
435 // Draw the window over the contrast area
436 effects->drawWindow(renderTarget, viewport, w, mask, region, data);
437}
438
439void ContrastEffect::doContrast(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, const QRegion &shape, const QRect &screen, const float opacity, const QMatrix4x4 &screenProjection)
440{
441 const qreal scale = viewport.scale();
442 const QRegion actualShape = shape & screen;
443 const QRectF r = viewport.mapToRenderTarget(actualShape.boundingRect());
444
445 // Upload geometry for the horizontal and vertical passes
447 vbo->reset();
448 if (!uploadGeometry(vbo, actualShape, scale)) {
449 return;
450 }
451 vbo->bindArrays();
452
453 Q_ASSERT(m_windowData.contains(w));
454 auto &windowData = m_windowData[w];
455 if (!windowData.texture || (renderTarget.texture() && windowData.texture->internalFormat() != renderTarget.texture()->internalFormat()) || windowData.texture->size() != r.size()) {
456 windowData.texture = GLTexture::allocate(renderTarget.texture() ? renderTarget.texture()->internalFormat() : GL_RGBA8, r.size().toSize());
457 if (!windowData.texture) {
458 return;
459 }
460 windowData.fbo = std::make_unique<GLFramebuffer>(windowData.texture.get());
461 windowData.texture->setFilter(GL_LINEAR);
462 windowData.texture->setWrapMode(GL_CLAMP_TO_EDGE);
463 }
464 GLTexture *contrastTexture = windowData.texture.get();
465 contrastTexture->bind();
466
467 windowData.fbo->blitFromFramebuffer(r.toRect(), QRect(QPoint(), contrastTexture->size()));
468
469 m_shader->setColorMatrix(m_windowData[w].colorMatrix);
470 m_shader->bind();
471
472 m_shader->setOpacity(opacity);
473 // Set up the texture matrix to transform from screen coordinates
474 // to texture coordinates.
475 const QRectF boundingRect = actualShape.boundingRect();
476 QMatrix4x4 textureMatrix;
477 textureMatrix.scale(1, -1);
478 textureMatrix.translate(0, -1);
479 // apply texture->buffer transformation
480 textureMatrix.translate(0.5, 0.5);
481 textureMatrix *= renderTarget.transform().toMatrix();
482 textureMatrix.translate(-0.5, -0.5);
483 // scaled logical to texture coordinates
484 textureMatrix.scale(1.0 / boundingRect.width(), 1.0 / boundingRect.height(), 1);
485 textureMatrix.translate(-boundingRect.x(), -boundingRect.y(), 0);
486 textureMatrix.scale(1.0 / viewport.scale(), 1.0 / viewport.scale());
487
488 m_shader->setTextureMatrix(textureMatrix);
489 m_shader->setModelViewProjectionMatrix(screenProjection);
490
491 vbo->draw(GL_TRIANGLES, 0, actualShape.rectCount() * 6);
492
493 contrastTexture->unbind();
494
495 vbo->unbindArrays();
496
497 if (opacity < 1.0) {
498 glDisable(GL_BLEND);
499 }
500
501 m_shader->unbind();
502}
503
505{
506 return !effects->isScreenLocked();
507}
508
510{
511 return false;
512}
513
514} // namespace KWin
515
516#include "moc_contrast.cpp"
void slotPropertyNotify(KWin::EffectWindow *w, long atom)
Definition contrast.cpp:220
bool eventFilter(QObject *watched, QEvent *event) override
Definition contrast.cpp:194
void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data) override
Definition contrast.cpp:397
bool blocksDirectScanout() const override
Definition contrast.cpp:509
void slotWindowAdded(KWin::EffectWindow *w)
Definition contrast.cpp:175
static bool supported()
Definition contrast.cpp:286
void slotScreenGeometryChanged()
Definition contrast.cpp:87
bool isActive() const override
Definition contrast.cpp:504
static bool enabledByDefault()
Definition contrast.cpp:266
static QMatrix4x4 colorMatrix(qreal contrast, qreal intensity, qreal saturation)
Definition contrast.cpp:227
~ContrastEffect() override
Definition contrast.cpp:79
void slotWindowDeleted(KWin::EffectWindow *w)
Definition contrast.cpp:208
Represents the Global for org_kde_kwin_contrast_manager interface.
Definition contrast.h:34
Representation of a window used by/for Effect classes.
QByteArray readProperty(long atom, long type, int format) const
SurfaceInterface * surface() const
QWindow * internalWindow
Display * waylandDisplay() const
void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion &region, WindowPaintData &data)
void propertyNotify(KWin::EffectWindow *w, long atom)
void windowDeleted(KWin::EffectWindow *w)
Q_SCRIPTABLE KWin::EffectWindow * findWindow(WId id) const
void reloadEffect(Effect *effect)
QList< EffectWindow * > stackingOrder
bool makeOpenGLContextCurrent()
Makes the OpenGL compositing context current.
xcb_atom_t announceSupportProperty(const QByteArray &propertyName, Effect *effect)
Announces support for the feature with the given name. If no other Effect has announced support for t...
void virtualScreenGeometryChanged()
bool isOpenGLCompositing() const
Whether the Compositor is OpenGL based (either GL 1 or 2).
xcb_connection_t * xcbConnection() const
void windowAdded(KWin::EffectWindow *w)
Effect * activeFullScreenEffect() const
static bool supported()
bool isVideoCore3D() const
bool isVideoCore4() const
static GLPlatform * instance()
Definition glplatform.h:394
bool isPanfrost() const
bool isLima() const
ChipClass chipClass() const
bool isIntel() const
bool isSoftwareEmulation() const
static std::unique_ptr< GLTexture > allocate(GLenum internalFormat, const QSize &size, int levels=1)
GLuint texture() const
GLenum internalFormat() const
Vertex Buffer Object.
void draw(GLenum primitiveMode, int first, int count)
static GLVertexBuffer * streamingBuffer()
QMatrix4x4 toMatrix() const
Definition output.cpp:300
GLTexture * texture() const
OutputTransform transform() const
QRectF renderRect() const
QRectF mapToRenderTarget(const QRectF &logicalGeometry) const
Resource representing a wl_surface.
Definition surface.h:80
qreal yTranslation() const
Definition effect.cpp:131
qreal yScale() const
Definition effect.cpp:61
QMatrix4x4 projectionMatrix() const
Definition effect.cpp:332
qreal xScale() const
Definition effect.cpp:56
qreal xTranslation() const
Definition effect.cpp:126
qreal opacity() const
Definition effect.cpp:259
@ PAINT_WINDOW_TRANSFORMED
Definition effect.h:552
qreal fromXNative(int value)
Definition xcbutils.cpp:632
@ WindowForceBackgroundContrastRole
For fullscreen effects to enforce the background contrast,.
@ VA_Position
@ VA_TexCoord
@ SandyBridge
Definition glplatform.h:104
@ MaliT8XX
Definition glplatform.h:134
KWIN_EXPORT QRectF scaledRect(const QRectF &rect, qreal scale)
Definition globals.h:243
EffectsHandler * effects
KWIN_EXPORT QVector2D roundVector(const QVector2D &input)
Definition globals.h:251