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