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