diff options
author | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2021-12-06 18:04:26 +0100 |
---|---|---|
committer | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2021-12-08 23:06:42 +0000 |
commit | b886a7ca65d566538f81b369f548943cd5e8ce08 (patch) | |
tree | 70d05707ea52a74ca103ac3e482c83149937bf2a /tests/auto/widgets/kernel | |
parent | 9ad00e4b3f4d71f31ba041af623741522a221ffc (diff) |
Add unit test for moving of opaque widgets
Expose QWidgetRepaintManager's data structures so that we can write
unit tests, and verify that they are correct after moving opaque
widgets (which triggers the accelerated move code path).
Improve the compareWidget logic to not rely on screen grabbing
(which requires permissions), but instead use QPlatformBackingStore's
toImage function, which is faster and more reliable, and also doesn't
require us to show the UI we want to grab full screen in order to
avoid issues with overlapping windows etc.
Change-Id: Iff2ea419f03a390ab6baca26814fef6ff45f7470
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'tests/auto/widgets/kernel')
-rw-r--r-- | tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp | 206 |
1 files changed, 171 insertions, 35 deletions
diff --git a/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp b/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp index 1c90583b3d..2031c68708 100644 --- a/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp +++ b/tests/auto/widgets/kernel/qwidgetrepaintmanager/tst_qwidgetrepaintmanager.cpp @@ -35,6 +35,8 @@ #include <private/qhighdpiscaling_p.h> #include <private/qwidget_p.h> +#include <private/qwidgetrepaintmanager_p.h> +#include <qpa/qplatformbackingstore.h> //#define MANUAL_DEBUG @@ -115,6 +117,11 @@ private: class Draggable : public OpaqueWidget { public: + Draggable(QWidget *parent = nullptr) + : OpaqueWidget(Qt::white, parent) + { + } + Draggable(const QColor &col, QWidget *parent = nullptr) : OpaqueWidget(col, parent) { @@ -187,6 +194,10 @@ public: yellowChild = new Draggable(Qt::yellow, this); yellowChild->setObjectName("yellowChild"); + nakedChild = new Draggable(this); + nakedChild->move(300, 0); + nakedChild->setObjectName("nakedChild"); + bar = new OpaqueWidget(Qt::darkGray, this); bar->setObjectName("bar"); } @@ -195,6 +206,7 @@ public: QWidget *redChild; QWidget *greenChild; QWidget *yellowChild; + QWidget *nakedChild; QWidget *bar; QSize sizeHint() const override { return QSize(400, 400); } @@ -215,6 +227,7 @@ public: tst_QWidgetRepaintManager(); public slots: + void initTestCase(); void cleanup(); private slots: @@ -223,51 +236,63 @@ private slots: void opaqueChildren(); void staticContents(); void scroll(); - void moveWithOverlap(); +#if defined(QT_BUILD_INTERNAL) + void scrollWithOverlap(); void overlappedRegion(); + void fastMove(); + void moveAccross(); + void moveInOutOverlapped(); protected: /* - This helper compares the widget as rendered on screen with the widget - as rendered via QWidget::grab. Since the latter always produces a fully - rendered image, it allows us to identify update issues in QWidgetRepaintManager - which would be visible in the former. + This helper compares the widget as rendered into the backingstore with the widget + as rendered via QWidget::grab. The latter always produces a fully rendered image, + so differences indicate bugs in QWidgetRepaintManager's or QWidget's painting code. */ bool compareWidget(QWidget *w) { - QScreen *screen = w->screen(); - const QRect screenGeometry = screen->geometry(); - QPoint globalPos = w->mapToGlobal(QPoint(0, 0)); - if (globalPos.x() >= screenGeometry.width()) - globalPos.rx() -= screenGeometry.x(); - if (globalPos.y() >= screenGeometry.height()) - globalPos.ry() -= screenGeometry.y(); - - QImage systemScreenshot; - QImage qtScreenshot; - bool result = QTest::qWaitFor([&]{ - if (w->isFullScreen()) - systemScreenshot = screen->grabWindow().toImage(); - else - systemScreenshot = screen->grabWindow(w->window()->winId(), - globalPos.x(), globalPos.y(), - w->width(), w->height()).toImage(); - systemScreenshot = systemScreenshot.convertToFormat(QImage::Format_RGB32); - qtScreenshot = w->grab().toImage().convertToFormat(systemScreenshot.format()); - return systemScreenshot == qtScreenshot; - }); + if (!waitForFlush(w)) { + qWarning() << "Widget" << w << "failed to flush"; + return false; + } + QBackingStore *backingStore = w->window()->backingStore(); + Q_ASSERT(backingStore && backingStore->handle()); + QPlatformBackingStore *platformBackingStore = backingStore->handle(); + + QImage backingstoreContent = platformBackingStore->toImage(); + if (!w->isWindow()) { + const qreal dpr = w->devicePixelRatioF(); + const QPointF offset = w->mapTo(w->window(), QPointF(0, 0)) * dpr; + backingstoreContent = backingstoreContent.copy(offset.x(), offset.y(), w->width() * dpr, w->height() * dpr); + } + const QImage widgetRender = w->grab().toImage().convertToFormat(backingstoreContent.format()); + + const bool result = backingstoreContent == widgetRender; #ifdef MANUAL_DEBUG if (!result) { - systemScreenshot.save(QString("/tmp/system_%1_%2.png").arg(QTest::currentTestFunction(), QTest::currentDataTag())); - qtScreenshot.save(QString("/tmp/qt_%1_%2.png").arg(QTest::currentTestFunction(), QTest::currentDataTag())); + backingstoreContent.save(QString("/tmp/backingstore_%1_%2.png").arg(QTest::currentTestFunction(), QTest::currentDataTag())); + widgetRender.save(QString("/tmp/grab_%1_%2.png").arg(QTest::currentTestFunction(), QTest::currentDataTag())); } #endif return result; }; + QRegion dirtyRegion(QWidget *widget) const + { + return QWidgetPrivate::get(widget)->dirty; + } + bool waitForFlush(QWidget *widget) const + { + auto *repaintManager = QWidgetPrivate::get(widget->window())->maybeRepaintManager(); + return QTest::qWaitFor([repaintManager]{ return !repaintManager->isDirty(); } ); + }; +#endif // QT_BUILD_INTERNAL + + private: const int m_fuzz; + bool m_implementsScroll = false; }; tst_QWidgetRepaintManager::tst_QWidgetRepaintManager() : @@ -275,6 +300,16 @@ tst_QWidgetRepaintManager::tst_QWidgetRepaintManager() : { } +void tst_QWidgetRepaintManager::initTestCase() +{ + QWidget widget; + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&widget)); + + m_implementsScroll = widget.backingStore()->handle()->scroll(QRegion(widget.rect()), 1, 1); + qDebug() << QGuiApplication::platformName() << "QPA backend implements scroll:" << m_implementsScroll; +} + void tst_QWidgetRepaintManager::cleanup() { QVERIFY(QApplication::topLevelWidgets().isEmpty()); @@ -356,7 +391,7 @@ void tst_QWidgetRepaintManager::opaqueChildren() child1->move(20, 30); QVERIFY(widget.waitForPainted()); QCOMPARE(widget.takePaintedRegions(), QRegion(20, 20, child1->width(), 10)); - if (QGuiApplication::platformName() == "cocoa") + if (!m_implementsScroll) QEXPECT_FAIL("", "child1 shouldn't get painted, we can just move the area of the backingstore", Continue); QCOMPARE(child1->takePaintedRegions(), QRegion()); } @@ -393,7 +428,7 @@ void tst_QWidgetRepaintManager::scroll() widget.scroll(10, 0); QVERIFY(widget.waitForPainted()); - if (QGuiApplication::platformName() == "cocoa") + if (!m_implementsScroll) QEXPECT_FAIL("", "This should just repaint the newly exposed region", Continue); QCOMPARE(widget.takePaintedRegions(), QRegion(0, 0, 10, widget.height())); @@ -411,19 +446,21 @@ void tst_QWidgetRepaintManager::scroll() child->setAttribute(Qt::WA_OpaquePaintEvent); child->scroll(10, 0); QVERIFY(child->waitForPainted()); - if (QStringList{"cocoa", "android"}.contains(QGuiApplication::platformName())) + if (!m_implementsScroll) QEXPECT_FAIL("", "This should just repaint the newly exposed region", Continue); QCOMPARE(child->takePaintedRegions(), QRegion(0, 0, 10, child->height())); QCOMPARE(widget.takePaintedRegions(), QRegion()); } +#if defined(QT_BUILD_INTERNAL) + /*! Verify that overlapping children are repainted correctly when a widget is moved (via a scroll area) for such a distance that none of the old area is still visible. QTBUG-26269 */ -void tst_QWidgetRepaintManager::moveWithOverlap() +void tst_QWidgetRepaintManager::scrollWithOverlap() { if (QStringList{"android"}.contains(QGuiApplication::platformName())) QSKIP("This test fails on Android"); @@ -446,6 +483,8 @@ void tst_QWidgetRepaintManager::moveWithOverlap() m_topWidget->setPalette(QPalette(Qt::red)); m_topWidget->setAutoFillBackground(true); m_topWidget->resize(300, 200); + + resize(600, 300); } void resizeEvent(QResizeEvent *e) override @@ -466,14 +505,14 @@ void tst_QWidgetRepaintManager::moveWithOverlap() }; MainWindow w; - w.showFullScreen(); + w.show(); QVERIFY(QTest::qWaitForWindowActive(&w)); bool result = compareWidget(w.topWidget()); // if this fails already, then the system we test on can't compare screenshots from grabbed widgets, - // and we have to skip this test. Possible reasons are that showing the window took too long, differences - // in surface formats, or unrelated bugs in QScreen::grabWindow. + // and we have to skip this test. Possible reasons are differences in surface formats or DPI, or + // unrelated bugs in QPlatformBackingStore::toImage or QWidget::grab. if (!result) QSKIP("Cannot compare QWidget::grab with QScreen::grabWindow on this machine"); @@ -560,5 +599,102 @@ void tst_QWidgetRepaintManager::overlappedRegion() QTRY_VERIFY(!overlap.isEmpty()); } +void tst_QWidgetRepaintManager::fastMove() +{ + TestScene scene; + scene.show(); + QVERIFY(QTest::qWaitForWindowExposed(&scene)); + + QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(&scene)->maybeRepaintManager(); + QVERIFY(repaintManager->dirtyRegion().isEmpty()); + + // moving yellow; nothing obscured + scene.yellowChild->move(QPoint(25, 0)); + QVERIFY(repaintManager->dirtyRegion().isEmpty()); // fast move + if (m_implementsScroll) { + QCOMPARE(repaintManager->dirtyWidgetList(), QList<QWidget *>() << &scene); + QVERIFY(dirtyRegion(scene.yellowChild).isEmpty()); + } else { + QCOMPARE(repaintManager->dirtyWidgetList(), QList<QWidget *>() << scene.yellowChild << &scene); + QCOMPARE(dirtyRegion(scene.yellowChild), QRect(0, 0, 100, 100)); + } + QCOMPARE(dirtyRegion(&scene), QRect(0, 0, 25, 100)); + QVERIFY(compareWidget(&scene)); +} + +void tst_QWidgetRepaintManager::moveAccross() +{ + TestScene scene; + scene.show(); + QVERIFY(QTest::qWaitForWindowExposed(&scene)); + + QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(&scene)->maybeRepaintManager(); + QVERIFY(repaintManager->dirtyRegion().isEmpty()); + + for (int i = 0; i < 4; ++i) { + scene.greenChild->move(scene.greenChild->pos() + QPoint(25, 0)); + waitForFlush(&scene); + } + QVERIFY(compareWidget(&scene)); + + for (int i = 0; i < 16; ++i) { + scene.redChild->move(scene.redChild->pos() + QPoint(25, 0)); + waitForFlush(&scene); + } + QVERIFY(compareWidget(&scene)); + + for (int i = 0; i < qMin(scene.area->width(), scene.area->height()); i += 25) { + scene.yellowChild->move(scene.yellowChild->pos() + QPoint(25, 25)); + waitForFlush(&scene); + } + QVERIFY(compareWidget(&scene)); +} + +void tst_QWidgetRepaintManager::moveInOutOverlapped() +{ + TestScene scene; + scene.show(); + QVERIFY(QTest::qWaitForWindowExposed(&scene)); + + QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(&scene)->maybeRepaintManager(); + QVERIFY(repaintManager->dirtyRegion().isEmpty()); + + // yellow out + scene.yellowChild->move(QPoint(-100, 0)); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // invalid dest rect + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); + + // yellow in, obscured by bar + scene.yellowChild->move(QPoint(scene.width() / 2, scene.height() / 2)); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // invalid source rect + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); + + // green out + scene.greenChild->move(QPoint(-100, 0)); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // invalid dest rect + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); + + // green back in, obscured by bar + scene.greenChild->move(QPoint(scene.area->width() / 2 - 50, scene.area->height() / 2 - 50)); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // invalid source rect + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); + + // red back under green + scene.redChild->move(scene.greenChild->pos()); + QVERIFY(!repaintManager->dirtyRegion().isEmpty()); // destination rect obscured + QVERIFY(repaintManager->dirtyWidgetList().isEmpty()); + QVERIFY(waitForFlush(&scene)); + QVERIFY(compareWidget(&scene)); +} +#endif //# defined(QT_BUILD_INTERNAL) + QTEST_MAIN(tst_QWidgetRepaintManager) #include "tst_qwidgetrepaintmanager.moc" |