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