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