1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtWidgets module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 
41 #include "qplatformdefs.h"
42 
43 #include "qwidgetrepaintmanager_p.h"
44 
45 #include <QtCore/qglobal.h>
46 #include <QtCore/qdebug.h>
47 #include <QtCore/qvarlengtharray.h>
48 #include <QtGui/qevent.h>
49 #include <QtWidgets/qapplication.h>
50 #include <QtGui/qpaintengine.h>
51 #if QT_CONFIG(graphicsview)
52 #include <QtWidgets/qgraphicsproxywidget.h>
53 #endif
54 
55 #include <private/qwidget_p.h>
56 #include <private/qapplication_p.h>
57 #include <private/qpaintengine_raster_p.h>
58 #if QT_CONFIG(graphicseffect)
59 #include <private/qgraphicseffect_p.h>
60 #endif
61 #include <QtGui/private/qwindow_p.h>
62 #include <QtGui/private/qhighdpiscaling_p.h>
63 
64 #include <qpa/qplatformbackingstore.h>
65 
66 #include <private/qmemory_p.h>
67 
68 QT_BEGIN_NAMESPACE
69 
70 #ifndef QT_NO_OPENGL
71 Q_GLOBAL_STATIC(QPlatformTextureList, qt_dummy_platformTextureList)
72 
73 // Watches one or more QPlatformTextureLists for changes in the lock state and
74 // triggers a backingstore sync when all the registered lists turn into
75 // unlocked state. This is essential when a custom composeAndFlush()
76 // implementation in a platform plugin is not synchronous and keeps
77 // holding on to the textures for some time even after returning from there.
78 class QPlatformTextureListWatcher : public QObject
79 {
80     Q_OBJECT
81 public:
QPlatformTextureListWatcher(QWidgetRepaintManager * repaintManager)82     QPlatformTextureListWatcher(QWidgetRepaintManager *repaintManager)
83         : m_repaintManager(repaintManager) {}
84 
watch(QPlatformTextureList * textureList)85     void watch(QPlatformTextureList *textureList) {
86         connect(textureList, SIGNAL(locked(bool)), SLOT(onLockStatusChanged(bool)));
87         m_locked[textureList] = textureList->isLocked();
88     }
89 
isLocked() const90     bool isLocked() const {
91         foreach (bool v, m_locked) {
92             if (v)
93                 return true;
94         }
95         return false;
96     }
97 
98 private slots:
onLockStatusChanged(bool locked)99      void onLockStatusChanged(bool locked) {
100         QPlatformTextureList *tl = static_cast<QPlatformTextureList *>(sender());
101         m_locked[tl] = locked;
102         if (!isLocked())
103             m_repaintManager->sync();
104      }
105 
106 private:
107      QHash<QPlatformTextureList *, bool> m_locked;
108      QWidgetRepaintManager *m_repaintManager;
109 };
110 #endif
111 
112 // ---------------------------------------------------------------------------
113 
QWidgetRepaintManager(QWidget * topLevel)114 QWidgetRepaintManager::QWidgetRepaintManager(QWidget *topLevel)
115     : tlw(topLevel), store(tlw->backingStore())
116 {
117     Q_ASSERT(store);
118 
119     // Ensure all existing subsurfaces and static widgets are added to their respective lists.
120     updateLists(topLevel);
121 }
122 
updateLists(QWidget * cur)123 void QWidgetRepaintManager::updateLists(QWidget *cur)
124 {
125     if (!cur)
126         return;
127 
128     QList<QObject*> children = cur->children();
129     for (int i = 0; i < children.size(); ++i) {
130         QWidget *child = qobject_cast<QWidget*>(children.at(i));
131         if (!child || child->isWindow())
132             continue;
133 
134         updateLists(child);
135     }
136 
137     if (cur->testAttribute(Qt::WA_StaticContents))
138         addStaticWidget(cur);
139 }
140 
~QWidgetRepaintManager()141 QWidgetRepaintManager::~QWidgetRepaintManager()
142 {
143     for (int c = 0; c < dirtyWidgets.size(); ++c)
144         resetWidget(dirtyWidgets.at(c));
145     for (int c = 0; c < dirtyRenderToTextureWidgets.size(); ++c)
146         resetWidget(dirtyRenderToTextureWidgets.at(c));
147 }
148 
149 /*!
150     \internal
151     Invalidates the \a r (in widget's coordinates) of the backing store, i.e.
152     all widgets intersecting with the region will be repainted when the backing
153     store is synced.
154 */
155 template <class T>
invalidateBackingStore(const T & r)156 void QWidgetPrivate::invalidateBackingStore(const T &r)
157 {
158     if (r.isEmpty())
159         return;
160 
161     if (QCoreApplication::closingDown())
162         return;
163 
164     Q_Q(QWidget);
165     if (!q->isVisible() || !q->updatesEnabled())
166         return;
167 
168     QTLWExtra *tlwExtra = q->window()->d_func()->maybeTopData();
169     if (!tlwExtra || !tlwExtra->backingStore)
170         return;
171 
172     T clipped(r);
173     clipped &= clipRect();
174     if (clipped.isEmpty())
175         return;
176 
177     if (!graphicsEffect && extra && extra->hasMask) {
178         QRegion masked(extra->mask);
179         masked &= clipped;
180         if (masked.isEmpty())
181             return;
182 
183         tlwExtra->repaintManager->markDirty(masked, q,
184             QWidgetRepaintManager::UpdateLater, QWidgetRepaintManager::BufferInvalid);
185     } else {
186         tlwExtra->repaintManager->markDirty(clipped, q,
187             QWidgetRepaintManager::UpdateLater, QWidgetRepaintManager::BufferInvalid);
188     }
189 }
190 // Needed by tst_QWidget
191 template Q_AUTOTEST_EXPORT void QWidgetPrivate::invalidateBackingStore<QRect>(const QRect &r);
192 
widgetRectFor(QWidget *,const QRect & r)193 static inline QRect widgetRectFor(QWidget *, const QRect &r) { return r; }
widgetRectFor(QWidget * widget,const QRegion &)194 static inline QRect widgetRectFor(QWidget *widget, const QRegion &) { return widget->rect(); }
195 
196 /*!
197     \internal
198     Marks the region of the widget as dirty (if not already marked as dirty) and
199     posts an UpdateRequest event to the top-level widget (if not already posted).
200 
201     If updateTime is UpdateNow, the event is sent immediately instead of posted.
202 
203     If bufferState is BufferInvalid, all widgets intersecting with the region will be dirty.
204 
205     If the widget paints directly on screen, the event is sent to the widget
206     instead of the top-level widget, and bufferState is completely ignored.
207 */
208 template <class T>
markDirty(const T & r,QWidget * widget,UpdateTime updateTime,BufferState bufferState)209 void QWidgetRepaintManager::markDirty(const T &r, QWidget *widget, UpdateTime updateTime, BufferState bufferState)
210 {
211     qCInfo(lcWidgetPainting) << "Marking" << r << "of" << widget << "dirty"
212         << "with" << updateTime;
213 
214     Q_ASSERT(tlw->d_func()->extra);
215     Q_ASSERT(tlw->d_func()->extra->topextra);
216     Q_ASSERT(widget->isVisible() && widget->updatesEnabled());
217     Q_ASSERT(widget->window() == tlw);
218     Q_ASSERT(!r.isEmpty());
219 
220 #if QT_CONFIG(graphicseffect)
221     widget->d_func()->invalidateGraphicsEffectsRecursively();
222 #endif
223 
224     QRect widgetRect = widgetRectFor(widget, r);
225 
226     // ---------------------------------------------------------------------------
227 
228     if (widget->d_func()->shouldPaintOnScreen()) {
229         if (widget->d_func()->dirty.isEmpty()) {
230             widget->d_func()->dirty = r;
231             sendUpdateRequest(widget, updateTime);
232             return;
233         } else if (qt_region_strictContains(widget->d_func()->dirty, widgetRect)) {
234             if (updateTime == UpdateNow)
235                 sendUpdateRequest(widget, updateTime);
236             return; // Already dirty
237         }
238 
239         const bool eventAlreadyPosted = !widget->d_func()->dirty.isEmpty();
240         widget->d_func()->dirty += r;
241         if (!eventAlreadyPosted || updateTime == UpdateNow)
242             sendUpdateRequest(widget, updateTime);
243         return;
244     }
245 
246     // ---------------------------------------------------------------------------
247 
248     if (QWidgetPrivate::get(widget)->renderToTexture) {
249         if (!widget->d_func()->inDirtyList)
250             addDirtyRenderToTextureWidget(widget);
251         if (!updateRequestSent || updateTime == UpdateNow)
252             sendUpdateRequest(tlw, updateTime);
253         return;
254     }
255 
256     // ---------------------------------------------------------------------------
257 
258     QRect effectiveWidgetRect = widget->d_func()->effectiveRectFor(widgetRect);
259     const QPoint offset = widget->mapTo(tlw, QPoint());
260     QRect translatedRect = effectiveWidgetRect.translated(offset);
261 #if QT_CONFIG(graphicseffect)
262     // Graphics effects may exceed window size, clamp
263     translatedRect = translatedRect.intersected(QRect(QPoint(), tlw->size()));
264 #endif
265     if (qt_region_strictContains(dirty, translatedRect)) {
266         if (updateTime == UpdateNow)
267             sendUpdateRequest(tlw, updateTime);
268         return; // Already dirty
269     }
270 
271     // ---------------------------------------------------------------------------
272 
273     if (bufferState == BufferInvalid) {
274         const bool eventAlreadyPosted = !dirty.isEmpty() || updateRequestSent;
275 #if QT_CONFIG(graphicseffect)
276         if (widget->d_func()->graphicsEffect)
277             dirty += widget->d_func()->effectiveRectFor(r).translated(offset);
278         else
279 #endif
280             dirty += r.translated(offset);
281 
282         if (!eventAlreadyPosted || updateTime == UpdateNow)
283             sendUpdateRequest(tlw, updateTime);
284         return;
285     }
286 
287     // ---------------------------------------------------------------------------
288 
289     if (dirtyWidgets.isEmpty()) {
290         addDirtyWidget(widget, r);
291         sendUpdateRequest(tlw, updateTime);
292         return;
293     }
294 
295     // ---------------------------------------------------------------------------
296 
297     if (widget->d_func()->inDirtyList) {
298         if (!qt_region_strictContains(widget->d_func()->dirty, effectiveWidgetRect)) {
299 #if QT_CONFIG(graphicseffect)
300             if (widget->d_func()->graphicsEffect)
301                 widget->d_func()->dirty += widget->d_func()->effectiveRectFor(r);
302             else
303 #endif
304                 widget->d_func()->dirty += r;
305         }
306     } else {
307         addDirtyWidget(widget, r);
308     }
309 
310     // ---------------------------------------------------------------------------
311 
312     if (updateTime == UpdateNow)
313         sendUpdateRequest(tlw, updateTime);
314 }
315 template void QWidgetRepaintManager::markDirty<QRect>(const QRect &, QWidget *, UpdateTime, BufferState);
316 template void QWidgetRepaintManager::markDirty<QRegion>(const QRegion &, QWidget *, UpdateTime, BufferState);
317 
addDirtyWidget(QWidget * widget,const QRegion & rgn)318 void QWidgetRepaintManager::addDirtyWidget(QWidget *widget, const QRegion &rgn)
319 {
320     if (widget && !widget->d_func()->inDirtyList && !widget->data->in_destructor) {
321         QWidgetPrivate *widgetPrivate = widget->d_func();
322 #if QT_CONFIG(graphicseffect)
323         if (widgetPrivate->graphicsEffect)
324             widgetPrivate->dirty = widgetPrivate->effectiveRectFor(rgn.boundingRect());
325         else
326 #endif // QT_CONFIG(graphicseffect)
327             widgetPrivate->dirty = rgn;
328         dirtyWidgets.append(widget);
329         widgetPrivate->inDirtyList = true;
330     }
331 }
332 
removeDirtyWidget(QWidget * w)333 void QWidgetRepaintManager::removeDirtyWidget(QWidget *w)
334 {
335     if (!w)
336         return;
337 
338     dirtyWidgets.removeAll(w);
339     dirtyRenderToTextureWidgets.removeAll(w);
340     resetWidget(w);
341 
342     needsFlushWidgets.removeAll(w);
343 
344     QWidgetPrivate *wd = w->d_func();
345     const int n = wd->children.count();
346     for (int i = 0; i < n; ++i) {
347         if (QWidget *child = qobject_cast<QWidget*>(wd->children.at(i)))
348             removeDirtyWidget(child);
349     }
350 }
351 
resetWidget(QWidget * widget)352 void QWidgetRepaintManager::resetWidget(QWidget *widget)
353 {
354     if (widget) {
355         widget->d_func()->inDirtyList = false;
356         widget->d_func()->isScrolled = false;
357         widget->d_func()->isMoved = false;
358         widget->d_func()->dirty = QRegion();
359     }
360 }
361 
addDirtyRenderToTextureWidget(QWidget * widget)362 void QWidgetRepaintManager::addDirtyRenderToTextureWidget(QWidget *widget)
363 {
364     if (widget && !widget->d_func()->inDirtyList && !widget->data->in_destructor) {
365         QWidgetPrivate *widgetPrivate = widget->d_func();
366         Q_ASSERT(widgetPrivate->renderToTexture);
367         dirtyRenderToTextureWidgets.append(widget);
368         widgetPrivate->inDirtyList = true;
369     }
370 }
371 
sendUpdateRequest(QWidget * widget,UpdateTime updateTime)372 void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime updateTime)
373 {
374     if (!widget)
375         return;
376 
377     qCInfo(lcWidgetPainting) << "Sending update request to" << widget << "with" << updateTime;
378 
379 #ifndef QT_NO_OPENGL
380     // Having every repaint() leading to a sync/flush is bad as it causes
381     // compositing and waiting for vsync each and every time. Change to
382     // UpdateLater, except for approx. once per frame to prevent starvation in
383     // case the control does not get back to the event loop.
384     QWidget *w = widget->window();
385     if (updateTime == UpdateNow && w && w->windowHandle() && QWindowPrivate::get(w->windowHandle())->compositing) {
386         int refresh = 60;
387         QScreen *ws = w->windowHandle()->screen();
388         if (ws)
389             refresh = ws->refreshRate();
390         QWindowPrivate *wd = QWindowPrivate::get(w->windowHandle());
391         if (wd->lastComposeTime.isValid()) {
392             const qint64 elapsed = wd->lastComposeTime.elapsed();
393             if (elapsed <= qint64(1000.0f / refresh))
394                 updateTime = UpdateLater;
395        }
396     }
397 #endif
398 
399     switch (updateTime) {
400     case UpdateLater:
401         updateRequestSent = true;
402         QCoreApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority);
403         break;
404     case UpdateNow: {
405         QEvent event(QEvent::UpdateRequest);
406         QCoreApplication::sendEvent(widget, &event);
407         break;
408         }
409     }
410 }
411 
412 // ---------------------------------------------------------------------------
413 
hasPlatformWindow(QWidget * widget)414 static bool hasPlatformWindow(QWidget *widget)
415 {
416     return widget && widget->windowHandle() && widget->windowHandle()->handle();
417 }
418 
getSortedRectsToScroll(const QRegion & region,int dx,int dy)419 static QVector<QRect> getSortedRectsToScroll(const QRegion &region, int dx, int dy)
420 {
421     QVector<QRect> rects;
422     std::copy(region.begin(), region.end(), std::back_inserter(rects));
423     if (rects.count() > 1) {
424         std::sort(rects.begin(), rects.end(), [=](const QRect &r1, const QRect &r2) {
425             if (r1.y() == r2.y()) {
426                 if (dx > 0)
427                     return r1.x() > r2.x();
428                 return r1.x() < r2.x();
429             }
430             if (dy > 0)
431                 return r1.y() > r2.y();
432             return r1.y() < r2.y();
433         });
434     }
435     return rects;
436 }
437 
438 //parent's coordinates; move whole rect; update parent and widget
439 //assume the screen blt has already been done, so we don't need to refresh that part
moveRect(const QRect & rect,int dx,int dy)440 void QWidgetPrivate::moveRect(const QRect &rect, int dx, int dy)
441 {
442     Q_Q(QWidget);
443     if (!q->isVisible() || (dx == 0 && dy == 0))
444         return;
445 
446     QWidget *tlw = q->window();
447     QTLWExtra* x = tlw->d_func()->topData();
448 
449     static const bool accelEnv = qEnvironmentVariableIntValue("QT_NO_FAST_MOVE") == 0;
450 
451     QWidget *pw = q->parentWidget();
452     QPoint toplevelOffset = pw->mapTo(tlw, QPoint());
453     QWidgetPrivate *pd = pw->d_func();
454     QRect clipR(pd->clipRect());
455     const QRect newRect(rect.translated(dx, dy));
456     QRect destRect = rect.intersected(clipR);
457     if (destRect.isValid())
458         destRect = destRect.translated(dx, dy).intersected(clipR);
459     const QRect sourceRect(destRect.translated(-dx, -dy));
460     const QRect parentRect(rect & clipR);
461     const bool nativeWithTextureChild = textureChildSeen && hasPlatformWindow(q);
462 
463     const bool accelerateMove = accelEnv && isOpaque && !nativeWithTextureChild
464 #if QT_CONFIG(graphicsview)
465                           // No accelerate move for proxy widgets.
466                           && !tlw->d_func()->extra->proxyWidget
467 #endif
468             ;
469 
470     if (!accelerateMove) {
471         QRegion parentR(effectiveRectFor(parentRect));
472         if (!extra || !extra->hasMask) {
473             parentR -= newRect;
474         } else {
475             // invalidateBackingStore() excludes anything outside the mask
476             parentR += newRect & clipR;
477         }
478         pd->invalidateBackingStore(parentR);
479         invalidateBackingStore((newRect & clipR).translated(-data.crect.topLeft()));
480     } else {
481 
482         QWidgetRepaintManager *repaintManager = x->repaintManager.get();
483         QRegion childExpose(newRect & clipR);
484         QRegion overlappedExpose;
485 
486         if (sourceRect.isValid()) {
487             overlappedExpose = (overlappedRegion(sourceRect) | overlappedRegion(destRect)) & clipR;
488 
489             const qreal factor = QHighDpiScaling::factor(q->windowHandle());
490             if (overlappedExpose.isEmpty() || qFloor(factor) == factor) {
491                 const QVector<QRect> rectsToScroll
492                         = getSortedRectsToScroll(QRegion(sourceRect) - overlappedExpose, dx, dy);
493                 for (QRect rect : rectsToScroll) {
494                     if (repaintManager->bltRect(rect, dx, dy, pw)) {
495                         childExpose -= rect.translated(dx, dy);
496                     }
497                 }
498             }
499 
500             childExpose -= overlappedExpose;
501         }
502 
503         if (!pw->updatesEnabled())
504             return;
505 
506         const bool childUpdatesEnabled = q->updatesEnabled();
507         if (childUpdatesEnabled) {
508             if (!overlappedExpose.isEmpty()) {
509                 overlappedExpose.translate(-data.crect.topLeft());
510                 invalidateBackingStore(overlappedExpose);
511             }
512             if (!childExpose.isEmpty()) {
513                 childExpose.translate(-data.crect.topLeft());
514                 repaintManager->markDirty(childExpose, q);
515                 isMoved = true;
516             }
517         }
518 
519         QRegion parentExpose(parentRect);
520         parentExpose -= newRect;
521         if (extra && extra->hasMask)
522             parentExpose += QRegion(newRect) - extra->mask.translated(data.crect.topLeft());
523 
524         if (!parentExpose.isEmpty()) {
525             repaintManager->markDirty(parentExpose, pw);
526             pd->isMoved = true;
527         }
528 
529         if (childUpdatesEnabled) {
530             QRegion needsFlush(sourceRect);
531             needsFlush += destRect;
532             repaintManager->markNeedsFlush(pw, needsFlush, toplevelOffset);
533         }
534     }
535 }
536 
537 //widget's coordinates; scroll within rect;  only update widget
scrollRect(const QRect & rect,int dx,int dy)538 void QWidgetPrivate::scrollRect(const QRect &rect, int dx, int dy)
539 {
540     Q_Q(QWidget);
541     QWidget *tlw = q->window();
542     QTLWExtra* x = tlw->d_func()->topData();
543 
544     QWidgetRepaintManager *repaintManager = x->repaintManager.get();
545     if (!repaintManager)
546         return;
547 
548     static const bool accelEnv = qEnvironmentVariableIntValue("QT_NO_FAST_SCROLL") == 0;
549 
550     const QRect clipR = clipRect();
551     const QRect scrollRect = rect & clipR;
552     const bool accelerateScroll = accelEnv && isOpaque && !q_func()->testAttribute(Qt::WA_WState_InPaintEvent);
553 
554     if (!accelerateScroll) {
555         if (!overlappedRegion(scrollRect.translated(data.crect.topLeft()), true).isEmpty()) {
556             QRegion region(scrollRect);
557             subtractOpaqueSiblings(region);
558             invalidateBackingStore(region);
559         }else {
560             invalidateBackingStore(scrollRect);
561         }
562     } else {
563         const QPoint toplevelOffset = q->mapTo(tlw, QPoint());
564         const QRect destRect = scrollRect.translated(dx, dy) & scrollRect;
565         const QRect sourceRect = destRect.translated(-dx, -dy);
566 
567         const QRegion overlappedExpose = (overlappedRegion(scrollRect.translated(data.crect.topLeft())))
568                 .translated(-data.crect.topLeft()) & clipR;
569         QRegion childExpose(scrollRect);
570 
571         const qreal factor = QHighDpiScaling::factor(q->windowHandle());
572         if (overlappedExpose.isEmpty() || qFloor(factor) == factor) {
573             const QVector<QRect> rectsToScroll
574                     = getSortedRectsToScroll(QRegion(sourceRect) - overlappedExpose, dx, dy);
575             for (const QRect &rect : rectsToScroll) {
576                 if (repaintManager->bltRect(rect, dx, dy, q)) {
577                     childExpose -= rect.translated(dx, dy);
578                 }
579             }
580         }
581 
582         childExpose -= overlappedExpose;
583 
584         if (inDirtyList) {
585             if (rect == q->rect()) {
586                 dirty.translate(dx, dy);
587             } else {
588                 QRegion dirtyScrollRegion = dirty.intersected(scrollRect);
589                 if (!dirtyScrollRegion.isEmpty()) {
590                     dirty -= dirtyScrollRegion;
591                     dirtyScrollRegion.translate(dx, dy);
592                     dirty += dirtyScrollRegion;
593                 }
594             }
595         }
596 
597         if (!q->updatesEnabled())
598             return;
599 
600         if (!overlappedExpose.isEmpty())
601             invalidateBackingStore(overlappedExpose);
602         if (!childExpose.isEmpty()) {
603             repaintManager->markDirty(childExpose, q);
604             isScrolled = true;
605         }
606 
607         // Instead of using native scroll-on-screen, we copy from
608         // backingstore, giving only one screen update for each
609         // scroll, and a solid appearance
610         repaintManager->markNeedsFlush(q, destRect, toplevelOffset);
611     }
612 }
613 
614 /*
615     Moves the whole rect by (dx, dy) in widget's coordinate system.
616     Doesn't generate any updates.
617 */
bltRect(const QRect & rect,int dx,int dy,QWidget * widget)618 bool QWidgetRepaintManager::bltRect(const QRect &rect, int dx, int dy, QWidget *widget)
619 {
620     const QPoint pos(widget->mapTo(tlw, rect.topLeft()));
621     const QRect tlwRect(QRect(pos, rect.size()));
622     if (dirty.intersects(tlwRect))
623         return false; // We don't want to scroll junk.
624     return store->scroll(tlwRect, dx, dy);
625 }
626 
627 // ---------------------------------------------------------------------------
628 
629 #ifndef QT_NO_OPENGL
findTextureWidgetsRecursively(QWidget * tlw,QWidget * widget,QPlatformTextureList * widgetTextures,QVector<QWidget * > * nativeChildren)630 static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatformTextureList *widgetTextures, QVector<QWidget *> *nativeChildren)
631 {
632     QWidgetPrivate *wd = QWidgetPrivate::get(widget);
633     if (wd->renderToTexture) {
634         QPlatformTextureList::Flags flags = wd->textureListFlags();
635         const QRect rect(widget->mapTo(tlw, QPoint()), widget->size());
636         widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags);
637     }
638 
639     for (int i = 0; i < wd->children.size(); ++i) {
640         QWidget *w = qobject_cast<QWidget *>(wd->children.at(i));
641         // Stop at native widgets but store them. Stop at hidden widgets too.
642         if (w && !w->isWindow() && hasPlatformWindow(w))
643             nativeChildren->append(w);
644         if (w && !w->isWindow() && !hasPlatformWindow(w) && !w->isHidden() && QWidgetPrivate::get(w)->textureChildSeen)
645             findTextureWidgetsRecursively(tlw, w, widgetTextures, nativeChildren);
646     }
647 }
648 
findAllTextureWidgetsRecursively(QWidget * tlw,QWidget * widget)649 static void findAllTextureWidgetsRecursively(QWidget *tlw, QWidget *widget)
650 {
651     // textureChildSeen does not take native child widgets into account and that's good.
652     if (QWidgetPrivate::get(widget)->textureChildSeen) {
653         QVector<QWidget *> nativeChildren;
654         auto tl = qt_make_unique<QPlatformTextureList>();
655         // Look for texture widgets (incl. widget itself) from 'widget' down,
656         // but skip subtrees with a parent of a native child widget.
657         findTextureWidgetsRecursively(tlw, widget, tl.get(), &nativeChildren);
658         // tl may be empty regardless of textureChildSeen if we have native or hidden children.
659         if (!tl->isEmpty())
660             QWidgetPrivate::get(tlw)->topData()->widgetTextures.push_back(std::move(tl));
661         // Native child widgets, if there was any, get their own separate QPlatformTextureList.
662         for (QWidget *ncw : qAsConst(nativeChildren)) {
663             if (QWidgetPrivate::get(ncw)->textureChildSeen)
664                 findAllTextureWidgetsRecursively(tlw, ncw);
665         }
666     }
667 }
668 
widgetTexturesFor(QWidget * tlw,QWidget * widget)669 static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget)
670 {
671     for (const auto &tl : QWidgetPrivate::get(tlw)->topData()->widgetTextures) {
672         Q_ASSERT(!tl->isEmpty());
673         for (int i = 0; i < tl->count(); ++i) {
674             QWidget *w = static_cast<QWidget *>(tl->source(i));
675             if ((hasPlatformWindow(w) && w == widget) || (!hasPlatformWindow(w) && w->nativeParentWidget() == widget))
676                 return tl.get();
677         }
678     }
679 
680     if (QWidgetPrivate::get(widget)->textureChildSeen) {
681         // No render-to-texture widgets in the (sub-)tree due to hidden or native
682         // children. Returning null results in using the normal backingstore flush path
683         // without OpenGL-based compositing. This is very desirable normally. However,
684         // some platforms cannot handle switching between the non-GL and GL paths for
685         // their windows so it has to be opt-in.
686         static bool switchableWidgetComposition =
687             QGuiApplicationPrivate::instance()->platformIntegration()
688                 ->hasCapability(QPlatformIntegration::SwitchableWidgetComposition);
689         if (!switchableWidgetComposition)
690             return qt_dummy_platformTextureList();
691     }
692 
693     return nullptr;
694 }
695 
696 #else
697 
widgetTexturesFor(QWidget * tlw,QWidget * widget)698 static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget)
699 {
700     Q_UNUSED(tlw);
701     Q_UNUSED(widget);
702     return nullptr;
703 }
704 
705 #endif // QT_NO_OPENGL
706 
707 // ---------------------------------------------------------------------------
708 
709 /*!
710     Synchronizes the \a exposedRegion of the \a exposedWidget with the backing store.
711 
712     If there are dirty widgets, including but not limited to the \a exposedWidget,
713     these will be repainted first. The backingstore is then flushed to the screen,
714     regardless of whether or not there were any repaints.
715 */
sync(QWidget * exposedWidget,const QRegion & exposedRegion)716 void QWidgetRepaintManager::sync(QWidget *exposedWidget, const QRegion &exposedRegion)
717 {
718     qCInfo(lcWidgetPainting) << "Syncing" << exposedRegion << "of" << exposedWidget;
719 
720     if (!tlw->isVisible())
721         return;
722 
723     if (!exposedWidget || !hasPlatformWindow(exposedWidget)
724         || !exposedWidget->isVisible() || !exposedWidget->testAttribute(Qt::WA_Mapped)
725         || !exposedWidget->updatesEnabled() || exposedRegion.isEmpty()) {
726         return;
727     }
728 
729     // Nothing to repaint.
730     if (!isDirty() && store->size().isValid()) {
731         QPlatformTextureList *widgetTextures = widgetTexturesFor(tlw, exposedWidget);
732         flush(exposedWidget, widgetTextures ? QRegion() : exposedRegion, widgetTextures);
733         return;
734     }
735 
736     // As requests to sync a specific widget typically comes from an expose event
737     // we can't rely solely on our own dirty tracking to decide what to flush, and
738     // need to respect the platform's request to at least flush the entire widget,
739     QPoint offset = exposedWidget != tlw ? exposedWidget->mapTo(tlw, QPoint()) : QPoint();
740     markNeedsFlush(exposedWidget, exposedRegion, offset);
741 
742     if (syncAllowed())
743         paintAndFlush();
744 }
745 
746 /*!
747     Synchronizes the backing store, i.e. dirty areas are repainted and flushed.
748 */
sync()749 void QWidgetRepaintManager::sync()
750 {
751     qCInfo(lcWidgetPainting) << "Syncing dirty widgets";
752 
753     updateRequestSent = false;
754     if (qt_widget_private(tlw)->shouldDiscardSyncRequest()) {
755         // If the top-level is minimized, it's not visible on the screen so we can delay the
756         // update until it's shown again. In order to do that we must keep the dirty states.
757         // These will be cleared when we receive the first expose after showNormal().
758         // However, if the widget is not visible (isVisible() returns false), everything will
759         // be invalidated once the widget is shown again, so clear all dirty states.
760         if (!tlw->isVisible()) {
761             dirty = QRegion();
762             for (int i = 0; i < dirtyWidgets.size(); ++i)
763                 resetWidget(dirtyWidgets.at(i));
764             dirtyWidgets.clear();
765         }
766         return;
767     }
768 
769     if (syncAllowed())
770         paintAndFlush();
771 }
772 
shouldDiscardSyncRequest() const773 bool QWidgetPrivate::shouldDiscardSyncRequest() const
774 {
775     Q_Q(const QWidget);
776     return !maybeTopData() || !q->testAttribute(Qt::WA_Mapped) || !q->isVisible();
777 }
778 
syncAllowed()779 bool QWidgetRepaintManager::syncAllowed()
780 {
781 #ifndef QT_NO_OPENGL
782     QTLWExtra *tlwExtra = tlw->d_func()->maybeTopData();
783     if (textureListWatcher && !textureListWatcher->isLocked()) {
784         textureListWatcher->deleteLater();
785         textureListWatcher = nullptr;
786     } else if (!tlwExtra->widgetTextures.empty()) {
787         bool skipSync = false;
788         for (const auto &tl : tlwExtra->widgetTextures) {
789             if (tl->isLocked()) {
790                 if (!textureListWatcher)
791                     textureListWatcher = new QPlatformTextureListWatcher(this);
792                 if (!textureListWatcher->isLocked())
793                     textureListWatcher->watch(tl.get());
794                 skipSync = true;
795             }
796         }
797         if (skipSync)  // cannot compose due to widget textures being in use
798             return false;
799     }
800 #endif
801     return true;
802 }
803 
paintAndFlush()804 void QWidgetRepaintManager::paintAndFlush()
805 {
806     qCInfo(lcWidgetPainting) << "Painting and flushing dirty"
807         << "top level" << dirty << "and dirty widgets" << dirtyWidgets;
808 
809     const bool updatesDisabled = !tlw->updatesEnabled();
810     bool repaintAllWidgets = false;
811 
812     const QRect tlwRect = tlw->data->crect;
813     if (!updatesDisabled && store->size() != tlwRect.size()) {
814         if (hasStaticContents() && !store->size().isEmpty() ) {
815             // Repaint existing dirty area and newly visible area.
816             const QRect clipRect(QPoint(0, 0), store->size());
817             const QRegion staticRegion(staticContents(nullptr, clipRect));
818             QRegion newVisible(0, 0, tlwRect.width(), tlwRect.height());
819             newVisible -= staticRegion;
820             dirty += newVisible;
821             store->setStaticContents(staticRegion);
822         } else {
823             // Repaint everything.
824             dirty = QRegion(0, 0, tlwRect.width(), tlwRect.height());
825             for (int i = 0; i < dirtyWidgets.size(); ++i)
826                 resetWidget(dirtyWidgets.at(i));
827             dirtyWidgets.clear();
828             repaintAllWidgets = true;
829         }
830     }
831 
832     if (store->size() != tlwRect.size())
833         store->resize(tlwRect.size());
834 
835     if (updatesDisabled)
836         return;
837 
838     // Contains everything that needs repaint.
839     QRegion toClean(dirty);
840 
841     // Loop through all update() widgets and remove them from the list before they are
842     // painted (in case someone calls update() in paintEvent). If the widget is opaque
843     // and does not have transparent overlapping siblings, append it to the
844     // opaqueNonOverlappedWidgets list and paint it directly without composition.
845     QVarLengthArray<QWidget *, 32> opaqueNonOverlappedWidgets;
846     for (int i = 0; i < dirtyWidgets.size(); ++i) {
847         QWidget *w = dirtyWidgets.at(i);
848         QWidgetPrivate *wd = w->d_func();
849         if (wd->data.in_destructor)
850             continue;
851 
852         // Clip with mask() and clipRect().
853         wd->dirty &= wd->clipRect();
854         wd->clipToEffectiveMask(wd->dirty);
855 
856         // Subtract opaque siblings and children.
857         bool hasDirtySiblingsAbove = false;
858         // We know for sure that the widget isn't overlapped if 'isMoved' is true.
859         if (!wd->isMoved)
860             wd->subtractOpaqueSiblings(wd->dirty, &hasDirtySiblingsAbove);
861 
862         // Make a copy of the widget's dirty region, to restore it in case there is an opaque
863         // render-to-texture child that completely covers the widget, because otherwise the
864         // render-to-texture child won't be visible, due to its parent widget not being redrawn
865         // with a proper blending mask.
866         const QRegion dirtyBeforeSubtractedOpaqueChildren = wd->dirty;
867 
868         // Scrolled and moved widgets must draw all children.
869         if (!wd->isScrolled && !wd->isMoved)
870             wd->subtractOpaqueChildren(wd->dirty, w->rect());
871 
872         if (wd->dirty.isEmpty() && wd->textureChildSeen)
873             wd->dirty = dirtyBeforeSubtractedOpaqueChildren;
874 
875         if (wd->dirty.isEmpty()) {
876             resetWidget(w);
877             continue;
878         }
879 
880         const QRegion widgetDirty(w != tlw ? wd->dirty.translated(w->mapTo(tlw, QPoint()))
881                                            : wd->dirty);
882         toClean += widgetDirty;
883 
884 #if QT_CONFIG(graphicsview)
885         if (tlw->d_func()->extra->proxyWidget) {
886             resetWidget(w);
887             continue;
888         }
889 #endif
890 
891         if (!hasDirtySiblingsAbove && wd->isOpaque && !dirty.intersects(widgetDirty.boundingRect())) {
892             opaqueNonOverlappedWidgets.append(w);
893         } else {
894             resetWidget(w);
895             dirty += widgetDirty;
896         }
897     }
898     dirtyWidgets.clear();
899 
900 #ifndef QT_NO_OPENGL
901     // Find all render-to-texture child widgets (including self).
902     // The search is cut at native widget boundaries, meaning that each native child widget
903     // has its own list for the subtree below it.
904     QTLWExtra *tlwExtra = tlw->d_func()->topData();
905     tlwExtra->widgetTextures.clear();
906     findAllTextureWidgetsRecursively(tlw, tlw);
907     qt_window_private(tlw->windowHandle())->compositing = false; // will get updated in flush()
908 #endif
909 
910     if (toClean.isEmpty()) {
911         // Nothing to repaint. However renderToTexture widgets are handled
912         // specially, they are not in the regular dirty list, in order to
913         // prevent triggering unnecessary backingstore painting when only the
914         // OpenGL content changes. Check if we have such widgets in the special
915         // dirty list.
916         QVarLengthArray<QWidget *, 16> paintPending;
917         const int numPaintPending = dirtyRenderToTextureWidgets.count();
918         paintPending.reserve(numPaintPending);
919         for (int i = 0; i < numPaintPending; ++i) {
920             QWidget *w = dirtyRenderToTextureWidgets.at(i);
921             paintPending << w;
922             resetWidget(w);
923         }
924         dirtyRenderToTextureWidgets.clear();
925         for (int i = 0; i < numPaintPending; ++i) {
926             QWidget *w = paintPending[i];
927             w->d_func()->sendPaintEvent(w->rect());
928             if (w != tlw) {
929                 QWidget *npw = w->nativeParentWidget();
930                 if (hasPlatformWindow(w) || (npw && npw != tlw)) {
931                     if (!hasPlatformWindow(w))
932                         w = npw;
933                     markNeedsFlush(w);
934                 }
935             }
936         }
937 
938         // We might have newly exposed areas on the screen if this function was
939         // called from sync(QWidget *, QRegion)), so we have to make sure those
940         // are flushed. We also need to composite the renderToTexture widgets.
941         flush();
942 
943         return;
944     }
945 
946 #ifndef QT_NO_OPENGL
947     for (const auto &tl : tlwExtra->widgetTextures) {
948         for (int i = 0; i < tl->count(); ++i) {
949             QWidget *w = static_cast<QWidget *>(tl->source(i));
950             if (dirtyRenderToTextureWidgets.contains(w)) {
951                 const QRect rect = tl->geometry(i); // mapped to the tlw already
952                 // Set a flag to indicate that the paint event for this
953                 // render-to-texture widget must not to be optimized away.
954                 w->d_func()->renderToTextureReallyDirty = 1;
955                 dirty += rect;
956                 toClean += rect;
957             }
958         }
959     }
960     for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i)
961         resetWidget(dirtyRenderToTextureWidgets.at(i));
962     dirtyRenderToTextureWidgets.clear();
963 #endif
964 
965 #if QT_CONFIG(graphicsview)
966     if (tlw->d_func()->extra->proxyWidget) {
967         updateStaticContentsSize();
968         dirty = QRegion();
969         updateRequestSent = false;
970         for (const QRect &rect : toClean)
971             tlw->d_func()->extra->proxyWidget->update(rect);
972         return;
973     }
974 #endif
975 
976     store->beginPaint(toClean);
977 
978     // Must do this before sending any paint events because
979     // the size may change in the paint event.
980     updateStaticContentsSize();
981     const QRegion dirtyCopy(dirty);
982     dirty = QRegion();
983     updateRequestSent = false;
984 
985     // Paint opaque non overlapped widgets.
986     for (int i = 0; i < opaqueNonOverlappedWidgets.size(); ++i) {
987         QWidget *w = opaqueNonOverlappedWidgets[i];
988         QWidgetPrivate *wd = w->d_func();
989 
990         QWidgetPrivate::DrawWidgetFlags flags = QWidgetPrivate::DrawRecursive;
991         // Scrolled and moved widgets must draw all children.
992         if (!wd->isScrolled && !wd->isMoved)
993             flags |= QWidgetPrivate::DontDrawOpaqueChildren;
994         if (w == tlw)
995             flags |= QWidgetPrivate::DrawAsRoot;
996 
997         QRegion toBePainted(wd->dirty);
998         resetWidget(w);
999 
1000         QPoint offset;
1001         if (w != tlw)
1002             offset += w->mapTo(tlw, QPoint());
1003         wd->drawWidget(store->paintDevice(), toBePainted, offset, flags, nullptr, this);
1004     }
1005 
1006     // Paint the rest with composition.
1007     if (repaintAllWidgets || !dirtyCopy.isEmpty()) {
1008         QWidgetPrivate::DrawWidgetFlags flags = QWidgetPrivate::DrawAsRoot | QWidgetPrivate::DrawRecursive;
1009         tlw->d_func()->drawWidget(store->paintDevice(), dirtyCopy, QPoint(), flags, nullptr, this);
1010     }
1011 
1012     store->endPaint();
1013 
1014     flush();
1015 }
1016 
1017 /*!
1018     Marks the \a region of the \a widget as needing a flush. The \a region will be copied from
1019     the backing store to the \a widget's native parent next time flush() is called.
1020 
1021     Paint on screen widgets are ignored.
1022 */
markNeedsFlush(QWidget * widget,const QRegion & region,const QPoint & topLevelOffset)1023 void QWidgetRepaintManager::markNeedsFlush(QWidget *widget, const QRegion &region, const QPoint &topLevelOffset)
1024 {
1025     if (!widget || widget->d_func()->shouldPaintOnScreen() || region.isEmpty())
1026         return;
1027 
1028     if (widget == tlw) {
1029         // Top-level (native)
1030         qCInfo(lcWidgetPainting) << "Marking" << region << "of top level"
1031             << widget << "as needing flush";
1032         topLevelNeedsFlush += region;
1033     } else if (!hasPlatformWindow(widget) && !widget->isWindow()) {
1034         QWidget *nativeParent = widget->nativeParentWidget();
1035         qCInfo(lcWidgetPainting) << "Marking" << region << "of"
1036                 << widget << "as needing flush in" << nativeParent
1037                 << "at offset" << topLevelOffset;
1038         if (nativeParent == tlw) {
1039             // Alien widgets with the top-level as the native parent (common case)
1040             topLevelNeedsFlush += region.translated(topLevelOffset);
1041         } else {
1042             // Alien widgets with native parent != tlw
1043             const QPoint nativeParentOffset = widget->mapTo(nativeParent, QPoint());
1044             markNeedsFlush(nativeParent, region.translated(nativeParentOffset));
1045         }
1046     } else {
1047         // Native child widgets
1048         qCInfo(lcWidgetPainting) << "Marking" << region
1049             << "of native child" << widget << "as needing flush";
1050         markNeedsFlush(widget, region);
1051     }
1052 }
1053 
markNeedsFlush(QWidget * widget,const QRegion & region)1054 void QWidgetRepaintManager::markNeedsFlush(QWidget *widget, const QRegion &region)
1055 {
1056     if (!widget)
1057         return;
1058 
1059     auto *widgetPrivate = qt_widget_private(widget);
1060     if (!widgetPrivate->needsFlush)
1061         widgetPrivate->needsFlush = new QRegion;
1062 
1063     *widgetPrivate->needsFlush += region;
1064 
1065     if (!needsFlushWidgets.contains(widget))
1066         needsFlushWidgets.append(widget);
1067 }
1068 
1069 /*!
1070     Flushes the contents of the backing store into the top-level widget.
1071 */
flush()1072 void QWidgetRepaintManager::flush()
1073 {
1074     qCInfo(lcWidgetPainting) << "Flushing top level"
1075         << topLevelNeedsFlush << "and children" << needsFlushWidgets;
1076 
1077     const bool hasNeedsFlushWidgets = !needsFlushWidgets.isEmpty();
1078     bool flushed = false;
1079 
1080     // Flush the top level widget
1081     if (!topLevelNeedsFlush.isEmpty()) {
1082         flush(tlw, topLevelNeedsFlush, widgetTexturesFor(tlw, tlw));
1083         topLevelNeedsFlush = QRegion();
1084         flushed = true;
1085     }
1086 
1087     // Render-to-texture widgets are not in topLevelNeedsFlush so flush if we have not done it above.
1088     if (!flushed && !hasNeedsFlushWidgets) {
1089 #ifndef QT_NO_OPENGL
1090         if (!tlw->d_func()->topData()->widgetTextures.empty()) {
1091             if (QPlatformTextureList *widgetTextures = widgetTexturesFor(tlw, tlw))
1092                 flush(tlw, QRegion(), widgetTextures);
1093         }
1094 #endif
1095     }
1096 
1097     if (!hasNeedsFlushWidgets)
1098         return;
1099 
1100     for (QWidget *w : qExchange(needsFlushWidgets, {})) {
1101         QWidgetPrivate *wd = w->d_func();
1102         Q_ASSERT(wd->needsFlush);
1103         QPlatformTextureList *widgetTexturesForNative = wd->textureChildSeen ? widgetTexturesFor(tlw, w) : nullptr;
1104         flush(w, *wd->needsFlush, widgetTexturesForNative);
1105         *wd->needsFlush = QRegion();
1106     }
1107 }
1108 
1109 /*
1110     Flushes the contents of the backingstore into the screen area of \a widget.
1111 
1112     \a region is the region to be updated in \a widget coordinates.
1113  */
flush(QWidget * widget,const QRegion & region,QPlatformTextureList * widgetTextures)1114 void QWidgetRepaintManager::flush(QWidget *widget, const QRegion &region, QPlatformTextureList *widgetTextures)
1115 {
1116 #ifdef QT_NO_OPENGL
1117     Q_UNUSED(widgetTextures);
1118     Q_ASSERT(!region.isEmpty());
1119 #else
1120     Q_ASSERT(!region.isEmpty() || widgetTextures);
1121 #endif
1122     Q_ASSERT(widget);
1123     Q_ASSERT(tlw);
1124 
1125     if (tlw->testAttribute(Qt::WA_DontShowOnScreen) || widget->testAttribute(Qt::WA_DontShowOnScreen))
1126         return;
1127 
1128     // Foreign Windows do not have backing store content and must not be flushed
1129     if (QWindow *widgetWindow = widget->windowHandle()) {
1130         if (widgetWindow->type() == Qt::ForeignWindow)
1131             return;
1132     }
1133 
1134     qCInfo(lcWidgetPainting) << "Flushing" << region << "of" << widget;
1135 
1136     static bool fpsDebug = qEnvironmentVariableIntValue("QT_DEBUG_FPS");
1137     if (fpsDebug) {
1138         if (!perfFrames++)
1139             perfTime.start();
1140         if (perfTime.elapsed() > 5000) {
1141             double fps = double(perfFrames * 1000) / perfTime.restart();
1142             qDebug("FPS: %.1f\n", fps);
1143             perfFrames = 0;
1144         }
1145     }
1146 
1147     QPoint offset;
1148     if (widget != tlw)
1149         offset += widget->mapTo(tlw, QPoint());
1150 
1151     QRegion effectiveRegion = region;
1152 #ifndef QT_NO_OPENGL
1153     const bool compositionWasActive = widget->d_func()->renderToTextureComposeActive;
1154     if (!widgetTextures) {
1155         widget->d_func()->renderToTextureComposeActive = false;
1156         // Detect the case of falling back to the normal flush path when no
1157         // render-to-texture widgets are visible anymore. We will force one
1158         // last flush to go through the OpenGL-based composition to prevent
1159         // artifacts. The next flush after this one will use the normal path.
1160         if (compositionWasActive)
1161             widgetTextures = qt_dummy_platformTextureList;
1162     } else {
1163         widget->d_func()->renderToTextureComposeActive = true;
1164     }
1165     // When changing the composition status, make sure the dirty region covers
1166     // the entire widget.  Just having e.g. the shown/hidden render-to-texture
1167     // widget's area marked as dirty is incorrect when changing flush paths.
1168     if (compositionWasActive != widget->d_func()->renderToTextureComposeActive)
1169         effectiveRegion = widget->rect();
1170 
1171     // re-test since we may have been forced to this path via the dummy texture list above
1172     if (widgetTextures) {
1173         qt_window_private(tlw->windowHandle())->compositing = true;
1174         widget->window()->d_func()->sendComposeStatus(widget->window(), false);
1175         // A window may have alpha even when the app did not request
1176         // WA_TranslucentBackground. Therefore the compositor needs to know whether the app intends
1177         // to rely on translucency, in order to decide if it should clear to transparent or opaque.
1178         const bool translucentBackground = widget->testAttribute(Qt::WA_TranslucentBackground);
1179         store->handle()->composeAndFlush(widget->windowHandle(), effectiveRegion, offset,
1180                                                 widgetTextures, translucentBackground);
1181         widget->window()->d_func()->sendComposeStatus(widget->window(), true);
1182     } else
1183 #endif
1184         store->flush(effectiveRegion, widget->windowHandle(), offset);
1185 }
1186 
1187 // ---------------------------------------------------------------------------
1188 
addStaticWidget(QWidget * widget)1189 void QWidgetRepaintManager::addStaticWidget(QWidget *widget)
1190 {
1191     if (!widget)
1192         return;
1193 
1194     Q_ASSERT(widget->testAttribute(Qt::WA_StaticContents));
1195     if (!staticWidgets.contains(widget))
1196         staticWidgets.append(widget);
1197 }
1198 
1199 // Move the reparented widget and all its static children from this backing store
1200 // to the new backing store if reparented into another top-level / backing store.
moveStaticWidgets(QWidget * reparented)1201 void QWidgetRepaintManager::moveStaticWidgets(QWidget *reparented)
1202 {
1203     Q_ASSERT(reparented);
1204     QWidgetRepaintManager *newPaintManager = reparented->d_func()->maybeRepaintManager();
1205     if (newPaintManager == this)
1206         return;
1207 
1208     int i = 0;
1209     while (i < staticWidgets.size()) {
1210         QWidget *w = staticWidgets.at(i);
1211         if (reparented == w || reparented->isAncestorOf(w)) {
1212             staticWidgets.removeAt(i);
1213             if (newPaintManager)
1214                 newPaintManager->addStaticWidget(w);
1215         } else {
1216             ++i;
1217         }
1218     }
1219 }
1220 
removeStaticWidget(QWidget * widget)1221 void QWidgetRepaintManager::removeStaticWidget(QWidget *widget)
1222 {
1223     staticWidgets.removeAll(widget);
1224 }
1225 
hasStaticContents() const1226 bool QWidgetRepaintManager::hasStaticContents() const
1227 {
1228 #if defined(Q_OS_WIN)
1229     return !staticWidgets.isEmpty();
1230 #else
1231     return !staticWidgets.isEmpty() && false;
1232 #endif
1233 }
1234 
1235 /*!
1236     Returns the static content inside the \a parent if non-zero; otherwise the static content
1237     for the entire backing store is returned. The content will be clipped to \a withinClipRect
1238     if non-empty.
1239 */
staticContents(QWidget * parent,const QRect & withinClipRect) const1240 QRegion QWidgetRepaintManager::staticContents(QWidget *parent, const QRect &withinClipRect) const
1241 {
1242     if (!parent && tlw->testAttribute(Qt::WA_StaticContents)) {
1243         QRect backingstoreRect(QPoint(0, 0), store->size());
1244         if (!withinClipRect.isEmpty())
1245             backingstoreRect &= withinClipRect;
1246         return QRegion(backingstoreRect);
1247     }
1248 
1249     QRegion region;
1250     if (parent && parent->d_func()->children.isEmpty())
1251         return region;
1252 
1253     const bool clipToRect = !withinClipRect.isEmpty();
1254     const int count = staticWidgets.count();
1255     for (int i = 0; i < count; ++i) {
1256         QWidget *w = staticWidgets.at(i);
1257         QWidgetPrivate *wd = w->d_func();
1258         if (!wd->isOpaque || !wd->extra || wd->extra->staticContentsSize.isEmpty()
1259             || !w->isVisible() || (parent && !parent->isAncestorOf(w))) {
1260             continue;
1261         }
1262 
1263         QRect rect(0, 0, wd->extra->staticContentsSize.width(), wd->extra->staticContentsSize.height());
1264         const QPoint offset = w->mapTo(parent ? parent : tlw, QPoint());
1265         if (clipToRect)
1266             rect &= withinClipRect.translated(-offset);
1267         if (rect.isEmpty())
1268             continue;
1269 
1270         rect &= wd->clipRect();
1271         if (rect.isEmpty())
1272             continue;
1273 
1274         QRegion visible(rect);
1275         wd->clipToEffectiveMask(visible);
1276         if (visible.isEmpty())
1277             continue;
1278         wd->subtractOpaqueSiblings(visible, nullptr, /*alsoNonOpaque=*/true);
1279 
1280         visible.translate(offset);
1281         region += visible;
1282     }
1283 
1284     return region;
1285 }
1286 
updateStaticContentsSize()1287 void QWidgetRepaintManager::updateStaticContentsSize()
1288 {
1289     for (int i = 0; i < staticWidgets.size(); ++i) {
1290         QWidgetPrivate *wd = staticWidgets.at(i)->d_func();
1291         if (!wd->extra)
1292             wd->createExtra();
1293         wd->extra->staticContentsSize = wd->data.crect.size();
1294     }
1295 }
1296 
1297 // ---------------------------------------------------------------------------
1298 
isDirty() const1299 bool QWidgetRepaintManager::isDirty() const
1300 {
1301     return !(dirtyWidgets.isEmpty() && dirty.isEmpty() && dirtyRenderToTextureWidgets.isEmpty());
1302 }
1303 
1304 /*!
1305     Invalidates the backing store when the widget is resized.
1306     Static areas are never invalidated unless absolutely needed.
1307 */
invalidateBackingStore_resizeHelper(const QPoint & oldPos,const QSize & oldSize)1308 void QWidgetPrivate::invalidateBackingStore_resizeHelper(const QPoint &oldPos, const QSize &oldSize)
1309 {
1310     Q_Q(QWidget);
1311     Q_ASSERT(!q->isWindow());
1312     Q_ASSERT(q->parentWidget());
1313 
1314     const bool staticContents = q->testAttribute(Qt::WA_StaticContents);
1315     const bool sizeDecreased = (data.crect.width() < oldSize.width())
1316                                || (data.crect.height() < oldSize.height());
1317 
1318     const QPoint offset(data.crect.x() - oldPos.x(), data.crect.y() - oldPos.y());
1319     const bool parentAreaExposed = !offset.isNull() || sizeDecreased;
1320     const QRect newWidgetRect(q->rect());
1321     const QRect oldWidgetRect(0, 0, oldSize.width(), oldSize.height());
1322 
1323     if (!staticContents || graphicsEffect) {
1324         QRegion staticChildren;
1325         QWidgetRepaintManager *bs = nullptr;
1326         if (offset.isNull() && (bs = maybeRepaintManager()))
1327             staticChildren = bs->staticContents(q, oldWidgetRect);
1328         const bool hasStaticChildren = !staticChildren.isEmpty();
1329 
1330         if (hasStaticChildren) {
1331             QRegion dirty(newWidgetRect);
1332             dirty -= staticChildren;
1333             invalidateBackingStore(dirty);
1334         } else {
1335             // Entire widget needs repaint.
1336             invalidateBackingStore(newWidgetRect);
1337         }
1338 
1339         if (!parentAreaExposed)
1340             return;
1341 
1342         // Invalidate newly exposed area of the parent.
1343         if (!graphicsEffect && extra && extra->hasMask) {
1344             QRegion parentExpose(extra->mask.translated(oldPos));
1345             parentExpose &= QRect(oldPos, oldSize);
1346             if (hasStaticChildren)
1347                 parentExpose -= data.crect; // Offset is unchanged, safe to do this.
1348             q->parentWidget()->d_func()->invalidateBackingStore(parentExpose);
1349         } else {
1350             if (hasStaticChildren && !graphicsEffect) {
1351                 QRegion parentExpose(QRect(oldPos, oldSize));
1352                 parentExpose -= data.crect; // Offset is unchanged, safe to do this.
1353                 q->parentWidget()->d_func()->invalidateBackingStore(parentExpose);
1354             } else {
1355                 q->parentWidget()->d_func()->invalidateBackingStore(effectiveRectFor(QRect(oldPos, oldSize)));
1356             }
1357         }
1358         return;
1359     }
1360 
1361     // Move static content to its new position.
1362     if (!offset.isNull()) {
1363         if (sizeDecreased) {
1364             const QSize minSize(qMin(oldSize.width(), data.crect.width()),
1365                                 qMin(oldSize.height(), data.crect.height()));
1366             moveRect(QRect(oldPos, minSize), offset.x(), offset.y());
1367         } else {
1368             moveRect(QRect(oldPos, oldSize), offset.x(), offset.y());
1369         }
1370     }
1371 
1372     // Invalidate newly visible area of the widget.
1373     if (!sizeDecreased || !oldWidgetRect.contains(newWidgetRect)) {
1374         QRegion newVisible(newWidgetRect);
1375         newVisible -= oldWidgetRect;
1376         invalidateBackingStore(newVisible);
1377     }
1378 
1379     if (!parentAreaExposed)
1380         return;
1381 
1382     // Invalidate newly exposed area of the parent.
1383     const QRect oldRect(oldPos, oldSize);
1384     if (extra && extra->hasMask) {
1385         QRegion parentExpose(oldRect);
1386         parentExpose &= extra->mask.translated(oldPos);
1387         parentExpose -= (extra->mask.translated(data.crect.topLeft()) & data.crect);
1388         q->parentWidget()->d_func()->invalidateBackingStore(parentExpose);
1389     } else {
1390         QRegion parentExpose(oldRect);
1391         parentExpose -= data.crect;
1392         q->parentWidget()->d_func()->invalidateBackingStore(parentExpose);
1393     }
1394 }
1395 
1396 QT_END_NAMESPACE
1397 
1398 #include "qwidgetrepaintmanager.moc"
1399