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

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