scene.cpp 44.3 KB
Newer Older
1
/********************************************************************
2
3
4
5
6
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 2006 Lubos Lunak <l.lunak@kde.org>

7
8
9
10
11
12
13
14
15
16
17
18
19
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
20
21
22
23

/*
 The base class for compositing, implementing shared functionality
 between the OpenGL and XRender backends.
24

25
 Design:
26

27
28
29
 When compositing is turned on, XComposite extension is used to redirect
 drawing of windows to pixmaps and XDamage extension is used to get informed
 about damage (changes) to window contents. This code is mostly in composite.cpp .
30

31
 Compositor::performCompositing() starts one painting pass. Painting is done
32
33
34
35
36
 by painting the screen, which in turn paints every window. Painting can be affected
 using effects, which are chained. E.g. painting a screen means that actually
 paintScreen() of the first effect is called, which possibly does modifications
 and calls next effect's paintScreen() and so on, until Scene::finalPaintScreen()
 is called.
37

38
39
 There are 3 phases of every paint (not necessarily done together):
 The pre-paint phase, the paint phase and the post-paint phase.
40

41
42
43
44
45
46
47
48
49
 The pre-paint phase is used to find out about how the painting will be actually
 done (i.e. what the effects will do). For example when only a part of the screen
 needs to be updated and no effect will do any transformation it is possible to use
 an optimized paint function. How the painting will be done is controlled
 by the mask argument, see PAINT_WINDOW_* and PAINT_SCREEN_* flags in scene.h .
 For example an effect that decides to paint a normal windows as translucent
 will need to modify the mask in its prePaintWindow() to include
 the PAINT_WINDOW_TRANSLUCENT flag. The paintWindow() function will then get
 the mask with this flag turned on and will also paint using transparency.
50

51
52
53
54
55
56
57
58
59
60
 The paint pass does the actual painting, based on the information collected
 using the pre-paint pass. After running through the effects' paintScreen()
 either paintGenericScreen() or optimized paintSimpleScreen() are called.
 Those call paintWindow() on windows (not necessarily all), possibly using
 clipping to optimize performance and calling paintWindow() first with only
 PAINT_WINDOW_OPAQUE to paint the opaque parts and then later
 with PAINT_WINDOW_TRANSLUCENT to paint the transparent parts. Function
 paintWindow() again goes through effects' paintWindow() until
 finalPaintWindow() is called, which calls the window's performPaint() to
 do the actual painting.
61

62
63
64
65
 The post-paint can be used for cleanups and is also used for scheduling
 repaints during the next painting pass for animations. Effects wanting to
 repaint certain parts can manually damage them during post-paint and repaint
 of these parts will be done during the next paint pass.
66

67
68
69
70
*/

#include "scene.h"

71
#include <QQuickWindow>
72
#include <QVector2D>
73

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
74
#include "x11client.h"
75
76
#include "deleted.h"
#include "effects.h"
77
#include "overlaywindow.h"
78
#include "screens.h"
79
#include "shadow.h"
80
#include "subsurfacemonitor.h"
81
#include "wayland_server.h"
82

83
#include "thumbnailitem.h"
Luboš Luňák's avatar
Luboš Luňák committed
84

85
86
87
#include <KWaylandServer/buffer_interface.h>
#include <KWaylandServer/subcompositor_interface.h>
#include <KWaylandServer/surface_interface.h>
88

89
90
91
92
93
94
95
namespace KWin
{

//****************************************
// Scene
//****************************************

96
97
Scene::Scene(QObject *parent)
    : QObject(parent)
98
{
99
    last_time.invalidate(); // Initialize the timer
100
101
}

102
Scene::~Scene()
103
{
104
    Q_ASSERT(m_windows.isEmpty());
105
}
106
107

// returns mask and possibly modified region
108
void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint,
109
                        QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection, const QRect &outputGeometry, const qreal screenScale)
110
{
111
112
    const QSize &screenSize = screens()->size();
    const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height());
113
    *mask = (damage == displayRegion) ? 0 : PAINT_SCREEN_REGION;
114

115
116
117
    updateTimeDiff();
    // preparation step
    static_cast<EffectsHandlerImpl*>(effects)->startPaint();
118

119
120
    QRegion region = damage;

121
122
    ScreenPrePaintData pdata;
    pdata.mask = *mask;
123
    pdata.paint = region;
124

125
    effects->prePaintScreen(pdata, time_diff);
126
    *mask = pdata.mask;
127
    region = pdata.paint;
128

129
130
131
    if (*mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) {
        // Region painting is not possible with transformations,
        // because screen damage doesn't match transformed positions.
132
        *mask &= ~PAINT_SCREEN_REGION;
133
        region = infiniteRegion();
134
135
    } else if (*mask & PAINT_SCREEN_REGION) {
        // make sure not to go outside visible screen
136
        region &= displayRegion;
137
138
    } else {
        // whole screen, not transformed, force region to be full
139
        region = displayRegion;
140
    }
141
142

    painted_region = region;
143
    repaint_region = repaint;
144

145
    if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) {
146
        paintBackground(region);
147
    }
148

149
    ScreenPaintData data(projection, outputGeometry, screenScale);
150
151
152
    effects->paintScreen(*mask, region, data);

    foreach (Window *w, stacking_order) {
153
154
        effects->postPaintWindow(effectWindow(w));
    }
155

156
    effects->postPaintScreen();
157

158
    // make sure not to go outside of the screen area
159
    *updateRegion = damaged_region;
160
161
    *validRegion = (region | painted_region) & displayRegion;

162
163
164
    repaint_region = QRegion();
    damaged_region = QRegion();

165
    // make sure all clipping is restored
166
167
    Q_ASSERT(!PaintClipper::clip());
}
168
169
170

// Compute time since the last painting pass.
void Scene::updateTimeDiff()
171
{
172
    if (!last_time.isValid()) {
173
174
175
176
177
        // Painting has been idle (optimized out) for some time,
        // which means time_diff would be huge and would break animations.
        // Simply set it to one (zero would mean no change at all and could
        // cause problems).
        time_diff = 1;
178
        last_time.start();
179
    } else
180

Thomas Lübking's avatar
tidy up    
Thomas Lübking committed
181
    time_diff = last_time.restart();
182

183
    if (time_diff < 0)   // check time rollback
184
        time_diff = 1;
185
}
186
187
188

// Painting pass is optimized away.
void Scene::idle()
189
{
190
    // Don't break time since last paint for the next pass.
Fredrik Höglund's avatar
Fredrik Höglund committed
191
    last_time.invalidate();
192
}
193
194

// the function that'll be eventually called by paintScreen() above
195
void Scene::finalPaintScreen(int mask, const QRegion &region, ScreenPaintData& data)
196
{
197
    if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS))
198
        paintGenericScreen(mask, data);
199
    else
200
201
        paintSimpleScreen(mask, region);
}
202
203
204

// The generic painting code that can handle even transformations.
// It simply paints bottom-to-top.
205
void Scene::paintGenericScreen(int orig_mask, const ScreenPaintData &)
206
{
207
    if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) {
208
        paintBackground(infiniteRegion());
209
    }
210
    QVector<Phase2Data> phase2;
211
    phase2.reserve(stacking_order.size());
212
    foreach (Window * w, stacking_order) { // bottom to top
213
214
        Toplevel* topw = w->window();

215
216
217
        // Let the scene window update the window pixmap tree.
        w->preprocess();

218
219
220
        // Reset the repaint_region.
        // This has to be done here because many effects schedule a repaint for
        // the next frame within Effects::prePaintWindow.
221
        topw->resetRepaints();
222

223
        WindowPrePaintData data;
224
        data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT);
225
        w->resetPaintingEnabled();
226
227
        data.paint = infiniteRegion(); // no clipping, so doesn't really matter
        data.clip = QRegion();
228
        data.quads = w->buildQuads();
229
        // preparation step
230
        effects->prePaintWindow(effectWindow(w), data, time_diff);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
231
#if !defined(QT_NO_DEBUG)
232
        if (data.quads.isTransformed()) {
233
            qFatal("Pre-paint calls are not allowed to transform quads!");
234
        }
235
#endif
236
        if (!w->isPaintingEnabled()) {
237
            continue;
238
        }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
239
        phase2.append({w, infiniteRegion(), data.clip, data.mask, data.quads});
240
241
    }

242
243
244
    foreach (const Phase2Data & d, phase2) {
        paintWindow(d.window, d.mask, d.region, d.quads);
    }
245

246
247
    const QSize &screenSize = screens()->size();
    damaged_region = QRegion(0, 0, screenSize.width(), screenSize.height());
248
249
}

250
251
252
// The optimized case without any transformations at all.
// It can paint only the requested region and can use clipping
// to reduce painting and improve performance.
253
void Scene::paintSimpleScreen(int orig_mask, const QRegion &region)
254
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
255
    Q_ASSERT((orig_mask & (PAINT_SCREEN_TRANSFORMED
256
                         | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) == 0);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
257
    QVector<Phase2Data> phase2data;
258
    phase2data.reserve(stacking_order.size());
259
260

    QRegion dirtyArea = region;
261
262
263
264
265
266
    bool opaqueFullscreen = false;

    // Traverse the scene windows from bottom to top.
    for (int i = 0; i < stacking_order.count(); ++i) {
        Window *window = stacking_order[i];
        Toplevel *toplevel = window->window();
267
        WindowPrePaintData data;
268
269
        data.mask = orig_mask | (window->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT);
        window->resetPaintingEnabled();
270
        data.paint = region;
271
        data.paint |= toplevel->repaints();
272

273
274
275
        // Let the scene window update the window pixmap tree.
        window->preprocess();

276
277
278
        // Reset the repaint_region.
        // This has to be done here because many effects schedule a repaint for
        // the next frame within Effects::prePaintWindow.
279
        toplevel->resetRepaints();
Thomas Lübking's avatar
Thomas Lübking committed
280

281
        // Clip out the decoration for opaque windows; the decoration is drawn in the second pass
282
        opaqueFullscreen = false; // TODO: do we care about unmanged windows here (maybe input windows?)
283
284
285
286
        if (window->isOpaque()) {
            AbstractClient *client = dynamic_cast<AbstractClient *>(toplevel);
            if (client) {
                opaqueFullscreen = client->isFullScreen();
287
            }
288
289
            if (!(client && client->decorationHasAlpha())) {
                data.clip = window->decorationShape().translated(window->pos());
290
            }
291
            data.clip |= window->clientShape().translated(window->pos() + window->bufferOffset());
292
        } else if (toplevel->hasAlpha() && toplevel->opacity() == 1.0) {
293
294
295
            const QRegion clientShape = window->clientShape().translated(window->pos() + window->bufferOffset());
            const QRegion opaqueShape = toplevel->opaqueRegion().translated(window->pos() + toplevel->clientPos());
            data.clip = clientShape & opaqueShape;
296
297
298
        } else {
            data.clip = QRegion();
        }
299
        data.quads = window->buildQuads();
300
        // preparation step
301
        effects->prePaintWindow(effectWindow(window), data, time_diff);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
302
#if !defined(QT_NO_DEBUG)
303
        if (data.quads.isTransformed()) {
304
            qFatal("Pre-paint calls are not allowed to transform quads!");
305
        }
306
#endif
307
        if (!window->isPaintingEnabled()) {
308
            continue;
309
        }
310
        dirtyArea |= data.paint;
311
        // Schedule the window for painting
312
        phase2data.append({ window, data.paint, data.clip, data.mask, data.quads });
313
    }
314

315
316
317
318
319
    // Save the part of the repaint region that's exclusively rendered to
    // bring a reused back buffer up to date. Then union the dirty region
    // with the repaint region.
    const QRegion repaintClip = repaint_region - dirtyArea;
    dirtyArea |= repaint_region;
320

321
322
    const QSize &screenSize = screens()->size();
    const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height());
323
324
325
326
327
328
    bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations
    if (!fullRepaint) {
        extendPaintRegion(dirtyArea, opaqueFullscreen);
        fullRepaint = (dirtyArea == displayRegion);
    }

329
    QRegion allclips, upperTranslucentDamage;
330
331
    upperTranslucentDamage = repaint_region;

332
    // This is the occlusion culling pass
333
    for (int i = phase2data.count() - 1; i >= 0; --i) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
334
        Phase2Data *data = &phase2data[i];
335

336
        if (fullRepaint) {
337
            data->region = displayRegion;
338
        } else {
339
            data->region |= upperTranslucentDamage;
340
        }
341

342
        // subtract the parts which will possibly been drawn as part of
343
344
345
        // a higher opaque window
        data->region -= allclips;

346
347
        // Here we rely on WindowPrePaintData::setTranslucent() to remove
        // the clip if needed.
348
        if (!data->clip.isEmpty() && !(data->mask & PAINT_WINDOW_TRANSLUCENT)) {
349
            // clip away the opaque regions for all windows below this one
350
            allclips |= data->clip;
351
            // extend the translucent damage for windows below this by remaining (translucent) regions
352
            if (!fullRepaint) {
353
                upperTranslucentDamage |= data->region - data->clip;
354
            }
355
        } else if (!fullRepaint) {
356
            upperTranslucentDamage |= data->region;
357
        }
358
    }
359

360
    QRegion paintedArea;
361
362
    // Fill any areas of the root window not covered by opaque windows
    if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) {
363
364
        paintedArea = dirtyArea - allclips;
        paintBackground(paintedArea);
365
366
    }

367
368
    // Now walk the list bottom to top and draw the windows.
    for (int i = 0; i < phase2data.count(); ++i) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
369
        Phase2Data *data = &phase2data[i];
370

371
372
373
        // add all regions which have been drawn so far
        paintedArea |= data->region;
        data->region = paintedArea;
374
375

        paintWindow(data->window, data->mask, data->region, data->quads);
376
    }
377
378

    if (fullRepaint) {
379
        painted_region = displayRegion;
Jacopo De Simoi's avatar
Jacopo De Simoi committed
380
        damaged_region = displayRegion - repaintClip;
381
    } else {
382
        painted_region |= paintedArea;
383
384
385
386
387
388
389
390
391

        // Clip the repainted region from the damaged region.
        // It's important that we don't add the union of the damaged region
        // and the repainted region to the damage history. Otherwise the
        // repaint region will grow with every frame until it eventually
        // covers the whole back buffer, at which point we're always doing
        // full repaints.
        damaged_region = paintedArea - repaintClip;
    }
392
}
393

394
void Scene::addToplevel(Toplevel *c)
395
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
396
    Q_ASSERT(!m_windows.contains(c));
397
398
    Scene::Window *w = createWindow(c);
    m_windows[ c ] = w;
399

400
    connect(c, SIGNAL(windowClosed(KWin::Toplevel*,KWin::Deleted*)), SLOT(windowClosed(KWin::Toplevel*,KWin::Deleted*)));
401
    if (c->surface()) {
402
403
404
405
406
        // We generate window quads for sub-surfaces so it's quite important to discard
        // the pixmap tree and cached window quads when the sub-surface tree is changed.
        SubSurfaceMonitor *monitor = new SubSurfaceMonitor(c->surface(), this);

        // TODO(vlad): Is there a more efficient way to manage window pixmap trees?
407
408
409
410
        connect(monitor, &SubSurfaceMonitor::subSurfaceAdded, w, &Window::discardPixmap);
        connect(monitor, &SubSurfaceMonitor::subSurfaceRemoved, w, &Window::discardPixmap);
        connect(monitor, &SubSurfaceMonitor::subSurfaceMapped, w, &Window::discardPixmap);
        connect(monitor, &SubSurfaceMonitor::subSurfaceUnmapped, w, &Window::discardPixmap);
411
        connect(monitor, &SubSurfaceMonitor::subSurfaceBufferSizeChanged, w, &Window::discardPixmap);
412
413
414
415
416
417
418

        connect(monitor, &SubSurfaceMonitor::subSurfaceAdded, w, &Window::discardQuads);
        connect(monitor, &SubSurfaceMonitor::subSurfaceRemoved, w, &Window::discardQuads);
        connect(monitor, &SubSurfaceMonitor::subSurfaceMoved, w, &Window::discardQuads);
        connect(monitor, &SubSurfaceMonitor::subSurfaceResized, w, &Window::discardQuads);
        connect(monitor, &SubSurfaceMonitor::subSurfaceMapped, w, &Window::discardQuads);
        connect(monitor, &SubSurfaceMonitor::subSurfaceUnmapped, w, &Window::discardQuads);
419
        connect(monitor, &SubSurfaceMonitor::subSurfaceSurfaceToBufferMatrixChanged, w, &Window::discardQuads);
420

421
        connect(c->surface(), &KWaylandServer::SurfaceInterface::bufferSizeChanged, w, &Window::discardPixmap);
422
        connect(c->surface(), &KWaylandServer::SurfaceInterface::surfaceToBufferMatrixChanged, w, &Window::discardQuads);
423
    }
424

425
426
427
    connect(c, &Toplevel::screenScaleChanged, w, &Window::discardQuads);
    connect(c, &Toplevel::shadowChanged, w, &Window::discardQuads);
    connect(c, &Toplevel::geometryShapeChanged, w, &Window::discardShape);
428

429
    c->effectWindow()->setSceneWindow(w);
430
    c->updateShadow();
431
432
433
    w->updateShadow(c->shadow());
}

434
void Scene::removeToplevel(Toplevel *toplevel)
435
{
436
437
438
    Q_ASSERT(m_windows.contains(toplevel));
    delete m_windows.take(toplevel);
    toplevel->effectWindow()->setSceneWindow(nullptr);
439
440
}

441
void Scene::windowClosed(Toplevel *toplevel, Deleted *deleted)
442
{
443
444
445
446
447
448
449
450
451
452
453
454
    if (!deleted) {
        removeToplevel(toplevel);
        return;
    }

    Q_ASSERT(m_windows.contains(toplevel));
    Window *window = m_windows.take(toplevel);
    window->updateToplevel(deleted);
    if (window->shadow()) {
        window->shadow()->setToplevel(deleted);
    }
    m_windows[deleted] = window;
455
456
}

457
void Scene::createStackingOrder(const QList<Toplevel *> &toplevels)
458
459
460
{
    // TODO: cache the stacking_order in case it has not changed
    foreach (Toplevel *c, toplevels) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
461
        Q_ASSERT(m_windows.contains(c));
462
463
464
465
466
467
468
469
470
        stacking_order.append(m_windows[ c ]);
    }
}

void Scene::clearStackingOrder()
{
    stacking_order.clear();
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
471
static Scene::Window *s_recursionCheck = nullptr;
472

473
void Scene::paintWindow(Window* w, int mask, const QRegion &_region, const WindowQuadList &quads)
474
{
475
    // no painting outside visible screen (and no transformations)
476
    const QRegion region = _region & QRect({0, 0}, screens()->size());
477
    if (region.isEmpty())  // completely clipped
478
        return;
479
480
481
482
    if (w->window()->isDeleted() && w->window()->skipsCloseAnimation()) {
        // should not get painted
        return;
    }
483

484
485
486
487
    if (s_recursionCheck == w) {
        return;
    }

488
    WindowPaintData data(w->window()->effectWindow(), screenProjectionMatrix());
489
    data.quads = quads;
490
    effects->paintWindow(effectWindow(w), mask, region, data);
491
    // paint thumbnails on top of window
492
493
494
495
496
    paintWindowThumbnails(w, region, data.opacity(), data.brightness(), data.saturation());
    // and desktop thumbnails
    paintDesktopThumbnails(w);
}

497
498
499
500
501
502
503
504
505
506
507
508
509
510
static void adjustClipRegion(AbstractThumbnailItem *item, QRegion &clippingRegion)
{
    if (item->clip() && item->clipTo()) {
        // the x/y positions of the parent item are not correct. The margins are added, though the size seems fine
        // that's why we have to get the offset by inspecting the anchors properties
        QQuickItem *parentItem = item->clipTo();
        QPointF offset;
        QVariant anchors = parentItem->property("anchors");
        if (anchors.isValid()) {
            if (QObject *anchorsObject = anchors.value<QObject*>()) {
                offset.setX(anchorsObject->property("leftMargin").toReal());
                offset.setY(anchorsObject->property("topMargin").toReal());
            }
        }
511
512
513
514
        QRectF rect = QRectF(parentItem->position() - offset, QSizeF(parentItem->width(), parentItem->height()));
        if (QQuickItem *p = parentItem->parentItem()) {
            rect = p->mapRectToScene(rect);
        }
515
516
517
518
        clippingRegion &= rect.adjusted(0,0,-1,-1).translated(item->window()->position()).toRect();
    }
}

519
void Scene::paintWindowThumbnails(Scene::Window *w, const QRegion &region, qreal opacity, qreal brightness, qreal saturation)
520
{
521
    EffectWindowImpl *wImpl = static_cast<EffectWindowImpl*>(effectWindow(w));
522
    for (QHash<WindowThumbnailItem*, QPointer<EffectWindowImpl> >::const_iterator it = wImpl->thumbnails().constBegin();
523
524
525
526
527
            it != wImpl->thumbnails().constEnd();
            ++it) {
        if (it.value().isNull()) {
            continue;
        }
528
        WindowThumbnailItem *item = it.key();
529
530
531
532
        if (!item->isVisible()) {
            continue;
        }
        EffectWindowImpl *thumb = it.value().data();
533
        WindowPaintData thumbData(thumb, screenProjectionMatrix());
534
535
536
        thumbData.setOpacity(opacity);
        thumbData.setBrightness(brightness * item->brightness());
        thumbData.setSaturation(saturation * item->saturation());
537

538
539
540
        const QRect visualThumbRect(thumb->expandedGeometry());

        QSizeF size = QSizeF(visualThumbRect.size());
541
        size.scale(QSizeF(item->width(), item->height()), Qt::KeepAspectRatio);
542
543
        if (size.width() > visualThumbRect.width() || size.height() > visualThumbRect.height()) {
            size = QSizeF(visualThumbRect.size());
544
        }
Thomas Lübking's avatar
Thomas Lübking committed
545
546
        thumbData.setXScale(size.width() / static_cast<qreal>(visualThumbRect.width()));
        thumbData.setYScale(size.height() / static_cast<qreal>(visualThumbRect.height()));
547

548
        if (!item->window()) {
549
550
            continue;
        }
Marco Martin's avatar
Marco Martin committed
551
        const QPointF point = item->mapToScene(QPointF(0,0));
Thomas Lübking's avatar
Thomas Lübking committed
552
553
554
555
        qreal x = point.x() + w->x() + (item->width() - size.width())/2;
        qreal y = point.y() + w->y() + (item->height() - size.height()) / 2;
        x -= thumb->x();
        y -= thumb->y();
556
        // compensate shadow topleft padding
Thomas Lübking's avatar
Thomas Lübking committed
557
558
        x += (thumb->x()-visualThumbRect.x())*thumbData.xScale();
        y += (thumb->y()-visualThumbRect.y())*thumbData.yScale();
Thomas Lübking's avatar
Thomas Lübking committed
559
560
        thumbData.setXTranslation(x);
        thumbData.setYTranslation(y);
561
        int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS;
562
        if (thumbData.opacity() == 1.0) {
563
564
565
566
            thumbMask |= PAINT_WINDOW_OPAQUE;
        } else {
            thumbMask |= PAINT_WINDOW_TRANSLUCENT;
        }
567
568
        QRegion clippingRegion = region;
        clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height());
569
        adjustClipRegion(item, clippingRegion);
570
        effects->drawWindow(thumb, thumbMask, clippingRegion, thumbData);
571
    }
572
}
573

574
575
576
577
578
579
580
581
582
583
void Scene::paintDesktopThumbnails(Scene::Window *w)
{
    EffectWindowImpl *wImpl = static_cast<EffectWindowImpl*>(effectWindow(w));
    for (QList<DesktopThumbnailItem*>::const_iterator it = wImpl->desktopThumbnails().constBegin();
            it != wImpl->desktopThumbnails().constEnd();
            ++it) {
        DesktopThumbnailItem *item = *it;
        if (!item->isVisible()) {
            continue;
        }
584
        if (!item->window()) {
585
586
587
588
589
            continue;
        }
        s_recursionCheck = w;

        ScreenPaintData data;
590
591
        const QSize &screenSize = screens()->size();
        QSize size = screenSize;
592
593

        size.scale(item->width(), item->height(), Qt::KeepAspectRatio);
594
595
        data *= QVector2D(size.width() / double(screenSize.width()),
                          size.height() / double(screenSize.height()));
596
        const QPointF point = item->mapToScene(item->position());
597
598
599
600
601
        const qreal x = point.x() + w->x() + (item->width() - size.width())/2;
        const qreal y = point.y() + w->y() + (item->height() - size.height()) / 2;
        const QRect region = QRect(x, y, item->width(), item->height());
        QRegion clippingRegion = region;
        clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height());
602
        adjustClipRegion(item, clippingRegion);
603
604
605
        data += QPointF(x, y);
        const int desktopMask = PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST;
        paintDesktop(item->desktop(), desktopMask, clippingRegion, data);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
606
        s_recursionCheck = nullptr;
607
608
609
610
611
612
613
614
    }
}

void Scene::paintDesktop(int desktop, int mask, const QRegion &region, ScreenPaintData &data)
{
    static_cast<EffectsHandlerImpl*>(effects)->paintDesktop(desktop, mask, region, data);
}

615
// the function that'll be eventually called by paintWindow() above
616
void Scene::finalPaintWindow(EffectWindowImpl* w, int mask, const QRegion &region, WindowPaintData& data)
617
618
619
{
    effects->drawWindow(w, mask, region, data);
}
620
621

// will be eventually called from drawWindow()
622
void Scene::finalDrawWindow(EffectWindowImpl* w, int mask, const QRegion &region, WindowPaintData& data)
623
{
624
    if (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) {
625
626
        return;
    }
627
    w->sceneWindow()->performPaint(mask, region, data);
628
}
629

630
631
632
633
634
635
void Scene::extendPaintRegion(QRegion &region, bool opaqueFullscreen)
{
    Q_UNUSED(region);
    Q_UNUSED(opaqueFullscreen);
}

636
637
638
639
640
bool Scene::blocksForRetrace() const
{
    return false;
}

641
642
643
644
645
bool Scene::syncsToVBlank() const
{
    return false;
}

646
647
void Scene::screenGeometryChanged(const QSize &size)
{
648
649
650
    if (!overlayWindow()) {
        return;
    }
651
    overlayWindow()->resize(size);
652
653
}

654
655
656
657
658
659
660
661
662
bool Scene::makeOpenGLContextCurrent()
{
    return false;
}

void Scene::doneOpenGLContextCurrent()
{
}

663
664
665
666
void Scene::triggerFence()
{
}

667
668
669
670
671
QMatrix4x4 Scene::screenProjectionMatrix() const
{
    return QMatrix4x4();
}

672
673
674
675
676
xcb_render_picture_t Scene::xrenderBufferPicture() const
{
    return XCB_RENDER_PICTURE_NONE;
}

677
678
679
680
681
QPainter *Scene::scenePainter() const
{
    return nullptr;
}

682
683
684
685
686
QImage *Scene::qpainterRenderBuffer() const
{
    return nullptr;
}

687
688
689
690
691
QVector<QByteArray> Scene::openGLPlatformInterfaceExtensions() const
{
    return QVector<QByteArray>{};
}

692
693
694
695
//****************************************
// Scene::Window
//****************************************

696
697
698
Scene::Window::Window(Toplevel *client, QObject *parent)
    : QObject(parent)
    , toplevel(client)
699
    , filter(ImageFilterFast)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
700
    , m_shadow(nullptr)
701
702
703
    , m_currentPixmap()
    , m_previousPixmap()
    , m_referencePixmapCounter(0)
704
    , disable_painting(0)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
705
    , cached_quad_list(nullptr)
706
707
{
}
708
709

Scene::Window::~Window()
710
{
711
    delete m_shadow;
712
}
713

714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
void Scene::Window::referencePreviousPixmap()
{
    if (!m_previousPixmap.isNull() && m_previousPixmap->isDiscarded()) {
        m_referencePixmapCounter++;
    }
}

void Scene::Window::unreferencePreviousPixmap()
{
    if (m_previousPixmap.isNull() || !m_previousPixmap->isDiscarded()) {
        return;
    }
    m_referencePixmapCounter--;
    if (m_referencePixmapCounter == 0) {
        m_previousPixmap.reset();
    }
}

732
void Scene::Window::discardPixmap()
733
{
734
735
736
737
738
739
740
    if (!m_currentPixmap.isNull()) {
        if (m_currentPixmap->isValid()) {
            m_previousPixmap.reset(m_currentPixmap.take());
            m_previousPixmap->markAsDiscarded();
        } else {
            m_currentPixmap.reset();
        }
741
742
743
    }
}

744
745
746
747
748
void Scene::Window::updatePixmap()
{
    if (m_currentPixmap.isNull()) {
        m_currentPixmap.reset(createWindowPixmap());
    }
749
750
751
    if (m_currentPixmap->isValid()) {
        m_currentPixmap->update();
    } else {
752
753
754
755
        m_currentPixmap->create();
    }
}

756
void Scene::Window::discardShape()
757
{
758
759
    // it is created on-demand and cached, simply
    // reset the flag
760
    m_bufferShapeIsValid = false;
761
    discardQuads();
762
}
763

764
QRegion Scene::Window::bufferShape() const
765
{
766
767
    if (m_bufferShapeIsValid) {
        return m_bufferShape;
768
    }
769
770
771
772

    const QRect bufferGeometry = toplevel->bufferGeometry();

    if (toplevel->shape()) {
773
        auto cookie = xcb_shape_get_rectangles_unchecked(connection(), toplevel->frameId(), XCB_SHAPE_SK_BOUNDING);
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
        ScopedCPointer<xcb_shape_get_rectangles_reply_t> reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr));
        if (!reply.isNull()) {
            m_bufferShape = QRegion();
            const xcb_rectangle_t *rects = xcb_shape_get_rectangles_rectangles(reply.data());
            const int rectCount = xcb_shape_get_rectangles_rectangles_length(reply.data());
            for (int i = 0; i < rectCount; ++i) {
                m_bufferShape += QRegion(rects[i].x, rects[i].y, rects[i].width, rects[i].height);
            }
            // make sure the shape is sane (X is async, maybe even XShape is broken)
            m_bufferShape &= QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height());
        } else {
            m_bufferShape = QRegion();
        }
    } else {
        m_bufferShape = QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height());
    }

    m_bufferShapeIsValid = true;

    return m_bufferShape;
794
}
795

796
QRegion Scene::Window::clientShape() const
797
{
798
799
    if (isShaded())
        return QRegion();
800
801
802
803
804
805
806
807
808

    const QRegion shape = bufferShape();
    const QMargins bufferMargins = toplevel->bufferMargins();
    if (bufferMargins.isNull()) {
        return shape;
    }

    const QRect clippingRect = QRect(QPoint(0, 0), toplevel->bufferGeometry().size()) - toplevel->bufferMargins();
    return shape & clippingRect;
809
810
811
812
}

QRegion Scene::Window::decorationShape() const
{
813
    return QRegion(toplevel->rect()) - toplevel->transparentRect();
814
815
816
817
818
819
820
}

QPoint Scene::Window::bufferOffset() const
{
    const QRect bufferGeometry = toplevel->bufferGeometry();
    const QRect frameGeometry = toplevel->frameGeometry();
    return bufferGeometry.topLeft() - frameGeometry.topLeft();
821
}
822

823
bool Scene::Window::isVisible() const
824
{
825
    if (toplevel->isDeleted())
826
        return false;
827
    if (!toplevel->isOnCurrentDesktop())
828
        return false;
829
    if (!toplevel->isOnCurrentActivity())
830
        return false;
831
832
    if (AbstractClient *c = dynamic_cast<AbstractClient*>(toplevel))
        return c->isShown(true);
833
    return true; // Unmanaged is always visible
834
}
835
836

bool Scene::Window::isOpaque() const
837
{
838
    return toplevel->opacity() == 1.0 && !toplevel->hasAlpha();
839
}
840

841
842
843
844
845
846
847
bool Scene::Window::isShaded() const
{
    if (AbstractClient *client = qobject_cast<AbstractClient *>(toplevel))
        return client->isShade();
    return false;
}

848
bool Scene::Window::isPaintingEnabled() const
849
{
850
    return !disable_painting;
851
}
852
853

void Scene::Window::resetPaintingEnabled()
854
{
855
    disable_painting = 0;
856
    if (toplevel->isDeleted())
857
        disable_painting |= PAINT_DISABLED_BY_DELETE;
858
859
860
861
862
863
864
865
    if (static_cast<EffectsHandlerImpl*>(effects)->isDesktopRendering()) {
        if (!toplevel->isOnDesktop(static_cast<EffectsHandlerImpl*>(effects)->currentRenderedDesktop())) {
            disable_painting |= PAINT_DISABLED_BY_DESKTOP;
        }
    } else {
        if (!toplevel->isOnCurrentDesktop())
            disable_painting |= PAINT_DISABLED_BY_DESKTOP;
    }
866
    if (!toplevel->isOnCurrentActivity())
867
        disable_painting |= PAINT_DISABLED_BY_ACTIVITY;
868
    if (AbstractClient *c = dynamic_cast<AbstractClient*>(toplevel)) {
869
        if (c->isMinimized())
870
            disable_painting |= PAINT_DISABLED_BY_MINIMIZE;
871
872
        if (c->isHiddenInternal()) {
            disable_painting |= PAINT_DISABLED;
873
        }
874
    }
875
}
876

877
878
void Scene::Window::enablePainting(int reason)
{
879
    disable_painting &= ~reason;
880
}
881

882
883
void Scene::Window::disablePainting(int reason)
{
884
    disable_painting |= reason;
885
}
886

887
888
WindowQuadList Scene::Window::buildQuads(bool force) const
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
889
    if (cached_quad_list != nullptr && !force)
890
        return *cached_quad_list;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
891

892
893
894
895
896
    WindowQuadList ret;

    if (!isShaded()) {
        ret += makeContentsQuads();
    }
897

898
    if (!toplevel->frameMargins().isNull()) {
899
        AbstractClient *client = dynamic_cast<AbstractClient*>(toplevel);
Martin Flöser's avatar
Martin Flöser committed
900
        QRegion center = toplevel->transparentRect();
901
        const QRegion decoration = decorationShape();
902
        qreal decorationScale = 1.0;
903

904
        QRect rects[4];
Thomas Lübking's avatar
Thomas Lübking committed
905
        bool isShadedClient = false;
906

Thomas Lübking's avatar
Thomas Lübking committed
907
        if (client) {