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