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