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
68QT_BEGIN_NAMESPACE
69
70#ifndef QT_NO_OPENGL
71Q_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.
78class QPlatformTextureListWatcher : public QObject
79{
80 Q_OBJECT
81public:
82 QPlatformTextureListWatcher(QWidgetRepaintManager *repaintManager)
83 : m_repaintManager(repaintManager) {}
84
85 void watch(QPlatformTextureList *textureList) {
86 connect(textureList, SIGNAL(locked(bool)), SLOT(onLockStatusChanged(bool)));
87 m_locked[textureList] = textureList->isLocked();
88 }
89
90 bool isLocked() const {
91 foreach (bool v, m_locked) {
92 if (v)
93 return true;
94 }
95 return false;
96 }
97
98private slots:
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
106private:
107 QHash<QPlatformTextureList *, bool> m_locked;
108 QWidgetRepaintManager *m_repaintManager;
109};
110#endif
111
112// ---------------------------------------------------------------------------
113
114QWidgetRepaintManager::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
123void 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
141QWidgetRepaintManager::~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*/
155template <class T>
156void 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
191template Q_AUTOTEST_EXPORT void QWidgetPrivate::invalidateBackingStore<QRect>(const QRect &r);
192
193static inline QRect widgetRectFor(QWidget *, const QRect &r) { return r; }
194static 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*/
208template <class T>
209void 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}
315template void QWidgetRepaintManager::markDirty<QRect>(const QRect &, QWidget *, UpdateTime, BufferState);
316template void QWidgetRepaintManager::markDirty<QRegion>(const QRegion &, QWidget *, UpdateTime, BufferState);
317
318void 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
333void 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
352void 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
362void 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
372void 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
414static bool hasPlatformWindow(QWidget *widget)
415{
416 return widget && widget->windowHandle() && widget->windowHandle()->handle();
417}
418
419static QList<QRect> getSortedRectsToScroll(const QRegion &region, int dx, int dy)
420{
421 QList<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
440void 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 QList<QRect> rectsToScroll =
492 getSortedRectsToScroll(QRegion(sourceRect) - overlappedExpose, dx, dy);
493 for (QRect r : rectsToScroll) {
494 if (repaintManager->bltRect(r, dx, dy, pw)) {
495 childExpose -= r.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
538void 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 QList<QRect> rectsToScroll =
574 getSortedRectsToScroll(QRegion(sourceRect) - overlappedExpose, dx, dy);
575 for (const QRect &r : rectsToScroll) {
576 if (repaintManager->bltRect(r, dx, dy, q)) {
577 childExpose -= r.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*/
618bool 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
630static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget,
631 QPlatformTextureList *widgetTextures,
632 QList<QWidget *> *nativeChildren)
633{
634 QWidgetPrivate *wd = QWidgetPrivate::get(widget);
635 if (wd->renderToTexture) {
636 QPlatformTextureList::Flags flags = wd->textureListFlags();
637 const QRect rect(widget->mapTo(tlw, QPoint()), widget->size());
638 widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags);
639 }
640
641 for (int i = 0; i < wd->children.size(); ++i) {
642 QWidget *w = qobject_cast<QWidget *>(wd->children.at(i));
643 // Stop at native widgets but store them. Stop at hidden widgets too.
644 if (w && !w->isWindow() && hasPlatformWindow(w))
645 nativeChildren->append(w);
646 if (w && !w->isWindow() && !hasPlatformWindow(w) && !w->isHidden() && QWidgetPrivate::get(w)->textureChildSeen)
647 findTextureWidgetsRecursively(tlw, w, widgetTextures, nativeChildren);
648 }
649}
650
651static void findAllTextureWidgetsRecursively(QWidget *tlw, QWidget *widget)
652{
653 // textureChildSeen does not take native child widgets into account and that's good.
654 if (QWidgetPrivate::get(widget)->textureChildSeen) {
655 QList<QWidget *> nativeChildren;
656 auto tl = qt_make_unique<QPlatformTextureList>();
657 // Look for texture widgets (incl. widget itself) from 'widget' down,
658 // but skip subtrees with a parent of a native child widget.
659 findTextureWidgetsRecursively(tlw, widget, tl.get(), &nativeChildren);
660 // tl may be empty regardless of textureChildSeen if we have native or hidden children.
661 if (!tl->isEmpty())
662 QWidgetPrivate::get(tlw)->topData()->widgetTextures.push_back(std::move(tl));
663 // Native child widgets, if there was any, get their own separate QPlatformTextureList.
664 for (QWidget *ncw : qAsConst(nativeChildren)) {
665 if (QWidgetPrivate::get(ncw)->textureChildSeen)
666 findAllTextureWidgetsRecursively(tlw, ncw);
667 }
668 }
669}
670
671static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget)
672{
673 for (const auto &tl : QWidgetPrivate::get(tlw)->topData()->widgetTextures) {
674 Q_ASSERT(!tl->isEmpty());
675 for (int i = 0; i < tl->count(); ++i) {
676 QWidget *w = static_cast<QWidget *>(tl->source(i));
677 if ((hasPlatformWindow(w) && w == widget) || (!hasPlatformWindow(w) && w->nativeParentWidget() == widget))
678 return tl.get();
679 }
680 }
681
682 if (QWidgetPrivate::get(widget)->textureChildSeen) {
683 // No render-to-texture widgets in the (sub-)tree due to hidden or native
684 // children. Returning null results in using the normal backingstore flush path
685 // without OpenGL-based compositing. This is very desirable normally. However,
686 // some platforms cannot handle switching between the non-GL and GL paths for
687 // their windows so it has to be opt-in.
688 static bool switchableWidgetComposition =
689 QGuiApplicationPrivate::instance()->platformIntegration()
690 ->hasCapability(QPlatformIntegration::SwitchableWidgetComposition);
691 if (!switchableWidgetComposition)
692 return qt_dummy_platformTextureList();
693 }
694
695 return nullptr;
696}
697
698#else
699
700static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget)
701{
702 Q_UNUSED(tlw);
703 Q_UNUSED(widget);
704 return nullptr;
705}
706
707#endif // QT_NO_OPENGL
708
709// ---------------------------------------------------------------------------
710
711/*!
712 Synchronizes the \a exposedRegion of the \a exposedWidget with the backing store.
713
714 If there are dirty widgets, including but not limited to the \a exposedWidget,
715 these will be repainted first. The backingstore is then flushed to the screen,
716 regardless of whether or not there were any repaints.
717*/
718void QWidgetRepaintManager::sync(QWidget *exposedWidget, const QRegion &exposedRegion)
719{
720 qCInfo(lcWidgetPainting) << "Syncing" << exposedRegion << "of" << exposedWidget;
721
722 if (!tlw->isVisible())
723 return;
724
725 if (!exposedWidget || !hasPlatformWindow(exposedWidget)
726 || !exposedWidget->isVisible() || !exposedWidget->testAttribute(Qt::WA_Mapped)
727 || !exposedWidget->updatesEnabled() || exposedRegion.isEmpty()) {
728 return;
729 }
730
731 // Nothing to repaint.
732 if (!isDirty() && store->size().isValid()) {
733 QPlatformTextureList *widgetTextures = widgetTexturesFor(tlw, exposedWidget);
734 flush(exposedWidget, widgetTextures ? QRegion() : exposedRegion, widgetTextures);
735 return;
736 }
737
738 // As requests to sync a specific widget typically comes from an expose event
739 // we can't rely solely on our own dirty tracking to decide what to flush, and
740 // need to respect the platform's request to at least flush the entire widget,
741 QPoint offset = exposedWidget != tlw ? exposedWidget->mapTo(tlw, QPoint()) : QPoint();
742 markNeedsFlush(exposedWidget, exposedRegion, offset);
743
744 if (syncAllowed())
745 paintAndFlush();
746}
747
748/*!
749 Synchronizes the backing store, i.e. dirty areas are repainted and flushed.
750*/
751void QWidgetRepaintManager::sync()
752{
753 qCInfo(lcWidgetPainting) << "Syncing dirty widgets";
754
755 updateRequestSent = false;
756 if (qt_widget_private(tlw)->shouldDiscardSyncRequest()) {
757 // If the top-level is minimized, it's not visible on the screen so we can delay the
758 // update until it's shown again. In order to do that we must keep the dirty states.
759 // These will be cleared when we receive the first expose after showNormal().
760 // However, if the widget is not visible (isVisible() returns false), everything will
761 // be invalidated once the widget is shown again, so clear all dirty states.
762 if (!tlw->isVisible()) {
763 dirty = QRegion();
764 for (int i = 0; i < dirtyWidgets.size(); ++i)
765 resetWidget(dirtyWidgets.at(i));
766 dirtyWidgets.clear();
767 }
768 return;
769 }
770
771 if (syncAllowed())
772 paintAndFlush();
773}
774
775bool QWidgetPrivate::shouldDiscardSyncRequest() const
776{
777 Q_Q(const QWidget);
778 return !maybeTopData() || !q->testAttribute(Qt::WA_Mapped) || !q->isVisible();
779}
780
781bool QWidgetRepaintManager::syncAllowed()
782{
783#ifndef QT_NO_OPENGL
784 QTLWExtra *tlwExtra = tlw->d_func()->maybeTopData();
785 if (textureListWatcher && !textureListWatcher->isLocked()) {
786 textureListWatcher->deleteLater();
787 textureListWatcher = nullptr;
788 } else if (!tlwExtra->widgetTextures.empty()) {
789 bool skipSync = false;
790 for (const auto &tl : tlwExtra->widgetTextures) {
791 if (tl->isLocked()) {
792 if (!textureListWatcher)
793 textureListWatcher = new QPlatformTextureListWatcher(this);
794 if (!textureListWatcher->isLocked())
795 textureListWatcher->watch(tl.get());
796 skipSync = true;
797 }
798 }
799 if (skipSync) // cannot compose due to widget textures being in use
800 return false;
801 }
802#endif
803 return true;
804}
805
806void QWidgetRepaintManager::paintAndFlush()
807{
808 qCInfo(lcWidgetPainting) << "Painting and flushing dirty"
809 << "top level" << dirty << "and dirty widgets" << dirtyWidgets;
810
811 const bool updatesDisabled = !tlw->updatesEnabled();
812 bool repaintAllWidgets = false;
813
814 const QRect tlwRect = tlw->data->crect;
815 if (!updatesDisabled && store->size() != tlwRect.size()) {
816 if (hasStaticContents() && !store->size().isEmpty() ) {
817 // Repaint existing dirty area and newly visible area.
818 const QRect clipRect(QPoint(0, 0), store->size());
819 const QRegion staticRegion(staticContents(nullptr, clipRect));
820 QRegion newVisible(0, 0, tlwRect.width(), tlwRect.height());
821 newVisible -= staticRegion;
822 dirty += newVisible;
823 store->setStaticContents(staticRegion);
824 } else {
825 // Repaint everything.
826 dirty = QRegion(0, 0, tlwRect.width(), tlwRect.height());
827 for (int i = 0; i < dirtyWidgets.size(); ++i)
828 resetWidget(dirtyWidgets.at(i));
829 dirtyWidgets.clear();
830 repaintAllWidgets = true;
831 }
832 }
833
834 if (store->size() != tlwRect.size())
835 store->resize(tlwRect.size());
836
837 if (updatesDisabled)
838 return;
839
840 // Contains everything that needs repaint.
841 QRegion toClean(dirty);
842
843 // Loop through all update() widgets and remove them from the list before they are
844 // painted (in case someone calls update() in paintEvent). If the widget is opaque
845 // and does not have transparent overlapping siblings, append it to the
846 // opaqueNonOverlappedWidgets list and paint it directly without composition.
847 QVarLengthArray<QWidget *, 32> opaqueNonOverlappedWidgets;
848 for (int i = 0; i < dirtyWidgets.size(); ++i) {
849 QWidget *w = dirtyWidgets.at(i);
850 QWidgetPrivate *wd = w->d_func();
851 if (wd->data.in_destructor)
852 continue;
853
854 // Clip with mask() and clipRect().
855 wd->dirty &= wd->clipRect();
856 wd->clipToEffectiveMask(wd->dirty);
857
858 // Subtract opaque siblings and children.
859 bool hasDirtySiblingsAbove = false;
860 // We know for sure that the widget isn't overlapped if 'isMoved' is true.
861 if (!wd->isMoved)
862 wd->subtractOpaqueSiblings(wd->dirty, &hasDirtySiblingsAbove);
863
864 // Make a copy of the widget's dirty region, to restore it in case there is an opaque
865 // render-to-texture child that completely covers the widget, because otherwise the
866 // render-to-texture child won't be visible, due to its parent widget not being redrawn
867 // with a proper blending mask.
868 const QRegion dirtyBeforeSubtractedOpaqueChildren = wd->dirty;
869
870 // Scrolled and moved widgets must draw all children.
871 if (!wd->isScrolled && !wd->isMoved)
872 wd->subtractOpaqueChildren(wd->dirty, w->rect());
873
874 if (wd->dirty.isEmpty() && wd->textureChildSeen)
875 wd->dirty = dirtyBeforeSubtractedOpaqueChildren;
876
877 if (wd->dirty.isEmpty()) {
878 resetWidget(w);
879 continue;
880 }
881
882 const QRegion widgetDirty(w != tlw ? wd->dirty.translated(w->mapTo(tlw, QPoint()))
883 : wd->dirty);
884 toClean += widgetDirty;
885
886#if QT_CONFIG(graphicsview)
887 if (tlw->d_func()->extra->proxyWidget) {
888 resetWidget(w);
889 continue;
890 }
891#endif
892
893 if (!hasDirtySiblingsAbove && wd->isOpaque && !dirty.intersects(widgetDirty.boundingRect())) {
894 opaqueNonOverlappedWidgets.append(w);
895 } else {
896 resetWidget(w);
897 dirty += widgetDirty;
898 }
899 }
900 dirtyWidgets.clear();
901
902#ifndef QT_NO_OPENGL
903 // Find all render-to-texture child widgets (including self).
904 // The search is cut at native widget boundaries, meaning that each native child widget
905 // has its own list for the subtree below it.
906 QTLWExtra *tlwExtra = tlw->d_func()->topData();
907 tlwExtra->widgetTextures.clear();
908 findAllTextureWidgetsRecursively(tlw, tlw);
909 qt_window_private(tlw->windowHandle())->compositing = false; // will get updated in flush()
910#endif
911
912 if (toClean.isEmpty()) {
913 // Nothing to repaint. However renderToTexture widgets are handled
914 // specially, they are not in the regular dirty list, in order to
915 // prevent triggering unnecessary backingstore painting when only the
916 // OpenGL content changes. Check if we have such widgets in the special
917 // dirty list.
918 QVarLengthArray<QWidget *, 16> paintPending;
919 const int numPaintPending = dirtyRenderToTextureWidgets.count();
920 paintPending.reserve(numPaintPending);
921 for (int i = 0; i < numPaintPending; ++i) {
922 QWidget *w = dirtyRenderToTextureWidgets.at(i);
923 paintPending << w;
924 resetWidget(w);
925 }
926 dirtyRenderToTextureWidgets.clear();
927 for (int i = 0; i < numPaintPending; ++i) {
928 QWidget *w = paintPending[i];
929 w->d_func()->sendPaintEvent(w->rect());
930 if (w != tlw) {
931 QWidget *npw = w->nativeParentWidget();
932 if (hasPlatformWindow(w) || (npw && npw != tlw)) {
933 if (!hasPlatformWindow(w))
934 w = npw;
935 markNeedsFlush(w);
936 }
937 }
938 }
939
940 // We might have newly exposed areas on the screen if this function was
941 // called from sync(QWidget *, QRegion)), so we have to make sure those
942 // are flushed. We also need to composite the renderToTexture widgets.
943 flush();
944
945 return;
946 }
947
948#ifndef QT_NO_OPENGL
949 for (const auto &tl : tlwExtra->widgetTextures) {
950 for (int i = 0; i < tl->count(); ++i) {
951 QWidget *w = static_cast<QWidget *>(tl->source(i));
952 if (dirtyRenderToTextureWidgets.contains(w)) {
953 const QRect rect = tl->geometry(i); // mapped to the tlw already
954 // Set a flag to indicate that the paint event for this
955 // render-to-texture widget must not to be optimized away.
956 w->d_func()->renderToTextureReallyDirty = 1;
957 dirty += rect;
958 toClean += rect;
959 }
960 }
961 }
962 for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i)
963 resetWidget(dirtyRenderToTextureWidgets.at(i));
964 dirtyRenderToTextureWidgets.clear();
965#endif
966
967#if QT_CONFIG(graphicsview)
968 if (tlw->d_func()->extra->proxyWidget) {
969 updateStaticContentsSize();
970 dirty = QRegion();
971 updateRequestSent = false;
972 for (const QRect &rect : toClean)
973 tlw->d_func()->extra->proxyWidget->update(rect);
974 return;
975 }
976#endif
977
978 store->beginPaint(toClean);
979
980 // Must do this before sending any paint events because
981 // the size may change in the paint event.
982 updateStaticContentsSize();
983 const QRegion dirtyCopy(dirty);
984 dirty = QRegion();
985 updateRequestSent = false;
986
987 // Paint opaque non overlapped widgets.
988 for (int i = 0; i < opaqueNonOverlappedWidgets.size(); ++i) {
989 QWidget *w = opaqueNonOverlappedWidgets[i];
990 QWidgetPrivate *wd = w->d_func();
991
992 QWidgetPrivate::DrawWidgetFlags flags = QWidgetPrivate::DrawRecursive;
993 // Scrolled and moved widgets must draw all children.
994 if (!wd->isScrolled && !wd->isMoved)
995 flags |= QWidgetPrivate::DontDrawOpaqueChildren;
996 if (w == tlw)
997 flags |= QWidgetPrivate::DrawAsRoot;
998
999 QRegion toBePainted(wd->dirty);
1000 resetWidget(w);
1001
1002 QPoint offset;
1003 if (w != tlw)
1004 offset += w->mapTo(tlw, QPoint());
1005 wd->drawWidget(store->paintDevice(), toBePainted, offset, flags, nullptr, this);
1006 }
1007
1008 // Paint the rest with composition.
1009 if (repaintAllWidgets || !dirtyCopy.isEmpty()) {
1010 QWidgetPrivate::DrawWidgetFlags flags = QWidgetPrivate::DrawAsRoot | QWidgetPrivate::DrawRecursive;
1011 tlw->d_func()->drawWidget(store->paintDevice(), dirtyCopy, QPoint(), flags, nullptr, this);
1012 }
1013
1014 store->endPaint();
1015
1016 flush();
1017}
1018
1019/*!
1020 Marks the \a region of the \a widget as needing a flush. The \a region will be copied from
1021 the backing store to the \a widget's native parent next time flush() is called.
1022
1023 Paint on screen widgets are ignored.
1024*/
1025void QWidgetRepaintManager::markNeedsFlush(QWidget *widget, const QRegion &region, const QPoint &topLevelOffset)
1026{
1027 if (!widget || widget->d_func()->shouldPaintOnScreen() || region.isEmpty())
1028 return;
1029
1030 if (widget == tlw) {
1031 // Top-level (native)
1032 qCInfo(lcWidgetPainting) << "Marking" << region << "of top level"
1033 << widget << "as needing flush";
1034 topLevelNeedsFlush += region;
1035 } else if (!hasPlatformWindow(widget) && !widget->isWindow()) {
1036 QWidget *nativeParent = widget->nativeParentWidget();
1037 qCInfo(lcWidgetPainting) << "Marking" << region << "of"
1038 << widget << "as needing flush in" << nativeParent
1039 << "at offset" << topLevelOffset;
1040 if (nativeParent == tlw) {
1041 // Alien widgets with the top-level as the native parent (common case)
1042 topLevelNeedsFlush += region.translated(topLevelOffset);
1043 } else {
1044 // Alien widgets with native parent != tlw
1045 const QPoint nativeParentOffset = widget->mapTo(nativeParent, QPoint());
1046 markNeedsFlush(nativeParent, region.translated(nativeParentOffset));
1047 }
1048 } else {
1049 // Native child widgets
1050 qCInfo(lcWidgetPainting) << "Marking" << region
1051 << "of native child" << widget << "as needing flush";
1052 markNeedsFlush(widget, region);
1053 }
1054}
1055
1056void QWidgetRepaintManager::markNeedsFlush(QWidget *widget, const QRegion &region)
1057{
1058 if (!widget)
1059 return;
1060
1061 auto *widgetPrivate = qt_widget_private(widget);
1062 if (!widgetPrivate->needsFlush)
1063 widgetPrivate->needsFlush = new QRegion;
1064
1065 *widgetPrivate->needsFlush += region;
1066
1067 if (!needsFlushWidgets.contains(widget))
1068 needsFlushWidgets.append(widget);
1069}
1070
1071/*!
1072 Flushes the contents of the backing store into the top-level widget.
1073*/
1074void QWidgetRepaintManager::flush()
1075{
1076 qCInfo(lcWidgetPainting) << "Flushing top level"
1077 << topLevelNeedsFlush << "and children" << needsFlushWidgets;
1078
1079 const bool hasNeedsFlushWidgets = !needsFlushWidgets.isEmpty();
1080 bool flushed = false;
1081
1082 // Flush the top level widget
1083 if (!topLevelNeedsFlush.isEmpty()) {
1084 flush(tlw, topLevelNeedsFlush, widgetTexturesFor(tlw, tlw));
1085 topLevelNeedsFlush = QRegion();
1086 flushed = true;
1087 }
1088
1089 // Render-to-texture widgets are not in topLevelNeedsFlush so flush if we have not done it above.
1090 if (!flushed && !hasNeedsFlushWidgets) {
1091#ifndef QT_NO_OPENGL
1092 if (!tlw->d_func()->topData()->widgetTextures.empty()) {
1093 if (QPlatformTextureList *widgetTextures = widgetTexturesFor(tlw, tlw))
1094 flush(tlw, QRegion(), widgetTextures);
1095 }
1096#endif
1097 }
1098
1099 if (!hasNeedsFlushWidgets)
1100 return;
1101
1102 for (QWidget *w : qExchange(needsFlushWidgets, {})) {
1103 QWidgetPrivate *wd = w->d_func();
1104 Q_ASSERT(wd->needsFlush);
1105 QPlatformTextureList *widgetTexturesForNative = wd->textureChildSeen ? widgetTexturesFor(tlw, w) : nullptr;
1106 flush(w, *wd->needsFlush, widgetTexturesForNative);
1107 *wd->needsFlush = QRegion();
1108 }
1109}
1110
1111/*
1112 Flushes the contents of the backingstore into the screen area of \a widget.
1113
1114 \a region is the region to be updated in \a widget coordinates.
1115 */
1116void QWidgetRepaintManager::flush(QWidget *widget, const QRegion &region, QPlatformTextureList *widgetTextures)
1117{
1118#ifdef QT_NO_OPENGL
1119 Q_UNUSED(widgetTextures);
1120 Q_ASSERT(!region.isEmpty());
1121#else
1122 Q_ASSERT(!region.isEmpty() || widgetTextures);
1123#endif
1124 Q_ASSERT(widget);
1125 Q_ASSERT(tlw);
1126
1127 if (tlw->testAttribute(Qt::WA_DontShowOnScreen) || widget->testAttribute(Qt::WA_DontShowOnScreen))
1128 return;
1129
1130 // Foreign Windows do not have backing store content and must not be flushed
1131 if (QWindow *widgetWindow = widget->windowHandle()) {
1132 if (widgetWindow->type() == Qt::ForeignWindow)
1133 return;
1134 }
1135
1136 qCInfo(lcWidgetPainting) << "Flushing" << region << "of" << widget;
1137
1138 static bool fpsDebug = qEnvironmentVariableIntValue("QT_DEBUG_FPS");
1139 if (fpsDebug) {
1140 if (!perfFrames++)
1141 perfTime.start();
1142 if (perfTime.elapsed() > 5000) {
1143 double fps = double(perfFrames * 1000) / perfTime.restart();
1144 qDebug("FPS: %.1f\n", fps);
1145 perfFrames = 0;
1146 }
1147 }
1148
1149 QPoint offset;
1150 if (widget != tlw)
1151 offset += widget->mapTo(tlw, QPoint());
1152
1153 QRegion effectiveRegion = region;
1154#ifndef QT_NO_OPENGL
1155 const bool compositionWasActive = widget->d_func()->renderToTextureComposeActive;
1156 if (!widgetTextures) {
1157 widget->d_func()->renderToTextureComposeActive = false;
1158 // Detect the case of falling back to the normal flush path when no
1159 // render-to-texture widgets are visible anymore. We will force one
1160 // last flush to go through the OpenGL-based composition to prevent
1161 // artifacts. The next flush after this one will use the normal path.
1162 if (compositionWasActive)
1163 widgetTextures = qt_dummy_platformTextureList;
1164 } else {
1165 widget->d_func()->renderToTextureComposeActive = true;
1166 }
1167 // When changing the composition status, make sure the dirty region covers
1168 // the entire widget. Just having e.g. the shown/hidden render-to-texture
1169 // widget's area marked as dirty is incorrect when changing flush paths.
1170 if (compositionWasActive != widget->d_func()->renderToTextureComposeActive)
1171 effectiveRegion = widget->rect();
1172
1173 // re-test since we may have been forced to this path via the dummy texture list above
1174 if (widgetTextures) {
1175 qt_window_private(tlw->windowHandle())->compositing = true;
1176 widget->window()->d_func()->sendComposeStatus(widget->window(), false);
1177 // A window may have alpha even when the app did not request
1178 // WA_TranslucentBackground. Therefore the compositor needs to know whether the app intends
1179 // to rely on translucency, in order to decide if it should clear to transparent or opaque.
1180 const bool translucentBackground = widget->testAttribute(Qt::WA_TranslucentBackground);
1181 store->handle()->composeAndFlush(widget->windowHandle(), effectiveRegion, offset,
1182 widgetTextures, translucentBackground);
1183 widget->window()->d_func()->sendComposeStatus(widget->window(), true);
1184 } else
1185#endif
1186 store->flush(effectiveRegion, widget->windowHandle(), offset);
1187}
1188
1189// ---------------------------------------------------------------------------
1190
1191void QWidgetRepaintManager::addStaticWidget(QWidget *widget)
1192{
1193 if (!widget)
1194 return;
1195
1196 Q_ASSERT(widget->testAttribute(Qt::WA_StaticContents));
1197 if (!staticWidgets.contains(widget))
1198 staticWidgets.append(widget);
1199}
1200
1201// Move the reparented widget and all its static children from this backing store
1202// to the new backing store if reparented into another top-level / backing store.
1203void QWidgetRepaintManager::moveStaticWidgets(QWidget *reparented)
1204{
1205 Q_ASSERT(reparented);
1206 QWidgetRepaintManager *newPaintManager = reparented->d_func()->maybeRepaintManager();
1207 if (newPaintManager == this)
1208 return;
1209
1210 int i = 0;
1211 while (i < staticWidgets.size()) {
1212 QWidget *w = staticWidgets.at(i);
1213 if (reparented == w || reparented->isAncestorOf(w)) {
1214 staticWidgets.removeAt(i);
1215 if (newPaintManager)
1216 newPaintManager->addStaticWidget(w);
1217 } else {
1218 ++i;
1219 }
1220 }
1221}
1222
1223void QWidgetRepaintManager::removeStaticWidget(QWidget *widget)
1224{
1225 staticWidgets.removeAll(widget);
1226}
1227
1228bool QWidgetRepaintManager::hasStaticContents() const
1229{
1230#if defined(Q_OS_WIN)
1231 return !staticWidgets.isEmpty();
1232#else
1233 return !staticWidgets.isEmpty() && false;
1234#endif
1235}
1236
1237/*!
1238 Returns the static content inside the \a parent if non-zero; otherwise the static content
1239 for the entire backing store is returned. The content will be clipped to \a withinClipRect
1240 if non-empty.
1241*/
1242QRegion QWidgetRepaintManager::staticContents(QWidget *parent, const QRect &withinClipRect) const
1243{
1244 if (!parent && tlw->testAttribute(Qt::WA_StaticContents)) {
1245 QRect backingstoreRect(QPoint(0, 0), store->size());
1246 if (!withinClipRect.isEmpty())
1247 backingstoreRect &= withinClipRect;
1248 return QRegion(backingstoreRect);
1249 }
1250
1251 QRegion region;
1252 if (parent && parent->d_func()->children.isEmpty())
1253 return region;
1254
1255 const bool clipToRect = !withinClipRect.isEmpty();
1256 const int count = staticWidgets.count();
1257 for (int i = 0; i < count; ++i) {
1258 QWidget *w = staticWidgets.at(i);
1259 QWidgetPrivate *wd = w->d_func();
1260 if (!wd->isOpaque || !wd->extra || wd->extra->staticContentsSize.isEmpty()
1261 || !w->isVisible() || (parent && !parent->isAncestorOf(w))) {
1262 continue;
1263 }
1264
1265 QRect rect(0, 0, wd->extra->staticContentsSize.width(), wd->extra->staticContentsSize.height());
1266 const QPoint offset = w->mapTo(parent ? parent : tlw, QPoint());
1267 if (clipToRect)
1268 rect &= withinClipRect.translated(-offset);
1269 if (rect.isEmpty())
1270 continue;
1271
1272 rect &= wd->clipRect();
1273 if (rect.isEmpty())
1274 continue;
1275
1276 QRegion visible(rect);
1277 wd->clipToEffectiveMask(visible);
1278 if (visible.isEmpty())
1279 continue;
1280 wd->subtractOpaqueSiblings(visible, nullptr, /*alsoNonOpaque=*/true);
1281
1282 visible.translate(offset);
1283 region += visible;
1284 }
1285
1286 return region;
1287}
1288
1289void QWidgetRepaintManager::updateStaticContentsSize()
1290{
1291 for (int i = 0; i < staticWidgets.size(); ++i) {
1292 QWidgetPrivate *wd = staticWidgets.at(i)->d_func();
1293 if (!wd->extra)
1294 wd->createExtra();
1295 wd->extra->staticContentsSize = wd->data.crect.size();
1296 }
1297}
1298
1299// ---------------------------------------------------------------------------
1300
1301bool QWidgetRepaintManager::isDirty() const
1302{
1303 return !(dirtyWidgets.isEmpty() && dirty.isEmpty() && dirtyRenderToTextureWidgets.isEmpty());
1304}
1305
1306/*!
1307 Invalidates the backing store when the widget is resized.
1308 Static areas are never invalidated unless absolutely needed.
1309*/
1310void QWidgetPrivate::invalidateBackingStore_resizeHelper(const QPoint &oldPos, const QSize &oldSize)
1311{
1312 Q_Q(QWidget);
1313 Q_ASSERT(!q->isWindow());
1314 Q_ASSERT(q->parentWidget());
1315
1316 const bool staticContents = q->testAttribute(Qt::WA_StaticContents);
1317 const bool sizeDecreased = (data.crect.width() < oldSize.width())
1318 || (data.crect.height() < oldSize.height());
1319
1320 const QPoint offset(data.crect.x() - oldPos.x(), data.crect.y() - oldPos.y());
1321 const bool parentAreaExposed = !offset.isNull() || sizeDecreased;
1322 const QRect newWidgetRect(q->rect());
1323 const QRect oldWidgetRect(0, 0, oldSize.width(), oldSize.height());
1324
1325 if (!staticContents || graphicsEffect) {
1326 QRegion staticChildren;
1327 QWidgetRepaintManager *bs = nullptr;
1328 if (offset.isNull() && (bs = maybeRepaintManager()))
1329 staticChildren = bs->staticContents(q, oldWidgetRect);
1330 const bool hasStaticChildren = !staticChildren.isEmpty();
1331
1332 if (hasStaticChildren) {
1333 QRegion dirty(newWidgetRect);
1334 dirty -= staticChildren;
1335 invalidateBackingStore(dirty);
1336 } else {
1337 // Entire widget needs repaint.
1338 invalidateBackingStore(newWidgetRect);
1339 }
1340
1341 if (!parentAreaExposed)
1342 return;
1343
1344 // Invalidate newly exposed area of the parent.
1345 if (!graphicsEffect && extra && extra->hasMask) {
1346 QRegion parentExpose(extra->mask.translated(oldPos));
1347 parentExpose &= QRect(oldPos, oldSize);
1348 if (hasStaticChildren)
1349 parentExpose -= data.crect; // Offset is unchanged, safe to do this.
1350 q->parentWidget()->d_func()->invalidateBackingStore(parentExpose);
1351 } else {
1352 if (hasStaticChildren && !graphicsEffect) {
1353 QRegion parentExpose(QRect(oldPos, oldSize));
1354 parentExpose -= data.crect; // Offset is unchanged, safe to do this.
1355 q->parentWidget()->d_func()->invalidateBackingStore(parentExpose);
1356 } else {
1357 q->parentWidget()->d_func()->invalidateBackingStore(effectiveRectFor(QRect(oldPos, oldSize)));
1358 }
1359 }
1360 return;
1361 }
1362
1363 // Move static content to its new position.
1364 if (!offset.isNull()) {
1365 if (sizeDecreased) {
1366 const QSize minSize(qMin(oldSize.width(), data.crect.width()),
1367 qMin(oldSize.height(), data.crect.height()));
1368 moveRect(QRect(oldPos, minSize), offset.x(), offset.y());
1369 } else {
1370 moveRect(QRect(oldPos, oldSize), offset.x(), offset.y());
1371 }
1372 }
1373
1374 // Invalidate newly visible area of the widget.
1375 if (!sizeDecreased || !oldWidgetRect.contains(newWidgetRect)) {
1376 QRegion newVisible(newWidgetRect);
1377 newVisible -= oldWidgetRect;
1378 invalidateBackingStore(newVisible);
1379 }
1380
1381 if (!parentAreaExposed)
1382 return;
1383
1384 // Invalidate newly exposed area of the parent.
1385 const QRect oldRect(oldPos, oldSize);
1386 if (extra && extra->hasMask) {
1387 QRegion parentExpose(oldRect);
1388 parentExpose &= extra->mask.translated(oldPos);
1389 parentExpose -= (extra->mask.translated(data.crect.topLeft()) & data.crect);
1390 q->parentWidget()->d_func()->invalidateBackingStore(parentExpose);
1391 } else {
1392 QRegion parentExpose(oldRect);
1393 parentExpose -= data.crect;
1394 q->parentWidget()->d_func()->invalidateBackingStore(parentExpose);
1395 }
1396}
1397
1398QT_END_NAMESPACE
1399
1400#include "qwidgetrepaintmanager.moc"
1401