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(asender: 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(widget: 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*>(o: children.at(i));
131 if (!child || child->isWindow())
132 continue;
133
134 updateLists(cur: child);
135 }
136
137 if (cur->testAttribute(attribute: Qt::WA_StaticContents))
138 addStaticWidget(widget: cur);
139}
140
141QWidgetRepaintManager::~QWidgetRepaintManager()
142{
143 for (int c = 0; c < dirtyWidgets.size(); ++c)
144 resetWidget(widget: dirtyWidgets.at(i: c));
145 for (int c = 0; c < dirtyRenderToTextureWidgets.size(); ++c)
146 resetWidget(widget: dirtyRenderToTextureWidgets.at(i: 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(r: masked, widget: q,
184 updateTime: QWidgetRepaintManager::UpdateLater, bufferState: 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(region: widget->d_func()->dirty, rect: 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(w: widget)->renderToTexture) {
249 if (!widget->d_func()->inDirtyList)
250 addDirtyRenderToTextureWidget(widget);
251 if (!updateRequestSent || updateTime == UpdateNow)
252 sendUpdateRequest(widget: tlw, updateTime);
253 return;
254 }
255
256 // ---------------------------------------------------------------------------
257
258 QRect effectiveWidgetRect = widget->d_func()->effectiveRectFor(rect: widgetRect);
259 const QPoint offset = widget->mapTo(tlw, QPoint());
260 QRect translatedRect = effectiveWidgetRect.translated(p: offset);
261#if QT_CONFIG(graphicseffect)
262 // Graphics effects may exceed window size, clamp
263 translatedRect = translatedRect.intersected(other: QRect(QPoint(), tlw->size()));
264#endif
265 if (qt_region_strictContains(region: dirty, rect: translatedRect)) {
266 if (updateTime == UpdateNow)
267 sendUpdateRequest(widget: 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(widget: tlw, updateTime);
284 return;
285 }
286
287 // ---------------------------------------------------------------------------
288
289 if (dirtyWidgets.isEmpty()) {
290 addDirtyWidget(widget, rgn: r);
291 sendUpdateRequest(widget: tlw, updateTime);
292 return;
293 }
294
295 // ---------------------------------------------------------------------------
296
297 if (widget->d_func()->inDirtyList) {
298 if (!qt_region_strictContains(region: widget->d_func()->dirty, rect: 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, rgn: r);
308 }
309
310 // ---------------------------------------------------------------------------
311
312 if (updateTime == UpdateNow)
313 sendUpdateRequest(widget: 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(rect: rgn.boundingRect());
325 else
326#endif // QT_CONFIG(graphicseffect)
327 widgetPrivate->dirty = rgn;
328 dirtyWidgets.append(t: widget);
329 widgetPrivate->inDirtyList = true;
330 }
331}
332
333void QWidgetRepaintManager::removeDirtyWidget(QWidget *w)
334{
335 if (!w)
336 return;
337
338 dirtyWidgets.removeAll(t: w);
339 dirtyRenderToTextureWidgets.removeAll(t: w);
340 resetWidget(widget: w);
341
342 needsFlushWidgets.removeAll(t: 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*>(o: wd->children.at(i)))
348 removeDirtyWidget(w: 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(t: 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(window: 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(window: 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(receiver: widget, event: new QEvent(QEvent::UpdateRequest), priority: Qt::LowEventPriority);
403 break;
404 case UpdateNow: {
405 QEvent event(QEvent::UpdateRequest);
406 QCoreApplication::sendEvent(receiver: widget, event: &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 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(x&: rects));
423 if (rects.count() > 1) {
424 std::sort(first: rects.begin(), last: rects.end(), comp: [=](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(varName: "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(other: clipR);
457 if (destRect.isValid())
458 destRect = destRect.translated(dx, dy).intersected(other: clipR);
459 const QRect sourceRect(destRect.translated(dx: -dx, dy: -dy));
460 const QRect parentRect(rect & clipR);
461 const bool nativeWithTextureChild = textureChildSeen && hasPlatformWindow(widget: 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(rect: 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(r: parentR);
479 invalidateBackingStore(r: (newRect & clipR).translated(p: -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(rect: sourceRect) | overlappedRegion(rect: destRect)) & clipR;
488
489 const qreal factor = QHighDpiScaling::factor(context: q->windowHandle());
490 if (overlappedExpose.isEmpty() || qFloor(v: factor) == factor) {
491 const QVector<QRect> rectsToScroll
492 = getSortedRectsToScroll(region: QRegion(sourceRect) - overlappedExpose, dx, dy);
493 for (QRect rect : rectsToScroll) {
494 if (repaintManager->bltRect(rect, dx, dy, widget: 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(p: -data.crect.topLeft());
510 invalidateBackingStore(r: overlappedExpose);
511 }
512 if (!childExpose.isEmpty()) {
513 childExpose.translate(p: -data.crect.topLeft());
514 repaintManager->markDirty(r: childExpose, widget: 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(p: data.crect.topLeft());
523
524 if (!parentExpose.isEmpty()) {
525 repaintManager->markDirty(r: parentExpose, widget: pw);
526 pd->isMoved = true;
527 }
528
529 if (childUpdatesEnabled) {
530 QRegion needsFlush(sourceRect);
531 needsFlush += destRect;
532 repaintManager->markNeedsFlush(widget: pw, region: needsFlush, topLevelOffset: 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(varName: "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(attribute: Qt::WA_WState_InPaintEvent);
553
554 if (!accelerateScroll) {
555 if (!overlappedRegion(rect: scrollRect.translated(p: data.crect.topLeft()), breakAfterFirst: true).isEmpty()) {
556 QRegion region(scrollRect);
557 subtractOpaqueSiblings(source&: region);
558 invalidateBackingStore(r: region);
559 }else {
560 invalidateBackingStore(r: 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: -dx, dy: -dy);
566
567 const QRegion overlappedExpose = (overlappedRegion(rect: scrollRect.translated(p: data.crect.topLeft())))
568 .translated(p: -data.crect.topLeft()) & clipR;
569 QRegion childExpose(scrollRect);
570
571 const qreal factor = QHighDpiScaling::factor(context: q->windowHandle());
572 if (overlappedExpose.isEmpty() || qFloor(v: factor) == factor) {
573 const QVector<QRect> rectsToScroll
574 = getSortedRectsToScroll(region: QRegion(sourceRect) - overlappedExpose, dx, dy);
575 for (const QRect &rect : rectsToScroll) {
576 if (repaintManager->bltRect(rect, dx, dy, widget: 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(r: 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(r: overlappedExpose);
602 if (!childExpose.isEmpty()) {
603 repaintManager->markDirty(r: childExpose, widget: 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(widget: q, region: destRect, topLevelOffset: 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(r: tlwRect))
623 return false; // We don't want to scroll junk.
624 return store->scroll(area: tlwRect, dx, dy);
625}
626
627// ---------------------------------------------------------------------------
628
629#ifndef QT_NO_OPENGL
630static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatformTextureList *widgetTextures, QVector<QWidget *> *nativeChildren)
631{
632 QWidgetPrivate *wd = QWidgetPrivate::get(w: widget);
633 if (wd->renderToTexture) {
634 QPlatformTextureList::Flags flags = wd->textureListFlags();
635 const QRect rect(widget->mapTo(tlw, QPoint()), widget->size());
636 widgetTextures->appendTexture(source: widget, textureId: wd->textureId(), geometry: rect, clipRect: wd->clipRect(), flags);
637 }
638
639 for (int i = 0; i < wd->children.size(); ++i) {
640 QWidget *w = qobject_cast<QWidget *>(o: wd->children.at(i));
641 // Stop at native widgets but store them. Stop at hidden widgets too.
642 if (w && !w->isWindow() && hasPlatformWindow(widget: w))
643 nativeChildren->append(t: w);
644 if (w && !w->isWindow() && !hasPlatformWindow(widget: w) && !w->isHidden() && QWidgetPrivate::get(w)->textureChildSeen)
645 findTextureWidgetsRecursively(tlw, widget: w, widgetTextures, nativeChildren);
646 }
647}
648
649static 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(w: 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, widgetTextures: tl.get(), nativeChildren: &nativeChildren);
658 // tl may be empty regardless of textureChildSeen if we have native or hidden children.
659 if (!tl->isEmpty())
660 QWidgetPrivate::get(w: tlw)->topData()->widgetTextures.push_back(x: std::move(tl));
661 // Native child widgets, if there was any, get their own separate QPlatformTextureList.
662 for (QWidget *ncw : qAsConst(t&: nativeChildren)) {
663 if (QWidgetPrivate::get(w: ncw)->textureChildSeen)
664 findAllTextureWidgetsRecursively(tlw, widget: ncw);
665 }
666 }
667}
668
669static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget)
670{
671 for (const auto &tl : QWidgetPrivate::get(w: 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(index: i));
675 if ((hasPlatformWindow(widget: w) && w == widget) || (!hasPlatformWindow(widget: w) && w->nativeParentWidget() == widget))
676 return tl.get();
677 }
678 }
679
680 if (QWidgetPrivate::get(w: 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(cap: QPlatformIntegration::SwitchableWidgetComposition);
689 if (!switchableWidgetComposition)
690 return qt_dummy_platformTextureList();
691 }
692
693 return nullptr;
694}
695
696#else
697
698static 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*/
716void 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(widget: exposedWidget)
724 || !exposedWidget->isVisible() || !exposedWidget->testAttribute(attribute: 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, widget: exposedWidget);
732 flush(widget: exposedWidget, region: 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(widget: exposedWidget, region: exposedRegion, topLevelOffset: offset);
741
742 if (syncAllowed())
743 paintAndFlush();
744}
745
746/*!
747 Synchronizes the backing store, i.e. dirty areas are repainted and flushed.
748*/
749void QWidgetRepaintManager::sync()
750{
751 qCInfo(lcWidgetPainting) << "Syncing dirty widgets";
752
753 updateRequestSent = false;
754 if (qt_widget_private(widget: 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(widget: dirtyWidgets.at(i));
764 dirtyWidgets.clear();
765 }
766 return;
767 }
768
769 if (syncAllowed())
770 paintAndFlush();
771}
772
773bool QWidgetPrivate::shouldDiscardSyncRequest() const
774{
775 Q_Q(const QWidget);
776 return !maybeTopData() || !q->testAttribute(attribute: Qt::WA_Mapped) || !q->isVisible();
777}
778
779bool 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(textureList: 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
804static bool isDrawnInEffect(const QWidget *w)
805{
806#if QT_CONFIG(graphicseffect)
807 do {
808 if (w->graphicsEffect())
809 return true;
810 w = w->parentWidget();
811 } while (w);
812#endif
813 return false;
814}
815
816void QWidgetRepaintManager::paintAndFlush()
817{
818 qCInfo(lcWidgetPainting) << "Painting and flushing dirty"
819 << "top level" << dirty << "and dirty widgets" << dirtyWidgets;
820
821 const bool updatesDisabled = !tlw->updatesEnabled();
822 bool repaintAllWidgets = false;
823
824 const QRect tlwRect = tlw->data->crect;
825 if (!updatesDisabled && store->size() != tlwRect.size()) {
826 if (hasStaticContents() && !store->size().isEmpty() ) {
827 // Repaint existing dirty area and newly visible area.
828 const QRect clipRect(QPoint(0, 0), store->size());
829 const QRegion staticRegion(staticContents(widget: nullptr, withinClipRect: clipRect));
830 QRegion newVisible(0, 0, tlwRect.width(), tlwRect.height());
831 newVisible -= staticRegion;
832 dirty += newVisible;
833 store->setStaticContents(staticRegion);
834 } else {
835 // Repaint everything.
836 dirty = QRegion(0, 0, tlwRect.width(), tlwRect.height());
837 for (int i = 0; i < dirtyWidgets.size(); ++i)
838 resetWidget(widget: dirtyWidgets.at(i));
839 dirtyWidgets.clear();
840 repaintAllWidgets = true;
841 }
842 }
843
844 if (store->size() != tlwRect.size())
845 store->resize(size: tlwRect.size());
846
847 if (updatesDisabled)
848 return;
849
850 // Contains everything that needs repaint.
851 QRegion toClean(dirty);
852
853 // Loop through all update() widgets and remove them from the list before they are
854 // painted (in case someone calls update() in paintEvent). If the widget is opaque
855 // and does not have transparent overlapping siblings, append it to the
856 // opaqueNonOverlappedWidgets list and paint it directly without composition.
857 QVarLengthArray<QWidget *, 32> opaqueNonOverlappedWidgets;
858 for (int i = 0; i < dirtyWidgets.size(); ++i) {
859 QWidget *w = dirtyWidgets.at(i);
860 QWidgetPrivate *wd = w->d_func();
861 if (wd->data.in_destructor)
862 continue;
863
864 // Clip with mask() and clipRect().
865 wd->dirty &= wd->clipRect();
866 wd->clipToEffectiveMask(region&: wd->dirty);
867
868 // Subtract opaque siblings and children.
869 bool hasDirtySiblingsAbove = false;
870 // We know for sure that the widget isn't overlapped if 'isMoved' is true.
871 if (!wd->isMoved)
872 wd->subtractOpaqueSiblings(source&: wd->dirty, hasDirtySiblingsAbove: &hasDirtySiblingsAbove);
873
874 // Make a copy of the widget's dirty region, to restore it in case there is an opaque
875 // render-to-texture child that completely covers the widget, because otherwise the
876 // render-to-texture child won't be visible, due to its parent widget not being redrawn
877 // with a proper blending mask.
878 const QRegion dirtyBeforeSubtractedOpaqueChildren = wd->dirty;
879
880 // Scrolled and moved widgets must draw all children.
881 if (!wd->isScrolled && !wd->isMoved)
882 wd->subtractOpaqueChildren(rgn&: wd->dirty, clipRect: w->rect());
883
884 if (wd->dirty.isEmpty() && wd->textureChildSeen)
885 wd->dirty = dirtyBeforeSubtractedOpaqueChildren;
886
887 if (wd->dirty.isEmpty()) {
888 resetWidget(widget: w);
889 continue;
890 }
891
892 const QRegion widgetDirty(w != tlw ? wd->dirty.translated(p: w->mapTo(tlw, QPoint()))
893 : wd->dirty);
894 toClean += widgetDirty;
895
896#if QT_CONFIG(graphicsview)
897 if (tlw->d_func()->extra->proxyWidget) {
898 resetWidget(widget: w);
899 continue;
900 }
901#endif
902
903 if (!isDrawnInEffect(w) && !hasDirtySiblingsAbove && wd->isOpaque
904 && !dirty.intersects(r: widgetDirty.boundingRect())) {
905 opaqueNonOverlappedWidgets.append(t: w);
906 } else {
907 resetWidget(widget: w);
908 dirty += widgetDirty;
909 }
910 }
911 dirtyWidgets.clear();
912
913#ifndef QT_NO_OPENGL
914 // Find all render-to-texture child widgets (including self).
915 // The search is cut at native widget boundaries, meaning that each native child widget
916 // has its own list for the subtree below it.
917 QTLWExtra *tlwExtra = tlw->d_func()->topData();
918 tlwExtra->widgetTextures.clear();
919 findAllTextureWidgetsRecursively(tlw, widget: tlw);
920 qt_window_private(window: tlw->windowHandle())->compositing = false; // will get updated in flush()
921#endif
922
923 if (toClean.isEmpty()) {
924 // Nothing to repaint. However renderToTexture widgets are handled
925 // specially, they are not in the regular dirty list, in order to
926 // prevent triggering unnecessary backingstore painting when only the
927 // OpenGL content changes. Check if we have such widgets in the special
928 // dirty list.
929 QVarLengthArray<QWidget *, 16> paintPending;
930 const int numPaintPending = dirtyRenderToTextureWidgets.count();
931 paintPending.reserve(size: numPaintPending);
932 for (int i = 0; i < numPaintPending; ++i) {
933 QWidget *w = dirtyRenderToTextureWidgets.at(i);
934 paintPending << w;
935 resetWidget(widget: w);
936 }
937 dirtyRenderToTextureWidgets.clear();
938 for (int i = 0; i < numPaintPending; ++i) {
939 QWidget *w = paintPending[i];
940 w->d_func()->sendPaintEvent(toBePainted: w->rect());
941 if (w != tlw) {
942 QWidget *npw = w->nativeParentWidget();
943 if (hasPlatformWindow(widget: w) || (npw && npw != tlw)) {
944 if (!hasPlatformWindow(widget: w))
945 w = npw;
946 markNeedsFlush(widget: w);
947 }
948 }
949 }
950
951 // We might have newly exposed areas on the screen if this function was
952 // called from sync(QWidget *, QRegion)), so we have to make sure those
953 // are flushed. We also need to composite the renderToTexture widgets.
954 flush();
955
956 return;
957 }
958
959#ifndef QT_NO_OPENGL
960 for (const auto &tl : tlwExtra->widgetTextures) {
961 for (int i = 0; i < tl->count(); ++i) {
962 QWidget *w = static_cast<QWidget *>(tl->source(index: i));
963 if (dirtyRenderToTextureWidgets.contains(t: w)) {
964 const QRect rect = tl->geometry(index: i); // mapped to the tlw already
965 // Set a flag to indicate that the paint event for this
966 // render-to-texture widget must not to be optimized away.
967 w->d_func()->renderToTextureReallyDirty = 1;
968 dirty += rect;
969 toClean += rect;
970 }
971 }
972 }
973 for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i)
974 resetWidget(widget: dirtyRenderToTextureWidgets.at(i));
975 dirtyRenderToTextureWidgets.clear();
976#endif
977
978#if QT_CONFIG(graphicsview)
979 if (tlw->d_func()->extra->proxyWidget) {
980 updateStaticContentsSize();
981 dirty = QRegion();
982 updateRequestSent = false;
983 for (const QRect &rect : toClean)
984 tlw->d_func()->extra->proxyWidget->update(rect);
985 return;
986 }
987#endif
988
989 store->beginPaint(toClean);
990
991 // Must do this before sending any paint events because
992 // the size may change in the paint event.
993 updateStaticContentsSize();
994 const QRegion dirtyCopy(dirty);
995 dirty = QRegion();
996 updateRequestSent = false;
997
998 // Paint opaque non overlapped widgets.
999 for (int i = 0; i < opaqueNonOverlappedWidgets.size(); ++i) {
1000 QWidget *w = opaqueNonOverlappedWidgets[i];
1001 QWidgetPrivate *wd = w->d_func();
1002
1003 QWidgetPrivate::DrawWidgetFlags flags = QWidgetPrivate::DrawRecursive;
1004 // Scrolled and moved widgets must draw all children.
1005 if (!wd->isScrolled && !wd->isMoved)
1006 flags |= QWidgetPrivate::DontDrawOpaqueChildren;
1007 if (w == tlw)
1008 flags |= QWidgetPrivate::DrawAsRoot;
1009
1010 QRegion toBePainted(wd->dirty);
1011 resetWidget(widget: w);
1012
1013 QPoint offset;
1014 if (w != tlw)
1015 offset += w->mapTo(tlw, QPoint());
1016 wd->drawWidget(pdev: store->paintDevice(), rgn: toBePainted, offset, flags, sharedPainter: nullptr, repaintManager: this);
1017 }
1018
1019 // Paint the rest with composition.
1020 if (repaintAllWidgets || !dirtyCopy.isEmpty()) {
1021 QWidgetPrivate::DrawWidgetFlags flags = QWidgetPrivate::DrawAsRoot | QWidgetPrivate::DrawRecursive;
1022 tlw->d_func()->drawWidget(pdev: store->paintDevice(), rgn: dirtyCopy, offset: QPoint(), flags, sharedPainter: nullptr, repaintManager: this);
1023 }
1024
1025 store->endPaint();
1026
1027 flush();
1028}
1029
1030/*!
1031 Marks the \a region of the \a widget as needing a flush. The \a region will be copied from
1032 the backing store to the \a widget's native parent next time flush() is called.
1033
1034 Paint on screen widgets are ignored.
1035*/
1036void QWidgetRepaintManager::markNeedsFlush(QWidget *widget, const QRegion &region, const QPoint &topLevelOffset)
1037{
1038 if (!widget || widget->d_func()->shouldPaintOnScreen() || region.isEmpty())
1039 return;
1040
1041 if (widget == tlw) {
1042 // Top-level (native)
1043 qCInfo(lcWidgetPainting) << "Marking" << region << "of top level"
1044 << widget << "as needing flush";
1045 topLevelNeedsFlush += region;
1046 } else if (!hasPlatformWindow(widget) && !widget->isWindow()) {
1047 QWidget *nativeParent = widget->nativeParentWidget();
1048 qCInfo(lcWidgetPainting) << "Marking" << region << "of"
1049 << widget << "as needing flush in" << nativeParent
1050 << "at offset" << topLevelOffset;
1051 if (nativeParent == tlw) {
1052 // Alien widgets with the top-level as the native parent (common case)
1053 topLevelNeedsFlush += region.translated(p: topLevelOffset);
1054 } else {
1055 // Alien widgets with native parent != tlw
1056 const QPoint nativeParentOffset = widget->mapTo(nativeParent, QPoint());
1057 markNeedsFlush(widget: nativeParent, region: region.translated(p: nativeParentOffset));
1058 }
1059 } else {
1060 // Native child widgets
1061 qCInfo(lcWidgetPainting) << "Marking" << region
1062 << "of native child" << widget << "as needing flush";
1063 markNeedsFlush(widget, region);
1064 }
1065}
1066
1067void QWidgetRepaintManager::markNeedsFlush(QWidget *widget, const QRegion &region)
1068{
1069 if (!widget)
1070 return;
1071
1072 auto *widgetPrivate = qt_widget_private(widget);
1073 if (!widgetPrivate->needsFlush)
1074 widgetPrivate->needsFlush = new QRegion;
1075
1076 *widgetPrivate->needsFlush += region;
1077
1078 if (!needsFlushWidgets.contains(t: widget))
1079 needsFlushWidgets.append(t: widget);
1080}
1081
1082/*!
1083 Flushes the contents of the backing store into the top-level widget.
1084*/
1085void QWidgetRepaintManager::flush()
1086{
1087 qCInfo(lcWidgetPainting) << "Flushing top level"
1088 << topLevelNeedsFlush << "and children" << needsFlushWidgets;
1089
1090 const bool hasNeedsFlushWidgets = !needsFlushWidgets.isEmpty();
1091 bool flushed = false;
1092
1093 // Flush the top level widget
1094 if (!topLevelNeedsFlush.isEmpty()) {
1095 flush(widget: tlw, region: topLevelNeedsFlush, widgetTextures: widgetTexturesFor(tlw, widget: tlw));
1096 topLevelNeedsFlush = QRegion();
1097 flushed = true;
1098 }
1099
1100 // Render-to-texture widgets are not in topLevelNeedsFlush so flush if we have not done it above.
1101 if (!flushed && !hasNeedsFlushWidgets) {
1102#ifndef QT_NO_OPENGL
1103 if (!tlw->d_func()->topData()->widgetTextures.empty()) {
1104 if (QPlatformTextureList *widgetTextures = widgetTexturesFor(tlw, widget: tlw))
1105 flush(widget: tlw, region: QRegion(), widgetTextures);
1106 }
1107#endif
1108 }
1109
1110 if (!hasNeedsFlushWidgets)
1111 return;
1112
1113 for (QWidget *w : qExchange(t&: needsFlushWidgets, newValue: {})) {
1114 QWidgetPrivate *wd = w->d_func();
1115 Q_ASSERT(wd->needsFlush);
1116 QPlatformTextureList *widgetTexturesForNative = wd->textureChildSeen ? widgetTexturesFor(tlw, widget: w) : nullptr;
1117 flush(widget: w, region: *wd->needsFlush, widgetTextures: widgetTexturesForNative);
1118 *wd->needsFlush = QRegion();
1119 }
1120}
1121
1122/*
1123 Flushes the contents of the backingstore into the screen area of \a widget.
1124
1125 \a region is the region to be updated in \a widget coordinates.
1126 */
1127void QWidgetRepaintManager::flush(QWidget *widget, const QRegion &region, QPlatformTextureList *widgetTextures)
1128{
1129#ifdef QT_NO_OPENGL
1130 Q_UNUSED(widgetTextures);
1131 Q_ASSERT(!region.isEmpty());
1132#else
1133 Q_ASSERT(!region.isEmpty() || widgetTextures);
1134#endif
1135 Q_ASSERT(widget);
1136 Q_ASSERT(tlw);
1137
1138 if (tlw->testAttribute(attribute: Qt::WA_DontShowOnScreen) || widget->testAttribute(attribute: Qt::WA_DontShowOnScreen))
1139 return;
1140
1141 // Foreign Windows do not have backing store content and must not be flushed
1142 if (QWindow *widgetWindow = widget->windowHandle()) {
1143 if (widgetWindow->type() == Qt::ForeignWindow)
1144 return;
1145 }
1146
1147 qCInfo(lcWidgetPainting) << "Flushing" << region << "of" << widget;
1148
1149 static bool fpsDebug = qEnvironmentVariableIntValue(varName: "QT_DEBUG_FPS");
1150 if (fpsDebug) {
1151 if (!perfFrames++)
1152 perfTime.start();
1153 if (perfTime.elapsed() > 5000) {
1154 double fps = double(perfFrames * 1000) / perfTime.restart();
1155 qDebug(msg: "FPS: %.1f\n", fps);
1156 perfFrames = 0;
1157 }
1158 }
1159
1160 QPoint offset;
1161 if (widget != tlw)
1162 offset += widget->mapTo(tlw, QPoint());
1163
1164 QRegion effectiveRegion = region;
1165#ifndef QT_NO_OPENGL
1166 const bool compositionWasActive = widget->d_func()->renderToTextureComposeActive;
1167 if (!widgetTextures) {
1168 widget->d_func()->renderToTextureComposeActive = false;
1169 // Detect the case of falling back to the normal flush path when no
1170 // render-to-texture widgets are visible anymore. We will force one
1171 // last flush to go through the OpenGL-based composition to prevent
1172 // artifacts. The next flush after this one will use the normal path.
1173 if (compositionWasActive)
1174 widgetTextures = qt_dummy_platformTextureList;
1175 } else {
1176 widget->d_func()->renderToTextureComposeActive = true;
1177 }
1178 // When changing the composition status, make sure the dirty region covers
1179 // the entire widget. Just having e.g. the shown/hidden render-to-texture
1180 // widget's area marked as dirty is incorrect when changing flush paths.
1181 if (compositionWasActive != widget->d_func()->renderToTextureComposeActive)
1182 effectiveRegion = widget->rect();
1183
1184 // re-test since we may have been forced to this path via the dummy texture list above
1185 if (widgetTextures) {
1186 qt_window_private(window: tlw->windowHandle())->compositing = true;
1187 widget->window()->d_func()->sendComposeStatus(w: widget->window(), end: false);
1188 // A window may have alpha even when the app did not request
1189 // WA_TranslucentBackground. Therefore the compositor needs to know whether the app intends
1190 // to rely on translucency, in order to decide if it should clear to transparent or opaque.
1191 const bool translucentBackground = widget->testAttribute(attribute: Qt::WA_TranslucentBackground);
1192 store->handle()->composeAndFlush(window: widget->windowHandle(), region: effectiveRegion, offset,
1193 textures: widgetTextures, translucentBackground);
1194 widget->window()->d_func()->sendComposeStatus(w: widget->window(), end: true);
1195 } else
1196#endif
1197 store->flush(region: effectiveRegion, window: widget->windowHandle(), offset);
1198}
1199
1200// ---------------------------------------------------------------------------
1201
1202void QWidgetRepaintManager::addStaticWidget(QWidget *widget)
1203{
1204 if (!widget)
1205 return;
1206
1207 Q_ASSERT(widget->testAttribute(Qt::WA_StaticContents));
1208 if (!staticWidgets.contains(t: widget))
1209 staticWidgets.append(t: widget);
1210}
1211
1212// Move the reparented widget and all its static children from this backing store
1213// to the new backing store if reparented into another top-level / backing store.
1214void QWidgetRepaintManager::moveStaticWidgets(QWidget *reparented)
1215{
1216 Q_ASSERT(reparented);
1217 QWidgetRepaintManager *newPaintManager = reparented->d_func()->maybeRepaintManager();
1218 if (newPaintManager == this)
1219 return;
1220
1221 int i = 0;
1222 while (i < staticWidgets.size()) {
1223 QWidget *w = staticWidgets.at(i);
1224 if (reparented == w || reparented->isAncestorOf(child: w)) {
1225 staticWidgets.removeAt(i);
1226 if (newPaintManager)
1227 newPaintManager->addStaticWidget(widget: w);
1228 } else {
1229 ++i;
1230 }
1231 }
1232}
1233
1234void QWidgetRepaintManager::removeStaticWidget(QWidget *widget)
1235{
1236 staticWidgets.removeAll(t: widget);
1237}
1238
1239bool QWidgetRepaintManager::hasStaticContents() const
1240{
1241#if defined(Q_OS_WIN)
1242 return !staticWidgets.isEmpty();
1243#else
1244 return !staticWidgets.isEmpty() && false;
1245#endif
1246}
1247
1248/*!
1249 Returns the static content inside the \a parent if non-zero; otherwise the static content
1250 for the entire backing store is returned. The content will be clipped to \a withinClipRect
1251 if non-empty.
1252*/
1253QRegion QWidgetRepaintManager::staticContents(QWidget *parent, const QRect &withinClipRect) const
1254{
1255 if (!parent && tlw->testAttribute(attribute: Qt::WA_StaticContents)) {
1256 QRect backingstoreRect(QPoint(0, 0), store->size());
1257 if (!withinClipRect.isEmpty())
1258 backingstoreRect &= withinClipRect;
1259 return QRegion(backingstoreRect);
1260 }
1261
1262 QRegion region;
1263 if (parent && parent->d_func()->children.isEmpty())
1264 return region;
1265
1266 const bool clipToRect = !withinClipRect.isEmpty();
1267 const int count = staticWidgets.count();
1268 for (int i = 0; i < count; ++i) {
1269 QWidget *w = staticWidgets.at(i);
1270 QWidgetPrivate *wd = w->d_func();
1271 if (!wd->isOpaque || !wd->extra || wd->extra->staticContentsSize.isEmpty()
1272 || !w->isVisible() || (parent && !parent->isAncestorOf(child: w))) {
1273 continue;
1274 }
1275
1276 QRect rect(0, 0, wd->extra->staticContentsSize.width(), wd->extra->staticContentsSize.height());
1277 const QPoint offset = w->mapTo(parent ? parent : tlw, QPoint());
1278 if (clipToRect)
1279 rect &= withinClipRect.translated(p: -offset);
1280 if (rect.isEmpty())
1281 continue;
1282
1283 rect &= wd->clipRect();
1284 if (rect.isEmpty())
1285 continue;
1286
1287 QRegion visible(rect);
1288 wd->clipToEffectiveMask(region&: visible);
1289 if (visible.isEmpty())
1290 continue;
1291 wd->subtractOpaqueSiblings(source&: visible, hasDirtySiblingsAbove: nullptr, /*alsoNonOpaque=*/true);
1292
1293 visible.translate(p: offset);
1294 region += visible;
1295 }
1296
1297 return region;
1298}
1299
1300void QWidgetRepaintManager::updateStaticContentsSize()
1301{
1302 for (int i = 0; i < staticWidgets.size(); ++i) {
1303 QWidgetPrivate *wd = staticWidgets.at(i)->d_func();
1304 if (!wd->extra)
1305 wd->createExtra();
1306 wd->extra->staticContentsSize = wd->data.crect.size();
1307 }
1308}
1309
1310// ---------------------------------------------------------------------------
1311
1312bool QWidgetRepaintManager::isDirty() const
1313{
1314 return !(dirtyWidgets.isEmpty() && dirty.isEmpty() && dirtyRenderToTextureWidgets.isEmpty());
1315}
1316
1317/*!
1318 Invalidates the backing store when the widget is resized.
1319 Static areas are never invalidated unless absolutely needed.
1320*/
1321void QWidgetPrivate::invalidateBackingStore_resizeHelper(const QPoint &oldPos, const QSize &oldSize)
1322{
1323 Q_Q(QWidget);
1324 Q_ASSERT(!q->isWindow());
1325 Q_ASSERT(q->parentWidget());
1326
1327 const bool staticContents = q->testAttribute(attribute: Qt::WA_StaticContents);
1328 const bool sizeDecreased = (data.crect.width() < oldSize.width())
1329 || (data.crect.height() < oldSize.height());
1330
1331 const QPoint offset(data.crect.x() - oldPos.x(), data.crect.y() - oldPos.y());
1332 const bool parentAreaExposed = !offset.isNull() || sizeDecreased;
1333 const QRect newWidgetRect(q->rect());
1334 const QRect oldWidgetRect(0, 0, oldSize.width(), oldSize.height());
1335
1336 if (!staticContents || graphicsEffect) {
1337 QRegion staticChildren;
1338 QWidgetRepaintManager *bs = nullptr;
1339 if (offset.isNull() && (bs = maybeRepaintManager()))
1340 staticChildren = bs->staticContents(parent: q, withinClipRect: oldWidgetRect);
1341 const bool hasStaticChildren = !staticChildren.isEmpty();
1342
1343 if (hasStaticChildren) {
1344 QRegion dirty(newWidgetRect);
1345 dirty -= staticChildren;
1346 invalidateBackingStore(r: dirty);
1347 } else {
1348 // Entire widget needs repaint.
1349 invalidateBackingStore(r: newWidgetRect);
1350 }
1351
1352 if (!parentAreaExposed)
1353 return;
1354
1355 // Invalidate newly exposed area of the parent.
1356 if (!graphicsEffect && extra && extra->hasMask) {
1357 QRegion parentExpose(extra->mask.translated(p: oldPos));
1358 parentExpose &= QRect(oldPos, oldSize);
1359 if (hasStaticChildren)
1360 parentExpose -= data.crect; // Offset is unchanged, safe to do this.
1361 q->parentWidget()->d_func()->invalidateBackingStore(r: parentExpose);
1362 } else {
1363 if (hasStaticChildren && !graphicsEffect) {
1364 QRegion parentExpose(QRect(oldPos, oldSize));
1365 parentExpose -= data.crect; // Offset is unchanged, safe to do this.
1366 q->parentWidget()->d_func()->invalidateBackingStore(r: parentExpose);
1367 } else {
1368 q->parentWidget()->d_func()->invalidateBackingStore(r: effectiveRectFor(rect: QRect(oldPos, oldSize)));
1369 }
1370 }
1371 return;
1372 }
1373
1374 // Move static content to its new position.
1375 if (!offset.isNull()) {
1376 if (sizeDecreased) {
1377 const QSize minSize(qMin(a: oldSize.width(), b: data.crect.width()),
1378 qMin(a: oldSize.height(), b: data.crect.height()));
1379 moveRect(rect: QRect(oldPos, minSize), dx: offset.x(), dy: offset.y());
1380 } else {
1381 moveRect(rect: QRect(oldPos, oldSize), dx: offset.x(), dy: offset.y());
1382 }
1383 }
1384
1385 // Invalidate newly visible area of the widget.
1386 if (!sizeDecreased || !oldWidgetRect.contains(r: newWidgetRect)) {
1387 QRegion newVisible(newWidgetRect);
1388 newVisible -= oldWidgetRect;
1389 invalidateBackingStore(r: newVisible);
1390 }
1391
1392 if (!parentAreaExposed)
1393 return;
1394
1395 // Invalidate newly exposed area of the parent.
1396 const QRect oldRect(oldPos, oldSize);
1397 if (extra && extra->hasMask) {
1398 QRegion parentExpose(oldRect);
1399 parentExpose &= extra->mask.translated(p: oldPos);
1400 parentExpose -= (extra->mask.translated(p: data.crect.topLeft()) & data.crect);
1401 q->parentWidget()->d_func()->invalidateBackingStore(r: parentExpose);
1402 } else {
1403 QRegion parentExpose(oldRect);
1404 parentExpose -= data.crect;
1405 q->parentWidget()->d_func()->invalidateBackingStore(r: parentExpose);
1406 }
1407}
1408
1409QT_END_NAMESPACE
1410
1411#include "qwidgetrepaintmanager.moc"
1412

source code of qtbase/src/widgets/kernel/qwidgetrepaintmanager.cpp